Files
Jakub 9d44921d34 feat(ui): migrate e-ink to misc-fixed font; fix Alarm UX and status-bar icon alignment
- GxEPDDisplay now uses the same single misc-fixed 6x9 font as the OLED
  driver (Lemon retired there too), with baseline math updated for the
  new ascent.
- Clock Tools' Alarm screen: Repeat and Armed now respond to LEFT/RIGHT
  like every other multi-value/toggle field in Settings, not Enter-only;
  Hour/Minute merged into one Time row edited with a hand-rolled HH:MM
  digit cursor (like the Timer's), replacing the two-row DigitEditor
  popups; Repeat's "Off" label now matches the codebase-wide ON/OFF
  casing.
- Top status bar: battery icon now shares the same box height as the
  other status icons (Bluetooth/mute/etc.) instead of standing 2px
  taller, and its charge nub is properly vertically centred instead of
  drifting off-centre at non-multiple-of-4 box heights.
- Small settings-gear icon glyph tweak; wio-tracker-l1 screenshot build
  variant now enables DUAL_SERIAL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 15:36:39 +02:00

668 lines
26 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <stdint.h>
#include <helpers/ui/DisplayDriver.h>
// ── 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);
}
// Draw a mini-icon at an exact top-left (no vertical centring). Used where the
// caller controls placement, e.g. flush to the top/bottom of a scroll track.
inline void miniIconDrawTop(DisplayDriver& d, int x, int y, const MiniIcon& ic) {
const int s = miniIconScale(d);
for (int r = 0; r < ic.h; r++)
for (int c = 0; c < ic.w; c++)
if (ic.rows[r] & (1 << c)) d.fillRect(x + c * s, y + r * s, s, s);
}
// Draw a mini-icon centred on a point (scaled) — used for map markers, which
// are positioned by their centre rather than a text-line top.
inline void miniIconDrawCentered(DisplayDriver& d, int cx, int cy, const MiniIcon& ic) {
const int s = miniIconScale(d);
miniIconDrawTop(d, cx - (ic.w * s) / 2, cy - (ic.h * s) / 2, ic);
}
// Centre a mini-icon inside the slot [x, 0, box_w, box_h] using the current ink
// colour (no background). Centres on the box itself, not the text line, so the
// glyph sits dead-centre regardless of font line height.
inline void drawSlotIcon(DisplayDriver& d, int x, int box_w, int box_h, const MiniIcon& ic) {
const int s = miniIconScale(d);
int ix = x + (box_w - ic.w * s) / 2;
int iy = (box_h - ic.h * s) / 2;
if (ix < x) ix = x;
if (iy < 0) iy = 0;
miniIconDrawTop(d, ix, iy, ic);
}
// Inverted status indicator: fill the box [x, 0, box_w, box_h] LIGHT, draw the
// glyph DARK and centred, then restore ink to LIGHT.
inline void drawBoxedIcon(DisplayDriver& d, int x, int box_w, int box_h, const MiniIcon& ic) {
d.setColor(DisplayDriver::LIGHT);
d.fillRect(x, 0, box_w, box_h);
d.setColor(DisplayDriver::DARK);
drawSlotIcon(d, x, box_w, box_h, ic);
d.setColor(DisplayDriver::LIGHT);
}
// 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("#..#"));
// Top-bar status glyphs (replace the single-letter M / B / A indicators).
MINI_ICON(ICON_MUTE, 6, // speaker + cross (sound off)
packRow("..#..."),
packRow(".##..."),
packRow("####.#"),
packRow("###.#."),
packRow("####.#"),
packRow(".##..."),
packRow("..#..."));
MINI_ICON(ICON_BLUETOOTH, 5, // ᛒ bluetooth rune
packRow("..#.."),
packRow("..##."),
packRow("#.#.#"),
packRow(".###."),
packRow("#.#.#"),
packRow("..##."),
packRow("..#.."));
MINI_ICON(ICON_ADVERT, 6, // broadcast mast + radiating waves (auto-advert)
packRow("#....#"),
packRow(".#..#."),
packRow("..##.."),
packRow("..##.."),
packRow(".####."),
packRow(".####."));
MINI_ICON(ICON_ALARM, 5, // bell — an alarm is armed
packRow("..#.."),
packRow(".###."),
packRow(".###."),
packRow(".###."),
packRow("#####"),
packRow("..#.."));
MINI_ICON(ICON_TRAIL, 6, // map pin / location marker (GPS trail logging)
packRow(".####."),
packRow("######"),
packRow("##..##"),
packRow("######"),
packRow(".####."),
packRow("..##.."));
MINI_ICON(ICON_REPEATER, 6, // » double chevron — relaying/forwarding (repeater active)
packRow("#..#.."),
packRow(".#..#."),
packRow("..#..#"),
packRow(".#..#."),
packRow("#..#.."));
MINI_ICON(ICON_GPS, 5, // reticle — GPS fix status (boxed = fix, plain = searching)
packRow(".###."),
packRow("#...#"),
packRow("#.#.#"),
packRow("#...#"),
packRow(".###."));
// Tools-menu glyphs (auto-reply bot, ringtone editor, diagnostics, system).
MINI_ICON(ICON_BOT, 5, // robot head: antenna + eyes + grille (auto-reply bot)
packRow("..#.."),
packRow("#####"),
packRow("#.#.#"),
packRow("#####"),
packRow("#.#.#"),
packRow("#####"));
MINI_ICON(ICON_NOTE, 5, // ♪ quaver — ringtone editor
packRow("...##"),
packRow("...##"),
packRow("...#."),
packRow("...#."),
packRow("...#."),
packRow("####."),
packRow("####."));
MINI_ICON(ICON_CHART, 5, // ascending bars — diagnostics / stats
packRow("....#"),
packRow("..#.#"),
packRow("..#.#"),
packRow("#.#.#"),
packRow("#.#.#"),
packRow("#####"));
MINI_ICON(ICON_GEAR, 7, // ⚙ cog with hub hole — system
packRow("..#.#.."),
packRow(".#####."),
packRow("#######"),
packRow(".##.##."),
packRow("#######"),
packRow(".#####."),
packRow("..#.#.."));
// Home-carousel page glyphs — a uniform 5x5 set, deliberately smaller than the
// menu/status icons above, used in place of the page-indicator dots. One per
// HomePage; see UITask HomeScreen::pageIcon().
MINI_ICON(ICON_PG_CLOCK, 5, // clock face + hands
packRow(".###."),
packRow("#.#.#"),
packRow("#.###"),
packRow("#...#"),
packRow(".###."));
MINI_ICON(ICON_PG_STAR, 5, // favourites
packRow("..#.."),
packRow("#####"),
packRow(".###."),
packRow("##.##"),
packRow("#...#"));
MINI_ICON(ICON_PG_RECENT, 5, // stacked lines — recent list
packRow("#####"),
packRow("....."),
packRow("#####"),
packRow("....."),
packRow("#####"));
MINI_ICON(ICON_PG_RADIO, 5, // antenna tower — radio params
packRow("..#.."),
packRow(".###."),
packRow("..#.."),
packRow(".#.#."),
packRow("#...#"));
MINI_ICON(ICON_PG_BT, 5, // bluetooth (compact)
packRow("..#.."),
packRow("#.##."),
packRow(".###."),
packRow("#.##."),
packRow("..#.."));
MINI_ICON(ICON_PG_ADVERT, 5, // mast + radiating waves — advert
packRow("#...#"),
packRow(".#.#."),
packRow("..#.."),
packRow("..#.."),
packRow("..#.."));
MINI_ICON(ICON_PG_GPS, 5, // location pin — GPS
packRow(".###."),
packRow("#.#.#"),
packRow("#.#.#"),
packRow(".#.#."),
packRow("..#.."));
MINI_ICON(ICON_PG_SENSORS, 5, // thermometer/gauge — sensors
packRow("..#.."),
packRow(".#.#."),
packRow(".#.#."),
packRow(".###."),
packRow(".###."));
MINI_ICON(ICON_PG_SETTINGS, 5, // small cog — settings
packRow(".#.#."),
packRow("#####"),
packRow(".#.#."),
packRow("#####"),
packRow(".#.#."));
MINI_ICON(ICON_PG_MAP, 5, // folded map — map page
packRow("#####"),
packRow("#.#.#"),
packRow("#.#.#"),
packRow("#.#.#"),
packRow("#####"));
MINI_ICON(ICON_PG_TOOLS, 5, // wrench (open jaw + handle) — tools
packRow(".#.#."),
packRow(".###."),
packRow("..#.."),
packRow("..#.."),
packRow("..#.."));
MINI_ICON(ICON_PG_MSG, 5, // speech bubble — quick messages
packRow("#####"),
packRow("#...#"),
packRow("#...#"),
packRow("#####"),
packRow(".#..."));
MINI_ICON(ICON_PG_POWER, 5, // power symbol — shutdown
packRow("..#.."),
packRow("#.#.#"),
packRow("#...#"),
packRow("#...#"),
packRow(".###."));
// Trail-map markers — centred on a point (see miniIconDrawCentered) rather
// than anchored to a text line.
MINI_ICON(ICON_MAP_DOT, 3, // ● filled trail point
packRow("###"),
packRow("###"),
packRow("###"));
MINI_ICON(ICON_MAP_RING, 3, // ○ hollow ring — marks a new trail segment start
packRow("###"),
packRow("#.#"),
packRow("###"));
MINI_ICON(ICON_MAP_WAYPOINT, 5, // ◇ hollow diamond — saved waypoint
packRow("..#.."),
packRow(".#.#."),
packRow("#...#"),
packRow(".#.#."),
packRow("..#.."));
MINI_ICON(ICON_MAP_START, 5, // + trail start marker
packRow("..#.."),
packRow("..#.."),
packRow("#####"),
packRow("..#.."),
packRow("..#.."));
MINI_ICON(ICON_MAP_CURRENT, 5, // ✕ live position / last trail point
packRow("#...#"),
packRow(".#.#."),
packRow("..#.."),
packRow(".#.#."),
packRow("#...#"));
MINI_ICON(ICON_MAP_CONTACT, 5, // ◆ filled diamond — a live-tracked contact ([LOC] share)
packRow("..#.."),
packRow(".###."),
packRow("#####"),
packRow(".###."),
packRow("..#.."));
MINI_ICON(ICON_MAP_NORTH, 5, // "N" with a peaked roof — compass north marker
packRow("..#.."),
packRow(".###."),
packRow("#...#"),
packRow("##..#"),
packRow("#.#.#"),
packRow("#..##"),
packRow("#...#"));
MINI_ICON(ICON_MAP_ARROW, 5, // → distance-to-nearest-tracked-contact indicator
packRow("..#.."),
packRow("...#."),
packRow("#####"),
packRow("...#."),
packRow("..#.."));
MINI_ICON(ICON_MAP_TARGET, 5, // ⚑ flag on a pole — the active Locator/Nav target
packRow("####."),
packRow("#..#."),
packRow("####."),
packRow("#...."),
packRow("#...."));
// Keyboard special-key glyphs.
MINI_ICON(ICON_SHIFT, 7, // ⇧ caps
packRow("...#..."),
packRow("..###.."),
packRow(".#####."),
packRow("#######"),
packRow("..###.."),
packRow("..###.."),
packRow("..###.."));
MINI_ICON(ICON_BACKSPACE, 8, // ⌫ delete-left (× knocked out of the arrow body)
packRow("...#####"),
packRow("..######"),
packRow(".###.#.#"),
packRow("#####.##"),
packRow(".###.#.#"),
packRow("..######"),
packRow("...#####"));
// Space ⎵ is wider than the 8-px mini-icon limit, so it is two halves drawn
// side by side (offset by ICON_SPACE_L.w * scale): ticks at both far ends + bar.
MINI_ICON(ICON_SPACE_L, 8,
packRow("#......."),
packRow("#......."),
packRow("#......."),
packRow("########"));
MINI_ICON(ICON_SPACE_R, 8,
packRow(".......#"),
packRow(".......#"),
packRow(".......#"),
packRow("########"));
// Scroll-indicator caps — small up/down triangles (authored on the 1× grid).
MINI_ICON(ICON_SCROLL_UP, 5, // ▲
packRow("..#.."),
packRow(".###."),
packRow("#####"));
MINI_ICON(ICON_SCROLL_DOWN, 5, // ▼
packRow("#####"),
packRow(".###."),
packRow("..#.."));
// Width of the right-edge column drawScrollIndicator occupies, or 0 when the
// list fits and no indicator is drawn. Subtract from a row's content width so
// text never runs under the scrollbar. Mirrors the column math below (5*scale
// triangle + 1px gap).
inline int scrollIndicatorColWidth(DisplayDriver& d) {
return 5 * miniIconScale(d) + 1; // triangle (5*scale) + 1px gap
}
inline int scrollIndicatorReserve(DisplayDriver& d, int total, int visible) {
return (total > visible) ? scrollIndicatorColWidth(d) : 0;
}
// Right-edge scroll indicator: a proportional track + thumb topped/tailed with
// up/down triangle mini-icon caps. Drop-in replacement for
// DisplayDriver::drawScrollArrows that also shows how much of the list is on
// screen and where you are within it. Everything lives in a ~5px column at the
// right edge and scales with the font (mini-icon scale).
// top_y : y of the first visible row (top of the viewport)
// track_h : pixel height of the viewport (e.g. visible_rows * row_pitch)
//
// Core takes the proportions in arbitrary units. For uniform-height lists pass
// item counts (see the item wrapper below). For variable-height lists (e.g. the
// portrait DM history, where each message box wraps to a different height) pass
// pixel sums — total_px = Σ all box heights, view_px = pixels currently on
// screen, scroll_px = pixels above the first visible box — so the thumb size and
// position track real content extent instead of jumping as box counts fluctuate.
// total_px : total extent of the whole list (items or pixels)
// view_px : extent currently visible
// scroll_px : extent scrolled past (offset of the first visible item)
// right_x : column origin's right edge, in screen pixels (the indicator
// occupies [right_x - col, right_x)). Full-screen lists pass
// d.width(); a narrower container (e.g. a centred popup) passes its
// own right edge so the indicator lands inside the box, not the screen.
inline void drawScrollIndicatorPx(DisplayDriver& d, int right_x, int top_y, int track_h,
long total_px, long view_px, long scroll_px) {
if (total_px <= view_px || track_h <= 0) return; // whole list fits — no indicator
const long span = total_px - view_px; // > 0 here
if (scroll_px < 0) scroll_px = 0;
if (scroll_px > span) scroll_px = span;
const int s = miniIconScale(d);
const int col = 5 * s; // triangle / column width
const int th = 3 * s; // triangle height
const int x = right_x - col; // indicator column origin
// Caps are static end-markers, always drawn flush at the very top / bottom of
// the track; the thumb travels in the fixed band between them. (They mark the
// track ends, not "more above/below", so they never vanish at the extremes.)
const int cap = th + 2; // triangle height + 2px gap
const int band_t = top_y + cap;
const int band_b = top_y + track_h - cap;
int band = band_b - band_t;
if (band < s) band = s;
const int bar_w = 3 * s; // odd width → centres exactly in the 5*s column
const int bar_x = x + (col - bar_w) / 2;
int thumb_h = (int)((long)band * view_px / total_px);
if (thumb_h < th) thumb_h = th;
if (thumb_h > band) thumb_h = band;
const int thumb_y = band_t + (int)((long)(band - thumb_h) * scroll_px / span);
// No halo: every caller already keeps its selection bar clear of this column
// (reserve/_reserve), so the markers never need to fight a LIGHT background
// for contrast — and a halo on the bottom arrow had no symmetric clip like
// the top one, so it bled 1px into the container's bottom border.
d.setColor(DisplayDriver::LIGHT);
d.fillRect(bar_x, thumb_y, bar_w, thumb_h);
miniIconDrawTop(d, x, top_y, ICON_SCROLL_UP);
miniIconDrawTop(d, x, top_y + track_h - th, ICON_SCROLL_DOWN);
}
// Convenience overload anchored to the screen's right edge — the common case
// for full-width lists.
inline void drawScrollIndicatorPx(DisplayDriver& d, int top_y, int track_h,
long total_px, long view_px, long scroll_px) {
drawScrollIndicatorPx(d, d.width(), top_y, track_h, total_px, view_px, scroll_px);
}
// Uniform-height wrapper: one item = one unit. Drop-in replacement for
// DisplayDriver::drawScrollArrows.
// total : total number of items
// visible : items shown at once
// first : index of the first visible item (scroll offset)
inline void drawScrollIndicator(DisplayDriver& d, int top_y, int track_h,
int total, int visible, int first) {
drawScrollIndicatorPx(d, top_y, track_h, total, visible, first);
}
// right_x variant — see drawScrollIndicatorPx's right_x for why a narrower
// container (e.g. a popup) needs this instead of the screen-edge default.
inline void drawScrollIndicator(DisplayDriver& d, int right_x, int top_y, int track_h,
int total, int visible, int first) {
drawScrollIndicatorPx(d, right_x, top_y, track_h, total, visible, first);
}
// Scrollable item-list skeleton shared by the list screens. Computes the visible
// window from the font metrics, keeps `sel` in view, reserves the scroll-column,
// draws each visible row through `row`, then the indicator. The callback
// `row(idx, y, sel, reserve)` draws one item — including its own selection bar —
// using `reserve` to keep right-aligned content clear of the indicator. Returns
// the visible row count (callers cache it for input handling).
template <class RenderRow>
inline int drawList(DisplayDriver& d, int total, int sel, int& scroll, RenderRow row) {
const int item_h = d.lineStep();
const int start_y = d.listStart();
int visible = d.listVisible(item_h);
if (visible < 1) visible = 1;
if (sel < scroll) scroll = sel;
if (sel >= scroll + visible) scroll = sel - visible + 1;
if (scroll < 0) scroll = 0;
const int reserve = scrollIndicatorReserve(d, total, visible);
for (int i = 0; i < visible && (scroll + i) < total; i++)
row(scroll + i, start_y + i * item_h, scroll + i == sel, reserve);
drawScrollIndicator(d, start_y, visible * item_h, total, visible, scroll);
return visible;
}
// Canonical selection bar for a drawList() row: spans the row width minus the
// scroll-indicator `reserve`, one pixel short of the row height, anchored one
// pixel above `y` (the row's text baseline-top). Call as the first line of a
// row callback, then draw content over it. Captures the geometry every list row
// repeated by hand; rows that intentionally differ (full-width, custom height)
// still call display.drawSelectionRow() directly.
inline void drawRowSelection(DisplayDriver& d, int y, bool sel, int reserve) {
d.drawSelectionRow(0, y - 1, d.width() - reserve, d.lineStep() - 1, sel);
}
// ── 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,
};