Skip to content

Slider Widget

The Slider is a draggable value control — a track with a knob, or a min..max RANGE with two knobs. Init installs a theme-tracked 0..100 VALUE slider at a default 200×44 size, wired for touch (drag or tap-to-set) and keypad (focus + LEFT/RIGHT step) with no extra event wiring. set_value() / set_range() drive the value and its scale; ext->type = UGUI_SLIDER_TYPE_RANGE switches to a two-knob range control; ext->orient = UGUI_SLIDER_VERT flips the whole layout to vertical, remapping the keys automatically; colour and size are plain ext fields (no dedicated setters), so a handful of assignments after init restyle the stock widget completely.

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 Slider

Two calls make a working control: ugui_widget_slider_init() installs the whole look, size and event mask, and set_callback() hooks the value change. Tapping anywhere on the track sets the value directly; the live readout below tracks on_change()'s value.

Default Slider

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

/* --- 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_SLIDER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t     s_slider;
static ugui_slider_ext_t s_slider_ext;
static ugui_widget_t     s_value;       /* live value readout under the slider */
static ugui_label_ext_t  s_value_ext;
static char s_value_buf[16];
#endif

/* --- local helpers & event callbacks --- */
static void on_change(int16_t val_lo, int16_t val_hi, void* ctx)
{
    (void)val_hi; (void)ctx;
    printf("[slider.default] value -> %d\n", (int)val_lo);
    (void)snprintf(s_value_buf, sizeof(s_value_buf), "%d", (int)val_lo);
    ugui_label_set_text(&s_value, s_value_buf);
}

/* --- build it (in your screen's init) --- */
ugui_widget_slider_init(&s_root, &s_slider, &s_slider_ext);
ugui_widget_set_align(&s_slider, UGUI_ALIGN_CENTER, 0, -10);
ugui_widget_set_id(&s_slider, WDGT_ID_DEMO);
ugui_widget_slider_set_callback(&s_slider, on_change, NULL);
ugui_screen_add_widget(&s_screen, &s_slider);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_slider);
#endif

/* Live value readout, centred just below the slider. */
ugui_widget_label_init(&s_root, &s_value, &s_value_ext);
ugui_widget_set_area(&s_value, 0, (int16_t)((UGUI_SCREEN_H / 2) + 30),
                     (int16_t)UGUI_SCREEN_W, 22);
ugui_widget_set_id(&s_value, WDGT_ID_VALUE);
ugui_widget_label_set_style(&s_value, false, UGUI_THEME_BG, "0",
                            &lexend_14pt_2bpp, UGUI_THEME_TEXT, UGUI_ALIGN_CENTER, 1u);
ugui_screen_add_widget(&s_screen, &s_value);

Value vs Range, Vertical, Disabled

Left column: a plain VALUE slider, a RANGE slider (two knobs via set_value() + set_value_hi()), and a disabled row locked at a fixed value with a muted fill. Right column: the SAME value slider with ext->orient = UGUI_SLIDER_VERT — one field flips the whole layout.

Value vs Range, Vertical, Disabled

/* --- 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_SLOT0 = 10, WDGT_ID_SLOT4, WDGT_ID_STATUS,
    WDGT_ID_SLOT3, WDGT_ID_SLOT7, WDGT_ID_SLOT10
};

/* --- 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_SLIDER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_status;             /* live readout */
static ugui_label_ext_t s_status_ext;
#define ROW_N  3u
static ugui_widget_t     s_row_w  [ROW_N];
static ugui_slider_ext_t s_row_ext[ROW_N];
static ugui_widget_t     s_row_lbl[ROW_N];
static ugui_label_ext_t  s_row_lbl_ext[ROW_N];
static const char* const k_row_label[ROW_N] = {
    "Value  0..100", "Range  0..100", "Disabled (locked 70)"
};
static ugui_widget_t     s_vert_w;
static ugui_slider_ext_t s_vert_ext;
static ugui_widget_t     s_vert_lbl;
static ugui_label_ext_t  s_vert_lbl_ext;
static ugui_widget_t     s_vert_val;
static ugui_label_ext_t  s_vert_val_ext;
#define ROW_X     10
#define ROW_W     280
#define LBL_H     18
#define SLD_H     40
#define ROW_STEP  72   /* LBL_H + SLD_H + 14px gap */
#define ROW0_Y    ((int16_t)(38 + 4))
#define VCOL_X    ((int16_t)(ROW_X + ROW_W + 40))
#define VCOL_W    44
#define VCOL_LBL_Y ROW0_Y
#define VCOL_SLD_Y ((int16_t)(VCOL_LBL_Y + LBL_H + 4))
#define VCOL_SLD_H 170
#define VCOL_VAL_Y ((int16_t)(VCOL_SLD_Y + VCOL_SLD_H + 6))
#define STATUS_H  26
#define STATUS_Y  ((int16_t)(UGUI_SCREEN_H - STATUS_H - 10))
static char s_status_buf[48];
static char s_vert_buf[8];
#endif

/* --- local helpers & event callbacks --- */
static void on_row_change(int16_t val_lo, int16_t val_hi, void* ctx)
{
    const char* name = (const char*)ctx;
    if (val_hi != val_lo) {
        (void)snprintf(s_status_buf, sizeof(s_status_buf), "%s: %d - %d",
                       name, (int)val_lo, (int)val_hi);
    } else {
        (void)snprintf(s_status_buf, sizeof(s_status_buf), "%s: %d", name, (int)val_lo);
    }
    ugui_label_set_text(&s_status, s_status_buf);
}

static void on_vert_change(int16_t val_lo, int16_t val_hi, void* ctx)
{
    (void)val_hi; (void)ctx;
    (void)snprintf(s_vert_buf, sizeof(s_vert_buf), "%d", (int)val_lo);
    ugui_label_set_text(&s_vert_val, s_vert_buf);
}

static void add_label(ugui_widget_t* w, ugui_label_ext_t* ext, uint8_t id,
                      int16_t x, int16_t y, int16_t cw, int16_t ch,
                      const char* str, ugui_align_t align)
{
    ugui_widget_label_init(&s_root, w, ext);
    ugui_widget_set_area(w, x, y, cw, ch);
    ugui_widget_set_id  (w, id);
    ugui_widget_label_set_style(w, false, UGUI_THEME_BG, str,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT, align, 1u);
    ugui_screen_add_widget(&s_screen, w);
}

/* --- build it (in your screen's init) --- */
uint8_t i;
for (i = 0u; i < ROW_N; i++) {
    int16_t lbl_y = (int16_t)(ROW0_Y + (int16_t)i * ROW_STEP);
    int16_t sld_y = (int16_t)(lbl_y + LBL_H);

    add_label(&s_row_lbl[i], &s_row_lbl_ext[i], (uint8_t)(WDGT_ID_SLOT0 + i),
             ROW_X, lbl_y, ROW_W, LBL_H, k_row_label[i], UGUI_ALIGN_LEFT);

    ugui_widget_slider_init(&s_root, &s_row_w[i], &s_row_ext[i]);
    ugui_widget_set_area(&s_row_w[i], ROW_X, sld_y, ROW_W, SLD_H);
    ugui_widget_set_id(&s_row_w[i], (uint8_t)(WDGT_ID_SLOT4 + i));

    switch (i) {
        case 1u:  /* Range */
            s_row_ext[i].type = (uint8_t)UGUI_SLIDER_TYPE_RANGE;
            ugui_widget_slider_set_value(&s_row_w[i], 20);
            ugui_widget_slider_set_value_hi(&s_row_w[i], 75);
            ugui_widget_slider_set_callback(&s_row_w[i], on_row_change,
                                            (void*)k_row_label[i]);
#if UGUI_ENABLE_KEYPAD
            ugui_screen_add_focus(&s_screen, &s_row_w[i]);
#endif
            break;
        case 2u:  /* Disabled, locked at 70 — muted colour signals the lock */
            ugui_widget_slider_set_value(&s_row_w[i], 70);
            s_row_ext[i].color_track_fill = UGUI_COLOR_GRAY;
            ugui_widget_set_enabled(&s_row_w[i], false);
            break;
        default:  /* case 0: plain default Value slider */
            ugui_widget_slider_set_callback(&s_row_w[i], on_row_change,
                                            (void*)k_row_label[i]);
#if UGUI_ENABLE_KEYPAD
            ugui_screen_add_focus(&s_screen, &s_row_w[i]);
#endif
            break;
    }
    ugui_screen_add_widget(&s_screen, &s_row_w[i]);
}

/* Live status bar across the foot. */
(void)snprintf(s_status_buf, sizeof(s_status_buf), "drag Value or Range - state lands here");
ugui_widget_label_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_STATUS);
ugui_widget_label_set_style(&s_status, false, UGUI_THEME_BG, s_status_buf,
                            &lexend_14pt_2bpp, UGUI_THEME_ACCENT, UGUI_ALIGN_CENTER, 1u);
ugui_screen_add_widget(&s_screen, &s_status);

/* Right column: same VALUE slider, vertical orientation. */
add_label(&s_vert_lbl, &s_vert_lbl_ext, WDGT_ID_SLOT3,
         VCOL_X, VCOL_LBL_Y, 90, LBL_H, "Vertical", UGUI_ALIGN_LEFT);

ugui_widget_slider_init(&s_root, &s_vert_w, &s_vert_ext);
ugui_widget_set_area(&s_vert_w, VCOL_X, VCOL_SLD_Y, VCOL_W, VCOL_SLD_H);
ugui_widget_set_id(&s_vert_w, WDGT_ID_SLOT7);
s_vert_ext.orient = (uint8_t)UGUI_SLIDER_VERT;
ugui_widget_slider_set_value(&s_vert_w, 50);
ugui_widget_slider_set_callback(&s_vert_w, on_vert_change, NULL);
ugui_screen_add_widget(&s_screen, &s_vert_w);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_vert_w);
#endif

add_label(&s_vert_val, &s_vert_val_ext, WDGT_ID_SLOT10,
         (int16_t)(VCOL_X - 20), VCOL_VAL_Y, 90, 20, "50", UGUI_ALIGN_LEFT);

Colours, Sizes & Labels

Left: six sliders restyled through plain ext fields (there is no set_colors() helper) — fill colour, a custom deselected-track colour, and a custom knob colour each get a row. Right: the same widget at three knob/track sizes. Bottom: show_labels = 1 turns on the built-in min/max/current-value text under the track.

Colours, Sizes & Labels

/* --- 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_SLOT0 = 10, WDGT_ID_SLOT18, WDGT_ID_SLOT8,
    WDGT_ID_SLOT11, WDGT_ID_SLOT17
};

/* --- 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_SLIDER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
#define COL_N  6u
static ugui_widget_t     s_col_w  [COL_N];
static ugui_slider_ext_t s_col_ext[COL_N];
static ugui_widget_t     s_col_lbl[COL_N];
static ugui_label_ext_t  s_col_lbl_ext[COL_N];
static const char* const k_col_name[COL_N] = {
    "Accent", "Success", "Caution", "Alert", "Custom track", "Custom knob"
};
#define SIZE_N  3u
static ugui_widget_t     s_size_w  [SIZE_N];
static ugui_slider_ext_t s_size_ext[SIZE_N];
static ugui_widget_t     s_size_lbl[SIZE_N];
static ugui_label_ext_t  s_size_lbl_ext[SIZE_N];
static const char* const k_size_name [SIZE_N] = { "Small", "Medium", "Large" };
static const uint8_t     k_size_kr   [SIZE_N] = { 6u, 9u, 14u };
static const uint8_t     k_size_delta[SIZE_N] = { 2u, 3u, 5u };
static const uint8_t     k_size_thick[SIZE_N] = { 3u, 4u, 6u };
static const int16_t     k_size_h    [SIZE_N] = { 24, 32, 44 };
static ugui_widget_t     s_lbl_w;
static ugui_slider_ext_t s_lbl_ext;
#define HDR_Y      38
#define COL_LBL_X  10
#define COL_LBL_W  74
#define COL_SLD_X  ((int16_t)(COL_LBL_X + COL_LBL_W + 4))
#define COL_SLD_W  120
#define COL_SLD_H  26
#define COL_ROW0   ((int16_t)(HDR_Y + 24))
#define COL_STEP   34
#define SIZE_LBL_X ((int16_t)(COL_SLD_X + COL_SLD_W + 30))
#define SIZE_LBL_W 62
#define SIZE_SLD_X ((int16_t)(SIZE_LBL_X + SIZE_LBL_W + 4))
#define SIZE_SLD_W 140
#define LBL_DEMO_Y     260
#define LBL_DEMO_SLD_Y ((int16_t)(LBL_DEMO_Y + 18))
#define LBL_DEMO_W     ((int16_t)(UGUI_SCREEN_W - 2 * 10))
#define LBL_DEMO_H     34
#endif

/* --- local helpers & event callbacks --- */
static void add_label(ugui_widget_t* w, ugui_label_ext_t* ext, uint8_t id,
                      int16_t x, int16_t y, int16_t cw, int16_t ch,
                      const char* str, ugui_align_t align)
{
    ugui_widget_label_init(&s_root, w, ext);
    ugui_widget_set_area(w, x, y, cw, ch);
    ugui_widget_set_id  (w, id);
    ugui_widget_label_set_style(w, false, UGUI_THEME_BG, str,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT, align, 1u);
    ugui_screen_add_widget(&s_screen, w);
}

/* --- build it (in your screen's init) --- */
ugui_color_t fill_col[COL_N];
ugui_color_t bg_col  [COL_N];
ugui_color_t knob_col[COL_N];
int16_t      init_val[COL_N];

fill_col[0] = UGUI_THEME_ACCENT;             bg_col[0] = UGUI_THEME_BORDER; knob_col[0] = UGUI_THEME_ACCENT; init_val[0] = 65; /* Accent  */
fill_col[1] = UGUI_THEME_OK;             bg_col[1] = UGUI_THEME_BORDER; knob_col[1] = UGUI_THEME_ACCENT; init_val[1] = 65; /* Success */
fill_col[2] = ugui_color_hex(0xF5A623u); bg_col[2] = UGUI_THEME_BORDER; knob_col[2] = UGUI_THEME_ACCENT; init_val[2] = 65; /* Caution */
fill_col[3] = UGUI_THEME_CANCEL;         bg_col[3] = UGUI_THEME_BORDER; knob_col[3] = UGUI_THEME_ACCENT; init_val[3] = 65; /* Alert   */
/* Custom deselect: track_bg = slate blue, fill = cyan, shown mostly OFF. */
fill_col[4] = ugui_color_hex(0x22B8E6u);
bg_col  [4] = ugui_color_hex(0x3A4A63u);
knob_col[4] = UGUI_THEME_ACCENT;
init_val[4] = 25;
/* Custom knob: gold knob on the plain Accent track. */
fill_col[5] = UGUI_THEME_ACCENT;
bg_col  [5] = UGUI_THEME_BORDER;
knob_col[5] = ugui_color_hex(0xFFD24Du);
init_val[5] = 65;

uint8_t i;
for (i = 0u; i < COL_N; i++) {
    int16_t row_y = (int16_t)(COL_ROW0 + (int16_t)i * COL_STEP);

    add_label(&s_col_lbl[i], &s_col_lbl_ext[i], (uint8_t)(WDGT_ID_SLOT0 + i),
             COL_LBL_X, row_y, COL_LBL_W, COL_SLD_H,
             k_col_name[i], (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));

    ugui_widget_slider_init(&s_root, &s_col_w[i], &s_col_ext[i]);
    ugui_widget_set_area(&s_col_w[i], COL_SLD_X, row_y, COL_SLD_W, COL_SLD_H);
    ugui_widget_set_id(&s_col_w[i], (uint8_t)(WDGT_ID_SLOT18 + i));
    s_col_ext[i].color_track_fill = fill_col[i];
    s_col_ext[i].color_track_bg   = bg_col[i];
    s_col_ext[i].color_knob       = knob_col[i];
    ugui_widget_slider_set_value(&s_col_w[i], init_val[i]);
    ugui_screen_add_widget(&s_screen, &s_col_w[i]);
#if UGUI_ENABLE_KEYPAD
    ugui_screen_add_focus(&s_screen, &s_col_w[i]);
#endif
}

int16_t size_y = COL_ROW0;
for (i = 0u; i < SIZE_N; i++) {
    int16_t row_h = k_size_h[i];

    add_label(&s_size_lbl[i], &s_size_lbl_ext[i], (uint8_t)(WDGT_ID_SLOT8 + i),
             SIZE_LBL_X, size_y, SIZE_LBL_W, row_h,
             k_size_name[i], (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));

    ugui_widget_slider_init(&s_root, &s_size_w[i], &s_size_ext[i]);
    ugui_widget_set_area(&s_size_w[i], SIZE_SLD_X, size_y, SIZE_SLD_W, row_h);
    ugui_widget_set_id(&s_size_w[i], (uint8_t)(WDGT_ID_SLOT11 + i));
    s_size_ext[i].knob_radius      = k_size_kr[i];
    s_size_ext[i].knob_press_delta = k_size_delta[i];
    s_size_ext[i].track_thickness  = k_size_thick[i];
    ugui_widget_slider_set_value(&s_size_w[i], 55);
    ugui_screen_add_widget(&s_screen, &s_size_w[i]);
#if UGUI_ENABLE_KEYPAD
    ugui_screen_add_focus(&s_screen, &s_size_w[i]);
#endif

    size_y = (int16_t)(size_y + row_h + 8);
}

ugui_widget_slider_init(&s_root, &s_lbl_w, &s_lbl_ext);
ugui_widget_set_area(&s_lbl_w, 10, LBL_DEMO_SLD_Y, LBL_DEMO_W, LBL_DEMO_H);
ugui_widget_set_id(&s_lbl_w, WDGT_ID_SLOT17);
s_lbl_ext.show_labels = 1u;
s_lbl_ext.label_font  = &lexend_14pt_2bpp;
ugui_widget_slider_set_value(&s_lbl_w, 42);
ugui_screen_add_widget(&s_screen, &s_lbl_w);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_lbl_w);
#endif

Mixer Form

The realistic case: a card panel of five labeled faders (Volume, Bass, Treble, Balance — VALUE — and Crossover — RANGE), each carrying its row name as callback context so ONE handler formats every drag into the status bar at the foot.

Mixer Form

/* --- 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_PANEL = 10, WDGT_ID_SLOT5, WDGT_ID_SLOT0,
    WDGT_ID_STATUS
};

/* --- 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_SLIDER)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_panel;             /* card behind the rows      */
static ugui_box_ext_t   s_panel_ext;
static ugui_widget_t    s_status;            /* live readout              */
static ugui_label_ext_t s_status_ext;
#define ROW_N  5u
static ugui_widget_t     s_slider_w  [ROW_N];
static ugui_slider_ext_t s_slider_ext[ROW_N];
static ugui_widget_t     s_label     [ROW_N];
static ugui_label_ext_t  s_label_ext [ROW_N];
static const char* const  k_row_label[ROW_N] = {
    "Master Volume", "Bass", "Treble", "Balance", "Crossover"
};
static const uint8_t k_row_is_range[ROW_N] = { 0u, 0u, 0u, 0u, 1u };
static const int16_t k_row_min     [ROW_N] = { 0,   -12, -12, -50,  0 };
static const int16_t k_row_max     [ROW_N] = { 100,  12,  12,  50, 100 };
static const int16_t k_row_init_lo [ROW_N] = { 70,    0,   0,   0,  30 };
static const int16_t k_row_init_hi [ROW_N] = { 70,    0,   0,   0,  70 };
#define STATUS_H  26
#define STATUS_Y  ((int16_t)(UGUI_SCREEN_H - STATUS_H - 10))
#define PANEL_X   10
#define PANEL_Y   ((int16_t)(38 + 4))
#define PANEL_W   ((int16_t)(UGUI_SCREEN_W - 2 * 10))
#define PANEL_H   ((int16_t)(STATUS_Y - PANEL_Y - 8))
#define ROW_STEP  ((int16_t)(PANEL_H / (int16_t)ROW_N))
#define SLD_H     30
#define LBL_X     ((int16_t)(PANEL_X + 12))
#define LBL_W     110
#define SLD_X     ((int16_t)(LBL_X + LBL_W + 8))
#define SLD_W     ((int16_t)(PANEL_X + PANEL_W - SLD_X - 12))
static char s_status_buf[48];
#endif

/* --- local helpers & event callbacks --- */
static void on_change(int16_t val_lo, int16_t val_hi, void* ctx)
{
    const char* name = (const char*)ctx;
    if (val_hi != val_lo) {
        (void)snprintf(s_status_buf, sizeof(s_status_buf), "%s: %d - %d",
                       name, (int)val_lo, (int)val_hi);
    } else {
        (void)snprintf(s_status_buf, sizeof(s_status_buf), "%s: %d", name, (int)val_lo);
    }
    ugui_label_set_text(&s_status, s_status_buf);
}

static void add_label(ugui_widget_t* w, ugui_label_ext_t* ext, uint8_t id,
                      int16_t x, int16_t y, int16_t cw, int16_t ch,
                      const char* str, ugui_align_t align)
{
    ugui_widget_label_init(&s_root, w, ext);
    ugui_widget_set_area(w, x, y, cw, ch);
    ugui_widget_set_id  (w, id);
    ugui_widget_label_set_style(w, false, UGUI_THEME_SURFACE, str,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT, align, 1u);
    ugui_screen_add_widget(&s_screen, w);
}

/* --- build it (in your screen's init) --- */
/* Card panel behind the rows (display-only, added first so it sits under
 * the sliders). */
ugui_widget_box_init(&s_root, &s_panel, &s_panel_ext);
ugui_widget_set_area(&s_panel, PANEL_X, PANEL_Y, PANEL_W, PANEL_H);
ugui_widget_set_id(&s_panel, WDGT_ID_PANEL);
ugui_widget_box_set_style(&s_panel, true, 6u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
ugui_widget_set_enabled(&s_panel, false);
ugui_screen_add_widget(&s_screen, &s_panel);

uint8_t i;
for (i = 0u; i < ROW_N; i++) {
    int16_t row_y = (int16_t)(PANEL_Y + (int16_t)i * ROW_STEP
                              + (ROW_STEP - SLD_H) / 2);

    add_label(&s_label[i], &s_label_ext[i], (uint8_t)(WDGT_ID_SLOT5 + i),
             LBL_X, row_y, LBL_W, SLD_H,
             k_row_label[i], (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));

    ugui_widget_slider_init(&s_root, &s_slider_w[i], &s_slider_ext[i]);
    ugui_widget_set_area(&s_slider_w[i], SLD_X, row_y, SLD_W, SLD_H);
    ugui_widget_set_id(&s_slider_w[i], (uint8_t)(WDGT_ID_SLOT0 + i));
    ugui_widget_slider_set_range(&s_slider_w[i], k_row_min[i], k_row_max[i]);
    if (k_row_is_range[i] != 0u) {
        s_slider_ext[i].type = (uint8_t)UGUI_SLIDER_TYPE_RANGE;
    }
    ugui_widget_slider_set_value(&s_slider_w[i], k_row_init_lo[i]);
    if (k_row_is_range[i] != 0u) {
        ugui_widget_slider_set_value_hi(&s_slider_w[i], k_row_init_hi[i]);
    }
    ugui_widget_slider_set_callback(&s_slider_w[i], on_change, (void*)k_row_label[i]);
    ugui_screen_add_widget(&s_screen, &s_slider_w[i]);
#if UGUI_ENABLE_KEYPAD
    ugui_screen_add_focus(&s_screen, &s_slider_w[i]);
#endif
}

/* Live status line across the foot. */
(void)snprintf(s_status_buf, sizeof(s_status_buf), "drag a fader to see it here");
ugui_widget_label_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_STATUS);
ugui_widget_label_set_style(&s_status, false, UGUI_THEME_BG, s_status_buf,
                            &lexend_14pt_2bpp, UGUI_THEME_ACCENT, UGUI_ALIGN_CENTER, 1u);
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.