
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()
andIsChecked()
provide control and query functions.- We override
Render()
for drawing,Handle()
for interaction, andOnTextChanged()
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:
- Draw the outer box rectangle.
- Draw the text label (with a 4-pixel gap from the box).
- 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: