feat(nav): heads-up compass + always-available Trail-start waypoint

Compass: switch to a heads-up card. The direction of travel is now the
fixed "up" index at the top and the compass card rotates underneath it —
a North needle plus N/E/S/W letters placed at screen angle (bearing - cog),
so North spins as you turn rather than the arrow pointing at your course.

Trail waypoints: the nav list now always starts with a synthetic
"Trail start" row whenever a trail exists, so you can navigate back to
where you began without having marked it. The "Waypoints" action appears
when there are saved waypoints OR a trail to backtrack to. Rename/Delete
apply only to saved waypoints; the start row is navigate-only. List/nav
indexing goes through rowTarget()/wpIndex() helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-03 16:51:40 +02:00
co-authored by Claude Opus 4.8
parent 3110b59f93
commit 98ccf13336
2 changed files with 88 additions and 40 deletions
+31 -14
View File
@@ -67,9 +67,12 @@ public:
int cog;
bool have = _task->currentCourse(cog);
// Compass ring ticks: N/E/S/W as short marks; N drawn as a filled wedge top.
display.drawRect(cx - r, cy - r, 2 * r + 1, 2 * r + 1); // bounding box stands in for a ring
display.drawTextCentered(cx, cy - r - 1, "N");
display.drawRect(cx - r, cy - r, 2 * r + 1, 2 * r + 1); // ring stand-in
// Fixed forward index at the top: the direction you are travelling is
// always "up". The compass card (North) rotates underneath it.
display.fillRect(cx - 1, cy - r - 2, 3, 1);
display.fillRect(cx, cy - r - 1, 1, 2);
if (!have) {
display.drawTextCentered(cx, cy - display.getLineHeight() / 2, "move to");
@@ -77,20 +80,34 @@ public:
return 1000;
}
// Arrow from centre toward the course (0° = up = north, clockwise).
float rad = cog * (float)M_PI / 180.0f;
int ex = cx + (int)(sinf(rad) * (r - 2));
int ey = cy - (int)(cosf(rad) * (r - 2));
drawLine(display, cx, cy, ex, ey);
// Small arrowhead: two short lines back from the tip at ±150°.
// Heads-up compass: with "up" = course, an absolute bearing B sits at
// screen angle (B - cog). Draw the rotating card — a North needle plus the
// other three cardinals as letters around the ring.
auto cardPos = [&](int bearing, int rad_px, int& px, int& py) {
float a = (bearing - cog) * (float)M_PI / 180.0f;
px = cx + (int)(sinf(a) * rad_px);
py = cy - (int)(cosf(a) * rad_px);
};
// North needle from centre.
int nx, ny; cardPos(0, r - 2, nx, ny);
drawLine(display, cx, cy, nx, ny);
float nrad = (0 - cog) * (float)M_PI / 180.0f;
for (int da = -25; da <= 25; da += 50) {
float a = rad + (float)M_PI + da * (float)M_PI / 180.0f;
int hx = ex + (int)(sinf(a) * 4);
int hy = ey - (int)(cosf(a) * 4);
drawLine(display, ex, ey, hx, hy);
float a = nrad + (float)M_PI + da * (float)M_PI / 180.0f;
drawLine(display, nx, ny, nx + (int)(sinf(a) * 4), ny - (int)(cosf(a) * 4));
}
// Numeric readout below the ring.
// Cardinal letters around the rim (N at the needle tip).
const int cw = display.getCharWidth(), ch = display.getLineHeight();
struct { int b; const char* s; } card[4] = { {0,"N"},{90,"E"},{180,"S"},{270,"W"} };
for (int i = 0; i < 4; i++) {
int lx, ly; cardPos(card[i].b, r - 2, lx, ly);
display.setCursor(lx - cw / 2, ly - ch / 2);
display.print(card[i].s);
}
// Numeric readout below the ring — the absolute course you're travelling.
char buf[16];
snprintf(buf, sizeof(buf), "%d %s", cog, geo::bearingCardinal(cog));
display.setTextSize(2);