Skip to content

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.

Default Markers

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

Shapes: Filled & Outline

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

HMI Icon Vocabulary

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