mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 23:08:11 +00:00
feat(ui): collapsible Tools, shared AccordionList helper
The flat 10-item Tools list grew crowded, so tools are now grouped into collapsible sections (Location / Comms / System) that fold in place — the same model as Settings. Extracted that fold-in-place behaviour into a reusable AccordionList helper (owns the collapse mask, flattened visible-row list, selection and scroll; host supplies section sizes + two row painters) and migrated SettingsScreen onto it, dropping its hand-rolled buildVis/visIndexOf/sectionIndex and five state fields. AccordionList joins drawList/DigitEditor/NavView as a shared UI element used by two screens. Added four mini-icons (bot, note, chart, gear) so every Tools row and section header carries a glyph. Builds verified: WioTrackerL1 + e-ink solo (Flash 61.0%). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
97
examples/companion_radio/ui-new/AccordionList.h
Normal file
97
examples/companion_radio/ui-new/AccordionList.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
// Shared collapsible section list (accordion) — a Settings-style list whose
|
||||
// rows are grouped under headers that expand/collapse in place. Owns the
|
||||
// collapse bitmask, the flattened visible-row list, selection and scroll; the
|
||||
// host screen only supplies the section sizes and two row painters.
|
||||
//
|
||||
// Reusable UI element (cf. drawList / DigitEditor / NavView): any screen that
|
||||
// wants grouped, foldable rows drives it the same way —
|
||||
// _acc.begin(sizes, n); // once, on enter()
|
||||
// _acc.render(display, headerFn, itemFn); // in render()
|
||||
// switch (_acc.handleInput(c)) { ... } // in handleInput()
|
||||
//
|
||||
// Relies on drawList() + the KEY_* codes already pulled in by icons.h /
|
||||
// UIScreen.h before this header (same include-order contract as the screens).
|
||||
#include "icons.h"
|
||||
|
||||
class AccordionList {
|
||||
public:
|
||||
// A visible row is either a section header (item < 0) or the item-th entry
|
||||
// within section `sec`.
|
||||
struct Row { int8_t sec; int8_t item; };
|
||||
|
||||
enum Result { IGNORED, HANDLED, ACTIVATED };
|
||||
|
||||
static const int MAX_SECTIONS = 8; // collapse bitmask is 8 bits
|
||||
static const int MAX_ROWS = 64;
|
||||
|
||||
// (Re)initialise from per-section item counts. `collapsed` sets the initial
|
||||
// fold state for every section (Settings opens folded, so default true).
|
||||
void begin(const uint8_t* section_sizes, int section_count, bool collapsed = true) {
|
||||
_count = section_count > MAX_SECTIONS ? MAX_SECTIONS : section_count;
|
||||
for (int i = 0; i < _count; i++) _sizes[i] = section_sizes[i];
|
||||
_collapsed = collapsed ? 0xFF : 0x00;
|
||||
_sel = 0; _scroll = 0;
|
||||
rebuild();
|
||||
}
|
||||
|
||||
int visibleCount() const { return _vis_count; }
|
||||
bool collapsed(int sec) const { return (_collapsed >> sec) & 1; }
|
||||
const Row& selected() const { return _rows[_sel]; }
|
||||
bool onHeader() const { return _vis_count && _rows[_sel].item < 0; }
|
||||
|
||||
// Paint via the shared drawList skeleton. `header(sec, y, sel, reserve,
|
||||
// collapsed)` and `item(sec, item, y, sel, reserve)` each draw one row
|
||||
// (including its own selection bar, as drawList's callers do).
|
||||
template <class HeaderFn, class ItemFn>
|
||||
void render(DisplayDriver& d, HeaderFn header, ItemFn item) {
|
||||
drawList(d, _vis_count, _sel, _scroll, [&](int idx, int y, bool sel, int reserve) {
|
||||
const Row& r = _rows[idx];
|
||||
if (r.item < 0) header(r.sec, y, sel, reserve, collapsed(r.sec));
|
||||
else item(r.sec, r.item, y, sel, reserve);
|
||||
});
|
||||
}
|
||||
|
||||
// Standard navigation. Returns ACTIVATED when Enter lands on an item (the
|
||||
// host then acts on selected()); HANDLED for nav / fold toggles; IGNORED
|
||||
// otherwise so the host can treat the key itself (e.g. Cancel).
|
||||
Result handleInput(char c) {
|
||||
if (c == KEY_UP) { if (_vis_count) _sel = (_sel > 0) ? _sel - 1 : _vis_count - 1; return HANDLED; }
|
||||
if (c == KEY_DOWN) { if (_vis_count) _sel = (_sel + 1 < _vis_count) ? _sel + 1 : 0; return HANDLED; }
|
||||
if (c == KEY_ENTER) {
|
||||
if (!_vis_count) return HANDLED;
|
||||
if (_rows[_sel].item < 0) { toggle(_rows[_sel].sec); return HANDLED; }
|
||||
return ACTIVATED;
|
||||
}
|
||||
return IGNORED;
|
||||
}
|
||||
|
||||
// Fold/unfold a section, keeping that section's header selected afterwards so
|
||||
// the cursor never jumps to an unrelated row when items appear/disappear.
|
||||
void toggle(int sec) {
|
||||
if (sec < 0 || sec >= _count) return;
|
||||
_collapsed ^= (uint8_t)(1u << sec);
|
||||
rebuild();
|
||||
for (int i = 0; i < _vis_count; i++)
|
||||
if (_rows[i].sec == sec && _rows[i].item < 0) { _sel = i; break; }
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t _sizes[MAX_SECTIONS];
|
||||
int _count = 0;
|
||||
uint8_t _collapsed = 0xFF;
|
||||
Row _rows[MAX_ROWS];
|
||||
int _vis_count = 0;
|
||||
int _sel = 0, _scroll = 0;
|
||||
|
||||
void rebuild() {
|
||||
_vis_count = 0;
|
||||
for (int s = 0; s < _count; s++) {
|
||||
if (_vis_count < MAX_ROWS) _rows[_vis_count++] = { (int8_t)s, (int8_t)-1 };
|
||||
if (!collapsed(s))
|
||||
for (int it = 0; it < _sizes[s] && _vis_count < MAX_ROWS; it++)
|
||||
_rows[_vis_count++] = { (int8_t)s, (int8_t)it };
|
||||
}
|
||||
if (_sel >= _vis_count) _sel = _vis_count ? _vis_count - 1 : 0;
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../RadioPresets.h"
|
||||
#include "RadioParamsEditor.h"
|
||||
#include "RadioPresetPicker.h"
|
||||
#include "AccordionList.h"
|
||||
|
||||
class SettingsScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
@@ -76,15 +77,20 @@ class SettingsScreen : public UIScreen {
|
||||
Count
|
||||
};
|
||||
|
||||
int _selected;
|
||||
int _scroll;
|
||||
int _visible; // items fitting on screen; updated each render, used by handleInput
|
||||
int _reserve = 0; // right-edge px reserved for the scrollbar (0 when list fits)
|
||||
bool _dirty;
|
||||
uint8_t _collapsed = 0x7F; // bit N set = section N collapsed (Display=0..Messages=6)
|
||||
static const int MAX_VIS = 60;
|
||||
uint8_t _vis[MAX_VIS]; // filtered list of visible SettingItem values
|
||||
int _vis_count = 0;
|
||||
// Cursor + scroll, fold state and the flattened visible list are owned by the
|
||||
// shared AccordionList helper. We keep only the section→SettingItem mapping it
|
||||
// needs (sections are walked once from the enum, honouring the #if guards).
|
||||
int _selected = 0; // SettingItem under the cursor, resolved per input/render
|
||||
int _reserve = 0; // right-edge px reserved for the scrollbar (0 when list fits)
|
||||
bool _dirty = false;
|
||||
|
||||
AccordionList _acc;
|
||||
static const int NUM_SECTIONS = 7;
|
||||
static const int MAX_PER_SEC = 16;
|
||||
uint8_t _sec_items[NUM_SECTIONS][MAX_PER_SEC]; // SettingItem per (section, row)
|
||||
uint8_t _sec_count[NUM_SECTIONS];
|
||||
uint8_t _sec_header[NUM_SECTIONS]; // the SECTION_* enum for each section
|
||||
int _num_sections = 0;
|
||||
|
||||
#if AUTO_OFF_MILLIS > 0
|
||||
static const uint16_t AUTO_OFF_OPTS[5];
|
||||
@@ -176,36 +182,33 @@ class SettingsScreen : public UIScreen {
|
||||
return "";
|
||||
}
|
||||
|
||||
int sectionIndex(int item) const {
|
||||
if (item == SECTION_DISPLAY) return 0;
|
||||
if (item == SECTION_SOUND) return 1;
|
||||
if (item == SECTION_HOME_PAGES) return 2;
|
||||
if (item == SECTION_RADIO) return 3;
|
||||
if (item == SECTION_SYSTEM) return 4;
|
||||
if (item == SECTION_CONTACTS) return 5;
|
||||
if (item == SECTION_MESSAGES) return 6;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int visIndexOf(int item) const {
|
||||
for (int i = 0; i < _vis_count; i++)
|
||||
if (_vis[i] == (uint8_t)item) return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void buildVis() {
|
||||
_vis_count = 0;
|
||||
int cur_sec = -1;
|
||||
bool cur_collapsed = false;
|
||||
// Walk the SettingItem enum once, bucketing items under their section header.
|
||||
// #if-guarded items need no special handling — they simply aren't in the enum.
|
||||
void buildSections() {
|
||||
int cur = -1;
|
||||
for (int i = 0; i < (int)Count; i++) {
|
||||
if (isSection(i)) {
|
||||
cur_sec++;
|
||||
cur_collapsed = (_collapsed >> cur_sec) & 1;
|
||||
if (_vis_count < MAX_VIS) _vis[_vis_count++] = (uint8_t)i;
|
||||
} else if (!cur_collapsed && _vis_count < MAX_VIS) {
|
||||
_vis[_vis_count++] = (uint8_t)i;
|
||||
if (++cur >= NUM_SECTIONS) break;
|
||||
_sec_header[cur] = (uint8_t)i;
|
||||
_sec_count[cur] = 0;
|
||||
} else if (cur >= 0 && _sec_count[cur] < MAX_PER_SEC) {
|
||||
_sec_items[cur][_sec_count[cur]++] = (uint8_t)i;
|
||||
}
|
||||
}
|
||||
_num_sections = cur + 1;
|
||||
}
|
||||
|
||||
// (Re)load the section sizes into the accordion (folds all, resets the cursor).
|
||||
void resetList() {
|
||||
uint8_t sizes[NUM_SECTIONS];
|
||||
for (int i = 0; i < _num_sections; i++) sizes[i] = _sec_count[i];
|
||||
_acc.begin(sizes, _num_sections);
|
||||
}
|
||||
|
||||
// Resolve the accordion's selected row to a SettingItem (header → SECTION_*).
|
||||
int currentItem() const {
|
||||
const AccordionList::Row& r = _acc.selected();
|
||||
return (r.item < 0) ? _sec_header[r.sec] : _sec_items[r.sec][r.item];
|
||||
}
|
||||
|
||||
bool isHomePage(int item) const {
|
||||
@@ -416,23 +419,9 @@ class SettingsScreen : public UIScreen {
|
||||
return item - MSG_SLOT_0;
|
||||
}
|
||||
|
||||
void renderItem(DisplayDriver& display, int item, int y) {
|
||||
void renderItem(DisplayDriver& display, int item, int y, bool sel) {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
|
||||
if (isSection(item)) {
|
||||
int si = sectionIndex(item);
|
||||
bool collapsed = (_collapsed >> si) & 1;
|
||||
bool sel = (item == _selected);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawSelectionRow(0, y - 1, display.width() - _reserve, display.lineStep() - 1, sel);
|
||||
display.setCursor(2, y);
|
||||
display.print(collapsed ? "+" : "-");
|
||||
display.print(" ");
|
||||
display.print(sectionName(item));
|
||||
return;
|
||||
}
|
||||
|
||||
bool sel = (item == _selected);
|
||||
display.drawSelectionRow(0, y - 1, display.width() - _reserve, display.lineStep() - 1, sel);
|
||||
|
||||
display.setCursor(2, y);
|
||||
@@ -641,7 +630,7 @@ class SettingsScreen : public UIScreen {
|
||||
}
|
||||
|
||||
// Keyboard state for editing message slots
|
||||
int _edit_slot; // -1 = not editing, 0..9 = slot being edited
|
||||
int _edit_slot = -1; // -1 = not editing, 0..9 = slot being edited
|
||||
KeyboardWidget* _kb;
|
||||
|
||||
// Radio preset picker — names are too long for the value column, so Enter on
|
||||
@@ -656,17 +645,15 @@ class SettingsScreen : public UIScreen {
|
||||
|
||||
public:
|
||||
SettingsScreen(UITask* task, KeyboardWidget* kb)
|
||||
: _task(task), _kb(kb), _selected(SECTION_DISPLAY), _scroll(0), _visible(4), _dirty(false), _edit_slot(-1) {
|
||||
buildVis();
|
||||
: _task(task), _kb(kb) {
|
||||
buildSections();
|
||||
resetList();
|
||||
}
|
||||
|
||||
|
||||
void markClean() {
|
||||
_dirty = false;
|
||||
_collapsed = 0x7F;
|
||||
_selected = SECTION_DISPLAY;
|
||||
buildVis();
|
||||
_scroll = 0;
|
||||
resetList();
|
||||
_editor.freq.active = false;
|
||||
}
|
||||
|
||||
@@ -677,18 +664,24 @@ public:
|
||||
return _kb->render(display);
|
||||
}
|
||||
|
||||
int item_h = display.lineStep();
|
||||
int start_y = display.listStart();
|
||||
_visible = display.listVisible(item_h);
|
||||
_reserve = scrollIndicatorReserve(display, _vis_count, _visible);
|
||||
|
||||
display.drawCenteredHeader("SETTINGS");
|
||||
|
||||
for (int i = 0; i < _visible && (_scroll + i) < _vis_count; i++) {
|
||||
renderItem(display, _vis[_scroll + i], start_y + i * item_h);
|
||||
}
|
||||
|
||||
drawScrollIndicator(display, start_y, _visible * item_h, _vis_count, _visible, _scroll);
|
||||
_acc.render(display,
|
||||
// Section header: "[+/-] Name"
|
||||
[&](int sec, int y, bool sel, int reserve, bool collapsed) {
|
||||
_reserve = reserve;
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
|
||||
display.setCursor(2, y);
|
||||
display.print(collapsed ? "+" : "-");
|
||||
display.print(" ");
|
||||
display.print(sectionName(_sec_header[sec]));
|
||||
},
|
||||
// Item row
|
||||
[&](int sec, int item, int y, bool sel, int reserve) {
|
||||
_reserve = reserve;
|
||||
renderItem(display, _sec_items[sec][item], y, sel);
|
||||
});
|
||||
|
||||
if (_picker.menu.active) _picker.menu.render(display);
|
||||
|
||||
@@ -760,42 +753,23 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c == KEY_UP && _vis_count > 0) {
|
||||
int vi = visIndexOf(_selected);
|
||||
vi = (vi > 0) ? vi - 1 : _vis_count - 1;
|
||||
_selected = _vis[vi];
|
||||
if (vi < _scroll) _scroll = vi;
|
||||
else if (vi >= _scroll + _visible) _scroll = vi - _visible + 1;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_DOWN && _vis_count > 0) {
|
||||
int vi = visIndexOf(_selected);
|
||||
vi = (vi + 1 < _vis_count) ? vi + 1 : 0;
|
||||
_selected = _vis[vi];
|
||||
if (vi >= _scroll + _visible) _scroll = vi - _visible + 1;
|
||||
else if (vi < _scroll) _scroll = vi;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_CANCEL) {
|
||||
if (_dirty) the_mesh.savePrefs();
|
||||
_task->gotoHomeScreen();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Up/down navigation and section fold/unfold live in the shared helper.
|
||||
// Enter on an item returns ACTIVATED and falls through to the per-item logic
|
||||
// below; left/right are ignored by the helper and likewise fall through.
|
||||
AccordionList::Result ar = _acc.handleInput(c);
|
||||
if (ar == AccordionList::HANDLED) return true;
|
||||
_selected = currentItem();
|
||||
|
||||
bool right = keyIsNext(c);
|
||||
bool left = keyIsPrev(c);
|
||||
bool enter = (c == KEY_ENTER);
|
||||
|
||||
if (enter && isSection(_selected)) {
|
||||
int si = sectionIndex(_selected);
|
||||
_collapsed ^= (1 << si);
|
||||
buildVis();
|
||||
int vi = visIndexOf(_selected);
|
||||
if (vi < _scroll) _scroll = vi;
|
||||
if (_visible > 0 && vi >= _scroll + _visible) _scroll = vi - _visible + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if FEAT_BRIGHTNESS_SETTING
|
||||
if (_selected == BRIGHTNESS) {
|
||||
uint8_t lvl = _task->getBrightnessLevel();
|
||||
|
||||
@@ -1,48 +1,132 @@
|
||||
#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;
|
||||
int _sel;
|
||||
int _scroll = 0;
|
||||
|
||||
static const int ITEM_COUNT = 10;
|
||||
static const char* ITEMS[ITEM_COUNT];
|
||||
enum Action {
|
||||
ACT_NEARBY, ACT_LIVESHARE, ACT_TRAIL, ACT_GEOALERT, ACT_COMPASS,
|
||||
ACT_BOT, ACT_AUTOADVERT, ACT_REPEATER,
|
||||
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_GEOALERT: _task->gotoGeoAlertScreen(); 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_RINGTONE: _task->gotoRingtoneEditor(); break;
|
||||
case ACT_DIAGNOSTICS: _task->gotoDiagnosticsScreen(); break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
ToolsScreen(UITask* task) : _task(task), _sel(0) {}
|
||||
ToolsScreen(UITask* task) : _task(task) {}
|
||||
|
||||
// Open folded at the section list each time Tools is entered from Home.
|
||||
void enter() {
|
||||
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");
|
||||
|
||||
drawList(display, ITEM_COUNT, _sel, _scroll, [&](int idx, int y, bool sel, int reserve) {
|
||||
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
|
||||
display.setCursor(2, y);
|
||||
display.print(ITEMS[idx]);
|
||||
});
|
||||
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) {
|
||||
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
|
||||
display.setCursor(2, y);
|
||||
display.print(collapsed ? "+" : "-");
|
||||
const int icon_x = 2 + cw + 2;
|
||||
drawIcon(display, icon_x, y, SECTIONS[sec].icon);
|
||||
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) {
|
||||
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
|
||||
const int icon_x = 2 + cw + 2; // align item icons under the header icon
|
||||
drawIcon(display, icon_x, y, SECTIONS[sec].tools[item].icon);
|
||||
display.setCursor(icon_x + g, y);
|
||||
display.print(SECTIONS[sec].tools[item].label);
|
||||
});
|
||||
return 500;
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
if (c == KEY_UP) { _sel = (_sel > 0) ? _sel - 1 : ITEM_COUNT - 1; return true; }
|
||||
if (c == KEY_DOWN) { _sel = (_sel < ITEM_COUNT - 1) ? _sel + 1 : 0; return true; }
|
||||
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) { _task->gotoHomeScreen(); return true; }
|
||||
if (c == KEY_ENTER) {
|
||||
if (_sel == 0) { _task->gotoRingtoneEditor(); return true; }
|
||||
if (_sel == 1) { _task->gotoBotScreen(); return true; }
|
||||
if (_sel == 2) { _task->gotoNearbyScreen(); return true; }
|
||||
if (_sel == 3) { _task->gotoAutoAdvertScreen(); return true; }
|
||||
if (_sel == 4) { _task->gotoLiveShareScreen(); return true; }
|
||||
if (_sel == 5) { _task->gotoTrailScreen(); return true; }
|
||||
if (_sel == 6) { _task->gotoGeoAlertScreen(); return true; }
|
||||
if (_sel == 7) { _task->gotoCompassScreen(); return true; }
|
||||
if (_sel == 8) { _task->gotoDiagnosticsScreen(); return true; }
|
||||
if (_sel == 9) { _task->gotoRepeaterScreen(); 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 char* ToolsScreen::ITEMS[10] = { "Ringtone Editor", "Auto-Reply Bot", "Nearby Nodes", "Auto-Advert", "Live Share", "Trail", "Geo Alert", "Compass", "Diagnostics", "Repeater" };
|
||||
|
||||
const ToolsScreen::Tool ToolsScreen::LOCATION_TOOLS[] = {
|
||||
{ "Nearby Nodes", &ICON_MAP_CONTACT, ACT_NEARBY },
|
||||
{ "Live Share", &ICON_ADVERT, ACT_LIVESHARE },
|
||||
{ "Trail", &ICON_TRAIL, ACT_TRAIL },
|
||||
{ "Geo Alert", &ICON_MAP_WAYPOINT, ACT_GEOALERT },
|
||||
{ "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 },
|
||||
};
|
||||
const ToolsScreen::Tool ToolsScreen::SYSTEM_TOOLS[] = {
|
||||
{ "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, 3 },
|
||||
{ "System", &ICON_GEAR, SYSTEM_TOOLS, 2 },
|
||||
};
|
||||
|
||||
@@ -1373,6 +1373,7 @@ void UITask::gotoSettingsScreen() {
|
||||
}
|
||||
|
||||
void UITask::gotoToolsScreen() {
|
||||
((ToolsScreen*)tools_screen)->enter();
|
||||
setCurrScreen(tools_screen);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,38 @@ MINI_ICON(ICON_REPEATER, 6, // » double chevron — relaying/forwarding (repe
|
||||
packRow(".#..#."),
|
||||
packRow("#..#.."));
|
||||
|
||||
// Tools-menu glyphs (auto-reply bot, ringtone editor, diagnostics, system).
|
||||
MINI_ICON(ICON_BOT, 5, // robot head: antenna + eyes + grille (auto-reply bot)
|
||||
packRow("..#.."),
|
||||
packRow("#####"),
|
||||
packRow("#.#.#"),
|
||||
packRow("#####"),
|
||||
packRow("#.#.#"),
|
||||
packRow("#####"));
|
||||
MINI_ICON(ICON_NOTE, 5, // ♪ quaver — ringtone editor
|
||||
packRow("...##"),
|
||||
packRow("...##"),
|
||||
packRow("...#."),
|
||||
packRow("...#."),
|
||||
packRow("...#."),
|
||||
packRow("##.#."),
|
||||
packRow("##..."));
|
||||
MINI_ICON(ICON_CHART, 5, // ascending bars — diagnostics / stats
|
||||
packRow("....#"),
|
||||
packRow("..#.#"),
|
||||
packRow("..#.#"),
|
||||
packRow("#.#.#"),
|
||||
packRow("#.#.#"),
|
||||
packRow("#####"));
|
||||
MINI_ICON(ICON_GEAR, 7, // ⚙ cog with hub hole — system
|
||||
packRow("..#.#.."),
|
||||
packRow(".#####."),
|
||||
packRow("#######"),
|
||||
packRow("###.###"),
|
||||
packRow("#######"),
|
||||
packRow(".#####."),
|
||||
packRow("..#.#.."));
|
||||
|
||||
// Trail-map markers — centred on a point (see miniIconDrawCentered) rather
|
||||
// than anchored to a text line.
|
||||
MINI_ICON(ICON_MAP_DOT, 3, // ● filled trail point
|
||||
|
||||
Reference in New Issue
Block a user