feat(l2): long trails with a card copy; map follow and redraw fixes

Trail on the L2: 32768 points every 5 m, no simplification, no point
spacing setting; copied to the card every minute and restored at power-on.
The line keeps up with the position dot (incremental sync, pending point).

Map: a tap's finger jitter no longer turns follow off; the GPS button
shows when the map follows. The trail is drawn as one polyline with
breaks and off-view chunks skipped (an lv_line queued a draw task per
point).

Fewer rebuilds: conversation bubbles patched (new messages, delivery
marks), home mini-map dots reused, Nearby rows rewritten in place,
history read in runs and each file opened once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-29 22:02:49 +02:00
co-authored by Claude Opus 5.5
parent 5f6cbe34b3
commit b3b77a1f6d
15 changed files with 601 additions and 188 deletions
+30 -11
View File
@@ -198,15 +198,22 @@ enum : uint8_t { T_CLEAR, T_WAYPOINT, T_TRAILSTART, T_LIVE };
static int code(uint8_t type, int idx) { return (type << 8) | (idx & 0xFF); }
} // namespace navmap
// A press only pans once it has moved DRAG_SLOP px: a tap's jitter (a pixel
// or two on the touch panel) would otherwise stop the map following you.
static const int DRAG_SLOP = 8;
static void onMapPress(lv_event_t* e) {
static lv_point_t held; // movement within the slop, applied once it's a pan
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_PRESSED) { mapview::s_drag = 0; return; }
if (code == LV_EVENT_PRESSED) { mapview::s_drag = 0; held = { 0, 0 }; return; }
lv_point_t v;
lv_indev_get_vect(lv_indev_active(), &v);
if (v.x == 0 && v.y == 0) return;
mapview::s_drag += abs(v.x) + abs(v.y);
held.x += v.x; held.y += v.y;
if (mapview::s_drag <= DRAG_SLOP) return;
mapview::s_last_pan_ms = millis();
s_ui->mapPan(v.x, v.y);
s_ui->mapPan(held.x, held.y);
held = { 0, 0 };
}
static void onMapLongPress(lv_event_t* e) {
if (mapview::s_drag > 8) return; // held after a pan: not a long-press on a spot
@@ -233,6 +240,14 @@ static void onMapMarker(lv_event_t* e) {
s_ui->mapOpenMarker((int)(uintptr_t)lv_event_get_user_data(e));
}
// The centre-on-me button in the accent colour while the map follows you.
static void followShown(lv_obj_t* btn, bool on) {
if (!btn) return;
lv_obj_t* l = lv_obj_get_child(btn, 0);
lv_color_t c = lv_color_hex(on ? theme::ACCENT : theme::TEXT);
if (!lv_color_eq(lv_obj_get_style_text_color(l, LV_PART_MAIN), c)) lv_obj_set_style_text_color(l, c, 0);
}
static lv_obj_t* mapButton(lv_obj_t* parent, const char* text, lv_event_cb_t cb) {
lv_obj_t* b = lv_button_create(parent);
lv_obj_set_size(b, 40, 40);
@@ -351,7 +366,8 @@ void UITask::buildMap() {
if (_map_nav) lv_obj_align(mapButton(body, LV_SYMBOL_BARS, onNavTools), LV_ALIGN_TOP_LEFT, 6, 98);
else lv_obj_align(mapButton(body, LV_SYMBOL_DOWNLOAD, onMapDownload), LV_ALIGN_TOP_RIGHT, -6, 98);
int bottom = _map_nav ? navmap::BAR_H : 0; // the nav bar takes the bottom edge
lv_obj_align(mapButton(body, LV_SYMBOL_GPS, onMapCenter), LV_ALIGN_BOTTOM_RIGHT, -6, -6 - bottom);
_map_center_btn = mapButton(body, LV_SYMBOL_GPS, onMapCenter);
lv_obj_align(_map_center_btn, LV_ALIGN_BOTTOM_RIGHT, -6, -6 - bottom);
_map_dl_pill = mapPill(body, ""); // tap: the download popup
lv_obj_set_style_text_color(_map_dl_pill, lv_color_hex(theme::ACCENT), 0);
lv_obj_set_style_pad_ver(_map_dl_pill, 3, 0);
@@ -485,6 +501,7 @@ void UITask::layoutMap() {
} else {
lv_obj_add_flag(_map_me, LV_OBJ_FLAG_HIDDEN);
}
followShown(_map_center_btn, _map_follow);
const int half = mapview::MARK_D / 2;
for (int k = 0; k < mapview::s_mark_count; k++) {
const mapview::Mark& m = mapview::s_marks[k];
@@ -515,18 +532,20 @@ void UITask::layoutMap() {
// One tile decode per call, nearest to the centre first.
void UITask::mapLoop() {
if (_map_follow && (int32_t)(millis() - _next_follow_ms) >= 0) { // following you: every fix
_next_follow_ms = millis() + 1000;
int32_t lat, lon;
if (_core->course.currentLocation(lat, lon)) {
double cx = mapview::lonToTileX(lon / 1e6, _map_z), cy = mapview::latToTileY(lat / 1e6, _map_z) + mapCenterBias();
if (cx != _map_cx || cy != _map_cy) { _map_cx = cx; _map_cy = cy; layoutMap(); }
}
}
if ((int32_t)(millis() - _next_map_marks_ms) >= 0) {
_next_map_marks_ms = millis() + 3000;
if (_map_follow) {
int32_t lat, lon;
if (_core->course.currentLocation(lat, lon)) {
_map_cx = mapview::lonToTileX(lon / 1e6, _map_z);
_map_cy = mapview::latToTileY(lat / 1e6, _map_z) + mapCenterBias();
}
}
rebuildMapMarkers();
layoutMap();
}
if (_map_nav && navTrailSync()) layoutMap(); // a new trail point: the line keeps up with you
if (_map_nav) navPollAveraging();
if (_map_nav && (int32_t)(millis() - _next_nav_bar_ms) >= 0) {
_next_nav_bar_ms = millis() + 1000;
@@ -668,7 +687,7 @@ void UITask::mapZoom(int delta) {
void UITask::mapCenterOnMe() {
int32_t lat, lon;
if (!ensureGps() || !_core->course.currentLocation(lat, lon)) { _map_follow = true; return; } // centres once a fix comes
if (!ensureGps() || !_core->course.currentLocation(lat, lon)) { _map_follow = true; followShown(_map_center_btn, true); return; } // centres once a fix comes
_map_follow = true;
_map_cx = mapview::lonToTileX(lon / 1e6, _map_z);
_map_cy = mapview::latToTileY(lat / 1e6, _map_z) + mapCenterBias();