mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
Tick marks, the scale baseline and the travel-direction pointer triangle were hardcoded pixel constants, so they shrank to near-invisible slivers next to the 2x cardinal labels and numeric readout on landscape e-ink. Same fix pattern as the trail map / keyboard separator: derive sizes from miniIconScale()/sepH() instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
127 lines
5.1 KiB
C++
127 lines
5.1 KiB
C++
#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 enter() {}
|
||
|
||
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;
|
||
}
|
||
};
|