Skip to content

Image Widget

The Image widget draws a compressed flash asset — an icon, glyph or sprite — the library's answer to "a picture," at a fraction of a raw bitmap's size (grayscale-4 icons run ≈290 bytes). Init installs a default TINT (theme ACCENT) and BG (page background) at a settable size; set_src() points it at a ugui_compressed_image_t asset from inc/icons/. Because the asset is a shape — a grayscale mask, not fixed colours — the codec blends every gray sample between bg (gray 0) and tint (max gray), so set_colors() recolours the SAME asset into an accent, ok, warning or alert indicator with no extra image data. set_src() alone re-points a widget at a different asset with no relayout, which is how an icon becomes STATE (a Wi-Fi signal level, a mic mute glyph) rather than decoration. Like a label, an image is display-only until given an event callback — the input layer hit-tests any visible, enabled widget regardless of type, so a tappable icon needs nothing beyond set_event_cb().

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 Image: Bare vs set_colors()

The three calls that put an icon on screen: ugui_widget_image_init(), set_area(), set_src(). With defaults on, init seeds tint = theme ACCENT and bg = page background, so a bare image (left) already shows an on-theme colour with no colour call at all; the right icon calls set_colors() to force white, overriding the default.

Default Image: Bare vs set_colors()

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

/* --- 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/gallery_icon_1_grayscale_4.h"   /* defines img_gallery_icon_1 */
#endif

/* --- file-scope storage --- */
#if defined(UGUI_WIDGET_ENABLE_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_img_default;
static ugui_image_ext_t s_img_default_ext;
static ugui_widget_t    s_img_white;
static ugui_image_ext_t s_img_white_ext;
#define ICON_SZ  24
#define ICON_Y   96
#define ICON_L_X ((int16_t)((UGUI_SCREEN_W / 2) - 60 - (ICON_SZ / 2)))
#define ICON_R_X ((int16_t)((UGUI_SCREEN_W / 2) + 60 - (ICON_SZ / 2)))
#endif

/* --- build it (in your screen's init) --- */
ugui_widget_image_init(&s_root, &s_img_default, &s_img_default_ext);
ugui_widget_set_area(&s_img_default, ICON_L_X, ICON_Y, ICON_SZ, ICON_SZ);
ugui_widget_set_id(&s_img_default, WDGT_ID_DEMO);
ugui_widget_image_set_src(&s_img_default, &img_gallery_icon_1);
ugui_screen_add_widget(&s_screen, &s_img_default);

/* Same icon, but set_colors() overrides the default to white. */
ugui_widget_image_init(&s_root, &s_img_white, &s_img_white_ext);
ugui_widget_set_area(&s_img_white, ICON_R_X, ICON_Y, ICON_SZ, ICON_SZ);
ugui_widget_set_id(&s_img_white, WDGT_ID_SLOT1);
ugui_widget_image_set_src(&s_img_white, &img_gallery_icon_1);
ugui_widget_image_set_colors(&s_img_white, UGUI_COLOR_WHITE, UGUI_THEME_BG);
ugui_screen_add_widget(&s_screen, &s_img_white);

Recolouring One Shape via Tint

A single grayscale heart icon becomes an accent, ok, warning or alert indicator just by changing set_colors()'s tint argument — the same ≈290-byte asset, no extra image data. Each icon's bg is set to its card's fill so the anti-aliased edge blends into the card instead of fringing.

Recolouring One Shape via Tint

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

/* --- 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/heart_icon_1_fill_grayscale_4.h"   /* defines img_heart_icon_1_fill */
#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_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
#define TINT_N  4u
static ugui_widget_t    s_card[TINT_N];
static ugui_box_ext_t   s_card_ext[TINT_N];
static ugui_widget_t    s_icon[TINT_N];
static ugui_image_ext_t s_icon_ext[TINT_N];
static ugui_widget_t    s_lbl[TINT_N];
static ugui_label_ext_t s_lbl_ext[TINT_N];
#define CARD_W   68
#define CARD_H   96
#define CARD_Y   64
#define CARD_GAP 9
#define CARD_X0  10
#define ICON_SZ  24
#endif

/* --- build it (in your screen's init) --- */
const struct { const char* label; ugui_color_t tint; } cols[TINT_N] = {
    { "accent", UGUI_THEME_ACCENT    },
    { "ok",     UGUI_COLOR_GREEN },
    { "warn",   UGUI_COLOR_AMBER },
    { "alert",  UGUI_COLOR_RED   },
};

for (uint8_t i = 0u; i < (uint8_t)TINT_N; i++) {
    const int16_t cx = (int16_t)(CARD_X0 + (i * (CARD_W + CARD_GAP)));

    /* Card (also the bg the icon's AA edge blends into). */
    ugui_widget_box_init(&s_root, &s_card[i], &s_card_ext[i]);
    ugui_widget_set_area(&s_card[i], cx, CARD_Y, CARD_W, CARD_H);
    ugui_widget_set_id(&s_card[i], (uint8_t)(WDGT_ID_SLOT0 + i));
    ugui_widget_box_set_style(&s_card[i], true, 8u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
    ugui_screen_add_widget(&s_screen, &s_card[i]);

    /* Icon centred near the top of the card. */
    ugui_widget_image_init(&s_root, &s_icon[i], &s_icon_ext[i]);
    ugui_widget_set_area(&s_icon[i], (int16_t)(cx + ((CARD_W - ICON_SZ) / 2)),
                         CARD_Y + 22, ICON_SZ, ICON_SZ);
    ugui_widget_set_id(&s_icon[i], WDGT_ID_DEMO);
    ugui_widget_image_set_src(&s_icon[i], &img_heart_icon_1_fill);
    ugui_widget_image_set_colors(&s_icon[i], cols[i].tint, UGUI_THEME_SURFACE);
    ugui_screen_add_widget(&s_screen, &s_icon[i]);

    /* Caption in the lower part of the card. */
    ugui_widget_label_init(&s_root, &s_lbl[i], &s_lbl_ext[i]);
    ugui_widget_set_area(&s_lbl[i], cx, CARD_Y + 62, CARD_W, 20);
    ugui_widget_set_id(&s_lbl[i], WDGT_ID_TITLE);
    ugui_widget_label_set_style(&s_lbl[i], false, UGUI_THEME_SURFACE, cols[i].label,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                                UGUI_ALIGN_CENTER, 1u);
    ugui_screen_add_widget(&s_screen, &s_lbl[i]);
}

Six independent icons laid out as a 3×2 grid of labelled tiles — a phone home screen or a device main menu. The same three-call recipe from the default screen drops straight into a loop over a small table; all six share the accent tint so the grid reads as one on-theme set.

Icon Gallery / App Launcher Grid

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

/* --- 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/camera_icon_1_grayscale_4.h"   /* defines img_camera_icon_1 */
#include "icons/clock_icon_1_grayscale_4.h"   /* defines img_clock_icon_1 */
#include "icons/gift_icon_1_grayscale_4.h"   /* defines img_gift_icon_1 */
#include "icons/music_icon_1_grayscale_4.h"   /* defines img_music_icon_1 */
#include "icons/search_icon_1_grayscale_4.h"   /* defines img_search_icon_1 */
#include "icons/setting_icon_1_grayscale_4.h"   /* defines img_setting_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_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
#define TILE_N  6u
static ugui_widget_t    s_card[TILE_N];
static ugui_box_ext_t   s_card_ext[TILE_N];
static ugui_widget_t    s_icon[TILE_N];
static ugui_image_ext_t s_icon_ext[TILE_N];
static ugui_widget_t    s_lbl[TILE_N];
static ugui_label_ext_t s_lbl_ext[TILE_N];
#define GRID_COLS   3
#define GRID_MARGIN 10
#define GRID_GAP    11
#define TILE_W      92
#define TILE_H      88
#define GRID_Y0     46
#define ROW_STEP    98
#define ICON_SZ     24
#endif

/* --- build it (in your screen's init) --- */
const struct { const ugui_compressed_image_t* img; const char* label; }
tiles[TILE_N] = {
    { &img_search_icon_1,  "Search"   },
    { &img_music_icon_1,   "Music"    },
    { &img_camera_icon_1,  "Camera"   },
    { &img_clock_icon_1,   "Clock"    },
    { &img_setting_icon_1, "Settings" },
    { &img_gift_icon_1,    "Gifts"    },
};

for (uint8_t i = 0u; i < (uint8_t)TILE_N; i++) {
    const uint8_t col = (uint8_t)(i % (uint8_t)GRID_COLS);
    const uint8_t row = (uint8_t)(i / (uint8_t)GRID_COLS);
    const int16_t x   = (int16_t)(GRID_MARGIN + (col * (TILE_W + GRID_GAP)));
    const int16_t y   = (int16_t)(GRID_Y0 + (row * ROW_STEP));

    ugui_widget_box_init(&s_root, &s_card[i], &s_card_ext[i]);
    ugui_widget_set_area(&s_card[i], x, y, TILE_W, TILE_H);
    ugui_widget_set_id(&s_card[i], (uint8_t)(WDGT_ID_SLOT0 + i));
    ugui_widget_box_set_style(&s_card[i], true, 8u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
    ugui_screen_add_widget(&s_screen, &s_card[i]);

    ugui_widget_image_init(&s_root, &s_icon[i], &s_icon_ext[i]);
    ugui_widget_set_area(&s_icon[i], (int16_t)(x + ((TILE_W - ICON_SZ) / 2)),
                         (int16_t)(y + 16), ICON_SZ, ICON_SZ);
    ugui_widget_set_id(&s_icon[i], WDGT_ID_DEMO);
    ugui_widget_image_set_src(&s_icon[i], tiles[i].img);
    ugui_widget_image_set_colors(&s_icon[i], UGUI_THEME_ACCENT, UGUI_THEME_SURFACE);
    ugui_screen_add_widget(&s_screen, &s_icon[i]);

    ugui_widget_label_init(&s_root, &s_lbl[i], &s_lbl_ext[i]);
    ugui_widget_set_area(&s_lbl[i], x, (int16_t)(y + TILE_H - 26), TILE_W, 20);
    ugui_widget_set_id(&s_lbl[i], WDGT_ID_TITLE);
    ugui_widget_label_set_style(&s_lbl[i], false, UGUI_THEME_SURFACE, tiles[i].label,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                                UGUI_ALIGN_CENTER, 1u);
    ugui_screen_add_widget(&s_screen, &s_lbl[i]);
}

Image as a Button (Tap to Mute)

An image becomes interactive the moment it is given an event callback — the input layer hit-tests it like any other widget. Each tap flips a muted flag and, in one handler, swaps the image source between the "on"/"off" icons via set_src(), recolours it via set_colors() (green live, grey muted), and updates a status label.

Image as a Button (Tap to Mute)

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

/* --- 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/mic_icon_1_off_grayscale_4.h"   /* defines img_mic_icon_1_off */
#include "icons/mic_icon_1_on_grayscale_4.h"   /* defines img_mic_icon_1_on */
#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_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t    s_mic;
static ugui_image_ext_t s_mic_ext;
static ugui_widget_t    s_status;
static bool s_muted;
#define CARD_W   120
#define CARD_H   120
#define CARD_X   ((int16_t)((UGUI_SCREEN_W - CARD_W) / 2))
#define CARD_Y   58
#define ICON_SZ  24
#endif

/* --- local helpers & event callbacks --- */
static void apply_state(void)
{
    ugui_widget_image_set_src(&s_mic,
        s_muted ? &img_mic_icon_1_off : &img_mic_icon_1_on);
    ugui_widget_image_set_colors(&s_mic,
        s_muted ? UGUI_COLOR_GRAY : UGUI_COLOR_GREEN, UGUI_THEME_SURFACE);
    ugui_widget_label_set_style(&s_status, false, UGUI_THEME_BG,
        s_muted ? "Microphone: OFF" : "Microphone: ON",
        &lexend_14pt_2bpp, s_muted ? UGUI_COLOR_GRAY : UGUI_COLOR_GREEN,
        UGUI_ALIGN_CENTER, 1u);
}

static void on_mic_click(uint8_t widget_id, ugui_event_t event)
{
    (void)widget_id;
    if (event != UGUI_EVENT_CLICK) { return; }
    s_muted = !s_muted;
    apply_state();
    printf("[image.button] microphone %s\n", s_muted ? "muted" : "live");
}

/* --- build it (in your screen's init) --- */
ugui_widget_image_init(&s_root, &s_mic, &s_mic_ext);
ugui_widget_set_area(&s_mic, (int16_t)(CARD_X + ((CARD_W - ICON_SZ) / 2)),
                     (int16_t)(CARD_Y + ((CARD_H - ICON_SZ) / 2)), ICON_SZ, ICON_SZ);
ugui_widget_set_id(&s_mic, WDGT_ID_DEMO);
ugui_widget_set_event_cb(&s_mic, on_mic_click,
                         UGUI_EMASK_CLICK | UGUI_EMASK_DOWN | UGUI_EMASK_UP
                         | UGUI_EMASK_KEY_CONFIRM);
ugui_screen_add_widget(&s_screen, &s_mic);
#if UGUI_ENABLE_KEYPAD
ugui_screen_add_focus(&s_screen, &s_mic);
#endif

Live Status Bar: Icon as State

A connectivity strip: set_src() re-points a widget at a different flash asset with no relayout, so a Wi-Fi glyph steps through its signal levels the same way a real driver would drive it from an RSSI reading. The Weaker/Stronger buttons step a level 0..5, re-pointing BOTH the strip icon and the big preview and recolouring them by strength.

Live Status Bar: Icon as State

/* --- 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_SLOT0, WDGT_ID_SLOT1,
    WDGT_ID_SLOT2, WDGT_ID_SLOT3, WDGT_ID_DEMO,
    WDGT_ID_TITLE, WDGT_ID_BACK, WDGT_ID_SLOT5
};

/* --- 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/cellular_icon_1_signal_full_grayscale_4.h"   /* defines img_cellular_icon_1_signal_full */
#include "icons/wifi_icon_1_offline_grayscale_4.h"   /* defines img_wifi_icon_1_offline */
#include "icons/wifi_icon_1_signal_1_grayscale_4.h"   /* defines img_wifi_icon_1_signal_1 */
#include "icons/wifi_icon_1_signal_2_grayscale_4.h"   /* defines img_wifi_icon_1_signal_2 */
#include "icons/wifi_icon_1_signal_3_grayscale_4.h"   /* defines img_wifi_icon_1_signal_3 */
#include "icons/wifi_icon_1_signal_4_grayscale_4.h"   /* defines img_wifi_icon_1_signal_4 */
#include "icons/wifi_icon_1_signal_5_full_grayscale_4.h"   /* defines img_wifi_icon_1_signal_5_full */
#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_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t   s_strip;
static ugui_box_ext_t  s_strip_ext;
static ugui_widget_t    s_clock;
static ugui_label_ext_t s_clock_ext;
static ugui_widget_t    s_cell;
static ugui_image_ext_t s_cell_ext;
static ugui_widget_t    s_wifi_bar;
static ugui_image_ext_t s_wifi_bar_ext;
static ugui_widget_t   s_card;
static ugui_box_ext_t  s_card_ext;
static ugui_widget_t    s_wifi_big;
static ugui_image_ext_t s_wifi_big_ext;
static ugui_widget_t    s_level_lbl;
static ugui_label_ext_t s_level_lbl_ext;
static ugui_widget_t     s_weaker;    static ugui_button_ext_t s_weaker_ext;
static ugui_widget_t     s_stronger;  static ugui_button_ext_t s_stronger_ext;
#endif
#if defined(UGUI_WIDGET_ENABLE_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_text_cfg_t   s_weaker_txt;
static ugui_text_cfg_t   s_stronger_txt;
#endif
#if defined(UGUI_WIDGET_ENABLE_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static char   s_level_buf[24];
static int8_t s_level;      /* 0 (offline) .. 5 (full) */
#define WIFI_LEVELS 6
static const ugui_compressed_image_t* const s_wifi_img[WIFI_LEVELS] = {
    &img_wifi_icon_1_offline,
    &img_wifi_icon_1_signal_1,
    &img_wifi_icon_1_signal_2,
    &img_wifi_icon_1_signal_3,
    &img_wifi_icon_1_signal_4,
    &img_wifi_icon_1_signal_5_full,
};
static const char* const s_wifi_name[WIFI_LEVELS] = {
    "Offline", "Very weak", "Weak", "OK", "Good", "Full",
};
#define ICON_SZ 24
#endif

/* --- local helpers & event callbacks --- */
static ugui_color_t level_color(int8_t lvl)
{
    if (lvl <= 0) { return UGUI_COLOR_GRAY;  }
    if (lvl <= 2) { return UGUI_COLOR_AMBER; }
    return UGUI_COLOR_GREEN;
}

static void apply_level(void)
{
    const ugui_compressed_image_t* img = s_wifi_img[s_level];
    const ugui_color_t col = level_color(s_level);

    ugui_widget_image_set_src(&s_wifi_bar, img);
    ugui_widget_image_set_colors(&s_wifi_bar, col, UGUI_THEME_SURFACE);
    ugui_widget_image_set_src(&s_wifi_big, img);
    ugui_widget_image_set_colors(&s_wifi_big, col, UGUI_THEME_SURFACE);

    (void)snprintf(s_level_buf, sizeof(s_level_buf), "Wi-Fi: %s",
                   s_wifi_name[s_level]);
    ugui_label_set_text(&s_level_lbl, s_level_buf);
}

static void on_step(uint8_t widget_id, ugui_event_t event)
{
    if (event != UGUI_EVENT_CLICK) { return; }
    if (widget_id == WDGT_ID_SLOT4) {            /* Weaker */
        if (s_level > 0) { s_level--; }
    } else {                                    /* Stronger */
        if (s_level < (WIFI_LEVELS - 1)) { s_level++; }
    }
    apply_level();
    printf("[image.statusbar] wifi level -> %d (%s)\n",
           (int)s_level, s_wifi_name[s_level]);
}

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,
                       ugui_color_t fill, 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, fill, 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);
#else
    (void)str;
#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) --- */
/* Status strip. */
ugui_widget_box_init(&s_root, &s_strip, &s_strip_ext);
ugui_widget_set_area(&s_strip, 10, 44,
                     (int16_t)(UGUI_SCREEN_W - (2 * 10)), 30);
ugui_widget_set_id(&s_strip, WDGT_ID_SLOT0);
ugui_widget_box_set_style(&s_strip, true, 6u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
ugui_screen_add_widget(&s_screen, &s_strip);

ugui_widget_label_init(&s_root, &s_clock, &s_clock_ext);
ugui_widget_set_area(&s_clock, 10 + 12, 44, 100, 30);
ugui_widget_set_id(&s_clock, WDGT_ID_SLOT1);
ugui_widget_label_set_style(&s_clock, false, UGUI_THEME_SURFACE, "12:45",
                            &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                            (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_screen_add_widget(&s_screen, &s_clock);

ugui_widget_image_init(&s_root, &s_cell, &s_cell_ext);
ugui_widget_set_area(&s_cell, (int16_t)(UGUI_SCREEN_W - 10 - 62), 47, ICON_SZ, ICON_SZ);
ugui_widget_set_id(&s_cell, WDGT_ID_SLOT2);
ugui_widget_image_set_src(&s_cell, &img_cellular_icon_1_signal_full);
ugui_widget_image_set_colors(&s_cell, UGUI_COLOR_GREEN, UGUI_THEME_SURFACE);
ugui_screen_add_widget(&s_screen, &s_cell);

ugui_widget_image_init(&s_root, &s_wifi_bar, &s_wifi_bar_ext);
ugui_widget_set_area(&s_wifi_bar, (int16_t)(UGUI_SCREEN_W - 10 - 32), 47, ICON_SZ, ICON_SZ);
ugui_widget_set_id(&s_wifi_bar, WDGT_ID_SLOT3);
ugui_screen_add_widget(&s_screen, &s_wifi_bar);

/* Big preview card. */
ugui_widget_box_init(&s_root, &s_card, &s_card_ext);
ugui_widget_set_area(&s_card, (int16_t)((UGUI_SCREEN_W - 100) / 2), 86, 100, 84);
ugui_widget_set_id(&s_card, WDGT_ID_DEMO);
ugui_widget_box_set_style(&s_card, true, 10u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
ugui_screen_add_widget(&s_screen, &s_card);

ugui_widget_image_init(&s_root, &s_wifi_big, &s_wifi_big_ext);
ugui_widget_set_area(&s_wifi_big, (int16_t)((UGUI_SCREEN_W - ICON_SZ) / 2), 106,
                     ICON_SZ, ICON_SZ);
ugui_widget_set_id(&s_wifi_big, WDGT_ID_TITLE);
ugui_screen_add_widget(&s_screen, &s_wifi_big);

ugui_widget_label_init(&s_root, &s_level_lbl, &s_level_lbl_ext);
ugui_widget_set_area(&s_level_lbl, 10, 174,
                     (int16_t)(UGUI_SCREEN_W - (2 * 10)), 22);
ugui_widget_set_id(&s_level_lbl, WDGT_ID_BACK);
ugui_widget_label_set_style(&s_level_lbl, false, UGUI_THEME_BG, s_level_buf,
                            &lexend_14pt_2bpp, UGUI_THEME_TEXT, UGUI_ALIGN_CENTER, 1u);
ugui_screen_add_widget(&s_screen, &s_level_lbl);

/* Stepper buttons. */
add_button(&s_weaker, &s_weaker_ext, &s_weaker_txt, WDGT_ID_SLOT4,
          30, 202, 120, 32, UGUI_THEME_SURFACE, "Weaker", on_step);
add_button(&s_stronger, &s_stronger_ext, &s_stronger_txt, WDGT_ID_SLOT5,
          170, 202, 120, 32, UGUI_THEME_SURFACE, "Stronger", on_step);

apply_level();   /* seed both wifi icons + the label for the initial level */

Weather Dashboard: Composition

The complex endpoint of the family: a "today" hero card (big condition icon + temperature + place) above a three-day forecast row (per-day card: weekday, tinted condition icon, high/low). Nothing new about a single image — the point is composition: image widgets for the glyphs, labels for the numbers, boxes for the cards, all laid out from a small table.

Weather Dashboard: Composition

/* --- 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_DEMO, WDGT_ID_SLOT1,
    WDGT_ID_SLOT2, WDGT_ID_SLOT3, WDGT_ID_TITLE,
    WDGT_ID_BACK
};

/* --- 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/weather_icon_1_cloudy_grayscale_4.h"   /* defines img_weather_icon_1_cloudy */
#include "icons/weather_icon_1_rainy_grayscale_4.h"   /* defines img_weather_icon_1_rainy */
#include "icons/weather_icon_1_snowflake_grayscale_4.h"   /* defines img_weather_icon_1_snowflake */
#include "icons/weather_icon_1_thunderstorms_grayscale_4.h"   /* defines img_weather_icon_1_thunderstorms */
#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_IMAGE) && defined(UGUI_WIDGET_ENABLE_LABEL) && defined(UGUI_ENABLE_FONT)
static ugui_screen_t  s_screen;
static ugui_widget_t   s_root;
static ugui_widget_t   s_today;
static ugui_box_ext_t  s_today_ext;
static ugui_widget_t    s_today_icon;
static ugui_image_ext_t s_today_icon_ext;
static ugui_widget_t    s_temp;
static ugui_label_ext_t s_temp_ext;
static ugui_widget_t    s_place;
static ugui_label_ext_t s_place_ext;
#define FC_N  3u
static ugui_widget_t    s_fc_card[FC_N];
static ugui_box_ext_t   s_fc_card_ext[FC_N];
static ugui_widget_t    s_fc_icon[FC_N];
static ugui_image_ext_t s_fc_icon_ext[FC_N];
static ugui_widget_t    s_fc_day[FC_N];
static ugui_label_ext_t s_fc_day_ext[FC_N];
static ugui_widget_t    s_fc_temp[FC_N];
static ugui_label_ext_t s_fc_temp_ext[FC_N];
#define ICON_SZ 24
#endif

/* --- build it (in your screen's init) --- */
/* ---- Today hero card ---- */
ugui_widget_box_init(&s_root, &s_today, &s_today_ext);
ugui_widget_set_area(&s_today, 10, 42,
                     (int16_t)(UGUI_SCREEN_W - (2 * 10)), 76);
ugui_widget_set_id(&s_today, WDGT_ID_SLOT0);
ugui_widget_box_set_style(&s_today, true, 10u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
ugui_screen_add_widget(&s_screen, &s_today);

ugui_widget_image_init(&s_root, &s_today_icon, &s_today_icon_ext);
ugui_widget_set_area(&s_today_icon, 10 + 22, 68, ICON_SZ, ICON_SZ);
ugui_widget_set_id(&s_today_icon, WDGT_ID_DEMO);
ugui_widget_image_set_src(&s_today_icon, &img_weather_icon_1_cloudy);
ugui_widget_image_set_colors(&s_today_icon, UGUI_THEME_ACCENT, UGUI_THEME_SURFACE);
ugui_screen_add_widget(&s_screen, &s_today_icon);

ugui_widget_label_init(&s_root, &s_temp, &s_temp_ext);
ugui_widget_set_area(&s_temp, 10 + 70, 52, 200, 28);
ugui_widget_set_id(&s_temp, WDGT_ID_SLOT1);
ugui_widget_label_set_style(&s_temp, false, UGUI_THEME_SURFACE, "18 C  Cloudy",
                            &lexend_14pt_2bpp, UGUI_THEME_ACCENT,
                            (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_screen_add_widget(&s_screen, &s_temp);

ugui_widget_label_init(&s_root, &s_place, &s_place_ext);
ugui_widget_set_area(&s_place, 10 + 70, 82, 220, 22);
ugui_widget_set_id(&s_place, WDGT_ID_SLOT2);
ugui_widget_label_set_style(&s_place, false, UGUI_THEME_SURFACE, "Berlin - updated 12:45",
                            &lexend_14pt_2bpp, UGUI_THEME_TEXT,
                            (ugui_align_t)(UGUI_ALIGN_LEFT | UGUI_ALIGN_V_CENTER), 1u);
ugui_screen_add_widget(&s_screen, &s_place);

/* ---- 3-day forecast ---- */
const struct { const char* day; const ugui_compressed_image_t* img;
               const char* temp; ugui_color_t tint; } fc[FC_N] = {
    { "Wed", &img_weather_icon_1_rainy,         "12/7", UGUI_COLOR_CYAN  },
    { "Thu", &img_weather_icon_1_snowflake,     "3/-2", UGUI_COLOR_CYAN  },
    { "Fri", &img_weather_icon_1_thunderstorms, "9/5",  UGUI_COLOR_AMBER },
};

const int16_t fc_w = 93;
const int16_t fc_y = 128;
const int16_t fc_h = 100;
for (uint8_t i = 0u; i < (uint8_t)FC_N; i++) {
    const int16_t x = (int16_t)(10 + (i * (fc_w + 10)));

    ugui_widget_box_init(&s_root, &s_fc_card[i], &s_fc_card_ext[i]);
    ugui_widget_set_area(&s_fc_card[i], x, fc_y, fc_w, fc_h);
    ugui_widget_set_id(&s_fc_card[i], (uint8_t)(WDGT_ID_SLOT3 + i));
    ugui_widget_box_set_style(&s_fc_card[i], true, 8u, 1u, UGUI_THEME_SURFACE, UGUI_THEME_ACCENT);
    ugui_screen_add_widget(&s_screen, &s_fc_card[i]);

    ugui_widget_label_init(&s_root, &s_fc_day[i], &s_fc_day_ext[i]);
    ugui_widget_set_area(&s_fc_day[i], x, (int16_t)(fc_y + 8), fc_w, 18);
    ugui_widget_set_id(&s_fc_day[i], WDGT_ID_TITLE);
    ugui_widget_label_set_style(&s_fc_day[i], false, UGUI_THEME_SURFACE, fc[i].day,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT, UGUI_ALIGN_CENTER, 1u);
    ugui_screen_add_widget(&s_screen, &s_fc_day[i]);

    ugui_widget_image_init(&s_root, &s_fc_icon[i], &s_fc_icon_ext[i]);
    ugui_widget_set_area(&s_fc_icon[i], (int16_t)(x + ((fc_w - ICON_SZ) / 2)),
                         (int16_t)(fc_y + 34), ICON_SZ, ICON_SZ);
    ugui_widget_set_id(&s_fc_icon[i], WDGT_ID_DEMO);
    ugui_widget_image_set_src(&s_fc_icon[i], fc[i].img);
    ugui_widget_image_set_colors(&s_fc_icon[i], fc[i].tint, UGUI_THEME_SURFACE);
    ugui_screen_add_widget(&s_screen, &s_fc_icon[i]);

    ugui_widget_label_init(&s_root, &s_fc_temp[i], &s_fc_temp_ext[i]);
    ugui_widget_set_area(&s_fc_temp[i], x, (int16_t)(fc_y + fc_h - 26), fc_w, 18);
    ugui_widget_set_id(&s_fc_temp[i], WDGT_ID_BACK);
    ugui_widget_label_set_style(&s_fc_temp[i], false, UGUI_THEME_SURFACE, fc[i].temp,
                                &lexend_14pt_2bpp, UGUI_THEME_TEXT, UGUI_ALIGN_CENTER, 1u);
    ugui_screen_add_widget(&s_screen, &s_fc_temp[i]);
}


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.