Skip to content

Circle Widget

The Circle widget draws a disc or ring — a status LED, a dot, a bullet, a decorative accent. Init installs a filled theme-accent disc with the radius auto-inscribed from the bounding box; set_style() switches between filled/outline and sets the border width and colours; set_radius() overrides the auto radius for a smaller dot inside the same box; and set_fill_color() sets a one-off colour that does not track the theme. A circle is a pure shape with no value to hold — the shortest widget in the library.

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 Disc

Two calls make a presentable disc: ugui_widget_circle_init() installs the whole look (filled, theme-accent fill, radius auto-inscribed from the bounding box, on-theme border already seeded) and set_area() places it. There is no value to set — a circle is a pure shape.

Default Disc

/* --- 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_CIRCLE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t      s_circle;
static ugui_circle_ext_t  s_circle_ext;
#define CIR_S  (UGUI_SCREEN_H - 38 - 8)
#define CIR_X  ((UGUI_SCREEN_W - CIR_S) / 2)
#define CIR_Y  (38 + 2)
#endif

/* --- build it (in your screen's init) --- */
ugui_widget_circle_init(&s_root, &s_circle, &s_circle_ext);
ugui_widget_set_area(&s_circle, CIR_X, CIR_Y, CIR_S, CIR_S);
ugui_widget_set_id(&s_circle, WDGT_ID_DEMO);
ugui_screen_add_widget(&s_screen, &s_circle);

The same widget restyled five ways: Filled (the plain init default), Outline (set_style(filled=false, border_width) — a hollow ring), Border (a disc with a contrasting rim), Dot (set_radius() overrides the auto radius for a small dot in the same box), and Custom (set_fill_color() — a fixed colour that does not track the theme).

Style Gallery

/* --- 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_CIRCLE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
#define ST_N 5
static ugui_widget_t     s_c[ST_N];
static ugui_circle_ext_t s_c_ext[ST_N];
#endif
#if defined(UGUI_WIDGET_ENABLE_CIRCLE)
#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_widget_t    s_cap[ST_N];
static ugui_label_ext_t s_cap_ext[ST_N];
static const char* const s_cap_txt[ST_N] = {
    "Filled", "Outline", "Border", "Dot", "Custom"
};
#endif
#endif
#if defined(UGUI_WIDGET_ENABLE_CIRCLE)
#define ST_CELL_W  (UGUI_SCREEN_W / ST_N)
#define ST_DISC    76
#define ST_DISC_Y  100
#define ST_DISC_X(i)  ((int16_t)((i) * ST_CELL_W + (ST_CELL_W - ST_DISC) / 2))
#define ST_CAP_Y   (ST_DISC_Y + ST_DISC + 8)
#define ST_CAP_H   18
#endif

/* --- local helpers & event callbacks --- */
static void disc_add(uint8_t i)
{
    ugui_widget_circle_init(&s_root, &s_c[i], &s_c_ext[i]);
    ugui_widget_set_area(&s_c[i], ST_DISC_X(i), ST_DISC_Y, ST_DISC, ST_DISC);
    ugui_widget_set_id  (&s_c[i], (uint8_t)(WDGT_ID_SLOT0 + i));
    ugui_screen_add_widget(&s_screen, &s_c[i]);

#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
    ugui_widget_label_init(&s_root, &s_cap[i], &s_cap_ext[i]);
    ugui_widget_set_area(&s_cap[i], (int16_t)(i * ST_CELL_W), ST_CAP_Y,
                         (int16_t)ST_CELL_W, ST_CAP_H);
    ugui_widget_set_id  (&s_cap[i], (uint8_t)(WDGT_ID_SLOT0 + ST_N + i));
    ugui_widget_label_set_style(&s_cap[i], false, UGUI_THEME_BG, s_cap_txt[i],
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                                UGUI_ALIGN_CENTER, 1u);
    ugui_widget_set_enabled(&s_cap[i], false);   /* display-only */
    ugui_screen_add_widget(&s_screen, &s_cap[i]);
#endif
}

/* --- build it (in your screen's init) --- */
for (uint8_t i = 0u; i < (uint8_t)ST_N; i++) { disc_add(i); }

/* 0 Filled  — plain init default (filled accent disc); no styling call. */

/* 1 Outline — hollow ring: not filled, drawn as a border_width-thick rim in
 *             the border colour (seeded to the theme accent below). */
ugui_widget_circle_set_style(&s_c[1], /*filled=*/false, /*border_w=*/7u,
                             UGUI_THEME_ACCENT, UGUI_THEME_ACCENT);

/* 2 Border  — filled disc with a contrasting rim (surface fill, accent rim). */
ugui_widget_circle_set_style(&s_c[2], /*filled=*/true, /*border_w=*/6u,
                             UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);

/* 3 Dot     — override the auto radius with a small explicit one; the dot
 *             stays centred in the same bounding box. */
ugui_widget_circle_set_radius(&s_c[3], 16);

/* 4 Custom  — a per-widget colour (fixed semantic green) that does NOT track
 *             the theme accent, unlike the discs above. */
ugui_widget_circle_set_fill_color(&s_c[4], UGUI_THEME_OK);

Status LEDs (fault simulator)

Four coloured indicator discs, one per system channel. A "Cycle system state" button walks Normal → Warning → Critical, and each press recolours every disc at runtime through the plain setters (set_style()/set_fill_color()) — exactly what a real fault handler would do. An OFF channel becomes a hollow ring (filled=false).

Status LEDs (fault simulator)

/* --- 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_LED0 = 10, WDGT_ID_SLOT9, WDGT_ID_DEMO,
    WDGT_ID_SLOT7
};

/* --- 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_CIRCLE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
enum { ST_OK = 0u, ST_WARN = 1u, ST_ALARM = 2u, ST_OFF = 3u };
#define NCH 4
static ugui_widget_t     s_led[NCH];
static ugui_circle_ext_t s_led_ext[NCH];
static const uint8_t s_matrix[3][NCH] = {
    /* Normal   */ { ST_OK, ST_OK,   ST_OK,    ST_OK  },
    /* Warning  */ { ST_OK, ST_WARN, ST_WARN,  ST_OK  },
    /* Critical */ { ST_OK, ST_WARN, ST_ALARM, ST_OFF },
};
static uint8_t s_state = 0u;
static ugui_widget_t     s_btn;
static ugui_button_ext_t s_btn_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_CIRCLE)
static ugui_text_cfg_t   s_btn_text;
#endif
#if defined(UGUI_WIDGET_ENABLE_CIRCLE)
#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static const char* const s_ch_name[NCH] = {
    "Power rail", "Battery", "Coolant temp", "Data link"
};
static const char* const s_state_name[3] = { "Normal", "Warning", "Critical" };
static ugui_widget_t    s_name[NCH]; static ugui_label_ext_t s_name_ext[NCH];
static ugui_widget_t    s_word[NCH]; static ugui_label_ext_t s_word_ext[NCH];
static char             s_word_buf[NCH][8];
static ugui_widget_t    s_banner;   static ugui_label_ext_t s_banner_ext;
static char             s_banner_buf[32];
#endif
#endif
#if defined(UGUI_WIDGET_ENABLE_CIRCLE)
#define LED_X     34
#define LED_D     22
#define ROW0_Y    62
#define ROW_STEP  42
#define ROW_Y(i)  ((int16_t)(ROW0_Y + (i) * ROW_STEP))
#define NAME_X    (LED_X + LED_D + 14)
#define WORD_W    120
#define WORD_X    (UGUI_SCREEN_W - WORD_W - 24)
#define BTN_Y     266
#endif

/* --- local helpers & event callbacks --- */
static ugui_color_t status_color(uint8_t st)
{
    switch (st) {
        case ST_OK:    return UGUI_THEME_OK;
        case ST_WARN:  return UGUI_COLOR_ORANGE;
        case ST_ALARM: return UGUI_THEME_CANCEL;
        default:       return UGUI_THEME_BORDER;   /* OFF */
    }
}

static const char* status_word(uint8_t st)
{
    switch (st) {
        case ST_OK:    return "OK";
        case ST_WARN:  return "WARN";
        case ST_ALARM: return "ALARM";
        default:       return "OFF";
    }
}

static void led_apply(uint8_t i, uint8_t st)
{
    if (st == ST_OFF) {
        ugui_widget_circle_set_style(&s_led[i], /*filled=*/false, /*border_w=*/3u,
                                     UGUI_THEME_BORDER, UGUI_THEME_BORDER);
    } else {
        ugui_widget_circle_set_style(&s_led[i], /*filled=*/true, /*border_w=*/0u,
                                     status_color(st), UGUI_THEME_BORDER);
    }
}

static void apply_state(void)
{
    for (uint8_t i = 0u; i < (uint8_t)NCH; i++) {
        uint8_t st = s_matrix[s_state][i];
        led_apply(i, st);
#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
        (void)snprintf(s_word_buf[i], sizeof(s_word_buf[i]), "%s", status_word(st));
        ugui_widget_label_set_style(&s_word[i], false, UGUI_THEME_BG, s_word_buf[i],
                                    &lexend_14pt_2bpp, status_color(st),
                                    (ugui_align_t)(UGUI_ALIGN_RIGHT | UGUI_ALIGN_V_CENTER),
                                    1u);
#endif
    }
#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
    (void)snprintf(s_banner_buf, sizeof(s_banner_buf), "System: %s",
                   s_state_name[s_state]);
    ugui_label_set_text(&s_banner, s_banner_buf);
#endif
}

static void on_cycle(uint8_t widget_id, ugui_event_t event)
{
    (void)widget_id;
    if (event != UGUI_EVENT_CLICK) { return; }
    s_state = (uint8_t)((s_state + 1u) % 3u);
    apply_state();
}

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
}

/* --- build it (in your screen's init) --- */
for (uint8_t i = 0u; i < (uint8_t)NCH; i++) {
    /* LED disc — vertically centred against its text row. */
    ugui_widget_circle_init(&s_root, &s_led[i], &s_led_ext[i]);
    ugui_widget_set_area(&s_led[i], LED_X, ROW_Y(i), LED_D, LED_D);
    ugui_widget_set_id  (&s_led[i], (uint8_t)(WDGT_ID_LED0 + i));
    ugui_screen_add_widget(&s_screen, &s_led[i]);

#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
    ugui_widget_label_init(&s_root, &s_name[i], &s_name_ext[i]);
    ugui_widget_set_area(&s_name[i], NAME_X, ROW_Y(i), (int16_t)(WORD_X - NAME_X - 6), LED_D);
    ugui_widget_set_id  (&s_name[i], (uint8_t)(WDGT_ID_SLOT9 + i));
    ugui_widget_label_set_style(&s_name[i], false, UGUI_THEME_BG, s_ch_name[i],
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                                (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
    ugui_widget_set_enabled(&s_name[i], false);
    ugui_screen_add_widget(&s_screen, &s_name[i]);

    ugui_widget_label_init(&s_root, &s_word[i], &s_word_ext[i]);
    ugui_widget_set_area(&s_word[i], WORD_X, ROW_Y(i), WORD_W, LED_D);
    ugui_widget_set_id  (&s_word[i], (uint8_t)(WDGT_ID_SLOT9 + NCH + i));
    ugui_widget_set_enabled(&s_word[i], false);
    ugui_screen_add_widget(&s_screen, &s_word[i]);
#endif
}

/* Fault simulator — one button walks Normal / Warning / Critical. */
add_button(&s_btn, &s_btn_ext, &s_btn_text, WDGT_ID_DEMO,
          LED_X, BTN_Y, (int16_t)(UGUI_SCREEN_W - 2 * LED_X), 30,
          "Cycle system state", on_cycle);

#if defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
ugui_widget_label_init(&s_root, &s_banner, &s_banner_ext);
ugui_widget_set_area(&s_banner, LED_X, (int16_t)(BTN_Y - 26),
                     (int16_t)(UGUI_SCREEN_W - 2 * LED_X), 20);
ugui_widget_set_id  (&s_banner, WDGT_ID_SLOT7);
ugui_widget_label_set_style(&s_banner, false, UGUI_THEME_BG, s_banner_buf,
                            &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                            (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_widget_set_enabled(&s_banner, false);
ugui_screen_add_widget(&s_screen, &s_banner);
#endif

apply_state();   /* seed the LEDs (+ words / banner) from the initial state */

Loading Spinner (app-driven animation)

A ring of eight plain circle widgets with no special "spinner" type and no per-widget animation state: positions are laid out once, and a per-frame feed recolours each dot from its distance behind the moving "head" — call it once per frame from your main loop. A dot repaints only when its shade actually changes.

Loading Spinner (app-driven animation)

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

/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_CIRCLE)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
#define SPN_N 8
static ugui_widget_t     s_dot[SPN_N];
static ugui_circle_ext_t s_dot_ext[SPN_N];
static uint8_t           s_last_dist[SPN_N];
#define SPN_DOT      22
#define SPN_R        94
#define SPN_CX       (UGUI_SCREEN_W / 2)
#define SPN_CY       ((38 + UGUI_SCREEN_H) / 2 - 10)
#endif

/* --- build it (in your screen's init) --- */
for (uint8_t i = 0u; i < (uint8_t)SPN_N; i++) {
    int16_t a  = (int16_t)(((int32_t)i * 360) / SPN_N);
    int16_t sn = ugui_isin(a);                              /* sin(a)  */
    int16_t cs = ugui_isin((int16_t)((a + 90) % 360));      /* cos(a)  */
    int16_t px = (int16_t)(SPN_CX + ((int32_t)SPN_R * sn) / 255);
    int16_t py = (int16_t)(SPN_CY - ((int32_t)SPN_R * cs) / 255);

    ugui_widget_circle_init(&s_root, &s_dot[i], &s_dot_ext[i]);
    ugui_widget_set_area(&s_dot[i], (int16_t)(px - SPN_DOT / 2),
                         (int16_t)(py - SPN_DOT / 2), SPN_DOT, SPN_DOT);
    ugui_widget_set_id(&s_dot[i], (uint8_t)(WDGT_ID_DOT0 + i));
    ugui_screen_add_widget(&s_screen, &s_dot[i]);

    s_last_dist[i] = 0xFFu;   /* sentinel — force the first feed to paint every dot */
}


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.