diff --git a/examples/companion_radio/ui-new/CompassScreen.h b/examples/companion_radio/ui-new/CompassScreen.h new file mode 100644 index 00000000..4d0026a0 --- /dev/null +++ b/examples/companion_radio/ui-new/CompassScreen.h @@ -0,0 +1,106 @@ +#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 + +class CompassScreen : public UIScreen { + UITask* _task; + + static bool gpsValid() { +#if ENV_INCLUDE_GPS == 1 + LocationProvider* loc = sensors.getLocationProvider(); + return loc && loc->isValid(); +#else + return false; +#endif + } + + static void drawLine(DisplayDriver& d, int x0, int y0, int x1, int y1) { + int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy; + while (true) { + d.fillRect(x0, y0, 1, 1); + if (x0 == x1 && y0 == y1) break; + int e2 = err * 2; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } + } + +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; + // 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; + int r = ((bottom - top) / 2); + int rx = (display.width() / 2) - 4; + if (rx < r) r = rx; + if (r < 6) r = 6; + + if (!gpsValid()) { + display.drawTextCentered(cx, cy - display.getLineHeight() / 2, "No GPS fix"); + return 1000; + } + + int cog; + bool have = _task->currentCourse(cog); + + // Compass ring ticks: N/E/S/W as short marks; N drawn as a filled wedge top. + display.drawRect(cx - r, cy - r, 2 * r + 1, 2 * r + 1); // bounding box stands in for a ring + display.drawTextCentered(cx, cy - r - 1, "N"); + + if (!have) { + display.drawTextCentered(cx, cy - display.getLineHeight() / 2, "move to"); + display.drawTextCentered(cx, cy + display.getLineHeight() / 2, "set heading"); + return 1000; + } + + // Arrow from centre toward the course (0° = up = north, clockwise). + float rad = cog * (float)M_PI / 180.0f; + int ex = cx + (int)(sinf(rad) * (r - 2)); + int ey = cy - (int)(cosf(rad) * (r - 2)); + drawLine(display, cx, cy, ex, ey); + // Small arrowhead: two short lines back from the tip at ±150°. + for (int da = -25; da <= 25; da += 50) { + float a = rad + (float)M_PI + da * (float)M_PI / 180.0f; + int hx = ex + (int)(sinf(a) * 4); + int hy = ey - (int)(cosf(a) * 4); + drawLine(display, ex, ey, hx, hy); + } + + // Numeric readout below the ring. + 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; + } +}; diff --git a/examples/companion_radio/ui-new/ToolsScreen.h b/examples/companion_radio/ui-new/ToolsScreen.h index ae478e29..2dc7c6e8 100644 --- a/examples/companion_radio/ui-new/ToolsScreen.h +++ b/examples/companion_radio/ui-new/ToolsScreen.h @@ -6,7 +6,7 @@ class ToolsScreen : public UIScreen { UITask* _task; int _sel; - static const int ITEM_COUNT = 5; + static const int ITEM_COUNT = 6; static const char* ITEMS[ITEM_COUNT]; public: @@ -45,8 +45,9 @@ public: if (_sel == 2) { _task->gotoNearbyScreen(); return true; } if (_sel == 3) { _task->gotoAutoAdvertScreen(); return true; } if (_sel == 4) { _task->gotoTrailScreen(); return true; } + if (_sel == 5) { _task->gotoCompassScreen(); return true; } } return false; } }; -const char* ToolsScreen::ITEMS[5] = { "Ringtone Editor", "Auto-Reply Bot", "Nearby Nodes", "Auto-Advert", "Trail" }; +const char* ToolsScreen::ITEMS[6] = { "Ringtone Editor", "Auto-Reply Bot", "Nearby Nodes", "Auto-Advert", "Trail", "Compass" }; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index c5627f46..2db6ce7d 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -118,6 +118,7 @@ static const int QUICK_MSGS_MAX = 10; #include "DashboardConfigScreen.h" #include "AutoAdvertScreen.h" #include "TrailScreen.h" +#include "CompassScreen.h" #include "ToolsScreen.h" #ifndef BATT_MIN_MILLIVOLTS @@ -1189,6 +1190,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no dashboard_config = new DashboardConfigScreen(this, node_prefs); auto_advert_screen = new AutoAdvertScreen(this, node_prefs); trail_screen = new TrailScreen(this, &_trail); + compass_screen = new CompassScreen(this); applyBrightness(); applyFont(); applyRotation(); @@ -1230,6 +1232,11 @@ void UITask::gotoTrailScreen() { setCurrScreen(trail_screen); } +void UITask::gotoCompassScreen() { + ((CompassScreen*)compass_screen)->enter(); + setCurrScreen(compass_screen); +} + void UITask::gotoAutoAdvertScreen() { ((AutoAdvertScreen*)auto_advert_screen)->enter(); setCurrScreen(auto_advert_screen); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index b97178e8..108a31da 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -76,6 +76,7 @@ class UITask : public AbstractUITask { UIScreen* dashboard_config; UIScreen* auto_advert_screen; UIScreen* trail_screen; + UIScreen* compass_screen; UIScreen* curr; CayenneLPP _dash_lpp; TrailStore _trail; @@ -144,6 +145,7 @@ public: void gotoDashboardConfig(); void gotoAutoAdvertScreen(); void gotoTrailScreen(); + void gotoCompassScreen(); TrailStore& trail() { return _trail; } WaypointStore& waypoints() { return _waypoints; } void saveWaypoints(); // persist the table to /waypoints