HContainer Widget¶
The HContainer is the lean horizontal-scroll container — the mirror of
VContainer: horizontal scroll plus a bottom-edge bar, no vertical axis and
no keypad focus-scope overhead. Init installs the whole look and
set_area() places it; parent a row of tiles wider than the panel and the
bar appears automatically. set_style() restyles the frame and
set_scrollbar_colors() restyles the bar. Like VContainer, each child keeps
its OWN keypad focus stop — TAB moves between them and the hcontainer's
per-frame tick auto-scrolls the focused one into view, no focus-scope code
needed.
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 HContainer¶
Two calls make it usable: ugui_widget_hcontainer_init() installs the default look and set_area() places it. A row of tiles wider than the panel (tile count derived from the panel width at init) makes the scrollbar appear automatically — drag the panel (touch) or TAB to it and press left/right (keypad) to drive 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_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_HCONTAINER)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_hc;
static ugui_hcontainer_ext_t s_hc_ext;
#define MAX_TILES ((uint8_t)UGUI_MAX_CHILDREN)
#define TILE_EXTRA 2u
static ugui_widget_t s_tile[MAX_TILES];
static ugui_box_ext_t s_tile_ext[MAX_TILES];
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
static ugui_text_cfg_t s_tile_txt[MAX_TILES];
static char s_tile_num[MAX_TILES][4];
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
#define PAN_X ((int16_t)12)
#define PAN_Y ((int16_t)(38 + 20))
#define PAN_W ((int16_t)(UGUI_SCREEN_W - 24))
#define PAN_H ((int16_t)140)
#define TILE_W 90
#define TILE_GAP 8
#define TILE_PAD 8
#define TILE_Y 8
#define TILE_H ((int16_t)(PAN_H - TILE_Y - 14)) /* leave the bottom bar room */
#define TILE_STEP (TILE_W + 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) --- */
const ugui_color_t tile_fill =
ugui_color_blend(UGUI_THEME_BORDER, UGUI_THEME_SURFACE, 56u);
ugui_widget_hcontainer_init(&s_root, &s_hc, &s_hc_ext);
ugui_widget_set_area(&s_hc, PAN_X, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_hc, WDGT_ID_DEMO);
ugui_widget_hcontainer_set_scroll_step(&s_hc, TILE_STEP);
ugui_screen_add_widget(&s_screen, &s_hc);
uint8_t tiles = fill_row_count(PAN_W, TILE_STEP, TILE_EXTRA, MAX_TILES);
for (uint8_t i = 0u; i < tiles; i++) {
ugui_widget_box_init(&s_hc, &s_tile[i], &s_tile_ext[i]);
ugui_widget_set_area(&s_tile[i], (int16_t)(TILE_PAD + i * TILE_STEP),
TILE_Y, TILE_W, TILE_H);
ugui_widget_set_id(&s_tile[i], (uint8_t)(WDGT_ID_SLOT0 + i));
ugui_widget_box_set_style(&s_tile[i], true, 6u, 0u, tile_fill, UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
(void)snprintf(s_tile_num[i], sizeof(s_tile_num[i]), "%u", (unsigned)(i + 1u));
ugui_text_cfg_init(&s_tile_txt[i], s_tile_num[i], &lexend_14pt_2bpp,
UGUI_THEME_TEXT, UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_box_set_text(&s_tile[i], &s_tile_txt[i]);
#endif
ugui_screen_add_widget(&s_screen, &s_tile[i]);
}
ugui_widget_hcontainer_relayout(&s_hc);
Styled (rounded, framed, custom bar)¶
The same scrollable strip, restyled: set_style() gives it a soft framed-card look (2 px border, 14 px corner radius) and set_scrollbar_colors() recolours the bar. The large corner radius makes the bottom bar's geometry visible — it sits just inside the border and its ends are trimmed by the radius.

/* --- 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_HCONTAINER)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_hc;
static ugui_hcontainer_ext_t s_hc_ext;
#define MAX_TILES ((uint8_t)UGUI_MAX_CHILDREN)
#define TILE_EXTRA 2u
static ugui_widget_t s_tile[MAX_TILES];
static ugui_box_ext_t s_tile_ext[MAX_TILES];
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
static ugui_text_cfg_t s_tile_txt[MAX_TILES];
static char s_tile_num[MAX_TILES][4];
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
#define PAN_X ((int16_t)12)
#define PAN_Y ((int16_t)(38 + 20))
#define PAN_W ((int16_t)(UGUI_SCREEN_W - 24))
#define PAN_H ((int16_t)150)
#define TILE_W 90
#define TILE_GAP 10
#define TILE_PAD 16
#define TILE_Y 16
#define TILE_H ((int16_t)(PAN_H - TILE_Y - 22))
#define TILE_STEP (TILE_W + 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) --- */
const ugui_color_t groove =
ugui_color_blend(UGUI_THEME_BORDER, UGUI_THEME_SURFACE, 96u);
const ugui_color_t tile_fill =
ugui_color_blend(UGUI_THEME_BORDER, UGUI_THEME_SURFACE, 56u);
ugui_widget_hcontainer_init(&s_root, &s_hc, &s_hc_ext);
ugui_widget_set_area(&s_hc, PAN_X, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_hc, WDGT_ID_DEMO);
ugui_widget_hcontainer_set_style(&s_hc, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT, 2u, 14u);
ugui_widget_hcontainer_set_scrollbar_colors(&s_hc, UGUI_THEME_ACCENT, groove);
ugui_widget_hcontainer_set_scroll_step(&s_hc, TILE_STEP);
s_hc_ext.scrollbar_w = 6u;
ugui_screen_add_widget(&s_screen, &s_hc);
uint8_t tiles = fill_row_count(PAN_W, TILE_STEP, TILE_EXTRA, MAX_TILES);
for (uint8_t i = 0u; i < tiles; i++) {
ugui_widget_box_init(&s_hc, &s_tile[i], &s_tile_ext[i]);
ugui_widget_set_area(&s_tile[i], (int16_t)(TILE_PAD + i * TILE_STEP),
TILE_Y, TILE_W, TILE_H);
ugui_widget_set_id(&s_tile[i], (uint8_t)(WDGT_ID_SLOT0 + i));
ugui_widget_box_set_style(&s_tile[i], true, 8u, 0u, tile_fill, UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
(void)snprintf(s_tile_num[i], sizeof(s_tile_num[i]), "%u", (unsigned)(i + 1u));
ugui_text_cfg_init(&s_tile_txt[i], s_tile_num[i], &lexend_14pt_2bpp,
UGUI_THEME_TEXT, UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_box_set_text(&s_tile[i], &s_tile_txt[i]);
#endif
ugui_screen_add_widget(&s_screen, &s_tile[i]);
}
ugui_widget_hcontainer_relayout(&s_hc);
Widget Gallery Carousel¶
A horizontal strip of cards wider than the panel, each showcasing a DIFFERENT interactive widget — a button, a toggle, an arc gauge/knob, a slider, a checkbox and a second arc. Like the vcontainer list, each widget is its own keypad focus stop: TAB moves focus across the cards and the hcontainer's tick auto-scrolls the focused card into view.

/* --- 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_HCONTAINER)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_hc;
static ugui_hcontainer_ext_t s_hc_ext;
static ugui_widget_t s_btn; static ugui_box_ext_t s_btn_ext;
static ugui_widget_t s_tgl; static ugui_toggle_ext_t s_tgl_ext;
static ugui_widget_t s_sld; static ugui_slider_ext_t s_sld_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
static ugui_text_cfg_t s_btn_txt;
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
static ugui_widget_t s_chk; static ugui_checkbox_ext_t s_chk_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
static ugui_text_cfg_t s_chk_txt;
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
static ugui_widget_t s_arc1; static ugui_arc_ext_t s_arc1_ext;
static ugui_widget_t s_arc2; static ugui_arc_ext_t s_arc2_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_HCONTAINER)
#define PAN_X ((int16_t)12)
#define PAN_Y ((int16_t)(38 + 12))
#define PAN_W ((int16_t)(UGUI_SCREEN_W - 24))
#define PAN_H ((int16_t)160)
#define CARD_W 124
#define CARD_GAP 8
#define CARD_PAD 10
#define CARD_STEP (CARD_W + CARD_GAP)
#define WGT_Y 44 /* widget row */
#endif
/* --- local helpers & event callbacks --- */
static void on_bool(bool state, void* ctx)
{
printf("[hcontainer.carousel] %s = %s\n", (const char*)ctx, state ? "ON" : "OFF");
}
static void on_slider(int16_t lo, int16_t hi, void* ctx)
{
(void)hi;
printf("[hcontainer.carousel] %s = %d\n", (const char*)ctx, (int)lo);
}
static void on_btn(uint8_t id, ugui_event_t ev)
{
(void)id;
if (ev == UGUI_EVENT_CLICK) { printf("[hcontainer.carousel] Button clicked\n"); }
}
static void on_arc(int16_t value, void* ctx)
{
printf("[hcontainer.carousel] %s = %d\n", (const char*)ctx, (int)value);
}
static void card_caption(int16_t card_x, const char* text) { (void)card_x; (void)text; }
/* --- build it (in your screen's init) --- */
const ugui_color_t groove =
ugui_color_blend(UGUI_THEME_BORDER, UGUI_THEME_SURFACE, 96u);
ugui_widget_hcontainer_init(&s_root, &s_hc, &s_hc_ext);
ugui_widget_set_area(&s_hc, PAN_X, PAN_Y, PAN_W, PAN_H);
ugui_widget_set_id(&s_hc, WDGT_ID_DEMO);
ugui_widget_hcontainer_set_style(&s_hc, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT, 1u, 8u);
ugui_widget_hcontainer_set_scroll_step(&s_hc, CARD_STEP);
ugui_screen_add_widget(&s_screen, &s_hc);
int16_t cx = CARD_PAD;
/* Card 1 — button */
card_caption(cx, "Button");
ugui_widget_box_init(&s_hc, &s_btn, &s_btn_ext);
ugui_widget_set_area(&s_btn, (int16_t)(cx + 14), (int16_t)(WGT_Y + 12), 96, 44);
ugui_widget_box_set_style(&s_btn, true, 8u, 1u, UGUI_THEME_ACCENT, UGUI_THEME_ACCENT);
ugui_widget_set_event_cb(&s_btn, on_btn,
UGUI_EMASK_CLICK | UGUI_EMASK_DOWN | UGUI_EMASK_UP
| UGUI_EMASK_KEY_CONFIRM);
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_btn_txt, "Tap", &lexend_14pt_2bpp, UGUI_THEME_KNOB,
UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_box_set_text(&s_btn, &s_btn_txt);
#endif
ugui_screen_add_widget(&s_screen, &s_btn);
cx = (int16_t)(cx + CARD_STEP);
/* Card 2 — toggle */
card_caption(cx, "Toggle");
ugui_widget_toggle_init(&s_hc, &s_tgl, &s_tgl_ext);
ugui_widget_set_area(&s_tgl, (int16_t)(cx + (CARD_W - 60) / 2), (int16_t)(WGT_Y + 20), 60, 32);
ugui_widget_toggle_set_colors(&s_tgl, groove, UGUI_THEME_OK, UGUI_THEME_KNOB);
ugui_widget_toggle_set_state(&s_tgl, true);
ugui_widget_toggle_set_callback(&s_tgl, on_bool, (void*)"Toggle");
ugui_screen_add_widget(&s_screen, &s_tgl);
cx = (int16_t)(cx + CARD_STEP);
/* Card 3 — arc knob */
#ifdef UGUI_WIDGET_ENABLE_ARC
card_caption(cx, "Arc knob");
ugui_widget_arc_init(&s_hc, &s_arc1, &s_arc1_ext);
ugui_widget_set_area(&s_arc1, (int16_t)(cx + (CARD_W - 74) / 2), (int16_t)(WGT_Y + 6), 74, 74);
ugui_widget_arc_set_range(&s_arc1, 0, 100);
ugui_widget_arc_set_value(&s_arc1, 65);
ugui_widget_arc_set_thickness(&s_arc1, 8u);
ugui_widget_arc_set_step(&s_arc1, 5);
ugui_widget_arc_set_colors(&s_arc1, UGUI_THEME_ACCENT, groove, UGUI_THEME_SURFACE);
ugui_widget_arc_set_callback(&s_arc1, on_arc, (void*)"Arc knob");
ugui_screen_add_widget(&s_screen, &s_arc1);
cx = (int16_t)(cx + CARD_STEP);
#endif
/* Card 4 — slider */
card_caption(cx, "Slider");
ugui_widget_slider_init(&s_hc, &s_sld, &s_sld_ext);
ugui_widget_set_area(&s_sld, (int16_t)(cx + 12), (int16_t)(WGT_Y + 26), (int16_t)(CARD_W - 24), 26);
ugui_widget_slider_set_range(&s_sld, 0, 100);
ugui_widget_slider_set_value(&s_sld, 55);
ugui_widget_slider_set_callback(&s_sld, on_slider, (void*)"Slider");
ugui_screen_add_widget(&s_screen, &s_sld);
cx = (int16_t)(cx + CARD_STEP);
/* Card 5 — checkbox */
#ifdef UGUI_WIDGET_ENABLE_CHECKBOX
card_caption(cx, "Checkbox");
ugui_widget_checkbox_init(&s_hc, &s_chk, &s_chk_ext);
ugui_widget_set_area(&s_chk, (int16_t)(cx + 24), (int16_t)(WGT_Y + 22), 80, 30);
ugui_widget_checkbox_set_callback(&s_chk, on_bool, (void*)"Checkbox");
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_chk_txt, "On", &lexend_14pt_2bpp, UGUI_THEME_TEXT,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
(ugui_point_t){ 0, 0 }, 1u);
ugui_widget_checkbox_set_text(&s_chk, &s_chk_txt);
#endif
ugui_screen_add_widget(&s_screen, &s_chk);
cx = (int16_t)(cx + CARD_STEP);
#endif
/* Card 6 — arc gauge (a second arc, styled as a read-out ring) */
#ifdef UGUI_WIDGET_ENABLE_ARC
card_caption(cx, "Arc gauge");
ugui_widget_arc_init(&s_hc, &s_arc2, &s_arc2_ext);
ugui_widget_set_area(&s_arc2, (int16_t)(cx + (CARD_W - 74) / 2), (int16_t)(WGT_Y + 6), 74, 74);
ugui_widget_arc_set_range(&s_arc2, 0, 100);
ugui_widget_arc_set_value(&s_arc2, 80);
ugui_widget_arc_set_thickness(&s_arc2, 8u);
ugui_widget_arc_set_step(&s_arc2, 5);
ugui_widget_arc_set_angles(&s_arc2, 135, 270);
ugui_widget_arc_set_colors(&s_arc2, UGUI_THEME_OK, groove, UGUI_THEME_SURFACE);
ugui_widget_arc_set_callback(&s_arc2, on_arc, (void*)"Arc gauge");
ugui_screen_add_widget(&s_screen, &s_arc2);
cx = (int16_t)(cx + CARD_STEP);
#endif
ugui_widget_hcontainer_relayout(&s_hc);
#if UGUI_ENABLE_KEYPAD
/* Each widget is its own focus stop; the hcontainer's tick scrolls the
* focused card into view. Order = visual left-to-right. */
ugui_screen_add_focus(&s_screen, &s_btn);
ugui_screen_add_focus(&s_screen, &s_tgl);
#ifdef UGUI_WIDGET_ENABLE_ARC
ugui_screen_add_focus(&s_screen, &s_arc1);
#endif
ugui_screen_add_focus(&s_screen, &s_sld);
#ifdef UGUI_WIDGET_ENABLE_CHECKBOX
ugui_screen_add_focus(&s_screen, &s_chk);
#endif
#ifdef UGUI_WIDGET_ENABLE_ARC
ugui_screen_add_focus(&s_screen, &s_arc2);
#endif
#endif
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.