Skip to content

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().

Default NumPad

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

Built-In Header, Preview & OK/Cancel

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

Tap-to-Edit Popup 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.