Table Widget¶
The Table widget is a data grid — a results report, a live vitals panel, an alarm/event log. Cells are a flat, app-owned array of 8-byte tagged-union entries (text or icon), so a static report can live entirely in flash (zero content RAM). A pinned header row, per-cell alignment and semantic status/fill colours (IEC 60601-1-8 style: red+white / amber+black) are all part of the cell format. Value cells use guarded setters — they compare before writing and repaint only on an actual change, so re-asserting every cell every frame costs nothing between real data changes.
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¶
Static Results (flash-only)¶
A pre-use self-test report whose cell array is entirely static const — the whole table costs zero RAM. Shows a pinned header row (set_header_rows()), per-cell alignment, a fixed+flex column mix, and semantic statuses (PASS in nominal green, a FAIL row as an ALERT+FILL cell, a stale reading in disabled gray). Beside it, a NULL-cells table — grid rules only, an empty layout frame.

/* --- 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_SLOT4 = 10, WDGT_ID_SLOT5
};
/* --- 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_TABLE)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_cap_tbl;
static ugui_label_ext_t s_cap_tbl_ext;
static ugui_widget_t s_cap_null;
static ugui_label_ext_t s_cap_null_ext;
static ugui_widget_t s_tbl; /* results table */
static ugui_table_ext_t s_tbl_ext;
static ugui_widget_t s_frame; /* NULL-cells empty grid */
static ugui_table_ext_t s_frame_ext;
#define C_L(s) { { (s) }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } }
#define C_R(s) { { (s) }, UGUI_TABLE_CELL_ALIGN_RIGHT, { 0u, 0u, 0u } }
#define C_C(s, f) { { (s) }, (uint8_t)(UGUI_TABLE_CELL_ALIGN_CENTER | (f)), { 0u, 0u, 0u } }
static const ugui_table_cell_t s_cells[5u * 3u] = {
C_L("TEST"), C_R("VALUE"), C_C("RESULT", 0u),
C_L("Leak"), C_R("3 ml/min"), C_C("PASS", UGUI_TABLE_ST_NOMINAL),
C_L("Occlusion"), C_R("812 mmHg"), C_C("PASS", UGUI_TABLE_ST_NOMINAL),
C_L("Battery"), C_R("6.4 V"), C_C("PASS", UGUI_TABLE_ST_NOMINAL),
{ { "Sensor" }, (uint8_t)(UGUI_TABLE_CELL_ALIGN_LEFT | UGUI_TABLE_ST_DISABLED), { 0u, 0u, 0u } },
C_R("---"), C_C("FAIL", UGUI_TABLE_ST_ALERT | UGUI_TABLE_CELL_FILL),
};
static const int16_t s_col_w[3] = { 0, 110, 90 };
#define TBL_X 20
#define TBL_Y ((int16_t)(38 + 26))
#define TBL_RH 26
#define TBL_W 310
#define TBL_H (5 * TBL_RH)
#define FRM_X ((int16_t)(TBL_X + TBL_W + 24))
#define FRM_W ((int16_t)(UGUI_SCREEN_W - FRM_X - 20))
#define FRM_H (4 * TBL_RH)
#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) --- */
add_label(&s_cap_tbl, &s_cap_tbl_ext, WDGT_ID_SLOT4,
TBL_X, 38, TBL_W, 22,
"flash cells - header, align, status",
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
ugui_widget_table_init(&s_root, &s_tbl, &s_tbl_ext);
ugui_widget_set_area(&s_tbl, TBL_X, TBL_Y, TBL_W, TBL_H);
ugui_widget_table_set_font(&s_tbl, &lexend_14pt_2bpp);
ugui_widget_table_set_geometry(&s_tbl, TBL_RH, 8u, 1u);
ugui_widget_table_set_grid(&s_tbl, s_cells, 5u, 3u);
ugui_widget_table_set_col_widths(&s_tbl, s_col_w);
ugui_widget_table_set_header_rows(&s_tbl, 1u);
ugui_screen_add_widget(&s_screen, &s_tbl);
/* NULL-cells table: surface + grid rules only — an empty layout frame
* (zero content RAM), the line/polygon/LED NULL-ext convention. */
add_label(&s_cap_null, &s_cap_null_ext, WDGT_ID_SLOT5,
FRM_X, 38, FRM_W, 22,
"NULL cells",
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER));
ugui_widget_table_init(&s_root, &s_frame, &s_frame_ext);
ugui_widget_set_area(&s_frame, FRM_X, TBL_Y, FRM_W, FRM_H);
ugui_widget_table_set_geometry(&s_frame, TBL_RH, 8u, 1u);
ugui_widget_table_set_grid(&s_frame, NULL, 4u, 3u);
ugui_screen_add_widget(&s_screen, &s_frame);
Live Vitals (guarded setters)¶
A patient-vitals grid whose VALUE/STAT cells are re-asserted every frame through the guarded set_cell_text()/set_cell_status() setters — they invalidate only on a real change, so the table repaints at the data rate, not the frame rate. SpO2 sweeps nominal green → caution amber → ALERT+FILL and back; Temp alternates between a live reading and the stale-data pattern.

/* --- 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_TABLE)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_tbl;
static ugui_table_ext_t s_tbl_ext;
#define ROWS 5u
#define COLS 4u
static ugui_table_cell_t s_cells[ROWS * COLS] = {
{ { "PARAM" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "VALUE" }, UGUI_TABLE_CELL_ALIGN_RIGHT, { 0u, 0u, 0u } },
{ { "UNIT" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "STAT" }, UGUI_TABLE_CELL_ALIGN_CENTER, { 0u, 0u, 0u } },
{ { "Heart rate" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "--" }, UGUI_TABLE_CELL_ALIGN_RIGHT, { 0u, 0u, 0u } },
{ { "bpm" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "OK" }, UGUI_TABLE_CELL_ALIGN_CENTER,{ 0u, 0u, 0u } },
{ { "SpO2" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "--" }, UGUI_TABLE_CELL_ALIGN_RIGHT, { 0u, 0u, 0u } },
{ { "%" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "OK" }, UGUI_TABLE_CELL_ALIGN_CENTER,{ 0u, 0u, 0u } },
{ { "NIBP" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "120/80" }, UGUI_TABLE_CELL_ALIGN_RIGHT, { 0u, 0u, 0u } },
{ { "mmHg" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "OK" }, UGUI_TABLE_CELL_ALIGN_CENTER,{ 0u, 0u, 0u } },
{ { "Temp" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "--" }, UGUI_TABLE_CELL_ALIGN_RIGHT, { 0u, 0u, 0u } },
{ { "C" }, UGUI_TABLE_CELL_ALIGN_LEFT, { 0u, 0u, 0u } },
{ { "OK" }, UGUI_TABLE_CELL_ALIGN_CENTER,{ 0u, 0u, 0u } },
};
static const int16_t s_col_w[COLS] = { 0, 90, 70, 80 };
#define TBL_X 20
#define TBL_Y ((int16_t)(38 + 4))
#define TBL_RH 30
#define TBL_W 330
#define TBL_H (5 * TBL_RH)
#endif
/* --- build it (in your screen's init) --- */
ugui_widget_table_init(&s_root, &s_tbl, &s_tbl_ext);
ugui_widget_set_area(&s_tbl, TBL_X, TBL_Y, TBL_W, TBL_H);
ugui_widget_table_set_font(&s_tbl, &lexend_14pt_2bpp);
ugui_widget_table_set_geometry(&s_tbl, TBL_RH, 8u, 1u);
ugui_widget_table_set_grid(&s_tbl, s_cells, (uint8_t)ROWS, (uint8_t)COLS);
ugui_widget_table_set_col_widths(&s_tbl, s_col_w);
ugui_widget_table_set_header_rows(&s_tbl, 1u);
ugui_screen_add_widget(&s_screen, &s_tbl);
Alarm Log (scroll, icons, selection)¶
A 15-entry alarm history in a 7-row viewport: pinned header, zebra striping, a right-edge scrollbar, ICON cells tinted by the cell's semantic status, row selection via set_on_select(), and scroll_to() for jumping the viewport (log tail-follow).

/* --- 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_SLOT1, WDGT_ID_SLOT4
};
/* --- image assets (inc/icons/ + inc/images/, shipped with the library;
* each header DEFINES its image — include it in ONE .c only) --- */
#ifdef UGUI_WIDGET_ENABLE_IMAGE
#include "icons/alert_icon_1_grayscale_4.h" /* defines img_alert_icon_1 */
#endif
/* --- 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_TABLE)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_top_btn;
static ugui_button_ext_t s_top_btn_ext;
static ugui_text_cfg_t s_top_btn_text;
static ugui_widget_t s_tail_btn;
static ugui_button_ext_t s_tail_btn_ext;
static ugui_text_cfg_t s_tail_btn_text;
static ugui_widget_t s_status;
static ugui_label_ext_t s_status_ext;
static char s_sel_buf[28];
static ugui_widget_t s_tbl;
static ugui_table_ext_t s_tbl_ext;
#define ROWS 16u /* 1 header + 15 log entries */
#define COLS 4u
#define C_TXT(s, f) { { (s) }, (uint8_t)(f), { 0u, 0u, 0u } }
#define C_ICO(st) { { .icon = &img_alert_icon_1 }, \
(uint8_t)(UGUI_TABLE_CELL_ICON | (st) | UGUI_TABLE_CELL_ALIGN_CENTER), \
{ 0u, 0u, 0u } }
#define C_NONE { { NULL }, 0u, { 0u, 0u, 0u } }
#define ROW_HI(t, src) \
C_ICO(UGUI_TABLE_ST_ALERT), C_TXT(t, 0u), C_TXT(src, 0u), \
C_TXT("HIGH", UGUI_TABLE_ST_ALERT | UGUI_TABLE_CELL_FILL | UGUI_TABLE_CELL_ALIGN_CENTER)
#define ROW_MED(t, src) \
C_ICO(UGUI_TABLE_ST_CAUTION), C_TXT(t, 0u), C_TXT(src, 0u), \
C_TXT("MED", UGUI_TABLE_ST_CAUTION | UGUI_TABLE_CELL_ALIGN_CENTER)
#define ROW_LOW(t, src) \
C_NONE, C_TXT(t, 0u), C_TXT(src, 0u), \
C_TXT("LOW", UGUI_TABLE_ST_INFO | UGUI_TABLE_CELL_ALIGN_CENTER)
static const ugui_table_cell_t s_cells[ROWS * COLS] = {
C_NONE, C_TXT("TIME", 0u), C_TXT("SOURCE", 0u),
C_TXT("PRI", UGUI_TABLE_CELL_ALIGN_CENTER),
ROW_LOW("07:58", "Self test pass"),
ROW_LOW("08:02", "Rec started"),
ROW_MED("08:14", "Signal weak"),
ROW_LOW("08:20", "On mains"),
ROW_HI ("08:31", "SpO2 low"),
ROW_MED("08:32", "Lead off"),
ROW_LOW("08:40", "Rec resumed"),
ROW_MED("08:57", "NIBP timeout"),
ROW_LOW("09:05", "Operator login"),
ROW_HI ("09:12", "Occlusion"),
ROW_MED("09:18", "Battery low"),
ROW_LOW("09:26", "Limits changed"),
ROW_HI ("09:33", "Apnea"),
ROW_LOW("09:41", "Alarm silenced"),
ROW_LOW("09:44", "Alarm re-armed"),
};
static const int16_t s_col_w[COLS] = { 34, 70, 0, 70 };
#define TBL_X 20
#define TBL_Y ((int16_t)(38 + 4))
#define TBL_RH 26
#define TBL_W ((int16_t)(UGUI_SCREEN_W - 160))
#define TBL_H (7 * TBL_RH)
#define BTN_X ((int16_t)(UGUI_SCREEN_W - 128))
#define BTN_W 108
#endif
/* --- local helpers & event callbacks --- */
static void on_top(uint8_t widget_id, ugui_event_t event)
{
(void)widget_id;
if (event == UGUI_EVENT_CLICK) { ugui_widget_table_scroll_to(&s_tbl, 1u); }
}
static void on_tail(uint8_t widget_id, ugui_event_t event)
{
(void)widget_id;
if (event == UGUI_EVENT_CLICK) {
ugui_widget_table_scroll_to(&s_tbl, (uint16_t)(ROWS - 1u));
}
}
static void on_select(ugui_widget_t* w, uint16_t row, void* ctx)
{
(void)w; (void)ctx;
/* SOURCE column of the selected entry (col 2 is always a text cell). */
const char* src = s_cells[((uint16_t)row * COLS) + 2u].u.text;
(void)snprintf(s_sel_buf, sizeof(s_sel_buf), "#%u %s", (unsigned)row,
(src != NULL) ? src : "-");
ugui_widget_label_update_text_str(&s_status, s_sel_buf);
}
static void add_button(ugui_widget_t* w, ugui_button_ext_t* ext, ugui_text_cfg_t* text,
uint8_t id, int16_t x, int16_t y, int16_t bw, int16_t bh,
const char* str, ugui_event_cb_t cb)
{
ugui_widget_button_init(&s_root, w, ext);
ugui_widget_set_area(w, x, y, bw, bh);
ugui_widget_set_id(w, id);
ugui_widget_button_set_style(w, true, 5u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(text, str, &lexend_14pt_2bpp, UGUI_THEME_TEXT,
UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_button_set_text(w, text);
#endif
ugui_widget_set_event_cb(w, cb,
UGUI_EMASK_CLICK | UGUI_EMASK_DOWN | UGUI_EMASK_UP
| UGUI_EMASK_KEY_CONFIRM);
ugui_screen_add_widget(&s_screen, w);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, w);
#endif
}
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_table_init(&s_root, &s_tbl, &s_tbl_ext);
ugui_widget_set_area(&s_tbl, TBL_X, TBL_Y, TBL_W, TBL_H);
ugui_widget_table_set_font(&s_tbl, &lexend_14pt_2bpp);
ugui_widget_table_set_geometry(&s_tbl, TBL_RH, 6u, 1u);
ugui_widget_table_set_grid(&s_tbl, s_cells, (uint8_t)ROWS, (uint8_t)COLS);
ugui_widget_table_set_col_widths(&s_tbl, s_col_w);
ugui_widget_table_set_header_rows(&s_tbl, 1u);
ugui_widget_table_set_zebra(&s_tbl, true);
ugui_widget_table_set_on_select(&s_tbl, on_select, NULL);
ugui_screen_add_widget(&s_screen, &s_tbl);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_tbl); /* keypad */
#endif
/* scroll_to(): Top/Tail buttons jump the viewport (log tail-follow). */
add_button(&s_top_btn, &s_top_btn_ext, &s_top_btn_text, WDGT_ID_SLOT0,
BTN_X, TBL_Y, BTN_W, 28, "Top", on_top);
add_button(&s_tail_btn, &s_tail_btn_ext, &s_tail_btn_text, WDGT_ID_SLOT1,
BTN_X, (int16_t)(TBL_Y + 34), BTN_W, 28, "Tail", on_tail);
/* Selection readout, updated by on_select() above. */
(void)snprintf(s_sel_buf, sizeof(s_sel_buf), "tap a row");
add_label(&s_status, &s_status_ext, WDGT_ID_SLOT4,
BTN_X, (int16_t)(TBL_Y + 74), BTN_W, 44,
s_sel_buf,
(ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_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.