From 80c14a75ed2f9ad21d1b59b46954e7c4e67f99c9 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:31:53 +0200 Subject: [PATCH] feat(nav): add a waypoint by typing coordinates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Trail action "Add by coords" creates a waypoint without a GPS fix: the keyboard takes a "lat,lon" string (decimal degrees), parsed by the shared geo::parseLatLon; on success it falls through to the normal label keyboard (blank → auto WP). Useful for a meeting point or a spot read off a map. Invalid input reports "Bad coordinates". Co-Authored-By: Claude Opus 4.8 --- .../tools_screen/tools_screen.md | 3 ++ examples/companion_radio/ui-new/TrailScreen.h | 43 ++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/docs/solo_features/tools_screen/tools_screen.md b/docs/solo_features/tools_screen/tools_screen.md index 5efea479..44a2e965 100644 --- a/docs/solo_features/tools_screen/tools_screen.md +++ b/docs/solo_features/tools_screen/tools_screen.md @@ -92,6 +92,7 @@ Cycle views with **LEFT / RIGHT**: | Readout | LEFT/RIGHT or Enter | Summary shows Speed or Pace (in the global unit system) | | Grid | LEFT/RIGHT or Enter | Toggle scale grid on the map | | Mark here | Enter | Drop a waypoint at the current GPS fix (see below) | +| Add by coords | Enter | Add a waypoint by typing `lat,lon` — no GPS fix needed | | Waypoints | Enter | Open the waypoint list / navigation | | Start / Stop tracking | Enter | Begin or end a recording session | | Save trail | Enter | Write RAM ring to flash (`/trail`) | @@ -109,6 +110,8 @@ A waypoint is a saved spot — your car, camp, a water source — that you can n **Dropping a waypoint** — **Hold Enter → Mark here**. This captures the current GPS fix and opens the on-screen keyboard for a short label (up to 11 characters — e.g. `CAR`, `CAMP`, `H2O`). Leaving it blank auto-names it `WP1`, `WP2`, … Marking works whether or not the trail is being recorded; it needs a GPS fix (otherwise it reports *No GPS fix*). +**Adding by coordinates** — **Hold Enter → Add by coords** lets you create a waypoint without being there: type `lat,lon` in decimal degrees (e.g. `37.42123,-122.08456`), then a label. No GPS fix is required — handy for entering a meeting point or a spot read off a map. Invalid input reports *Bad coordinates*. + **On the map** — saved waypoints show on the Trail Map view as a hollow diamond with the label's first two characters beside it (enough to tell nearby waypoints apart). Waypoints and your current GPS position are drawn continuously — even with no trail recording in progress — so the Map view doubles as a live "you + your marks" view, not just a recorded-track plot. With **no trail**, the view auto-fits to your waypoints and position. **While a trail exists**, the view frames the recorded route instead, and any waypoint that falls outside it is clamped to the nearest map edge — a distant mark can't blow up the scale and squash the trail. **Navigating** — **Hold Enter → Waypoints** opens the list (each row shows the label and live distance). The list always begins with a synthetic **Trail start** row whenever a trail exists, so you can backtrack to where you began without having marked it. Select a row and press **Enter** to open the navigation view: diff --git a/examples/companion_radio/ui-new/TrailScreen.h b/examples/companion_radio/ui-new/TrailScreen.h index 0236e82f..8d867a76 100644 --- a/examples/companion_radio/ui-new/TrailScreen.h +++ b/examples/companion_radio/ui-new/TrailScreen.h @@ -30,6 +30,7 @@ class TrailScreen : public UIScreen { PopupMenu _wp_ctx; // Rename / Delete on a selected waypoint KeyboardWidget _wp_kb; // label entry (mark new / rename) bool _kb_active = false; + bool _kb_coords = false; // keyboard is entering "lat,lon" (then asks for a label) int _kb_rename_idx = -1; // -1 = marking new, ≥0 = renaming that index int32_t _mark_lat = 0, _mark_lon = 0; // pending new-mark position uint32_t _mark_ts = 0; @@ -39,9 +40,9 @@ class TrailScreen : public UIScreen { // (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. - enum ActionId { ACT_MIN_DIST, ACT_UNITS, ACT_GRID, ACT_TOGGLE, ACT_SAVE, ACT_LOAD, ACT_RESET, ACT_EXPORT, ACT_EXPORT_SAVED, ACT_MARK, ACT_WAYPOINTS, ACT_WP_CLEAR }; + enum ActionId { ACT_MIN_DIST, ACT_UNITS, ACT_GRID, ACT_TOGGLE, ACT_SAVE, ACT_LOAD, ACT_RESET, ACT_EXPORT, ACT_EXPORT_SAVED, ACT_MARK, ACT_ADD_COORDS, ACT_WAYPOINTS, ACT_WP_CLEAR }; PopupMenu _action_menu; - uint8_t _act_map[14]; // 12 used today; pad so adding an action doesn't OOB + uint8_t _act_map[16]; // 13 used today; pad so adding an action doesn't OOB uint8_t _act_count = 0; char _act_min_dist_label[24]; char _act_units_label[24]; @@ -62,6 +63,7 @@ public: _wp_mode = WP_OFF; _wp_ctx.active = false; _kb_active = false; + _kb_coords = false; } int render(DisplayDriver& display) override { @@ -101,8 +103,12 @@ public: // Label keyboard (mark new / rename) takes all input first. if (_kb_active) { auto r = _wp_kb.handleInput(c); - if (r == KeyboardWidget::DONE) { commitLabel(_wp_kb.buf); _kb_active = false; } - else if (r == KeyboardWidget::CANCELLED) { _kb_active = false; } + if (r == KeyboardWidget::DONE) { + if (_kb_coords) commitCoords(_wp_kb.buf); // parse → then ask for a label + else { commitLabel(_wp_kb.buf); _kb_active = false; } + } else if (r == KeyboardWidget::CANCELLED) { + _kb_active = false; _kb_coords = false; + } return true; } @@ -193,6 +199,7 @@ public: else if (act == ACT_EXPORT) { handleExport(); } else if (act == ACT_EXPORT_SAVED) { handleExportSaved(); } else if (act == ACT_MARK) { handleMarkHere(); } + else if (act == ACT_ADD_COORDS) { handleAddByCoords(); } else if (act == ACT_WAYPOINTS) { _wp_mode = WP_LIST; _wp_sel = 0; _wp_scroll = 0; } else if (act == ACT_WP_CLEAR) { _task->waypoints().clear(); _task->saveWaypoints(); _task->showAlert("Waypoints cleared", 800); } @@ -319,7 +326,8 @@ private: _act_map[_act_count++] = ACT_GRID; _action_menu.addItem(_act_grid_label); _act_map[_act_count++] = ACT_TOGGLE; _action_menu.addItem(_act_toggle_label); // Waypoints: mark the current spot, and browse/navigate saved ones. - _act_map[_act_count++] = ACT_MARK; _action_menu.addItem("Mark here"); + _act_map[_act_count++] = ACT_MARK; _action_menu.addItem("Mark here"); + _act_map[_act_count++] = ACT_ADD_COORDS; _action_menu.addItem("Add by coords"); // "Waypoints" opens the nav list — useful when there are saved waypoints // OR a trail to backtrack to (the list always includes a "Trail start" row). if (_task->waypoints().count() > 0 || !_store->empty()) { @@ -382,6 +390,31 @@ private: _kb_active = true; } + // Add a waypoint by typing "lat,lon" (no GPS fix needed). Opens the keyboard + // for coordinates; on a valid parse it falls through to the label keyboard. + void handleAddByCoords() { + if (_task->waypoints().full()) { _task->showAlert("Waypoints full", 1000); return; } + _kb_coords = true; + _kb_rename_idx = -1; + _wp_kb.begin("", 30); // "-12.345678,-123.456789" fits in 30 + _kb_active = true; + } + + // Parse the typed coordinates; on success store the fix and switch the + // keyboard to label entry (reusing the Mark-here commit path). + void commitCoords(const char* buf) { + int32_t lat, lon; + if (!geo::parseLatLon(buf, lat, lon)) { + _task->showAlert("Bad coordinates", 1200); + _kb_active = false; _kb_coords = false; + return; + } + _mark_lat = lat; _mark_lon = lon; _mark_ts = (uint32_t)rtc_clock.getCurrentTime(); + _kb_coords = false; + _kb_rename_idx = -1; + _wp_kb.begin("", WAYPOINT_LABEL_LEN - 1); // now name it (empty → auto WP) + } + void commitLabel(const char* buf) { if (_kb_rename_idx >= 0) { _task->waypoints().rename(_kb_rename_idx, buf);