Skip to content

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.

Default HContainer

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

Styled (rounded, framed, custom 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_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);

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 Gallery Carousel

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