Files
MeshCore-Solo/examples/companion_radio/ui-new/CompassScreen.h

127 lines
5.1 KiB
C
Raw Normal View History

#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"
#include "icons.h" // miniIconScale() — keeps the tape ticks/pointer legible on e-ink
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 onShow() override {}
int render(DisplayDriver& display) override {
display.setTextSize(1);
display.setColor(DisplayDriver::LIGHT);
display.drawCenteredHeader("COMPASS");
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 = display.listStart();
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;
// Tick/pointer sizes are authored at 1x and scaled by the font (same factor
// the mini-icon framework uses), so they stay legible next to the 2x
// cardinal labels and numeric readout on landscape e-ink.
const int s = miniIconScale(display);
const int sh = display.sepH(); // baseline thickness (2px landscape e-ink, else 1)
const int pointerH_units = 6;
const int pointerH = pointerH_units * s;
const int tickMajor = 6 * s;
const int tickMinor = 3 * s;
const int blockH = pointerH + sh + 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 + sh + tickMajor + 2;
const int x_lo = area_x, x_hi = area_x + area_w;
// Scale baseline.
display.fillRect(area_x, line_y, area_w, sh);
// 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 - s / 2, line_y + sh, s, major ? tickMajor : tickMinor);
if (major) {
const char* letter = cardinalLetter(deg);
display.setCursor(x - cw / 2, label_y);
display.print(letter);
}
}
// Fixed travel-direction pointer: a downward triangle whose tip touches the
// baseline at screen centre (your current course always sits under it).
// Each of the pointerH_units logical rows is drawn as an s-pixel-tall strip
// — the same block-scaling miniIconDraw uses for bitmap icons.
for (int i = 0; i < pointerH_units; i++) {
int half = (pointerH_units - 1 - i) * s;
display.fillRect(cx - half, block_top + i * s, 2 * half + 1, s);
}
// ── 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;
}
};