From e5887f1d0178933e90d86f89565c2de92418a221 Mon Sep 17 00:00:00 2001 From: MarekZegare4 Date: Wed, 24 Jun 2026 17:56:45 +0200 Subject: [PATCH] feat(companion): highlight the active target on the map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting a Locator/Nav target had no spatial feedback — you could arm a destination but never see where it is. Draw it as a flag marker (ICON_MAP_TARGET) on both the home Map preview and the full Trail map, folded into each view's bounding box so a destination you set never sits off-frame, and drawn last so it stays legible atop a coincident waypoint or contact. activeTargetPos() resolves the target once (waypoint coords, or a person via resolvePersonPos), gated only on a target being set — independent of whether the Locator alert is enabled — so it doubles as the engine's resolver (locatorDistance now delegates to it). Co-Authored-By: Claude Opus 4.8 --- examples/companion_radio/ui-new/TrailScreen.h | 22 ++++++++++++++++++- examples/companion_radio/ui-new/UITask.cpp | 22 ++++++++++++++----- examples/companion_radio/ui-new/UITask.h | 5 +++++ examples/companion_radio/ui-new/icons.h | 6 +++++ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/ui-new/TrailScreen.h b/examples/companion_radio/ui-new/TrailScreen.h index 4a2871c9..4810c02d 100644 --- a/examples/companion_radio/ui-new/TrailScreen.h +++ b/examples/companion_radio/ui-new/TrailScreen.h @@ -601,6 +601,8 @@ private: 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); } return init; } @@ -648,6 +650,16 @@ private: int ex, ey; proj.project(_store->last().lat_1e6, _store->last().lon_1e6, ex, ey); drawCurrentMarker(display, ex, ey); } + + // 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); + } } void renderMap(DisplayDriver& display) { @@ -663,8 +675,10 @@ private: int32_t my_lat, my_lon; const bool have_gps = ownPos(my_lat, my_lon); 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); - if (!have_trail && nwp == 0 && !have_gps && !have_live) { + if (!have_trail && nwp == 0 && !have_gps && !have_live && !have_tgt) { display.drawTextCentered(display.width() / 2, (top + bottom) / 2, "No GPS / no trail"); return; } @@ -694,6 +708,7 @@ private: drawWaypointLabel(display, s, ccx, ccy, area_x, area_y, area_w, area_h); } if (have_gps || have_trail) drawCurrentMarker(display, ccx, ccy); + { int32_t tla, tlo; if (_task->activeTargetPos(tla, tlo)) drawTargetMarker(display, ccx, ccy); } return; } @@ -939,4 +954,9 @@ private: 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); + } }; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index e6a5a13d..d221efeb 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -574,6 +574,9 @@ public: int32_t mla, mlo; bool have_gps = _task->currentLocation(mla, mlo); if (have_gps) fold(mla, mlo); + int32_t tla, tlo; + bool have_tgt = _task->activeTargetPos(tla, tlo); // active Locator/Nav target + if (have_tgt) fold(tla, tlo); if (!init) return false; // North marker — top-right, the same mini-icon as the full Trail map. @@ -589,6 +592,7 @@ public: for (int i = 0; i < LiveTrackStore::CAPACITY; i++) if (lt.isActive(i, now)) { miniIconDrawCentered(display, cx, cy, ICON_MAP_CONTACT); break; } if (have_gps || !tr.empty()) miniIconDrawCentered(display, cx, cy, ICON_MAP_CURRENT); + if (have_tgt) miniIconDrawCentered(display, cx, cy, ICON_MAP_TARGET); // highlight on top return true; } float avg_lat_rad = ((mnla + mxla) / 2.0e6f) * (float)M_PI / 180.0f; @@ -615,6 +619,8 @@ public: miniIconDrawCentered(display, px, py, ICON_MAP_CONTACT); } if (have_gps) { int px, py; project(mla, mlo, px, py); miniIconDrawCentered(display, px, py, ICON_MAP_CURRENT); } + // Active target flag drawn last so it stays legible even atop a contact/own dot. + if (have_tgt) { int px, py; project(tla, tlo, px, py); miniIconDrawCentered(display, px, py, ICON_MAP_TARGET); } // Bottom-left scale reference, always shown — distance to the nearest // live-tracked contact now lives on the status line below instead (see @@ -2298,14 +2304,18 @@ bool UITask::resolvePersonPos(const uint8_t* key, int32_t& lat, int32_t& lon, return false; } -bool UITask::locatorDistance(float& dist_m, float& radius_m) const { +bool UITask::activeTargetPos(int32_t& lat, int32_t& lon) const { if (!_node_prefs || !_node_prefs->locator_has_target) return false; + if (_node_prefs->locator_target_kind == 1) + return resolvePersonPos(_node_prefs->locator_key, lat, lon); + lat = _node_prefs->locator_lat_1e6; + lon = _node_prefs->locator_lon_1e6; + return true; +} + +bool UITask::locatorDistance(float& dist_m, float& radius_m) const { int32_t tlat, tlon; - if (_node_prefs->locator_target_kind == 1) { - if (!resolvePersonPos(_node_prefs->locator_key, tlat, tlon)) return false; - } else { - tlat = _node_prefs->locator_lat_1e6; tlon = _node_prefs->locator_lon_1e6; - } + if (!activeTargetPos(tlat, tlon)) return false; int32_t lat, lon; if (!currentLocation(lat, lon)) return false; dist_m = geo::haversineKm(lat, lon, tlat, tlon) * 1000.0f; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index b4296959..3ec3a983 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -211,6 +211,11 @@ public: // Locator engine (locatorDistance) and the target picker. bool resolvePersonPos(const uint8_t* key, int32_t& lat, int32_t& lon, bool* live = nullptr, uint32_t* ts = nullptr) const; + // Resolved position of the active target — a waypoint's coords, or a person + // via resolvePersonPos(). Gated only on a target being set, independent of + // whether the Locator alert is enabled, so a destination you set still shows + // on the map. Used by locatorDistance() and the map renderers. + bool activeTargetPos(int32_t& lat, int32_t& lon) const; void gotoTrailScreen(); void gotoMapScreen(); // opens the Trail screen directly in its Map view void gotoCompassScreen(); diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 0677a86e..8ce27f52 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -334,6 +334,12 @@ MINI_ICON(ICON_MAP_ARROW, 5, // → distance-to-nearest-tracked-contact ind packRow("#####"), packRow("...#."), packRow("..#..")); +MINI_ICON(ICON_MAP_TARGET, 5, // ⚑ flag on a pole — the active Locator/Nav target + packRow("#...."), + packRow("####."), + packRow("#..#."), + packRow("####."), + packRow("#....")); // Keyboard special-key glyphs. MINI_ICON(ICON_SHIFT, 7, // ⇧ caps