diff --git a/docs/design/solo_ui_framework.md b/docs/design/solo_ui_framework.md index a684e019..7771a4b5 100644 --- a/docs/design/solo_ui_framework.md +++ b/docs/design/solo_ui_framework.md @@ -33,6 +33,7 @@ public: virtual int render(DisplayDriver& display) = 0; // returns ms until the next render virtual bool handleInput(char c) { return false; } virtual void poll() { } + virtual void onShow() { } // reset per-visit state }; ``` @@ -44,23 +45,23 @@ public: - **`handleInput(c)`** gets one key (`KEY_*`, see §7). Return `true` if consumed. - **`poll()`** runs every loop tick regardless of focus — rare, for background housekeeping (e.g. the shutdown button). - -Most screens also define a non-virtual **`enter()`** that resets per-visit state -(`_sel = 0`, `_dirty = false`, …). It is called by the screen's `goto…()` wrapper, -not by the base class. +- **`onShow()`** is called by `setCurrScreen()` every time the screen becomes + current — override it to reset per-visit state (`_sel = 0`, `_dirty = false`, + sub-views). Default no-op for screens that keep state across visits. Because + it's invoked centrally, a navigator can't forget to reset on show. ### Wiring a screen into UITask 1. Add a `UIScreen* my_screen;` member in `UITask.h` (near the others). 2. Construct it in `UITask::begin()` (`UITask.cpp`): `my_screen = new MyScreen(this, …);` -3. Add a navigator: +3. Add a navigator — usually just the one line (the cast-free `onShow()` runs + inside `setCurrScreen`): ```cpp - void UITask::gotoMyScreen() { - ((MyScreen*)my_screen)->enter(); - setCurrScreen(my_screen); // sets curr + schedules an immediate redraw - } + void UITask::gotoMyScreen() { setCurrScreen(my_screen); } ``` + Only screens needing a *parameter* at entry add a typed call after it (e.g. + `gotoRingtoneEditor` → `selectSlot(slot)`, `gotoMapScreen` → `showMapView()`). 4. Reach it from somewhere — usually a row in `ToolsScreen.h` (add an `Action` enum value, a row in the right section table, and a `dispatch()` case). @@ -292,7 +293,7 @@ class MyToolScreen : public UIScreen { static const int ROWS = 3; public: MyToolScreen(UITask* t, NodePrefs* p) : _task(t), _prefs(p) {} - void enter() { _sel = 0; _scroll = 0; _dirty = false; } + void onShow() override { _sel = 0; _scroll = 0; _dirty = false; } int render(DisplayDriver& d) override { d.setTextSize(1); diff --git a/examples/companion_radio/ui-new/AutoAdvertScreen.h b/examples/companion_radio/ui-new/AutoAdvertScreen.h index f6c0a286..22aa4cc0 100644 --- a/examples/companion_radio/ui-new/AutoAdvertScreen.h +++ b/examples/companion_radio/ui-new/AutoAdvertScreen.h @@ -20,7 +20,7 @@ class AutoAdvertScreen : public UIScreen { public: AutoAdvertScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs) {} - void enter() { _dirty = false; } + void onShow() override { _dirty = false; } int render(DisplayDriver& display) override { display.setTextSize(1); diff --git a/examples/companion_radio/ui-new/BotScreen.h b/examples/companion_radio/ui-new/BotScreen.h index 2b153a90..c43441ea 100644 --- a/examples/companion_radio/ui-new/BotScreen.h +++ b/examples/companion_radio/ui-new/BotScreen.h @@ -42,7 +42,7 @@ class BotScreen : public UIScreen { public: BotScreen(UITask* task, NodePrefs* prefs, KeyboardWidget* kb) : _task(task), _prefs(prefs), _kb(kb) {} - void enter() { + void onShow() override { _sel = 0; _scroll = 0; _kb_field = -1; diff --git a/examples/companion_radio/ui-new/CompassScreen.h b/examples/companion_radio/ui-new/CompassScreen.h index dcadb5af..c37ea150 100644 --- a/examples/companion_radio/ui-new/CompassScreen.h +++ b/examples/companion_radio/ui-new/CompassScreen.h @@ -27,7 +27,7 @@ class CompassScreen : public UIScreen { public: CompassScreen(UITask* task) : _task(task) {} - void enter() {} + void onShow() override {} int render(DisplayDriver& display) override { display.setTextSize(1); diff --git a/examples/companion_radio/ui-new/DashboardConfigScreen.h b/examples/companion_radio/ui-new/DashboardConfigScreen.h index a370f05e..88278d5a 100644 --- a/examples/companion_radio/ui-new/DashboardConfigScreen.h +++ b/examples/companion_radio/ui-new/DashboardConfigScreen.h @@ -37,7 +37,7 @@ class DashboardConfigScreen : public UIScreen { public: DashboardConfigScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs) {} - void enter() { _sel = 0; _dirty = false; } + void onShow() override { _sel = 0; _dirty = false; } int render(DisplayDriver& display) override { display.setTextSize(1); diff --git a/examples/companion_radio/ui-new/LiveShareScreen.h b/examples/companion_radio/ui-new/LiveShareScreen.h index 83b33878..40e73b33 100644 --- a/examples/companion_radio/ui-new/LiveShareScreen.h +++ b/examples/companion_radio/ui-new/LiveShareScreen.h @@ -36,7 +36,7 @@ class LiveShareScreen : public UIScreen { public: LiveShareScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs) {} - void enter() { _dirty = false; _sel = 0; _scroll = 0; } + void onShow() override { _dirty = false; _sel = 0; _scroll = 0; } // Resolve the configured target's display name (channel name or contact name). void currentTargetName(char* buf, int n) { diff --git a/examples/companion_radio/ui-new/LocatorScreen.h b/examples/companion_radio/ui-new/LocatorScreen.h index b6dd5112..7034b3ce 100644 --- a/examples/companion_radio/ui-new/LocatorScreen.h +++ b/examples/companion_radio/ui-new/LocatorScreen.h @@ -72,7 +72,7 @@ class LocatorScreen : public UIScreen { public: LocatorScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs) {} - void enter() { _dirty = false; _sel = 0; _scroll = 0; _picking = false; } + void onShow() override { _dirty = false; _sel = 0; _scroll = 0; _picking = false; } void valueLabel(Kind k, char* buf, int n) { switch (k) { diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index d07b0f5d..eb21c3b3 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -587,7 +587,7 @@ public: _sort_label[0] = '\0'; } - void enter() { + void onShow() override { _sel = _scroll = 0; _detail = false; _nav = false; diff --git a/examples/companion_radio/ui-new/RepeaterScreen.h b/examples/companion_radio/ui-new/RepeaterScreen.h index b606d059..66619bf7 100644 --- a/examples/companion_radio/ui-new/RepeaterScreen.h +++ b/examples/companion_radio/ui-new/RepeaterScreen.h @@ -128,7 +128,7 @@ class RepeaterScreen : public UIScreen { public: RepeaterScreen(UITask* task) : _task(task), _dirty(false), _sel(0), _scroll(0), _item_count(1) {} - void enter() { + void onShow() override { _dirty = false; _sel = 0; _scroll = 0; _picker.menu.active = false; _editor.freq.active = false; _picker.saving = false; _picker.deleting = false; diff --git a/examples/companion_radio/ui-new/RingtoneEditorScreen.h b/examples/companion_radio/ui-new/RingtoneEditorScreen.h index 26ac7696..37f71043 100644 --- a/examples/companion_radio/ui-new/RingtoneEditorScreen.h +++ b/examples/companion_radio/ui-new/RingtoneEditorScreen.h @@ -57,7 +57,9 @@ class RingtoneEditorScreen : public UIScreen { public: RingtoneEditorScreen(UITask* task, NodePrefs* prefs) : _task(task), _prefs(prefs), _slot(0) {} - void enter(int slot = 0) { + // Load a melody slot for editing. Called by gotoRingtoneEditor() right after + // setCurrScreen() (whose onShow() can't carry the slot argument). + void selectSlot(int slot = 0) { _slot = (slot == 1) ? 1 : 0; bool s2 = (_slot == 1); _bpm_idx = (_prefs && (s2 ? _prefs->ringtone2_bpm_idx : _prefs->ringtone_bpm_idx) < 5) @@ -197,7 +199,7 @@ public: break; case MI_SWITCH: _task->stopMelody(); - this->enter(1 - _slot); + this->selectSlot(1 - _slot); break; case MI_DURATION: break; // already handled by LEFT/RIGHT case MI_BPM: break; // already handled by LEFT/RIGHT diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 09fbc3b8..32cb17b4 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -651,7 +651,7 @@ public: } - void markClean() { + void onShow() override { _dirty = false; resetList(); _editor.freq.active = false; diff --git a/examples/companion_radio/ui-new/ToolsScreen.h b/examples/companion_radio/ui-new/ToolsScreen.h index a066d3b4..87c66cbb 100644 --- a/examples/companion_radio/ui-new/ToolsScreen.h +++ b/examples/companion_radio/ui-new/ToolsScreen.h @@ -58,7 +58,7 @@ public: ToolsScreen(UITask* task) : _task(task) {} // Open folded at the section list each time Tools is entered from Home. - void enter() { + 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); diff --git a/examples/companion_radio/ui-new/TrailScreen.h b/examples/companion_radio/ui-new/TrailScreen.h index 1f2d2e2f..15709c82 100644 --- a/examples/companion_radio/ui-new/TrailScreen.h +++ b/examples/companion_radio/ui-new/TrailScreen.h @@ -96,7 +96,7 @@ public: TrailScreen(UITask* task, TrailStore* store) : _task(task), _store(store), _wp(task, store) {} - void enter() { + void onShow() override { _view = V_SUMMARY; _summary_scroll = 0; _list_scroll = 0; @@ -107,9 +107,10 @@ public: _wp.reset(); } - // Enter straight into the Map view (used by the Home "Map" page). Remembers - // that we came from Home so KEY_CANCEL returns there, not to Tools. - void enterMap() { enter(); _view = V_MAP; _return_home = true; } + // Switch straight into the Map view (used by the Home "Map" page). Layered on + // top of onShow()'s reset by gotoMapScreen(); remembers we came from Home so + // KEY_CANCEL returns there, not to Tools. + void showMapView() { _view = V_MAP; _return_home = true; } int render(DisplayDriver& display) override { display.setTextSize(1); diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ac666167..c1e8a070 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1473,74 +1473,34 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no setCurrScreen(splash); } -void UITask::gotoSettingsScreen() { - ((SettingsScreen*)settings)->markClean(); - setCurrScreen(settings); -} - -void UITask::gotoToolsScreen() { - ((ToolsScreen*)tools_screen)->enter(); - setCurrScreen(tools_screen); -} +// onShow() is invoked by setCurrScreen(), so most navigators are just that. +void UITask::gotoSettingsScreen() { setCurrScreen(settings); } +void UITask::gotoToolsScreen() { setCurrScreen(tools_screen); } +void UITask::gotoBotScreen() { setCurrScreen(bot_screen); } +void UITask::gotoNearbyScreen() { setCurrScreen(nearby_screen); } +void UITask::gotoDashboardConfig() { setCurrScreen(dashboard_config); } +void UITask::gotoTrailScreen() { setCurrScreen(trail_screen); } +void UITask::gotoCompassScreen() { setCurrScreen(compass_screen); } +void UITask::gotoDiagnosticsScreen() { setCurrScreen(diag_screen); } +void UITask::gotoRepeaterScreen() { setCurrScreen(repeater_screen); } +void UITask::gotoLiveShareScreen() { setCurrScreen(live_share_screen); } +// Ringtone takes a slot argument that onShow() can't carry — pass it after the +// reset (setCurrScreen's onShow runs first, then this layers the slot on top). void UITask::gotoRingtoneEditor(int slot) { - ((RingtoneEditorScreen*)ringtone_edit)->enter(slot); setCurrScreen(ringtone_edit); + ((RingtoneEditorScreen*)ringtone_edit)->selectSlot(slot); } -void UITask::gotoBotScreen() { - ((BotScreen*)bot_screen)->enter(); - setCurrScreen(bot_screen); -} - -void UITask::gotoNearbyScreen() { - ((NearbyScreen*)nearby_screen)->enter(); - setCurrScreen(nearby_screen); -} - -void UITask::gotoDashboardConfig() { - ((DashboardConfigScreen*)dashboard_config)->enter(); - setCurrScreen(dashboard_config); -} - -void UITask::gotoTrailScreen() { - ((TrailScreen*)trail_screen)->enter(); - setCurrScreen(trail_screen); -} - +// Map is a sub-view variant of the Trail screen: reset via onShow(), then +// switch into the map view. void UITask::gotoMapScreen() { - ((TrailScreen*)trail_screen)->enterMap(); setCurrScreen(trail_screen); + ((TrailScreen*)trail_screen)->showMapView(); } -void UITask::gotoCompassScreen() { - ((CompassScreen*)compass_screen)->enter(); - setCurrScreen(compass_screen); -} - -void UITask::gotoDiagnosticsScreen() { - setCurrScreen(diag_screen); -} - -void UITask::gotoRepeaterScreen() { - ((RepeaterScreen*)repeater_screen)->enter(); - setCurrScreen(repeater_screen); -} - -void UITask::gotoLiveShareScreen() { - ((LiveShareScreen*)live_share_screen)->enter(); - setCurrScreen(live_share_screen); -} - -void UITask::gotoLocatorScreen() { - ((LocatorScreen*)locator_screen)->enter(); - setCurrScreen(locator_screen); -} - -void UITask::gotoAutoAdvertScreen() { - ((AutoAdvertScreen*)auto_advert_screen)->enter(); - setCurrScreen(auto_advert_screen); -} +void UITask::gotoLocatorScreen() { setCurrScreen(locator_screen); } +void UITask::gotoAutoAdvertScreen() { setCurrScreen(auto_advert_screen); } // Public method to handle ping result callback void UITask::handlePingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms) { @@ -1789,6 +1749,7 @@ void UITask::userLedHandler() { void UITask::setCurrScreen(UIScreen* c) { curr = c; + if (c) c->onShow(); // central per-visit reset hook (see UIScreen::onShow) _next_refresh = 100; } diff --git a/src/helpers/ui/UIScreen.h b/src/helpers/ui/UIScreen.h index bdde8e04..db2793b0 100644 --- a/src/helpers/ui/UIScreen.h +++ b/src/helpers/ui/UIScreen.h @@ -57,5 +57,10 @@ public: virtual int render(DisplayDriver& display) =0; // return value is number of millis until next render virtual bool handleInput(char c) { return false; } virtual void poll() { } + // Called by UITask::setCurrScreen() each time this screen becomes current — + // the place to reset per-visit state (selection, dirty flag, sub-views). + // Default no-op for screens that keep state across visits. Because it's + // invoked centrally, a new screen can't "forget" to be reset on show. + virtual void onShow() { } };