mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
feat(ui): a turn dial / compass rose on narrow navigation screens
navview::draw (Waypoints, trail back, Nearby, a message's location) shows a dial beside the lines, or under them on a tall screen (portrait e-ink) where that makes it larger: with a course a turn dial (the top is where you're heading, the arrow points at the target, "Turn R 55"), without it the compass rose. The 4.2" screens keep theirs. On a short screen (the 64 px OLED) the gap under the distance and the pitch of the lines give way so the ETA is not cut off at the bottom. sim_nav_waypoint() opens a waypoint's navigate view from the simulator page. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
15ac4d8f83
commit
52812d8235
@@ -138,6 +138,18 @@ inline void arrow(DisplayDriver& d, int cx, int cy, int r, int deg) {
|
||||
}
|
||||
}
|
||||
|
||||
// A turn dial of radius r round (cx, cy): the way you're heading is the top
|
||||
// (the mark in from the ring), and an arrow points at where to go from there
|
||||
// (`deg` clockwise from the top, 0 = straight on) -- turn until it points up.
|
||||
// `ahead` doubles the ring: you're on course.
|
||||
inline void turnDial(DisplayDriver& d, int cx, int cy, int r, int deg, bool ahead) {
|
||||
const int s = miniIconScale(d);
|
||||
circle(d, cx, cy, r);
|
||||
if (ahead) circle(d, cx, cy, r - d.sepH() - 1);
|
||||
d.fillRect(cx - s, cy - r + d.sepH() + 1, 2 * s, 3 * s + 1); // the top mark
|
||||
arrow(d, cx, cy + s, r - 4 * s - 2, deg); // a touch low: clear of the mark
|
||||
}
|
||||
|
||||
// A compass rose of radius r round (cx, cy), north up: the ring, a tick in at
|
||||
// each cardinal point, N in a gap at the top, and -- for deg >= 0 -- a needle
|
||||
// from the centre to the ring, towards deg.
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
namespace navview {
|
||||
|
||||
static const int AHEAD_DEG = 12; // within this of straight on: "ahead"
|
||||
|
||||
// own_valid : is there a usable GPS fix for the device right now
|
||||
// cog_valid : is there a stable course-over-ground (UITask::currentCourse)
|
||||
// eta : optional closing-speed/ETA tracker; when supplied a third line is
|
||||
@@ -41,21 +43,63 @@ inline void draw(DisplayDriver& d,
|
||||
char dist[12];
|
||||
geo::fmtDist(dist, sizeof(dist), dist_km, imperial);
|
||||
|
||||
const int step = d.lineStep();
|
||||
const int lh = d.getLineHeight(), cw = d.getCharWidth();
|
||||
int step = d.lineStep();
|
||||
int y = d.listStart();
|
||||
// A screen 40 characters wide (the 4.2"): the lines down the left, and a
|
||||
// compass rose (north up) at the right with an arrow to the target and a
|
||||
// mark on the rim where you're heading.
|
||||
const bool wide = d.width() >= 40 * d.getCharWidth();
|
||||
const bool wide = d.width() >= 40 * cw;
|
||||
|
||||
// The distance at size 2, then `lines` under it (turn / To or To / Hdg, and
|
||||
// the ETA). On a short screen (the 64 px OLED) the last would run off the
|
||||
// bottom: the gap under the distance goes first, then the pitch of the lines.
|
||||
const int lines = eta ? 3 : 2;
|
||||
d.setTextSize(2);
|
||||
const int dist_h = d.getLineHeight(), dw = d.getTextWidth(dist);
|
||||
d.setTextSize(1);
|
||||
int dist_gap = 3;
|
||||
int over = y + dist_h + dist_gap + (lines - 1) * step + lh - d.height();
|
||||
if (over > 0) { const int g = over < dist_gap ? over : dist_gap; dist_gap -= g; over -= g; }
|
||||
if (over > 0) { step -= (over + lines - 2) / (lines - 1); if (step < lh) step = lh; }
|
||||
|
||||
// A narrow screen: a dial, the lines down the left. With a course it's a
|
||||
// turn dial (the top is where you're heading, turn until the arrow points
|
||||
// up); without, the compass rose (north up). Beside the lines, or under them
|
||||
// where the screen is tall (the portrait e-ink) and that makes it larger.
|
||||
// Too small for one (a very short screen): the text alone, as before.
|
||||
int dial_r = 0, dial_x = 0, dial_y = 0;
|
||||
if (!wide) {
|
||||
const int top = d.listStart();
|
||||
const int tw = (dw > 9 * cw ? dw : 9 * cw) + 4; // the widest of the lines at the left
|
||||
int r_side = (d.height() - top) / 2 - lh / 2 - 1; // room over the ring for the rose's N
|
||||
if (r_side > (d.width() - tw) / 2 - 1) r_side = (d.width() - tw) / 2 - 1;
|
||||
const int text_bottom = y + dist_h + dist_gap + lines * step;
|
||||
int r_below = (d.height() - text_bottom) / 2 - lh / 2 - 2;
|
||||
if (r_below > d.width() / 2 - 3) r_below = d.width() / 2 - 3;
|
||||
if (r_below > 50) r_below = 50;
|
||||
if (r_below >= 14 && r_below > r_side) {
|
||||
dial_r = r_below;
|
||||
dial_x = d.width() / 2;
|
||||
dial_y = text_bottom + (d.height() - text_bottom) / 2 + lh / 4;
|
||||
} else if (r_side >= 14) {
|
||||
dial_r = r_side;
|
||||
dial_x = d.width() - r_side - 2;
|
||||
dial_y = top + (d.height() - top) / 2 + lh / 4;
|
||||
}
|
||||
}
|
||||
int turn = 0; // -180..179 degrees to turn, right positive; with a course
|
||||
if (cog_valid) turn = ((to_deg - cog_deg + 540) % 360) - 180;
|
||||
const bool ahead = cog_valid && turn <= AHEAD_DEG && turn >= -AHEAD_DEG;
|
||||
|
||||
// Distance — emphasised at size 2.
|
||||
d.setTextSize(2);
|
||||
if (wide) { d.setCursor(2, y); d.print(dist); }
|
||||
else d.drawTextCentered(cx, y, dist);
|
||||
y += d.getLineHeight() + 3;
|
||||
if (wide || dial_r) { d.setCursor(2, y); d.print(dist); }
|
||||
else d.drawTextCentered(cx, y, dist);
|
||||
y += dist_h + dist_gap;
|
||||
d.setTextSize(1);
|
||||
if (wide) {
|
||||
const int lh = d.getLineHeight(), top = d.listStart();
|
||||
const int top = d.listStart();
|
||||
const int r = (d.height() - top) / 2 - lh;
|
||||
const int rx = d.width() - r - lh, ry = top + (d.height() - top) / 2 + lh / 4;
|
||||
info::rose(d, rx, ry, r, to_deg);
|
||||
@@ -72,12 +116,23 @@ inline void draw(DisplayDriver& d,
|
||||
}
|
||||
|
||||
char line[20];
|
||||
if (dial_r) {
|
||||
if (cog_valid) info::turnDial(d, dial_x, dial_y, dial_r, turn, ahead);
|
||||
else info::rose(d, dial_x, dial_y, dial_r, to_deg);
|
||||
}
|
||||
if (dial_r && cog_valid) { // how far to turn says what "To" and "Hdg" leave to be subtracted
|
||||
if (ahead) snprintf(line, sizeof(line), "Straight on");
|
||||
else snprintf(line, sizeof(line), "Turn %c %d", turn > 0 ? 'R' : 'L', turn > 0 ? turn : -turn);
|
||||
d.drawTextLeftAlign(2, y, line); y += step;
|
||||
}
|
||||
snprintf(line, sizeof(line), "To: %d %s", to_deg, geo::bearingCardinal(to_deg));
|
||||
d.drawTextLeftAlign(2, y, line); y += step;
|
||||
|
||||
if (cog_valid) snprintf(line, sizeof(line), "Hdg: %d %s", cog_deg, geo::bearingCardinal(cog_deg));
|
||||
else snprintf(line, sizeof(line), "Hdg: --");
|
||||
d.drawTextLeftAlign(2, y, line); y += step;
|
||||
if (!(dial_r && cog_valid)) {
|
||||
if (cog_valid) snprintf(line, sizeof(line), "Hdg: %d %s", cog_deg, geo::bearingCardinal(cog_deg));
|
||||
else snprintf(line, sizeof(line), "Hdg: --");
|
||||
d.drawTextLeftAlign(2, y, line); y += step;
|
||||
}
|
||||
|
||||
// Optional ETA / closing-speed line. Approaching only — a receding or
|
||||
// stationary target shows "ETA: --" rather than a misleading time.
|
||||
|
||||
@@ -117,6 +117,7 @@ public:
|
||||
|
||||
// Forwarded to UITask's GPS duty-cycle hold — see WaypointsView::needsLiveGps().
|
||||
bool wpNeedsLiveGps() const { return _wp.needsLiveGps(); }
|
||||
void simWaypointNav(int row) { _wp.simNav(row); } // sim tests
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
display.setTextSize(1);
|
||||
|
||||
@@ -472,6 +472,11 @@ void UITask::simNodes() {
|
||||
|
||||
// Sim tests: a walked trail ending at the GPS fix of simNodes(), and two
|
||||
// waypoints beside it, for the maps.
|
||||
void UITask::simNavWaypoint(int row) {
|
||||
setCurrScreen(trail_screen);
|
||||
((TrailScreen*)trail_screen)->simWaypointNav(row);
|
||||
}
|
||||
|
||||
void UITask::simTrail() {
|
||||
TrailStore& tr = _core->trail.store();
|
||||
const uint32_t now = rtc_clock.getCurrentTime();
|
||||
@@ -966,6 +971,10 @@ extern "C" EMSCRIPTEN_KEEPALIVE void sim_nodes() {
|
||||
if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->simNodes();
|
||||
}
|
||||
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE void sim_nav_waypoint(int row) {
|
||||
if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->simNavWaypoint(row);
|
||||
}
|
||||
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE void sim_trail() {
|
||||
if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->simTrail();
|
||||
}
|
||||
|
||||
@@ -326,6 +326,7 @@ public:
|
||||
void simUnread(int which); // sim tests: read and new messages, opened
|
||||
void simDisplay(bool on); // sim tests: the display off / on
|
||||
void simNodes(); // sim tests: you and nodes with positions, Nodes open
|
||||
void simNavWaypoint(int row); // sim tests: the navigate view of a waypoint (after simTrail)
|
||||
void simTrail(); // sim tests: a trail and two waypoints for the maps
|
||||
#endif
|
||||
void openContactDM(const ContactInfo& ci);
|
||||
|
||||
@@ -284,6 +284,7 @@ public:
|
||||
|
||||
// Entry points called from TrailScreen's action menu.
|
||||
void openList() { _mode = LIST; _sel = 0; _scroll = 0; }
|
||||
void simNav(int row) { _mode = NAV; _sel = row; _scroll = 0; _wp_eta.reset(); } // sim tests: its navigate view
|
||||
void markHere() {
|
||||
int32_t lat, lon;
|
||||
if (!ownPos(lat, lon)) { _task->showAlert("No GPS fix", 1000); return; }
|
||||
|
||||
Reference in New Issue
Block a user