Step-by-Step Guide to Building a Basic SDL UI Control: UICheckBox

sdl-ui-control-dev-checkbox

1. Introduction

The CheckBox is one of the most common selection controls in graphical user interfaces. It’s typically used to represent a binary state, such as “on/off,” “yes/no,” or “selected/unselected.”

In a low-level, cross-platform library like SDL, there are no pre-built UI controls. This means we have to manually draw the rectangular box, render the text, detect click events, and manage the internal state.

In this article, we’ll build a UICheckBox control. As a new component in our SDL Basic UI Controls series, it will inherit from UIControl to maintain a consistent interface style with UIButton and UITextEdit.

2. Functional Requirements Analysis

A basic checkbox needs the following capabilities:

  • Display a square box.
  • Show a text label to the right of the box.
  • Display a checkmark inside the box for the “checked” state and a blank space for the “unchecked” state.
  • Support mouse clicks to toggle its state.
  • Trigger an event (like ET_CHECKCHANGED) when its state changes.

Additional details for potential future expansion:

  • Support a disabled (grayed-out) state.
  • Support keyboard toggling.
  • Allow an icon to replace the checkmark.

3. Class Declaration

To fit within the UIControl framework, our class inherits from it and provides interfaces for rendering and event handling.

C++

#pragma once
#include "UIControl.h"

class UICheckBox : public UIControl
{
public:
    UICheckBox();
    UICheckBox(const std::string& _text);
    virtual ~UICheckBox() = default;

    void Render() override;
    bool Handle(SDL_Event* e) override;
    void OnTextChanged(const std::string& old) override;

    bool IsChecked() const { return m_ischecked; }
    void SetCheck(bool _check);

protected:
    bool m_ischecked{ false };
};

As you can see:

  • m_ischecked stores the checked state.
  • SetCheck() and IsChecked() provide control and query functions.
  • We override Render() for drawing, Handle() for interaction, and OnTextChanged() to recalculate the size.

4. Constructor and Initialization

C++

UICheckBox::UICheckBox()
    : UIControl()
{
}

UICheckBox::UICheckBox(const std::string& _text)
    : UIControl()
{
    m_text = _text;
    OnTextChanged("");
}

We support two types of constructors:

  • A parameterless constructor for a blank checkbox.
  • A constructor that takes a string to set the label text directly and calls OnTextChanged() to adapt the size.

5. Rendering Logic

C++

void UICheckBox::Render()
{
    auto rc = GetGlobalRect();
    auto rcCheck = SDL_Rect{ rc.x, rc.y, CHECK_BOX_SIZE, CHECK_BOX_SIZE };

    auto [w, h] = sRender->GetTextSize(m_text.c_str(), m_fontSize);
    int dism = (rc.h - h) / 2;
    auto rcText = SDL_Rect{ rcCheck.x + rcCheck.w + 4, rc.y, w, h };

    // Draw the square box
    sRender->DrawRect(rcCheck, m_textColor);

    // Draw the text
    sRender->DrawString(m_text.c_str(), m_fontSize, rcText, m_textColor);

    // Draw the checkmark
    if (m_ischecked)
    {
        SDL_Point p1, p2, p3;
        p1 = { rcCheck.x + 2, rc.y + rc.h / 2 };
        p2 = { rcCheck.x + rcCheck.w / 2, (rc.y + rc.h) - 4 };
        p3 = { rcCheck.x + rcCheck.w - 4, rcCheck.y + 4 };
        sRender->DrawLine(p1, p2, m_textColor);
        sRender->DrawLine(p2, p3, m_textColor);
    }
}

The rendering steps are:

  1. Draw the outer box rectangle.
  2. Draw the text label (with a 4-pixel gap from the box).
  3. If the checkbox is checked, draw the checkmark lines.

6. Event Handling

C++

bool UICheckBox::Handle(SDL_Event* e)
{
    if (e->type == SDL_MOUSEBUTTONDOWN)
    {
        if (e->button.button == SDL_BUTTON_LEFT)
        {
            m_ischecked = !m_ischecked;
            FireEvent(EventType::ET_CHECKCHANGED, nullptr);
        }
    }
    return UIControl::Handle(e);
}

This logic does the following:

  • Listens for a left mouse button click.
  • Toggles the m_ischecked state.
  • Fires an ET_CHECKCHANGED event, allowing external code to respond to the change.
  • Finally, it calls the parent class’s Handle() to maintain the event chain.

7. Text Size Adjustment

C++

void UICheckBox::OnTextChanged(const std::string& old)
{
    auto [w, h] = sRender->GetTextSize(m_text.c_str(), m_fontSize);
    Vec2 sz = { w + CHECK_BOX_SIZE, SDL_max(h, CHECK_BOX_SIZE) };
    SetSize(sz);
}

When the text content changes:

  • Recalculate the text size.
  • Set the overall control size to ensure both the box and the text are displayed correctly.

8. State Control Interface

C++

void UICheckBox::SetCheck(bool _check)
{
    m_ischecked = _check;
}

The SetCheck() function allows external code to directly control the checked state, for example, to set a default value during initialization.

9. Usage Example

C++

auto chk = std::shared_ptr<UICheckBox>(new UICheckBox("auto login"));
chk->SetPosition({ 320,270 });
chk->AddEvent(EventType::ET_CHECKCHANGED, [=](void*)
    {
        char szmsg[64];
        sprintf_s(szmsg, "auto login is: %s", chk->IsChecked() ? "checked" : "unchecked");
        m_lbmsg->SetText(szmsg);
    });
AddNode(chk);

This example shows how to:

  • Create a UICheckBox.
  • Set its position.
  • Add an event listener to print a message when the state changes.
  • Add the checkbox to a UI container.

10. Summary and Future Enhancements

In this article, we’ve implemented a lightweight SDL checkbox control, UICheckBox, which includes:

  • Drawing the outer box and text.
  • Toggling the state on click.
  • Drawing the checkmark.
  • Automatic size adjustment.

The current implementation is simple and functional. For a more polished look and feel, you can extend it with features like:

  • Highlighting on mouse hover.
  • Using a custom icon for the checkmark.
  • Keyboard operation (toggling with the spacebar).

You can find the code for this chapter here: https://github.com/84378996/simple_sdl_gui_tutorials/tree/main/tutorials05

Final demonstration:

Leave a Reply