Skip to content

List Widget

The List is a kinetic scrolling list — a vertically-scrollable, virtual row view with touch inertia, for a long, drag-to-browse collection (a track list, a contact book). Init installs the whole look (theme-tracked surface + focus colours, tracker bar) at a settable size; set_content() sets the item count, row height and initial focus. Rows are virtual — the widget only ever draws the handful that fit the viewport, so a 5-row and a 1000-row list cost the same RAM — and the application supplies a mandatory draw_item callback (assigned directly to the ext struct, no setter function) to paint each visible row: the widget has already filled the row's background (surface, or the focused row's highlight) before calling it, so draw_item only needs ugui_font_draw_text_cfg() with cfg.use_widget_area = 0 and cfg.area set to the row-relative rect it is given. Drag (or the keypad) scrolls with inertia; set_theme() replaces every colour with a fixed palette independent of the active app theme; ext.on_select (also a direct field assignment) fires on KEY_CONFIRM / OK with the focused row; scroll_to() snaps programmatically to any index with no animation.

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 Track List

Three calls make a working kinetic list: ugui_widget_list_init() installs the whole look, set_content() sets the item count and row height, and the required draw_item callback paints each row's label — the widget already painted the background and (for the focused row) the highlight before calling it. Drag up/down to scroll with inertia; the tracker bar on the right shows how far into the 20-track list the viewport sits.

Default Track List

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

#include "ugui_font.h"

/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_KINETIC_LIST)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_list;
static ugui_list_ext_t  s_list_ext;
#define LIST_X   10
#define LIST_Y   38
#define LIST_W   (UGUI_SCREEN_W - 2 * LIST_X)
#define LIST_H   (UGUI_SCREEN_H - LIST_Y - 8)
#define ROW_H    32
#define TRACK_N  20u
static char s_track[TRACK_N][12];
#endif

/* --- local helpers & event callbacks --- */
static void draw_item(ugui_widget_t* w, uint16_t idx, const ugui_rect_t* item_rect,
                      bool focused, void* ctx)
{
    (void)ctx;
#ifdef UGUI_ENABLE_FONT
    if (idx >= TRACK_N) { return; }
    ugui_text_cfg_t cfg;
    ugui_text_cfg_init(&cfg, s_track[idx], &lexend_14pt_2bpp,
                       focused ? UGUI_THEME_ACCENT : UGUI_THEME_TEXT,
                       (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
                       (ugui_point_t){ 14, 0 }, 0u);
    cfg.area = *item_rect;
    ugui_font_draw_text_cfg(w, &cfg, 0u);
#else
    (void)w; (void)idx; (void)item_rect; (void)focused;
#endif
}

/* --- build it (in your screen's init) --- */
ugui_widget_list_init(&s_root, &s_list, &s_list_ext);
ugui_widget_set_area(&s_list, LIST_X, LIST_Y, LIST_W, LIST_H);
ugui_widget_set_id(&s_list, WDGT_ID_DEMO);
ugui_widget_list_set_content(&s_list, (uint16_t)TRACK_N, ROW_H, 0u);
s_list_ext.draw_item = draw_item;
ugui_screen_add_widget(&s_screen, &s_list);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_list);
#endif

Styled Contacts + on_select

set_theme() replaces the surface/focus colours with a fixed "midnight" palette, independent of the active app theme; set_content()'s init_idx starts the list already focused on "Frank" (index 5) — the widget snaps there with no animation. ext.on_select (no dedicated setter — assigned directly, same as the numpad/alphanum OK/Cancel callbacks) fires on KEY_CONFIRM / OK and writes the picked name into the status line.

Styled Contacts + on_select

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

#include "ugui_font.h"

/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_KINETIC_LIST)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t   s_status;
#endif
#if defined(UGUI_WIDGET_ENABLE_KINETIC_LIST)
static char            s_status_str[32];
#endif
#if defined(UGUI_WIDGET_ENABLE_KINETIC_LIST)
static ugui_widget_t    s_list;
static ugui_list_ext_t  s_list_ext;
#define LIST_X   10
#define LIST_Y   38
#define LIST_W   (UGUI_SCREEN_W - 2 * LIST_X)
#define LIST_H   (UGUI_SCREEN_H - LIST_Y - 40)
#define ROW_H    30
static const char* const k_name[] = {
    "Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace",
    "Hank", "Ivy", "Jack", "Kim", "Leo", "Mona", "Nick",
};
#define NAME_N ((uint16_t)(sizeof(k_name) / sizeof(k_name[0])))
#endif

/* --- local helpers & event callbacks --- */
static void draw_item(ugui_widget_t* w, uint16_t idx, const ugui_rect_t* item_rect,
                      bool focused, void* ctx)
{
    (void)ctx;
#ifdef UGUI_ENABLE_FONT
    if (idx >= NAME_N) { return; }
    ugui_text_cfg_t cfg;
    ugui_text_cfg_init(&cfg, k_name[idx], &lexend_14pt_2bpp,
                       focused ? ugui_color_hex(0xFFD200u) : ugui_color_hex(0xC8D0E0u),
                       (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
                       (ugui_point_t){ 14, 0 }, 0u);
    cfg.area = *item_rect;
    ugui_font_draw_text_cfg(w, &cfg, 0u);
#else
    (void)w; (void)idx; (void)item_rect; (void)focused;
#endif
}

static void on_pick(uint16_t idx, void* ctx)
{
    (void)ctx;
    if (idx >= NAME_N) { return; }
#ifdef UGUI_ENABLE_FONT
    (void)snprintf(s_status_str, sizeof(s_status_str), "picked: %s", k_name[idx]);
    ugui_widget_invalidate(&s_status);
#endif
    printf("[list.styled] picked %s (idx %u)\n", k_name[idx], (unsigned)idx);
}

/* --- build it (in your screen's init) --- */
ugui_widget_list_init(&s_root, &s_list, &s_list_ext);
ugui_widget_set_area(&s_list, LIST_X, LIST_Y, LIST_W, LIST_H);
ugui_widget_set_id(&s_list, WDGT_ID_DEMO);
ugui_widget_list_set_theme(&s_list,
                           ugui_color_hex(0x0F1420u),   /* surface     */
                           ugui_color_hex(0x1E2A44u),   /* focus fill  */
                           ugui_color_hex(0xFFD200u));  /* focus border */
ugui_widget_list_set_content(&s_list, NAME_N, ROW_H, 5u);  /* start on "Frank" */
s_list_ext.draw_item  = draw_item;
s_list_ext.on_select   = on_pick;
ugui_screen_add_widget(&s_screen, &s_list);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_list);
#endif

Programmatic Navigation: Jump to Index

The realistic "A-Z index sidebar" pattern: two buttons call ugui_widget_list_scroll_to() to snap the list straight to the first or last row with no animation, and a status line reads the new focus back with get_focus(). Everything else is the plain library default — same draw_item shape as the default screen.

Programmatic Navigation: Jump to Index

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

#include "ugui_font.h"

/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_KINETIC_LIST)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_list;
static ugui_list_ext_t  s_list_ext;
#define ITEM_N   26u   /* one row per letter, A..Z */
#define ROW_H    30
#define BTN_H    30
#define BTN_GAP  8
#define BTN_Y    (UGUI_SCREEN_H - BTN_H - BTN_GAP)
#define STATUS_H 22
#define STATUS_Y (BTN_Y - BTN_GAP - STATUS_H)
#define LIST_X   10
#define LIST_Y   38
#define LIST_W   (UGUI_SCREEN_W - 2 * 10)
#define LIST_H   (STATUS_Y - LIST_Y - BTN_GAP)
static char s_letter[ITEM_N][10];
#endif

/* --- local helpers & event callbacks --- */
static void draw_item(ugui_widget_t* w, uint16_t idx, const ugui_rect_t* item_rect,
                      bool focused, void* ctx)
{
    (void)ctx;
#ifdef UGUI_ENABLE_FONT
    if (idx >= (uint16_t)ITEM_N) { return; }
    ugui_text_cfg_t cfg;
    ugui_text_cfg_init(&cfg, s_letter[idx], &lexend_14pt_2bpp,
                       focused ? UGUI_THEME_ACCENT : UGUI_THEME_TEXT,
                       (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER),
                       (ugui_point_t){ 14, 0 }, 0u);
    cfg.area = *item_rect;
    ugui_font_draw_text_cfg(w, &cfg, 0u);
#else
    (void)w; (void)idx; (void)item_rect; (void)focused;
#endif
}

/* --- build it (in your screen's init) --- */
ugui_widget_list_init(&s_root, &s_list, &s_list_ext);
ugui_widget_set_area(&s_list, LIST_X, LIST_Y, LIST_W, LIST_H);
ugui_widget_set_id(&s_list, WDGT_ID_DEMO);
ugui_widget_list_set_content(&s_list, (uint16_t)ITEM_N, ROW_H, 0u);
s_list_ext.draw_item = draw_item;
ugui_screen_add_widget(&s_screen, &s_list);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_list);
#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.