From 1580de53f54d103dc5a37e6fb994219b80ea37f2 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Thu, 1 Oct 2026 22:13:11 +0200 Subject: [PATCH] feat(l1): one soft-cornered frame for popups, alerts and bubbles; small screen fixes DisplayDriver gets drawSoftRect / fillSoftRect / drawPanel (the unread badge's corner knockout; a panel also clears a 1 px moat so lines below stop short of its border). Used by alerts, popups, the accent picker, the lock hint, chat bubbles and the send button. Fixes: no "Msg:" toast for a message landing in the open thread; Nodes scan text fits the screen; the PIN pad skips the blanks beside 0; Auto-Advert is a plain value row; the Admin picker says what it needs when empty; the GPS page shows "-" instead of 999 before a first fix. Co-Authored-By: Claude Opus 5.5 --- .../companion_radio/ui-new/AutoAdvertScreen.h | 33 +++++++------------ .../companion_radio/ui-new/KeyboardWidget.h | 14 ++++---- .../companion_radio/ui-new/MessagesScreen.h | 8 ++--- .../companion_radio/ui-new/NearbyScreen.h | 7 ++-- examples/companion_radio/ui-new/PopupMenu.h | 5 +-- examples/companion_radio/ui-new/UITask.cpp | 27 ++++++++------- src/helpers/ui/DisplayDriver.h | 27 +++++++++++++++ 7 files changed, 72 insertions(+), 49 deletions(-) diff --git a/examples/companion_radio/ui-new/AutoAdvertScreen.h b/examples/companion_radio/ui-new/AutoAdvertScreen.h index c597f9d3..4f269482 100644 --- a/examples/companion_radio/ui-new/AutoAdvertScreen.h +++ b/examples/companion_radio/ui-new/AutoAdvertScreen.h @@ -22,31 +22,22 @@ public: void onShow() override { _dirty = false; } + // One value row, drawn like every other settings list (LEFT/RIGHT or Enter + // cycles, Back saves) instead of a screen of its own with typed hints. int render(DisplayDriver& display) override { display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); - int label_y = display.listStart(); - int bar_y = label_y + display.lineStep(); - int bar_h = display.lineStep(); - int tip_y = bar_y + bar_h + 4; - display.drawCenteredHeader("AUTO-ADVERT"); - - display.setCursor(2, label_y); - display.print("Interval:"); - - int idx = currentIdx(); - display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, bar_y, display.width(), bar_h); - display.setColor(DisplayDriver::DARK); - display.drawTextCentered(display.width() / 2, bar_y + 1, OPT_LABELS[idx]); - display.setColor(DisplayDriver::LIGHT); - - display.setCursor(2, tip_y); - display.print("< > to change"); - display.setCursor(2, tip_y + display.lineStep()); - display.print("[Esc] to save"); - return 500; + const int valx = display.width() / 2 + 6; + int sel = 0, scroll = 0; + drawList(display, 1, sel, scroll, [&](int, int y, bool is_sel, int reserve) { + drawRowSelection(display, y, is_sel, reserve); + display.setCursor(4, y); + display.print("Interval"); + display.setCursor(valx, y); + display.print(OPT_LABELS[currentIdx()]); + }); + return 5000; } bool handleInput(char c) override { diff --git a/examples/companion_radio/ui-new/KeyboardWidget.h b/examples/companion_radio/ui-new/KeyboardWidget.h index 4b8f23a7..1db8112e 100644 --- a/examples/companion_radio/ui-new/KeyboardWidget.h +++ b/examples/companion_radio/ui-new/KeyboardWidget.h @@ -801,10 +801,7 @@ struct KeyboardWidget { int bx = (display.width() - bw) / 2; int by = compact_ui ? (chars_y + 2 * lh + 2) : (chars_y + row * cell_h - 1); int bh = compact_ui ? (lh + 2) : (cell_h + 1); - display.setColor(DisplayDriver::DARK); - display.fillRect(bx, by, bw, bh); - display.setColor(DisplayDriver::LIGHT); - display.drawRect(bx, by, bw, bh); + display.drawPanel(bx, by, bw, bh); for (int i = 0; i < n; i++) { char one[5]; kbUtf8CharAt(group, i, one); char shown[5]; kbApplyCapsUtf8(one, caps, shown, sizeof(shown)); @@ -971,22 +968,27 @@ struct KeyboardWidget { } return NONE; } + // The blank cells either side of 0 are never a stop: moving up/down + // onto one lands on 0, moving sideways steps over it. + auto blank = [&]() { return row < rows && KB_PIN_DIGITS[row][col] == ' '; }; if (c == KEY_UP) { // Navigation row = (row > 0) ? row - 1 : rows; + if (blank()) col = 1; return NONE; } if (c == KEY_DOWN) { row = (row < rows) ? row + 1 : 0; + if (blank()) col = 1; return NONE; } if (c == KEY_LEFT) { int max_col = (row == rows) ? KB_PIN_SPECIAL - 1 : cols - 1; - col = (col > 0) ? col - 1 : max_col; + do { col = (col > 0) ? col - 1 : max_col; } while (blank()); return NONE; } if (c == KEY_RIGHT) { int max_col = (row == rows) ? KB_PIN_SPECIAL - 1 : cols - 1; - col = (col < max_col) ? col + 1 : 0; + do { col = (col < max_col) ? col + 1 : 0; } while (blank()); return NONE; } if (c == KEY_ENTER) { diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index 66228c69..0c80a52d 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -459,10 +459,10 @@ class MessagesScreen : public UIScreen { static void drawHistRowFrame(DisplayDriver& d, int box_x, int box_w, int y, int bh, int lh, bool sel) { d.setColor(DisplayDriver::LIGHT); if (sel) { - d.fillRect(box_x, y, box_w, bh); + d.fillSoftRect(box_x, y, box_w, bh); d.setColor(DisplayDriver::DARK); } else { - d.drawRect(box_x, y, box_w, bh); + d.drawSoftRect(box_x, y, box_w, bh); } } @@ -519,8 +519,8 @@ class MessagesScreen : public UIScreen { int bw = ctw + 4; int bx = d.width() - bw; d.setColor(DisplayDriver::LIGHT); - if (sel) { d.fillRect(bx, cby, bw, lh); d.setColor(DisplayDriver::DARK); } - else d.drawRect(bx, cby, bw, lh); + if (sel) { d.fillSoftRect(bx, cby, bw, lh); d.setColor(DisplayDriver::DARK); } + else d.drawSoftRect(bx, cby, bw, lh); d.setCursor(bx + 2, cby); d.print(ctxt); d.setColor(DisplayDriver::LIGHT); diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index b1d0c61a..099dc886 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -631,14 +631,15 @@ public: char empty[24]; if (_source == SRC_SCAN) { const char* msg; - if (_scanning) msg = "Waiting for replies..."; + if (_scanning) msg = "No replies yet"; // fits 128 px; the header already says SCANNING else if (flt) { snprintf(empty, sizeof(empty), "No %s nodes", flt); msg = empty; } else msg = "No nodes found"; display.drawTextCentered(display.width() / 2, display.height() / 2, msg); } else { const char* hint; - if (flt) { snprintf(empty, sizeof(empty), "No %s contacts", flt); hint = "[<>] change filter"; } - else { snprintf(empty, sizeof(empty), "No contacts found"); hint = "[Enter]=Discover"; } + if (_pick_admin_target && !flt) { snprintf(empty, sizeof(empty), "Nothing to admin"); hint = "Repeaters & rooms"; } + else if (flt) { snprintf(empty, sizeof(empty), "No %s contacts", flt); hint = "[<>] change filter"; } + else { snprintf(empty, sizeof(empty), "No contacts found"); hint = "[Enter]=Discover"; } display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, empty); display.drawTextCentered(display.width() / 2, display.height() / 2 + display.lineStep() / 2, hint); } diff --git a/examples/companion_radio/ui-new/PopupMenu.h b/examples/companion_radio/ui-new/PopupMenu.h index 840fe2e3..943bfbc5 100644 --- a/examples/companion_radio/ui-new/PopupMenu.h +++ b/examples/companion_radio/ui-new/PopupMenu.h @@ -149,10 +149,7 @@ struct PopupMenu { if (bx < margin) bx = margin; if (by < margin) by = margin; - display.setColor(DisplayDriver::DARK); - display.fillRect(bx, by, bw, bh); - display.setColor(DisplayDriver::LIGHT); - display.drawRect(bx, by, bw, bh); + display.drawPanel(bx, by, bw, bh); if (_title) display.drawTextEllipsized(bx + pad, by + 1, bw - pad * 2, _title); display.fillRect(bx, by + lh + 2, bw, sh); // separator just under the title; gap follows diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7d0842a7..f6c3b055 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -932,9 +932,7 @@ public: const int hy = display.height() - lk_lh - p * 2; const int hw = display.getTextWidth(hint); const int hx = (display.width() - hw) / 2; - display.setColor(DisplayDriver::LIGHT); - display.fillRect(hx - p, hy - p, hw + p*2, lk_lh + p*2); - display.setColor(DisplayDriver::DARK); + display.drawPanel(hx - p, hy - p, hw + p*2, lk_lh + p*2); display.setCursor(hx, hy); display.print(hint); } else if (_page == HomePage::RADIO) { @@ -1015,7 +1013,10 @@ public: display.drawTextRightAlign(display.width()-1, y, buf); y += step; display.drawTextLeftAlign(0, y, "pos"); - snprintf(buf, sizeof(buf),"%.4f %.4f", + // The driver reports 999 deg until a first fix: show a dash, not the + // placeholder. After a fix the last known position stays. + if (labs(nmea->getLatitude()) > 90000000L) strcpy(buf, "-"); + else snprintf(buf, sizeof(buf),"%.4f %.4f", nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); display.drawTextRightAlign(display.width()-1, y, buf); y += step; @@ -1854,9 +1855,16 @@ void UITask::onMessageArrived(const UiEvent& ev) { } if (ev.kind == UIEventType::channelMessage) _last_notif_ch_idx = ev.idx; - char alert_buf[80]; - snprintf(alert_buf, sizeof(alert_buf), "Msg: %.20s", ev.text); - showAlert(alert_buf, 3000); + // No toast when the message lands in the thread that is open on screen: + // it already shows up there, and the box would only cover it. + bool in_view = curr == messages_screen && _display && _display->isOn() && !_locked && + ((ev.kind == UIEventType::contactMessage && ev.flag && isViewingDM(ev.key)) || + (ev.kind == UIEventType::channelMessage && ev.idx >= 0 && isViewingChannel((uint8_t)ev.idx))); + if (!in_view) { + char alert_buf[80]; + snprintf(alert_buf, sizeof(alert_buf), "Msg: %.20s", ev.text); + showAlert(alert_buf, 3000); + } if (_display != NULL && !_locked) { bool wake_disabled = _node_prefs && (_node_prefs->msg_wake_screen_off || inQuietHours(*_node_prefs, rtc_clock.getCurrentTime())); @@ -1909,10 +1917,7 @@ void UITask::renderAlertOverlay() { if (nl < 1) nl = 1; int box_h = nl * lh + pad * 2; int box_y = (_display->height() - box_h) / 2; - _display->setColor(DisplayDriver::DARK); - _display->fillRect(box_x, box_y, box_w, box_h); - _display->setColor(DisplayDriver::LIGHT); - _display->drawRect(box_x, box_y, box_w, box_h); + _display->drawPanel(box_x, box_y, box_w, box_h); for (int i = 0; i < nl; i++) _display->drawTextCentered(_display->width() / 2, box_y + pad + i * lh, s_wrap_lines[i]); } diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index 75cd19c3..7f6a0f0c 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -193,6 +193,33 @@ public: } } + // Soft-cornered boxes: the same corner-pixel knockout as drawUnreadBadge(), + // shared by bubbles, popups, alerts and hints so every box reads alike. + // Outline only, corners left out; line thickness follows sepH(). + void drawSoftRect(int x, int y, int w, int h) { + int t = sepH(); + fillRect(x + 1, y, w - 2, t); + fillRect(x + 1, y + h - t, w - 2, t); + fillRect(x, y + 1, t, h - 2); + fillRect(x + w - t, y + 1, t, h - 2); + } + // Filled in the current colour; the four corner pixels aren't painted, so + // they keep whatever is under them. + void fillSoftRect(int x, int y, int w, int h) { + fillRect(x + 1, y, w - 2, h); + fillRect(x, y + 1, 1, h - 2); + fillRect(x + w - 1, y + 1, 1, h - 2); + } + // A box floating over the screen (alert, popup, hint): clears a 1 px moat + // around it so lines of the view below stop short of the border instead of + // running into it, fills it dark and draws the soft border. Leaves ink LIGHT. + void drawPanel(int x, int y, int w, int h) { + setColor(DARK); + fillRect(x - 1, y - 1, w + 2, h + 2); + setColor(LIGHT); + drawSoftRect(x, y, w, h); + } + // Format a small unread count into buf: "1".."99", then "99+". count >= 1. // No stdio — DisplayDriver.h only pulls stdint/string. // overflow: the count itself is honest (how many still-held entries are