mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
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:
@@ -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).
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(".###."),
|
||||
|
||||
Reference in New Issue
Block a user