mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 23:08:11 +00:00
fix(ui): scrollable Summary in TrailScreen so OLED hint stops overlapping
OLED (64 px height) couldn't fit 5 summary items plus the bottom hint row — Time/Speed lines overlapped the [Ent]/[Esc] hint. Convert Summary to a scrolling list: visible-count derived from the available area (height - listStart - hint - margin) divided by lineStep, scroll arrows "^"/"v" on the right edge when more items hide above/below. UP/DOWN scroll within Summary; LEFT/RIGHT still switch views. E-ink portrait and OLED both benefit; e-ink landscape fits all 5 items without ever showing the arrows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,9 @@ class TrailScreen : public UIScreen {
|
||||
|
||||
enum View { V_SUMMARY = 0, V_MAP = 1, V_COUNT };
|
||||
uint8_t _view = V_SUMMARY;
|
||||
int _summary_scroll = 0; // top visible item index in Summary view
|
||||
|
||||
static const int SUMMARY_ITEM_COUNT = 5;
|
||||
|
||||
public:
|
||||
TrailScreen(UITask* task, TrailStore* store) : _task(task), _store(store) {}
|
||||
@@ -50,6 +53,8 @@ public:
|
||||
}
|
||||
if (c == KEY_LEFT || c == KEY_PREV) { _view = (_view + V_COUNT - 1) % V_COUNT; return true; }
|
||||
if (c == KEY_RIGHT || c == KEY_NEXT) { _view = (_view + 1) % V_COUNT; return true; }
|
||||
if (_view == V_SUMMARY && c == KEY_UP && _summary_scroll > 0) { _summary_scroll--; return true; }
|
||||
if (_view == V_SUMMARY && c == KEY_DOWN) { _summary_scroll++; return true; } // clamped on render
|
||||
if (c == KEY_ENTER) {
|
||||
_store->setActive(!_store->isActive());
|
||||
_task->showAlert(_store->isActive() ? "Tracking started" : "Tracking stopped", 800);
|
||||
@@ -59,39 +64,69 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// Format the i-th summary line into buf. Indices match SUMMARY_ITEM_COUNT.
|
||||
void summaryItem(int i, char* buf, size_t n) const {
|
||||
switch (i) {
|
||||
case 0:
|
||||
snprintf(buf, n, "Status: %s", _store->isActive() ? "tracking" : "stopped");
|
||||
break;
|
||||
case 1:
|
||||
snprintf(buf, n, "Points: %d / %d", _store->count(), TrailStore::CAPACITY);
|
||||
break;
|
||||
case 2: {
|
||||
uint32_t d = _store->totalDistanceMeters();
|
||||
if (d < 1000) snprintf(buf, n, "Dist: %lu m", (unsigned long)d);
|
||||
else snprintf(buf, n, "Dist: %lu.%02lu km",
|
||||
(unsigned long)(d / 1000), (unsigned long)((d % 1000) / 10));
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
uint32_t es = _store->elapsedSeconds();
|
||||
snprintf(buf, n, "Time: %lu:%02lu",
|
||||
(unsigned long)(es / 3600), (unsigned long)((es % 3600) / 60));
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
snprintf(buf, n, "Speed: %u km/h", (unsigned)_store->currentSpeedKmh());
|
||||
break;
|
||||
default:
|
||||
buf[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void renderSummary(DisplayDriver& display) {
|
||||
const int y0 = display.listStart();
|
||||
const int step = display.lineStep();
|
||||
const int y0 = display.listStart();
|
||||
const int step = display.lineStep();
|
||||
const int hint_h = step;
|
||||
const int avail = display.height() - y0 - hint_h - 2;
|
||||
int visible = avail / step;
|
||||
if (visible < 1) visible = 1;
|
||||
if (visible > SUMMARY_ITEM_COUNT) visible = SUMMARY_ITEM_COUNT;
|
||||
|
||||
char buf[28];
|
||||
// Clamp scroll once we know visible count.
|
||||
int max_scroll = SUMMARY_ITEM_COUNT - visible;
|
||||
if (_summary_scroll > max_scroll) _summary_scroll = max_scroll;
|
||||
if (_summary_scroll < 0) _summary_scroll = 0;
|
||||
|
||||
snprintf(buf, sizeof(buf), "Status: %s",
|
||||
_store->isActive() ? "tracking" : "stopped");
|
||||
display.setCursor(2, y0);
|
||||
display.print(buf);
|
||||
for (int i = 0; i < visible; i++) {
|
||||
int idx = _summary_scroll + i;
|
||||
if (idx >= SUMMARY_ITEM_COUNT) break;
|
||||
char buf[28];
|
||||
summaryItem(idx, buf, sizeof(buf));
|
||||
display.setCursor(2, y0 + i * step);
|
||||
display.print(buf);
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "Points: %d / %d", _store->count(), TrailStore::CAPACITY);
|
||||
display.setCursor(2, y0 + step);
|
||||
display.print(buf);
|
||||
|
||||
uint32_t dist = _store->totalDistanceMeters();
|
||||
if (dist < 1000) snprintf(buf, sizeof(buf), "Dist: %lu m", (unsigned long)dist);
|
||||
else snprintf(buf, sizeof(buf), "Dist: %lu.%02lu km",
|
||||
(unsigned long)(dist / 1000),
|
||||
(unsigned long)((dist % 1000) / 10));
|
||||
display.setCursor(2, y0 + step * 2);
|
||||
display.print(buf);
|
||||
|
||||
uint32_t es = _store->elapsedSeconds();
|
||||
snprintf(buf, sizeof(buf), "Time: %lu:%02lu",
|
||||
(unsigned long)(es / 3600),
|
||||
(unsigned long)((es % 3600) / 60));
|
||||
display.setCursor(2, y0 + step * 3);
|
||||
display.print(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "Speed: %u km/h", (unsigned)_store->currentSpeedKmh());
|
||||
display.setCursor(2, y0 + step * 4);
|
||||
display.print(buf);
|
||||
// Scroll arrows on the right edge when there's more above/below.
|
||||
int cw = display.getCharWidth();
|
||||
if (_summary_scroll > 0) {
|
||||
display.setCursor(display.width() - cw, y0);
|
||||
display.print("^");
|
||||
}
|
||||
if (_summary_scroll + visible < SUMMARY_ITEM_COUNT) {
|
||||
display.setCursor(display.width() - cw, y0 + (visible - 1) * step);
|
||||
display.print("v");
|
||||
}
|
||||
}
|
||||
|
||||
// Pixel-by-pixel polyline. Bounds are auto-fitted to the available area and
|
||||
|
||||
Reference in New Issue
Block a user