refactor(companion): extract drawRowSelection() for the canonical list-row bar

Every drawList/AccordionList row opened with the same hand-written
display.drawSelectionRow(0, y-1, width-reserve, lineStep-1, sel) line —
14 copies of the same geometry and magic offsets. Add a drawRowSelection(d,
y, sel, reserve) helper in icons.h next to drawList and route the canonical
sites through it (Bot/LiveShare/Locator/Repeater/Settings/Tools/Nearby/
QuickMsg/Waypoints).

Rows that intentionally differ (full-width DashboardConfig/QuickMsg, the
keyboard/ringtone grids) keep their explicit drawSelectionRow() call — the
helper is opt-in rather than baked into drawList, so legitimate variants
aren't forced into one geometry. Framework doc updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-29 18:53:18 +02:00
parent 0e6bd743a6
commit cc88e89a64
11 changed files with 30 additions and 19 deletions

View File

@@ -111,14 +111,17 @@ indicator:
```cpp ```cpp
drawList(display, count, _sel, _scroll, [&](int idx, int y, bool sel, int reserve) { drawList(display, count, _sel, _scroll, [&](int idx, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve); // canonical highlight bar
display.drawTextEllipsized(2, y, display.width() - reserve - 4, items[idx].name); display.drawTextEllipsized(2, y, display.width() - reserve - 4, items[idx].name);
}); });
``` ```
`reserve` is the width the scrollbar took (0 when the list fits) — subtract it `reserve` is the width the scrollbar took (0 when the list fits) — subtract it
from any right-aligned content so nothing slides under the indicator. The row from any right-aligned content so nothing slides under the indicator. The row
callback owns its own selection bar (so a screen can style it). For the callback owns its own selection bar: `drawRowSelection(d, y, sel, reserve)`
(`ui-new/icons.h`) draws the standard one (full row minus reserve, one pixel
short); call `display.drawSelectionRow()` directly only when a row needs a
non-standard geometry (full-width, custom height). For the
fold-in-place pattern (sections that expand/collapse) use `AccordionList` fold-in-place pattern (sections that expand/collapse) use `AccordionList`
instead (`ui-new/AccordionList.h`) — same idea, two callbacks (header + item). instead (`ui-new/AccordionList.h`) — same idea, two callbacks (header + item).
@@ -299,7 +302,7 @@ public:
d.setTextSize(1); d.setTextSize(1);
d.drawCenteredHeader("MY TOOL"); d.drawCenteredHeader("MY TOOL");
drawList(d, ROWS, _sel, _scroll, [&](int i, int y, bool sel, int reserve) { drawList(d, ROWS, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
d.drawSelectionRow(0, y - 1, d.width() - reserve, d.lineStep() - 1, sel); drawRowSelection(d, y, sel, reserve);
d.setCursor(4, y); d.setCursor(4, y);
d.print(i == 0 ? "Alpha" : i == 1 ? "Bravo" : "Charlie"); d.print(i == 0 ? "Alpha" : i == 1 ? "Bravo" : "Charlie");
}); });

View File

@@ -75,7 +75,7 @@ public:
"Trigger Ch", "Reply Ch", "Commands", "Trigger Ch", "Reply Ch", "Commands",
"Quiet from", "Quiet to" }; "Quiet from", "Quiet to" };
drawList(display, ITEM_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) { drawList(display, ITEM_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
display.setCursor(2, y); display.setCursor(2, y);
display.print(labels[i]); display.print(labels[i]);
display.setCursor(val_x, y); display.setCursor(val_x, y);

View File

@@ -92,7 +92,7 @@ public:
const int valx = display.width() / 2 + 6; const int valx = display.width() / 2 + 6;
drawList(display, ROW_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) { drawList(display, ROW_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
Row r = rows(i); Row r = rows(i);
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
display.setCursor(4, y); display.setCursor(4, y);
display.print(r.label); display.print(r.label);
char val[24]; char val[24];

View File

@@ -116,7 +116,7 @@ public:
const int valx = display.width() / 2 + 6; const int valx = display.width() / 2 + 6;
drawList(display, rc, _sel, _scroll, [&](int i, int y, bool sel, int reserve) { drawList(display, rc, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
Row r = rows(i); Row r = rows(i);
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
display.setCursor(4, y); display.setCursor(4, y);
display.print(r.label); display.print(r.label);
char val[24]; char val[24];
@@ -265,7 +265,7 @@ public:
display.drawCenteredHeader("PICK TARGET"); display.drawCenteredHeader("PICK TARGET");
uint32_t now = rtc_clock.getCurrentTime(); uint32_t now = rtc_clock.getCurrentTime();
drawList(display, _target_n, _pick_sel, _pick_scroll, [&](int i, int y, bool sel, int reserve) { drawList(display, _target_n, _pick_sel, _pick_scroll, [&](int i, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
const Target& t = _targets[i]; const Target& t = _targets[i];
char row[36]; char row[36];
if (t.kind != 1) { if (t.kind != 1) {

View File

@@ -686,7 +686,7 @@ public:
drawList(display, _count, _sel, _scroll, [&](int idx, int y, bool sel, int reserve) { drawList(display, _count, _sel, _scroll, [&](int idx, int y, bool sel, int reserve) {
const Entry& e = _entries[idx]; const Entry& e = _entries[idx];
display.drawSelectionRow(0, y - 1, display.width() - reserve, item_h - 1, sel); drawRowSelection(display, y, sel, reserve);
char filt[32]; char filt[32];
int tx = 2; int tx = 2;

View File

@@ -1046,7 +1046,7 @@ public:
drawList(display, _num_contacts, _contact_sel, _contact_scroll, [&](int list_idx, int y, bool sel, int reserve) { drawList(display, _num_contacts, _contact_sel, _contact_scroll, [&](int list_idx, int y, bool sel, int reserve) {
int mesh_idx = _sorted[list_idx]; int mesh_idx = _sorted[list_idx];
display.drawSelectionRow(0, y - 1, display.width() - reserve, item_h - 1, sel); drawRowSelection(display, y, sel, reserve);
ContactInfo c; ContactInfo c;
if (the_mesh.getContactByIdx(mesh_idx, c)) { if (the_mesh.getContactByIdx(mesh_idx, c)) {
@@ -1079,7 +1079,7 @@ public:
} }
drawList(display, _num_channels, _channel_sel, _channel_scroll, [&](int list_idx, int y, bool sel, int reserve) { drawList(display, _num_channels, _channel_sel, _channel_scroll, [&](int list_idx, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, item_h - 1, sel); drawRowSelection(display, y, sel, reserve);
ChannelDetails ch; ChannelDetails ch;
if (the_mesh.getChannel(_channel_indices[list_idx], ch)) { if (the_mesh.getChannel(_channel_indices[list_idx], ch)) {
uint8_t unread = _ch_unread[_channel_indices[list_idx]]; uint8_t unread = _ch_unread[_channel_indices[list_idx]];
@@ -1437,7 +1437,7 @@ public:
int total_msg_items = 1 + _active_msg_count; int total_msg_items = 1 + _active_msg_count;
drawList(display, total_msg_items, _msg_sel, _msg_scroll, [&](int idx, int y, bool sel, int reserve) { drawList(display, total_msg_items, _msg_sel, _msg_scroll, [&](int idx, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, item_h - 1, sel); drawRowSelection(display, y, sel, reserve);
if (idx == 0) { if (idx == 0) {
display.setCursor(2, y); display.setCursor(2, y);

View File

@@ -146,7 +146,7 @@ public:
// Config only — live forwarding stats live on Tools Diagnostics. // Config only — live forwarding stats live on Tools Diagnostics.
drawList(display, _item_count, _sel, _scroll, [&](int row, int y, bool sel, int reserve) { drawList(display, _item_count, _sel, _scroll, [&](int row, int y, bool sel, int reserve) {
int item = _items[row]; int item = _items[row];
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
display.setCursor(2, y); display.setCursor(2, y);
display.print(itemLabel(item)); display.print(itemLabel(item));
if (item == IT_RFREQ && sel && _editor.active()) { if (item == IT_RFREQ && sel && _editor.active()) {

View File

@@ -422,7 +422,7 @@ class SettingsScreen : public UIScreen {
void renderItem(DisplayDriver& display, int item, int y, bool sel) { void renderItem(DisplayDriver& display, int item, int y, bool sel) {
NodePrefs* p = _task->getNodePrefs(); NodePrefs* p = _task->getNodePrefs();
display.drawSelectionRow(0, y - 1, display.width() - _reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, _reserve);
display.setCursor(2, y); display.setCursor(2, y);
@@ -671,7 +671,7 @@ public:
[&](int sec, int y, bool sel, int reserve, bool collapsed) { [&](int sec, int y, bool sel, int reserve, bool collapsed) {
_reserve = reserve; _reserve = reserve;
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
display.setCursor(2, y); display.setCursor(2, y);
display.print(collapsed ? "+" : "-"); display.print(collapsed ? "+" : "-");
display.print(" "); display.print(" ");

View File

@@ -75,7 +75,7 @@ public:
_acc.render(display, _acc.render(display,
// Section header: "[+/-] <icon> Name" // Section header: "[+/-] <icon> Name"
[&](int sec, int y, bool sel, int reserve, bool collapsed) { [&](int sec, int y, bool sel, int reserve, bool collapsed) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
display.setCursor(2, y); display.setCursor(2, y);
display.print(collapsed ? "+" : "-"); display.print(collapsed ? "+" : "-");
const int icon_x = 2 + cw + 2; const int icon_x = 2 + cw + 2;
@@ -85,7 +85,7 @@ public:
}, },
// Item: indented "<icon> Label" // Item: indented "<icon> Label"
[&](int sec, int item, int y, bool sel, int reserve) { [&](int sec, int item, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel); drawRowSelection(display, y, sel, reserve);
const int icon_x = 2 + cw + 2; // align item icons under the header icon const int icon_x = 2 + cw + 2; // align item icons under the header icon
// drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon); // icons disabled for now, don't fit visually // drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon); // icons disabled for now, don't fit visually
display.setCursor(icon_x + g, y); display.setCursor(icon_x + g, y);

View File

@@ -153,12 +153,10 @@ class WaypointsView {
int n = wpListCount(); int n = wpListCount();
int total = n + 1; // final row = "+ Add by coords" int total = n + 1; // final row = "+ Add by coords"
const int step = display.lineStep();
int32_t mylat, mylon; bool have = ownPos(mylat, mylon); int32_t mylat, mylon; bool have = ownPos(mylat, mylon);
drawList(display, total, _sel, _scroll, [&](int row, int y, bool sel, int reserve) { drawList(display, total, _sel, _scroll, [&](int row, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, step - 1, sel); drawRowSelection(display, y, sel, reserve);
if (row == n) { // the synthetic "Add" row if (row == n) { // the synthetic "Add" row
display.setCursor(2, y); display.print("+ Add by coords"); display.setCursor(2, y); display.print("+ Add by coords");

View File

@@ -499,6 +499,16 @@ inline int drawList(DisplayDriver& d, int total, int sel, int& scroll, RenderRow
return visible; return visible;
} }
// Canonical selection bar for a drawList() row: spans the row width minus the
// scroll-indicator `reserve`, one pixel short of the row height, anchored one
// pixel above `y` (the row's text baseline-top). Call as the first line of a
// row callback, then draw content over it. Captures the geometry every list row
// repeated by hand; rows that intentionally differ (full-width, custom height)
// still call display.drawSelectionRow() directly.
inline void drawRowSelection(DisplayDriver& d, int y, bool sel, int reserve) {
d.drawSelectionRow(0, y - 1, d.width() - reserve, d.lineStep() - 1, sel);
}
// ── Big ASCII-art icons (skeleton, not yet used) ───────────────────────────── // ── Big ASCII-art icons (skeleton, not yet used) ─────────────────────────────
// Same authoring idea as the mini-icons but for full page glyphs up to 32 px // Same authoring idea as the mini-icons but for full page glyphs up to 32 px
// wide: one uint32_t per row. The existing XBM bitmaps below (logo/bluetooth/…) // wide: one uint32_t per row. The existing XBM bitmaps below (logo/bluetooth/…)