Polygon Widget¶
The Polygon widget draws an arbitrary vertex shape — a triangle, a pentagon, a star, an HMI glyph (caution triangle, play/stop, trend arrows, home). Vertices are app-owned and widget-relative, so one static const array in flash can be shared by both a filled and an outline instance. Filling uses an even-odd scanline rule, so a self-intersecting shape (a pentagram, say) correctly renders a hollow core. A NULL ext is a valid, zero-RAM form — an implicit filled diamond in the widget's own colour.
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 Markers¶
A polygon created with nothing but ugui_widget_polygon_init() + one set_area() — the accent-diamond default (NULL points). Beside it, the zero-RAM form: status-list bullets initialised with a NULL ext — a filled diamond 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_SLOT0 = 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_POLYGON)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_demo;
static ugui_polygon_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_bullet[ROW_N]; /* deliberately NO ext structs */
static const char* const s_row_txt[ROW_N] = {
"System ready", "Battery 82%", "Signal good", "GPS locked"
};
#define DEMO_X 40
#define DEMO_Y ((int16_t)(38 + 40))
#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 ((int16_t)(38 + 30))
#define ROW_H 30
#define ROW_STEP (ROW_H + 8)
#define BULLET 12
#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_polygon_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 diamond bullets: init with a NULL ext — the implicit
* filled diamond 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_polygon_init(&s_root, &s_bullet[i], NULL);
ugui_widget_set_area(&s_bullet[i],
LIST_X, (int16_t)(row_y + ((ROW_H - BULLET) / 2)),
BULLET, BULLET);
s_bullet[i].color = UGUI_THEME_ACCENT;
ugui_screen_add_widget(&s_screen, &s_bullet[i]);
/* Row label sits to the right of its bullet. */
add_label(&s_row[i], &s_row_ext[i], (uint8_t)(WDGT_ID_SLOT0 + i),
(int16_t)(LIST_X + BULLET + 10), row_y,
(int16_t)(LIST_W - BULLET - 10), ROW_H,
s_row_txt[i],
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
}
Shapes: Filled & Outline¶
The same vertex arrays drawn two ways: set_points() and the default filled render (top row — the self-intersecting star correctly leaves its core hollow under the even-odd rule) versus set_filled(false) plus a set_thickness() outline stroke (bottom row).

/* --- 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
};
/* --- 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_POLYGON)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_cap[4]; /* per-column shape name */
static ugui_label_ext_t s_cap_ext[4];
#define SHAPE_WH 72
#define COL_N 4
#define COL_W ((int16_t)(UGUI_SCREEN_W / COL_N))
#define ROW0_Y ((int16_t)(38 + 26))
#define ROW1_Y ((int16_t)(ROW0_Y + SHAPE_WH + 34))
#define SHAPE_X(c) ((int16_t)((c) * COL_W + ((COL_W - SHAPE_WH) / 2)))
#define S 71
static const ugui_point_t k_triangle[3] = { {35, 0}, {S, S}, {0, S} };
static const ugui_point_t k_pentagon[5] = { {35, 0}, {S, 27}, {57, S}, {14, S}, {0, 27} };
static const ugui_point_t k_hexagon [6] = { {18, 0}, {53, 0}, {S, 35}, {53, S}, {18, S}, {0, 35} };
static const ugui_point_t k_star [5] = { {35, 0}, {57, S}, {0, 27}, {S, 27}, {14, S} };
typedef struct {
const ugui_point_t* pts;
uint8_t n;
const char* name;
} shape_def_t;
static const shape_def_t s_shapes[COL_N] = {
{ k_triangle, 3u, "triangle" },
{ k_pentagon, 5u, "pentagon" },
{ k_hexagon, 6u, "hexagon" },
{ k_star, 5u, "star" },
};
static ugui_widget_t s_fill[COL_N]; /* row 0: filled */
static ugui_polygon_ext_t s_fill_ext[COL_N];
static ugui_widget_t s_line[COL_N]; /* row 1: outline */
static ugui_polygon_ext_t s_line_ext[COL_N];
#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) --- */
for (uint8_t c = 0u; c < (uint8_t)COL_N; c++) {
int16_t x = SHAPE_X(c);
/* Column caption — the shape name. */
add_label(&s_cap[c], &s_cap_ext[c], (uint8_t)(WDGT_ID_SLOT0 + c),
(int16_t)(c * COL_W), 38, COL_W, 20,
s_shapes[c].name, UGUI_ALIGN_CENTER);
/* Row 0 — filled (the default): set_points() and nothing else. */
ugui_widget_polygon_init(&s_root, &s_fill[c], &s_fill_ext[c]);
ugui_widget_set_area(&s_fill[c], x, ROW0_Y, SHAPE_WH, SHAPE_WH);
ugui_widget_polygon_set_points(&s_fill[c], s_shapes[c].pts, s_shapes[c].n);
s_fill[c].color = UGUI_THEME_ACCENT;
ugui_screen_add_widget(&s_screen, &s_fill[c]);
/* Row 1 — outline: same vertices, set_filled(false) + thicker stroke. */
ugui_widget_polygon_init(&s_root, &s_line[c], &s_line_ext[c]);
ugui_widget_set_area(&s_line[c], x, ROW1_Y, SHAPE_WH, SHAPE_WH);
ugui_widget_polygon_set_points(&s_line[c], s_shapes[c].pts, s_shapes[c].n);
ugui_widget_polygon_set_filled(&s_line[c], false);
ugui_widget_polygon_set_thickness(&s_line[c], 2u);
s_line[c].color = UGUI_THEME_TEXT;
ugui_screen_add_widget(&s_screen, &s_line[c]);
}
HMI Icon Vocabulary¶
Real icon idioms built from single polygon widgets — caution, play, stop, trend up/down (concave 7-vertex arrows) and a home glyph — each parented to a surface chip so its anti-aliased edge blends against the chip fill and the chip clips 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
};
/* --- 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_POLYGON)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
#define I 51
static const ugui_point_t k_caution[3] = { {26, 2}, {I, I}, {0, I} }; /* warning triangle */
static const ugui_point_t k_play [3] = { {8, 6}, {8, 46}, {46, 26} }; /* media play */
static const ugui_point_t k_stop [4] = { {8, 8}, {44, 8}, {44, 44}, {8, 44} }; /* media stop */
static const ugui_point_t k_up [7] = { {26, 2}, {I, 26}, {36, 26}, {36, I},
{16, I}, {16, 26}, {0, 26} }; /* trend up arrow */
static const ugui_point_t k_down [7] = { {26, I}, {I, 26}, {36, 26}, {36, 2},
{16, 2}, {16, 26}, {0, 26} }; /* trend down arrow */
static const ugui_point_t k_home [5] = { {26, 2}, {I, 22}, {I, I}, {0, I}, {0, 22} }; /* house/home */
typedef struct {
const ugui_point_t* pts;
uint8_t n;
uint32_t rgb; /* semantic colour */
bool filled; /* filled marker vs outline glyph */
const char* label;
} icon_def_t;
#define ICON_N 6
static const icon_def_t s_icons[ICON_N] = {
{ k_caution, 3u, 0xF39C12u, true, "caution" }, /* amber */
{ k_play, 3u, 0x2ECC71u, true, "play" }, /* green */
{ k_stop, 4u, 0xE74C3Cu, true, "stop" }, /* red */
{ k_up, 7u, 0x2ECC71u, true, "trend up" }, /* green */
{ k_down, 7u, 0xE74C3Cu, true, "trend dn" }, /* red */
{ k_home, 5u, 0x3498DBu, false, "home" }, /* blue outline */
};
static ugui_widget_t s_panel[ICON_N];
static ugui_box_ext_t s_panel_ext[ICON_N];
static ugui_widget_t s_icon[ICON_N];
static ugui_polygon_ext_t s_icon_ext[ICON_N];
static ugui_widget_t s_cap[ICON_N];
static ugui_label_ext_t s_cap_ext[ICON_N];
#define GRID_COLS 3
#define PANEL_WH 88
#define ICON_WH 52
#define ICON_OFF ((PANEL_WH - ICON_WH) / 2)
#define CELL_W ((int16_t)(UGUI_SCREEN_W / GRID_COLS))
#define ROW_STEP ((int16_t)(PANEL_WH + 24))
#define GRID_Y ((int16_t)(38 + 6))
#define PANEL_X(c) ((int16_t)((c) * CELL_W + ((CELL_W - PANEL_WH) / 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) --- */
for (uint8_t i = 0u; i < (uint8_t)ICON_N; i++) {
uint8_t col = (uint8_t)(i % GRID_COLS);
uint8_t row = (uint8_t)(i / GRID_COLS);
int16_t px = PANEL_X(col);
int16_t py = (int16_t)(GRID_Y + ((int16_t)row * ROW_STEP));
/* Chip: a rounded surface panel; the icon blends against its fill. */
ugui_widget_box_init(&s_root, &s_panel[i], &s_panel_ext[i]);
ugui_widget_set_area(&s_panel[i], px, py, PANEL_WH, PANEL_WH);
ugui_widget_box_set_style(&s_panel[i], true, 8u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
ugui_screen_add_widget(&s_screen, &s_panel[i]);
/* Icon — child of the panel, so set_area is panel-relative and the
* AA edge blends against UGUI_THEME_SURFACE (not the page background). */
ugui_widget_polygon_init(&s_panel[i], &s_icon[i], &s_icon_ext[i]);
ugui_widget_set_area(&s_icon[i], ICON_OFF, ICON_OFF, ICON_WH, ICON_WH);
ugui_widget_polygon_set_points(&s_icon[i], s_icons[i].pts, s_icons[i].n);
ugui_widget_polygon_set_filled(&s_icon[i], s_icons[i].filled);
if (!s_icons[i].filled) { ugui_widget_polygon_set_thickness(&s_icon[i], 3u); }
s_icon[i].color = ugui_color_hex(s_icons[i].rgb);
ugui_screen_add_widget(&s_screen, &s_icon[i]);
/* Caption under the chip. */
add_label(&s_cap[i], &s_cap_ext[i], (uint8_t)(WDGT_ID_SLOT0 + i),
(int16_t)(px - 6), (int16_t)(py + PANEL_WH + 1),
(int16_t)(PANEL_WH + 12), 18,
s_icons[i].label, UGUI_ALIGN_CENTER);
}
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.