Checkbox Widget¶
The Checkbox widget is a labelled boolean toggle. Init installs the
whole look (theme-accent box, rounded corners, tick mark) AND a default
event mask, so it already toggles on touch/keypad with no
set_event_cb() call; set_text() adds the caption; set_state() sets it
programmatically WITHOUT firing the change callback — the key property that
lets a "select all" master and its children update each other without
recursing. set_colors() overrides the theme accent per-instance, and
set_enabled(false) locks a box into a greyed, non-interactive display.
Every example below is complete — the file-scope storage, the callbacks and
the build calls — beside the exact frame it renders on a 480×320 panel. Each
compiles against nothing but the released library: drop it into the
Application Skeleton and it runs as-is. The icons come from
inc/icons/, the lexend fonts from inc/fonts/ — both ship with the library.
Examples¶
Default Checkboxes¶
Three checkboxes, each needing only init() (installs the on-theme look + interactive event mask), set_area(), and set_text() — no colours, no manual event wiring. set_state() pre-checks one so the checked look is visible on entry.

/* --- widget IDs: only need to be unique within the screen (10+ leaves
* room for the IDs your app already uses, e.g. the root's) --- */
enum {
WDGT_ID_SLOT0 = 10
};
/* --- fonts: each is DEFINED in inc/fonts/<name>.h — #include that
* header in exactly one .c of your app; elsewhere just: --- */
extern const ugui_font_t lexend_14pt_2bpp;
/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
#define CB_N 3
static ugui_widget_t s_cb[CB_N];
static ugui_checkbox_ext_t s_cb_ext[CB_N];
#endif
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX)
static ugui_text_cfg_t s_cb_txt[CB_N];
static const char* const s_cb_lbl[CB_N] = { "Wi-Fi", "Bluetooth", "Location" };
#endif
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX)
#define CB_X 40
#define CB_W (UGUI_SCREEN_W - 2 * CB_X)
#define CB_H 28
#define CB_Y0 (38 + 16)
#define CB_STEP 40
#endif
/* --- local helpers & event callbacks --- */
static void on_cb_change(bool checked, void* ctx)
{
printf("[checkbox.default] %s -> %s\n",
(ctx != NULL) ? (const char*)ctx : "?", checked ? "ON" : "OFF");
}
/* --- build it (in your screen's init) --- */
for (uint8_t i = 0u; i < (uint8_t)CB_N; i++)
{
ugui_widget_checkbox_init(&s_root, &s_cb[i], &s_cb_ext[i]);
ugui_widget_set_area(&s_cb[i], CB_X, (int16_t)(CB_Y0 + (int16_t)i * CB_STEP),
CB_W, CB_H);
ugui_widget_set_id(&s_cb[i], (uint8_t)(WDGT_ID_SLOT0 + i));
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_cb_txt[i], s_cb_lbl[i], &lexend_14pt_2bpp, UGUI_THEME_TEXT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
(ugui_point_t){ 0, 0 }, 0u);
ugui_widget_checkbox_set_text(&s_cb[i], &s_cb_txt[i]);
ugui_widget_checkbox_set_callback(&s_cb[i], on_cb_change, (void*)s_cb_lbl[i]);
#endif
ugui_screen_add_widget(&s_screen, &s_cb[i]);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_cb[i]);
#endif
}
/* Start with the first one ticked so the checked look is visible on entry. */
ugui_widget_checkbox_set_state(&s_cb[0], true);
States & Colour Overrides¶
The full visual vocabulary: theme default checked/unchecked (no colour calls), three set_colors() overrides (success/warning/danger, all ticked so the fill colour is obvious), and a disabled + checked box, greyed and non-interactive via set_enabled(false).

/* --- widget IDs: only need to be unique within the screen (10+ leaves
* room for the IDs your app already uses, e.g. the root's) --- */
enum {
WDGT_ID_SLOT0 = 10
};
/* --- fonts: each is DEFINED in inc/fonts/<name>.h — #include that
* header in exactly one .c of your app; elsewhere just: --- */
extern const ugui_font_t lexend_14pt_2bpp;
/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
#define CB_N 6
static ugui_widget_t s_cb[CB_N];
static ugui_checkbox_ext_t s_cb_ext[CB_N];
#endif
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX)
static ugui_text_cfg_t s_cb_txt[CB_N];
#endif
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX)
#define CB_X 28
#define CB_W (UGUI_SCREEN_W - 2 * CB_X)
#define CB_H 26
#define CB_Y0 (38 + 4)
#define CB_STEP 31
#endif
/* --- local helpers & event callbacks --- */
static void cb_add(uint8_t i, const char* label, bool checked, bool enabled,
bool custom, ugui_color_t color)
{
ugui_widget_checkbox_init(&s_root, &s_cb[i], &s_cb_ext[i]);
ugui_widget_set_area(&s_cb[i], CB_X, (int16_t)(CB_Y0 + (int16_t)i * CB_STEP),
CB_W, CB_H);
ugui_widget_set_id(&s_cb[i], (uint8_t)(WDGT_ID_SLOT0 + i));
if (custom)
{
/* border = check = the accent colour; bg = the surface (unchecked look). */
ugui_widget_checkbox_set_colors(&s_cb[i], color, color, UGUI_THEME_SURFACE);
}
if (checked) { ugui_widget_checkbox_set_state(&s_cb[i], true); }
if (!enabled) { ugui_widget_set_enabled(&s_cb[i], false); }
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_cb_txt[i], label, &lexend_14pt_2bpp,
enabled ? UGUI_THEME_TEXT : UGUI_COLOR_GRAY,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
(ugui_point_t){ 0, 0 }, 0u);
ugui_widget_checkbox_set_text(&s_cb[i], &s_cb_txt[i]);
#else
(void)label;
#endif
ugui_screen_add_widget(&s_screen, &s_cb[i]);
#if UGUI_ENABLE_KEYPAD
if (enabled) { ugui_screen_add_focus(&s_screen, &s_cb[i]); }
#endif
}
/* --- build it (in your screen's init) --- */
/* idx label checked enabled custom colour */
cb_add(0u, "Theme default (on)", true, true, false, UGUI_THEME_ACCENT);
cb_add(1u, "Theme default (off)", false, true, false, UGUI_THEME_ACCENT);
cb_add(2u, "Custom: success", true, true, true, UGUI_THEME_OK);
cb_add(3u, "Custom: warning", true, true, true, UGUI_COLOR_ORANGE);
cb_add(4u, "Custom: danger", true, true, true, UGUI_THEME_CANCEL);
cb_add(5u, "Disabled (locked on)",true, false, false, UGUI_THEME_ACCENT);
Settings Panel (live summary)¶
The everyday pattern: five option checkboxes under two section headers, each reporting changes through its on_change callback, which the screen turns into a live human-readable summary at the bottom.

/* --- widget IDs: only need to be unique within the screen (10+ leaves
* room for the IDs your app already uses, e.g. the root's) --- */
enum {
WDGT_ID_SLOT0 = 10, WDGT_ID_HDR_NOTIFICATIONS, WDGT_ID_HDR_PRIVACY,
WDGT_ID_DEMO
};
/* --- fonts: each is DEFINED in inc/fonts/<name>.h — #include that
* header in exactly one .c of your app; elsewhere just: --- */
extern const ugui_font_t lexend_14pt_2bpp;
/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
#define CB_N 5
static ugui_widget_t s_cb[CB_N];
static ugui_checkbox_ext_t s_cb_ext[CB_N];
static ugui_text_cfg_t s_cb_txt[CB_N];
static const char* const s_cb_name[CB_N] =
{ "Email", "SMS", "Push", "Analytics", "Crash logs" };
static ugui_widget_t s_hdr[2];
static ugui_label_ext_t s_hdr_ext[2];
static ugui_widget_t s_status;
static ugui_label_ext_t s_status_ext;
static char s_status_buf[72];
#define CB_X 24
#define CB_W (UGUI_SCREEN_W - 2 * CB_X)
#define CB_H 26
#define HDR_H 16
#endif
/* --- local helpers & event callbacks --- */
static void str_append(uint8_t* len, uint8_t cap, const char* src)
{
while ((*src != '\0') && (*len < cap)) { s_status_buf[*len] = *src; (*len)++; src++; }
}
static void update_status(void)
{
const uint8_t cap = (uint8_t)(sizeof(s_status_buf) - 1u);
uint8_t len = 0u;
uint8_t count = 0u;
str_append(&len, cap, "On:");
for (uint8_t i = 0u; i < (uint8_t)CB_N; i++)
{
if (ugui_widget_checkbox_get_state(&s_cb[i]))
{
str_append(&len, cap, " ");
str_append(&len, cap, s_cb_name[i]);
count++;
}
}
if (count == 0u) { str_append(&len, cap, " (none)"); }
s_status_buf[len] = '\0';
ugui_label_set_text(&s_status, s_status_buf);
}
static void on_item_change(bool checked, void* ctx)
{
(void)checked;
(void)ctx;
update_status();
}
static void hdr_add(uint8_t i, int16_t y, const char* text, uint8_t id)
{
ugui_widget_label_init(&s_root, &s_hdr[i], &s_hdr_ext[i]);
ugui_widget_set_area(&s_hdr[i], CB_X, y, CB_W, HDR_H);
ugui_widget_set_id (&s_hdr[i], id);
ugui_widget_label_set_style(&s_hdr[i], false, UGUI_THEME_BG, text, &lexend_14pt_2bpp,
UGUI_THEME_ACCENT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_screen_add_widget(&s_screen, &s_hdr[i]);
}
static void cb_add(uint8_t i, int16_t y, bool checked)
{
ugui_widget_checkbox_init(&s_root, &s_cb[i], &s_cb_ext[i]);
ugui_widget_set_area(&s_cb[i], CB_X, y, CB_W, CB_H);
ugui_widget_set_id (&s_cb[i], (uint8_t)(WDGT_ID_SLOT0 + i));
ugui_widget_checkbox_set_callback(&s_cb[i], on_item_change, NULL);
if (checked) { ugui_widget_checkbox_set_state(&s_cb[i], true); }
ugui_text_cfg_init(&s_cb_txt[i], s_cb_name[i], &lexend_14pt_2bpp, UGUI_THEME_TEXT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
(ugui_point_t){ 0, 0 }, 0u);
ugui_widget_checkbox_set_text(&s_cb[i], &s_cb_txt[i]);
ugui_screen_add_widget(&s_screen, &s_cb[i]);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_cb[i]);
#endif
}
/* --- build it (in your screen's init) --- */
hdr_add(0u, (int16_t)(38 + 2), "NOTIFICATIONS", WDGT_ID_HDR_NOTIFICATIONS);
cb_add (0u, 58, true); /* Email (on) */
cb_add (1u, 86, true); /* SMS (on) */
cb_add (2u, 114, false); /* Push (off) */
hdr_add(1u, 142, "PRIVACY", WDGT_ID_HDR_PRIVACY);
cb_add (3u, 160, false); /* Analytics (off) */
cb_add (4u, 188, true); /* Crash logs (on) */
/* Live status line. */
ugui_widget_label_init(&s_root, &s_status, &s_status_ext);
ugui_widget_set_area(&s_status, CB_X, 216, CB_W, 20);
ugui_widget_set_id (&s_status, WDGT_ID_DEMO);
ugui_widget_label_set_style(&s_status, false, UGUI_THEME_BG, s_status_buf,
&lexend_14pt_2bpp, UGUI_THEME_TEXT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_screen_add_widget(&s_screen, &s_status);
update_status(); /* seed the summary from the initial states */
Select-All Group¶
The classic parent/child pattern, wired two ways: toggling the MASTER sets every child to its state; toggling any CHILD re-derives the master (checked only when all children are). set_state() never fires the on_change callback, so the master driving children (and children re-deriving the master) never recurses.

/* --- widget IDs: only need to be unique within the screen (10+ leaves
* room for the IDs your app already uses, e.g. the root's) --- */
enum {
WDGT_ID_SLOT0 = 10, WDGT_ID_HDR, WDGT_ID_DEMO,
WDGT_ID_COUNT
};
/* --- fonts: each is DEFINED in inc/fonts/<name>.h — #include that
* header in exactly one .c of your app; elsewhere just: --- */
extern const ugui_font_t lexend_14pt_2bpp;
/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_CHECKBOX) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
#define GRP_N 4
static ugui_widget_t s_master;
static ugui_checkbox_ext_t s_master_ext;
static ugui_text_cfg_t s_master_txt;
static ugui_widget_t s_item[GRP_N];
static ugui_checkbox_ext_t s_item_ext[GRP_N];
static ugui_text_cfg_t s_item_txt[GRP_N];
static const char* const s_item_name[GRP_N] =
{ "Camera", "Microphone", "Location", "Contacts" };
static ugui_widget_t s_hdr;
static ugui_label_ext_t s_hdr_ext;
static ugui_widget_t s_count;
static ugui_label_ext_t s_count_ext;
static char s_count_buf[24];
#define GRP_X 24
#define GRP_INDENT 44
#define GRP_W (UGUI_SCREEN_W - GRP_X - 24)
#define GRP_ITEM_W (UGUI_SCREEN_W - GRP_INDENT - 24)
#define CB_H 26
#endif
/* --- local helpers & event callbacks --- */
static void update_count(void)
{
uint8_t sel = 0u;
for (uint8_t i = 0u; i < (uint8_t)GRP_N; i++)
{
if (ugui_widget_checkbox_get_state(&s_item[i])) { sel++; }
}
(void)snprintf(s_count_buf, sizeof(s_count_buf), "%d of %d selected",
(int)sel, (int)GRP_N);
ugui_label_set_text(&s_count, s_count_buf);
}
static void update_master(void)
{
bool all = true;
for (uint8_t i = 0u; i < (uint8_t)GRP_N; i++)
{
if (!ugui_widget_checkbox_get_state(&s_item[i])) { all = false; }
}
ugui_widget_checkbox_set_state(&s_master, all);
}
static void on_master_change(bool checked, void* ctx)
{
(void)ctx;
for (uint8_t i = 0u; i < (uint8_t)GRP_N; i++)
{
ugui_widget_checkbox_set_state(&s_item[i], checked);
}
update_count();
}
static void on_item_change(bool checked, void* ctx)
{
(void)checked;
(void)ctx;
update_master();
update_count();
}
static void item_add(uint8_t i, int16_t y, bool checked)
{
ugui_widget_checkbox_init(&s_root, &s_item[i], &s_item_ext[i]);
ugui_widget_set_area(&s_item[i], GRP_INDENT, y, GRP_ITEM_W, CB_H);
ugui_widget_set_id (&s_item[i], (uint8_t)(WDGT_ID_SLOT0 + i));
ugui_widget_checkbox_set_callback(&s_item[i], on_item_change, NULL);
if (checked) { ugui_widget_checkbox_set_state(&s_item[i], true); }
ugui_text_cfg_init(&s_item_txt[i], s_item_name[i], &lexend_14pt_2bpp, UGUI_THEME_TEXT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
(ugui_point_t){ 0, 0 }, 0u);
ugui_widget_checkbox_set_text(&s_item[i], &s_item_txt[i]);
ugui_screen_add_widget(&s_screen, &s_item[i]);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_item[i]);
#endif
}
/* --- build it (in your screen's init) --- */
/* Section header. */
ugui_widget_label_init(&s_root, &s_hdr, &s_hdr_ext);
ugui_widget_set_area(&s_hdr, GRP_X, (int16_t)(38 + 2), GRP_W, 16);
ugui_widget_set_id (&s_hdr, WDGT_ID_HDR);
ugui_widget_label_set_style(&s_hdr, false, UGUI_THEME_BG, "APP PERMISSIONS",
&lexend_14pt_2bpp, UGUI_THEME_ACCENT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_screen_add_widget(&s_screen, &s_hdr);
/* Master "Select all". */
ugui_widget_checkbox_init(&s_root, &s_master, &s_master_ext);
ugui_widget_set_area(&s_master, GRP_X, 58, GRP_W, CB_H);
ugui_widget_set_id (&s_master, WDGT_ID_DEMO);
ugui_widget_checkbox_set_callback(&s_master, on_master_change, NULL);
ugui_text_cfg_init(&s_master_txt, "Select all", &lexend_14pt_2bpp, UGUI_THEME_TEXT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
(ugui_point_t){ 0, 0 }, 0u);
ugui_widget_checkbox_set_text(&s_master, &s_master_txt);
ugui_screen_add_widget(&s_screen, &s_master);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_master);
#endif
/* Children (indented) — start with a mixed selection. */
item_add(0u, 90, true); /* Camera (on) */
item_add(1u, 118, false); /* Microphone (off) */
item_add(2u, 146, true); /* Location (on) */
item_add(3u, 174, false); /* Contacts (off) */
/* Live counter. */
ugui_widget_label_init(&s_root, &s_count, &s_count_ext);
ugui_widget_set_area(&s_count, GRP_X, 206, GRP_W, 22);
ugui_widget_set_id (&s_count, WDGT_ID_COUNT);
ugui_widget_label_set_style(&s_count, false, UGUI_THEME_BG, s_count_buf,
&lexend_14pt_2bpp, UGUI_THEME_TEXT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_screen_add_widget(&s_screen, &s_count);
/* Seed the master + counter from the initial child selection. */
update_master();
update_count();
From this widget to a working app
Everything above is the widget-specific code. The scaffolding it drops into — storage, HAL, main loop, display-mode bring-up — is the same for every app: copy it from the Application Skeleton.