Line Widget¶
The Line widget draws a straight stroke — a section divider, a chart
axis, a threshold marker, a "muted" cross-out. The widget's box IS the line:
horizontal and vertical strokes fill their box directly (no anti-aliasing
needed), while diagonals and the set_points() endpoint API reuse the
library's AA line renderer. A NULL ext is a valid, zero-RAM form — an
always-visible divider in the widget's own colour with an AUTO-resolved
direction.
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 Separators¶
A line created with nothing but ugui_widget_line_init() + set_align() — the 200×2 theme-divider default, centred. Below it, the zero-RAM form: settings-list row separators initialised with a NULL ext — AUTO direction resolves to horizontal from the wide box, colour comes from the theme, and each separator costs 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_DEMO = 10, WDGT_ID_SLOT0
};
/* --- 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_LINE)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_demo;
static ugui_line_ext_t s_demo_ext;
#define ROW_N 3
static ugui_widget_t s_row[ROW_N];
static ugui_label_ext_t s_row_ext[ROW_N];
static ugui_widget_t s_sep[ROW_N - 1]; /* deliberately NO ext structs */
static const char* const s_row_txt[ROW_N] = {
"Brightness", "Volume", "Language"
};
#define LIST_X ((int16_t)(UGUI_SCREEN_W / 8))
#define LIST_W ((int16_t)(UGUI_SCREEN_W - (2 * LIST_X)))
#define LIST_Y ((int16_t)(UGUI_SCREEN_H / 2))
#define ROW_H 26
#define ROW_STEP (ROW_H + 2)
#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_line_init(&s_root, &s_demo, &s_demo_ext);
ugui_widget_set_align(&s_demo, UGUI_ALIGN_H_CENTER | UGUI_ALIGN_TOP,
0, (int16_t)(38 + 38));
ugui_widget_set_id(&s_demo, WDGT_ID_DEMO);
ugui_screen_add_widget(&s_screen, &s_demo);
/* Zero-RAM separators: a settings-style list. Each separator is a line
* widget with a NULL ext — AUTO direction (wide box -> horizontal),
* default thickness, theme divider colour. */
for (uint8_t i = 0u; i < (uint8_t)ROW_N; i++) {
add_label(&s_row[i], &s_row_ext[i], (uint8_t)(WDGT_ID_SLOT0 + i),
LIST_X, (int16_t)(LIST_Y + ((int16_t)i * ROW_STEP)),
LIST_W, ROW_H, s_row_txt[i], UGUI_ALIGN_LEFT);
if (i < (uint8_t)(ROW_N - 1)) {
ugui_widget_line_init(&s_root, &s_sep[i], NULL);
ugui_widget_set_area(&s_sep[i],
LIST_X,
(int16_t)(LIST_Y + ((int16_t)(i + 1u) * ROW_STEP) - 2),
LIST_W, 1);
ugui_screen_add_widget(&s_screen, &s_sep[i]);
}
}
Directions & Thickness¶
Every geometry API side by side: a horizontal thickness ramp (AUTO direction resolves from the wide box), an explicit vertical ramp via set_dir(), two diagonals parented to a panel (so the AA edge blends against the panel colour and the panel clips it), and the endpoint-style set_points(x0,y0,x1,y1) API with dots marking the requested endpoints.

/* --- 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_DEMO
};
/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_LINE)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
#define H_N 4
#define V_N 3
static ugui_widget_t s_h[H_N];
static ugui_line_ext_t s_h_ext[H_N];
static const uint8_t s_h_thick[H_N] = { 1u, 2u, 4u, 8u };
static ugui_widget_t s_v[V_N];
static ugui_line_ext_t s_v_ext[V_N];
static const uint8_t s_v_thick[V_N] = { 1u, 3u, 6u };
static ugui_widget_t s_panel[2]; /* clip + AA-blend hosts */
static ugui_box_ext_t s_panel_ext[2];
static ugui_widget_t s_diag[2];
static ugui_line_ext_t s_diag_ext[2];
static ugui_widget_t s_pts; /* set_points() footer demo */
static ugui_line_ext_t s_pts_ext;
static ugui_widget_t s_dot[2]; /* endpoint markers */
static ugui_circle_ext_t s_dot_ext[2];
#define COL_W ((int16_t)(UGUI_SCREEN_W / 3))
#define BLK_Y ((int16_t)(38 + 24)) /* below column captions */
#define BLK_H ((int16_t)((UGUI_SCREEN_H * 38) / 100))
#define FOOT_Y ((int16_t)(BLK_Y + BLK_H + 26)) /* set_points strip */
#define DOT_R 4 /* endpoint marker size */
#endif
/* --- build it (in your screen's init) --- */
/* Column 1 — thickness ramp; AUTO direction resolves to H (wide boxes).
* Accent colour so the ramp pops against the page background. */
for (uint8_t i = 0u; i < (uint8_t)H_N; i++) {
ugui_widget_line_init(&s_root, &s_h[i], &s_h_ext[i]);
ugui_widget_set_area(&s_h[i],
(int16_t)(10 + 4),
(int16_t)(BLK_Y + 8 + ((int16_t)i * (BLK_H / H_N))),
(int16_t)(COL_W - (2 * (10 + 4))), 12);
ugui_widget_line_set_thickness(&s_h[i], s_h_thick[i]);
s_h[i].color = UGUI_THEME_ACCENT;
ugui_widget_set_id(&s_h[i], (uint8_t)(WDGT_ID_SLOT0 + i));
ugui_screen_add_widget(&s_screen, &s_h[i]);
}
/* Column 2 — explicit vertical direction, thickness ramp. */
for (uint8_t i = 0u; i < (uint8_t)V_N; i++) {
ugui_widget_line_init(&s_root, &s_v[i], &s_v_ext[i]);
ugui_widget_set_area(&s_v[i],
(int16_t)(COL_W + 20 + ((int16_t)i * ((COL_W - 40) / V_N))),
(int16_t)(BLK_Y + 4), 12, (int16_t)(BLK_H - 8));
ugui_widget_line_set_dir(&s_v[i], UGUI_LINE_DIR_V);
ugui_widget_line_set_thickness(&s_v[i], s_v_thick[i]);
s_v[i].color = UGUI_THEME_ACCENT;
ugui_screen_add_widget(&s_screen, &s_v[i]);
}
/* Column 3 — the two diagonals, each inside a surface panel. The line is
* a CHILD of the panel, so its AA edge blends against the panel colour
* and the panel's clip bounds it. */
for (uint8_t i = 0u; i < 2u; i++) {
ugui_widget_box_init(&s_root, &s_panel[i], &s_panel_ext[i]);
ugui_widget_set_area(&s_panel[i],
(int16_t)((2 * COL_W) + 14),
(int16_t)(BLK_Y + 4 + ((int16_t)i * ((BLK_H / 2) + 2))),
(int16_t)(COL_W - 28), (int16_t)((BLK_H / 2) - 6));
ugui_widget_box_set_style(&s_panel[i], true, 4u, 0u,
UGUI_THEME_SURFACE, UGUI_THEME_SURFACE);
ugui_screen_add_widget(&s_screen, &s_panel[i]);
ugui_widget_line_init(&s_panel[i], &s_diag[i], &s_diag_ext[i]);
ugui_widget_set_area(&s_diag[i], 0, 0,
(int16_t)(COL_W - 28), (int16_t)((BLK_H / 2) - 6));
ugui_widget_line_set_dir(&s_diag[i], (i == 0u) ? UGUI_LINE_DIR_DIAG_DOWN
: UGUI_LINE_DIR_DIAG_UP);
ugui_widget_line_set_thickness(&s_diag[i], 3u);
s_diag[i].color = UGUI_THEME_ACCENT;
ugui_screen_add_widget(&s_screen, &s_diag[i]);
}
/* Footer — the endpoint-style API: one call places box + direction.
* The two dots sit ON the requested endpoints. */
{
int16_t x0 = (int16_t)(UGUI_SCREEN_W / 4);
int16_t y0 = (int16_t)(FOOT_Y + 26);
int16_t x1 = (int16_t)((UGUI_SCREEN_W * 3) / 4);
int16_t y1 = (int16_t)(FOOT_Y + 4);
ugui_widget_line_init(&s_root, &s_pts, &s_pts_ext);
ugui_widget_line_set_thickness(&s_pts, 2u);
ugui_widget_line_set_points(&s_pts, x0, y0, x1, y1);
s_pts.color = UGUI_THEME_TEXT;
ugui_widget_set_id(&s_pts, WDGT_ID_DEMO);
ugui_screen_add_widget(&s_screen, &s_pts);
for (uint8_t i = 0u; i < 2u; i++) {
int16_t cx = (i == 0u) ? x0 : x1;
int16_t cy = (i == 0u) ? y0 : y1;
ugui_widget_circle_init(&s_root, &s_dot[i], &s_dot_ext[i]);
ugui_widget_set_area(&s_dot[i],
(int16_t)(cx - DOT_R), (int16_t)(cy - DOT_R),
(int16_t)(2 * DOT_R + 1), (int16_t)(2 * DOT_R + 1));
ugui_widget_circle_set_fill_color(&s_dot[i], UGUI_THEME_ACCENT);
ugui_screen_add_widget(&s_screen, &s_dot[i]);
}
}
Dash Patterns & Markers¶
set_dash() patterns from dotted to a wide dash-gap, then two classic marker idioms built from plain line widgets: an "alarm limit" (a solid measured-level line plus a dashed limit line, both parented to a panel) and a "muted" cross-out (a disc with a thick diagonal child line through it).

/* --- 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_DEMO
};
/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_LINE)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
#define D_N 3
static ugui_widget_t s_d[D_N];
static ugui_line_ext_t s_d_ext[D_N];
static const uint8_t s_d_dash[D_N] = { 2u, 6u, 12u };
static const uint8_t s_d_gap [D_N] = { 2u, 3u, 6u };
static ugui_widget_t s_panel; /* alarm-limit host */
static ugui_box_ext_t s_panel_ext;
static ugui_widget_t s_level; /* solid "measured" line */
static ugui_line_ext_t s_level_ext;
static ugui_widget_t s_limit; /* dashed alert line */
static ugui_line_ext_t s_limit_ext;
static ugui_widget_t s_disc; /* cross-out pictogram */
static ugui_circle_ext_t s_disc_ext;
static ugui_widget_t s_cross;
static ugui_line_ext_t s_cross_ext;
#define DASH_X ((int16_t)(UGUI_SCREEN_W / 6))
#define DASH_W ((int16_t)((UGUI_SCREEN_W * 4) / 6))
#define DASH_Y ((int16_t)(38 + 24))
#define DASH_STEP 18
#define DEMO_Y ((int16_t)(DASH_Y + (D_N * DASH_STEP) + 26))
#define PANEL_W ((int16_t)((UGUI_SCREEN_W / 2) - (2 * 10)))
#define PANEL_H ((int16_t)(UGUI_SCREEN_H - DEMO_Y - 36))
#define DISC_D ((int16_t)((PANEL_H < 72) ? PANEL_H : 72))
#endif
/* --- build it (in your screen's init) --- */
/* Dash ramp — same stroke, three patterns. */
for (uint8_t i = 0u; i < (uint8_t)D_N; i++) {
ugui_widget_line_init(&s_root, &s_d[i], &s_d_ext[i]);
ugui_widget_set_area(&s_d[i],
DASH_X, (int16_t)(DASH_Y + ((int16_t)i * DASH_STEP)),
DASH_W, 8);
ugui_widget_line_set_dash(&s_d[i], s_d_dash[i], s_d_gap[i]);
s_d[i].color = UGUI_THEME_TEXT;
ugui_widget_set_id(&s_d[i], (uint8_t)(WDGT_ID_SLOT0 + i));
ugui_screen_add_widget(&s_screen, &s_d[i]);
}
/* Alarm limit — solid level line + dashed alert limit, both children of
* the panel so AA blend and clip come from the panel automatically. */
ugui_widget_box_init(&s_root, &s_panel, &s_panel_ext);
ugui_widget_set_area(&s_panel, 10, DEMO_Y, PANEL_W, PANEL_H);
ugui_widget_box_set_style(&s_panel, true, 4u, 0u, UGUI_THEME_SURFACE, UGUI_THEME_SURFACE);
ugui_screen_add_widget(&s_screen, &s_panel);
ugui_widget_line_init(&s_panel, &s_level, &s_level_ext);
ugui_widget_set_area(&s_level, 8, (int16_t)((PANEL_H * 2) / 3),
(int16_t)(PANEL_W - 16), 6);
ugui_widget_line_set_thickness(&s_level, 6u);
s_level.color = UGUI_THEME_OK;
ugui_screen_add_widget(&s_screen, &s_level);
ugui_widget_line_init(&s_panel, &s_limit, &s_limit_ext);
ugui_widget_set_area(&s_limit, 8, (int16_t)(PANEL_H / 3),
(int16_t)(PANEL_W - 16), 2);
ugui_widget_line_set_dash(&s_limit, 6u, 4u);
s_limit.color = UGUI_THEME_CANCEL;
ugui_widget_set_id(&s_limit, WDGT_ID_DEMO);
ugui_screen_add_widget(&s_screen, &s_limit);
/* Cross-out — disc + thick diagonal child = "muted" pictogram. */
ugui_widget_circle_init(&s_root, &s_disc, &s_disc_ext);
ugui_widget_set_area(&s_disc,
(int16_t)((UGUI_SCREEN_W * 3) / 4 - (DISC_D / 2)),
(int16_t)(DEMO_Y + ((PANEL_H - DISC_D) / 2)),
DISC_D, DISC_D);
ugui_screen_add_widget(&s_screen, &s_disc);
ugui_widget_line_init(&s_disc, &s_cross, &s_cross_ext);
ugui_widget_set_area(&s_cross, (int16_t)(DISC_D / 5), (int16_t)(DISC_D / 5),
(int16_t)((DISC_D * 3) / 5), (int16_t)((DISC_D * 3) / 5));
ugui_widget_line_set_dir(&s_cross, UGUI_LINE_DIR_DIAG_UP);
ugui_widget_line_set_thickness(&s_cross, 4u);
s_cross.color = UGUI_THEME_KNOB;
ugui_screen_add_widget(&s_screen, &s_cross);
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.