Skip to content

LED Widget

The LED is a round indicator lamp — a status dot, an annunciator, a panel light. Its lit colour blends with the parent background at a settable brightness, so an OFF lamp reads as a dim, still-identifiable tint rather than vanishing, and it carries an optional themed bezel. LED is display-only: blink and animation are application policy (e.g. regulated alarm priorities), driven by re-asserting ugui_widget_led_set_on() / set_brightness() from your own clock — the setters only redraw on an actual change, so per-frame re-assertion between edges is free.

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 LED

A lamp created with nothing but ugui_widget_led_init() + one set_area() — the lit nominal-green defaults (brightness 255, 2 px themed bezel). Beside it, the zero-RAM form: status-list dots initialised with a NULL ext — an always-lit dot in the widget's own colour, each costing only the widget node.

Default LED

/* --- 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_ROW0 = 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_LED)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t   s_demo;
static ugui_led_ext_t  s_demo_ext;
#define ROW_N 4
static ugui_widget_t    s_row[ROW_N];
static ugui_label_ext_t s_row_ext[ROW_N];
static ugui_widget_t s_dot[ROW_N];          /* deliberately NO ext structs */
static const char* const s_row_txt[ROW_N] = {
    "Power", "CAN bus up", "Sensor calibrated", "Recording"
};
#define DEMO_X   40
#define DEMO_Y   78
#define DEMO_WH  96
#define LIST_X   ((int16_t)(UGUI_SCREEN_W / 2))
#define LIST_W   ((int16_t)(UGUI_SCREEN_W - LIST_X - 24))
#define LIST_Y   68
#define ROW_H    30
#define ROW_STEP (ROW_H + 8)
#define DOT      14
#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, /*bg_fill=*/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_widget_led_init(&s_root, &s_demo, &s_demo_ext);
ugui_widget_set_area(&s_demo, DEMO_X, DEMO_Y, DEMO_WH, DEMO_WH);
ugui_screen_add_widget(&s_screen, &s_demo);

/* 2. Zero-RAM status dots: init with a NULL ext — the implicit
 *    always-lit dot in each widget's own colour. No ext structs exist. */
for (uint8_t i = 0u; i < (uint8_t)ROW_N; i++) {
    int16_t row_y = (int16_t)(LIST_Y + ((int16_t)i * ROW_STEP));

    ugui_widget_led_init(&s_root, &s_dot[i], NULL);
    ugui_widget_set_area(&s_dot[i],
                         LIST_X, (int16_t)(row_y + ((ROW_H - DOT) / 2)),
                         DOT, DOT);
    /* Last row demonstrates a non-default dot colour (recording = red). */
    if (i == (uint8_t)(ROW_N - 1u)) {
        s_dot[i].color = ugui_color_preset_alert();
    }
    ugui_screen_add_widget(&s_screen, &s_dot[i]);

    /* Row label sits to the right of its dot. */
    add_label(&s_row[i], &s_row_ext[i], (uint8_t)(WDGT_ID_ROW0 + i),
             (int16_t)(LIST_X + DOT + 10), row_y,
             (int16_t)(LIST_W - DOT - 10), ROW_H,
             s_row_txt[i],
             (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
}

Colours, Brightness, Bezel, Toggle

Four idioms: semantic colours straight from the colour presets, a brightness ramp via set_brightness(), bezel styles via set_bezel(), and a live toggle — a button drives ugui_widget_led_toggle() on a big lamp, showing the OFF look (a dark tint of the same colour, still identifiable) beside ON.

Colours, Brightness, Bezel, Toggle

/* --- 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_CAP_SEM = 10, WDGT_ID_CAP_DIM, WDGT_ID_CAP_BEZ,
    WDGT_ID_CAP_TGL, WDGT_ID_TOGGLE_BTN
};

/* --- 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_LED)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_cap_sem;
static ugui_label_ext_t s_cap_sem_ext;
static ugui_widget_t    s_cap_dim;
static ugui_label_ext_t s_cap_dim_ext;
static ugui_widget_t    s_cap_bez;
static ugui_label_ext_t s_cap_bez_ext;
static ugui_widget_t    s_cap_tgl;
static ugui_label_ext_t s_cap_tgl_ext;
#define SEM_N 4
#define DIM_N 5
#define BEZ_N 3
static ugui_widget_t  s_sem[SEM_N];         /* semantic colour row        */
static ugui_led_ext_t s_sem_ext[SEM_N];
static ugui_widget_t  s_dim[DIM_N];         /* brightness ramp row        */
static ugui_led_ext_t s_dim_ext[DIM_N];
static ugui_widget_t  s_bez[BEZ_N];         /* bezel style row            */
static ugui_led_ext_t s_bez_ext[BEZ_N];
static ugui_widget_t  s_big;                /* live toggle target         */
static ugui_led_ext_t s_big_ext;
static ugui_widget_t     s_tgl_btn;
static ugui_button_ext_t s_tgl_btn_ext;
static ugui_text_cfg_t   s_tgl_btn_text;
#define ROW_X     24
#define LAMP      36
#define LAMP_GAP  14
#define SEC0_Y    42
#define SEC_STEP  66                        /* caption (22) + lamp (36) + gap */
#define LAMP_Y(s) ((int16_t)(SEC0_Y + ((s) * SEC_STEP) + 24))
#define BIG_X     ((int16_t)(UGUI_SCREEN_W - 140))
#define BIG_Y     78
#define BIG_WH    88
#endif

/* --- local helpers & event callbacks --- */
static void on_toggle(uint8_t widget_id, ugui_event_t event)
{
    (void)widget_id;
    if (event == UGUI_EVENT_CLICK) { ugui_widget_led_toggle(&s_big); }
}

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, /*bg_fill=*/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) --- */
add_label(&s_cap_sem, &s_cap_sem_ext, WDGT_ID_CAP_SEM,
         ROW_X, SEC0_Y, 260, 22,
         "alert / caution / nominal / info",
         (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
{
    const ugui_color_t sem_col[SEM_N] = {
        ugui_color_preset_alert(),   ugui_color_preset_caution(),
        ugui_color_preset_nominal(), ugui_color_preset_info()
    };
    for (uint8_t i = 0u; i < (uint8_t)SEM_N; i++) {
        ugui_widget_led_init(&s_root, &s_sem[i], &s_sem_ext[i]);
        ugui_widget_set_area(&s_sem[i],
                             (int16_t)(ROW_X + ((int16_t)i * (LAMP + LAMP_GAP))),
                             LAMP_Y(0), LAMP, LAMP);
        s_sem[i].color = sem_col[i];
        ugui_screen_add_widget(&s_screen, &s_sem[i]);
    }
}

/* 2. Brightness ramp: 0 (dark tint) .. 255 (fully lit). */
add_label(&s_cap_dim, &s_cap_dim_ext, WDGT_ID_CAP_DIM,
         ROW_X, (int16_t)(SEC0_Y + SEC_STEP), 260, 22,
         "brightness 0 - 255",
         (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
for (uint8_t i = 0u; i < (uint8_t)DIM_N; i++) {
    ugui_widget_led_init(&s_root, &s_dim[i], &s_dim_ext[i]);
    ugui_widget_set_area(&s_dim[i],
                         (int16_t)(ROW_X + ((int16_t)i * (LAMP + LAMP_GAP))),
                         LAMP_Y(1), LAMP, LAMP);
    ugui_widget_led_set_brightness(&s_dim[i],
                                   (uint8_t)(((uint16_t)i * 255u)
                                             / ((uint16_t)DIM_N - 1u)));
    ugui_screen_add_widget(&s_screen, &s_dim[i]);
}

/* 3. Bezel styles: none / default 2 px / heavy 6 px housing. */
add_label(&s_cap_bez, &s_cap_bez_ext, WDGT_ID_CAP_BEZ,
         ROW_X, (int16_t)(SEC0_Y + (2 * SEC_STEP)), 260, 22,
         "bezel: none / 2px / 6px",
         (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
for (uint8_t i = 0u; i < (uint8_t)BEZ_N; i++) {
    static const uint8_t bez_w[BEZ_N] = { 0u, 2u, 6u };
    ugui_widget_led_init(&s_root, &s_bez[i], &s_bez_ext[i]);
    ugui_widget_set_area(&s_bez[i],
                         (int16_t)(ROW_X + ((int16_t)i * (LAMP + LAMP_GAP))),
                         LAMP_Y(2), LAMP, LAMP);
    ugui_widget_led_set_bezel(&s_bez[i], bez_w[i], UGUI_THEME_BORDER);
    ugui_screen_add_widget(&s_screen, &s_bez[i]);
}

/* 4. Live toggle: the button flips the big lamp ON <-> OFF, showing
 *    the OFF look (a dark tint of the same red, still identifiable). */
add_label(&s_cap_tgl, &s_cap_tgl_ext, WDGT_ID_CAP_TGL,
         (int16_t)(BIG_X - 20), 38, (int16_t)(BIG_WH + 40), 22,
         "set_on / toggle", UGUI_ALIGN_CENTER);
ugui_widget_led_init(&s_root, &s_big, &s_big_ext);
ugui_widget_set_area(&s_big, BIG_X, BIG_Y, BIG_WH, BIG_WH);
s_big.color = ugui_color_preset_alert();
ugui_screen_add_widget(&s_screen, &s_big);

ugui_widget_button_init(&s_root, &s_tgl_btn, &s_tgl_btn_ext);
ugui_widget_set_area(&s_tgl_btn, (int16_t)(BIG_X - 6), (int16_t)(BIG_Y + BIG_WH + 16),
                     (int16_t)(BIG_WH + 12), 30);
ugui_widget_set_id(&s_tgl_btn, WDGT_ID_TOGGLE_BTN);
ugui_widget_button_set_style(&s_tgl_btn, true, 5u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_tgl_btn_text, "Toggle", &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                   UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_button_set_text(&s_tgl_btn, &s_tgl_btn_text);
#endif
ugui_widget_set_event_cb(&s_tgl_btn, on_toggle,
                         UGUI_EMASK_CLICK | UGUI_EMASK_DOWN | UGUI_EMASK_UP
                         | UGUI_EMASK_KEY_CONFIRM);
ugui_screen_add_widget(&s_screen, &s_tgl_btn);

A realistic machine-status panel: the lamps are children of a panel box, so their dimmed OFF faces blend against the panel fill, not the page background. Blink is deliberately application policy — panel_feed() derives every lamp's ON/OFF state from the millisecond clock and re-asserts it with set_on() each frame; because set_on() only invalidates on an actual change, this costs a redraw ONLY at the blink edges.

Annunciator Panel (app-driven blink)

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

/* --- 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_LED)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t   s_panel;             /* housing for the lamp rows   */
static ugui_box_ext_t  s_panel_ext;
#define ROW_N 5
static ugui_widget_t    s_lamp[ROW_N];
static ugui_led_ext_t   s_lamp_ext[ROW_N];
static ugui_widget_t    s_lbl[ROW_N];
static ugui_label_ext_t s_lbl_ext[ROW_N];
typedef struct {
    const char* text;
    uint16_t    period_ms;   /* full blink period; 0 = steady            */
    bool        steady_on;   /* state when period_ms == 0                */
} panel_row_t;
static const panel_row_t k_rows[ROW_N] = {
    { "POWER",              0u, true  },
    { "ALARM: PRESSURE",  500u, true  },
    { "WARN: FLUID LOW", 1600u, true  },
    { "COMM",             250u, true  },
    { "MAINTENANCE",        0u, false },
};
#define PANEL_X   ((int16_t)(UGUI_SCREEN_W / 2 - 170))
#define PANEL_Y   46
#define PANEL_W   340
#define PANEL_H   ((int16_t)(ROW_N * 44 + 16))
#define LAMP_WH   30
#define ROW_STEP  44
#endif

/* --- local helpers & event callbacks --- */
static void panel_feed(void)
{
    uint32_t now = ugui_hal_get_tick_ms();
    for (uint8_t i = 0u; i < (uint8_t)ROW_N; i++) {
        bool on = k_rows[i].steady_on;
        if (k_rows[i].period_ms != 0u) {
            /* ON for the first half of each period. */
            on = ((now % (uint32_t)k_rows[i].period_ms)
                  < ((uint32_t)k_rows[i].period_ms / 2u));
        }
        ugui_widget_led_set_on(&s_lamp[i], on);   /* redraws only on edges */
    }
}

static void add_label(ugui_widget_t* parent, 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(parent, w, ext);
    ugui_widget_set_area(w, x, y, cw, ch);
    ugui_widget_set_id  (w, id);
    ugui_widget_label_set_style(w, /*bg_fill=*/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) --- */
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_screen_add_widget(&s_screen, &s_panel);

{
    const ugui_color_t row_col[ROW_N] = {
        ugui_color_preset_nominal(),   /* POWER       green  */
        ugui_color_preset_alert(),     /* ALARM       red    */
        ugui_color_preset_caution(),   /* WARNING     amber  */
        ugui_color_preset_info(),      /* COMM        blue   */
        ugui_color_preset_caution(),   /* MAINTENANCE amber, OFF */
    };
    for (uint8_t i = 0u; i < (uint8_t)ROW_N; i++) {
        int16_t row_y = (int16_t)(12 + ((int16_t)i * ROW_STEP));

        ugui_widget_led_init(&s_panel, &s_lamp[i], &s_lamp_ext[i]);
        ugui_widget_set_area(&s_lamp[i], 16, row_y, LAMP_WH, LAMP_WH);
        s_lamp[i].color = row_col[i];
        ugui_widget_led_set_on(&s_lamp[i],
                               (k_rows[i].period_ms != 0u) ? true
                                                           : k_rows[i].steady_on);
        ugui_screen_add_widget(&s_screen, &s_lamp[i]);

        add_label(&s_panel, &s_lbl[i], &s_lbl_ext[i], (uint8_t)(WDGT_ID_LAMP0 + i),
                 (int16_t)(16 + LAMP_WH + 14), row_y,
                 (int16_t)(PANEL_W - LAMP_WH - 46), LAMP_WH,
                 k_rows[i].text,
                 (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
    }
}
panel_feed();   /* prime so the first rendered frame is already correct */


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.