refactor(ui): trail — drop bottom hint, N/3 in title, popup-only start

The hint row plus its separator ate ~12 px on OLED. View counter moves
into the title bar ("TRAIL 1/3", "TRAIL MAP 2/3", "TRAIL LIST 3/3") so
the same context is available without a dedicated row, and the freed
space goes to whatever the view is rendering.

Short Enter now never toggles tracking. Both start and stop are only
reachable through the Hold-Enter popup — symmetric, no stray-tap risk.
Short Enter on the screen just shows "Hold Enter for menu" so the user
who tapped finds out where to look.

Available area for Summary / List recomputed without the hint
reservation (avail = height − y0 − 2); map's bottom edge moves down to
height − 2 too.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-25 17:58:56 +02:00
co-authored by Claude Sonnet 4.6
parent 652e1f0979
commit dfc9d96ce5
+14 -28
View File
@@ -48,9 +48,13 @@ public:
display.setTextSize(1); display.setTextSize(1);
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
const char* title = (_view == V_MAP) ? "TRAIL MAP" // Title carries the view counter so the bottom hint row can be reclaimed
: (_view == V_LIST) ? "TRAIL LIST" // for content.
: "TRAIL"; const char* base = (_view == V_MAP) ? "TRAIL MAP"
: (_view == V_LIST) ? "TRAIL LIST"
: "TRAIL";
char title[20];
snprintf(title, sizeof(title), "%s %d/%d", base, (int)_view + 1, (int)V_COUNT);
display.drawTextCentered(display.width() / 2, 0, title); display.drawTextCentered(display.width() / 2, 0, title);
display.fillRect(0, display.headerH() - 1, display.width(), display.sepH()); display.fillRect(0, display.headerH() - 1, display.width(), display.sepH());
@@ -58,17 +62,6 @@ public:
else if (_view == V_LIST) renderList(display); else if (_view == V_LIST) renderList(display);
else renderSummary(display); else renderSummary(display);
// Bottom hint with separator above so content never touches it.
display.setColor(DisplayDriver::LIGHT);
const int hint_y = display.height() - display.lineStep();
display.fillRect(0, hint_y - 2, display.width(), 1);
display.setCursor(2, hint_y);
char hint[28];
snprintf(hint, sizeof(hint), "<>%d/%d %s [Hold]menu",
(int)_view + 1, (int)V_COUNT,
_store->isActive() ? "act" : "[Ent]start");
display.print(hint);
if (_action_menu.active) _action_menu.render(display); if (_action_menu.active) _action_menu.render(display);
return _store->isActive() ? 1000 : 5000; return _store->isActive() ? 1000 : 5000;
} }
@@ -118,15 +111,10 @@ public:
if (_view == V_LIST && c == KEY_UP && _list_scroll > 0) { _list_scroll--; return true; } if (_view == V_LIST && c == KEY_UP && _list_scroll > 0) { _list_scroll--; return true; }
if (_view == V_LIST && c == KEY_DOWN) { _list_scroll++; return true; } if (_view == V_LIST && c == KEY_DOWN) { _list_scroll++; return true; }
if (c == KEY_ENTER) { if (c == KEY_ENTER) {
// Short Enter starts the trail if it's stopped — start is safe. Once // Short Enter intentionally does nothing destructive — both start and
// active, short Enter does nothing on purpose: stop is only reachable // stop go through the Hold-Enter popup so a stray tap can't change the
// through the popup so a stray tap can't end a recording. // tracking state.
if (!_store->isActive()) { _task->showAlert("Hold Enter for menu", 1000);
_store->setActive(true);
_task->showAlert(gpsHasFix() ? "Tracking started" : "Waiting for GPS fix", 1000);
} else {
_task->showAlert("Hold Enter for menu", 1000);
}
return true; return true;
} }
return false; return false;
@@ -270,8 +258,7 @@ private:
void renderSummary(DisplayDriver& display) { void renderSummary(DisplayDriver& display) {
const int y0 = display.listStart(); const int y0 = display.listStart();
const int step = display.lineStep(); const int step = display.lineStep();
const int hint_h = step; const int avail = display.height() - y0 - 2;
const int avail = display.height() - y0 - hint_h - 4;
int visible = avail / step; int visible = avail / step;
if (visible < 1) visible = 1; if (visible < 1) visible = 1;
if (visible > SUMMARY_ITEM_COUNT) visible = SUMMARY_ITEM_COUNT; if (visible > SUMMARY_ITEM_COUNT) visible = SUMMARY_ITEM_COUNT;
@@ -303,8 +290,7 @@ private:
void renderList(DisplayDriver& display) { void renderList(DisplayDriver& display) {
const int top = display.listStart(); const int top = display.listStart();
const int step = display.lineStep(); const int step = display.lineStep();
const int hint_h = step; const int avail = display.height() - top - 2;
const int avail = display.height() - top - hint_h - 4;
if (_store->empty()) { if (_store->empty()) {
display.drawTextCentered(display.width() / 2, top + avail / 2, "No trail yet"); display.drawTextCentered(display.width() / 2, top + avail / 2, "No trail yet");
@@ -362,7 +348,7 @@ private:
void renderMap(DisplayDriver& display) { void renderMap(DisplayDriver& display) {
const int top = display.listStart(); const int top = display.listStart();
const int bottom = display.height() - display.lineStep() - 3; const int bottom = display.height() - 2;
if (_store->empty()) { if (_store->empty()) {
display.drawTextCentered(display.width() / 2, (top + bottom) / 2, "No trail yet"); display.drawTextCentered(display.width() / 2, (top + bottom) / 2, "No trail yet");