From 4c1ebebcde8373d2e71d31a5c9dd6bfb982f0019 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:53:59 +0200 Subject: [PATCH] fix(ui): lines and outlines follow the pixel scale on the 4.26" e-ink, opening a conversation no longer re-anchors on its second frame Co-Authored-By: Claude Opus 5.5 --- .../companion_radio/ui-new/CalendarView.h | 2 +- examples/companion_radio/ui-new/GfxUtils.h | 3 ++- examples/companion_radio/ui-new/HomeScreen.h | 27 ++++++++++--------- examples/companion_radio/ui-new/InfoKit.h | 23 +++++++++------- .../companion_radio/ui-new/KeyboardWidget.h | 4 +-- .../companion_radio/ui-new/MessagesScreen.h | 4 +-- examples/companion_radio/ui-new/TabBar.h | 16 ++++++----- examples/companion_radio/ui-new/UITask.cpp | 9 ++++--- examples/companion_radio/ui-new/UITask.h | 2 +- variants/sim/web/mesh.html | 4 +-- 10 files changed, 52 insertions(+), 42 deletions(-) diff --git a/examples/companion_radio/ui-new/CalendarView.h b/examples/companion_radio/ui-new/CalendarView.h index fa022a79..5e4cabde 100644 --- a/examples/companion_radio/ui-new/CalendarView.h +++ b/examples/companion_radio/ui-new/CalendarView.h @@ -49,7 +49,7 @@ inline void draw(DisplayDriver& d, int x, int y, int w, const struct tm& t, uint if (marked & (1UL << (day - 1))) { // under the number, in the gap below it const int uw = d.getTextWidth(buf); d.setColor(today && day == t.tm_mday ? DisplayDriver::DARK : DisplayDriver::LIGHT); - d.fillRect(cx + (cw - uw) / 2, cy + (tight ? lh - 2 : lh), uw, 1); + d.fillRect(cx + (cw - uw) / 2, cy + (tight ? lh - 2 : lh), uw, d.sepH()); d.setColor(DisplayDriver::LIGHT); } } diff --git a/examples/companion_radio/ui-new/GfxUtils.h b/examples/companion_radio/ui-new/GfxUtils.h index 1bee7418..2b8019b0 100644 --- a/examples/companion_radio/ui-new/GfxUtils.h +++ b/examples/companion_radio/ui-new/GfxUtils.h @@ -15,8 +15,9 @@ static inline void drawLine(DisplayDriver& d, int x0, int y0, int x1, int y1) { int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int err = dx + dy; + const int t = d.sepH(); // 2px on a doubled panel while (true) { - d.fillRect(x0, y0, 1, 1); + d.fillRect(x0 - t / 2, y0 - t / 2, t, t); if (x0 == x1 && y0 == y1) break; int e2 = err * 2; if (e2 >= dy) { err += dy; x0 += sx; } diff --git a/examples/companion_radio/ui-new/HomeScreen.h b/examples/companion_radio/ui-new/HomeScreen.h index 13320b8c..55ff561b 100644 --- a/examples/companion_radio/ui-new/HomeScreen.h +++ b/examples/companion_radio/ui-new/HomeScreen.h @@ -572,19 +572,17 @@ class HomeScreen : public UIScreen { } else { // icon — scales with lh, same box height as the status icons beside it (ind_h) const int iconH = ind_h; const int iconW = (bar_h > lh) ? ind_h * 2 : lh * 2; - const int bm = display.pixelScale() > 1 ? 3 : 2; // inner margin: 3px on a doubled panel, else 2px + const int t = display.sepH(); // outline: 1px, 2px on a doubled panel + const int bm = 2 * t; // inner margin: the outline plus as much gap battLeftX = display.width() - iconW - 3; // 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); + display.drawSoftRect(battLeftX, 0, iconW, iconH); // 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), // so it visibly drifted off-centre once the box height changed. const int nub_h = iconH / 2; - display.fillRect(battLeftX + iconW, (iconH - nub_h) / 2, 2, nub_h); + display.fillRect(battLeftX + iconW, (iconH - nub_h) / 2, 2 * t, nub_h); int fillW = (pct * (iconW - 2 * bm)) / 100; display.fillRect(battLeftX + bm, bm, fillW, iconH - 2 * bm); } @@ -793,7 +791,7 @@ public: if (barpx < 5) barpx = 5; if (barpx > aw / 4) barpx = aw / 4; int bx = ax + 1, mid = ty + display.getLineHeight() / 2; - display.fillRect(bx, mid, barpx, 1); // single tick, on the text baseline + display.fillRect(bx, mid, barpx, display.sepH()); // single tick, on the text baseline display.setCursor(bx + barpx + 2, ty); display.print(L[sel]); } @@ -1023,8 +1021,9 @@ public: const int bar_y = y + bc.h + bar / 2 + 1; if (show_sec) { // fills over the minute: dotted track, solid part const int x0 = W / 8, bw = W - 2 * x0, fill = bw * ti.tm_sec / 60; - d.fillRect(x0, bar_y, fill, 1); - for (int x = x0 + fill + (fill & 1); x < x0 + bw; x += 2) d.fillRect(x, bar_y, 1, 1); + const int t = d.sepH(); + d.fillRect(x0, bar_y, fill, t); + for (int x = x0 + fill + (fill & 1) * t; x < x0 + bw; x += 2 * t) d.fillRect(x, bar_y, t, t); } // Alarm armed: the bell at the bar's left end (the status bar, which // shows it everywhere else, is hidden on this page). @@ -1375,7 +1374,8 @@ public: int x = display.width() / 2 - pitch * (n - 1) / 2; // The current page: its icon knocked out of a soft pill. During a page // turn the pill glides over from the page being left. - int pw = icon_w + 4; + const int pad = display.sepH(); // the pill's margin round the icon + int pw = icon_w + 4 * pad; if (pw > pitch - 1) pw = pitch - 1; int pill_x = x + pitch * curr_vis; if (slide >= 0) { @@ -1383,7 +1383,7 @@ public: if (order[i] == _slide_from) { pill_x = x + pitch * i + (int)((pill_x - x - pitch * i) * slide + 0.5f); break; } } display.setColor(DisplayDriver::LIGHT); - display.fillSoftRect(pill_x - pw / 2, dots_y - pg_half - 1, pw, pg_half * 2 + 3); + display.fillSoftRect(pill_x - pw / 2, dots_y - pg_half - pad, pw, pg_half * 2 + 1 + 2 * pad); for (int i = 0; i < n; i++) { const MiniIcon* ic = pageIcon(order[i]); const int d2 = 2 * (x - pill_x < 0 ? pill_x - x : x - pill_x); @@ -1455,8 +1455,9 @@ public: if (sel) { display.fillSoftRect(tx, ty, tw, th); display.setColor(DisplayDriver::DARK); } else if (prefix) display.drawSoftRect(tx, ty, tw, th); else { - for (int x = tx + 2; x < tx + tw - 1; x += 2) { display.fillRect(x, ty, 1, 1); display.fillRect(x, ty + th - 1, 1, 1); } - for (int y = ty + 2; y < ty + th - 1; y += 2) { display.fillRect(tx, y, 1, 1); display.fillRect(tx + tw - 1, y, 1, 1); } + const int t = display.sepH(); // dot size: 2px on a doubled panel + for (int x = tx + 2 * t; x < tx + tw - t; x += 2 * t) { display.fillRect(x, ty, t, t); display.fillRect(x, ty + th - t, t, t); } + for (int y = ty + 2 * t; y < ty + th - t; y += 2 * t) { display.fillRect(tx, y, t, t); display.fillRect(tx + tw - t, y, t, t); } } char name[26]; diff --git a/examples/companion_radio/ui-new/InfoKit.h b/examples/companion_radio/ui-new/InfoKit.h index 07719988..bde683bb 100644 --- a/examples/companion_radio/ui-new/InfoKit.h +++ b/examples/companion_radio/ui-new/InfoKit.h @@ -73,10 +73,7 @@ inline void battery(DisplayDriver& d, int x, int y, float frac) { 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.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.drawSoftRect(x, y, w, h); // the outline without its corner pixels 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); @@ -92,15 +89,21 @@ inline void meter(DisplayDriver& d, int x, int y, int w, int h, float frac) { if (fw > 0) d.fillRect(x + in, y + in, fw, h - 2 * in); } +// One point of a line or circle, sepH() square (2px on a doubled panel). +inline void plot(DisplayDriver& d, int x, int y) { + const int t = d.sepH(); + d.fillRect(x - t / 2, y - t / 2, t, t); +} + // Midpoint circle; `step` > 1 dots it (every step-th point of each octant). inline void circle(DisplayDriver& d, int cx, int cy, int r, int step = 1) { int x = r, y = 0, err = 1 - r, i = 0; while (x >= y) { if (i++ % step == 0) { - d.fillRect(cx + x, cy + y, 1, 1); d.fillRect(cx - x, cy + y, 1, 1); - d.fillRect(cx + x, cy - y, 1, 1); d.fillRect(cx - x, cy - y, 1, 1); - d.fillRect(cx + y, cy + x, 1, 1); d.fillRect(cx - y, cy + x, 1, 1); - d.fillRect(cx + y, cy - x, 1, 1); d.fillRect(cx - y, cy - x, 1, 1); + plot(d, cx + x, cy + y); plot(d, cx - x, cy + y); + plot(d, cx + x, cy - y); plot(d, cx - x, cy - y); + plot(d, cx + y, cy + x); plot(d, cx - y, cy + x); + plot(d, cx + y, cy - x); plot(d, cx - y, cy - x); } y++; if (err < 0) err += 2 * y + 1; @@ -108,13 +111,13 @@ inline void circle(DisplayDriver& d, int cx, int cy, int r, int step = 1) { } } -// A 1 px line (Bresenham). +// A line (Bresenham), sepH() thick. inline void line(DisplayDriver& d, int x0, int y0, int x1, int y1) { const int dx = x1 > x0 ? x1 - x0 : x0 - x1, dy = y1 > y0 ? y0 - y1 : y1 - y0; const int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1; int err = dx + dy; for (;;) { - d.fillRect(x0, y0, 1, 1); + plot(d, x0, y0); if (x0 == x1 && y0 == y1) break; const int e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += sx; } diff --git a/examples/companion_radio/ui-new/KeyboardWidget.h b/examples/companion_radio/ui-new/KeyboardWidget.h index e75bdf72..bb059d91 100644 --- a/examples/companion_radio/ui-new/KeyboardWidget.h +++ b/examples/companion_radio/ui-new/KeyboardWidget.h @@ -568,7 +568,7 @@ struct KeyboardWidget { const int slots = len + (len < max_len ? 1 : 0); int x = display.width() / 2 - (slots * pitch - 3) / 2; for (int i = 0; i < slots; i++, x += pitch) { - if (i == len) { display.fillRect(x, y + lh - 2, cw, 1); continue; } // next digit goes here + if (i == len) { display.fillRect(x, y + lh - 2, cw, display.sepH()); continue; } // next digit goes here if (pin_kb_mask_enabled) { display.fillSoftRect(x, y + 1, cw, lh - 3); } else { @@ -887,7 +887,7 @@ struct KeyboardWidget { // completely differently (one letter vs. every following letter). // caps_lock implies the cell is filled, so the current (inverted) // ink colour is the one that shows against it. - if (i == 0 && caps_lock) display.fillRect(sx + 2, spec_y + cell_h - 3, spec_w - 5, 1); + if (i == 0 && caps_lock) display.fillRect(sx + 2, spec_y + cell_h - 3, spec_w - 5, display.sepH()); } display.setColor(DisplayDriver::LIGHT); } diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index cf7ba863..10abed0f 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -1083,8 +1083,8 @@ public: int tw = d.getTextWidth(t); int tx = (w - tw) / 2, mid = y + lh / 2; d.setColor(DisplayDriver::LIGHT); - d.fillRect(1, mid, tx - 4, 1); - d.fillRect(tx + tw + 3, mid, w - 1 - (tx + tw + 3), 1); + d.fillRect(1, mid, tx - 4, d.sepH()); + d.fillRect(tx + tw + 3, mid, w - 1 - (tx + tw + 3), d.sepH()); d.setCursor(tx, y + 1); d.print(t); } diff --git a/examples/companion_radio/ui-new/TabBar.h b/examples/companion_radio/ui-new/TabBar.h index ae184133..a6b00f5a 100644 --- a/examples/companion_radio/ui-new/TabBar.h +++ b/examples/companion_radio/ui-new/TabBar.h @@ -28,12 +28,13 @@ inline void drawPill(DisplayDriver& display, int x, int w, const char* label, bo } // Small solid triangle pointing left (dir < 0) or right, 3 px wide and 5 -// tall, centred on row y_mid: "more tabs this way". +// tall (times sepH()), centred on row y_mid: "more tabs this way". inline void drawEdgeArrow(DisplayDriver& display, int x, int y_mid, int dir) { + const int t = display.sepH(); display.setColor(DisplayDriver::LIGHT); for (int c = 0; c < 3; c++) { - int h = dir < 0 ? c * 2 + 1 : 5 - c * 2; - display.fillRect(x + c, y_mid - h / 2, 1, h); + int h = (dir < 0 ? c * 2 + 1 : 5 - c * 2) * t; + display.fillRect(x + c * t, y_mid - h / 2, t, h); } } @@ -75,16 +76,17 @@ inline void draw(DisplayDriver& display, const char* const* labels, int count, i return Fit{ shown, lx + gap, rx_limit - rx + gap }; }; + const int arr = 3 * display.sepH(); // the edge arrow's width drawPill(display, ax, aw, labels[active], true); Fit f = layout(0, false); - if (f.shown < count && f.left_free < 4 && f.right_free < 4) - f = layout(5, true); // no room for an arrow anywhere: give up a tab for them + if (f.shown < count && f.left_free < arr + 1 && f.right_free < arr + 1) + f = layout(arr + 2, true); // no room for an arrow anywhere: give up a tab for them else layout(0, true); if (f.shown < count) { // more tabs off screen: an arrow wherever there's room int y_mid = display.getLineHeight() / 2; - if (f.left_free >= 4) drawEdgeArrow(display, 0, y_mid, -1); - if (f.right_free >= 4) drawEdgeArrow(display, rx_limit - 3, y_mid, +1); + if (f.left_free >= arr + 1) drawEdgeArrow(display, 0, y_mid, -1); + if (f.right_free >= arr + 1) drawEdgeArrow(display, rx_limit - arr, y_mid, +1); } display.setColor(DisplayDriver::LIGHT); display.fillRect(0, display.headerH() - display.sepH(), display.width(), display.sepH()); diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 106198a0..67191710 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1085,9 +1085,12 @@ void UITask::loop() { #endif pollConnection(); // BLE link state -> hasConnection() (see UITaskBase) drainCoreEvents(); // react to what the Core filed during mesh processing (alerts, wake, sounds) - { // a conversation in view again (display on, unlocked): on to what came in meanwhile - bool on = messagesInView(); - if (on && !_display_was_on) ((MessagesScreen*)messages_screen)->onDisplayOn(); + { // a conversation in view again (display on, unlocked): on to what came in meanwhile. + // Only on the display or lock changing -- not on opening the screen, which + // positions itself (a second pass would re-anchor on what the first frame + // had just marked read, and the list jumped). + bool on = _display && _display->isOn() && !_locked; + if (on && !_display_was_on && curr == messages_screen) ((MessagesScreen*)messages_screen)->onDisplayOn(); _display_was_on = on; } if (status_screen) ((StatusScreen*)status_screen)->sample(); // Status history lines, once a minute diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 8e96ffbc..3c6abf1d 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -60,7 +60,7 @@ class UITask : public UITaskBase, public UiCoreHost { GenericVibration vibration; #endif unsigned long _next_refresh, _auto_off; - bool _display_was_on = true; // loop(): messagesInView() last time round + bool _display_was_on = true; // loop(): display on and unlocked last time round NodePrefs* _node_prefs; bool _locked; bool _tool_from_home = false; diff --git a/variants/sim/web/mesh.html b/variants/sim/web/mesh.html index 5464e425..3b5b06f7 100644 --- a/variants/sim/web/mesh.html +++ b/variants/sim/web/mesh.html @@ -231,14 +231,14 @@ - +