Files
MeshCore-Solo/examples/companion_radio/ui-new/CompassScreen.h
T
JakubandClaude Opus 4.8 0f2399009b fix(compass): shrink ring so rotating cardinals sit outside it
The N/E/S/W letters rode on the ring radius, so they overlapped the circle
as the card rotated. Place the letters on an outer radius and draw the ring
a glyph-height smaller, leaving a clear gap between them. The forward index
now sits in that gap, just outside the ring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:57:03 +02:00

113 lines
4.2 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 rotating
// arrow plus a numeric degrees + cardinal readout. When standing still the
// heading is undefined, so it shows a hint to move.
//
// Reuses UITask's COG ring (currentCourse) — works whether or not a trail is
// being recorded.
#include "../GeoUtils.h"
#include "GfxUtils.h"
#include <math.h>
class CompassScreen : public UIScreen {
UITask* _task;
bool gpsValid() const { int32_t lat, lon; return _task->currentLocation(lat, lon); }
public:
CompassScreen(UITask* task) : _task(task) {}
void enter() {}
int render(DisplayDriver& display) override {
display.setTextSize(1);
display.setColor(DisplayDriver::LIGHT);
display.drawTextCentered(display.width() / 2, 0, "COMPASS");
display.fillRect(0, display.headerH() - 1, display.width(), display.sepH());
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 bottom = display.height() - bigH - 2;
const int cy = (top + bottom) / 2;
// The cardinal letters ride on the outer radius; the ring is drawn a glyph
// smaller so the rotating letters sit just outside it and never touch it.
int outer = (bottom - top) / 2;
int outerx = display.width() / 2 - 2;
if (outerx < outer) outer = outerx;
int lr = outer - ch / 2 - 1; // radius of the cardinal-letter centres
int r = lr - ch / 2 - 3; // ring radius (clear gap below the letters)
if (r < 6) { r = 6; lr = r + ch / 2 + 3; }
if (!gpsValid()) {
display.drawTextCentered(cx, cy - display.getLineHeight() / 2, "No GPS fix");
return 1000;
}
int cog;
bool have = _task->currentCourse(cog);
gfx::drawCircle(display, cx, cy, r); // compass ring
// 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) {
// No stable heading yet — keep the ring as context but put the hint in
// the bottom readout band so it doesn't overlap the circle.
int lh = display.getLineHeight();
display.drawTextCentered(cx, bottom + 1, "move to");
display.drawTextCentered(cx, bottom + 1 + lh, "set heading");
return 1000;
}
// 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);
gfx::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 = nrad + (float)M_PI + da * (float)M_PI / 180.0f;
gfx::drawLine(display, nx, ny, nx + (int)(sinf(a) * 4), ny - (int)(cosf(a) * 4));
}
// Cardinal letters just outside the ring (N over the needle tip).
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, lr, 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);
display.drawTextCentered(cx, bottom + 1, 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;
}
};