Files
MeshCore-Solo/examples/companion_radio/ui-new/CompassScreen.h
Jakub 9da730e3b6 refactor(ui): shared drawList + header + key-decode helpers
- drawList(): scrollable list skeleton (visible window, keep-sel-in-view,
  scroll-column reserve, per-row callback, indicator). Migrate Tools, Bot,
  Nearby, Waypoints list and the three QuickMsg lists onto it; centralises the
  scroll-clamp/reserve/indicator that each had open-coded (and inconsistently).
- drawCenteredHeader(): centred title + separator, replacing the duplicated
  two-liner across ~10 screens (incl. QuickMsg, which used width()/2 spacing and
  was missed before); unifies the separator at headerH()-sepH().
- keyIsPrev()/keyIsNext() in UIScreen.h for the LEFT||PREV / RIGHT||NEXT idiom;
  applied to 6 screens, behaviour-preserving. Cancel/context-menu stay
  per-screen (they mean different things on different screens).
- rotLabel() dedups the 0/90/180/270 array in Settings.
- Unify selection-row height to lineStep()-1 (Tools/Bot matched the rest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00

119 lines
4.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
// Tools Compass. A standalone heading view: shows the device's course over
// ground (derived from GPS movement — there is no magnetometer) as a horizontal
// scrolling tape (N..E..S..W) under a fixed travel-direction marker, plus a
// large numeric degrees + cardinal readout. When standing still the heading is
// undefined, so it shows a hint to move.
//
// A linear tape (no trig) reads well in the short vertical space of the OLED,
// where a circular dial leaves only a few-pixel needle.
//
// Reuses UITask's COG ring (currentCourse) — works whether or not a trail is
// being recorded.
#include "../GeoUtils.h"
class CompassScreen : public UIScreen {
UITask* _task;
bool gpsValid() const { int32_t lat, lon; return _task->currentLocation(lat, lon); }
static const char* cardinalLetter(int deg) {
switch (deg) { case 0: return "N"; case 90: return "E";
case 180: return "S"; case 270: return "W"; }
return "";
}
public:
CompassScreen(UITask* task) : _task(task) {}
void enter() {}
int render(DisplayDriver& display) override {
display.setTextSize(1);
display.setColor(DisplayDriver::LIGHT);
display.drawCenteredHeader("COMPASS");
const int hdr = display.headerH();
const int cx = display.width() / 2;
const int ch = display.getLineHeight();
const int cw = display.getCharWidth();
// Reserve a size-2 row at the bottom for the numeric readout.
display.setTextSize(2);
const int bigH = display.getLineHeight();
display.setTextSize(1);
const int top = hdr + 2;
const int readout_y = display.height() - bigH - 1; // size-2 readout baseline
const int mid = (top + readout_y) / 2;
if (!gpsValid()) {
display.drawTextCentered(cx, mid - ch / 2, "No GPS fix");
return 1000;
}
int cog;
bool have = _task->currentCourse(cog);
if (!have) {
display.drawTextCentered(cx, mid - ch, "move to");
display.drawTextCentered(cx, mid, "set heading");
return 1000;
}
// ── heading tape ─────────────────────────────────────────────────────────
// Centre of the tape = current course; bearings spread left/right at a fixed
// pixels-per-degree. HALF_SPAN degrees reach each edge.
const int area_x = 2;
const int area_w = display.width() - 4;
const float HALF_SPAN = 80.0f; // degrees visible to each side
const float ppd = (area_w / 2.0f) / HALF_SPAN;
const int pointerH = 6;
const int tickMajor = 6;
const int tickMinor = 3;
const int blockH = pointerH + 1 + tickMajor + 2 + ch;
const int block_top = top + ((readout_y - top) - blockH) / 2;
const int line_y = block_top + pointerH; // scale baseline
const int label_y = line_y + 1 + tickMajor + 2;
const int x_lo = area_x, x_hi = area_x + area_w;
// Scale baseline.
display.fillRect(area_x, line_y, area_w, 1);
// Ticks + cardinal labels every 30°; majors (N/E/S/W) are taller + labelled.
for (int deg = 0; deg < 360; deg += 30) {
int delta = deg - cog;
while (delta > 180) delta -= 360;
while (delta < -180) delta += 360;
int x = cx + (int)(delta * ppd);
if (x < x_lo || x > x_hi) continue;
bool major = (deg % 90 == 0);
display.fillRect(x, line_y + 1, 1, major ? tickMajor : tickMinor);
if (major) {
const char* s = cardinalLetter(deg);
display.setCursor(x - cw / 2, label_y);
display.print(s);
}
}
// Fixed travel-direction pointer: a downward triangle whose tip touches the
// baseline at screen centre (your current course always sits under it).
for (int i = 0; i < pointerH; i++) {
int half = pointerH - 1 - i;
display.fillRect(cx - half, block_top + i, 2 * half + 1, 1);
}
// ── numeric readout ──────────────────────────────────────────────────────
char buf[16];
snprintf(buf, sizeof(buf), "%d %s", cog, geo::bearingCardinal(cog));
display.setTextSize(2);
display.drawTextCentered(cx, readout_y, buf);
display.setTextSize(1);
return 1000;
}
bool handleInput(char c) override {
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) { _task->gotoToolsScreen(); return true; }
return true;
}
};