NumPad Widget¶
The NumPad is a numeric entry keypad — a themed 4×3 key grid (digits,
decimal point, backspace) for rate/dose/setpoint fields. Init installs the
whole look (theme-tracked SURFACE keys, an ACCENT press highlight that
fades back out) and the built-in dark/light layout at a settable size;
set_font() gives it text and set_callback() reports each key as an ASCII
digit/. code or UGUI_NUMPAD_KEY_BACKSPACE — the app owns the edit
buffer. enable_header_preview() adds a muted-accent title bar plus a live
preview strip of the buffer, and enable_ok_cancel() adds the themed
OK/Cancel row; together they turn the bare grid into a self-contained
editor. Passing a bg_root to enable_ok_cancel() (or using
ugui_numpad_popup_open() directly) switches it into POPUP mode: hidden
until opened, it repaints the background on OK/Cancel and copies its
preview text back into the caller's buffer — the "tap a value field"
pattern.
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 NumPad¶
A bare 4×3 keypad exactly as ugui_widget_numpad_init() delivers it: every colour comes from the theme roles and the built-in layout matches dark/light mode automatically. The panel on the right echoes each key code the widget reports via set_callback().

/* --- 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
};
/* --- 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_NUM_KEYPAD)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_echo;
#endif
#if defined(UGUI_WIDGET_ENABLE_NUM_KEYPAD)
static char s_echo_str[16];
#endif
#if defined(UGUI_WIDGET_ENABLE_NUM_KEYPAD)
static ugui_widget_t s_numpad;
static ugui_numkeypad_ext_t s_numpad_ext;
#define NP_X 20
#define NP_Y (38 + 4)
#define NP_W (UGUI_SCREEN_W - 152) /* 168 on a 320 panel */
#define NP_H (UGUI_SCREEN_H - NP_Y - 10)
#endif
/* --- local helpers & event callbacks --- */
static void on_key(uint8_t code, void* ctx)
{
(void)ctx;
#ifdef UGUI_ENABLE_FONT
if (code == (uint8_t)UGUI_NUMPAD_KEY_BACKSPACE) {
(void)snprintf(s_echo_str, sizeof(s_echo_str), "key: BKSP");
} else {
(void)snprintf(s_echo_str, sizeof(s_echo_str), "key: %c", (char)code);
}
ugui_widget_invalidate(&s_echo);
#endif
printf("[numpad.default] key 0x%02X\n", (unsigned)code);
}
/* --- build it (in your screen's init) --- */
ugui_widget_numpad_init(&s_root, &s_numpad, &s_numpad_ext);
ugui_widget_set_area(&s_numpad, NP_X, NP_Y, (int16_t)NP_W, (int16_t)NP_H);
ugui_widget_set_id(&s_numpad, WDGT_ID_DEMO);
#ifdef UGUI_ENABLE_FONT
ugui_widget_numpad_set_font(&s_numpad, &lexend_14pt_2bpp);
#endif
ugui_widget_numpad_set_callback(&s_numpad, on_key, NULL);
ugui_screen_add_widget(&s_screen, &s_numpad);
Built-In Header, Preview & OK/Cancel¶
enable_header_preview() draws a title bar and a live preview of the edit buffer; enable_ok_cancel() adds the themed OK/Cancel row. In non-popup mode (bg_root = NULL) the app owns the editing — the key callback appends digits and handles backspace, then invalidates the widget so the preview repaints.

/* --- 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
};
/* --- 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_NUM_KEYPAD)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_readout;
static ugui_widget_t s_numpad;
static ugui_numkeypad_ext_t s_numpad_ext;
static char s_edit_buf[12]; /* live entry (preview strip) */
#endif
#if defined(UGUI_WIDGET_ENABLE_NUM_KEYPAD)
static char s_committed_str[20];
#endif
#if defined(UGUI_WIDGET_ENABLE_NUM_KEYPAD)
#define NP_X 20
#define NP_Y (38 + 4)
#define NP_W (UGUI_SCREEN_W - 140) /* 180 on a 320 panel */
#define NP_H (UGUI_SCREEN_H - NP_Y - 10)
#endif
/* --- local helpers & event callbacks --- */
static uint8_t buf_len(const char* s)
{
uint8_t n = 0u;
while ((n < (uint8_t)(sizeof(s_edit_buf) - 1u)) && (s[n] != '\0')) { n++; }
return n;
}
static void on_key(uint8_t code, void* ctx)
{
(void)ctx;
uint8_t len = buf_len(s_edit_buf);
if (code == (uint8_t)UGUI_NUMPAD_KEY_BACKSPACE) {
if (len > 0u) { s_edit_buf[len - 1u] = '\0'; }
} else if (code == (uint8_t)UGUI_NUMPAD_KEY_DECIMAL) {
bool has_dot = false;
uint8_t i;
for (i = 0u; i < len; i++) {
if (s_edit_buf[i] == '.') { has_dot = true; }
}
if ((!has_dot) && (len < (uint8_t)(sizeof(s_edit_buf) - 1u))) {
s_edit_buf[len] = '.';
s_edit_buf[len + 1u] = '\0';
}
} else if ((code >= (uint8_t)UGUI_NUMPAD_KEY_0) &&
(code <= (uint8_t)UGUI_NUMPAD_KEY_9)) {
if (len < (uint8_t)(sizeof(s_edit_buf) - 1u)) {
s_edit_buf[len] = (char)code;
s_edit_buf[len + 1u] = '\0';
}
} else {
/* other codes: ignore */
}
ugui_widget_invalidate(&s_numpad); /* repaint the preview strip */
}
static void on_ok(void* ctx)
{
(void)ctx;
#ifdef UGUI_ENABLE_FONT
(void)snprintf(s_committed_str, sizeof(s_committed_str), "%s",
(s_edit_buf[0] != '\0') ? s_edit_buf : "-");
ugui_widget_invalidate(&s_readout);
#endif
printf("[numpad.entry] OK -> \"%s\"\n", s_edit_buf);
}
static void on_cancel(void* ctx)
{
(void)ctx;
s_edit_buf[0] = '\0';
ugui_widget_invalidate(&s_numpad);
printf("[numpad.entry] Cancel — entry cleared\n");
}
/* --- build it (in your screen's init) --- */
ugui_widget_numpad_init(&s_root, &s_numpad, &s_numpad_ext);
ugui_widget_set_area(&s_numpad, NP_X, NP_Y, (int16_t)NP_W, (int16_t)NP_H);
ugui_widget_set_id(&s_numpad, WDGT_ID_DEMO);
#ifdef UGUI_ENABLE_FONT
ugui_widget_numpad_set_font(&s_numpad, &lexend_14pt_2bpp);
#endif
ugui_widget_numpad_set_callback(&s_numpad, on_key, NULL);
ugui_widget_numpad_enable_header_preview(&s_numpad, 22u, 26u, "Set flow ml/h",
s_edit_buf, (uint8_t)sizeof(s_edit_buf));
/* bg_root = NULL keeps the widget visible (no popup) and leaves the OK /
* Cancel actions to the application: */
ugui_widget_numpad_enable_ok_cancel(&s_numpad, 30u, NULL);
s_numpad_ext.on_ok = on_ok;
s_numpad_ext.on_cancel = on_cancel;
ugui_screen_add_widget(&s_screen, &s_numpad);
Tap-to-Edit Popup Form¶
Two numeric form fields (rate/volume); tapping one calls ugui_numpad_popup_open() — the numpad appears centred over the form, pre-filled with the field's current text. The built-in popup handlers do ALL the editing: OK copies the preview back into the field and repaints it, Cancel discards; both hide the numpad and repaint the form.

/* --- 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
};
/* --- 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_NUM_KEYPAD) && defined(UGUI_WIDGET_ENABLE_TEXTEDIT)
static ugui_screen_t s_screen;
static ugui_widget_t s_root;
static ugui_widget_t s_numpad;
static ugui_numkeypad_ext_t s_numpad_ext;
static char s_edit_buf[12]; /* popup preview buffer */
#define POP_W 272
#define POP_H 208
#define POP_X ((UGUI_SCREEN_W - POP_W) / 2)
#define POP_Y ((UGUI_SCREEN_H - POP_H) / 2)
#endif
/* --- build it (in your screen's init) --- */
s_edit_buf[0] = '\0';
ugui_widget_numpad_init(NULL, &s_numpad, &s_numpad_ext);
ugui_widget_set_area(&s_numpad, POP_X, POP_Y, POP_W, POP_H);
ugui_widget_set_id(&s_numpad, WDGT_ID_DEMO);
#ifdef UGUI_ENABLE_FONT
ugui_widget_numpad_set_font(&s_numpad, &lexend_14pt_2bpp);
#endif
ugui_widget_numpad_enable_header_preview(&s_numpad, 22u, 28u, "Enter value",
s_edit_buf, (uint8_t)sizeof(s_edit_buf));
ugui_widget_numpad_enable_ok_cancel(&s_numpad, 30u, &s_root);
ugui_screen_add_widget(&s_screen, &s_numpad);
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.