From c05e722afc80be78abcd19bc4949b2045ed07154 Mon Sep 17 00:00:00 2001 From: MarekZegare4 Date: Mon, 15 Jun 2026 09:42:29 +0200 Subject: [PATCH] refactor(ui): readable compile-time icon authoring (ASCII-art + dims) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Author mini-icons as ASCII-art rows packed to bytes at compile time via constexpr packRow(), so the glyph shape is visible in source while the binary holds only the packed bytes (strings never reach flash, identical to the previous hand-written hex). Bundle each icon's width/height with its data through MiniIcon and the MINI_ICON(name, width, ...) macro, so draw sites carry no magic numbers: miniIconDraw(d, x, y, ICON_CHECK). Also add an unused-by-design skeleton for full-size page icons (≤32 px): packRow32 / BigIcon / BIG_ICON / bigIconDraw, for authoring a future big glyph the same readable way. Existing XBM bitmaps left untouched. Flash size unchanged; verified no ASCII-art strings reach the .elf. Co-Authored-By: Claude Opus 4.8 --- .../companion_radio/ui-new/QuickMsgScreen.h | 4 +- examples/companion_radio/ui-new/icons.h | 85 +++++++++++++++++-- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/ui-new/QuickMsgScreen.h b/examples/companion_radio/ui-new/QuickMsgScreen.h index a7a9eebf..bf8ccf34 100644 --- a/examples/companion_radio/ui-new/QuickMsgScreen.h +++ b/examples/companion_radio/ui-new/QuickMsgScreen.h @@ -290,8 +290,8 @@ class QuickMsgScreen : public UIScreen { static void drawAckGlyph(DisplayDriver& d, int x, int top_y, AckState s, int sends = 1) { switch (s) { case ACK_PENDING: miniIconDotRow(d, x, top_y, sends); break; - case ACK_OK: miniIconDraw(d, x, top_y, ICON_CHECK, 5, 4); break; - case ACK_FAIL: miniIconDraw(d, x, top_y, ICON_CROSS, 4, 4); break; + case ACK_OK: miniIconDraw(d, x, top_y, ICON_CHECK); break; + case ACK_FAIL: miniIconDraw(d, x, top_y, ICON_CROSS); break; default: break; // ACK_NONE → nothing } } diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index c766333f..e38aa546 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -10,10 +10,36 @@ // further down, which are fixed-size full bitmaps drawn via drawXbm(). // // To add a mini-icon: -// 1. Define a bitmap: H rows top→bottom, one byte per row, bit (1<= 8) ? 0 : (uint8_t)(packBit(s, x) | packRow(s, x + 1)); +} + +// A mini-icon bundles its pixel data with its dimensions, so call sites can't +// pass a stale width/height. Built via the MINI_ICON macro below. +struct MiniIcon { uint8_t w, h; const uint8_t* rows; }; + +// Define a mini-icon: the row count (height) is derived from the initializer, +// the width is stated once. Emits a packed byte array plus a MiniIcon view. +#define MINI_ICON(name, width, ...) \ + static constexpr uint8_t name##_rows[] = { __VA_ARGS__ }; \ + static constexpr MiniIcon name = { (uint8_t)(width), \ + (uint8_t)sizeof(name##_rows), name##_rows } // Pixel scale from the font: 1× on an 8px OLED line, 2× on a 16px landscape // e-ink line, etc. Bitmaps are authored on the 1× grid. @@ -34,6 +60,11 @@ inline void miniIconDraw(DisplayDriver& d, int x, int top_y, if (rows[r] & (1 << c)) d.fillRect(x + c * s, y + r * s, s, s); } +// Preferred overload: dimensions travel with the icon — no magic numbers. +inline void miniIconDraw(DisplayDriver& d, int x, int top_y, const MiniIcon& ic) { + miniIconDraw(d, x, top_y, ic.rows, ic.w, ic.h); +} + // Horizontal row of `count` square dots (scaled, vertically centred). Used by // the "awaiting ACK" marker, where the dot count = number of send attempts. inline void miniIconDotRow(DisplayDriver& d, int x, int top_y, int count) { @@ -44,9 +75,51 @@ inline void miniIconDotRow(DisplayDriver& d, int x, int top_y, int count) { for (int i = 0; i < count; i++) d.fillRect(x + i * pitch, y, dot, dot); } -// Mini-icon bitmaps (authored on the 1× grid). -static const uint8_t ICON_CHECK[4] = { 0x10, 0x08, 0x05, 0x02 }; // ✓ 5×4 -static const uint8_t ICON_CROSS[4] = { 0x09, 0x06, 0x06, 0x09 }; // ✗ 4×4 +// Mini-icon bitmaps (authored on the 1× grid as ASCII-art; see packRow above). +MINI_ICON(ICON_CHECK, 5, // ✓ + packRow("....#"), + packRow("...#."), + packRow("#.#.."), + packRow(".#...")); +MINI_ICON(ICON_CROSS, 4, // ✗ + packRow("#..#"), + packRow(".##."), + packRow(".##."), + packRow("#..#")); + +// ── Big ASCII-art icons (skeleton, not yet used) ───────────────────────────── +// Same authoring idea as the mini-icons but for full page glyphs up to 32 px +// wide: one uint32_t per row. The existing XBM bitmaps below (logo/bluetooth/…) +// stay hand-encoded; reach for this when adding a *new* big icon so it's +// readable in source. Drawn at 1× (page icons aren't font-scaled). +// +// To add one: +// BIG_ICON(MY_ICON, 16, +// packRow32("......####......"), +// packRow32(".....######....."), +// ...); +// bigIconDraw(display, x, y, MY_ICON); +constexpr uint32_t packBit32(const char* s, int x) { + return (s[x] && s[x] != ' ' && s[x] != '.') ? (1u << x) : 0u; +} +constexpr uint32_t packRow32(const char* s, int x = 0) { + return (!s[x] || x >= 32) ? 0u : (packBit32(s, x) | packRow32(s, x + 1)); +} + +struct BigIcon { uint8_t w, h; const uint32_t* rows; }; + +#define BIG_ICON(name, width, ...) \ + static constexpr uint32_t name##_rows[] = { __VA_ARGS__ }; \ + static constexpr BigIcon name = { \ + (uint8_t)(width), \ + (uint8_t)(sizeof(name##_rows) / sizeof(uint32_t)), name##_rows } + +// Draw a packed big icon at 1× with the current ink colour, top-left at (x, y). +inline void bigIconDraw(DisplayDriver& d, int x, int y, const BigIcon& ic) { + for (int r = 0; r < ic.h; r++) + for (int c = 0; c < ic.w; c++) + if (ic.rows[r] & (1u << c)) d.fillRect(x + c, y + r, 1, 1); +} // 'meshcore', 128x13px static const uint8_t meshcore_logo [] = {