Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cafb00d
feat: add radar minimap screen showing nearby nodes by bearing and di…
egorsiniaev Apr 13, 2026
7498c29
feat(radar): add IMU heading support for heading-up orientation
egorsiniaev Apr 14, 2026
fb648ad
feat(radar): maximise screen use for 128x64 OLED
egorsiniaev Apr 14, 2026
b89e057
feat(radar): round ring labels, info panel, custom icon, menu toggle,…
egorsiniaev Apr 15, 2026
fcdff2b
feat(radar): fix panel overflow, add cross marker for closest node
egorsiniaev Apr 15, 2026
6dfc4dd
feat(radar): 5 stable node symbols matching radar dots to panel list
egorsiniaev Apr 15, 2026
c46121d
feat(radar): use all 4 panel rows for node list, remove scale/count rows
egorsiniaev Apr 15, 2026
f4f9261
feat(radar): show up to 5 nodes by tightening row pitch to contentH/5
egorsiniaev Apr 15, 2026
e02b16e
refactor(radar): merge radar into compass screen as a long-press toggle
egorsiniaev Apr 21, 2026
884ecde
fix(radar): move Radar View to top of position menu, show 5 nodes, 1p…
egorsiniaev Apr 21, 2026
7c86a47
fix(radar): scale from plotted nodes only; show outer-ring range label
egorsiniaev May 11, 2026
73a5ec5
feat(radar): Compass Face picker, favorites filter, unique markers
egorsiniaev May 11, 2026
6542ea7
refactor(radar): swap radar onto bearings frame, rename to Tracking View
egorsiniaev May 11, 2026
6daed66
feat(radar): move scale label to header title
egorsiniaev May 12, 2026
1cfd24f
fix(radar): adapt to flattened NodeInfoLite schema
egorsiniaev May 12, 2026
4623f28
Merge branch 'develop' into feat/radar-node-view
egorsiniaev May 12, 2026
40ec6c9
Merge branch 'develop' into feat/radar-node-view
egorsiniaev May 15, 2026
df0af76
fix(radar): reserve footer space so 5th list row clears BT link icon
egorsiniaev May 17, 2026
7ec9d1c
fix(radar): one consistent layout, 1 px gap above BT link icon
egorsiniaev May 17, 2026
e68a7a3
fix(radar): full-size circle + reserved list area so 5th row clears icon
egorsiniaev May 17, 2026
da026c3
fix(radar): tighten list to use descender gap, center markers vertically
egorsiniaev May 17, 2026
6c7e920
fix(radar): preserve radar arc and last list row by skipping footer wipe
egorsiniaev May 17, 2026
4f7449b
Revert "fix(radar): preserve radar arc and last list row by skipping …
egorsiniaev May 17, 2026
a21b4a4
fix(radar): draw BT/API icon inside the overlay to avoid the footer wipe
egorsiniaev May 17, 2026
6971863
Merge remote-tracking branch 'origin/develop' into feat/radar-node-view
egorsiniaev May 17, 2026
ecf3fb1
Merge remote-tracking branch 'personal/feat/radar-node-view' into fea…
egorsiniaev May 17, 2026
60574c5
fix(radar): make ourNode pointer const to satisfy cppcheck
egorsiniaev May 17, 2026
79e6264
style(radar): apply clang-format to fix trunk CI
egorsiniaev May 17, 2026
3cf951c
Merge remote-tracking branch 'origin/develop' into feat/radar-node-view
egorsiniaev Jun 1, 2026
02eadc1
feat(radar): 10 nodes, ring labels, 8 tick marks, padding, new symbols
egorsiniaev Jun 2, 2026
5c1b578
feat(radar): adapt node count and decorations to screen size
egorsiniaev Jun 2, 2026
b002495
fix(radar): reposition N between rings 2-3, remove ticks, unitless ri…
egorsiniaev Jun 2, 2026
cde583c
fix(radar): 5px list top pad, smaller ring labels, 3 rings, N closer …
egorsiniaev Jun 2, 2026
7485ab5
fix(radar): use const not constexpr for runtime font height
egorsiniaev Jun 2, 2026
525828a
fix(radar): place ring labels opposite N, rotate with heading
egorsiniaev Jun 2, 2026
4d6109c
Merge remote-tracking branch 'origin/develop' into feat/radar-node-view
egorsiniaev Jun 3, 2026
0b4a522
style(radar): apply clang-format
egorsiniaev Jun 3, 2026
8f663e9
Merge remote-tracking branch 'origin/develop' into feat/radar-node-view
egorsiniaev Jun 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/graphics/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2164,6 +2164,12 @@ int Screen::handleInputEvent(const InputEvent *event)
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
menuHandler::favoriteBaseMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_location &&
uiconfig.bearings_view_radar) {
// Bearings/distance frame is being drawn as a radar — use the
// radar-specific options menu (zoom, heading, favorites filter,
// Tracking View picker).
menuHandler::radarBearingsMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_nodes ||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_location ||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
Expand Down
119 changes: 117 additions & 2 deletions src/graphics/draw/MenuHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "graphics/SharedUIDisplay.h"
#include "graphics/TFTColorRegions.h"
#include "graphics/draw/MessageRenderer.h"
#include "graphics/draw/RadarRenderer.h"
#include "graphics/draw/UIRenderer.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
Expand Down Expand Up @@ -556,6 +557,57 @@ void menuHandler::showConfirmationBanner(const char *message, std::function<void
screen->showOverlayBanner(confirmBanner);
}

// Where trackingViewPicker should return when the user picks "Back". Set by
// the calling menu (nodeListMenu or radarBearingsMenu) just before queueing
// the picker, so back behaves like the Clock Face → Clock Menu flow.
static menuHandler::screenMenus s_trackingViewPickerReturn = menuHandler::MenuNone;

void menuHandler::setTrackingViewPickerReturn(screenMenus target)
{
s_trackingViewPickerReturn = target;
}

void menuHandler::trackingViewPicker()
{
// Mirrors clockFacePicker: pick which view the bearings/distance frame
// shows. Stored in uiconfig.bearings_view_radar (false=Bearings list,
// true=Radar overlay).
static const ClockFaceOption trackingOptions[] = {
{"Back", OptionsAction::Back},
{"Bearings", OptionsAction::Select, false},
{"Radar", OptionsAction::Select, true},
};

constexpr size_t trackingCount = sizeof(trackingOptions) / sizeof(trackingOptions[0]);
static std::array<const char *, trackingCount> trackingLabels{};

auto bannerOptions = createStaticBannerOptions("Tracking View?", trackingOptions, trackingLabels,
[](const ClockFaceOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = s_trackingViewPickerReturn;
if (s_trackingViewPickerReturn != MenuNone)
screen->runNow();
return;
}

if (!option.hasValue) {
return;
}

if (uiconfig.bearings_view_radar == option.value) {
return;
}

uiconfig.bearings_view_radar = option.value;
menuHandler::saveUIConfig();
screen->setFrames(Screen::FOCUS_PRESERVE);
screen->runNow();
});

bannerOptions.InitialSelected = uiconfig.bearings_view_radar ? 2 : 1;
screen->showOverlayBanner(bannerOptions);
}

void menuHandler::clockFacePicker()
{
static const ClockFaceOption clockFaceOptions[] = {
Expand Down Expand Up @@ -1350,7 +1402,7 @@ void menuHandler::positionBaseMenu()
CompassCalibrate,
GPSSmartPosition,
GPSUpdateInterval,
GPSPositionBroadcast
GPSPositionBroadcast,
};

static const PositionMenuOption baseOptions[] = {
Expand Down Expand Up @@ -1445,16 +1497,72 @@ void menuHandler::positionBaseMenu()
screen->showOverlayBanner(bannerOptions);
}

void menuHandler::radarBearingsMenu()
{
enum optionsNumbers { Back, TrackingView, ToggleHeading, ToggleFavorites, ZoomIn, ZoomOut };
static const char *optionsArray[] = {
"Back", "Tracking View",
nullptr, // ToggleHeading — filled dynamically below
nullptr, // ToggleFavorites — filled dynamically below
"Zoom In", "Zoom Out",
};
static int optionsEnumArray[] = {Back, TrackingView, ToggleHeading, ToggleFavorites, ZoomIn, ZoomOut};

optionsArray[ToggleHeading] = graphics::RadarRenderer::isNorthUp() ? "Switch to HDG-UP" : "Switch to N-UP";
optionsArray[ToggleFavorites] = uiconfig.radar_favorites_only ? "Show: All Nodes" : "Show: Favorites Only";

BannerOverlayOptions bannerOptions;
bannerOptions.message = "Radar Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = sizeof(optionsEnumArray) / sizeof(optionsEnumArray[0]);
bannerOptions.optionsEnumPtr = optionsEnumArray;

bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
screen->setFrames(Screen::FOCUS_PRESERVE);
} else if (selected == TrackingView) {
// Radar bearings menu has no enum value (invoked directly from input
// handler), so back from the picker just dismisses to the screen.
setTrackingViewPickerReturn(MenuNone);
menuQueue = TrackingViewPicker;
screen->runNow();
} else if (selected == ToggleHeading) {
graphics::RadarRenderer::toggleNorthUp();
screen->setFrames(Screen::FOCUS_PRESERVE);
screen->runNow();
} else if (selected == ToggleFavorites) {
uiconfig.radar_favorites_only = !uiconfig.radar_favorites_only;
menuHandler::saveUIConfig();
screen->setFrames(Screen::FOCUS_PRESERVE);
screen->runNow();
} else if (selected == ZoomIn) {
graphics::RadarRenderer::zoomIn();
screen->setFrames(Screen::FOCUS_PRESERVE);
screen->runNow();
} else if (selected == ZoomOut) {
graphics::RadarRenderer::zoomOut();
screen->setFrames(Screen::FOCUS_PRESERVE);
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}

void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, NodePicker, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
enum optionsNumbers { Back, NodePicker, TrackingView, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;

optionsArray[options] = "Node Actions / Settings";
optionsEnumArray[options++] = NodePicker;

#if HAS_GPS
optionsArray[options] = "Tracking View";
optionsEnumArray[options++] = TrackingView;
#endif

if (currentResolution != ScreenResolution::UltraLow) {
optionsArray[options] = "Show Long/Short Name";
optionsEnumArray[options++] = NodeNameLength;
Expand All @@ -1471,6 +1579,10 @@ void menuHandler::nodeListMenu()
if (selected == NodePicker) {
menuQueue = NodePickerMenu;
screen->runNow();
} else if (selected == TrackingView) {
setTrackingViewPickerReturn(NodeBaseMenu);
menuQueue = TrackingViewPicker;
screen->runNow();
} else if (selected == Reset) {
menuQueue = ResetNodeDbMenu;
screen->runNow();
Expand Down Expand Up @@ -2781,6 +2893,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case ClockMenu:
clockMenu();
break;
case TrackingViewPicker:
trackingViewPicker();
break;
case SystemBaseMenu:
systemBaseMenu();
break;
Expand Down
7 changes: 7 additions & 0 deletions src/graphics/draw/MenuHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class menuHandler
TwelveHourPicker,
ClockFacePicker,
ClockMenu,
TrackingViewPicker,
PositionBaseMenu,
NodeBaseMenu,
GpsToggleMenu,
Expand Down Expand Up @@ -75,6 +76,11 @@ class menuHandler
static void TZPicker();
static void twelveHourPicker();
static void clockFacePicker();
static void trackingViewPicker();
// Set the menu to re-open when the user picks "Back" from trackingViewPicker.
// Caller invokes this immediately before queueing TrackingViewPicker so the
// picker can return to its actual parent menu (or MenuNone to dismiss).
static void setTrackingViewPickerReturn(screenMenus target);
static void messageResponseMenu();
static void messageViewModeMenu();
static void replyMenu();
Expand All @@ -93,6 +99,7 @@ class menuHandler
static void BuzzerModeMenu();
static void switchToMUIMenu();
static void nodeListMenu();
static void radarBearingsMenu();
static void resetNodeDBMenu();
static void BrightnessPickerMenu();
static void rebootMenu();
Expand Down
13 changes: 13 additions & 0 deletions src/graphics/draw/NodeListRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "CompassRenderer.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
#include "RadarRenderer.h"
#if !MESHTASTIC_EXCLUDE_STATUS
#include "modules/StatusMessageModule.h"
#endif
Expand Down Expand Up @@ -831,6 +832,18 @@ void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *st
lastSwitchTime = now;
}
#endif

// === Radar overlay mode ===
// When bearings_view_radar is enabled (toggled via long-press menu →
// Tracking View), the bearings/distance frame is replaced by the
// circular radar minimap.
if (uiconfig.bearings_view_radar) {
// RadarRenderer draws its own BT/API icon at the end of the overlay
// (without the full-width black wipe drawCommonFooter performs), so
// the radar arc and last list row stay intact when BT is connected.
graphics::RadarRenderer::drawRadarOverlay(display, x, y);
return;
}
// On very first call (on boot or state enter)
if (lastRenderedMode == MODE_COUNT_LOCATION) {
currentMode_Location = MODE_DISTANCE;
Expand Down
Loading