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.

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

/* --- 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);
Annunciator Panel (app-driven blink)¶
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.

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