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.

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

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

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