feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
#pragma once
|
2026-05-25 09:10:40 +02:00
|
|
|
|
// GPS trail viewer. Tools › Trail.
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
// Three live views — Summary, Map, List — cyclable with LEFT/RIGHT.
|
|
|
|
|
|
// All settings and actions live in the Hold-Enter popup so a short Enter never
|
|
|
|
|
|
// accidentally stops tracking.
|
2026-05-25 09:10:40 +02:00
|
|
|
|
// Included by UITask.cpp after Trail store + ToolsScreen.
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-05-25 09:10:40 +02:00
|
|
|
|
#include "../Trail.h"
|
2026-06-03 08:28:15 +02:00
|
|
|
|
#include "../GeoUtils.h"
|
2026-06-04 00:13:44 +02:00
|
|
|
|
#include "GfxUtils.h"
|
2026-06-18 09:16:57 +02:00
|
|
|
|
#include "icons.h" // scalable mini-icons (map markers, north arrow, grid dots)
|
2026-06-03 08:28:15 +02:00
|
|
|
|
#include "NavView.h"
|
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
|
|
|
|
#include "WaypointsView.h"
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
#include <helpers/StreamUtils.h>
|
2026-05-25 09:13:52 +02:00
|
|
|
|
#include <math.h>
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Bounds every Serial write the GPX export makes. A full trail dump is tens
|
|
|
|
|
|
// of KB across hundreds of individual write() calls (one or more per point) —
|
|
|
|
|
|
// without this, a serial monitor that's open but not actively reading (DTR
|
|
|
|
|
|
// asserted, nothing draining the USB-CDC FIFO) would block each one, and the
|
|
|
|
|
|
// main loop with it, indefinitely (see StreamUtils.h). Duck-typed to the
|
|
|
|
|
|
// handful of Print methods Trail.h's GPX writers actually call, so it slots
|
|
|
|
|
|
// into their `S& out` template parameter without any change there.
|
|
|
|
|
|
// __FlashStringHelper content is plain flash on nRF52/ARM (no
|
|
|
|
|
|
// Harvard-architecture PROGMEM split), so it's safe to read like any other
|
|
|
|
|
|
// char*.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The first stalled write gives up for the rest of the export (_gave_up),
|
|
|
|
|
|
// rather than re-arming a fresh timeout on every one of those hundreds of
|
|
|
|
|
|
// calls — a host that's truly stuck would otherwise cost calls × timeout_ms
|
|
|
|
|
|
// in total instead of one bounded stall.
|
|
|
|
|
|
struct BoundedSerialPrint {
|
|
|
|
|
|
uint32_t timeout_ms;
|
|
|
|
|
|
bool _gave_up;
|
|
|
|
|
|
BoundedSerialPrint(uint32_t t) : timeout_ms(t), _gave_up(false) {}
|
|
|
|
|
|
size_t write(const uint8_t* buf, size_t len) {
|
|
|
|
|
|
if (_gave_up) return 0;
|
|
|
|
|
|
size_t sent = streamWriteUntil(Serial, buf, len, millis() + timeout_ms);
|
|
|
|
|
|
if (sent < len) _gave_up = true;
|
|
|
|
|
|
return sent;
|
|
|
|
|
|
}
|
|
|
|
|
|
size_t print(const char* s) { return write((const uint8_t*)s, strlen(s)); }
|
|
|
|
|
|
size_t print(const __FlashStringHelper* s) {
|
|
|
|
|
|
const char* p = reinterpret_cast<const char*>(s);
|
|
|
|
|
|
return write((const uint8_t*)p, strlen(p));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-25 09:10:40 +02:00
|
|
|
|
class TrailScreen : public UIScreen {
|
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
TrailStore* _store;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
enum View { V_SUMMARY = 0, V_MAP = 1, V_LIST = 2, V_COUNT };
|
|
|
|
|
|
uint8_t _view = V_SUMMARY;
|
|
|
|
|
|
int _summary_scroll = 0;
|
|
|
|
|
|
int _list_scroll = 0;
|
2026-06-18 09:16:57 +02:00
|
|
|
|
// Top-of-scroll bound for each view, recomputed every render() from the
|
|
|
|
|
|
// live display height (mirrors how _scroll itself is reclamped) — lets
|
|
|
|
|
|
// handleInput() wrap top<->bottom without duplicating the layout math.
|
|
|
|
|
|
int _summary_max_scroll = 0;
|
|
|
|
|
|
int _list_max_scroll = 0;
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
bool _cfg_dirty = false;
|
2026-05-27 17:33:20 +02:00
|
|
|
|
bool _map_grid = true; // show scale grid in map view (Enter to toggle)
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
bool _return_home = false; // entered via Home "Map" page → KEY_CANCEL returns to Home
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
|
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
|
|
|
|
// Waypoint management UI (list / nav / add / mark / rename / delete / send)
|
|
|
|
|
|
// lives in its own component; TrailScreen delegates to it while it's active.
|
|
|
|
|
|
WaypointsView _wp;
|
2026-06-04 13:39:09 +02:00
|
|
|
|
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
// Action popup (Hold Enter / KEY_CONTEXT_MENU). Carries both settings rows
|
|
|
|
|
|
// (Min dist, Units — cycled with LEFT/RIGHT while focused) and actions
|
|
|
|
|
|
// (Start/Stop tracking, Reset). PopupMenu stores label pointers verbatim, so
|
|
|
|
|
|
// the labels live in member buffers that get refreshed in openActionMenu()
|
|
|
|
|
|
// and after every LEFT/RIGHT cycle.
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
enum ActionId { ACT_MIN_DIST, ACT_UNITS, ACT_GRID, ACT_AUTOPAUSE, ACT_TOGGLE, ACT_SAVE, ACT_LOAD, ACT_RESET, ACT_EXPORT, ACT_EXPORT_SAVED, ACT_MARK, ACT_WAYPOINTS, ACT_FILE, ACT_SETTINGS,
|
|
|
|
|
|
ACT_SHARE_NOW };
|
|
|
|
|
|
// The action popup is multi-level: a short main menu, plus "Trail file…" and
|
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
|
|
|
|
// "Settings…" submenus. _menu_level tracks which is open so input is routed
|
|
|
|
|
|
// correctly (settings rows cycle with LEFT/RIGHT; everything else is Enter).
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Live-share *config* lives in its own tool (Tools › Live Share); the map only
|
|
|
|
|
|
// keeps the one-shot "Share my pos" action.
|
2026-06-26 14:11:48 +02:00
|
|
|
|
enum MenuLevel { ML_MAIN, ML_FILE, ML_SETTINGS, ML_CONFIRM_GPS };
|
2026-05-25 17:29:00 +02:00
|
|
|
|
PopupMenu _action_menu;
|
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
|
|
|
|
uint8_t _menu_level = ML_MAIN;
|
|
|
|
|
|
uint8_t _act_map[16]; // max rows on any one level; pushAction guards the cap
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
uint8_t _act_count = 0;
|
|
|
|
|
|
char _act_min_dist_label[24];
|
|
|
|
|
|
char _act_units_label[24];
|
2026-05-27 17:33:20 +02:00
|
|
|
|
char _act_grid_label[16];
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
char _act_autopause_label[24];
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
char _act_toggle_label[20];
|
2026-05-25 17:29:00 +02:00
|
|
|
|
|
2026-05-25 09:23:06 +02:00
|
|
|
|
static const int SUMMARY_ITEM_COUNT = 5;
|
2026-05-25 09:13:52 +02:00
|
|
|
|
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
public:
|
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
|
|
|
|
TrailScreen(UITask* task, TrailStore* store)
|
|
|
|
|
|
: _task(task), _store(store), _wp(task, store) {}
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-06-29 18:37:04 +02:00
|
|
|
|
void onShow() override {
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
_view = V_SUMMARY;
|
2026-05-25 13:08:27 +02:00
|
|
|
|
_summary_scroll = 0;
|
|
|
|
|
|
_list_scroll = 0;
|
|
|
|
|
|
_cfg_dirty = false;
|
2026-05-25 17:29:00 +02:00
|
|
|
|
_action_menu.active = false;
|
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
|
|
|
|
_menu_level = ML_MAIN;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
_return_home = false;
|
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
|
|
|
|
_wp.reset();
|
2026-05-25 13:08:27 +02:00
|
|
|
|
}
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-06-29 18:37:04 +02:00
|
|
|
|
// Switch straight into the Map view (used by the Home "Map" page). Layered on
|
|
|
|
|
|
// top of onShow()'s reset by gotoMapScreen(); remembers we came from Home so
|
|
|
|
|
|
// KEY_CANCEL returns there, not to Tools.
|
|
|
|
|
|
void showMapView() { _view = V_MAP; _return_home = true; }
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-25 13:08:27 +02:00
|
|
|
|
|
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
|
|
|
|
// Waypoint management UI owns the screen while active.
|
|
|
|
|
|
if (_wp.active()) return _wp.render(display);
|
2026-06-03 08:28:15 +02:00
|
|
|
|
|
2026-05-25 17:58:56 +02:00
|
|
|
|
// Title carries the view counter so the bottom hint row can be reclaimed
|
|
|
|
|
|
// for content.
|
2026-05-27 17:33:20 +02:00
|
|
|
|
const char* base = (_view == V_MAP) ? (_map_grid ? "TRAIL MAP+" : "TRAIL MAP")
|
2026-05-25 17:58:56 +02:00
|
|
|
|
: (_view == V_LIST) ? "TRAIL LIST"
|
|
|
|
|
|
: "TRAIL";
|
|
|
|
|
|
char title[20];
|
|
|
|
|
|
snprintf(title, sizeof(title), "%s %d/%d", base, (int)_view + 1, (int)V_COUNT);
|
refactor(ui): shared drawList + header + key-decode helpers
- drawList(): scrollable list skeleton (visible window, keep-sel-in-view,
scroll-column reserve, per-row callback, indicator). Migrate Tools, Bot,
Nearby, Waypoints list and the three QuickMsg lists onto it; centralises the
scroll-clamp/reserve/indicator that each had open-coded (and inconsistently).
- drawCenteredHeader(): centred title + separator, replacing the duplicated
two-liner across ~10 screens (incl. QuickMsg, which used width()/2 spacing and
was missed before); unifies the separator at headerH()-sepH().
- keyIsPrev()/keyIsNext() in UIScreen.h for the LEFT||PREV / RIGHT||NEXT idiom;
applied to 6 screens, behaviour-preserving. Cancel/context-menu stay
per-screen (they mean different things on different screens).
- rotLabel() dedups the 0/90/180/270 array in Settings.
- Unify selection-row height to lineStep()-1 (Tools/Bot matched the rest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00
|
|
|
|
display.drawCenteredHeader(title);
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
if (_view == V_MAP) renderMap(display);
|
|
|
|
|
|
else if (_view == V_LIST) renderList(display);
|
|
|
|
|
|
else renderSummary(display);
|
2026-05-25 09:13:52 +02:00
|
|
|
|
|
2026-05-25 17:29:00 +02:00
|
|
|
|
if (_action_menu.active) _action_menu.render(display);
|
2026-05-25 12:51:37 +02:00
|
|
|
|
return _store->isActive() ? 1000 : 5000;
|
2026-05-25 09:13:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool handleInput(char c) override {
|
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
|
|
|
|
// Waypoint management UI consumes all input while active.
|
|
|
|
|
|
if (_wp.active()) return _wp.handleInput(c);
|
2026-06-03 08:28:15 +02:00
|
|
|
|
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
// Action popup overrides everything else.
|
2026-05-25 17:29:00 +02:00
|
|
|
|
if (_action_menu.active) {
|
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
|
|
|
|
// LEFT/RIGHT cycles the focused value — only meaningful in the Settings
|
|
|
|
|
|
// submenu; the popup stays open so the user can keep tapping.
|
2026-06-29 18:01:29 +02:00
|
|
|
|
if (keyIsPrev(c) || keyIsNext(c)) {
|
|
|
|
|
|
int dir = keyIsNext(c) ? 1 : -1;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
int idx = _action_menu.selectedIndex();
|
|
|
|
|
|
if (idx >= 0 && idx < _act_count && _menu_level == ML_SETTINGS)
|
|
|
|
|
|
cycleSetting((ActionId)_act_map[idx], dir);
|
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
|
|
|
|
return true; // swallow elsewhere
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
}
|
2026-05-25 17:29:00 +02:00
|
|
|
|
auto res = _action_menu.handleInput(c);
|
|
|
|
|
|
if (res == PopupMenu::SELECTED) {
|
2026-06-26 14:11:48 +02:00
|
|
|
|
// GPS-off confirmation popup: rows aren't ActionIds, route by level.
|
|
|
|
|
|
if (_menu_level == ML_CONFIRM_GPS) {
|
|
|
|
|
|
if (_action_menu.selectedIndex() == 0) { // "Enable GPS & start"
|
|
|
|
|
|
_task->toggleGPS(); // off → on (persists, shows GPS alert)
|
|
|
|
|
|
_store->setActive(true);
|
|
|
|
|
|
_task->showAlert("GPS on, tracking started", 1200);
|
|
|
|
|
|
}
|
|
|
|
|
|
_menu_level = ML_MAIN; // popup already closed by handleInput()
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
int sel = _action_menu.selectedIndex();
|
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
|
|
|
|
ActionId act = (sel >= 0 && sel < _act_count) ? (ActionId)_act_map[sel] : ACT_TOGGLE;
|
|
|
|
|
|
switch (act) {
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
case ACT_FILE: buildFileMenu(); return true; // descend into submenu
|
|
|
|
|
|
case ACT_SETTINGS: buildSettingsMenu(); return true;
|
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
|
|
|
|
// Settings rows: Enter advances/toggles the value and keeps focus.
|
|
|
|
|
|
case ACT_MIN_DIST:
|
|
|
|
|
|
case ACT_UNITS:
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
case ACT_GRID:
|
|
|
|
|
|
case ACT_AUTOPAUSE: cycleSetting(act, 1); reopenSettingsAt(sel); return true;
|
|
|
|
|
|
case ACT_SHARE_NOW: shareMyLocationNow(); break;
|
2026-06-26 14:11:48 +02:00
|
|
|
|
case ACT_TOGGLE:
|
|
|
|
|
|
// Starting a trail with GPS switched off logs nothing and just
|
|
|
|
|
|
// sits on "Waiting for GPS fix" forever -- prompt to enable it
|
|
|
|
|
|
// first (only on boards that actually have a toggleable GPS).
|
|
|
|
|
|
if (!_store->isActive() && _task->hasGPS() && !_task->getGPSState()) {
|
|
|
|
|
|
buildGpsConfirmMenu();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
handleToggle();
|
|
|
|
|
|
break;
|
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
|
|
|
|
case ACT_MARK: _wp.markHere(); break;
|
|
|
|
|
|
case ACT_WAYPOINTS: _wp.openList(); break;
|
|
|
|
|
|
case ACT_SAVE: handleSave(); break;
|
|
|
|
|
|
case ACT_LOAD: handleLoad(); break;
|
|
|
|
|
|
case ACT_RESET: handleReset(); break;
|
|
|
|
|
|
case ACT_EXPORT: handleExport(); break;
|
|
|
|
|
|
case ACT_EXPORT_SAVED: handleExportSaved(); break;
|
2026-05-25 17:29:00 +02:00
|
|
|
|
}
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
if (_cfg_dirty) { the_mesh.savePrefs(); _cfg_dirty = false; }
|
|
|
|
|
|
} else if (res == PopupMenu::CANCELLED) {
|
|
|
|
|
|
if (_cfg_dirty) { the_mesh.savePrefs(); _cfg_dirty = false; }
|
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
|
|
|
|
if (_menu_level != ML_MAIN) buildMainMenu(); // Cancel backs out of a submenu
|
2026-05-25 17:29:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (c == KEY_CANCEL) {
|
2026-05-25 13:08:27 +02:00
|
|
|
|
if (_cfg_dirty) { the_mesh.savePrefs(); _cfg_dirty = false; }
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
if (_return_home) _task->gotoHomeScreen();
|
|
|
|
|
|
else _task->gotoToolsScreen();
|
2026-05-25 13:08:27 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
if (c == KEY_CONTEXT_MENU) { openActionMenu(); return true; }
|
2026-05-25 13:08:27 +02:00
|
|
|
|
|
2026-06-29 18:01:29 +02:00
|
|
|
|
if (keyIsPrev(c)) { _view = (uint8_t)((_view + V_COUNT - 1) % V_COUNT); return true; }
|
|
|
|
|
|
if (keyIsNext(c)) { _view = (uint8_t)((_view + 1) % V_COUNT); return true; }
|
2026-06-18 09:16:57 +02:00
|
|
|
|
if (_view == V_SUMMARY && c == KEY_UP) {
|
|
|
|
|
|
_summary_scroll = (_summary_scroll > 0) ? _summary_scroll - 1 : _summary_max_scroll;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_view == V_SUMMARY && c == KEY_DOWN) {
|
|
|
|
|
|
_summary_scroll = (_summary_scroll < _summary_max_scroll) ? _summary_scroll + 1 : 0;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_view == V_LIST && c == KEY_UP) {
|
|
|
|
|
|
_list_scroll = (_list_scroll > 0) ? _list_scroll - 1 : _list_max_scroll;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_view == V_LIST && c == KEY_DOWN) {
|
|
|
|
|
|
_list_scroll = (_list_scroll < _list_max_scroll) ? _list_scroll + 1 : 0;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-25 09:13:52 +02:00
|
|
|
|
if (c == KEY_ENTER) {
|
2026-05-25 17:58:56 +02:00
|
|
|
|
// Short Enter intentionally does nothing destructive — both start and
|
|
|
|
|
|
// stop go through the Hold-Enter popup so a stray tap can't change the
|
|
|
|
|
|
// tracking state.
|
|
|
|
|
|
_task->showAlert("Hold Enter for menu", 1000);
|
2026-05-25 09:13:52 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 00:16:28 +02:00
|
|
|
|
// True when there's a usable GPS fix (same source as ownPos).
|
|
|
|
|
|
bool gpsHasFix() const { int32_t lat, lon; return ownPos(lat, lon); }
|
2026-05-25 10:26:33 +02:00
|
|
|
|
|
2026-05-25 09:13:52 +02:00
|
|
|
|
private:
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
void handleToggle() {
|
|
|
|
|
|
bool was = _store->isActive();
|
|
|
|
|
|
_store->setActive(!was);
|
|
|
|
|
|
_task->showAlert(was ? "Tracking stopped"
|
|
|
|
|
|
: (gpsHasFix() ? "Tracking started" : "Waiting for GPS fix"),
|
|
|
|
|
|
was ? 800 : 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
void handleReset() {
|
|
|
|
|
|
if (_store->isActive()) _store->setActive(false);
|
|
|
|
|
|
_store->clear();
|
|
|
|
|
|
_task->showAlert("Trail reset", 800);
|
|
|
|
|
|
}
|
2026-05-26 07:49:17 +02:00
|
|
|
|
void handleSave() {
|
|
|
|
|
|
DataStore* ds = the_mesh.getDataStore();
|
|
|
|
|
|
if (!ds) { _task->showAlert("FS unavailable", 800); return; }
|
|
|
|
|
|
File f = ds->openWrite(TRAIL_FILE);
|
|
|
|
|
|
if (!f) { _task->showAlert("Save failed", 800); return; }
|
|
|
|
|
|
bool ok = _store->writeTo(f);
|
|
|
|
|
|
f.close();
|
|
|
|
|
|
_task->showAlert(ok ? "Trail saved" : "Save failed", 800);
|
|
|
|
|
|
}
|
|
|
|
|
|
void handleLoad() {
|
|
|
|
|
|
DataStore* ds = the_mesh.getDataStore();
|
|
|
|
|
|
if (!ds) { _task->showAlert("FS unavailable", 800); return; }
|
|
|
|
|
|
File f = ds->openRead(TRAIL_FILE);
|
|
|
|
|
|
if (!f) { _task->showAlert("No saved trail", 800); return; }
|
|
|
|
|
|
bool ok = _store->readFrom(f);
|
|
|
|
|
|
f.close();
|
|
|
|
|
|
_task->showAlert(ok ? "Trail loaded" : "Load failed", 800);
|
|
|
|
|
|
}
|
|
|
|
|
|
void handleExport() {
|
2026-05-26 07:54:53 +02:00
|
|
|
|
if (!Serial) { _task->showAlert("Connect USB first", 1200); return; }
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
BoundedSerialPrint out{300};
|
|
|
|
|
|
size_t n = _store->exportGpx(out, _task->waypoints());
|
2026-05-26 08:02:16 +02:00
|
|
|
|
showExportAlert(n);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void handleExportSaved() {
|
|
|
|
|
|
if (!Serial) { _task->showAlert("Connect USB first", 1200); return; }
|
|
|
|
|
|
DataStore* ds = the_mesh.getDataStore();
|
|
|
|
|
|
if (!ds) { _task->showAlert("FS unavailable", 800); return; }
|
|
|
|
|
|
File f = ds->openRead(TRAIL_FILE);
|
|
|
|
|
|
if (!f) { _task->showAlert("No saved trail", 800); return; }
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
BoundedSerialPrint out{300};
|
|
|
|
|
|
size_t n = TrailStore::exportGpxFromFile(f, out, _task->waypoints());
|
2026-05-26 08:02:16 +02:00
|
|
|
|
f.close();
|
|
|
|
|
|
if (n == 0) { _task->showAlert("Bad saved file", 1000); return; }
|
|
|
|
|
|
showExportAlert(n);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void showExportAlert(size_t n) {
|
2026-05-26 07:54:53 +02:00
|
|
|
|
// The companion app multiplexes BLE + USB on the same frame protocol.
|
2026-05-26 07:55:56 +02:00
|
|
|
|
// When BLE is connected, USB-receive is ignored and the dump can't
|
2026-05-26 08:02:16 +02:00
|
|
|
|
// collide. Without a BLE link the app might be on USB itself, so warn
|
|
|
|
|
|
// the user — they may need to reconnect their app afterwards.
|
2026-05-26 07:55:56 +02:00
|
|
|
|
char alert[28];
|
2026-05-26 08:02:16 +02:00
|
|
|
|
if (_task->hasConnection()) snprintf(alert, sizeof(alert), "GPX %u B (USB)", (unsigned)n);
|
|
|
|
|
|
else snprintf(alert, sizeof(alert), "GPX %u B - disc. app", (unsigned)n);
|
2026-05-26 07:54:53 +02:00
|
|
|
|
_task->showAlert(alert, 1500);
|
2026-05-26 07:49:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static constexpr const char* TRAIL_FILE = "/trail";
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
|
|
|
|
|
|
// Refresh the three settings/action labels in-place. Pointer identity is
|
|
|
|
|
|
// preserved, so PopupMenu picks up the new text on the next render.
|
|
|
|
|
|
void refreshActionLabels() {
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
snprintf(_act_min_dist_label, sizeof(_act_min_dist_label),
|
2026-06-04 00:50:42 +02:00
|
|
|
|
"Min dist: %s", TrailStore::minDeltaLabel(p ? p->trail_min_delta_idx : 0,
|
|
|
|
|
|
p && p->units_imperial));
|
2026-06-04 00:44:56 +02:00
|
|
|
|
// Unit system (km/mi) is the global Settings choice; here we only toggle
|
|
|
|
|
|
// what the Summary readout shows — speed or pace.
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
snprintf(_act_units_label, sizeof(_act_units_label),
|
2026-06-04 00:44:56 +02:00
|
|
|
|
"Readout: %s", (p && p->trail_show_pace) ? "Pace" : "Speed");
|
2026-05-27 17:33:20 +02:00
|
|
|
|
snprintf(_act_grid_label, sizeof(_act_grid_label),
|
|
|
|
|
|
"Grid: %s", _map_grid ? "ON" : "OFF");
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
snprintf(_act_autopause_label, sizeof(_act_autopause_label),
|
|
|
|
|
|
"Auto-pause: %s", NodePrefs::trailAutoPauseLabel(p ? p->trail_autopause_idx : 0));
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
snprintf(_act_toggle_label, sizeof(_act_toggle_label),
|
|
|
|
|
|
"%s tracking", _store->isActive() ? "Stop" : "Start");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 13:37:03 +02:00
|
|
|
|
// Append an action row, guarding both the id map and the popup capacity so
|
|
|
|
|
|
// adding a new action can never silently overrun _act_map / PopupMenu.
|
|
|
|
|
|
void pushAction(ActionId id, const char* label) {
|
|
|
|
|
|
if (_act_count >= (int)sizeof(_act_map)) return;
|
|
|
|
|
|
_act_map[_act_count++] = (uint8_t)id;
|
|
|
|
|
|
_action_menu.addItem(label);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
bool fileMenuHasItems() const { return !_store->empty() || savedTrailExists(); }
|
|
|
|
|
|
|
|
|
|
|
|
// Hold-Enter entry point — always opens the short main menu.
|
|
|
|
|
|
void openActionMenu() { buildMainMenu(); }
|
|
|
|
|
|
|
|
|
|
|
|
void buildMainMenu() {
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
refreshActionLabels();
|
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
|
|
|
|
_menu_level = ML_MAIN;
|
|
|
|
|
|
_act_count = 0;
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
_action_menu.begin("Trail", 4);
|
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
|
|
|
|
pushAction(ACT_TOGGLE, _act_toggle_label);
|
2026-06-14 13:37:03 +02:00
|
|
|
|
pushAction(ACT_MARK, "Mark here");
|
2026-06-04 13:43:01 +02:00
|
|
|
|
// "Waypoints" opens the nav list — always available; it hosts the list,
|
|
|
|
|
|
// backtrack (Trail-start row) and the "+ Add by coords" entry.
|
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
|
|
|
|
pushAction(ACT_WAYPOINTS, "Waypoints...");
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
pushAction(ACT_SHARE_NOW, "Share my pos");
|
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
|
|
|
|
if (fileMenuHasItems()) pushAction(ACT_FILE, "Trail file...");
|
|
|
|
|
|
pushAction(ACT_SETTINGS, "Settings...");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 14:11:48 +02:00
|
|
|
|
// Confirmation shown when "Start tracking" is chosen with GPS off. Rows are
|
|
|
|
|
|
// plain (not ActionIds); handled by _menu_level in the SELECTED branch.
|
|
|
|
|
|
void buildGpsConfirmMenu() {
|
|
|
|
|
|
_menu_level = ML_CONFIRM_GPS;
|
|
|
|
|
|
_act_count = 0;
|
|
|
|
|
|
_action_menu.begin("GPS is off", 2);
|
|
|
|
|
|
_action_menu.addItem("Enable GPS & start");
|
|
|
|
|
|
_action_menu.addItem("Cancel");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
// Trail-file submenu — only the operations that make sense right now.
|
|
|
|
|
|
void buildFileMenu() {
|
|
|
|
|
|
_menu_level = ML_FILE;
|
|
|
|
|
|
_act_count = 0;
|
|
|
|
|
|
_action_menu.begin("Trail file", 4);
|
2026-05-26 08:02:16 +02:00
|
|
|
|
bool saved = savedTrailExists();
|
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
|
|
|
|
if (!_store->empty()) pushAction(ACT_SAVE, "Save trail");
|
|
|
|
|
|
if (saved) pushAction(ACT_LOAD, "Load trail");
|
|
|
|
|
|
if (!_store->empty()) pushAction(ACT_EXPORT, "Export (live)");
|
|
|
|
|
|
if (saved) pushAction(ACT_EXPORT_SAVED, "Export (saved)");
|
|
|
|
|
|
if (!_store->empty()) pushAction(ACT_RESET, "Reset trail");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Settings submenu — values cycled with LEFT/RIGHT (or Enter). View-aware:
|
|
|
|
|
|
// Grid only matters on the Map, Readout only on the Summary.
|
|
|
|
|
|
void buildSettingsMenu() {
|
|
|
|
|
|
refreshActionLabels();
|
|
|
|
|
|
_menu_level = ML_SETTINGS;
|
|
|
|
|
|
_act_count = 0;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
_action_menu.begin("Settings", 4);
|
|
|
|
|
|
pushAction(ACT_MIN_DIST, _act_min_dist_label);
|
|
|
|
|
|
pushAction(ACT_AUTOPAUSE, _act_autopause_label);
|
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
|
|
|
|
if (_view == V_SUMMARY) pushAction(ACT_UNITS, _act_units_label);
|
|
|
|
|
|
if (_view == V_MAP) pushAction(ACT_GRID, _act_grid_label);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Cycle a settings value. Returns true if `act` was a settings row.
|
|
|
|
|
|
bool cycleSetting(ActionId act, int dir) {
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
if (act == ACT_MIN_DIST && p) { cycleMinDelta(p, dir); _cfg_dirty = true; refreshActionLabels(); return true; }
|
|
|
|
|
|
if (act == ACT_UNITS && p) { cycleUnits(p, dir); _cfg_dirty = true; refreshActionLabels(); return true; }
|
|
|
|
|
|
if (act == ACT_GRID) { _map_grid = !_map_grid; refreshActionLabels(); return true; }
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
if (act == ACT_AUTOPAUSE && p){ cycleAutoPause(p, dir); _cfg_dirty = true; refreshActionLabels(); return true; }
|
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
|
|
|
|
return false;
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 07:49:17 +02:00
|
|
|
|
static bool savedTrailExists() {
|
|
|
|
|
|
DataStore* ds = the_mesh.getDataStore();
|
|
|
|
|
|
if (!ds) return false;
|
|
|
|
|
|
File f = ds->openRead(TRAIL_FILE);
|
|
|
|
|
|
bool ok = (bool)f;
|
|
|
|
|
|
if (f) f.close();
|
|
|
|
|
|
return ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
// GPS / unit helpers shared by the trail views and the map. (Waypoint list /
|
|
|
|
|
|
// navigation / add / mark / rename / delete / send moved to WaypointsView.)
|
2026-06-04 00:12:15 +02:00
|
|
|
|
bool ownPos(int32_t& lat, int32_t& lon) const { return _task->currentLocation(lat, lon); }
|
2026-06-14 13:21:59 +02:00
|
|
|
|
bool useImperial() const { return _task && _task->useImperial(); }
|
2026-06-04 00:44:56 +02:00
|
|
|
|
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
// After Enter on a settings row, the popup auto-closes per PopupMenu's
|
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
|
|
|
|
// semantics. Re-open the Settings submenu with focus restored to that row so
|
|
|
|
|
|
// the user can continue cycling.
|
|
|
|
|
|
void reopenSettingsAt(int sel) {
|
|
|
|
|
|
buildSettingsMenu();
|
2026-06-14 13:37:03 +02:00
|
|
|
|
_action_menu.setSelected(sel);
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cycleMinDelta(NodePrefs* p, int dir) {
|
|
|
|
|
|
uint8_t idx = p->trail_min_delta_idx;
|
|
|
|
|
|
if (idx >= TrailStore::MIN_DELTA_COUNT) idx = 0;
|
|
|
|
|
|
idx = (dir > 0) ? (idx + 1) % TrailStore::MIN_DELTA_COUNT
|
|
|
|
|
|
: (idx + TrailStore::MIN_DELTA_COUNT - 1) % TrailStore::MIN_DELTA_COUNT;
|
|
|
|
|
|
p->trail_min_delta_idx = idx;
|
|
|
|
|
|
}
|
2026-06-04 00:44:56 +02:00
|
|
|
|
// The trail "Readout" row toggles speed vs pace; the metric/imperial unit
|
|
|
|
|
|
// comes from the global Settings preference, so this is a plain on/off flip.
|
|
|
|
|
|
void cycleUnits(NodePrefs* p, int /*dir*/) { p->trail_show_pace ^= 1; }
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
void cycleAutoPause(NodePrefs* p, int dir) {
|
|
|
|
|
|
uint8_t idx = p->trail_autopause_idx;
|
|
|
|
|
|
if (idx >= NodePrefs::TRAIL_AUTOPAUSE_COUNT) idx = 0;
|
|
|
|
|
|
idx = (dir > 0) ? (idx + 1) % NodePrefs::TRAIL_AUTOPAUSE_COUNT
|
|
|
|
|
|
: (idx + NodePrefs::TRAIL_AUTOPAUSE_COUNT - 1) % NodePrefs::TRAIL_AUTOPAUSE_COUNT;
|
|
|
|
|
|
p->trail_autopause_idx = idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// One-shot manual share: build "[LOC]lat,lon" and hand it to the Messages
|
|
|
|
|
|
// screen, where the user picks a DM or channel recipient. (Auto live-share
|
|
|
|
|
|
// config lives in Tools › Live Share.)
|
|
|
|
|
|
void shareMyLocationNow() {
|
|
|
|
|
|
int32_t lat, lon;
|
|
|
|
|
|
if (!_task->currentLocation(lat, lon)) { _task->showAlert("No GPS fix", 1000); return; }
|
|
|
|
|
|
char text[40];
|
|
|
|
|
|
snprintf(text, sizeof(text), LOCATION_MSG_TAG "%.5f,%.5f", lat / 1e6, lon / 1e6);
|
|
|
|
|
|
_task->shareToMessage(text);
|
|
|
|
|
|
}
|
2026-05-25 13:08:27 +02:00
|
|
|
|
|
2026-06-04 00:44:56 +02:00
|
|
|
|
// Render the average speed (or pace) in the globally-selected unit system.
|
2026-05-25 13:16:02 +02:00
|
|
|
|
void formatAvgPaceOrSpeed(char* buf, size_t n, NodePrefs* p) const {
|
2026-06-04 00:44:56 +02:00
|
|
|
|
bool imperial = p && p->units_imperial;
|
|
|
|
|
|
bool pace = p && p->trail_show_pace;
|
|
|
|
|
|
uint16_t kmh = _store->avgSpeedKmh();
|
|
|
|
|
|
if (!pace) {
|
|
|
|
|
|
if (imperial) snprintf(buf, n, "Avg: %u mph", (unsigned)((float)kmh * 0.621371f + 0.5f));
|
|
|
|
|
|
else snprintf(buf, n, "Avg: %u km/h", (unsigned)kmh);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t d_m = _store->totalDistanceMeters();
|
|
|
|
|
|
uint32_t es_s = _store->elapsedSeconds();
|
|
|
|
|
|
const char* unit = imperial ? "/mi" : "/km";
|
|
|
|
|
|
float dist_unit = imperial ? (d_m / 1609.344f) : (d_m / 1000.0f);
|
|
|
|
|
|
if (d_m == 0 || es_s == 0 || dist_unit <= 0) {
|
|
|
|
|
|
snprintf(buf, n, "Pace: --:-- %s", unit);
|
|
|
|
|
|
return;
|
2026-05-25 13:16:02 +02:00
|
|
|
|
}
|
2026-06-04 00:44:56 +02:00
|
|
|
|
float pace_min = (es_s / 60.0f) / dist_unit;
|
|
|
|
|
|
unsigned pm = (unsigned)pace_min;
|
|
|
|
|
|
unsigned ps = (unsigned)((pace_min - pm) * 60.0f);
|
|
|
|
|
|
snprintf(buf, n, "Pace: %u:%02u %s", pm, ps, unit);
|
2026-05-25 13:16:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 09:23:06 +02:00
|
|
|
|
void summaryItem(int i, char* buf, size_t n) const {
|
|
|
|
|
|
switch (i) {
|
2026-05-25 10:26:33 +02:00
|
|
|
|
case 0: {
|
|
|
|
|
|
const char* st;
|
|
|
|
|
|
if (!_store->isActive()) st = "stopped";
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
else if (_store->isPaused()) st = "paused";
|
2026-05-25 10:26:33 +02:00
|
|
|
|
else if (_store->empty()) st = "waiting fix";
|
|
|
|
|
|
else st = "tracking";
|
|
|
|
|
|
snprintf(buf, n, "Status: %s", st);
|
2026-05-25 09:23:06 +02:00
|
|
|
|
break;
|
2026-05-25 10:26:33 +02:00
|
|
|
|
}
|
2026-05-25 09:23:06 +02:00
|
|
|
|
case 1:
|
|
|
|
|
|
snprintf(buf, n, "Points: %d / %d", _store->count(), TrailStore::CAPACITY);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 2: {
|
|
|
|
|
|
uint32_t d = _store->totalDistanceMeters();
|
2026-06-04 00:44:56 +02:00
|
|
|
|
char ds[12];
|
|
|
|
|
|
geo::fmtDist(ds, sizeof(ds), d / 1000.0f, useImperial());
|
|
|
|
|
|
snprintf(buf, n, "Dist: %s", ds);
|
2026-05-25 09:23:06 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
case 3: {
|
2026-05-25 12:51:37 +02:00
|
|
|
|
uint32_t es = _store->elapsedSeconds();
|
2026-06-14 13:37:03 +02:00
|
|
|
|
// Unit-suffixed so "1h 05m" can't be misread as "1m 05s".
|
|
|
|
|
|
if (es < 3600) snprintf(buf, n, "Time: %lum %02lus",
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
(unsigned long)(es / 60), (unsigned long)(es % 60));
|
2026-06-14 13:37:03 +02:00
|
|
|
|
else snprintf(buf, n, "Time: %luh %02lum",
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
(unsigned long)(es / 3600), (unsigned long)((es % 3600) / 60));
|
2026-05-25 09:23:06 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
case 4:
|
2026-05-25 13:16:02 +02:00
|
|
|
|
formatAvgPaceOrSpeed(buf, n, _task->getNodePrefs());
|
2026-05-25 09:23:06 +02:00
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 09:13:52 +02:00
|
|
|
|
void renderSummary(DisplayDriver& display) {
|
2026-05-25 09:23:06 +02:00
|
|
|
|
const int y0 = display.listStart();
|
|
|
|
|
|
const int step = display.lineStep();
|
2026-05-25 17:58:56 +02:00
|
|
|
|
const int avail = display.height() - y0 - 2;
|
2026-05-25 09:23:06 +02:00
|
|
|
|
int visible = avail / step;
|
|
|
|
|
|
if (visible < 1) visible = 1;
|
|
|
|
|
|
if (visible > SUMMARY_ITEM_COUNT) visible = SUMMARY_ITEM_COUNT;
|
|
|
|
|
|
|
|
|
|
|
|
int max_scroll = SUMMARY_ITEM_COUNT - visible;
|
|
|
|
|
|
if (_summary_scroll > max_scroll) _summary_scroll = max_scroll;
|
|
|
|
|
|
if (_summary_scroll < 0) _summary_scroll = 0;
|
2026-06-18 09:16:57 +02:00
|
|
|
|
_summary_max_scroll = max_scroll;
|
2026-05-25 09:23:06 +02:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(ui): proportional scrollbar with mini-icon caps; reclaim row space
Replace the text ^/v scroll arrows with a font-scaling indicator drawn in
icons.h: a proportional thumb between fixed up/down triangle mini-icon caps.
drawScrollIndicator() lives in a ~5px right-edge column and scales via
miniIconScale (1x OLED, 2x landscape e-ink).
- Mini-icons: ICON_SCROLL_UP/DOWN; miniIconDrawTop (exact placement) and
miniIconDrawHalo (shape-hugging 1px DARK halo, clip_top to spare the header
separator) so caps stay visible on the LIGHT selection bar without a box.
- Thumb is 3*s wide so it centres on the triangle column; caps are static end
markers (always drawn) so neither vanishes at the extremes.
- scrollIndicatorReserve(): right-edge gutter (0 when the list fits). Content,
the selection bar and the message-history bubbles are all narrowed by it so
nothing renders under the scrollbar. Portrait e-ink wrap width subtracts the
reserve in both the box-height pass and render so wrapped text can't spill.
- Drop the redundant ">" selection marker (the highlight bar already shows
selection) and shift rows to x=2, reclaiming left-edge space.
- Wire the indicator into every scrollable list (Bot, Settings, Nearby, Tools,
Trail, QuickMsg lists + DM/channel history) and add one to WaypointsView,
which scrolled with no indicator before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:41:46 +02:00
|
|
|
|
drawScrollIndicator(display, y0, visible * step, SUMMARY_ITEM_COUNT, visible, _summary_scroll);
|
2026-05-25 09:13:52 +02:00
|
|
|
|
}
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-05-25 10:14:14 +02:00
|
|
|
|
void renderList(DisplayDriver& display) {
|
|
|
|
|
|
const int top = display.listStart();
|
|
|
|
|
|
const int step = display.lineStep();
|
2026-05-25 17:58:56 +02:00
|
|
|
|
const int avail = display.height() - top - 2;
|
2026-05-25 10:14:14 +02:00
|
|
|
|
|
|
|
|
|
|
if (_store->empty()) {
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, top + avail / 2, "No trail yet");
|
2026-06-18 09:16:57 +02:00
|
|
|
|
_list_max_scroll = 0;
|
2026-05-25 10:14:14 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int visible = avail / step;
|
|
|
|
|
|
if (visible < 1) visible = 1;
|
|
|
|
|
|
const int total = _store->count();
|
|
|
|
|
|
if (visible > total) visible = total;
|
|
|
|
|
|
|
|
|
|
|
|
int max_scroll = total - visible;
|
|
|
|
|
|
if (_list_scroll > max_scroll) _list_scroll = max_scroll;
|
|
|
|
|
|
if (_list_scroll < 0) _list_scroll = 0;
|
2026-06-18 09:16:57 +02:00
|
|
|
|
_list_max_scroll = max_scroll;
|
2026-05-25 10:14:14 +02:00
|
|
|
|
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
int32_t tz_off = (int32_t)(p ? p->tz_offset_hours : 0) * 3600;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < visible; i++) {
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
int idx = total - 1 - (_list_scroll + i);
|
2026-05-25 10:14:14 +02:00
|
|
|
|
if (idx < 0) break;
|
|
|
|
|
|
const TrailPoint& pt = _store->at(idx);
|
|
|
|
|
|
|
|
|
|
|
|
time_t lt = (time_t)((int32_t)pt.ts + tz_off);
|
|
|
|
|
|
struct tm* ti = gmtime(<);
|
|
|
|
|
|
|
|
|
|
|
|
char dist[12];
|
|
|
|
|
|
if (idx == 0 || (pt.flags & TRAIL_FLAG_SEG_START)) {
|
|
|
|
|
|
snprintf(dist, sizeof(dist), "start");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const TrailPoint& prev = _store->at(idx - 1);
|
|
|
|
|
|
float d = TrailStore::haversineMeters(prev.lat_1e6, prev.lon_1e6,
|
|
|
|
|
|
pt.lat_1e6, pt.lon_1e6);
|
2026-06-04 00:44:56 +02:00
|
|
|
|
char ds[10];
|
|
|
|
|
|
geo::fmtDist(ds, sizeof(ds), d / 1000.0f, useImperial());
|
|
|
|
|
|
snprintf(dist, sizeof(dist), "+%s", ds);
|
2026-05-25 10:14:14 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
char row[28];
|
|
|
|
|
|
snprintf(row, sizeof(row), "%02d:%02d %s",
|
|
|
|
|
|
ti->tm_hour, ti->tm_min, dist);
|
|
|
|
|
|
display.setCursor(2, top + i * step);
|
|
|
|
|
|
display.print(row);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(ui): proportional scrollbar with mini-icon caps; reclaim row space
Replace the text ^/v scroll arrows with a font-scaling indicator drawn in
icons.h: a proportional thumb between fixed up/down triangle mini-icon caps.
drawScrollIndicator() lives in a ~5px right-edge column and scales via
miniIconScale (1x OLED, 2x landscape e-ink).
- Mini-icons: ICON_SCROLL_UP/DOWN; miniIconDrawTop (exact placement) and
miniIconDrawHalo (shape-hugging 1px DARK halo, clip_top to spare the header
separator) so caps stay visible on the LIGHT selection bar without a box.
- Thumb is 3*s wide so it centres on the triangle column; caps are static end
markers (always drawn) so neither vanishes at the extremes.
- scrollIndicatorReserve(): right-edge gutter (0 when the list fits). Content,
the selection bar and the message-history bubbles are all narrowed by it so
nothing renders under the scrollbar. Portrait e-ink wrap width subtracts the
reserve in both the box-height pass and render so wrapped text can't spill.
- Drop the redundant ">" selection marker (the highlight bar already shows
selection) and shift rows to x=2, reclaiming left-edge space.
- Wire the indicator into every scrollable list (Bot, Settings, Nearby, Tools,
Trail, QuickMsg lists + DM/channel history) and add one to WaypointsView,
which scrolled with no indicator before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:41:46 +02:00
|
|
|
|
drawScrollIndicator(display, top, visible * step, total, visible, _list_scroll);
|
2026-05-25 10:14:14 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
// Shared map projection: geographic (1e-6 deg) → screen pixels. The scale is
|
|
|
|
|
|
// isotropic (cos(lat) folded into lon), so a metre maps to the same pixel
|
|
|
|
|
|
// count on both axes. Built once in renderMap and handed to renderGrid and
|
|
|
|
|
|
// the marker drawing so the projection equation lives in exactly one place.
|
|
|
|
|
|
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 {
|
|
|
|
|
|
px = off_x + (int)((float)(lon - min_lon) * lon_scale_geo * scale);
|
|
|
|
|
|
py = off_y + (int)((float)(max_lat - lat) * scale);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Fold the trail / waypoints / live position into a bounding box. With a trail,
|
|
|
|
|
|
// only the route + live position define the box (far waypoints clamp to the
|
|
|
|
|
|
// edge when drawn); without one, the waypoints do. Returns false if empty.
|
|
|
|
|
|
bool computeBounds(bool have_trail, bool have_gps, int32_t my_lat, int32_t my_lon,
|
|
|
|
|
|
int32_t& min_lat, int32_t& min_lon,
|
|
|
|
|
|
int32_t& max_lat, int32_t& max_lon) const {
|
|
|
|
|
|
bool init = false;
|
|
|
|
|
|
auto fold = [&](int32_t lat, int32_t lon) {
|
|
|
|
|
|
if (!init) { min_lat = max_lat = lat; min_lon = max_lon = lon; init = true; }
|
|
|
|
|
|
else {
|
|
|
|
|
|
if (lat < min_lat) min_lat = lat; if (lat > max_lat) max_lat = lat;
|
|
|
|
|
|
if (lon < min_lon) min_lon = lon; if (lon > max_lon) max_lon = lon;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
if (have_trail) {
|
|
|
|
|
|
int32_t a, b, c, d; _store->boundingBox(a, b, c, d);
|
|
|
|
|
|
fold(a, b); fold(c, d);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
WaypointStore& wp = _task->waypoints();
|
|
|
|
|
|
for (int i = 0; i < wp.count(); i++) fold(wp.at(i).lat_1e6, wp.at(i).lon_1e6);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (have_gps) fold(my_lat, my_lon);
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Fold in live-tracked contacts ([LOC] shares) so they stay in frame.
|
|
|
|
|
|
{
|
|
|
|
|
|
LiveTrackStore& lt = _task->liveTrack();
|
|
|
|
|
|
uint32_t now = rtc_clock.getCurrentTime();
|
|
|
|
|
|
for (int i = 0; i < LiveTrackStore::CAPACITY; i++)
|
|
|
|
|
|
if (lt.isActive(i, now)) fold(lt.slotAt(i).lat_1e6, lt.slotAt(i).lon_1e6);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Fold in the active target too, so a destination you set never sits off-frame.
|
|
|
|
|
|
{ int32_t tla, tlo; if (_task->activeTargetPos(tla, tlo)) fold(tla, tlo); }
|
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
|
|
|
|
return init;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Draw the saved waypoints (label's first two chars beside each, clamped to the
|
|
|
|
|
|
// frame) and the current-position marker (live GPS, else last trail point).
|
|
|
|
|
|
void drawMarkers(DisplayDriver& display, const MapProjection& proj,
|
|
|
|
|
|
bool have_gps, int32_t my_lat, int32_t my_lon, bool have_trail) {
|
|
|
|
|
|
const int area_x = proj.area_x, area_y = proj.area_y;
|
|
|
|
|
|
const int area_w = proj.area_w, area_h = proj.area_h;
|
|
|
|
|
|
WaypointStore& wp = _task->waypoints();
|
|
|
|
|
|
const int wp_x_max = area_x + area_w - 1, wp_y_max = area_y + area_h - 1;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Reserved rectangles of labels already placed this frame, so a dense
|
|
|
|
|
|
// cluster of points doesn't smear their labels on top of one another.
|
|
|
|
|
|
// Live-tracked contacts are labelled FIRST so a moving person's name wins a
|
|
|
|
|
|
// slot over a nearby static waypoint (on a live map the person matters most).
|
|
|
|
|
|
LblRect occ[40]; int nocc = 0;
|
|
|
|
|
|
{
|
|
|
|
|
|
LiveTrackStore& lt = _task->liveTrack();
|
|
|
|
|
|
uint32_t now = rtc_clock.getCurrentTime();
|
|
|
|
|
|
for (int i = 0; i < LiveTrackStore::CAPACITY; i++) {
|
|
|
|
|
|
if (!lt.isActive(i, now)) continue;
|
|
|
|
|
|
const LiveTrackStore::Entry& s = lt.slotAt(i);
|
|
|
|
|
|
int cx, cy; proj.project(s.lat_1e6, s.lon_1e6, cx, cy);
|
|
|
|
|
|
if (cx < area_x) cx = area_x; else if (cx > wp_x_max) cx = wp_x_max;
|
|
|
|
|
|
if (cy < area_y) cy = area_y; else if (cy > wp_y_max) cy = wp_y_max;
|
|
|
|
|
|
drawContactMarker(display, cx, cy);
|
|
|
|
|
|
char s2[3] = { s.name[0], s.name[0] ? s.name[1] : (char)0, 0 };
|
|
|
|
|
|
drawLabelAvoiding(display, s2, cx, cy, area_x, area_y, area_w, area_h, occ, nocc, 40);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
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
|
|
|
|
for (int i = 0; i < wp.count(); i++) {
|
|
|
|
|
|
const Waypoint& w = wp.at(i);
|
|
|
|
|
|
int wx, wy; proj.project(w.lat_1e6, w.lon_1e6, wx, wy);
|
|
|
|
|
|
if (wx < area_x) wx = area_x; else if (wx > wp_x_max) wx = wp_x_max;
|
|
|
|
|
|
if (wy < area_y) wy = area_y; else if (wy > wp_y_max) wy = wp_y_max;
|
|
|
|
|
|
drawWaypointMarker(display, wx, wy);
|
|
|
|
|
|
char s[3] = { w.label[0], w.label[0] ? w.label[1] : (char)0, 0 };
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
drawLabelAvoiding(display, s, wx, wy, area_x, area_y, area_w, area_h, occ, nocc, 40);
|
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
|
|
|
|
}
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
|
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
|
|
|
|
if (have_gps) {
|
|
|
|
|
|
int mx, my; proj.project(my_lat, my_lon, mx, my);
|
|
|
|
|
|
drawCurrentMarker(display, mx, my);
|
|
|
|
|
|
} else if (have_trail) {
|
|
|
|
|
|
int ex, ey; proj.project(_store->last().lat_1e6, _store->last().lon_1e6, ex, ey);
|
|
|
|
|
|
drawCurrentMarker(display, ex, ey);
|
|
|
|
|
|
}
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
|
|
|
|
|
|
// Active target flag, last so it stays legible atop any waypoint/contact it
|
|
|
|
|
|
// coincides with. Clamped to the frame like the other off-edge markers.
|
|
|
|
|
|
int32_t tla, tlo;
|
|
|
|
|
|
if (_task->activeTargetPos(tla, tlo)) {
|
|
|
|
|
|
int tx, ty; proj.project(tla, tlo, tx, ty);
|
|
|
|
|
|
if (tx < area_x) tx = area_x; else if (tx > wp_x_max) tx = wp_x_max;
|
|
|
|
|
|
if (ty < area_y) ty = area_y; else if (ty > wp_y_max) ty = wp_y_max;
|
|
|
|
|
|
drawTargetMarker(display, tx, ty);
|
|
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 09:13:52 +02:00
|
|
|
|
void renderMap(DisplayDriver& display) {
|
|
|
|
|
|
const int top = display.listStart();
|
2026-05-25 17:58:56 +02:00
|
|
|
|
const int bottom = display.height() - 2;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-06-03 08:30:16 +02:00
|
|
|
|
WaypointStore& wp = _task->waypoints();
|
|
|
|
|
|
const int nwp = wp.count();
|
|
|
|
|
|
const bool have_trail = !_store->empty();
|
|
|
|
|
|
|
2026-06-03 23:54:48 +02:00
|
|
|
|
// Live GPS position — lets the map double as a "you + waypoints" view even
|
|
|
|
|
|
// when no trail is being recorded.
|
|
|
|
|
|
int32_t my_lat, my_lon;
|
|
|
|
|
|
const bool have_gps = ownPos(my_lat, my_lon);
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
const bool have_live = _task->liveTrack().active(rtc_clock.getCurrentTime()) > 0;
|
|
|
|
|
|
int32_t tgt_lat, tgt_lon;
|
|
|
|
|
|
const bool have_tgt = _task->activeTargetPos(tgt_lat, tgt_lon);
|
2026-06-03 23:54:48 +02:00
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
if (!have_trail && nwp == 0 && !have_gps && !have_live && !have_tgt) {
|
2026-06-03 23:54:48 +02:00
|
|
|
|
display.drawTextCentered(display.width() / 2, (top + bottom) / 2, "No GPS / no trail");
|
2026-05-25 09:13:52 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const int area_x = 2;
|
|
|
|
|
|
const int area_y = top + 1;
|
|
|
|
|
|
const int area_w = display.width() - 4;
|
|
|
|
|
|
const int area_h = bottom - top - 2;
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
if (area_w < 6 || area_h < 6) return;
|
2026-05-25 09:13:52 +02:00
|
|
|
|
|
2026-06-03 23:54:48 +02:00
|
|
|
|
// Bounding box spans the trail, every waypoint, and the live position, so
|
|
|
|
|
|
// everything of interest stays in frame.
|
2026-06-03 08:30:16 +02:00
|
|
|
|
int32_t min_lat = 0, min_lon = 0, max_lat = 0, max_lon = 0;
|
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
|
|
|
|
computeBounds(have_trail, have_gps, my_lat, my_lon, min_lat, min_lon, max_lat, max_lon);
|
2026-05-25 09:13:52 +02:00
|
|
|
|
|
2026-06-03 08:30:16 +02:00
|
|
|
|
// Degenerate: everything at one coordinate — just centre the markers.
|
|
|
|
|
|
if (min_lat == max_lat && min_lon == max_lon) {
|
|
|
|
|
|
int ccx = area_x + area_w / 2, ccy = area_y + area_h / 2;
|
2026-06-04 15:42:36 +02:00
|
|
|
|
// Only pin a waypoint to the centre in the genuine "lone waypoint, no
|
|
|
|
|
|
// trail" case. With a trail the centre is the trail's spot, and the
|
|
|
|
|
|
// waypoints (not folded into this box) belong elsewhere — don't draw
|
|
|
|
|
|
// them here, or they'd appear at the trail's location.
|
|
|
|
|
|
if (nwp > 0 && !have_trail) {
|
2026-06-03 23:56:53 +02:00
|
|
|
|
drawWaypointMarker(display, ccx, ccy);
|
|
|
|
|
|
const char* lbl = wp.at(0).label; // single coincident spot — label it
|
2026-06-04 00:04:11 +02:00
|
|
|
|
char s[3] = { lbl[0], lbl[0] ? lbl[1] : (char)0, 0 };
|
2026-06-04 12:26:02 +02:00
|
|
|
|
drawWaypointLabel(display, s, ccx, ccy, area_x, area_y, area_w, area_h);
|
2026-06-03 23:56:53 +02:00
|
|
|
|
}
|
2026-06-03 23:54:48 +02:00
|
|
|
|
if (have_gps || have_trail) drawCurrentMarker(display, ccx, ccy);
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
{ int32_t tla, tlo; if (_task->activeTargetPos(tla, tlo)) drawTargetMarker(display, ccx, ccy); }
|
2026-05-25 09:13:52 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float avg_lat_rad = ((min_lat + max_lat) / 2.0e6f) * (float)M_PI / 180.0f;
|
|
|
|
|
|
float lon_scale_geo = cosf(avg_lat_rad);
|
refactor(ui): trail — settings back in popup, hold-Enter to stop
Per feedback the dedicated Config view added too much navigation. Roll
back to three views (Summary, Map, List) cyclable with LEFT/RIGHT, and
move every knob and action into the Hold-Enter (KEY_CONTEXT_MENU)
popup:
Min dist: <value> ← LEFT/RIGHT cycles, popup stays open
Units: <value> ← same
Start/Stop tracking ← Enter executes, popup closes
Reset trail ← Enter executes, only listed when not empty
Short Enter only starts a stopped trail; on an active trail it shows
"Hold Enter for menu" instead of stopping. Stop is therefore reachable
only through the popup, so a stray tap can never end a recording.
PopupMenu lacks LEFT/RIGHT semantics, so the screen intercepts those
keys when the focused row is a settings row, refreshes the label
in-place (same buffer pointer the menu already holds), and keeps the
popup open. Enter on a settings row re-opens the popup with focus
preserved on that row so the user can keep cycling with Enter too if
they prefer. Settings edits batch in _cfg_dirty and commit once on
popup close, Start, or Back.
Hint line shows current view N/3, the short-Enter action ("start" or
"act" depending on state), and a [Hold]menu reminder. Separator above
the hint keeps the row clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:37:43 +02:00
|
|
|
|
if (lon_scale_geo < 0.05f) lon_scale_geo = 0.05f;
|
2026-05-25 09:13:52 +02:00
|
|
|
|
|
|
|
|
|
|
float lat_span = (float)(max_lat - min_lat);
|
|
|
|
|
|
float lon_span = (float)(max_lon - min_lon) * lon_scale_geo;
|
|
|
|
|
|
|
|
|
|
|
|
float scale_lat = (float)area_h / (lat_span > 0 ? lat_span : 1.0f);
|
|
|
|
|
|
float scale_lon = (float)area_w / (lon_span > 0 ? lon_span : 1.0f);
|
|
|
|
|
|
float scale = (scale_lat < scale_lon) ? scale_lat : scale_lon;
|
|
|
|
|
|
|
|
|
|
|
|
int used_w = (int)(lon_span * scale);
|
|
|
|
|
|
int used_h = (int)(lat_span * scale);
|
|
|
|
|
|
|
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
|
|
|
|
MapProjection proj;
|
|
|
|
|
|
proj.min_lat = min_lat; proj.max_lat = max_lat; proj.min_lon = min_lon;
|
|
|
|
|
|
proj.lon_scale_geo = lon_scale_geo; proj.scale = scale;
|
|
|
|
|
|
proj.off_x = area_x + (area_w - used_w) / 2;
|
|
|
|
|
|
proj.off_y = area_y + (area_h - used_h) / 2;
|
|
|
|
|
|
proj.area_x = area_x; proj.area_y = area_y;
|
|
|
|
|
|
proj.area_w = area_w; proj.area_h = area_h;
|
2026-05-25 09:13:52 +02:00
|
|
|
|
|
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
|
|
|
|
if (_map_grid) renderGrid(display, proj);
|
2026-05-27 17:33:20 +02:00
|
|
|
|
|
2026-06-03 08:30:16 +02:00
|
|
|
|
if (have_trail) {
|
|
|
|
|
|
int x0, y0;
|
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
|
|
|
|
proj.project(_store->at(0).lat_1e6, _store->at(0).lon_1e6, x0, y0);
|
2026-06-18 09:16:57 +02:00
|
|
|
|
miniIconDrawCentered(display, x0, y0, ICON_MAP_DOT);
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
gfx::drawTrail(display, *_store,
|
|
|
|
|
|
[&](int32_t la, int32_t lo, int& x, int& y) { proj.project(la, lo, x, y); },
|
|
|
|
|
|
[&](int xa, int ya, int xb, int yb) { drawFilledDot(display, xa, ya); drawOpenDot(display, xb, yb); });
|
2026-06-03 23:54:48 +02:00
|
|
|
|
int sx, sy;
|
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
|
|
|
|
proj.project(_store->first().lat_1e6, _store->first().lon_1e6, sx, sy);
|
2026-06-03 08:30:16 +02:00
|
|
|
|
drawStartMarker(display, sx, sy);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
drawMarkers(display, proj, have_gps, my_lat, my_lon, have_trail);
|
2026-06-03 23:54:48 +02:00
|
|
|
|
|
2026-06-18 09:16:57 +02:00
|
|
|
|
int nx, ny;
|
|
|
|
|
|
northIconPos(display, area_x, area_w, area_y, nx, ny);
|
|
|
|
|
|
miniIconDrawTop(display, nx, ny, ICON_MAP_NORTH);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Top-left placement of the north marker — flush to the top-right corner of
|
|
|
|
|
|
// the map area with a small buffer. Shared with renderGrid so its exclusion
|
|
|
|
|
|
// box always matches exactly where the icon is actually drawn.
|
|
|
|
|
|
static void northIconPos(DisplayDriver& d, int area_x, int area_w, int area_y,
|
|
|
|
|
|
int& nx, int& ny) {
|
|
|
|
|
|
const int s = miniIconScale(d);
|
|
|
|
|
|
nx = area_x + area_w - ICON_MAP_NORTH.w * s - 1;
|
|
|
|
|
|
ny = area_y + 1;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
void renderGrid(DisplayDriver& display, const MapProjection& proj) {
|
|
|
|
|
|
const int area_x = proj.area_x, area_y = proj.area_y;
|
|
|
|
|
|
const int area_w = proj.area_w, area_h = proj.area_h;
|
2026-05-27 17:33:20 +02:00
|
|
|
|
static constexpr float M_PER_1E6 = 0.11132f; // metres per 1e-6 deg lat
|
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
|
|
|
|
float ppm = proj.scale / M_PER_1E6; // pixels per metre (isotropic)
|
2026-05-27 17:33:20 +02:00
|
|
|
|
if (ppm <= 0.0f) return;
|
|
|
|
|
|
|
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
|
|
|
|
// Round scale steps with matching labels. Imperial steps are stored as their
|
|
|
|
|
|
// metre equivalents but labelled with their exact round ft/mi value. Both
|
|
|
|
|
|
// tables are parallel (step ↔ label).
|
2026-06-04 00:50:42 +02:00
|
|
|
|
static const float METRIC_STEPS[] = {
|
2026-05-27 17:33:20 +02:00
|
|
|
|
1, 2, 5, 10, 20, 50, 100, 200, 500,
|
|
|
|
|
|
1000, 2000, 5000, 10000, 20000, 50000, 100000
|
|
|
|
|
|
};
|
2026-06-04 00:50:42 +02:00
|
|
|
|
static const char* METRIC_LABELS[] = {
|
|
|
|
|
|
"1m", "2m", "5m", "10m", "20m", "50m", "100m", "200m", "500m",
|
|
|
|
|
|
"1km", "2km", "5km", "10km", "20km", "50km", "100km"
|
|
|
|
|
|
};
|
|
|
|
|
|
static const float IMPERIAL_STEPS[] = { // 10..2000 ft, then 1..100 mi (metres)
|
|
|
|
|
|
3.048f, 7.62f, 15.24f, 30.48f, 76.2f, 152.4f, 304.8f, 609.6f,
|
|
|
|
|
|
1609.344f, 3218.69f, 8046.72f, 16093.44f, 32186.88f, 80467.2f, 160934.4f
|
|
|
|
|
|
};
|
|
|
|
|
|
static const char* IMPERIAL_LABELS[] = {
|
|
|
|
|
|
"10ft", "25ft", "50ft", "100ft", "250ft", "500ft", "1000ft", "2000ft",
|
|
|
|
|
|
"1mi", "2mi", "5mi", "10mi", "20mi", "50mi", "100mi"
|
|
|
|
|
|
};
|
|
|
|
|
|
const bool imp = useImperial();
|
|
|
|
|
|
const float* STEPS = imp ? IMPERIAL_STEPS : METRIC_STEPS;
|
|
|
|
|
|
const char* const* LABELS = imp ? IMPERIAL_LABELS : METRIC_LABELS;
|
|
|
|
|
|
const int N_STEPS = imp ? (int)(sizeof(IMPERIAL_STEPS)/sizeof(IMPERIAL_STEPS[0]))
|
|
|
|
|
|
: (int)(sizeof(METRIC_STEPS)/sizeof(METRIC_STEPS[0]));
|
2026-05-27 17:33:20 +02:00
|
|
|
|
|
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
|
|
|
|
// Pick the round step nearest ~1/3 of the shorter side, then keep it sparse
|
|
|
|
|
|
// enough to read on an OLED (≥ MIN_GRID_PX between intersections).
|
2026-05-27 17:33:20 +02:00
|
|
|
|
static const float MIN_GRID_PX = 22.0f;
|
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
|
|
|
|
int shorter_px = area_w < area_h ? area_w : area_h;
|
|
|
|
|
|
float target_m = (shorter_px / ppm) / 3.0f;
|
|
|
|
|
|
int sel = 0;
|
|
|
|
|
|
for (int si = N_STEPS - 1; si >= 0; si--) { if (STEPS[si] <= target_m) { sel = si; break; } }
|
|
|
|
|
|
while (sel < N_STEPS - 1 && STEPS[sel] * ppm < MIN_GRID_PX) sel++;
|
|
|
|
|
|
|
|
|
|
|
|
// Square cells, snapped to the shorter side: divide the shorter dimension
|
|
|
|
|
|
// into a whole number of equal cells (so the grid touches that pair of
|
|
|
|
|
|
// borders), use that exact size as the square cell, and centre the whole
|
|
|
|
|
|
// number of cells that fit along the longer dimension. Result: square grid,
|
|
|
|
|
|
// reaches the frame top↔bottom (landscape), symmetric left↔right — no
|
|
|
|
|
|
// one-sided drift, no rectangular cells. Cap so spacing stays ≥ MIN_GRID_PX.
|
|
|
|
|
|
float step_px = STEPS[sel] * ppm;
|
|
|
|
|
|
bool h_shorter = (area_h <= area_w);
|
|
|
|
|
|
int short_px = h_shorter ? area_h : area_w;
|
|
|
|
|
|
int long_px = h_shorter ? area_w : area_h;
|
|
|
|
|
|
int max_short = (int)(short_px / MIN_GRID_PX); if (max_short < 1) max_short = 1;
|
|
|
|
|
|
int n_short = (int)((float)short_px / step_px + 0.5f);
|
|
|
|
|
|
if (n_short < 1) n_short = 1; if (n_short > max_short) n_short = max_short;
|
|
|
|
|
|
float cell = (float)short_px / (float)n_short; // exact square size
|
|
|
|
|
|
int n_long = (int)((float)long_px / cell); // whole cells that fit
|
|
|
|
|
|
if (n_long < 1) n_long = 1;
|
|
|
|
|
|
int off_long = (int)(((float)long_px - (float)n_long * cell) / 2.0f + 0.5f);
|
|
|
|
|
|
|
|
|
|
|
|
// Map the short/long axes back to screen x/y.
|
|
|
|
|
|
int x0 = h_shorter ? area_x + off_long : area_x;
|
|
|
|
|
|
int y0 = h_shorter ? area_y : area_y + off_long;
|
|
|
|
|
|
int cx = h_shorter ? n_long : n_short; // cell count along x
|
|
|
|
|
|
int cy = h_shorter ? n_short : n_long; // cell count along y
|
|
|
|
|
|
|
|
|
|
|
|
const char* lbl = LABELS[sel];
|
|
|
|
|
|
int lh = display.getLineHeight();
|
|
|
|
|
|
int cw = display.getCharWidth();
|
|
|
|
|
|
int x_max = area_x + area_w - 1, y_max = area_y + area_h - 1;
|
|
|
|
|
|
|
|
|
|
|
|
// Exclusion boxes so grid dots don't smear the scale label (bottom-left) or
|
2026-06-18 09:16:57 +02:00
|
|
|
|
// the north marker (top-right) — northIconPos() is the single source of
|
|
|
|
|
|
// truth for where that icon actually lands, so this box can't drift out of
|
|
|
|
|
|
// sync with it as the mini-icon scale changes.
|
|
|
|
|
|
const int s = miniIconScale(display);
|
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
|
|
|
|
int lbl_x2 = area_x + (int)strlen(lbl) * cw + 1;
|
|
|
|
|
|
int lbl_y1 = area_y + area_h - lh - 1;
|
2026-06-18 09:16:57 +02:00
|
|
|
|
int nx, ny;
|
|
|
|
|
|
northIconPos(display, area_x, area_w, area_y, nx, ny);
|
|
|
|
|
|
int arr_x1 = nx - 1;
|
|
|
|
|
|
int arr_y2 = ny + ICON_MAP_NORTH.h * s + 1;
|
2026-05-27 17:33:20 +02:00
|
|
|
|
|
2026-06-18 09:16:57 +02:00
|
|
|
|
// Grid dots are drawn at mini-icon scale too, so they stay visible instead
|
|
|
|
|
|
// of shrinking to a single pixel on large-font (e-ink) layouts.
|
2026-05-27 17:33:20 +02:00
|
|
|
|
auto fillSafe = [&](int x, int y) {
|
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
|
|
|
|
if (x < area_x || x > x_max || y < area_y || y > y_max) return;
|
|
|
|
|
|
if (x <= lbl_x2 && y >= lbl_y1) return; // under the scale label
|
|
|
|
|
|
if (x >= arr_x1 && y <= arr_y2) return; // under the north arrow
|
2026-06-18 09:16:57 +02:00
|
|
|
|
display.fillRect(x - s / 2, y - s / 2, s, s);
|
2026-05-27 17:33:20 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
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
|
|
|
|
// A single dot per intersection — cleaner and more legible on an OLED than
|
|
|
|
|
|
// a 5-pixel cross (whose arms also clipped at the frame edges).
|
|
|
|
|
|
for (int i = 0; i <= cx; i++) {
|
|
|
|
|
|
int px = x0 + (int)((float)i * cell + 0.5f);
|
|
|
|
|
|
if (px > x_max) px = x_max;
|
|
|
|
|
|
for (int j = 0; j <= cy; j++) {
|
|
|
|
|
|
int py = y0 + (int)((float)j * cell + 0.5f);
|
|
|
|
|
|
if (py > y_max) py = y_max;
|
|
|
|
|
|
fillSafe(px, py);
|
2026-05-27 17:33:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
display.setCursor(area_x, area_y + area_h - lh);
|
|
|
|
|
|
display.print(lbl);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 12:26:02 +02:00
|
|
|
|
// Place a 1–2 char waypoint label beside its marker, kept inside the map
|
|
|
|
|
|
// rect: prefer upper-right, flip to the left if it would clip the right
|
|
|
|
|
|
// edge, and clamp vertically so edge/corner waypoints stay on the map.
|
|
|
|
|
|
static void drawWaypointLabel(DisplayDriver& d, const char* s, int wx, int wy,
|
|
|
|
|
|
int ax, int ay, int aw, int ah) {
|
|
|
|
|
|
if (!s[0]) return;
|
|
|
|
|
|
int tw = (int)d.getTextWidth(s);
|
|
|
|
|
|
int ch = d.getLineHeight();
|
|
|
|
|
|
int lx = wx + 4;
|
|
|
|
|
|
if (lx + tw > ax + aw) lx = wx - 4 - tw; // would clip right → flip left
|
|
|
|
|
|
if (lx < ax) lx = ax; // clamp to left edge
|
|
|
|
|
|
if (lx + tw > ax + aw) lx = ax + aw - tw; // clamp to right edge
|
|
|
|
|
|
int ly = wy - 3;
|
|
|
|
|
|
if (ly < ay) ly = ay; // clamp to top edge
|
|
|
|
|
|
if (ly + ch > ay + ah) ly = ay + ah - ch; // clamp to bottom edge
|
|
|
|
|
|
d.setCursor(lx, ly);
|
|
|
|
|
|
d.print(s);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Map label collision avoidance. Each placed label reserves a rectangle; a new
|
|
|
|
|
|
// label tries up to four offsets around its marker (upper-right, upper-left,
|
|
|
|
|
|
// lower-right, lower-left) and is skipped — the marker still shows — if none
|
|
|
|
|
|
// fit on-frame without overlapping an already-placed one. On a dense map this
|
|
|
|
|
|
// drops a few labels instead of smearing them into an unreadable blob.
|
|
|
|
|
|
struct LblRect { int x, y, w, h; };
|
|
|
|
|
|
static bool rectsOverlap(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) {
|
|
|
|
|
|
return ax < bx + bw && bx < ax + aw && ay < by + bh && by < ay + ah;
|
|
|
|
|
|
}
|
|
|
|
|
|
static void drawLabelAvoiding(DisplayDriver& d, const char* s, int wx, int wy,
|
|
|
|
|
|
int ax, int ay, int aw, int ah,
|
|
|
|
|
|
LblRect* occ, int& nocc, int occ_max) {
|
|
|
|
|
|
if (!s[0]) return;
|
|
|
|
|
|
int tw = (int)d.getTextWidth(s);
|
|
|
|
|
|
int ch = d.getLineHeight();
|
|
|
|
|
|
const int cand[4][2] = { { wx + 4, wy - 3 }, { wx - 4 - tw, wy - 3 },
|
|
|
|
|
|
{ wx + 4, wy + 3 }, { wx - 4 - tw, wy + 3 } };
|
|
|
|
|
|
for (int c = 0; c < 4; c++) {
|
|
|
|
|
|
int lx = cand[c][0], ly = cand[c][1];
|
|
|
|
|
|
if (lx < ax || lx + tw > ax + aw || ly < ay || ly + ch > ay + ah) continue;
|
|
|
|
|
|
bool clash = false;
|
|
|
|
|
|
for (int k = 0; k < nocc; k++)
|
|
|
|
|
|
if (rectsOverlap(lx, ly, tw, ch, occ[k].x, occ[k].y, occ[k].w, occ[k].h)) { clash = true; break; }
|
|
|
|
|
|
if (clash) continue;
|
|
|
|
|
|
d.setCursor(lx, ly);
|
|
|
|
|
|
d.print(s);
|
|
|
|
|
|
if (nocc < occ_max) occ[nocc++] = { lx, ly, tw, ch };
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// No free slot — skip the label; the marker alone still conveys the point.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 10:41:09 +02:00
|
|
|
|
static void drawFilledDot(DisplayDriver& d, int cx, int cy) {
|
2026-06-18 09:16:57 +02:00
|
|
|
|
miniIconDrawCentered(d, cx, cy, ICON_MAP_DOT);
|
2026-05-25 10:41:09 +02:00
|
|
|
|
}
|
2026-06-03 08:30:16 +02:00
|
|
|
|
// Hollow diamond — distinct from the trail's dots (○●), start (+) and
|
|
|
|
|
|
// current (✕) markers.
|
|
|
|
|
|
static void drawWaypointMarker(DisplayDriver& d, int cx, int cy) {
|
2026-06-18 09:16:57 +02:00
|
|
|
|
miniIconDrawCentered(d, cx, cy, ICON_MAP_WAYPOINT);
|
2026-06-03 08:30:16 +02:00
|
|
|
|
}
|
2026-05-25 10:41:09 +02:00
|
|
|
|
static void drawOpenDot(DisplayDriver& d, int cx, int cy) {
|
2026-06-18 09:16:57 +02:00
|
|
|
|
miniIconDrawCentered(d, cx, cy, ICON_MAP_RING);
|
2026-05-25 10:41:09 +02:00
|
|
|
|
}
|
2026-05-25 09:13:52 +02:00
|
|
|
|
static void drawStartMarker(DisplayDriver& d, int cx, int cy) {
|
2026-06-18 09:16:57 +02:00
|
|
|
|
miniIconDrawCentered(d, cx, cy, ICON_MAP_START);
|
2026-05-25 09:13:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
static void drawCurrentMarker(DisplayDriver& d, int cx, int cy) {
|
2026-06-18 09:16:57 +02:00
|
|
|
|
miniIconDrawCentered(d, cx, cy, ICON_MAP_CURRENT);
|
2026-05-25 13:54:54 +02:00
|
|
|
|
}
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:04:21 +02:00
|
|
|
|
// Filled diamond — a contact whose position arrived via a [LOC] share.
|
|
|
|
|
|
static void drawContactMarker(DisplayDriver& d, int cx, int cy) {
|
|
|
|
|
|
miniIconDrawCentered(d, cx, cy, ICON_MAP_CONTACT);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Flag — the active Locator/Nav target. Drawn last (on top) so it stays
|
|
|
|
|
|
// legible even when it lands on the same spot as a waypoint or contact.
|
|
|
|
|
|
static void drawTargetMarker(DisplayDriver& d, int cx, int cy) {
|
|
|
|
|
|
miniIconDrawCentered(d, cx, cy, ICON_MAP_TARGET);
|
|
|
|
|
|
}
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
};
|