feat(ui): GPS fix icon on status bar, minimap distance label, hide Tools icons

- Status bar: add a boxed/plain GPS reticle icon reflecting LocationProvider
  fix state, shown only on boards that have a location provider. Drop the
  now-redundant "Fix:y/n" text from the map page's info line.
- Home minimap: replace the bottom-left scale bar with a "->dist" label to
  the nearest live-tracked point when one is available, falling back to a
  smaller single-tick scale indicator otherwise. Less likely to be covered
  by a plotted point than the old full-width bar, and more useful at a
  glance. Drop the now-duplicate nearest-distance text from the map page's
  info line.
- Tools screen: comment out per-row/section icons (kept for easy restore) —
  they don't fit visually in that list for now.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-23 10:37:46 +02:00
parent 79ebad58ae
commit 5bb3111bb0
3 changed files with 70 additions and 50 deletions

View File

@@ -79,7 +79,7 @@ public:
display.setCursor(2, y);
display.print(collapsed ? "+" : "-");
const int icon_x = 2 + cw + 2;
drawIcon(display, icon_x, y, SECTIONS[sec].icon);
// drawIcon(display, icon_x, y, SECTIONS[sec].icon); // icons disabled for now, don't fit visually
display.setCursor(icon_x + g, y);
display.print(SECTIONS[sec].name);
},
@@ -87,7 +87,7 @@ public:
[&](int sec, int item, int y, bool sel, int reserve) {
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
const int icon_x = 2 + cw + 2; // align item icons under the header icon
drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon);
// drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon); // icons disabled for now, don't fit visually
display.setCursor(icon_x + g, y);
display.print(SECTIONS[sec].tools[item].label);
});

View File

@@ -491,6 +491,17 @@ class HomeScreen : public UIScreen {
leftmostX = rX - 1;
}
}
// GPS fix status — boxed (lit) when the receiver has a valid fix, plain
// glyph while searching. Hidden entirely on boards with no GPS hardware,
// so profiles without one don't carry a permanently-empty slot.
LocationProvider* loc = _sensors ? _sensors->getLocationProvider() : nullptr;
if (loc) {
int gX = leftmostX - ind - ind_gap;
if (loc->isValid()) drawBoxedIcon(display, gX, ind, ind_h, ICON_GPS);
else drawSlotIcon(display, gX, ind, ind_h, ICON_GPS);
leftmostX = gX - 1;
}
return leftmostX;
}
@@ -594,34 +605,52 @@ public:
}
if (have_gps) { int px, py; project(mla, mlo, px, py); miniIconDrawCentered(display, px, py, ICON_MAP_CURRENT); }
// Scale bar (bottom-left): a round distance ~1/3 of the width, drawn with
// fillRect ticks (gfx line isn't available this early in the TU) + a label,
// so the preview conveys rough scale / distance.
// Bottom-left info: distance to the nearest live-tracked point, when we
// have both a fix and at least one active share — a single concrete
// number beats an abstract scale, and as plain text it's far less likely
// to land under a plotted dot than the old full-width bar+ticks were.
// Falls back to a compact one-line scale tick when there's nothing to
// measure against (no fix, or a trail with no live contacts).
{
static const float M_PER_1E6 = 0.11132f; // metres per 1e-6° lat
float ppm = scale / M_PER_1E6; // pixels per metre
if (ppm > 0.0f) {
bool imp = _task->useImperial();
static const float MET_M[] = { 5,10,25,50,100,250,500,1000,2000,5000,10000,25000,50000 };
static const char* MET_L[] = { "5m","10m","25m","50m","100m","250m","500m","1km","2km","5km","10km","25km","50km" };
static const float IMP_M[] = { 4.572f,15.24f,30.48f,76.2f,152.4f,402.34f,804.67f,1609.34f,4828.0f,16093.4f,80467.2f };
static const char* IMP_L[] = { "15ft","50ft","100ft","250ft","500ft","1/4mi","1/2mi","1mi","3mi","10mi","50mi" };
const float* M = imp ? IMP_M : MET_M;
const char* const* L = imp ? IMP_L : MET_L;
int N = imp ? (int)(sizeof(IMP_M) / sizeof(IMP_M[0])) : (int)(sizeof(MET_M) / sizeof(MET_M[0]));
float target = (aw / 3.0f) / ppm; // ~1/3 of the width, in metres
int sel = 0;
for (int i = N - 1; i >= 0; i--) if (M[i] <= target) { sel = i; break; }
int barpx = (int)(M[sel] * ppm + 0.5f);
if (barpx < 6) barpx = 6;
if (barpx > aw - 2) barpx = aw - 2;
int bx = ax + 1, by = ay + ah - 1;
display.setColor(DisplayDriver::LIGHT);
display.fillRect(bx, by, barpx, 1); // bar
display.fillRect(bx, by - 2, 1, 3); // left tick
display.fillRect(bx + barpx - 1, by - 2, 1, 3); // right tick
display.setCursor(bx, by - 2 - display.getLineHeight());
display.print(L[sel]);
float nearest_km = -1.0f;
if (have_gps) {
for (int i = 0; i < LiveTrackStore::CAPACITY; i++) {
if (!lt.isActive(i, now)) continue;
float d = geo::haversineKm(mla, mlo, lt.slotAt(i).lat_1e6, lt.slotAt(i).lon_1e6);
if (nearest_km < 0.0f || d < nearest_km) nearest_km = d;
}
}
display.setColor(DisplayDriver::LIGHT);
int ty = ay + ah - display.getLineHeight();
if (nearest_km >= 0.0f) {
char dist[12], buf[16];
geo::fmtDist(dist, sizeof(dist), nearest_km, _task->useImperial());
snprintf(buf, sizeof(buf), "->%s", dist);
display.setCursor(ax + 1, ty);
display.print(buf);
} else {
static const float M_PER_1E6 = 0.11132f; // metres per 1e-6° lat
float ppm = scale / M_PER_1E6; // pixels per metre
if (ppm > 0.0f) {
bool imp = _task->useImperial();
static const float MET_M[] = { 5,10,25,50,100,250,500,1000,2000,5000,10000,25000,50000 };
static const char* MET_L[] = { "5m","10m","25m","50m","100m","250m","500m","1km","2km","5km","10km","25km","50km" };
static const float IMP_M[] = { 4.572f,15.24f,30.48f,76.2f,152.4f,402.34f,804.67f,1609.34f,4828.0f,16093.4f,80467.2f };
static const char* IMP_L[] = { "15ft","50ft","100ft","250ft","500ft","1/4mi","1/2mi","1mi","3mi","10mi","50mi" };
const float* M = imp ? IMP_M : MET_M;
const char* const* L = imp ? IMP_L : MET_L;
int N = imp ? (int)(sizeof(IMP_M) / sizeof(IMP_M[0])) : (int)(sizeof(MET_M) / sizeof(MET_M[0]));
float target = 8.0f / ppm; // short reference tick, not 1/3 of the width
int sel = 0;
for (int i = N - 1; i >= 0; i--) if (M[i] <= target) { sel = i; break; }
int barpx = (int)(M[sel] * ppm + 0.5f);
if (barpx < 5) barpx = 5;
if (barpx > aw / 4) barpx = aw / 4;
int bx = ax + 1, mid = ty + display.getLineHeight() / 2;
display.fillRect(bx, mid, barpx, 1); // single tick, on the text baseline
display.setCursor(bx + barpx + 2, ty);
display.print(L[sel]);
}
}
}
return true;
@@ -1036,28 +1065,12 @@ public:
int area_h = info_y - content_y - 2;
bool drew = drawMapPreview(display, 2, content_y, display.width() - 4, area_h);
char info[40];
int32_t la, lo;
bool fix = _task->currentLocation(la, lo);
uint32_t now_m = rtc_clock.getCurrentTime();
LiveTrackStore& lt = _task->liveTrack();
int trk = lt.active(now_m);
// Distance to the nearest tracked contact, so the page also answers
// "how far is the closest shared point".
float nearest = -1.0f;
if (fix) {
for (int i = 0; i < LiveTrackStore::CAPACITY; i++) {
if (!lt.isActive(i, now_m)) continue;
float d = geo::haversineKm(la, lo, lt.slotAt(i).lat_1e6, lt.slotAt(i).lon_1e6);
if (nearest < 0 || d < nearest) nearest = d;
}
}
if (nearest >= 0) {
char ds[12];
geo::fmtDist(ds, sizeof(ds), nearest, _task->useImperial());
snprintf(info, sizeof(info), "Fix:%s Trk:%d %s", fix ? "y" : "n", trk, ds);
} else {
snprintf(info, sizeof(info), "Fix:%s Track:%d", fix ? "y" : "n", trk);
}
// Fix state now lives in the top-bar GPS icon and distance-to-nearest
// inside the preview itself, so this line just covers track count.
snprintf(info, sizeof(info), "Track:%d", trk);
display.setColor(DisplayDriver::LIGHT);
if (!drew)
display.drawTextCentered(display.width() / 2, content_y + area_h / 2, "No GPS / no trail");

View File

@@ -165,6 +165,13 @@ MINI_ICON(ICON_REPEATER, 6, // » double chevron — relaying/forwarding (repe
packRow(".#..#."),
packRow("#..#.."));
MINI_ICON(ICON_GPS, 5, // reticle — GPS fix status (boxed = fix, plain = searching)
packRow(".###."),
packRow("#...#"),
packRow("#.#.#"),
packRow("#...#"),
packRow(".###."));
// Tools-menu glyphs (auto-reply bot, ringtone editor, diagnostics, system).
MINI_ICON(ICON_BOT, 5, // robot head: antenna + eyes + grille (auto-reply bot)
packRow("..#.."),
@@ -238,8 +245,8 @@ MINI_ICON(ICON_PG_ADVERT, 5, // mast + radiating waves — advert
packRow("..#.."));
MINI_ICON(ICON_PG_GPS, 5, // location pin — GPS
packRow(".###."),
packRow("#...#"),
packRow("#...#"),
packRow("#.#.#"),
packRow("#.#.#"),
packRow(".#.#."),
packRow("..#.."));
MINI_ICON(ICON_PG_SENSORS, 5, // thermometer/gauge — sensors