Files
MeshCore-Solo/docs/design/trail_redesign.md
MarekZegare4 38e9273ec7 refactor(ui): reorganize Tools › Trail (popup, map, waypoints)
Tidies the Trail screen end to end, with no functional change beyond the
intended UX cleanups. Squashed from refactor/trail-screen.

Popup:
- Two-level action menu (Hold Enter): short main menu (Start/Stop, Mark
  here, Waypoints…, Trail file…, Settings…) plus "Trail file…" and
  "Settings…" submenus, replacing one flat ~12-item list that mixed
  settings and actions. One interaction pattern per level; Reset lives in
  Trail file…, away from a stray Enter. Settings submenu is view-aware
  (Grid only on Map, Readout only on Summary).

Map:
- MapProjection (geo→pixel) built once and shared by the map, grid and
  marker drawing — removes the duplicated projection math and renderGrid's
  11 scalar params.
- renderMap split into computeBounds() and drawMarkers() helpers.

Grid:
- Square cells fitted to the frame: the shorter side is divided into a
  whole number of equal cells (grid touches that pair of borders), and the
  longer side centres the whole cells that fit — square, symmetric, no
  one-sided drift. Drawn as one dot per intersection (legible on OLED).

Waypoints:
- Extracted the whole waypoint management UI (list / navigate / add-by-
  coords / mark / rename / delete / send) into a self-contained
  WaypointsView component that TrailScreen owns and delegates to.
- Dropped the redundant bulk "Clear all" (per-waypoint Delete covers it).

Docs updated (tools_screen.md) + design rationale (docs/design/
trail_redesign.md). Builds clean on solo envs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:05:49 +02:00

8.0 KiB
Raw Blame History

Trail (Tools Trail) — analiza i propozycja uporządkowania

Branch: refactor/trail-screen Plik źródłowy: TrailScreen.h (1065 linii) Status: propozycja (do akceptacji przed implementacją)


1. Jak ekran jest zbudowany dzisiaj

TrailScreen
├── 3 widoki (LEFT/RIGHT):  Summary · Map · List
├── popup akcji  (Hold Enter)  — JEDNA płaska lista, do 12 pozycji
│   ├── ustawienia (LEFT/RIGHT cykluje w miejscu): Min dist · Readout · Grid
│   ├── toggle:    Start/Stop tracking
│   ├── waypointy: Mark here · Waypoints · Clear waypoints
│   └── trail:     Save · Load · Export(live) · Export(saved) · Reset
├── pod-ekrany waypointów (nakładane na widoki):
│   ├── WP_LIST  (lista + dystanse; Trail-start + „+ Add by coords")
│   ├── WP_NAV   (navview)
│   ├── WP_ADD   (formularz lat/lon/label)
│   └── _wp_ctx popup: Rename · Delete · Send
└── KeyboardWidget (label / lat / lon)  — nakładka pełnoekranowa

Renderowanie mapy: renderMap() (≈140 linii) + renderGrid() (≈130 linii)

  • 7 funkcji rysujących markery.

2. Co jest nieuporządkowane

2.1 Popup akcji to jedna płaska lista 12 pozycji z mieszanymi rolami

openActionMenu() (TrailScreen.h:363) buduje jedno menu, które miesza cztery różne klasy pozycji:

Klasa Pozycje Interakcja
Ustawienia Min dist, Readout, Grid LEFT/RIGHT (w miejscu)
Stan nagrywania Start/Stop tracking Enter
Waypointy Mark here, Waypoints, Clear waypoints Enter
Plik trasy Save, Load, Export(live), Export(saved), Reset Enter

Problemy:

  • Długi scroll — na OLED widać ~4 wiersze naraz, więc do „Reset trail" trzeba przewinąć przez całą listę;
  • Dwa wzorce interakcji w jednym menu — część pozycji reaguje na LEFT/RIGHT (ustawienia), część na Enter (akcje). Enter na wierszu ustawień nic sensownego nie robi — tylko zamyka i otwiera menu od nowa (reopenAt, TrailScreen.h:581);
  • Brak kontekstu widokuGrid (dotyczy tylko mapy) i Readout (dotyczy tylko Summary) są widoczne zawsze, też tam, gdzie nie mają efektu;
  • Grid ma dwie ścieżki — i LEFT/RIGHT (:221) i Enter (:234) robią to samo; lekko myli.

2.2 renderGrid dostaje 11 skalarnych parametrów — brak wspólnej projekcji

renderMap liczy projekcję (lokalne lambdy projectLL/project, :816), a renderGrid (:876) dostaje 11 osobnych liczb (area_*, min/max_lat, min_lon, lon_scale_geo, scale, off_*) i powtarza tę samą matematykę projekcji ręcznie w pętli (:988, :992). To samo równanie żyje w trzech miejscach. Każda zmiana modelu mapy wymaga edycji w kilku miejscach naraz.

2.3 Wybór kroku siatki — wielostopniowa heurystyka z nieaktualnym komentarzem

renderGrid wybiera krok siatki w czterech następujących po sobie korektach (:912:952):

  1. największy krok ≤ target_m,
  2. zwiększaj aż odstęp pikseli ≥ MIN_GRID_PX (22 px),
  3. zmniejszaj aż zmieszczą się ≥2 interwały,
  4. zwiększaj aż liczba linii ≤ MAX_GRID_LINES (40).

Uwagi:

  • Komentarz przy kroku 4 (:937) mówi o „static buffers (40×40 = ~1600 intersections)" — takich buforów już nie ma; pętla rysuje na bieżąco z continue-guardami (:9861002). Cap 40 ogranicza dziś tylko liczbę iteracji pętli (wydajność), nie chroni żadnego bufora. Komentarz wprowadza w błąd.
  • Kroki 2 i 3 mogą sobie przeczyć na bardzo małych ekranach (MIN_GRID_PX = 22 vs shorter_px/2, gdy shorter_px < 44). Nie powoduje błędu, ale „ostateczny" krok bywa wtedy przypadkowy.

2.4 Drobne

  • _act_map[16] z komentarzem „12 used today; pad" — ręczne pilnowanie rozmiaru; pushAction już to zabezpiecza, więc magiczna 16 jest zbędna.
  • Bounding-box mapy i markery mają sporo powtarzalnego clamp-to-edge (:852, :1011).

3. Propozycja uporządkowania

3.1 Popup: dwa poziomy zamiast jednej płaskiej listy

Górne menu krótkie (akcje), ustawienia i operacje na pliku w podmenu:

Hold Enter →  Trail
  • Start / Stop tracking
  • Mark here
  • Waypoints…          → istniejący WP_LIST
  • Trail file…         → Save / Load / Export (live) / Export (saved) / Reset
  • Settings…           → Min dist · Readout · Grid   (LEFT/RIGHT w miejscu)

Korzyści:

  • górne menu to ~5 pozycji, bez scrolla na OLED;
  • jeden wzorzec na poziom: górny i „Trail file" = Enter-akcje; „Settings" = wartości cyklowane LEFT/RIGHT — bez mieszania w jednym widoku;
  • destrukcyjny Reset przeniesiony do „Trail file…", dalej od przypadkowego Entera;
  • (opcjonalnie) Grid pokazywać tylko gdy aktywny jest widok Map, a Readout tylko przy Summary — menu zależne od kontekstu widoku.

Wariant minimalny (mniej kodu): zostać przy jednej liście, ale pogrupować (ustawienia → akcje → plik), Reset na sam dół, usunąć podwójną ścieżkę Grid. Mniej porządku niż podmenu, ale tańsze.

3.2 Mapa: wspólny obiekt projekcji

Wydzielić mały MapProjection liczony raz w renderMap i przekazywany do renderGrid oraz markerów:

struct MapProjection {
  int32_t min_lat, max_lat, min_lon;
  float   lon_scale_geo, scale;
  int     off_x, off_y, area_x, area_y, area_w, area_h;
  void project(int32_t lat, int32_t lon, int& px, int& py) const;
};
  • renderGrid(display, proj) zamiast 11 parametrów;
  • jedno równanie projekcji (dziś powielone 3×);
  • markery/waypointy też przez proj.project(...).

Czysto refaktoryzacyjne — bez zmiany wyglądu mapy.

3.3 Siatka: uproszczenie i naprawa komentarza

  • poprawić/skasować komentarz o „static buffers" (już nieaktualny);
  • scalić wybór kroku w jedną pętlę „znajdź najmniejszy krok, który daje odstęp ≥ MIN_GRID_PX i ≤ MAX_GRID_LINES linii" zamiast czterech następujących korekt;
  • bbox etykiety/strzałki północy liczyć z jednej funkcji pomocniczej.

Wynik wizualnie identyczny, logika krótsza i łatwiejsza do utrzymania.


4. Proponowany zakres (etapami)

Etap Zmiana Ryzyko
1 Popup → dwa poziomy (Trail file…, Settings…); Reset głębiej; usuń podwójny Grid niskie
2 Wydziel MapProjection; renderGrid i markery przez projekcję średnie (czysty refactor)
3 Uprość wybór kroku siatki; popraw nieaktualne komentarze; sprzątnij _act_map niskie

Etap 1 = największa poprawa „uporządkowania popupu" (główna prośba). Etapy 23 = czyszczenie logiki mapy/siatki bez zmiany wyglądu.


5. Decyzje (zatwierdzone 2026-06-14)

  1. Popup — pełne podmenu. Górne menu krótkie; Trail file… i Settings… jako podmenu.
  2. Menu zależne od widoku — tak. Grid widoczny w Settings tylko na widoku Map, Readout tylko na Summary.
  3. Mapa — MapProjection + uproszczenie siatki (etap 2 i 3 razem).
  4. Siatka — kwadratowe oczka, dociągnięte do krótszego boku. Krok w przestrzeni pikseli (skala izotropiczna). Krótszy bok dzielony na całkowitą liczbę równych kwadratów (siatka dotyka tej pary krawędzi — na poziomym OLED: góra/dół), na dłuższym boku mieści się całkowita liczba tych samych kwadratów, wyśrodkowana. Dzięki temu oczka są kwadratowe, siatka wpisana w ramkę i symetryczna (brak jednostronnego przesunięcia). Etykieta skali = nominalna okrągła wartość. (Kwadraty + linie na wszystkich 4 krawędziach są geometrycznie niemożliwe dla dowolnego prostokąta — wybrano kwadraty + 2 krawędzie + symetria.)