Fold Widget¶
The Fold is an accordion / tree menu — collapsible sections with an
animated reveal, the shape of a real device settings tree. Init installs
the whole look (theme-tracked colours, default size, tracker bar, and the
slide/key/click/confirm/cancel event mask) at a settable size; set_font()
gives it text and set_items() attaches the tree via a caller-owned
ugui_fold_item_t array — expanded state and reveal-animation height live
IN the array, so it must never be shared between two folds. Tapping (or
ENTER on) a header expands or collapses it, animated; a leaf fires
on_select. Rows can even carry an EMBEDDED child widget (a slider, a
toggle) — parented to the fold, positioned by its own draw loop, hit-tested
directly by the input layer, with a tap on the row's label forwarding to the
embed. set_theme() replaces every colour with a fixed palette that ignores
the app theme, and set_expanded() opens a section programmatically (e.g.
the default section on screen entry).
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 Fold¶
Three calls make a working accordion menu: ugui_widget_fold_init() installs the whole look, size and event mask, set_font() gives it text, set_items() attaches the tree with every section collapsed. Tapping a header expands or collapses it with an animated reveal — the widget's own tick drives it, no per-frame app code.

/* --- 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_FOLD_MENU)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_fold;
static ugui_fold_ext_t s_fold_ext;
static ugui_fold_item_t s_items[] = {
{ "Display", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Brightness", 1u, false, 0u, 10u, false, 0, NULL, 0u },
{ " Night mode", 1u, false, 0u, 11u, false, 0, NULL, 0u },
{ "Audio", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Volume", 1u, false, 0u, 20u, false, 0, NULL, 0u },
{ " Key click", 1u, false, 0u, 21u, false, 0, NULL, 0u },
{ "Network", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Wi-Fi", 1u, false, 0u, 30u, false, 0, NULL, 0u },
{ " Bluetooth", 1u, false, 0u, 31u, false, 0, NULL, 0u },
};
#define ITEM_N ((uint8_t)(sizeof(s_items) / sizeof(s_items[0])))
#endif
/* --- local helpers & event callbacks --- */
static void on_select(uint8_t item_id, void* ctx)
{
(void)ctx;
printf("[fold.default] leaf selected: id=%u\n", (unsigned)item_id);
}
/* --- build it (in your screen's init) --- */
ugui_widget_fold_init(&s_root, &s_fold, &s_fold_ext);
ugui_widget_set_align(&s_fold, UGUI_ALIGN_CENTER, 0, 10);
ugui_widget_set_id(&s_fold, WDGT_ID_DEMO);
#ifdef UGUI_ENABLE_FONT
ugui_widget_fold_set_font(&s_fold, &lexend_14pt_2bpp);
#endif
ugui_widget_fold_set_items(&s_fold, s_items, ITEM_N);
ugui_widget_fold_set_on_select(&s_fold, on_select, NULL);
ugui_screen_add_widget(&s_screen, &s_fold);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_fold);
#endif
Deep Tree & Scroll¶
Six sections / 26 rows in a fixed-height viewport, so expanding a couple of sections overflows it. The tracker bar appears automatically on overflow and its thumb length/position track the content; key navigation auto-scrolls so the focused row always stays inside the viewport; collapsed sections cost no rows.

/* --- 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_FOLD_MENU)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_fold;
static ugui_fold_ext_t s_fold_ext;
static ugui_fold_item_t s_items[] = {
{ "Motor", 0u, true, 4u, 0u, false, 0, NULL, 0u },
{ " Speed limit", 1u, false, 0u, 10u, false, 0, NULL, 0u },
{ " Acceleration", 1u, false, 0u, 11u, false, 0, NULL, 0u },
{ " Direction", 1u, false, 0u, 12u, false, 0, NULL, 0u },
{ " Brake mode", 1u, false, 0u, 13u, false, 0, NULL, 0u },
{ "Sensors", 0u, true, 3u, 0u, false, 0, NULL, 0u },
{ " Calibrate", 1u, false, 0u, 20u, false, 0, NULL, 0u },
{ " Sample rate", 1u, false, 0u, 21u, false, 0, NULL, 0u },
{ " Filter", 1u, false, 0u, 22u, false, 0, NULL, 0u },
{ "Limits", 0u, true, 3u, 0u, false, 0, NULL, 0u },
{ " Max current", 1u, false, 0u, 30u, false, 0, NULL, 0u },
{ " Max temp", 1u, false, 0u, 31u, false, 0, NULL, 0u },
{ " Soft stop", 1u, false, 0u, 32u, false, 0, NULL, 0u },
{ "Comms", 0u, true, 4u, 0u, false, 0, NULL, 0u },
{ " CAN bitrate", 1u, false, 0u, 40u, false, 0, NULL, 0u },
{ " Node ID", 1u, false, 0u, 41u, false, 0, NULL, 0u },
{ " Heartbeat", 1u, false, 0u, 42u, false, 0, NULL, 0u },
{ " Timeout", 1u, false, 0u, 43u, false, 0, NULL, 0u },
{ "Logging", 0u, true, 3u, 0u, false, 0, NULL, 0u },
{ " Log level", 1u, false, 0u, 50u, false, 0, NULL, 0u },
{ " Export", 1u, false, 0u, 51u, false, 0, NULL, 0u },
{ " Clear log", 1u, false, 0u, 52u, false, 0, NULL, 0u },
{ "Service", 0u, true, 3u, 0u, false, 0, NULL, 0u },
{ " Self test", 1u, false, 0u, 60u, false, 0, NULL, 0u },
{ " Firmware", 1u, false, 0u, 61u, false, 0, NULL, 0u },
{ " Factory reset", 1u, false, 0u, 62u, false, 0, NULL, 0u },
};
#define ITEM_N ((uint8_t)(sizeof(s_items) / sizeof(s_items[0])))
#define FOLD_X 10
#define FOLD_Y (38 + 2)
#define FOLD_W 260
#define FOLD_H ((int16_t)(UGUI_SCREEN_H - FOLD_Y - 10))
#endif
/* --- local helpers & event callbacks --- */
static void on_select(uint8_t item_id, void* ctx)
{
(void)ctx;
printf("[fold.scroll] leaf selected: id=%u\n", (unsigned)item_id);
}
/* --- build it (in your screen's init) --- */
ugui_widget_fold_init(&s_root, &s_fold, &s_fold_ext);
ugui_widget_set_area(&s_fold, FOLD_X, FOLD_Y, FOLD_W, FOLD_H);
ugui_widget_set_id(&s_fold, WDGT_ID_DEMO);
#ifdef UGUI_ENABLE_FONT
ugui_widget_fold_set_font(&s_fold, &lexend_14pt_2bpp);
#endif
ugui_widget_fold_set_items(&s_fold, s_items, ITEM_N);
ugui_widget_fold_set_on_select(&s_fold, on_select, NULL);
ugui_screen_add_widget(&s_screen, &s_fold);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_fold);
#endif
Styling & Programmatic State¶
LEFT: the whole look comes from init() (it tracks the active theme); the only extra call is set_expanded(0, true) so the first section plays its reveal animation as the screen appears. RIGHT: full custom skin — set_theme() replaces all seven colours with a fixed amber industrial palette and set_scrollbar() matches the tracker bar to it.

/* --- 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_SLOT2 = 10, WDGT_ID_SLOT3
};
/* --- 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_FOLD_MENU)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_fold_a; /* left: theme defaults */
static ugui_fold_ext_t s_fold_a_ext;
static ugui_widget_t s_fold_b; /* right: custom set_theme */
static ugui_fold_ext_t s_fold_b_ext;
static ugui_fold_item_t s_items_a[] = {
{ "Presets", 0u, true, 3u, 0u, false, 0, NULL, 0u },
{ " Eco", 1u, false, 0u, 10u, false, 0, NULL, 0u },
{ " Normal", 1u, false, 0u, 11u, false, 0, NULL, 0u },
{ " Sport", 1u, false, 0u, 12u, false, 0, NULL, 0u },
{ "Advanced", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Tuning", 1u, false, 0u, 20u, false, 0, NULL, 0u },
{ " Telemetry", 1u, false, 0u, 21u, false, 0, NULL, 0u },
};
#define ITEM_A_N ((uint8_t)(sizeof(s_items_a) / sizeof(s_items_a[0])))
static ugui_fold_item_t s_items_b[] = {
{ "Furnace", 0u, true, 3u, 0u, false, 0, NULL, 0u },
{ " Setpoint", 1u, false, 0u, 10u, false, 0, NULL, 0u },
{ " Ramp rate", 1u, false, 0u, 11u, false, 0, NULL, 0u },
{ " Soak time", 1u, false, 0u, 12u, false, 0, NULL, 0u },
{ "Alarms", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Over-temp", 1u, false, 0u, 20u, false, 0, NULL, 0u },
{ " Door open", 1u, false, 0u, 21u, false, 0, NULL, 0u },
};
#define ITEM_B_N ((uint8_t)(sizeof(s_items_b) / sizeof(s_items_b[0])))
#define COL_W ((int16_t)((UGUI_SCREEN_W - 3 * 10) / 2))
#define COL0_X ((int16_t)10)
#define COL1_X ((int16_t)(2 * 10 + COL_W))
#define FOLD_Y (38 + 24)
#define FOLD_H ((int16_t)(UGUI_SCREEN_H - FOLD_Y - 10))
#endif
/* --- build it (in your screen's init) --- */
/* LEFT — library look untouched; open the first section programmatically
* so the screen appears with its default section already revealed. */
ugui_widget_fold_init(&s_root, &s_fold_a, &s_fold_a_ext);
ugui_widget_set_area(&s_fold_a, COL0_X, FOLD_Y, COL_W, FOLD_H);
ugui_widget_set_id(&s_fold_a, WDGT_ID_SLOT2);
#ifdef UGUI_ENABLE_FONT
ugui_widget_fold_set_font(&s_fold_a, &lexend_14pt_2bpp);
#endif
ugui_widget_fold_set_items(&s_fold_a, s_items_a, ITEM_A_N);
ugui_widget_fold_set_expanded(&s_fold_a, 0u, true);
ugui_screen_add_widget(&s_screen, &s_fold_a);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_fold_a);
#endif
/* RIGHT — replace every colour with an amber industrial palette (fixed
* hex values on purpose: this fold must NOT follow the theme switcher),
* then match the tracker bar to it. */
ugui_widget_fold_init(&s_root, &s_fold_b, &s_fold_b_ext);
ugui_widget_set_area(&s_fold_b, COL1_X, FOLD_Y, COL_W, FOLD_H);
ugui_widget_set_id(&s_fold_b, WDGT_ID_SLOT3);
ugui_widget_fold_set_theme(&s_fold_b,
ugui_color_hex(0x201608u), /* surface */
ugui_color_hex(0x3A2A10u), /* header fill */
ugui_color_hex(0xFFC060u), /* header text */
ugui_color_hex(0xD0B080u), /* leaf text */
ugui_color_hex(0xFFA020u), /* focus fill */
ugui_color_hex(0x201608u), /* focus text */
ugui_color_hex(0xFFC060u), /* chevron */
#ifdef UGUI_ENABLE_FONT
&lexend_14pt_2bpp);
#else
NULL);
#endif
#if UGUI_FOLD_ENABLE_SCROLLBAR
ugui_widget_fold_set_scrollbar(&s_fold_b, true,
ugui_color_hex(0xFFA020u), /* thumb */
ugui_color_hex(0x2E2110u)); /* track */
#endif
ugui_widget_fold_set_items(&s_fold_b, s_items_b, ITEM_B_N);
ugui_widget_fold_set_expanded(&s_fold_b, 4u, true); /* open "Alarms" */
ugui_screen_add_widget(&s_screen, &s_fold_b);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_fold_b);
#endif
Settings Tree with Live Controls¶
The realistic case: sliders (Brightness, Volume) and toggles (Night mode, Mute, Wi-Fi, Bluetooth) live INSIDE fold rows as embedded child widgets — the fold positions and hit-tests them directly, and tapping a row's label forwards the click to its embed. Plain leaves (Profiles) confirm the classic on_select menu-command path. Every interaction writes a line into the status bar.

/* --- 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_SLOT4
};
/* --- 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_FOLD_MENU) && defined(UGUI_WIDGET_ENABLE_TOGGLE_BUTTON) && defined(UGUI_WIDGET_ENABLE_SLIDER)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_status;
static ugui_box_ext_t s_status_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_FOLD_MENU) && defined(UGUI_WIDGET_ENABLE_TOGGLE_BUTTON) && defined(UGUI_WIDGET_ENABLE_SLIDER)
static ugui_text_cfg_t s_status_text;
#endif
#if defined(UGUI_WIDGET_ENABLE_FOLD_MENU) && defined(UGUI_WIDGET_ENABLE_TOGGLE_BUTTON) && defined(UGUI_WIDGET_ENABLE_SLIDER)
static ugui_widget_t s_fold;
static ugui_fold_ext_t s_fold_ext;
#define SLIDER_N 2u
#define TOGGLE_N 4u
static ugui_widget_t s_slider_w [SLIDER_N];
static ugui_slider_ext_t s_slider_ext[SLIDER_N];
static ugui_widget_t s_toggle_w [TOGGLE_N];
static ugui_toggle_ext_t s_toggle_ext[TOGGLE_N];
static ugui_fold_item_t s_items[] = {
{ "Display", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Brightness", 1u, false, 0u, 10u, false, 0, NULL, 0u }, /* slider */
{ " Night mode", 1u, false, 0u, 11u, false, 0, NULL, 0u }, /* toggle */
{ "Sound", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Volume", 1u, false, 0u, 20u, false, 0, NULL, 0u }, /* slider */
{ " Mute", 1u, false, 0u, 21u, false, 0, NULL, 0u }, /* toggle */
{ "Network", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Wi-Fi", 1u, false, 0u, 30u, false, 0, NULL, 0u }, /* toggle */
{ " Bluetooth", 1u, false, 0u, 31u, false, 0, NULL, 0u }, /* toggle */
{ "Profiles", 0u, true, 2u, 0u, false, 0, NULL, 0u },
{ " Day profile", 1u, false, 0u, 40u, false, 0, NULL, 0u }, /* plain */
{ " Night profile", 1u, false, 0u, 41u, false, 0, NULL, 0u }, /* plain */
};
#define ITEM_N ((uint8_t)(sizeof(s_items) / sizeof(s_items[0])))
#define FOLD_X 10
#define FOLD_Y (38 + 4)
#define FOLD_W 290
#define STATUS_H 28
#define STATUS_Y ((int16_t)(UGUI_SCREEN_H - STATUS_H - 10))
#define FOLD_H ((int16_t)(STATUS_Y - FOLD_Y - 8))
#endif
#if defined(UGUI_WIDGET_ENABLE_FOLD_MENU) && defined(UGUI_WIDGET_ENABLE_TOGGLE_BUTTON) && defined(UGUI_WIDGET_ENABLE_SLIDER)
static char s_status_buf[64];
#endif
/* --- local helpers & event callbacks --- */
static void status_show(const char* name, const char* val)
{
#ifdef UGUI_ENABLE_FONT
if (*name == ' ') { name++; }
(void)snprintf(s_status_buf, sizeof(s_status_buf), "%s: %s", name, val);
ugui_widget_box_update_text_str(&s_status, s_status_buf);
#else
(void)name; (void)val;
#endif
}
static void on_embed_slider(int16_t val_lo, int16_t val_hi, void* ctx)
{
(void)val_hi;
#ifdef UGUI_ENABLE_FONT
char num[8];
(void)snprintf(num, sizeof(num), "%d%%", (int)val_lo);
status_show((const char*)ctx, num);
#else
(void)val_lo; (void)ctx;
#endif
}
static void on_embed_toggle(bool state, void* ctx)
{
status_show((const char*)ctx, state ? "ON" : "OFF");
}
static void on_select(uint8_t item_id, void* ctx)
{
(void)ctx;
uint8_t i;
for (i = 0u; i < ITEM_N; i++) {
if ((s_items[i].depth == 1u) && (s_items[i].id == item_id)) {
status_show(s_items[i].label, "loaded");
return;
}
}
}
/* --- build it (in your screen's init) --- */
ugui_widget_fold_init(&s_root, &s_fold, &s_fold_ext);
ugui_widget_set_area(&s_fold, FOLD_X, FOLD_Y, FOLD_W, FOLD_H);
ugui_widget_set_id(&s_fold, WDGT_ID_DEMO);
#ifdef UGUI_ENABLE_FONT
ugui_widget_fold_set_font(&s_fold, &lexend_14pt_2bpp);
#endif
ugui_widget_fold_set_items(&s_fold, s_items, ITEM_N);
ugui_widget_fold_set_on_select(&s_fold, on_select, NULL);
/* Slider embeds (Brightness, Volume). Children of the FOLD, not of the
* screen: the fold positions them inside their rows and the input layer
* hit-tests them directly. Theme colours come from slider init. */
{
static const uint8_t k_slider_rows[SLIDER_N] = { 1u, 4u };
static const int16_t k_slider_init[SLIDER_N] = { 70, 40 };
uint8_t si;
for (si = 0u; si < SLIDER_N; si++) {
ugui_fold_item_t* row = &s_items[k_slider_rows[si]];
ugui_widget_slider_init(&s_fold, &s_slider_w[si], &s_slider_ext[si]);
ugui_widget_slider_set_range(&s_slider_w[si], 0, 100);
ugui_widget_slider_set_value(&s_slider_w[si], k_slider_init[si]);
ugui_widget_slider_set_callback(&s_slider_w[si], on_embed_slider,
(void*)row->label);
ugui_widget_set_enabled(&s_slider_w[si], true);
row->embed = &s_slider_w[si];
row->embed_w = 110u;
}
}
/* Toggle embeds (Night mode, Mute, Wi-Fi, Bluetooth). Wi-Fi starts ON. */
{
static const uint8_t k_toggle_rows[TOGGLE_N] = { 2u, 5u, 7u, 8u };
uint8_t ti;
for (ti = 0u; ti < TOGGLE_N; ti++) {
ugui_fold_item_t* row = &s_items[k_toggle_rows[ti]];
ugui_widget_toggle_init(&s_fold, &s_toggle_w[ti], &s_toggle_ext[ti]);
ugui_widget_toggle_set_callback(&s_toggle_w[ti], on_embed_toggle,
(void*)row->label);
ugui_widget_set_enabled(&s_toggle_w[ti], true);
row->embed = &s_toggle_w[ti];
row->embed_w = 52u;
}
ugui_widget_toggle_set_state(&s_toggle_w[2], true); /* Wi-Fi ON */
}
/* Default state: Display section open when the screen appears. */
ugui_widget_fold_set_expanded(&s_fold, 0u, true);
ugui_screen_add_widget(&s_screen, &s_fold);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_fold);
#endif
/* Live status bar (filled card) across the foot. */
ugui_widget_box_init(&s_root, &s_status, &s_status_ext);
ugui_widget_set_area(&s_status, 10, STATUS_Y,
(int16_t)(UGUI_SCREEN_W - 20), STATUS_H);
ugui_widget_set_id(&s_status, WDGT_ID_SLOT4);
ugui_widget_box_set_style(&s_status, true, 4u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
(void)snprintf(s_status_buf, sizeof(s_status_buf),
"Interact with a row - every change lands here");
ugui_text_cfg_init(&s_status_text, s_status_buf, &lexend_14pt_2bpp, UGUI_THEME_ACCENT,
UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_box_set_text(&s_status, &s_status_text);
#endif
ugui_widget_set_enabled(&s_status, false); /* display-only */
ugui_screen_add_widget(&s_screen, &s_status);
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.