From 140d8749a65ee4362a5c03e27c225026416c11c3 Mon Sep 17 00:00:00 2001 From: dangson Date: Sun, 28 Dec 2025 21:59:14 -0600 Subject: [PATCH] Support swapping the functionality of the front buttons (#133) ## Summary **What is the goal of this PR?** Adds a setting to swap the front buttons. The default functionality are: Back/Confirm/Left/Right. When this setting is enabled they become: Left/Right/Back/Confirm. This makes it more comfortable to use when holding in your right hand since your thumb can more easily rest on the next button. The original firmware has a similar setting. **What changes are included?** - Add the new setting. - Create a mapper to dynamically switch the buttons based on the setting. - Use mapper on the various activity screens. - Update the button hints to reflect the swapped buttons. ## Additional Context Full disclosure: I used Codex CLI to put this PR together, but did review it to make sure it makes sense. Also tested on my device: https://share.cleanshot.com/k76891NY --- USER_GUIDE.md | 1 + src/CrossPointSettings.cpp | 5 +- src/CrossPointSettings.h | 7 ++ src/MappedInputManager.cpp | 77 +++++++++++++++++++ src/MappedInputManager.h | 31 ++++++++ src/activities/Activity.h | 9 ++- src/activities/ActivityWithSubactivity.h | 4 +- src/activities/boot_sleep/BootActivity.h | 3 +- src/activities/boot_sleep/SleepActivity.h | 4 +- src/activities/home/HomeActivity.cpp | 13 ++-- src/activities/home/HomeActivity.h | 4 +- .../network/CrossPointWebServerActivity.cpp | 11 +-- .../network/CrossPointWebServerActivity.h | 4 +- .../network/NetworkModeSelectionActivity.cpp | 15 ++-- .../network/NetworkModeSelectionActivity.h | 4 +- .../network/WifiSelectionActivity.cpp | 38 +++++---- .../network/WifiSelectionActivity.h | 4 +- src/activities/reader/EpubReaderActivity.cpp | 18 ++--- src/activities/reader/EpubReaderActivity.h | 4 +- .../EpubReaderChapterSelectionActivity.cpp | 14 ++-- .../EpubReaderChapterSelectionActivity.h | 4 +- .../reader/FileSelectionActivity.cpp | 21 ++--- src/activities/reader/FileSelectionActivity.h | 4 +- src/activities/reader/ReaderActivity.cpp | 12 +-- src/activities/reader/ReaderActivity.h | 4 +- src/activities/reader/XtcReaderActivity.cpp | 14 ++-- src/activities/reader/XtcReaderActivity.h | 4 +- src/activities/settings/OtaUpdateActivity.cpp | 10 +-- src/activities/settings/OtaUpdateActivity.h | 5 +- src/activities/settings/SettingsActivity.cpp | 27 ++++--- src/activities/settings/SettingsActivity.h | 5 +- .../util/FullScreenMessageActivity.h | 4 +- src/activities/util/KeyboardEntryActivity.cpp | 12 +-- src/activities/util/KeyboardEntryActivity.h | 13 ++-- src/main.cpp | 16 ++-- 35 files changed, 285 insertions(+), 140 deletions(-) create mode 100644 src/MappedInputManager.cpp create mode 100644 src/MappedInputManager.h diff --git a/USER_GUIDE.md b/USER_GUIDE.md index a19a1e8..c0eeded 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -67,6 +67,7 @@ The Settings screen allows you to configure the device's behavior. There are a f - **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book, if disabled, paragraphs will not have vertical space between them, but will have first word indentation. - **Short Power Button Click**: Whether to trigger the power button on a short press or a long press. +- **Front Button Layout**: Swap the order of the bottom edge buttons from Back/Confirm/Left/Right to Left/Right/Back/Confirm. ### 3.6 Sleep Screen diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 9328422..a0a002f 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -11,7 +11,7 @@ CrossPointSettings CrossPointSettings::instance; namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 5; +constexpr uint8_t SETTINGS_COUNT = 6; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -31,6 +31,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, shortPwrBtn); serialization::writePod(outputFile, statusBar); serialization::writePod(outputFile, orientation); + serialization::writePod(outputFile, frontButtonLayout); outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -67,6 +68,8 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, orientation); if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, frontButtonLayout); + if (++settingsRead >= fileSettingsCount) break; } while (false); inputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 2b99664..78356ff 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -28,6 +28,11 @@ class CrossPointSettings { LANDSCAPE_CCW = 3 // 800x480 logical coordinates, native panel orientation }; + // Front button layout options + // Default: Back, Confirm, Left, Right + // Swapped: Left, Right, Back, Confirm + enum FRONT_BUTTON_LAYOUT { BACK_CONFIRM_LEFT_RIGHT = 0, LEFT_RIGHT_BACK_CONFIRM = 1 }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Status bar settings @@ -39,6 +44,8 @@ class CrossPointSettings { // EPUB reading orientation settings // 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise uint8_t orientation = PORTRAIT; + // Front button layout + uint8_t frontButtonLayout = BACK_CONFIRM_LEFT_RIGHT; ~CrossPointSettings() = default; diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp new file mode 100644 index 0000000..eeda106 --- /dev/null +++ b/src/MappedInputManager.cpp @@ -0,0 +1,77 @@ +#include "MappedInputManager.h" + +decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button button) const { + const auto layout = static_cast(SETTINGS.frontButtonLayout); + + switch (button) { + case Button::Back: + switch (layout) { + case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM: + return InputManager::BTN_LEFT; + case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: + default: + return InputManager::BTN_BACK; + } + case Button::Confirm: + switch (layout) { + case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM: + return InputManager::BTN_RIGHT; + case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: + default: + return InputManager::BTN_CONFIRM; + } + case Button::Left: + switch (layout) { + case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM: + return InputManager::BTN_BACK; + case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: + default: + return InputManager::BTN_LEFT; + } + case Button::Right: + switch (layout) { + case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM: + return InputManager::BTN_CONFIRM; + case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: + default: + return InputManager::BTN_RIGHT; + } + case Button::Up: + return InputManager::BTN_UP; + case Button::Down: + return InputManager::BTN_DOWN; + case Button::Power: + return InputManager::BTN_POWER; + case Button::PageBack: + return InputManager::BTN_UP; + case Button::PageForward: + return InputManager::BTN_DOWN; + } + + return InputManager::BTN_BACK; +} + +bool MappedInputManager::wasPressed(const Button button) const { return inputManager.wasPressed(mapButton(button)); } + +bool MappedInputManager::wasReleased(const Button button) const { return inputManager.wasReleased(mapButton(button)); } + +bool MappedInputManager::isPressed(const Button button) const { return inputManager.isPressed(mapButton(button)); } + +bool MappedInputManager::wasAnyPressed() const { return inputManager.wasAnyPressed(); } + +bool MappedInputManager::wasAnyReleased() const { return inputManager.wasAnyReleased(); } + +unsigned long MappedInputManager::getHeldTime() const { return inputManager.getHeldTime(); } + +MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous, + const char* next) const { + const auto layout = static_cast(SETTINGS.frontButtonLayout); + + switch (layout) { + case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM: + return {previous, next, back, confirm}; + case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: + default: + return {back, confirm, previous, next}; + } +} diff --git a/src/MappedInputManager.h b/src/MappedInputManager.h new file mode 100644 index 0000000..138d793 --- /dev/null +++ b/src/MappedInputManager.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "CrossPointSettings.h" + +class MappedInputManager { + public: + enum class Button { Back, Confirm, Left, Right, Up, Down, Power, PageBack, PageForward }; + + struct Labels { + const char* btn1; + const char* btn2; + const char* btn3; + const char* btn4; + }; + + explicit MappedInputManager(InputManager& inputManager) : inputManager(inputManager) {} + + bool wasPressed(Button button) const; + bool wasReleased(Button button) const; + bool isPressed(Button button) const; + bool wasAnyPressed() const; + bool wasAnyReleased() const; + unsigned long getHeldTime() const; + Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const; + + private: + InputManager& inputManager; + decltype(InputManager::BTN_BACK) mapButton(Button button) const; +}; diff --git a/src/activities/Activity.h b/src/activities/Activity.h index 3a61db6..66dfde1 100644 --- a/src/activities/Activity.h +++ b/src/activities/Activity.h @@ -5,18 +5,19 @@ #include #include -class InputManager; +#include "../MappedInputManager.h" + class GfxRenderer; class Activity { protected: std::string name; GfxRenderer& renderer; - InputManager& inputManager; + MappedInputManager& mappedInput; public: - explicit Activity(std::string name, GfxRenderer& renderer, InputManager& inputManager) - : name(std::move(name)), renderer(renderer), inputManager(inputManager) {} + explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput) + : name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {} virtual ~Activity() = default; virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); } virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); } diff --git a/src/activities/ActivityWithSubactivity.h b/src/activities/ActivityWithSubactivity.h index af55987..141dbbc 100644 --- a/src/activities/ActivityWithSubactivity.h +++ b/src/activities/ActivityWithSubactivity.h @@ -10,8 +10,8 @@ class ActivityWithSubactivity : public Activity { void enterNewActivity(Activity* activity); public: - explicit ActivityWithSubactivity(std::string name, GfxRenderer& renderer, InputManager& inputManager) - : Activity(std::move(name), renderer, inputManager) {} + explicit ActivityWithSubactivity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput) + : Activity(std::move(name), renderer, mappedInput) {} void loop() override; void onExit() override; }; diff --git a/src/activities/boot_sleep/BootActivity.h b/src/activities/boot_sleep/BootActivity.h index a14d0c7..312f2ab 100644 --- a/src/activities/boot_sleep/BootActivity.h +++ b/src/activities/boot_sleep/BootActivity.h @@ -3,6 +3,7 @@ class BootActivity final : public Activity { public: - explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity("Boot", renderer, inputManager) {} + explicit BootActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) + : Activity("Boot", renderer, mappedInput) {} void onEnter() override; }; diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h index 774fd25..3a77d33 100644 --- a/src/activities/boot_sleep/SleepActivity.h +++ b/src/activities/boot_sleep/SleepActivity.h @@ -5,8 +5,8 @@ class Bitmap; class SleepActivity final : public Activity { public: - explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager) - : Activity("Sleep", renderer, inputManager) {} + explicit SleepActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) + : Activity("Sleep", renderer, mappedInput) {} void onEnter() override; private: diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 5e330d8..f62b393 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -49,14 +49,14 @@ void HomeActivity::onExit() { } void HomeActivity::loop() { - const bool prevPressed = - inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); - const bool nextPressed = - inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT); + const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left); + const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right); const int menuCount = getMenuItemCount(); - if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (hasContinueReading) { // Menu: Continue Reading, Browse, File transfer, Settings if (selectorIndex == 0) { @@ -143,7 +143,8 @@ void HomeActivity::render() const { renderer.drawText(UI_FONT_ID, 20, menuY, "Settings", selectorIndex != menuIndex); - renderer.drawButtonHints(UI_FONT_ID, "Back", "Confirm", "Left", "Right"); + const auto labels = mappedInput.mapLabels("Back", "Confirm", "Left", "Right"); + renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); } diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h index 0704819..fcbab54 100644 --- a/src/activities/home/HomeActivity.h +++ b/src/activities/home/HomeActivity.h @@ -24,10 +24,10 @@ class HomeActivity final : public Activity { int getMenuItemCount() const; public: - explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, + explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function& onContinueReading, const std::function& onReaderOpen, const std::function& onSettingsOpen, const std::function& onFileTransferOpen) - : Activity("Home", renderer, inputManager), + : Activity("Home", renderer, mappedInput), onContinueReading(onContinueReading), onReaderOpen(onReaderOpen), onSettingsOpen(onSettingsOpen), diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index e0ec682..eda20a8 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -57,7 +57,7 @@ void CrossPointWebServerActivity::onEnter() { // Launch network mode selection subactivity Serial.printf("[%lu] [WEBACT] Launching NetworkModeSelectionActivity...\n", millis()); enterNewActivity(new NetworkModeSelectionActivity( - renderer, inputManager, [this](const NetworkMode mode) { onNetworkModeSelected(mode); }, + renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); }, [this]() { onGoBack(); } // Cancel goes back to home )); } @@ -141,7 +141,7 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) state = WebServerActivityState::WIFI_SELECTION; Serial.printf("[%lu] [WEBACT] Launching WifiSelectionActivity...\n", millis()); - enterNewActivity(new WifiSelectionActivity(renderer, inputManager, + enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); } else { // AP mode - start access point @@ -174,7 +174,7 @@ void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) exitActivity(); state = WebServerActivityState::MODE_SELECTION; enterNewActivity(new NetworkModeSelectionActivity( - renderer, inputManager, [this](const NetworkMode mode) { onNetworkModeSelected(mode); }, + renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); }, [this]() { onGoBack(); })); } } @@ -305,7 +305,7 @@ void CrossPointWebServerActivity::loop() { } // Handle exit on Back button - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { onGoBack(); return; } @@ -428,5 +428,6 @@ void CrossPointWebServerActivity::renderServerRunning() const { REGULAR); } - renderer.drawButtonHints(UI_FONT_ID, "« Exit", "", "", ""); + const auto labels = mappedInput.mapLabels("« Exit", "", "", ""); + renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } diff --git a/src/activities/network/CrossPointWebServerActivity.h b/src/activities/network/CrossPointWebServerActivity.h index 038a0c4..deb7cea 100644 --- a/src/activities/network/CrossPointWebServerActivity.h +++ b/src/activities/network/CrossPointWebServerActivity.h @@ -63,9 +63,9 @@ class CrossPointWebServerActivity final : public ActivityWithSubactivity { void stopWebServer(); public: - explicit CrossPointWebServerActivity(GfxRenderer& renderer, InputManager& inputManager, + explicit CrossPointWebServerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function& onGoBack) - : ActivityWithSubactivity("CrossPointWebServer", renderer, inputManager), onGoBack(onGoBack) {} + : ActivityWithSubactivity("CrossPointWebServer", renderer, mappedInput), onGoBack(onGoBack) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/network/NetworkModeSelectionActivity.cpp b/src/activities/network/NetworkModeSelectionActivity.cpp index af68a20..0cc3594 100644 --- a/src/activities/network/NetworkModeSelectionActivity.cpp +++ b/src/activities/network/NetworkModeSelectionActivity.cpp @@ -51,23 +51,23 @@ void NetworkModeSelectionActivity::onExit() { void NetworkModeSelectionActivity::loop() { // Handle back button - cancel - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { onCancel(); return; } // Handle confirm button - select current option - if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { const NetworkMode mode = (selectedIndex == 0) ? NetworkMode::JOIN_NETWORK : NetworkMode::CREATE_HOTSPOT; onModeSelected(mode); return; } // Handle navigation - const bool prevPressed = - inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); - const bool nextPressed = - inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT); + const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left); + const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right); if (prevPressed) { selectedIndex = (selectedIndex + MENU_ITEM_COUNT - 1) % MENU_ITEM_COUNT; @@ -122,7 +122,8 @@ void NetworkModeSelectionActivity::render() const { } // Draw help text at bottom - renderer.drawButtonHints(UI_FONT_ID, "« Back", "Select", "", ""); + const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); + renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); } diff --git a/src/activities/network/NetworkModeSelectionActivity.h b/src/activities/network/NetworkModeSelectionActivity.h index 90f4282..b9f2e1e 100644 --- a/src/activities/network/NetworkModeSelectionActivity.h +++ b/src/activities/network/NetworkModeSelectionActivity.h @@ -31,10 +31,10 @@ class NetworkModeSelectionActivity final : public Activity { void render() const; public: - explicit NetworkModeSelectionActivity(GfxRenderer& renderer, InputManager& inputManager, + explicit NetworkModeSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function& onModeSelected, const std::function& onCancel) - : Activity("NetworkModeSelection", renderer, inputManager), onModeSelected(onModeSelected), onCancel(onCancel) {} + : Activity("NetworkModeSelection", renderer, mappedInput), onModeSelected(onModeSelected), onCancel(onCancel) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index 5bf25f1..9f0e502 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -190,7 +190,7 @@ void WifiSelectionActivity::selectNetwork(const int index) { // Don't allow screen updates while changing activity xSemaphoreTake(renderingMutex, portMAX_DELAY); enterNewActivity(new KeyboardEntryActivity( - renderer, inputManager, "Enter WiFi Password", + renderer, mappedInput, "Enter WiFi Password", "", // No initial text 50, // Y position 64, // Max password length @@ -302,17 +302,19 @@ void WifiSelectionActivity::loop() { // Handle save prompt state if (state == WifiSelectionState::SAVE_PROMPT) { - if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left)) { if (savePromptSelection > 0) { savePromptSelection--; updateRequired = true; } - } else if (inputManager.wasPressed(InputManager::BTN_RIGHT) || inputManager.wasPressed(InputManager::BTN_DOWN)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right)) { if (savePromptSelection < 1) { savePromptSelection++; updateRequired = true; } - } else if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (savePromptSelection == 0) { // User chose "Yes" - save the password xSemaphoreTake(renderingMutex, portMAX_DELAY); @@ -321,7 +323,7 @@ void WifiSelectionActivity::loop() { } // Complete - parent will start web server onComplete(true); - } else if (inputManager.wasPressed(InputManager::BTN_BACK)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { // Skip saving, complete anyway onComplete(true); } @@ -330,17 +332,19 @@ void WifiSelectionActivity::loop() { // Handle forget prompt state (connection failed with saved credentials) if (state == WifiSelectionState::FORGET_PROMPT) { - if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left)) { if (forgetPromptSelection > 0) { forgetPromptSelection--; updateRequired = true; } - } else if (inputManager.wasPressed(InputManager::BTN_RIGHT) || inputManager.wasPressed(InputManager::BTN_DOWN)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right)) { if (forgetPromptSelection < 1) { forgetPromptSelection++; updateRequired = true; } - } else if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (forgetPromptSelection == 0) { // User chose "Yes" - forget the network xSemaphoreTake(renderingMutex, portMAX_DELAY); @@ -356,7 +360,7 @@ void WifiSelectionActivity::loop() { // Go back to network list state = WifiSelectionState::NETWORK_LIST; updateRequired = true; - } else if (inputManager.wasPressed(InputManager::BTN_BACK)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { // Skip forgetting, go back to network list state = WifiSelectionState::NETWORK_LIST; updateRequired = true; @@ -373,7 +377,8 @@ void WifiSelectionActivity::loop() { // Handle connection failed state if (state == WifiSelectionState::CONNECTION_FAILED) { - if (inputManager.wasPressed(InputManager::BTN_BACK) || inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back) || + mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { // If we used saved credentials, offer to forget the network if (usedSavedPassword) { state = WifiSelectionState::FORGET_PROMPT; @@ -390,13 +395,13 @@ void WifiSelectionActivity::loop() { // Handle network list state if (state == WifiSelectionState::NETWORK_LIST) { // Check for Back button to exit (cancel) - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { onComplete(false); return; } // Check for Confirm button to select network or rescan - if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (!networks.empty()) { selectNetwork(selectedNetworkIndex); } else { @@ -406,12 +411,14 @@ void WifiSelectionActivity::loop() { } // Handle UP/DOWN navigation - if (inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left)) { if (selectedNetworkIndex > 0) { selectedNetworkIndex--; updateRequired = true; } - } else if (inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right)) { if (!networks.empty() && selectedNetworkIndex < static_cast(networks.size()) - 1) { selectedNetworkIndex++; updateRequired = true; @@ -557,7 +564,8 @@ void WifiSelectionActivity::renderNetworkList() const { // Draw help text renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved"); - renderer.drawButtonHints(UI_FONT_ID, "« Back", "Connect", "", ""); + const auto labels = mappedInput.mapLabels("« Back", "Connect", "", ""); + renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } void WifiSelectionActivity::renderConnecting() const { diff --git a/src/activities/network/WifiSelectionActivity.h b/src/activities/network/WifiSelectionActivity.h index 1cba2a4..33ea26b 100644 --- a/src/activities/network/WifiSelectionActivity.h +++ b/src/activities/network/WifiSelectionActivity.h @@ -92,9 +92,9 @@ class WifiSelectionActivity final : public ActivityWithSubactivity { std::string getSignalStrengthIndicator(int32_t rssi) const; public: - explicit WifiSelectionActivity(GfxRenderer& renderer, InputManager& inputManager, + explicit WifiSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function& onComplete) - : ActivityWithSubactivity("WifiSelection", renderer, inputManager), onComplete(onComplete) {} + : ActivityWithSubactivity("WifiSelection", renderer, mappedInput), onComplete(onComplete) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index ebf5b0a..6864a56 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -106,12 +106,12 @@ void EpubReaderActivity::loop() { } // Enter chapter selection activity - if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { // Don't start activity transition while rendering xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); enterNewActivity(new EpubReaderChapterSelectionActivity( - this->renderer, this->inputManager, epub, currentSpineIndex, + this->renderer, this->mappedInput, epub, currentSpineIndex, [this] { exitActivity(); updateRequired = true; @@ -129,21 +129,21 @@ void EpubReaderActivity::loop() { } // Long press BACK (1s+) goes directly to home - if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= goHomeMs) { + if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) { onGoHome(); return; } // Short press BACK goes to file selection - if (inputManager.wasReleased(InputManager::BTN_BACK) && inputManager.getHeldTime() < goHomeMs) { + if (mappedInput.wasReleased(MappedInputManager::Button::Back) && mappedInput.getHeldTime() < goHomeMs) { onGoBack(); return; } - const bool prevReleased = - inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); - const bool nextReleased = - inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT); + const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || + mappedInput.wasReleased(MappedInputManager::Button::Left); + const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || + mappedInput.wasReleased(MappedInputManager::Button::Right); if (!prevReleased && !nextReleased) { return; @@ -157,7 +157,7 @@ void EpubReaderActivity::loop() { return; } - const bool skipChapter = inputManager.getHeldTime() > skipChapterMs; + const bool skipChapter = mappedInput.getHeldTime() > skipChapterMs; if (skipChapter) { // We don't want to delete the section mid-render, so grab the semaphore diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index f1abc92..63d4887 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -27,9 +27,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity { void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; public: - explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr epub, + explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr epub, const std::function& onGoBack, const std::function& onGoHome) - : ActivityWithSubactivity("EpubReader", renderer, inputManager), + : ActivityWithSubactivity("EpubReader", renderer, mappedInput), epub(std::move(epub)), onGoBack(onGoBack), onGoHome(onGoHome) {} diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index ab9d8f7..dc0bee5 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -66,17 +66,17 @@ void EpubReaderChapterSelectionActivity::onExit() { } void EpubReaderChapterSelectionActivity::loop() { - const bool prevReleased = - inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); - const bool nextReleased = - inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT); + const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) || + mappedInput.wasReleased(MappedInputManager::Button::Left); + const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) || + mappedInput.wasReleased(MappedInputManager::Button::Right); - const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS; + const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; const int pageItems = getPageItems(); - if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { onSelectSpineIndex(selectorIndex); - } else if (inputManager.wasReleased(InputManager::BTN_BACK)) { + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoBack(); } else if (prevReleased) { if (skipPage) { diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.h b/src/activities/reader/EpubReaderChapterSelectionActivity.h index fefd225..cf3f190 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.h +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.h @@ -27,11 +27,11 @@ class EpubReaderChapterSelectionActivity final : public Activity { void renderScreen(); public: - explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, InputManager& inputManager, + explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::shared_ptr& epub, const int currentSpineIndex, const std::function& onGoBack, const std::function& onSelectSpineIndex) - : Activity("EpubReaderChapterSelection", renderer, inputManager), + : Activity("EpubReaderChapterSelection", renderer, mappedInput), epub(epub), currentSpineIndex(currentSpineIndex), onGoBack(onGoBack), diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp index e891d77..447db6d 100644 --- a/src/activities/reader/FileSelectionActivity.cpp +++ b/src/activities/reader/FileSelectionActivity.cpp @@ -89,7 +89,7 @@ void FileSelectionActivity::onExit() { void FileSelectionActivity::loop() { // Long press BACK (1s+) goes to root folder - if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= GO_HOME_MS) { + if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS) { if (basepath != "/") { basepath = "/"; loadFiles(); @@ -98,14 +98,14 @@ void FileSelectionActivity::loop() { return; } - const bool prevReleased = - inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); - const bool nextReleased = - inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT); + const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) || + mappedInput.wasReleased(MappedInputManager::Button::Left); + const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) || + mappedInput.wasReleased(MappedInputManager::Button::Right); - const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS; + const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; - if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (files.empty()) { return; } @@ -118,9 +118,9 @@ void FileSelectionActivity::loop() { } else { onSelect(basepath + files[selectorIndex]); } - } else if (inputManager.wasReleased(InputManager::BTN_BACK)) { + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { // Short press: go up one directory, or go home if at root - if (inputManager.getHeldTime() < GO_HOME_MS) { + if (mappedInput.getHeldTime() < GO_HOME_MS) { if (basepath != "/") { basepath.replace(basepath.find_last_of('/'), std::string::npos, ""); if (basepath.empty()) basepath = "/"; @@ -166,7 +166,8 @@ void FileSelectionActivity::render() const { renderer.drawCenteredText(READER_FONT_ID, 10, "Books", true, BOLD); // Help text - renderer.drawButtonHints(UI_FONT_ID, "« Home", "Open", "", ""); + const auto labels = mappedInput.mapLabels("« Home", "Open", "", ""); + renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); if (files.empty()) { renderer.drawText(UI_FONT_ID, 20, 60, "No books found"); diff --git a/src/activities/reader/FileSelectionActivity.h b/src/activities/reader/FileSelectionActivity.h index f642e20..88e97d0 100644 --- a/src/activities/reader/FileSelectionActivity.h +++ b/src/activities/reader/FileSelectionActivity.h @@ -25,10 +25,10 @@ class FileSelectionActivity final : public Activity { void loadFiles(); public: - explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager, + explicit FileSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function& onSelect, const std::function& onGoHome, std::string initialPath = "/") - : Activity("FileSelection", renderer, inputManager), + : Activity("FileSelection", renderer, mappedInput), basepath(initialPath.empty() ? "/" : std::move(initialPath)), onSelect(onSelect), onGoHome(onGoHome) {} diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index 222cc97..d98e167 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -61,7 +61,7 @@ std::unique_ptr ReaderActivity::loadXtc(const std::string& path) { void ReaderActivity::onSelectBookFile(const std::string& path) { currentBookPath = path; // Track current book path exitActivity(); - enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Loading...")); + enterNewActivity(new FullScreenMessageActivity(renderer, mappedInput, "Loading...")); if (isXtcFile(path)) { // Load XTC file @@ -70,7 +70,7 @@ void ReaderActivity::onSelectBookFile(const std::string& path) { onGoToXtcReader(std::move(xtc)); } else { exitActivity(); - enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load XTC", REGULAR, + enterNewActivity(new FullScreenMessageActivity(renderer, mappedInput, "Failed to load XTC", REGULAR, EInkDisplay::HALF_REFRESH)); delay(2000); onGoToFileSelection(); @@ -82,7 +82,7 @@ void ReaderActivity::onSelectBookFile(const std::string& path) { onGoToEpubReader(std::move(epub)); } else { exitActivity(); - enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load epub", REGULAR, + enterNewActivity(new FullScreenMessageActivity(renderer, mappedInput, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH)); delay(2000); onGoToFileSelection(); @@ -95,7 +95,7 @@ void ReaderActivity::onGoToFileSelection(const std::string& fromBookPath) { // If coming from a book, start in that book's folder; otherwise start from root const auto initialPath = fromBookPath.empty() ? "/" : extractFolderPath(fromBookPath); enterNewActivity(new FileSelectionActivity( - renderer, inputManager, [this](const std::string& path) { onSelectBookFile(path); }, onGoBack, initialPath)); + renderer, mappedInput, [this](const std::string& path) { onSelectBookFile(path); }, onGoBack, initialPath)); } void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) { @@ -103,7 +103,7 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) { currentBookPath = epubPath; exitActivity(); enterNewActivity(new EpubReaderActivity( - renderer, inputManager, std::move(epub), [this, epubPath] { onGoToFileSelection(epubPath); }, + renderer, mappedInput, std::move(epub), [this, epubPath] { onGoToFileSelection(epubPath); }, [this] { onGoBack(); })); } @@ -112,7 +112,7 @@ void ReaderActivity::onGoToXtcReader(std::unique_ptr xtc) { currentBookPath = xtcPath; exitActivity(); enterNewActivity(new XtcReaderActivity( - renderer, inputManager, std::move(xtc), [this, xtcPath] { onGoToFileSelection(xtcPath); }, + renderer, mappedInput, std::move(xtc), [this, xtcPath] { onGoToFileSelection(xtcPath); }, [this] { onGoBack(); })); } diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h index f40417e..df44afe 100644 --- a/src/activities/reader/ReaderActivity.h +++ b/src/activities/reader/ReaderActivity.h @@ -21,9 +21,9 @@ class ReaderActivity final : public ActivityWithSubactivity { void onGoToXtcReader(std::unique_ptr xtc); public: - explicit ReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::string initialBookPath, + explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath, const std::function& onGoBack) - : ActivityWithSubactivity("Reader", renderer, inputManager), + : ActivityWithSubactivity("Reader", renderer, mappedInput), initialBookPath(std::move(initialBookPath)), onGoBack(onGoBack) {} void onEnter() override; diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index aa9de70..8933606 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -71,21 +71,21 @@ void XtcReaderActivity::onExit() { void XtcReaderActivity::loop() { // Long press BACK (1s+) goes directly to home - if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= goHomeMs) { + if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) { onGoHome(); return; } // Short press BACK goes to file selection - if (inputManager.wasReleased(InputManager::BTN_BACK) && inputManager.getHeldTime() < goHomeMs) { + if (mappedInput.wasReleased(MappedInputManager::Button::Back) && mappedInput.getHeldTime() < goHomeMs) { onGoBack(); return; } - const bool prevReleased = - inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); - const bool nextReleased = - inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT); + const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || + mappedInput.wasReleased(MappedInputManager::Button::Left); + const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || + mappedInput.wasReleased(MappedInputManager::Button::Right); if (!prevReleased && !nextReleased) { return; @@ -98,7 +98,7 @@ void XtcReaderActivity::loop() { return; } - const bool skipPages = inputManager.getHeldTime() > skipPageMs; + const bool skipPages = mappedInput.getHeldTime() > skipPageMs; const int skipAmount = skipPages ? 10 : 1; if (prevReleased) { diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index f923d8a..bf72064 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -32,9 +32,9 @@ class XtcReaderActivity final : public Activity { void loadProgress(); public: - explicit XtcReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr xtc, + explicit XtcReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr xtc, const std::function& onGoBack, const std::function& onGoHome) - : Activity("XtcReader", renderer, inputManager), xtc(std::move(xtc)), onGoBack(onGoBack), onGoHome(onGoHome) {} + : Activity("XtcReader", renderer, mappedInput), xtc(std::move(xtc)), onGoBack(onGoBack), onGoHome(onGoHome) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/settings/OtaUpdateActivity.cpp b/src/activities/settings/OtaUpdateActivity.cpp index d31f410..846438b 100644 --- a/src/activities/settings/OtaUpdateActivity.cpp +++ b/src/activities/settings/OtaUpdateActivity.cpp @@ -72,7 +72,7 @@ void OtaUpdateActivity::onEnter() { // Launch WiFi selection subactivity Serial.printf("[%lu] [OTA] Launching WifiSelectionActivity...\n", millis()); - enterNewActivity(new WifiSelectionActivity(renderer, inputManager, + enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); } @@ -191,7 +191,7 @@ void OtaUpdateActivity::loop() { } if (state == WAITING_CONFIRMATION) { - if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { Serial.printf("[%lu] [OTA] New update available, starting download...\n", millis()); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = UPDATE_IN_PROGRESS; @@ -215,7 +215,7 @@ void OtaUpdateActivity::loop() { updateRequired = true; } - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { goBack(); } @@ -223,14 +223,14 @@ void OtaUpdateActivity::loop() { } if (state == FAILED) { - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { goBack(); } return; } if (state == NO_UPDATE) { - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { goBack(); } return; diff --git a/src/activities/settings/OtaUpdateActivity.h b/src/activities/settings/OtaUpdateActivity.h index 20be6fa..9e35567 100644 --- a/src/activities/settings/OtaUpdateActivity.h +++ b/src/activities/settings/OtaUpdateActivity.h @@ -35,8 +35,9 @@ class OtaUpdateActivity : public ActivityWithSubactivity { void render(); public: - explicit OtaUpdateActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& goBack) - : ActivityWithSubactivity("OtaUpdate", renderer, inputManager), goBack(goBack), updater() {} + explicit OtaUpdateActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& goBack) + : ActivityWithSubactivity("OtaUpdate", renderer, mappedInput), goBack(goBack), updater() {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 71fe331..0ddf409 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -9,7 +9,7 @@ // Define the static settings list namespace { -constexpr int settingsCount = 6; +constexpr int settingsCount = 7; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}}, @@ -20,6 +20,10 @@ const SettingInfo settingsList[settingsCount] = { SettingType::ENUM, &CrossPointSettings::orientation, {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}}, + {"Front Button Layout", + SettingType::ENUM, + &CrossPointSettings::frontButtonLayout, + {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm"}}, {"Check for updates", SettingType::ACTION, nullptr, {}}, }; } // namespace @@ -68,24 +72,26 @@ void SettingsActivity::loop() { } // Handle actions with early return - if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { toggleCurrentSetting(); updateRequired = true; return; } - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { SETTINGS.saveToFile(); onGoHome(); return; } // Handle navigation - if (inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left)) { // Move selection up (with wrap-around) selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1); updateRequired = true; - } else if (inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right)) { // Move selection down if (selectedSettingIndex < settingsCount - 1) { selectedSettingIndex++; @@ -113,7 +119,7 @@ void SettingsActivity::toggleCurrentSetting() { if (std::string(setting.name) == "Check for updates") { xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); - enterNewActivity(new OtaUpdateActivity(renderer, inputManager, [this] { + enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] { exitActivity(); updateRequired = true; })); @@ -173,10 +179,13 @@ void SettingsActivity::render() const { } } - // Draw help text - renderer.drawButtonHints(UI_FONT_ID, "« Save", "Toggle", "", ""); + // Draw version text above button hints renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), - pageHeight - 30, CROSSPOINT_VERSION); + pageHeight - 60, CROSSPOINT_VERSION); + + // Draw help text + const auto labels = mappedInput.mapLabels("« Save", "Toggle", "", ""); + renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); // Always use standard refresh for settings screen renderer.displayBuffer(); diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index d88dc85..83beb9d 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -34,8 +34,9 @@ class SettingsActivity final : public ActivityWithSubactivity { void toggleCurrentSetting(); public: - explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& onGoHome) - : ActivityWithSubactivity("Settings", renderer, inputManager), onGoHome(onGoHome) {} + explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& onGoHome) + : ActivityWithSubactivity("Settings", renderer, mappedInput), onGoHome(onGoHome) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/util/FullScreenMessageActivity.h b/src/activities/util/FullScreenMessageActivity.h index 8c6e30e..c798505 100644 --- a/src/activities/util/FullScreenMessageActivity.h +++ b/src/activities/util/FullScreenMessageActivity.h @@ -13,10 +13,10 @@ class FullScreenMessageActivity final : public Activity { EInkDisplay::RefreshMode refreshMode; public: - explicit FullScreenMessageActivity(GfxRenderer& renderer, InputManager& inputManager, std::string text, + explicit FullScreenMessageActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string text, const EpdFontStyle style = REGULAR, const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) - : Activity("FullScreenMessage", renderer, inputManager), + : Activity("FullScreenMessage", renderer, mappedInput), text(std::move(text)), style(style), refreshMode(refreshMode) {} diff --git a/src/activities/util/KeyboardEntryActivity.cpp b/src/activities/util/KeyboardEntryActivity.cpp index 8a72f1b..7a13aab 100644 --- a/src/activities/util/KeyboardEntryActivity.cpp +++ b/src/activities/util/KeyboardEntryActivity.cpp @@ -138,7 +138,7 @@ void KeyboardEntryActivity::handleKeyPress() { void KeyboardEntryActivity::loop() { // Navigation - if (inputManager.wasPressed(InputManager::BTN_UP)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Up)) { if (selectedRow > 0) { selectedRow--; // Clamp column to valid range for new row @@ -148,7 +148,7 @@ void KeyboardEntryActivity::loop() { updateRequired = true; } - if (inputManager.wasPressed(InputManager::BTN_DOWN)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Down)) { if (selectedRow < NUM_ROWS - 1) { selectedRow++; const int maxCol = getRowLength(selectedRow) - 1; @@ -157,7 +157,7 @@ void KeyboardEntryActivity::loop() { updateRequired = true; } - if (inputManager.wasPressed(InputManager::BTN_LEFT)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Left)) { // Special bottom row case if (selectedRow == SPECIAL_ROW) { // Bottom row has special key widths @@ -187,7 +187,7 @@ void KeyboardEntryActivity::loop() { updateRequired = true; } - if (inputManager.wasPressed(InputManager::BTN_RIGHT)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Right)) { const int maxCol = getRowLength(selectedRow) - 1; // Special bottom row case @@ -220,13 +220,13 @@ void KeyboardEntryActivity::loop() { } // Selection - if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { handleKeyPress(); updateRequired = true; } // Cancel - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { if (onCancel) { onCancel(); } diff --git a/src/activities/util/KeyboardEntryActivity.h b/src/activities/util/KeyboardEntryActivity.h index 552a3e8..1c1b1f3 100644 --- a/src/activities/util/KeyboardEntryActivity.h +++ b/src/activities/util/KeyboardEntryActivity.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include #include #include @@ -31,7 +30,7 @@ class KeyboardEntryActivity : public Activity { /** * Constructor * @param renderer Reference to the GfxRenderer for drawing - * @param inputManager Reference to InputManager for handling input + * @param mappedInput Reference to MappedInputManager for handling input * @param title Title to display above the keyboard * @param initialText Initial text to show in the input field * @param startY Y position to start rendering the keyboard @@ -40,11 +39,11 @@ class KeyboardEntryActivity : public Activity { * @param onComplete Callback invoked when input is complete * @param onCancel Callback invoked when input is cancelled */ - explicit KeyboardEntryActivity(GfxRenderer& renderer, InputManager& inputManager, std::string title = "Enter Text", - std::string initialText = "", const int startY = 10, const size_t maxLength = 0, - const bool isPassword = false, OnCompleteCallback onComplete = nullptr, - OnCancelCallback onCancel = nullptr) - : Activity("KeyboardEntry", renderer, inputManager), + explicit KeyboardEntryActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + std::string title = "Enter Text", std::string initialText = "", const int startY = 10, + const size_t maxLength = 0, const bool isPassword = false, + OnCompleteCallback onComplete = nullptr, OnCancelCallback onCancel = nullptr) + : Activity("KeyboardEntry", renderer, mappedInput), title(std::move(title)), text(std::move(initialText)), startY(startY), diff --git a/src/main.cpp b/src/main.cpp index 9b950f1..877d1c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ #include "Battery.h" #include "CrossPointSettings.h" #include "CrossPointState.h" +#include "MappedInputManager.h" #include "activities/boot_sleep/BootActivity.h" #include "activities/boot_sleep/SleepActivity.h" #include "activities/home/HomeActivity.h" @@ -41,6 +42,7 @@ EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); InputManager inputManager; +MappedInputManager mappedInputManager(inputManager); GfxRenderer renderer(einkDisplay); Activity* currentActivity; @@ -124,7 +126,7 @@ void waitForPowerRelease() { // Enter deep sleep mode void enterDeepSleep() { exitActivity(); - enterNewActivity(new SleepActivity(renderer, inputManager)); + enterNewActivity(new SleepActivity(renderer, mappedInputManager)); einkDisplay.deepSleep(); Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1); @@ -139,24 +141,24 @@ void enterDeepSleep() { void onGoHome(); void onGoToReader(const std::string& initialEpubPath) { exitActivity(); - enterNewActivity(new ReaderActivity(renderer, inputManager, initialEpubPath, onGoHome)); + enterNewActivity(new ReaderActivity(renderer, mappedInputManager, initialEpubPath, onGoHome)); } void onGoToReaderHome() { onGoToReader(std::string()); } void onContinueReading() { onGoToReader(APP_STATE.openEpubPath); } void onGoToFileTransfer() { exitActivity(); - enterNewActivity(new CrossPointWebServerActivity(renderer, inputManager, onGoHome)); + enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome)); } void onGoToSettings() { exitActivity(); - enterNewActivity(new SettingsActivity(renderer, inputManager, onGoHome)); + enterNewActivity(new SettingsActivity(renderer, mappedInputManager, onGoHome)); } void onGoHome() { exitActivity(); - enterNewActivity(new HomeActivity(renderer, inputManager, onContinueReading, onGoToReaderHome, onGoToSettings, + enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToReaderHome, onGoToSettings, onGoToFileTransfer)); } @@ -193,7 +195,7 @@ void setup() { Serial.printf("[%lu] [ ] SD card initialization failed\n", millis()); setupDisplayAndFonts(); exitActivity(); - enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "SD card error", BOLD)); + enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", BOLD)); return; } @@ -205,7 +207,7 @@ void setup() { setupDisplayAndFonts(); exitActivity(); - enterNewActivity(new BootActivity(renderer, inputManager)); + enterNewActivity(new BootActivity(renderer, mappedInputManager)); APP_STATE.loadFromFile(); if (APP_STATE.openEpubPath.empty()) {