Skip to content

Container Widget

The Container is the full 2-axis scroll panel — vertical AND horizontal overflow, a bar on either edge as needed, and a built-in keypad model: it is ONE focus stop that proxies a cursor to its children. Init installs the whole look (surface fill, themed border, accent thumb over a subtle track groove) and set_area() places it; parent content bigger than the panel on either axis and the matching bar appears automatically. set_style() restyles the frame and set_scrollbar_colors() restyles the bars. TAB focuses the container; ENTER descends into child-navigation (arrows move/adjust the highlighted child, ENTER activates it, ESC leaves) — no per-widget focus wiring in the app. For a lean, single-axis alternative with one keypad focus stop per child instead, see VContainer / HContainer.

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 Container

Two calls make a scrollable panel: ugui_widget_container_init() installs the whole look and set_area() places it. Parent a handful of rows together taller than the panel and the vertical scrollbar appears automatically, using nothing but the library defaults.

Default Container

/* --- 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_DEMO = 10, WDGT_ID_SLOT0
};

/* --- 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_CONTAINER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t         s_cont;
static ugui_container_ext_t  s_cont_ext;
#define MAX_ROWS    ((uint8_t)UGUI_MAX_CHILDREN)
#define ROW_EXTRA   2u   /* guaranteed rows past the visible edge, any display */
static ugui_widget_t   s_row[MAX_ROWS];
static ugui_box_ext_t  s_row_ext[MAX_ROWS];
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_text_cfg_t s_row_txt[MAX_ROWS];
static char            s_row_label[MAX_ROWS][12];   /* "Item 1".."Item 24" */
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
#define PAN_X   ((int16_t)((UGUI_SCREEN_W - PAN_W) / 2))
#define PAN_Y   ((int16_t)(38 + 2))
#define PAN_W   ((int16_t)220)
#define PAN_H   ((int16_t)(UGUI_SCREEN_H - PAN_Y - 8))
#define ROW_X       10
#define ROW_W       ((int16_t)(PAN_W - 20 - 6))   /* leave the right gutter free */
#define ROW_H       30
#define ROW_TOP     8
#define ROW_STEP    36
#endif

/* --- local helpers & event callbacks --- */
static uint8_t fill_row_count(int16_t avail_px, int16_t step_px, uint8_t extra, uint8_t cap)
{
    int32_t fit = (step_px > 0) ? ((int32_t)avail_px / (int32_t)step_px) : 0;
    if (fit < 0) { fit = 0; }
    int32_t n = fit + (int32_t)extra;
    if (n < 1) { n = 1; }
    if (n > (int32_t)cap) { n = (int32_t)cap; }
    return (uint8_t)n;
}

/* --- build it (in your screen's init) --- */
ugui_widget_container_init(&s_root, &s_cont, &s_cont_ext);
ugui_widget_set_area(&s_cont, PAN_X, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_cont, WDGT_ID_DEMO);
ugui_screen_add_widget(&s_screen, &s_cont);

/* Rows are ordinary boxes parented to the container (coords are relative to
 * it, and they are clipped to it). Together they are taller than the panel,
 * so the container measures the overflow and shows a vertical bar.
 * Filled a touch lighter than the panel surface so they read as list cards
 * on it — the tint tracks the theme (dark AND light). */
const ugui_color_t row_fill =
    ugui_color_blend(UGUI_THEME_BORDER, UGUI_THEME_SURFACE, 56u);
uint8_t row_count = fill_row_count(PAN_H, ROW_STEP, ROW_EXTRA, MAX_ROWS);
for (uint8_t i = 0u; i < row_count; i++) {
    ugui_widget_box_init(&s_cont, &s_row[i], &s_row_ext[i]);
    ugui_widget_set_area(&s_row[i], ROW_X, (int16_t)(ROW_TOP + i * ROW_STEP),
                         ROW_W, ROW_H);
    ugui_widget_set_id(&s_row[i], (uint8_t)(WDGT_ID_SLOT0 + i));
    ugui_widget_box_set_style(&s_row[i], true, 5u, 0u,
                              row_fill, UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
    (void)snprintf(s_row_label[i], sizeof(s_row_label[i]), "Item %u",
                   (unsigned)(i + 1u));
    ugui_text_cfg_init(&s_row_txt[i], s_row_label[i], &lexend_14pt_2bpp,
                       UGUI_THEME_TEXT,
                       (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
                       (ugui_point_t){ 10, 0 }, 1u);
    ugui_widget_box_set_text(&s_row[i], &s_row_txt[i]);
#endif
    ugui_screen_add_widget(&s_screen, &s_row[i]);
}

/* Re-measure content + re-clamp now that every child is attached. */
ugui_widget_container_relayout(&s_cont);

Two-Axis Scroll

A grid of numbered tiles parented to a container smaller than the grid in width AND height — it auto-measures the overflow and shows a vertical bar (right gutter) and a horizontal bar (bottom gutter) at once. Tiles are coloured by column, so horizontal scroll sweeps the colour bands and vertical scroll walks the numbers.

Two-Axis Scroll

/* --- 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_DEMO = 10, WDGT_ID_SLOT0
};

/* --- 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_CONTAINER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t         s_cont;
static ugui_container_ext_t  s_cont_ext;
#define MAX_COLS    4u
#define MAX_ROWS    6u
#define COL_EXTRA   1u   /* guaranteed columns past the right edge, any display */
#define ROW_EXTRA   1u   /* guaranteed rows past the bottom edge, any display   */
#define MAX_TILES  (MAX_COLS * MAX_ROWS)
static ugui_widget_t   s_tile[MAX_TILES];
static ugui_box_ext_t  s_tile_ext[MAX_TILES];
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_text_cfg_t s_tile_txt[MAX_TILES];
static char            s_tile_num[MAX_TILES][4];   /* "1".."24" */
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
#define PAN_X   ((int16_t)((UGUI_SCREEN_W - PAN_W) / 2))
#define PAN_Y   ((int16_t)(38 + 2))
#define PAN_W   ((int16_t)260)
#define PAN_H   ((int16_t)(UGUI_SCREEN_H - PAN_Y - 8))
#define TILE_W   68
#define TILE_H   44
#define TILE_GAP 8
#define TILE_PAD 8
#define TILE_STEP_X (TILE_W + TILE_GAP)
#define TILE_STEP_Y (TILE_H + TILE_GAP)
#endif

/* --- local helpers & event callbacks --- */
static uint8_t fill_row_count(int16_t avail_px, int16_t step_px, uint8_t extra, uint8_t cap)
{
    int32_t fit = (step_px > 0) ? ((int32_t)avail_px / (int32_t)step_px) : 0;
    if (fit < 0) { fit = 0; }
    int32_t n = fit + (int32_t)extra;
    if (n < 1) { n = 1; }
    if (n > (int32_t)cap) { n = (int32_t)cap; }
    return (uint8_t)n;
}

/* --- build it (in your screen's init) --- */
/* Four vivid column colours (the theme swatch hues) — kept fixed so the
 * colour bands read the same whatever accent is active. MAX_COLS == 4
 * matches this table exactly, so a plain index (no modulo) is safe. */
const ugui_color_t col_color[MAX_COLS] = {
    UGUI_COLOR_PURPLE, UGUI_COLOR_ORANGE, UGUI_COLOR_TEAL, UGUI_COLOR_GREEN
};
ugui_widget_container_init(&s_root, &s_cont, &s_cont_ext);
ugui_widget_set_area(&s_cont, PAN_X, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_cont, WDGT_ID_DEMO);
/* Bigger step so a single key press moves about one tile. */
ugui_widget_container_set_scroll_step(&s_cont, TILE_STEP_Y);
ugui_screen_add_widget(&s_screen, &s_cont);

uint8_t cols = fill_row_count(PAN_W, TILE_STEP_X, COL_EXTRA, MAX_COLS);
uint8_t rows = fill_row_count(PAN_H, TILE_STEP_Y, ROW_EXTRA, MAX_ROWS);
for (uint8_t j = 0u; j < rows; j++) {
    for (uint8_t i = 0u; i < cols; i++) {
        uint8_t k = (uint8_t)(j * cols + i);
        ugui_widget_box_init(&s_cont, &s_tile[k], &s_tile_ext[k]);
        ugui_widget_set_area(&s_tile[k],
                             (int16_t)(TILE_PAD + i * TILE_STEP_X),
                             (int16_t)(TILE_PAD + j * TILE_STEP_Y),
                             TILE_W, TILE_H);
        ugui_widget_set_id(&s_tile[k], WDGT_ID_SLOT0);
        ugui_widget_box_set_style(&s_tile[k], true, 6u, 0u,
                                  col_color[i], UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
        (void)snprintf(s_tile_num[k], sizeof(s_tile_num[k]), "%u",
                       (unsigned)(k + 1u));
        ugui_text_cfg_init(&s_tile_txt[k], s_tile_num[k], &lexend_14pt_2bpp,
                           UGUI_COLOR_WHITE, UGUI_ALIGN_CENTER,
                           (ugui_point_t){ 0, 0 }, 1u);
        ugui_widget_box_set_text(&s_tile[k], &s_tile_txt[k]);
#endif
        ugui_screen_add_widget(&s_screen, &s_tile[k]);
    }
}

ugui_widget_container_relayout(&s_cont);

Styles

Two scrollable panels holding the same list, styled differently: LEFT uses set_style() for a soft framed card (2 px border, 10 px radius) plus set_scrollbar_colors() and a wider thumb; RIGHT is flat and borderless with a green thumb. Everything else — measurement, clipping, input — is identical; only the style calls differ.

Styles

/* --- 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_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_CONTAINER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t         s_lcont, s_rcont;
static ugui_container_ext_t  s_lcont_ext, s_rcont_ext;
static ugui_widget_t         s_lcap, s_rcap;   /* captions above each panel */
static ugui_label_ext_t      s_lcap_ext, s_rcap_ext;
#define MAX_ROWS    ((uint8_t)UGUI_MAX_CHILDREN)
#define ROW_EXTRA   2u   /* guaranteed rows past the visible edge, any display */
static ugui_widget_t   s_lrow[MAX_ROWS], s_rrow[MAX_ROWS];
static ugui_box_ext_t  s_lrow_ext[MAX_ROWS], s_rrow_ext[MAX_ROWS];
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_text_cfg_t s_lrow_txt[MAX_ROWS], s_rrow_txt[MAX_ROWS];
static char            s_lrow_num[MAX_ROWS][8], s_rrow_num[MAX_ROWS][8];
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
#define PAN_W   144
#define PAN_LX  10
#define PAN_RX  (PAN_LX + PAN_W + 12)
#define CAP_Y   ((int16_t)38)
#define PAN_Y   ((int16_t)(38 + 22))
#define PAN_H   ((int16_t)(UGUI_SCREEN_H - PAN_Y - 8))
#define ROW_X     8
#define ROW_W     ((int16_t)(PAN_W - ROW_X - 8))
#define ROW_H     28
#define ROW_TOP   8
#define ROW_STEP  34
#endif

/* --- local helpers & event callbacks --- */
static void add_row(ugui_widget_t* cont, ugui_widget_t* row, ugui_box_ext_t* ext,
                    uint8_t i, ugui_color_t fill
#ifdef UGUI_ENABLE_FONT
                    , ugui_text_cfg_t* txt, char* num
#endif
                    )
{
    ugui_widget_box_init(cont, row, ext);
    ugui_widget_set_area(row, ROW_X, (int16_t)(ROW_TOP + i * ROW_STEP), ROW_W, ROW_H);
    ugui_widget_set_id(row, WDGT_ID_SLOT0);
    ugui_widget_box_set_style(row, true, 5u, 0u, fill, UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
    (void)snprintf(num, 8, "Row %u", (unsigned)(i + 1u));
    ugui_text_cfg_init(txt, num, &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                       (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
                       (ugui_point_t){ 8, 0 }, 1u);
    ugui_widget_box_set_text(row, txt);
#endif
    ugui_screen_add_widget(&s_screen, row);
}

static uint8_t fill_row_count(int16_t avail_px, int16_t step_px, uint8_t extra, uint8_t cap)
{
    int32_t fit = (step_px > 0) ? ((int32_t)avail_px / (int32_t)step_px) : 0;
    if (fit < 0) { fit = 0; }
    int32_t n = fit + (int32_t)extra;
    if (n < 1) { n = 1; }
    if (n > (int32_t)cap) { n = (int32_t)cap; }
    return (uint8_t)n;
}

static void add_label(ugui_widget_t* w, ugui_label_ext_t* ext, int16_t x, int16_t y,
                      int16_t cw, int16_t ch, const char* str)
{
    ugui_widget_label_init(&s_root, w, ext);
    ugui_widget_set_area(w, x, y, cw, ch);
    ugui_widget_label_set_style(w, false, UGUI_THEME_BG, str,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT, UGUI_ALIGN_CENTER, 1u);
    ugui_screen_add_widget(&s_screen, w);
}

/* --- build it (in your screen's init) --- */
const ugui_color_t row_fill =
    ugui_color_blend(UGUI_THEME_BORDER, UGUI_THEME_SURFACE, 56u);
const ugui_color_t groove =
    ugui_color_blend(UGUI_THEME_BORDER, UGUI_THEME_SURFACE, 96u);
add_label(&s_lcap, &s_lcap_ext, PAN_LX, CAP_Y, PAN_W, 20, "Rounded");
add_label(&s_rcap, &s_rcap_ext, PAN_RX, CAP_Y, PAN_W, 20, "Square + green bar");

/* ── LEFT: rounded, framed, wide accent thumb ─────────────────────────── */
ugui_widget_container_init(&s_root, &s_lcont, &s_lcont_ext);
ugui_widget_set_area(&s_lcont, PAN_LX, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_lcont, WDGT_ID_DEMO);
ugui_widget_container_set_style(&s_lcont, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT, 2u, 10u);
ugui_widget_container_set_scrollbar_colors(&s_lcont, UGUI_THEME_ACCENT, groove);
s_lcont_ext.scrollbar_w = 6u;               /* wider thumb (no dedicated setter) */
ugui_screen_add_widget(&s_screen, &s_lcont);
uint8_t row_count = fill_row_count(PAN_H, ROW_STEP, ROW_EXTRA, MAX_ROWS);
for (uint8_t i = 0u; i < row_count; i++) {
    add_row(&s_lcont, &s_lrow[i], &s_lrow_ext[i], i, row_fill
#ifdef UGUI_ENABLE_FONT
            , &s_lrow_txt[i], s_lrow_num[i]
#endif
            );
}
ugui_widget_container_relayout(&s_lcont);

/* ── RIGHT: square, borderless, green thumb ───────────────────────────── */
ugui_widget_container_init(&s_root, &s_rcont, &s_rcont_ext);
ugui_widget_set_area(&s_rcont, PAN_RX, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_rcont, WDGT_ID_DEMO);
ugui_widget_container_set_style(&s_rcont, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT, 0u, 0u);
ugui_widget_container_set_scrollbar_colors(&s_rcont, UGUI_COLOR_GREEN,
                                           UGUI_COLOR_DARK_GRAY);
ugui_screen_add_widget(&s_screen, &s_rcont);
for (uint8_t i = 0u; i < row_count; i++) {
    add_row(&s_rcont, &s_rrow[i], &s_rrow_ext[i], i, row_fill
#ifdef UGUI_ENABLE_FONT
            , &s_rrow_txt[i], s_rrow_num[i]
#endif
            );
}
ugui_widget_container_relayout(&s_rcont);

#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_lcont);
ugui_screen_add_focus(&s_screen, &s_rcont);
#endif

Settings Form (ENTER=edit, ESC=back)

The container holds a column of REAL interactive widgets — slider, checkboxes, a radio group, a toggle, a progress bar and a button — plus read-only labels, taller than the panel. Two input models, zero per-widget wiring: mouse clicks any control directly; keypad TABs to the container, ENTER descends into it (highlights the first control), arrows move/adjust the highlight, ENTER activates it, ESC leaves.

Settings Form (ENTER=edit, ESC=back)

/* --- 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_DEMO = 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_CONTAINER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t         s_cont;
static ugui_container_ext_t  s_cont_ext;
static ugui_widget_t      s_slider;   static ugui_slider_ext_t   s_slider_ext;
static ugui_widget_t      s_toggle;   static ugui_toggle_ext_t   s_toggle_ext;
static ugui_widget_t      s_batt;     static ugui_progress_bar_ext_t s_batt_ext;
static ugui_widget_t      s_apply;    static ugui_box_ext_t      s_apply_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_text_cfg_t    s_apply_txt;
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_widget_t      s_hdr;        static ugui_label_ext_t s_hdr_ext;
static ugui_widget_t      s_toggle_lbl; static ugui_label_ext_t s_toggle_lbl_ext;
#endif
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_widget_t      s_wifi;   static ugui_checkbox_ext_t s_wifi_ext;
static ugui_widget_t      s_bt;     static ugui_checkbox_ext_t s_bt_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_text_cfg_t    s_wifi_txt, s_bt_txt;
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_widget_t      s_dark;   static ugui_radio_button_ext_t s_dark_ext;
static ugui_widget_t      s_light;  static ugui_radio_button_ext_t s_light_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
static ugui_text_cfg_t    s_dark_txt, s_light_txt;
#endif
#if defined(UGUI_WIDGET_ENABLE_CONTAINER)
#define PAN_X   12
#define PAN_Y   ((int16_t)38)
#define PAN_W   ((int16_t)(UGUI_SCREEN_W - 24))
#define PAN_H   ((int16_t)(UGUI_SCREEN_H - PAN_Y - 8))
#define CX        12
#define CW_FULL   ((int16_t)(PAN_W - CX - 14))
#define CW_CTRL   ((int16_t)(PAN_W - CX - 60))
#define LBL_ALIGN ((ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER))
#endif

/* --- local helpers & event callbacks --- */
static void on_bool(bool state, void* ctx)
{
    printf("[container.form] %s = %s\n", (const char*)ctx, state ? "ON" : "OFF");
}

static void on_slider(int16_t lo, int16_t hi, void* ctx)
{
    (void)hi;
    printf("[container.form] %s = %d\n", (const char*)ctx, (int)lo);
}

static void on_apply(uint8_t widget_id, ugui_event_t event)
{
    (void)widget_id;
    if (event == UGUI_EVENT_CLICK) { printf("[container.form] APPLY clicked\n"); }
}

/* --- build it (in your screen's init) --- */
ugui_widget_container_init(&s_root, &s_cont, &s_cont_ext);
ugui_widget_set_area(&s_cont, PAN_X, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_cont, WDGT_ID_DEMO);
ugui_widget_container_set_style(&s_cont, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT, 1u, 8u);
ugui_screen_add_widget(&s_screen, &s_cont);

/* 1. section header — read-only label (not focusable). */
#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
ugui_widget_label_init(&s_cont, &s_hdr, &s_hdr_ext);
ugui_widget_set_area(&s_hdr, CX, 8, CW_FULL, 22);
ugui_widget_label_set_style(&s_hdr, true, UGUI_THEME_SURFACE, "QUICK SETTINGS",
                            &lexend_14pt_2bpp, UGUI_THEME_ACCENT, LBL_ALIGN, 1u);
ugui_screen_add_widget(&s_screen, &s_hdr);
#endif

/* 2. slider — focusable; ENTER highlights it, left/right change value. */
ugui_widget_slider_init(&s_cont, &s_slider, &s_slider_ext);
ugui_widget_set_area(&s_slider, CX, 40, CW_FULL, 26);
ugui_widget_slider_set_range(&s_slider, 0, 100);
ugui_widget_slider_set_value(&s_slider, 60);
ugui_widget_slider_set_callback(&s_slider, on_slider, (void*)"Brightness");
ugui_screen_add_widget(&s_screen, &s_slider);

/* 3 + 4. checkboxes — focusable; ENTER toggles. */
#ifdef UGUI_WIDGET_ENABLE_CHECKBOX
ugui_widget_checkbox_init(&s_cont, &s_wifi, &s_wifi_ext);
ugui_widget_set_area(&s_wifi, CX, 76, CW_CTRL, 26);
ugui_widget_checkbox_set_state(&s_wifi, true);
ugui_widget_checkbox_set_callback(&s_wifi, on_bool, (void*)"Wi-Fi");
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_wifi_txt, "Wi-Fi", &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                   LBL_ALIGN, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_checkbox_set_text(&s_wifi, &s_wifi_txt);
#endif
ugui_screen_add_widget(&s_screen, &s_wifi);

ugui_widget_checkbox_init(&s_cont, &s_bt, &s_bt_ext);
ugui_widget_set_area(&s_bt, CX, 108, CW_CTRL, 26);
ugui_widget_checkbox_set_callback(&s_bt, on_bool, (void*)"Bluetooth");
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_bt_txt, "Bluetooth", &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                   LBL_ALIGN, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_checkbox_set_text(&s_bt, &s_bt_txt);
#endif
ugui_screen_add_widget(&s_screen, &s_bt);
#endif /* UGUI_WIDGET_ENABLE_CHECKBOX */

/* 5 + 6. radio group (mutually exclusive) — focusable; ENTER selects. */
#ifdef UGUI_WIDGET_ENABLE_RADIO_BUTTON
ugui_widget_radio_init(&s_cont, &s_dark, &s_dark_ext);
ugui_widget_set_area(&s_dark, CX, 144, CW_CTRL, 26);
ugui_widget_radio_set_group(&s_dark, 1u);
ugui_widget_radio_set_state(&s_dark, true);
ugui_widget_radio_set_callback(&s_dark, on_bool, (void*)"Theme:Dark");
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_dark_txt, "Dark theme", &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                   LBL_ALIGN, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_radio_set_text(&s_dark, &s_dark_txt);
#endif
ugui_screen_add_widget(&s_screen, &s_dark);

ugui_widget_radio_init(&s_cont, &s_light, &s_light_ext);
ugui_widget_set_area(&s_light, CX, 174, CW_CTRL, 26);
ugui_widget_radio_set_group(&s_light, 1u);
ugui_widget_radio_set_callback(&s_light, on_bool, (void*)"Theme:Light");
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_light_txt, "Light theme", &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                   LBL_ALIGN, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_radio_set_text(&s_light, &s_light_txt);
#endif
ugui_screen_add_widget(&s_screen, &s_light);
#endif /* UGUI_WIDGET_ENABLE_RADIO_BUTTON */

/* 7. toggle switch (+ its own label) — focusable; ENTER flips. */
ugui_widget_toggle_init(&s_cont, &s_toggle, &s_toggle_ext);
ugui_widget_set_area(&s_toggle, CX, 208, 54, 28);
ugui_widget_toggle_set_colors(&s_toggle,
                              ugui_color_blend(UGUI_THEME_BORDER,
                                               UGUI_THEME_SURFACE, 96u),
                              UGUI_THEME_OK, UGUI_THEME_KNOB);
ugui_widget_toggle_set_callback(&s_toggle, on_bool, (void*)"Night mode");
ugui_screen_add_widget(&s_screen, &s_toggle);
#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
ugui_widget_label_init(&s_cont, &s_toggle_lbl, &s_toggle_lbl_ext);
ugui_widget_set_area(&s_toggle_lbl, 74, 208, (int16_t)(PAN_W - 74 - 14), 28);
ugui_widget_label_set_style(&s_toggle_lbl, true, UGUI_THEME_SURFACE, "Night mode",
                            &lexend_14pt_2bpp, UGUI_THEME_TEXT, LBL_ALIGN, 1u);
ugui_screen_add_widget(&s_screen, &s_toggle_lbl);
#endif

/* 8. progress bar — read-only (not focusable). */
ugui_widget_progress_init(&s_cont, &s_batt, &s_batt_ext);
ugui_widget_set_area(&s_batt, CX, 244, CW_FULL, 22);
ugui_widget_progress_set_range(&s_batt, 0, 100);
ugui_widget_progress_set_value(&s_batt, 72);
ugui_widget_progress_set_colors(&s_batt, UGUI_THEME_OK,
                                ugui_color_blend(UGUI_THEME_BORDER,
                                                 UGUI_THEME_SURFACE, 64u),
                                UGUI_THEME_BORDER);
#ifdef UGUI_ENABLE_FONT
/* (progress text intentionally omitted — keep the bar clean) */
#endif
ugui_screen_add_widget(&s_screen, &s_batt);

/* 9. push button — focusable; ENTER clicks. */
ugui_widget_box_init(&s_cont, &s_apply, &s_apply_ext);
ugui_widget_set_area(&s_apply, CX, 276, CW_FULL, 32);
ugui_widget_box_set_style(&s_apply, true, 6u, 1u, UGUI_THEME_ACCENT, UGUI_THEME_ACCENT);
ugui_widget_set_event_cb(&s_apply, on_apply,
                         UGUI_EMASK_CLICK | UGUI_EMASK_DOWN | UGUI_EMASK_UP
                         | UGUI_EMASK_KEY_CONFIRM);
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_apply_txt, "APPLY", &lexend_14pt_2bpp, UGUI_THEME_KNOB,
                   UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_box_set_text(&s_apply, &s_apply_txt);
#endif
ugui_screen_add_widget(&s_screen, &s_apply);

/* Re-measure content + re-clamp now that every child is attached. */
ugui_widget_container_relayout(&s_cont);


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.