Skip to content

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.

Default Checkboxes

/* --- 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).

States & Colour Overrides

/* --- 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.

Settings Panel (live summary)

/* --- 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.

Select-All Group

/* --- 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.