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 [] = {