#pragma once #include #include // ── Scalable mini-icons ────────────────────────────────────────────────────── // Small procedural glyphs (delivery markers, etc.) authored on a 1× pixel grid // and scaled to the current font, so they stay legible on large-font layouts // (e.g. landscape e-ink renders text at 2×). Distinct from the XBM page icons // further down, which are fixed-size full bitmaps drawn via drawXbm(). // // To add a mini-icon: // 1. Draw it as ASCII-art rows, one string per row (width ≤ 8): any char // other than ' ' or '.' is a filled pixel. packRow() packs each string // into one byte at compile time, so the strings never reach flash — the // binary holds only the packed bytes, identical to hand-written hex. // 2. Wrap the rows in a MINI_ICON(name, width, ...) — width and row count are // bundled with the data, so the draw site carries no magic numbers. // 3. Draw it with miniIconDraw(display, x, topY, name). // Scaling is automatic — no per-display handling needed. // // packRow() is written as a single-expression recursion so it stays a valid // constant expression on any C++ standard the firmware targets (≥ C++11), not // just the relaxed C++14 constexpr with loops. It packs up to the string's NUL // (capped at 8 cols), so width lives once in MINI_ICON, not in every row. constexpr uint8_t packBit(const char* s, int x) { return (s[x] && s[x] != ' ' && s[x] != '.') ? (uint8_t)(1u << x) : 0; } constexpr uint8_t packRow(const char* s, int x = 0) { return (!s[x] || x >= 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. inline int miniIconScale(DisplayDriver& d) { int s = d.getLineHeight() / 8; return s < 1 ? 1 : s; } // Draw a w×h (w ≤ 8) bitmap with the current ink colour, scaled by the font and // vertically centred in the text line that starts at top_y. inline void miniIconDraw(DisplayDriver& d, int x, int top_y, const uint8_t* rows, int w, int h) { const int s = miniIconScale(d); int y = top_y + (d.getLineHeight() - h * s) / 2; if (y < top_y) y = top_y; for (int r = 0; r < h; r++) for (int c = 0; c < w; c++) 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) { const int s = miniIconScale(d); const int dot = 2 * s, pitch = 3 * s; // 2px dot + 1px gap, scaled int y = top_y + (d.getLineHeight() - dot) / 2; if (y < top_y) y = top_y; for (int i = 0; i < count; i++) d.fillRect(x + i * pitch, y, dot, dot); } // 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 [] = { 0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe, 0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, 0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, 0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, 0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, 0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, 0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, 0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, 0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, 0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, }; static const uint8_t bluetooth_on[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x00, 0x3B, 0xE0, 0x00, 0x30, 0x38, 0xF8, 0x00, 0x3C, 0x38, 0x7C, 0x00, 0x3E, 0x38, 0x7C, 0x00, 0x1F, 0xB8, 0xF8, 0x70, 0x07, 0xF9, 0xF0, 0x78, 0x03, 0xFF, 0xC0, 0x78, 0x00, 0xFF, 0x80, 0x3C, 0x00, 0x7F, 0x07, 0x1C, 0x00, 0x7E, 0x07, 0x1C, 0x03, 0xFF, 0x82, 0x1C, 0x03, 0xFF, 0xC0, 0x78, 0x07, 0xFB, 0xE0, 0x78, 0x0F, 0xB8, 0xF8, 0x70, 0x3E, 0x38, 0x7C, 0x00, 0x3C, 0x38, 0x7C, 0x00, 0x38, 0x38, 0xF8, 0x00, 0x00, 0x39, 0xF0, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; static const uint8_t bluetooth_off[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x38, 0x03, 0xF8, 0x00, 0x3C, 0x03, 0xFC, 0x00, 0x3E, 0x03, 0xBF, 0x00, 0x0F, 0x83, 0x8F, 0x80, 0x07, 0xC3, 0x87, 0xC0, 0x03, 0xF0, 0x03, 0xC0, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0x7C, 0x0F, 0x00, 0x00, 0x1F, 0x0E, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x3F, 0xBE, 0x00, 0x00, 0x7F, 0x9F, 0x00, 0x00, 0xFB, 0x8F, 0xC0, 0x03, 0xE3, 0x83, 0xE0, 0x03, 0xC3, 0x87, 0xF0, 0x03, 0x83, 0x8F, 0xFC, 0x00, 0x03, 0xBF, 0x3C, 0x00, 0x03, 0xFC, 0x1C, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; static const uint8_t power_icon[] = { 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x33, 0xCC, 0x00, 0x00, 0xF3, 0xCF, 0x00, 0x01, 0xF3, 0xCF, 0x80, 0x03, 0xF3, 0xCF, 0xC0, 0x07, 0xF3, 0xCF, 0xE0, 0x0F, 0xE3, 0xC7, 0xF0, 0x1F, 0xC3, 0xC3, 0xF8, 0x1F, 0x83, 0xC1, 0xF8, 0x3F, 0x03, 0xC0, 0xFC, 0x3E, 0x03, 0xC0, 0x7C, 0x3E, 0x03, 0xC0, 0x7C, 0x7E, 0x01, 0x80, 0x7E, 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x7C, 0x3F, 0x00, 0x00, 0xFC, 0x1F, 0x80, 0x01, 0xF8, 0x1F, 0xC0, 0x03, 0xF8, 0x0F, 0xE0, 0x07, 0xF0, 0x0F, 0xF8, 0x1F, 0xF0, 0x07, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00, }; static const uint8_t advert_icon[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x1C, 0x00, 0x00, 0x38, 0x18, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0C, 0x30, 0x60, 0x06, 0x0C, 0x60, 0xE0, 0x07, 0x06, 0x61, 0xC0, 0x03, 0x86, 0xE1, 0x81, 0x81, 0x87, 0xC3, 0x07, 0xE0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x07, 0xE0, 0xC3, 0xC1, 0x83, 0xC1, 0x83, 0x61, 0x80, 0x01, 0x86, 0x60, 0xC0, 0x03, 0x06, 0x70, 0xE0, 0x07, 0x0E, 0x30, 0x40, 0x02, 0x0C, 0x38, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };