mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 02:11:57 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user