Files
MeshCore-Solo/examples/companion_radio/ui-new/ToolsScreen.h
Jakub 56aaff1f76 refactor(admin): make Admin remote-only, move device settings to Settings
A device administering itself via the Admin tool read as awkward, so the
local mode is gone and its options relocated to Settings.

- AdminScreen is remote-only: drop the CHOOSER phase, _local_mode, the
  LocalField/LocalKind model and activateLocalField/commitLocalField.
  Phases collapse to LOGIN/COMMAND/REPLY, entered only via startFor().
- Tools > Admin opens straight to the Nodes picker (ACT_ADMIN ->
  pickAdminTarget()). Backing out of a command screen returns to that
  picker; the picker's Cancel returns to Tools. gotoAdminScreen() removed.
- Settings > System gains Name (keyboard-edited node_name) and Reboot
  (action row, placed last so it isn't the default cursor). Radio + TX
  power were already in Settings; Send advert is the home ADVERT page.

Docs: tools_screen.md Admin section rewritten remote-only; settings_screen.md
System table gains Name + Reboot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:10:24 +02:00

137 lines
5.4 KiB
C++

#pragma once
// Custom screen — not part of upstream UITask.cpp
// Included by UITask.cpp just before HomeScreen.
//
// The flat 10-item list grew crowded, so tools are grouped into collapsible
// sections (Location / Comms / System) via the shared AccordionList helper —
// the same fold-in-place model as Settings. Each row carries a mini-icon;
// section headers show their cog/marker plus a fold indicator.
#include "AccordionList.h"
class ToolsScreen : public UIScreen {
UITask* _task;
enum Action {
ACT_NEARBY, ACT_LIVESHARE, ACT_TRAIL, ACT_LOCATOR, ACT_COMPASS,
ACT_BOT, ACT_AUTOADVERT, ACT_REPEATER, ACT_ADMIN,
ACT_CLOCK, ACT_RINGTONE, ACT_DIAGNOSTICS
};
struct Tool { const char* label; const MiniIcon* icon; Action action; };
struct Section { const char* name; const MiniIcon* icon; const Tool* tools; uint8_t count; };
static const Tool LOCATION_TOOLS[];
static const Tool COMMS_TOOLS[];
static const Tool SYSTEM_TOOLS[];
static const Section SECTIONS[];
static const int SECTION_COUNT = 3;
AccordionList _acc;
// Fixed icon gutter so labels line up whether or not a row has a glyph. Sized
// for the widest icon (the 7px cog) at the current font scale.
static int gutter(DisplayDriver& d) { return 7 * miniIconScale(d) + 2; }
static void drawIcon(DisplayDriver& d, int x, int y, const MiniIcon* ic) {
if (!ic) return;
const int s = miniIconScale(d);
const int top = (y - 1) + ((d.lineStep() - 1) - ic->h * s) / 2;
miniIconDrawTop(d, x, top, *ic);
}
void dispatch(Action a) {
switch (a) {
case ACT_NEARBY: _task->gotoNearbyScreen(); break;
case ACT_LIVESHARE: _task->gotoLiveShareScreen(); break;
case ACT_TRAIL: _task->gotoTrailScreen(); break;
case ACT_LOCATOR: _task->gotoLocatorScreen(); break;
case ACT_COMPASS: _task->gotoCompassScreen(); break;
case ACT_BOT: _task->gotoBotScreen(); break;
case ACT_AUTOADVERT: _task->gotoAutoAdvertScreen(); break;
case ACT_REPEATER: _task->gotoRepeaterScreen(); break;
case ACT_ADMIN: _task->pickAdminTarget(); break; // Admin is remote-only: pick a node first
case ACT_CLOCK: _task->gotoClockTools(); break;
case ACT_RINGTONE: _task->gotoRingtoneEditor(); break;
case ACT_DIAGNOSTICS: _task->gotoDiagnosticsScreen(); break;
}
}
public:
ToolsScreen(UITask* task) : _task(task) {}
// Open folded at the section list each time Tools is entered from Home.
void onShow() override {
static uint8_t sizes[SECTION_COUNT];
for (int i = 0; i < SECTION_COUNT; i++) sizes[i] = SECTIONS[i].count;
_acc.begin(sizes, SECTION_COUNT);
}
int render(DisplayDriver& display) override {
display.setTextSize(1);
display.setColor(DisplayDriver::LIGHT);
display.drawCenteredHeader("TOOLS");
const int cw = display.getCharWidth();
const int g = gutter(display);
_acc.render(display,
// Section header: "[+/-] <icon> Name"
[&](int sec, int y, bool sel, int reserve, bool collapsed) {
drawRowSelection(display, y, sel, reserve);
display.setCursor(2, y);
display.print(collapsed ? "+" : "-");
const int icon_x = 2 + cw + 2;
// drawIcon(display, icon_x, y, SECTIONS[sec].icon); // icons disabled for now, don't fit visually
display.setCursor(icon_x + g, y);
display.print(SECTIONS[sec].name);
},
// Item: indented "<icon> Label"
[&](int sec, int item, int y, bool sel, int reserve) {
drawRowSelection(display, y, sel, reserve);
const int icon_x = 2 + cw + 2; // align item icons under the header icon
// drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon); // icons disabled for now, don't fit visually
display.setCursor(icon_x + g, y);
display.print(SECTIONS[sec].tools[item].label);
});
return 500;
}
bool handleInput(char c) override {
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) { _task->gotoHomeScreen(); return true; }
switch (_acc.handleInput(c)) {
case AccordionList::ACTIVATED: {
const AccordionList::Row& r = _acc.selected();
dispatch(SECTIONS[r.sec].tools[r.item].action);
return true;
}
case AccordionList::HANDLED: return true;
case AccordionList::IGNORED: return false;
}
return false;
}
};
const ToolsScreen::Tool ToolsScreen::LOCATION_TOOLS[] = {
{ "Nodes", &ICON_MAP_CONTACT, ACT_NEARBY },
{ "Live Share", &ICON_GPS, ACT_LIVESHARE },
{ "Trail", &ICON_TRAIL, ACT_TRAIL },
{ "Locator", &ICON_MAP_WAYPOINT, ACT_LOCATOR },
{ "Compass", &ICON_MAP_NORTH, ACT_COMPASS },
};
const ToolsScreen::Tool ToolsScreen::COMMS_TOOLS[] = {
{ "Auto-Reply Bot", &ICON_BOT, ACT_BOT },
{ "Auto-Advert", &ICON_ADVERT, ACT_AUTOADVERT },
{ "Repeater", &ICON_REPEATER, ACT_REPEATER },
{ "Admin", &ICON_GEAR, ACT_ADMIN },
};
const ToolsScreen::Tool ToolsScreen::SYSTEM_TOOLS[] = {
{ "Clock Tools", &ICON_ALARM, ACT_CLOCK },
{ "Ringtone Editor", &ICON_NOTE, ACT_RINGTONE },
{ "Diagnostics", &ICON_CHART, ACT_DIAGNOSTICS },
};
const ToolsScreen::Section ToolsScreen::SECTIONS[] = {
{ "Location", &ICON_MAP_CONTACT, LOCATION_TOOLS, 5 },
{ "Comms", &ICON_ADVERT, COMMS_TOOLS, 4 },
{ "System", &ICON_GEAR, SYSTEM_TOOLS, 3 },
};