Skip to content

Gauge Widget

The Gauge is a classic needle dial — automotive speedometer, medical pressure gauge, any instrument reading. Init installs a 270° sweep, ticks, a damped AA needle and hub; set_area() places it, set_range() maps the application units. set_zone() paints coloured bands and fires an edge-triggered on_zone callback the moment the value crosses into a new one — the integration point for an alarm manager. set_dynamics() switches between an instant, no-lag needle (clinical displays) and slew + first-order damping (a mechanical instrument's glide); set_needle() restyles the needle itself, and two gauges side by side are just two parameter sets on the same widget.

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 Gauge (live feed)

Three calls make a presentable instrument: ugui_widget_gauge_init() installs the whole look (270° sweep, 9×4 ticks, red AA needle, hub, damped motion), set_area() places it, set_range() maps the units. The dial face never repaints while the needle moves — the tick hook invalidates only the needle strip.

Default Gauge (live feed)

/* --- 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
};

/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_GAUGE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_gauge;
static ugui_gauge_ext_t s_gauge_ext;
#define DIAL_S  (UGUI_SCREEN_H - 38 - 8)
#define DIAL_X  ((UGUI_SCREEN_W - DIAL_S) / 2)
#define DIAL_Y  (38 + 2)
#endif

/* --- build it (in your screen's init) --- */
ugui_widget_gauge_init(&s_root, &s_gauge, &s_gauge_ext);
ugui_widget_set_area(&s_gauge, DIAL_X, DIAL_Y, DIAL_S, DIAL_S);
ugui_widget_gauge_set_range(&s_gauge, 0, 100);
ugui_widget_set_id(&s_gauge, WDGT_ID_DEMO);
ugui_screen_add_widget(&s_screen, &s_gauge);

Automotive Speedometer (live feed)

A 0-240 km/h dial with scale numerals at the major ticks, a red zone from 200 (add_zone()), and two in-widget labels — the digital speed + "km/h" unit — the add_label() slots, not child widgets. The digital readout follows get_shown(), the DAMPED needle value, so the number and needle always agree.

Automotive Speedometer (live feed)

/* --- 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_GAUGE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_gauge;
static ugui_gauge_ext_t s_gauge_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_GAUGE)
static ugui_text_cfg_t  s_val_text;
static ugui_text_cfg_t  s_unit_text;
static char             s_val_str[8];
#endif
#if defined(UGUI_WIDGET_ENABLE_GAUGE)
#define DIAL_S  (UGUI_SCREEN_H - 38 - 8)
#define DIAL_X  ((UGUI_SCREEN_W - DIAL_S) / 2)
#define DIAL_Y  (38 + 2)
#define VAL_X   ((DIAL_S / 2) - 35)
#define VAL_Y   ((DIAL_S / 2) + 26)
#define VAL_W   70
#define VAL_H   18
#define UNIT_Y  (VAL_Y + VAL_H + 2)
#endif

/* --- build it (in your screen's init) --- */
ugui_widget_gauge_init(&s_root, &s_gauge, &s_gauge_ext);
ugui_widget_set_area(&s_gauge, DIAL_X, DIAL_Y, DIAL_S, DIAL_S);
ugui_widget_set_id(&s_gauge, WDGT_ID_DEMO);
ugui_widget_gauge_set_range(&s_gauge, 0, 240);
(void)ugui_widget_gauge_add_zone(&s_gauge, 200, 240, UGUI_COLOR_RED);
#ifdef UGUI_ENABLE_FONT
ugui_widget_gauge_set_numerals(&s_gauge, &lexend_14pt_2bpp);

s_val_str[0] = '0'; s_val_str[1] = '\0';
ugui_text_cfg_init(&s_val_text, s_val_str, &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                   UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 0u);
s_val_text.area = (ugui_rect_t){ VAL_X, VAL_Y, VAL_W, VAL_H };
(void)ugui_widget_gauge_add_label(&s_gauge, &s_val_text);

ugui_text_cfg_init(&s_unit_text, "km/h", &lexend_14pt_2bpp,
                   ugui_color_blend(UGUI_THEME_TEXT, UGUI_THEME_BG, 140u),
                   UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 0u);
s_unit_text.area = (ugui_rect_t){ VAL_X, UNIT_Y, VAL_W, VAL_H };
(void)ugui_widget_gauge_add_label(&s_gauge, &s_unit_text);
#endif
ugui_screen_add_widget(&s_screen, &s_gauge);

Medical Pressure Dial (zone alarm)

An airway-pressure gauge (0-60 cmH2O, 240° sweep) with three clinical bands — amber LOW, green NORMAL, red HIGH — classified every set_value() and reported through the edge-triggered on_zone callback, which this screen turns into a status banner. Dynamics are off (slew 0 / damping 0): a clinical display shows the value as measured, no cosmetic lag.

Medical Pressure Dial (zone alarm)

/* --- 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_GAUGE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_gauge;
static ugui_gauge_ext_t s_gauge_ext;
static ugui_widget_t    s_banner;              /* zone status (alarm hook)    */
static ugui_box_ext_t   s_banner_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_GAUGE)
static ugui_text_cfg_t  s_banner_text;
static ugui_text_cfg_t  s_val_text;
static char             s_val_str[8];
#endif
#if defined(UGUI_WIDGET_ENABLE_GAUGE)
#define DIAL_S   (UGUI_SCREEN_H - 38 - 8)
#define DIAL_X   10
#define DIAL_Y   (38 + 2)
#define BAN_X    (DIAL_X + DIAL_S + 12)
#define BAN_Y    (DIAL_Y + 24)
#define BAN_W    (UGUI_SCREEN_W - BAN_X - 10)
#define BAN_H    44
#define VAL_X    ((DIAL_S / 2) - 35)
#define VAL_Y    ((DIAL_S / 2) + 24)
#define VAL_W    70
#define VAL_H    18
#endif

/* --- local helpers & event callbacks --- */
static void on_zone(ugui_widget_t* w, int8_t zone, void* ctx)
{
    static const char* const k_txt[] = { "LOW", "NORMAL", "HIGH" };
    ugui_color_t fill;
    (void)w; (void)ctx;

    fill = (zone == 2) ? UGUI_COLOR_RED
         : (zone == 0) ? UGUI_COLOR_AMBER
         : (zone == 1) ? UGUI_COLOR_GREEN
                       : UGUI_THEME_SURFACE;   /* -1: outside all zones */
    ugui_widget_box_set_style(&s_banner, true, 4u, 0u, fill, fill);
#ifdef UGUI_ENABLE_FONT
    s_banner_text.str = (zone >= 0) ? k_txt[zone] : "--";
#endif
    ugui_widget_invalidate(&s_banner);
    printf("[gauge.medical] zone -> %d\n", (int)zone);
}

/* --- build it (in your screen's init) --- */
ugui_widget_gauge_init(&s_root, &s_gauge, &s_gauge_ext);
ugui_widget_set_area(&s_gauge, DIAL_X, DIAL_Y, DIAL_S, DIAL_S);
ugui_widget_set_id(&s_gauge, WDGT_ID_DEMO);
ugui_widget_gauge_set_range(&s_gauge, 0, 60);
ugui_widget_gauge_set_angles(&s_gauge, -1200, 1200);
ugui_widget_gauge_set_needle(&s_gauge, 0u, 2u, 0u, UGUI_THEME_TEXT); /* theme-
                                          aware: dark on light, white on dark */
ugui_widget_gauge_set_dynamics(&s_gauge, 0u, 0u);
(void)ugui_widget_gauge_add_zone(&s_gauge,  0,  5, UGUI_COLOR_AMBER);
(void)ugui_widget_gauge_add_zone(&s_gauge,  5, 35, UGUI_COLOR_GREEN);
(void)ugui_widget_gauge_add_zone(&s_gauge, 35, 60, UGUI_COLOR_RED);
ugui_widget_gauge_set_on_zone(&s_gauge, on_zone, NULL);
#ifdef UGUI_ENABLE_FONT
ugui_widget_gauge_set_numerals(&s_gauge, &lexend_14pt_2bpp);
ugui_widget_gauge_set_ticks(&s_gauge, 7u, 1u, 10u, 5u);   /* 0,10,..,60 */

s_val_str[0] = '5'; s_val_str[1] = '\0';
ugui_text_cfg_init(&s_val_text, s_val_str, &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                   UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 0u);
s_val_text.area = (ugui_rect_t){ VAL_X, VAL_Y, VAL_W, VAL_H };
(void)ugui_widget_gauge_add_label(&s_gauge, &s_val_text);
#endif
ugui_screen_add_widget(&s_screen, &s_gauge);

/* Zone status banner — an ordinary box the on_zone callback repaints.
 * enabled=0: display-only, it must not take touch. */
ugui_widget_box_init(&s_root, &s_banner, &s_banner_ext);
ugui_widget_set_area(&s_banner, BAN_X, BAN_Y, BAN_W, BAN_H);
ugui_widget_box_set_style(&s_banner, true, 4u, 0u, UGUI_COLOR_GREEN, UGUI_COLOR_GREEN);
ugui_widget_set_enabled(&s_banner, false);
#ifdef UGUI_ENABLE_FONT
ugui_text_cfg_init(&s_banner_text, "NORMAL", &lexend_14pt_2bpp, UGUI_COLOR_WHITE,
                   UGUI_ALIGN_CENTER, (ugui_point_t){ 0, 0 }, 1u);
ugui_widget_box_set_text(&s_banner, &s_banner_text);
#endif
ugui_screen_add_widget(&s_screen, &s_banner);

Styles + Dynamics

Both dials receive the SAME square-wave target: left "Instant" has dynamics off (the needle snaps, sparse ticks, thin cyan needle); right "Damped" keeps the init defaults — slew + first-order lag plus a counterweight tail — and glides like a mechanical instrument. Side by side the motion model is obvious.

Styles + Dynamics

/* --- 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_INSTANT = 10, WDGT_ID_DAMPED
};

/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_GAUGE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_instant;
static ugui_gauge_ext_t s_instant_ext;
static ugui_widget_t    s_damped;
static ugui_gauge_ext_t s_damped_ext;
#define DIAL_S   150
#define DIAL_Y   (38 + 2)
#define DIAL_LX  ((UGUI_SCREEN_W / 2 - DIAL_S) / 2)
#define DIAL_RX  (UGUI_SCREEN_W / 2 + DIAL_LX)
#endif

/* --- build it (in your screen's init) --- */
/* Left: instant needle, sparse scale, thin cyan needle. */
ugui_widget_gauge_init(&s_root, &s_instant, &s_instant_ext);
ugui_widget_set_area(&s_instant, DIAL_LX, DIAL_Y, DIAL_S, DIAL_S);
ugui_widget_set_id(&s_instant, WDGT_ID_INSTANT);
ugui_widget_gauge_set_range(&s_instant, 0, 100);
ugui_widget_gauge_set_dynamics(&s_instant, 0u, 0u);        /* snap        */
ugui_widget_gauge_set_ticks(&s_instant, 5u, 1u, 10u, 5u);
ugui_widget_gauge_set_needle(&s_instant, 0u, 2u, 0u, UGUI_THEME_ACCENT); /* theme
                                          accent — readable in light + dark */
ugui_widget_gauge_set_hub(&s_instant, 4u);
ugui_screen_add_widget(&s_screen, &s_instant);

/* Right: default damping + slew, counterweight tail. */
ugui_widget_gauge_init(&s_root, &s_damped, &s_damped_ext);
ugui_widget_set_area(&s_damped, DIAL_RX, DIAL_Y, DIAL_S, DIAL_S);
ugui_widget_set_id(&s_damped, WDGT_ID_DAMPED);
ugui_widget_gauge_set_range(&s_damped, 0, 100);
ugui_widget_gauge_set_needle(&s_damped, 0u, 3u, 16u, UGUI_COLOR_RED);
ugui_screen_add_widget(&s_screen, &s_damped);


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.