diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 232c7c57..320ce217 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -22,8 +22,59 @@ void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) { namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 23; +constexpr uint8_t SETTINGS_COUNT = 27; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; + +// Validate front button mapping to ensure each hardware button is unique. +// If duplicates are detected, reset to the default physical order to prevent invalid mappings. +void validateFrontButtonMapping(CrossPointSettings& settings) { + // Snapshot the logical->hardware mapping so we can compare for duplicates. + const uint8_t mapping[] = {settings.frontButtonBack, settings.frontButtonConfirm, settings.frontButtonLeft, + settings.frontButtonRight}; + for (size_t i = 0; i < 4; i++) { + for (size_t j = i + 1; j < 4; j++) { + if (mapping[i] == mapping[j]) { + // Duplicate detected: restore the default physical order (Back, Confirm, Left, Right). + settings.frontButtonBack = CrossPointSettings::FRONT_HW_BACK; + settings.frontButtonConfirm = CrossPointSettings::FRONT_HW_CONFIRM; + settings.frontButtonLeft = CrossPointSettings::FRONT_HW_LEFT; + settings.frontButtonRight = CrossPointSettings::FRONT_HW_RIGHT; + return; + } + } + } +} + +// Convert legacy front button layout into explicit logical->hardware mapping. +void applyLegacyFrontButtonLayout(CrossPointSettings& settings) { + switch (static_cast(settings.frontButtonLayout)) { + case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM: + settings.frontButtonBack = CrossPointSettings::FRONT_HW_LEFT; + settings.frontButtonConfirm = CrossPointSettings::FRONT_HW_RIGHT; + settings.frontButtonLeft = CrossPointSettings::FRONT_HW_BACK; + settings.frontButtonRight = CrossPointSettings::FRONT_HW_CONFIRM; + break; + case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT: + settings.frontButtonBack = CrossPointSettings::FRONT_HW_CONFIRM; + settings.frontButtonConfirm = CrossPointSettings::FRONT_HW_LEFT; + settings.frontButtonLeft = CrossPointSettings::FRONT_HW_BACK; + settings.frontButtonRight = CrossPointSettings::FRONT_HW_RIGHT; + break; + case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT: + settings.frontButtonBack = CrossPointSettings::FRONT_HW_BACK; + settings.frontButtonConfirm = CrossPointSettings::FRONT_HW_CONFIRM; + settings.frontButtonLeft = CrossPointSettings::FRONT_HW_RIGHT; + settings.frontButtonRight = CrossPointSettings::FRONT_HW_LEFT; + break; + case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: + default: + settings.frontButtonBack = CrossPointSettings::FRONT_HW_BACK; + settings.frontButtonConfirm = CrossPointSettings::FRONT_HW_CONFIRM; + settings.frontButtonLeft = CrossPointSettings::FRONT_HW_LEFT; + settings.frontButtonRight = CrossPointSettings::FRONT_HW_RIGHT; + break; + } +} } // namespace bool CrossPointSettings::saveToFile() const { @@ -42,7 +93,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, shortPwrBtn); serialization::writePod(outputFile, statusBar); serialization::writePod(outputFile, orientation); - serialization::writePod(outputFile, frontButtonLayout); + serialization::writePod(outputFile, frontButtonLayout); // legacy serialization::writePod(outputFile, sideButtonLayout); serialization::writePod(outputFile, fontFamily); serialization::writePod(outputFile, fontSize); @@ -60,6 +111,10 @@ bool CrossPointSettings::saveToFile() const { serialization::writeString(outputFile, std::string(opdsUsername)); serialization::writeString(outputFile, std::string(opdsPassword)); serialization::writePod(outputFile, sleepScreenCoverFilter); + serialization::writePod(outputFile, frontButtonBack); + serialization::writePod(outputFile, frontButtonConfirm); + serialization::writePod(outputFile, frontButtonLeft); + serialization::writePod(outputFile, frontButtonRight); // New fields added at end for backward compatibility outputFile.close(); @@ -86,6 +141,8 @@ bool CrossPointSettings::loadFromFile() { // load settings that exist (support older files with fewer fields) uint8_t settingsRead = 0; + // Track whether remap fields were present in the settings file. + bool frontButtonMappingRead = false; do { readAndValidate(inputFile, sleepScreen, SLEEP_SCREEN_MODE_COUNT); if (++settingsRead >= fileSettingsCount) break; @@ -97,7 +154,7 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; readAndValidate(inputFile, orientation, ORIENTATION_COUNT); if (++settingsRead >= fileSettingsCount) break; - readAndValidate(inputFile, frontButtonLayout, FRONT_BUTTON_LAYOUT_COUNT); + readAndValidate(inputFile, frontButtonLayout, FRONT_BUTTON_LAYOUT_COUNT); // legacy if (++settingsRead >= fileSettingsCount) break; readAndValidate(inputFile, sideButtonLayout, SIDE_BUTTON_LAYOUT_COUNT); if (++settingsRead >= fileSettingsCount) break; @@ -148,9 +205,23 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT); if (++settingsRead >= fileSettingsCount) break; + readAndValidate(inputFile, frontButtonBack, FRONT_BUTTON_HARDWARE_COUNT); + if (++settingsRead >= fileSettingsCount) break; + readAndValidate(inputFile, frontButtonConfirm, FRONT_BUTTON_HARDWARE_COUNT); + if (++settingsRead >= fileSettingsCount) break; + readAndValidate(inputFile, frontButtonLeft, FRONT_BUTTON_HARDWARE_COUNT); + if (++settingsRead >= fileSettingsCount) break; + readAndValidate(inputFile, frontButtonRight, FRONT_BUTTON_HARDWARE_COUNT); + frontButtonMappingRead = true; // New fields added at end for backward compatibility } while (false); + if (frontButtonMappingRead) { + validateFrontButtonMapping(*this); + } else { + applyLegacyFrontButtonLayout(*this); + } + inputFile.close(); Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis()); return true; diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index c450d348..10b7ba07 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -42,7 +42,7 @@ class CrossPointSettings { ORIENTATION_COUNT }; - // Front button layout options + // Front button layout options (legacy) // Default: Back, Confirm, Left, Right // Swapped: Left, Right, Back, Confirm enum FRONT_BUTTON_LAYOUT { @@ -53,6 +53,15 @@ class CrossPointSettings { FRONT_BUTTON_LAYOUT_COUNT }; + // Front button hardware identifiers (for remapping) + enum FRONT_BUTTON_HARDWARE { + FRONT_HW_BACK = 0, + FRONT_HW_CONFIRM = 1, + FRONT_HW_LEFT = 2, + FRONT_HW_RIGHT = 3, + FRONT_BUTTON_HARDWARE_COUNT + }; + // Side button layout options // Default: Previous, Next // Swapped: Next, Previous @@ -113,9 +122,15 @@ class CrossPointSettings { // EPUB reading orientation settings // 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise uint8_t orientation = PORTRAIT; - // Button layouts + // Button layouts (front layout retained for migration only) uint8_t frontButtonLayout = BACK_CONFIRM_LEFT_RIGHT; uint8_t sideButtonLayout = PREV_NEXT; + // Front button remap (logical -> hardware) + // Used by MappedInputManager to translate logical buttons into physical front buttons. + uint8_t frontButtonBack = FRONT_HW_BACK; + uint8_t frontButtonConfirm = FRONT_HW_CONFIRM; + uint8_t frontButtonLeft = FRONT_HW_LEFT; + uint8_t frontButtonRight = FRONT_HW_RIGHT; // Reader font settings uint8_t fontFamily = BOOKERLY; uint8_t fontSize = MEDIUM; diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp index e5423724..467c0f75 100644 --- a/src/MappedInputManager.cpp +++ b/src/MappedInputManager.cpp @@ -5,26 +5,11 @@ namespace { using ButtonIndex = uint8_t; -struct FrontLayoutMap { - ButtonIndex back; - ButtonIndex confirm; - ButtonIndex left; - ButtonIndex right; -}; - struct SideLayoutMap { ButtonIndex pageBack; ButtonIndex pageForward; }; -// Order matches CrossPointSettings::FRONT_BUTTON_LAYOUT. -constexpr FrontLayoutMap kFrontLayouts[] = { - {HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM, HalGPIO::BTN_LEFT, HalGPIO::BTN_RIGHT}, - {HalGPIO::BTN_LEFT, HalGPIO::BTN_RIGHT, HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM}, - {HalGPIO::BTN_CONFIRM, HalGPIO::BTN_LEFT, HalGPIO::BTN_BACK, HalGPIO::BTN_RIGHT}, - {HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM, HalGPIO::BTN_RIGHT, HalGPIO::BTN_LEFT}, -}; - // Order matches CrossPointSettings::SIDE_BUTTON_LAYOUT. constexpr SideLayoutMap kSideLayouts[] = { {HalGPIO::BTN_UP, HalGPIO::BTN_DOWN}, @@ -33,29 +18,36 @@ constexpr SideLayoutMap kSideLayouts[] = { } // namespace bool MappedInputManager::mapButton(const Button button, bool (HalGPIO::*fn)(uint8_t) const) const { - const auto frontLayout = static_cast(SETTINGS.frontButtonLayout); const auto sideLayout = static_cast(SETTINGS.sideButtonLayout); - const auto& front = kFrontLayouts[frontLayout]; const auto& side = kSideLayouts[sideLayout]; switch (button) { case Button::Back: - return (gpio.*fn)(front.back); + // Logical Back maps to user-configured front button. + return (gpio.*fn)(SETTINGS.frontButtonBack); case Button::Confirm: - return (gpio.*fn)(front.confirm); + // Logical Confirm maps to user-configured front button. + return (gpio.*fn)(SETTINGS.frontButtonConfirm); case Button::Left: - return (gpio.*fn)(front.left); + // Logical Left maps to user-configured front button. + return (gpio.*fn)(SETTINGS.frontButtonLeft); case Button::Right: - return (gpio.*fn)(front.right); + // Logical Right maps to user-configured front button. + return (gpio.*fn)(SETTINGS.frontButtonRight); case Button::Up: + // Side buttons remain fixed for Up/Down. return (gpio.*fn)(HalGPIO::BTN_UP); case Button::Down: + // Side buttons remain fixed for Up/Down. return (gpio.*fn)(HalGPIO::BTN_DOWN); case Button::Power: + // Power button bypasses remapping. return (gpio.*fn)(HalGPIO::BTN_POWER); case Button::PageBack: + // Reader page navigation uses side buttons and can be swapped via settings. return (gpio.*fn)(side.pageBack); case Button::PageForward: + // Reader page navigation uses side buttons and can be swapped via settings. return (gpio.*fn)(side.pageForward); } @@ -76,17 +68,42 @@ unsigned long MappedInputManager::getHeldTime() const { return gpio.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); + // Build the label order based on the configured hardware mapping. + auto labelForHardware = [&](uint8_t hw) -> const char* { + // Compare against configured logical roles and return the matching label. + if (hw == SETTINGS.frontButtonBack) { + return back; + } + if (hw == SETTINGS.frontButtonConfirm) { + return confirm; + } + if (hw == SETTINGS.frontButtonLeft) { + return previous; + } + if (hw == SETTINGS.frontButtonRight) { + return next; + } + return ""; + }; - switch (layout) { - case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM: - return {previous, next, back, confirm}; - case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT: - return {previous, back, confirm, next}; - case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT: - return {back, confirm, next, previous}; - case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT: - default: - return {back, confirm, previous, next}; + return {labelForHardware(HalGPIO::BTN_BACK), labelForHardware(HalGPIO::BTN_CONFIRM), + labelForHardware(HalGPIO::BTN_LEFT), labelForHardware(HalGPIO::BTN_RIGHT)}; +} + +int MappedInputManager::getPressedFrontButton() const { + // Scan the raw front buttons in hardware order. + // This bypasses remapping so the remap activity can capture physical presses. + if (gpio.wasPressed(HalGPIO::BTN_BACK)) { + return HalGPIO::BTN_BACK; } + if (gpio.wasPressed(HalGPIO::BTN_CONFIRM)) { + return HalGPIO::BTN_CONFIRM; + } + if (gpio.wasPressed(HalGPIO::BTN_LEFT)) { + return HalGPIO::BTN_LEFT; + } + if (gpio.wasPressed(HalGPIO::BTN_RIGHT)) { + return HalGPIO::BTN_RIGHT; + } + return -1; } \ No newline at end of file diff --git a/src/MappedInputManager.h b/src/MappedInputManager.h index f507a928..bd594a25 100644 --- a/src/MappedInputManager.h +++ b/src/MappedInputManager.h @@ -22,6 +22,8 @@ class MappedInputManager { bool wasAnyReleased() const; unsigned long getHeldTime() const; Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const; + // Returns the raw front button index that was pressed this frame (or -1 if none). + int getPressedFrontButton() const; private: HalGPIO& gpio; diff --git a/src/activities/settings/ButtonRemapActivity.cpp b/src/activities/settings/ButtonRemapActivity.cpp new file mode 100644 index 00000000..e0ee5bfa --- /dev/null +++ b/src/activities/settings/ButtonRemapActivity.cpp @@ -0,0 +1,226 @@ +#include "ButtonRemapActivity.h" + +#include + +#include "CrossPointSettings.h" +#include "MappedInputManager.h" +#include "fontIds.h" + +namespace { +// UI steps correspond to logical roles in order: Back, Confirm, Left, Right. +constexpr uint8_t kRoleCount = 4; +// Marker used when a role has not been assigned yet. +constexpr uint8_t kUnassigned = 0xFF; +// Duration to show temporary error text when reassigning a button. +constexpr unsigned long kErrorDisplayMs = 1500; +} // namespace + +void ButtonRemapActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void ButtonRemapActivity::onEnter() { + Activity::onEnter(); + + renderingMutex = xSemaphoreCreateMutex(); + // Start with all roles unassigned to avoid duplicate blocking. + currentStep = 0; + tempMapping[0] = kUnassigned; + tempMapping[1] = kUnassigned; + tempMapping[2] = kUnassigned; + tempMapping[3] = kUnassigned; + errorMessage.clear(); + errorUntil = 0; + updateRequired = true; + + xTaskCreate(&ButtonRemapActivity::taskTrampoline, "ButtonRemapTask", 4096, this, 1, &displayTaskHandle); +} + +void ButtonRemapActivity::onExit() { + Activity::onExit(); + + // Ensure display task is stopped outside of active rendering. + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void ButtonRemapActivity::loop() { + // Side buttons: + // - Up: reset mapping to defaults and exit. + // - Down: cancel without saving. + if (mappedInput.wasPressed(MappedInputManager::Button::Up)) { + // Persist default mapping immediately so the user can recover quickly. + SETTINGS.frontButtonBack = CrossPointSettings::FRONT_HW_BACK; + SETTINGS.frontButtonConfirm = CrossPointSettings::FRONT_HW_CONFIRM; + SETTINGS.frontButtonLeft = CrossPointSettings::FRONT_HW_LEFT; + SETTINGS.frontButtonRight = CrossPointSettings::FRONT_HW_RIGHT; + SETTINGS.saveToFile(); + onBack(); + return; + } + + if (mappedInput.wasPressed(MappedInputManager::Button::Down)) { + // Exit without changing settings. + onBack(); + return; + } + + // Wait for the UI to refresh before accepting another assignment. + // This avoids rapid double-presses that can advance the step without a visible redraw. + if (updateRequired) { + return; + } + + // Wait for a front button press to assign to the current role. + const int pressedButton = mappedInput.getPressedFrontButton(); + if (pressedButton < 0) { + return; + } + + // Update temporary mapping and advance the remap step. + // Only accept the press if this hardware button isn't already assigned elsewhere. + if (!validateUnassigned(static_cast(pressedButton))) { + updateRequired = true; + return; + } + tempMapping[currentStep] = static_cast(pressedButton); + currentStep++; + + if (currentStep >= kRoleCount) { + // All roles assigned; save to settings and exit. + applyTempMapping(); + SETTINGS.saveToFile(); + onBack(); + return; + } + + updateRequired = true; +} + +[[noreturn]] void ButtonRemapActivity::displayTaskLoop() { + while (true) { + if (updateRequired) { + // Ensure render calls are serialized with UI thread changes. + xSemaphoreTake(renderingMutex, portMAX_DELAY); + render(); + updateRequired = false; + xSemaphoreGive(renderingMutex); + } + + // Clear any temporary warning after its timeout. + if (errorUntil > 0 && millis() > errorUntil) { + errorMessage.clear(); + errorUntil = 0; + updateRequired = true; + } + + vTaskDelay(50 / portTICK_PERIOD_MS); + } +} + +void ButtonRemapActivity::render() { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + const auto labelForHardware = [&](uint8_t hardwareIndex) -> const char* { + for (uint8_t i = 0; i < kRoleCount; i++) { + if (tempMapping[i] == hardwareIndex) { + return getRoleName(i); + } + } + return "-"; + }; + + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Remap Front Buttons", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 40, "Press a front button for each role"); + + for (uint8_t i = 0; i < kRoleCount; i++) { + const int y = 70 + i * 30; + const bool isSelected = (i == currentStep); + + // Highlight the role that is currently being assigned. + if (isSelected) { + renderer.fillRect(0, y - 2, pageWidth - 1, 30); + } + + const char* roleName = getRoleName(i); + renderer.drawText(UI_10_FONT_ID, 20, y, roleName, !isSelected); + + // Show currently assigned hardware button (or unassigned). + const char* assigned = (tempMapping[i] == kUnassigned) ? "Unassigned" : getHardwareName(tempMapping[i]); + const auto width = renderer.getTextWidth(UI_10_FONT_ID, assigned); + renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, y, assigned, !isSelected); + } + + // Temporary warning banner for duplicates. + if (!errorMessage.empty()) { + renderer.drawCenteredText(UI_10_FONT_ID, 210, errorMessage.c_str(), true); + } + + // Provide side button actions at the bottom of the screen (split across two lines). + renderer.drawCenteredText(SMALL_FONT_ID, 250, "Side button Up: Reset to default layout", true); + renderer.drawCenteredText(SMALL_FONT_ID, 280, "Side button Down: Cancel remapping", true); + + // Live preview of logical labels under front buttons. + // This mirrors the on-device front button order: Back, Confirm, Left, Right. + renderer.drawButtonHints(UI_10_FONT_ID, labelForHardware(CrossPointSettings::FRONT_HW_BACK), + labelForHardware(CrossPointSettings::FRONT_HW_CONFIRM), + labelForHardware(CrossPointSettings::FRONT_HW_LEFT), + labelForHardware(CrossPointSettings::FRONT_HW_RIGHT)); + renderer.displayBuffer(); +} + +void ButtonRemapActivity::applyTempMapping() { + // Commit temporary mapping into settings (logical role -> hardware). + SETTINGS.frontButtonBack = tempMapping[0]; + SETTINGS.frontButtonConfirm = tempMapping[1]; + SETTINGS.frontButtonLeft = tempMapping[2]; + SETTINGS.frontButtonRight = tempMapping[3]; +} + +bool ButtonRemapActivity::validateUnassigned(const uint8_t pressedButton) { + // Block reusing a hardware button already assigned to another role. + for (uint8_t i = 0; i < kRoleCount; i++) { + if (tempMapping[i] == pressedButton && i != currentStep) { + errorMessage = "Already assigned"; + errorUntil = millis() + kErrorDisplayMs; + return false; + } + } + return true; +} + +const char* ButtonRemapActivity::getRoleName(const uint8_t roleIndex) const { + switch (roleIndex) { + case 0: + return "Back"; + case 1: + return "Confirm"; + case 2: + return "Left"; + case 3: + default: + return "Right"; + } +} + +const char* ButtonRemapActivity::getHardwareName(const uint8_t buttonIndex) const { + switch (buttonIndex) { + case CrossPointSettings::FRONT_HW_BACK: + return "Back (1st button)"; + case CrossPointSettings::FRONT_HW_CONFIRM: + return "Confirm (2nd button)"; + case CrossPointSettings::FRONT_HW_LEFT: + return "Left (3rd button)"; + case CrossPointSettings::FRONT_HW_RIGHT: + return "Right (4th button)"; + default: + return "Unknown"; + } +} diff --git a/src/activities/settings/ButtonRemapActivity.h b/src/activities/settings/ButtonRemapActivity.h new file mode 100644 index 00000000..f87a66ea --- /dev/null +++ b/src/activities/settings/ButtonRemapActivity.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include + +#include +#include + +#include "activities/Activity.h" + +class ButtonRemapActivity final : public Activity { + public: + explicit ButtonRemapActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& onBack) + : Activity("ButtonRemap", renderer, mappedInput), onBack(onBack) {} + + void onEnter() override; + void onExit() override; + void loop() override; + + private: + // Rendering task state. + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + bool updateRequired = false; + + // Callback used to exit the remap flow back to the settings list. + const std::function onBack; + // Index of the logical role currently awaiting input. + uint8_t currentStep = 0; + // Temporary mapping from logical role -> hardware button index. + uint8_t tempMapping[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + // Error banner timing (used when reassigning duplicate buttons). + unsigned long errorUntil = 0; + std::string errorMessage; + + // FreeRTOS task helpers. + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render(); + + // Commit temporary mapping to settings. + void applyTempMapping(); + // Returns false if a hardware button is already assigned to a different role. + bool validateUnassigned(uint8_t pressedButton); + // Labels for UI display. + const char* getRoleName(uint8_t roleIndex) const; + const char* getHardwareName(uint8_t buttonIndex) const; +}; diff --git a/src/activities/settings/CategorySettingsActivity.cpp b/src/activities/settings/CategorySettingsActivity.cpp index 7fd5ef5f..8c8d01c1 100644 --- a/src/activities/settings/CategorySettingsActivity.cpp +++ b/src/activities/settings/CategorySettingsActivity.cpp @@ -5,6 +5,7 @@ #include +#include "ButtonRemapActivity.h" #include "CalibreSettingsActivity.h" #include "ClearCacheActivity.h" #include "CrossPointSettings.h" @@ -127,6 +128,15 @@ void CategorySettingsActivity::toggleCurrentSetting() { updateRequired = true; })); xSemaphoreGive(renderingMutex); + } else if (strcmp(setting.name, "Remap Front Buttons") == 0) { + // Start the button remap flow. + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new ButtonRemapActivity(renderer, mappedInput, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); } } else { return; @@ -186,7 +196,7 @@ void CategorySettingsActivity::render() const { renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), pageHeight - 60, CROSSPOINT_VERSION); - const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", ""); + const auto labels = mappedInput.mapLabels("« Back", "Toggle", "Up", "Down"); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 7316db05..d5c717d8 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -40,9 +40,8 @@ const SettingInfo readerSettings[readerSettingsCount] = { constexpr int controlsSettingsCount = 4; const SettingInfo controlsSettings[controlsSettingsCount] = { - SettingInfo::Enum( - "Front Button Layout", &CrossPointSettings::frontButtonLayout, - {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght", "Bck, Cnfrm, Rght, Lft"}), + // Launches the remap wizard for front buttons. + SettingInfo::Action("Remap Front Buttons"), SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout, {"Prev, Next", "Next, Prev"}), SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip), @@ -199,7 +198,7 @@ void SettingsActivity::render() const { pageHeight - 60, CROSSPOINT_VERSION); // Draw help text - const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); + const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); // Always use standard refresh for settings screen