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.

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

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

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

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