2026-05-12 14:45:02 +02:00
|
|
|
#pragma once
|
|
|
|
|
// Custom screen — not part of upstream UITask.cpp
|
|
|
|
|
// Included by UITask.cpp just before HomeScreen.
|
2026-06-25 19:04:21 +02:00
|
|
|
//
|
|
|
|
|
// The flat 10-item list grew crowded, so tools are grouped into collapsible
|
|
|
|
|
// sections (Location / Comms / System) via the shared AccordionList helper —
|
2026-10-02 10:23:58 +02:00
|
|
|
// the same fold-in-place model as Settings. Rows are text only; section
|
|
|
|
|
// headers carry a fold mark.
|
2026-06-25 19:04:21 +02:00
|
|
|
|
|
|
|
|
#include "AccordionList.h"
|
2026-05-12 14:45:02 +02:00
|
|
|
|
|
|
|
|
class ToolsScreen : public UIScreen {
|
2026-10-02 10:23:58 +02:00
|
|
|
public:
|
2026-06-25 19:04:21 +02:00
|
|
|
enum Action {
|
2026-10-01 23:51:47 +02:00
|
|
|
ACT_NEARBY, ACT_LIVESHARE, ACT_TRAIL, ACT_LOCATOR, ACT_COMPASS,
|
2026-07-12 19:40:53 +02:00
|
|
|
ACT_BOT, ACT_AUTOADVERT, ACT_REPEATER, ACT_ADMIN,
|
2026-10-01 23:51:47 +02:00
|
|
|
ACT_CLOCK, ACT_RINGTONE
|
2026-07-21 20:30:03 +02:00
|
|
|
#if defined(PIN_GPIO1)
|
|
|
|
|
, ACT_GPIO
|
|
|
|
|
#endif
|
2026-06-25 19:04:21 +02:00
|
|
|
};
|
2026-10-02 10:23:58 +02:00
|
|
|
struct Tool { const char* label; Action action; };
|
2026-10-02 08:45:05 +02:00
|
|
|
struct Section { const char* name; const Tool* tools; uint8_t count; };
|
2026-06-25 19:04:21 +02:00
|
|
|
|
2026-10-02 10:23:58 +02:00
|
|
|
// The two tools used last, newest first, for the Home Tools panel. Kept in
|
|
|
|
|
// RAM only: a reboot starts again from Nodes and Live Share.
|
|
|
|
|
static const int RECENT_MAX = 2;
|
|
|
|
|
static Action s_recent[RECENT_MAX];
|
|
|
|
|
|
|
|
|
|
static const char* labelOf(Action a) {
|
|
|
|
|
for (int i = 0; i < SECTION_COUNT; i++)
|
|
|
|
|
for (int j = 0; j < SECTIONS[i].count; j++)
|
|
|
|
|
if (SECTIONS[i].tools[j].action == a) return SECTIONS[i].tools[j].label;
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Opens a tool and moves it to the front of the recent list.
|
|
|
|
|
static void run(UITask* task, Action a) {
|
|
|
|
|
if (s_recent[0] != a) { s_recent[1] = s_recent[0]; s_recent[0] = 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;
|
|
|
|
|
#if defined(PIN_GPIO1)
|
|
|
|
|
case ACT_GPIO: task->gotoGpioScreen(); break;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
2026-06-25 19:04:21 +02:00
|
|
|
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;
|
|
|
|
|
|
2026-05-12 14:45:02 +02:00
|
|
|
public:
|
2026-06-25 19:04:21 +02:00
|
|
|
ToolsScreen(UITask* task) : _task(task) {}
|
|
|
|
|
|
|
|
|
|
// Open folded at the section list each time Tools is entered from Home.
|
2026-06-29 18:37:04 +02:00
|
|
|
void onShow() override {
|
2026-06-25 19:04:21 +02:00
|
|
|
static uint8_t sizes[SECTION_COUNT];
|
|
|
|
|
for (int i = 0; i < SECTION_COUNT; i++) sizes[i] = SECTIONS[i].count;
|
|
|
|
|
_acc.begin(sizes, SECTION_COUNT);
|
|
|
|
|
}
|
2026-05-12 14:45:02 +02:00
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-10-02 08:45:05 +02:00
|
|
|
drawScreenHeader(display, "Tools");
|
2026-05-22 18:34:20 +02:00
|
|
|
|
2026-06-25 19:04:21 +02:00
|
|
|
const int cw = display.getCharWidth();
|
|
|
|
|
|
|
|
|
|
_acc.render(display,
|
2026-10-02 08:45:05 +02:00
|
|
|
// Section header: disclosure mark + name
|
2026-06-25 19:04:21 +02:00
|
|
|
[&](int sec, int y, bool sel, int reserve, bool collapsed) {
|
2026-06-29 18:53:18 +02:00
|
|
|
drawRowSelection(display, y, sel, reserve);
|
2026-10-02 08:45:05 +02:00
|
|
|
drawDisclosure(display, 2, y, !collapsed);
|
2026-10-02 10:23:58 +02:00
|
|
|
display.setCursor(2 + 2 * cw, y); // after the disclosure mark, as in Settings
|
2026-06-25 19:04:21 +02:00
|
|
|
display.print(SECTIONS[sec].name);
|
|
|
|
|
},
|
2026-10-02 10:23:58 +02:00
|
|
|
// Item: its label, under its section's name
|
2026-06-25 19:04:21 +02:00
|
|
|
[&](int sec, int item, int y, bool sel, int reserve) {
|
2026-06-29 18:53:18 +02:00
|
|
|
drawRowSelection(display, y, sel, reserve);
|
2026-10-02 10:23:58 +02:00
|
|
|
display.setCursor(2 + 2 * cw, y);
|
2026-06-25 19:04:21 +02:00
|
|
|
display.print(SECTIONS[sec].tools[item].label);
|
|
|
|
|
});
|
2026-05-12 14:45:02 +02:00
|
|
|
return 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handleInput(char c) override {
|
2026-08-31 15:43:10 +02:00
|
|
|
if (c == KEY_CANCEL) { _task->gotoHomeScreen(); return true; }
|
2026-06-25 19:04:21 +02:00
|
|
|
switch (_acc.handleInput(c)) {
|
|
|
|
|
case AccordionList::ACTIVATED: {
|
|
|
|
|
const AccordionList::Row& r = _acc.selected();
|
2026-10-02 10:23:58 +02:00
|
|
|
run(_task, SECTIONS[r.sec].tools[r.item].action);
|
2026-06-25 19:04:21 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
case AccordionList::HANDLED: return true;
|
|
|
|
|
case AccordionList::IGNORED: return false;
|
2026-05-12 14:45:02 +02:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-06-25 19:04:21 +02:00
|
|
|
|
2026-10-02 10:23:58 +02:00
|
|
|
ToolsScreen::Action ToolsScreen::s_recent[ToolsScreen::RECENT_MAX] = { ACT_NEARBY, ACT_LIVESHARE };
|
|
|
|
|
|
2026-06-25 19:04:21 +02:00
|
|
|
const ToolsScreen::Tool ToolsScreen::LOCATION_TOOLS[] = {
|
2026-10-02 10:23:58 +02:00
|
|
|
{ "Nodes", ACT_NEARBY },
|
|
|
|
|
{ "Live Share", ACT_LIVESHARE },
|
|
|
|
|
{ "Trail", ACT_TRAIL },
|
|
|
|
|
{ "Locator", ACT_LOCATOR },
|
|
|
|
|
{ "Compass", ACT_COMPASS },
|
2026-06-25 19:04:21 +02:00
|
|
|
};
|
|
|
|
|
const ToolsScreen::Tool ToolsScreen::COMMS_TOOLS[] = {
|
2026-10-02 10:23:58 +02:00
|
|
|
{ "Remote Bot", ACT_BOT },
|
|
|
|
|
{ "Auto-Advert", ACT_AUTOADVERT },
|
|
|
|
|
{ "Repeater", ACT_REPEATER },
|
|
|
|
|
{ "Admin", ACT_ADMIN },
|
2026-06-25 19:04:21 +02:00
|
|
|
};
|
|
|
|
|
const ToolsScreen::Tool ToolsScreen::SYSTEM_TOOLS[] = {
|
2026-10-02 10:23:58 +02:00
|
|
|
{ "Clock Tools", ACT_CLOCK },
|
|
|
|
|
{ "Ringtone Editor", ACT_RINGTONE },
|
2026-07-21 20:30:03 +02:00
|
|
|
#if defined(PIN_GPIO1)
|
2026-10-02 10:23:58 +02:00
|
|
|
{ "GPIO", ACT_GPIO },
|
2026-07-21 20:30:03 +02:00
|
|
|
#endif
|
2026-06-25 19:04:21 +02:00
|
|
|
};
|
|
|
|
|
const ToolsScreen::Section ToolsScreen::SECTIONS[] = {
|
2026-10-02 08:45:05 +02:00
|
|
|
{ "Location", LOCATION_TOOLS, (uint8_t)(sizeof(LOCATION_TOOLS)/sizeof(LOCATION_TOOLS[0])) },
|
|
|
|
|
{ "Comms", COMMS_TOOLS, 4 },
|
|
|
|
|
{ "System", SYSTEM_TOOLS, (uint8_t)(sizeof(SYSTEM_TOOLS)/sizeof(SYSTEM_TOOLS[0])) },
|
2026-06-25 19:04:21 +02:00
|
|
|
};
|