feat(eink): 7 px icons in landscape, a rounded battery

The landscape build's 8x13 digits stand 9 px, so its status, page and
check glyphs and the row-heading map markers come in a 7 px set instead
of 5. The battery outline, in the header and on the Status tile, leaves
its corner pixels out.

The page icons go back to a row of their own under the header: beside
them on the header line only three status icons fitted, and the rest
went missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-02 20:39:26 +02:00
co-authored by Claude Opus 5.5
parent 3948d65362
commit e16fe2c621
3 changed files with 232 additions and 35 deletions
+14 -7
View File
@@ -60,16 +60,23 @@ inline void section(DisplayDriver& d, int y, const char* label, int reserve = 0)
rule(d, lx + lw + gap, ry, right - (lx + lw + gap));
}
// A battery glyph the size of a mini icon (7 x 5 at 1x), filled to frac.
inline int batteryW(DisplayDriver& d) { return 7 * miniIconScale(d); }
// A battery glyph the size of a mini icon (7 x 5 at 1x, 9 x 7 in the large
// set), its corners softly rounded, filled to frac.
static constexpr int BATT_BODY_W = MINI_ICONS_LARGE ? 8 : 6, BATT_BODY_H = MINI_ICONS_LARGE ? 7 : 5;
inline int batteryW(DisplayDriver& d) { return (BATT_BODY_W + 1) * miniIconScale(d); }
inline int batteryH(DisplayDriver& d) { return BATT_BODY_H * miniIconScale(d); }
inline void battery(DisplayDriver& d, int x, int y, float frac) {
const int s = miniIconScale(d);
const int s = miniIconScale(d), w = BATT_BODY_W * s, h = BATT_BODY_H * s;
if (frac < 0) frac = 0;
if (frac > 1) frac = 1;
d.drawRect(x, y, 6 * s, 5 * s);
d.fillRect(x + 6 * s, y + s, s, 3 * s);
const int fw = (int)(4 * s * frac + 0.5f);
if (fw > 0) d.fillRect(x + s, y + s, fw, 3 * s);
d.fillRect(x + 1, y, w - 2, 1); // the outline without its corner pixels
d.fillRect(x + 1, y + h - 1, w - 2, 1);
d.fillRect(x, y + 1, 1, h - 2);
d.fillRect(x + w - 1, y + 1, 1, h - 2);
d.fillRect(x + w, y + s, s, h - 2 * s);
const int in = MINI_ICONS_LARGE ? 2 * s : s; // the large one keeps a gap inside its outline
const int fw = (int)((w - 2 * in) * frac + 0.5f);
if (fw > 0) d.fillRect(x + in, y + in, fw, h - 2 * in);
}
// A soft-cornered meter, filled to frac (0..1).
+13 -28
View File
@@ -335,25 +335,10 @@ class HomeScreen : public UIScreen {
uint8_t _slide_from = 0;
unsigned long _slide_t0 = 0;
// A wide screen (landscape e-ink) sets the page icons on the header line,
// between the time and the status icons, instead of on a row of their own.
// Measured for every page at full spacing, so it doesn't flip as pages are
// shown or hidden.
static int stripHalfW(DisplayDriver& d) {
const int s = miniIconScale(d);
return (10 * s * ((int)Count - 1) + 5 * s + 8) / 2;
}
static bool stripOnHeader(DisplayDriver& d) {
const int mid = d.width() / 2, half = stripHalfW(d);
// Left: the time and a gap. Right: the battery icon and two status icons.
const int right_need = 2 * d.getLineHeight() + 6 + 2 * (d.getCharWidth() + 3);
return mid - half >= 6 * d.getCharWidth() && mid + half <= d.width() - right_need;
}
// First row under the header and the page-icon row (see render()).
static int contentTop(DisplayDriver& d) {
const int pg_half = (5 * miniIconScale(d) + 1) / 2, lh = d.getLineHeight();
if (stripOnHeader(d)) return (lh > 2 * pg_half + 2 ? lh : 2 * pg_half + 2) + 3;
return lh + 2 * pg_half + 4;
const int pg_half = (PAGE_ICON_PX * miniIconScale(d) + 1) / 2;
return d.getLineHeight() + 2 * pg_half + 4;
}
// 0..1 eased progress of the running slide, or -1 when none.
float slideProgress() {
@@ -759,7 +744,11 @@ class HomeScreen : public UIScreen {
const int iconW = lh * 2;
const int bm = display.pixelScale() > 1 ? 3 : 2; // inner margin: 3px on a doubled panel, else 2px
battLeftX = display.width() - iconW - 3;
display.drawRect(battLeftX, 0, iconW, iconH);
// The outline with its corner pixels left out, softly rounded.
display.fillRect(battLeftX + 1, 0, iconW - 2, 1);
display.fillRect(battLeftX + 1, iconH - 1, iconW - 2, 1);
display.fillRect(battLeftX, 1, 1, iconH - 2);
display.fillRect(battLeftX + iconW - 1, 1, 1, iconH - 2);
// Nub height/2, vertically centred by remaining-space/2 rather than a flat
// iconH/4 margin — the flat form only centres when iconH is a multiple of
// 4 (true for the old built-in font's lh=8, false for misc-fixed's 7/9),
@@ -1296,7 +1285,7 @@ public:
auto tile = [&](int col, int row, const MiniIcon* ic, float batt, const char* head, const char* detail) {
const int x = col ? mid_x + 3 : 1, y = row ? mid_y + 2 : top + 1;
const int w = (col ? W - x : mid_x - 2 - x);
const int iy = y + (lh - 5 * s) / 2 - s;
const int iy = y + (lh - info::batteryH(d)) / 2 - s;
int tx = x;
if (ic) { miniIconDraw(d, x, iy, *ic); tx = x + ic->w * s + 2 * s; }
else if (batt >= 0) { info::battery(d, x, iy, batt); tx = x + info::batteryW(d) + 2 * s; }
@@ -1371,15 +1360,12 @@ public:
display.setTextSize(1);
const int lh = display.getLineHeight(); // line height at sz1
const int step = display.lineStep(); // lh + 2
// Page-indicator row: small (5px) page icons replace the old dots. Centre and
// Page-indicator row: small page icons (PAGE_ICON_PX) replace the old dots. Centre and
// gap scale with the font so the band clears the header above and content
// below (identical to the old lh+4 / +6 dots layout at 1x).
const int pg_half = (5 * miniIconScale(display) + 1) / 2;
const bool one_row = stripOnHeader(display); // the icons on the header line
const int dots_y = one_row ? lh / 2 : lh + pg_half + 1; // icon-row centre
const int pg_half = (PAGE_ICON_PX * miniIconScale(display) + 1) / 2;
const int dots_y = lh + pg_half + 1; // icon-row centre, below the header
const int content_y = contentTop(display); // first content row, below the icons
const int strip_l = display.width() / 2 - stripHalfW(display);
const int strip_r = display.width() / 2 + stripHalfW(display);
const float slide = slideProgress(); // -1, or how far a page turn has got
// Title bar displaying node name (except on lock screen), status icons and battery.
@@ -1388,9 +1374,8 @@ public:
display.setColor(DisplayDriver::LIGHT);
// The lock page has nothing on the left of the bar (its clock sits
// below it), so the status icons may use the whole row.
const int lock_reserve = _page == LOCK ? 0 : one_row ? strip_r + 2 : -1;
const int lock_reserve = _page == LOCK ? 0 : -1;
int rightEdge = renderBatteryIndicator(display, _task->getBattMilliVolts(), lock_reserve);
if (one_row && _page != LOCK) rightEdge = strip_l; // the time stops at the icons
display.setColor(DisplayDriver::LIGHT);
if (_page != LOCK) {
@@ -1435,7 +1420,7 @@ public:
int curr_vis = 0;
for (int i = 0; i < n; i++) if (order[i] == _page) { curr_vis = i; break; }
const int s = miniIconScale(display);
const int icon_w = 5 * s;
const int icon_w = PAGE_ICON_PX * s;
int pitch = icon_w + 5 * s; // comfortable spacing
if (n > 1) { // shrink to fit if many pages
int fit = (display.width() - icon_w) / (n - 1);
+205
View File
@@ -41,6 +41,15 @@ struct MiniIcon { uint8_t w, h; const uint8_t* rows; };
static constexpr MiniIcon name = { (uint8_t)(width), \
(uint8_t)sizeof(name##_rows), name##_rows }
// The landscape e-ink build writes in the 8x13, whose digits stand 9 px:
// there the status, page and check glyphs come in a 7 px set instead of 5.
#if defined(EINK_LARGE_FONT) && EINK_LARGE_FONT
#define MINI_ICONS_LARGE 1
#else
#define MINI_ICONS_LARGE 0
#endif
static constexpr int PAGE_ICON_PX = MINI_ICONS_LARGE ? 7 : 5; // the page glyphs' box
// 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) {
@@ -104,6 +113,20 @@ inline void miniIconDotRow(DisplayDriver& d, int x, int top_y, int count) {
}
// Mini-icon bitmaps (authored on the 1× grid as ASCII-art; see packRow above).
#if MINI_ICONS_LARGE
MINI_ICON(ICON_CHECK, 7, // ✓
packRow("......#"),
packRow(".....#."),
packRow("#...#.."),
packRow(".#.#..."),
packRow("..#...."));
MINI_ICON(ICON_CROSS, 5, // ✗
packRow("#...#"),
packRow(".#.#."),
packRow("..#.."),
packRow(".#.#."),
packRow("#...#"));
#else
MINI_ICON(ICON_CHECK, 5, // ✓
packRow("....#"),
packRow("...#."),
@@ -114,6 +137,7 @@ MINI_ICON(ICON_CROSS, 4, // ✗
packRow(".##."),
packRow(".##."),
packRow("#..#"));
#endif
// Tiny 3×5 digits — for a small count that needs to sit in an icon-sized slot
// (e.g. next to ICON_CHECK) where the normal font is too tall to fit. See
@@ -206,6 +230,81 @@ inline void miniIconDrawNumber(DisplayDriver& d, int x, int top_y, int n) {
}
// Top-bar status glyphs (replace the single-letter M / B / A indicators).
#if MINI_ICONS_LARGE
MINI_ICON(ICON_MUTE, 8, // 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, 7, // ((•)) advert
packRow(".#...#."),
packRow("#.....#"),
packRow("#.###.#"),
packRow("#.###.#"),
packRow("#.###.#"),
packRow("#.....#"),
packRow(".#...#."));
MINI_ICON(ICON_ALARM, 7, // bell — an alarm is armed
packRow("...#..."),
packRow("..###.."),
packRow(".#####."),
packRow(".#####."),
packRow(".#####."),
packRow("#######"),
packRow("...#..."));
MINI_ICON(ICON_TRAIL, 7, // map pin (GPS trail logging)
packRow("..###.."),
packRow(".#####."),
packRow("##...##"),
packRow("##...##"),
packRow(".#####."),
packRow("..###.."),
packRow("...#..."));
MINI_ICON(ICON_REPEATER, 8, // » relaying (repeater active)
packRow("#...#..."),
packRow(".#...#.."),
packRow("..#...#."),
packRow("...#...#"),
packRow("..#...#."),
packRow(".#...#.."),
packRow("#...#..."));
MINI_ICON(ICON_GPS, 7, // reticle with its dot: GPS has a fix
packRow("..###.."),
packRow(".#...#."),
packRow("#..#..#"),
packRow("#.###.#"),
packRow("#..#..#"),
packRow(".#...#."),
packRow("..###.."));
MINI_ICON(ICON_GPS_SEARCH, 7, // the reticle broken: still searching
packRow("..#.#.."),
packRow(".#...#."),
packRow("#.....#"),
packRow("......."),
packRow("#.....#"),
packRow(".#...#."),
packRow("..#.#.."));
MINI_ICON(ICON_CHART, 7, // ascending bars — Home › Status
packRow("......#"),
packRow("....#.#"),
packRow("....#.#"),
packRow("..#.#.#"),
packRow("..#.#.#"),
packRow("#.#.#.#"),
packRow("#######"));
#else
MINI_ICON(ICON_MUTE, 6, // speaker + cross (sound off)
packRow("..#..."),
packRow(".##..."),
@@ -273,10 +372,93 @@ MINI_ICON(ICON_CHART, 5, // ascending bars — Home › Status
packRow("#.#.#"),
packRow("#.#.#"),
packRow("#####"));
#endif
// 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().
#if MINI_ICONS_LARGE
MINI_ICON(ICON_PG_CLOCK, 7, // clock face + hands
packRow("..###.."),
packRow(".#.#.#."),
packRow("#..#..#"),
packRow("#..##.#"),
packRow("#.....#"),
packRow(".#...#."),
packRow("..###.."));
MINI_ICON(ICON_PG_STAR, 7, // favourites
packRow("...#..."),
packRow("...#..."),
packRow("#######"),
packRow(".#####."),
packRow("..###.."),
packRow(".##.##."),
packRow(".#...#."));
MINI_ICON(ICON_PG_RADIO, 7, // antenna with waves — radio
packRow(".#...#."),
packRow("#..#..#"),
packRow("#.###.#"),
packRow(".#.#.#."),
packRow("...#..."),
packRow("..#.#.."),
packRow(".#...#."));
MINI_ICON(ICON_PG_BT, 5, // bluetooth
packRow("..#.."),
packRow("..##."),
packRow("#.#.#"),
packRow(".###."),
packRow("#.#.#"),
packRow("..##."),
packRow("..#.."));
MINI_ICON(ICON_PG_ADVERT, 7, // advert page: the same waves
packRow(".#...#."),
packRow("#.....#"),
packRow("#.###.#"),
packRow("#.###.#"),
packRow("#.###.#"),
packRow("#.....#"),
packRow(".#...#."));
MINI_ICON(ICON_PG_SETTINGS, 7, // cog with a hub hole
packRow("..#.#.."),
packRow(".#####."),
packRow("##...##"),
packRow(".#...#."),
packRow("##...##"),
packRow(".#####."),
packRow("..#.#.."));
MINI_ICON(ICON_PG_MAP, 7, // folded map
packRow("#######"),
packRow("#..#..#"),
packRow("#..#..#"),
packRow("#..#..#"),
packRow("#..#..#"),
packRow("#..#..#"),
packRow("#######"));
MINI_ICON(ICON_PG_TOOLS, 7, // wrench, open jaw top right
packRow("....#.#"),
packRow("....#.#"),
packRow("....###"),
packRow("...##.."),
packRow("..##..."),
packRow(".##...."),
packRow("##....."));
MINI_ICON(ICON_PG_MSG, 7, // speech bubble with three dots
packRow("#######"),
packRow("#.....#"),
packRow("#.#.#.#"),
packRow("#.....#"),
packRow("#######"),
packRow("##....."),
packRow("#......"));
MINI_ICON(ICON_PG_POWER, 7, // power symbol
packRow("...#..."),
packRow(".#.#.#."),
packRow("#..#..#"),
packRow("#.....#"),
packRow("#.....#"),
packRow(".#...#."),
packRow("..###.."));
#else
MINI_ICON(ICON_PG_CLOCK, 5, // clock face + hands
packRow(".###."),
packRow("#.#.#"),
@@ -336,6 +518,7 @@ MINI_ICON(ICON_PG_POWER, 5, // power symbol — shutdown
packRow("#...#"),
packRow("#...#"),
packRow(".###."));
#endif
// Trail-map markers — centred on a point (see miniIconDrawCentered) rather
// than anchored to a text line.
@@ -347,12 +530,23 @@ MINI_ICON(ICON_MAP_RING, 3, // ○ hollow ring — marks a new trail segme
packRow("###"),
packRow("#.#"),
packRow("###"));
#if MINI_ICONS_LARGE // the markers that also head rows of text
MINI_ICON(ICON_MAP_WAYPOINT, 7, // ◇ hollow diamond — saved waypoint
packRow("...#..."),
packRow("..#.#.."),
packRow(".#...#."),
packRow("#.....#"),
packRow(".#...#."),
packRow("..#.#.."),
packRow("...#..."));
#else
MINI_ICON(ICON_MAP_WAYPOINT, 5, // ◇ hollow diamond — saved waypoint
packRow("..#.."),
packRow(".#.#."),
packRow("#...#"),
packRow(".#.#."),
packRow("..#.."));
#endif
MINI_ICON(ICON_MAP_START, 5, // + trail start marker
packRow("..#.."),
packRow("..#.."),
@@ -365,12 +559,23 @@ MINI_ICON(ICON_MAP_CURRENT, 5, // ✕ live position / last trail point
packRow("..#.."),
packRow(".#.#."),
packRow("#...#"));
#if MINI_ICONS_LARGE
MINI_ICON(ICON_MAP_CONTACT, 7, // ◆ filled diamond — a live-tracked contact
packRow("...#..."),
packRow("..###.."),
packRow(".#####."),
packRow("#######"),
packRow(".#####."),
packRow("..###.."),
packRow("...#..."));
#else
MINI_ICON(ICON_MAP_CONTACT, 5, // ◆ filled diamond — a live-tracked contact ([LOC] share)
packRow("..#.."),
packRow(".###."),
packRow("#####"),
packRow(".###."),
packRow("..#.."));
#endif
MINI_ICON(ICON_MAP_NORTH, 5, // "N" with a peaked roof — compass north marker
packRow("..#.."),
packRow(".###."),