From a1d0ddec49211f06daea31a663ecd1ca074ef66d Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sun, 26 Apr 2026 12:26:29 +0200 Subject: [PATCH 1/5] Introduce extended button handler --- lib/I18n/translations/english.yaml | 27 ++++ src/ButtonEventManager.cpp | 121 ++++++++++++++++++ src/ButtonEventManager.h | 84 ++++++++++++ src/CrossPointSettings.cpp | 5 +- src/CrossPointSettings.h | 55 +++++++- src/SettingsList.h | 114 ++++++++++++++++- src/activities/Activity.h | 9 +- src/activities/ActivityManager.cpp | 8 ++ src/activities/ActivityManager.h | 13 +- src/activities/reader/EpubReaderActivity.cpp | 116 ++++++++++++++++- src/activities/reader/EpubReaderActivity.h | 1 + src/activities/reader/MdReaderActivity.cpp | 62 ++++++++- src/activities/reader/MdReaderActivity.h | 1 + src/activities/reader/ReaderUtils.h | 17 +-- src/activities/reader/TxtReaderActivity.cpp | 48 +++++++ src/activities/reader/TxtReaderActivity.h | 1 + src/activities/reader/XtcReaderActivity.cpp | 55 ++++++-- src/activities/reader/XtcReaderActivity.h | 1 + src/main.cpp | 128 +++++++++++++++++++ 19 files changed, 833 insertions(+), 33 deletions(-) create mode 100644 src/ButtonEventManager.cpp create mode 100644 src/ButtonEventManager.h diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 8df785b2..cc4eaa80 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -547,3 +547,30 @@ STR_CAPTIVE_PORTAL_DETECTED: "Login Required" STR_CAPTIVE_PORTAL_HINT_1: "Network requires browser login. On another device," STR_CAPTIVE_PORTAL_HINT_2: "visit the URL below to authorize, then press OK." STR_CAPTIVE_PORTAL_DONE: "I'm authorized" +STR_MENU_BTN_ACTIONS: "Button Actions" +STR_BTN_SHORT_PRESS: "Short Press" +STR_BTN_DOUBLE_PRESS: "Double Press" +STR_BTN_LONG_PRESS: "Long Press" +STR_BTN_BACK: "Back Button" +STR_BTN_CONFIRM: "Confirm Button" +STR_BTN_LEFT: "Left Button" +STR_BTN_RIGHT: "Right Button" +STR_BTN_UP: "Up Button" +STR_BTN_DOWN: "Down Button" +STR_BTN_PAGE_BACK: "Page Back Button" +STR_BTN_PAGE_FORWARD: "Page Forward Button" +STR_BTN_POWER: "Power Button" +STR_BTN_ACT_DEFAULT: "Default" +STR_BTN_ACT_PAGE_FORWARD: "Next Page" +STR_BTN_ACT_PAGE_BACK: "Previous Page" +STR_BTN_ACT_PAGE_FORWARD_10: "Skip 10 Pages Forward" +STR_BTN_ACT_PAGE_BACK_10: "Skip 10 Pages Back" +STR_BTN_ACT_GO_HOME: "Go Home" +STR_BTN_ACT_SLEEP: "Sleep" +STR_BTN_ACT_FORCE_REFRESH: "Refresh Screen" +STR_BTN_ACT_OPEN_TOC: "Open Table of Contents" +STR_BTN_ACT_OPEN_BOOKMARKS: "Open Bookmarks" +STR_BTN_ACT_STAR_PAGE: "Star Page" +STR_BTN_ACT_FOOTNOTES: "Footnotes" +STR_BTN_ACT_NEXT_SECTION: "Next Section / Chapter" +STR_BTN_ACT_PREV_SECTION: "Previous Section / Chapter" diff --git a/src/ButtonEventManager.cpp b/src/ButtonEventManager.cpp new file mode 100644 index 00000000..16345b55 --- /dev/null +++ b/src/ButtonEventManager.cpp @@ -0,0 +1,121 @@ +#include "ButtonEventManager.h" + +#include "CrossPointSettings.h" + +// Required for constexpr array definition in .cpp +constexpr ButtonEventManager::Button ButtonEventManager::ALL_BUTTONS[ButtonEventManager::NUM_BUTTONS]; + +bool ButtonEventManager::hasDoubleAction(const Button button) { + using BA = CrossPointSettings::BUTTON_ACTION; + switch (button) { + case Button::Back: + return SETTINGS.btnDoubleBack != BA::BTN_DEFAULT; + case Button::Confirm: + return SETTINGS.btnDoubleConfirm != BA::BTN_DEFAULT; + case Button::Left: + return SETTINGS.btnDoubleLeft != BA::BTN_DEFAULT; + case Button::Right: + return SETTINGS.btnDoubleRight != BA::BTN_DEFAULT; + case Button::Up: + return SETTINGS.btnDoubleUp != BA::BTN_DEFAULT; + case Button::Down: + return SETTINGS.btnDoubleDown != BA::BTN_DEFAULT; + case Button::PageBack: + return SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT; + case Button::PageForward: + return SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT; + case Button::Power: + return SETTINGS.btnDoublePower != BA::BTN_DEFAULT; + } + return false; +} + +void ButtonEventManager::pushEvent(const Button button, const PressType type) { + const int next = (eventTail + 1) % EVENT_BUF; + if (next == eventHead) return; // buffer full, drop oldest not possible — just drop newest + eventBuf[eventTail] = {button, type}; + eventTail = next; +} + +bool ButtonEventManager::consumeEvent(ButtonEvent& out) { + if (eventHead == eventTail) return false; + out = eventBuf[eventHead]; + eventHead = (eventHead + 1) % EVENT_BUF; + return true; +} + +void ButtonEventManager::drain() { + for (auto& b : buttons) { + b.state = State::Idle; + b.pressDownTime = 0; + b.releaseTime = 0; + } + eventHead = eventTail = 0; +} + +void ButtonEventManager::processButton(const int idx, const Button btn) { + PerButton& s = buttons[idx]; + const unsigned long now = millis(); + const bool pressed = input.wasPressed(btn); + const bool released = input.wasReleased(btn); + const bool held = input.isPressed(btn); + + switch (s.state) { + case State::Idle: + if (pressed) { + s.state = State::Pressed; + s.pressDownTime = now; + } + break; + + case State::Pressed: + if (released) { + const unsigned long heldMs = now - s.pressDownTime; + if (heldMs >= LONG_PRESS_MS) { + pushEvent(btn, PressType::Long); + s.state = State::Idle; + } else if (hasDoubleAction(btn)) { + // Delay short-press decision until double-click window expires + s.releaseTime = now; + s.state = State::ReleasedOnce; + } else { + // No double action configured — fire immediately + pushEvent(btn, PressType::Short); + s.state = State::Idle; + } + } else if (!held) { + // Button disappeared without wasReleased edge (e.g. after drain) — reset + s.state = State::Idle; + } + break; + + case State::ReleasedOnce: + if (pressed) { + // Second press within window — start tracking it + s.state = State::DoublePressed; + s.pressDownTime = now; + } else if (now - s.releaseTime >= DOUBLE_WINDOW_MS) { + // Window expired without a second press — it was a short press + pushEvent(btn, PressType::Short); + s.state = State::Idle; + } + break; + + case State::DoublePressed: + if (released) { + pushEvent(btn, PressType::Double); + s.state = State::Idle; + } else if (!held) { + // Disappeared without edge — treat as double anyway + pushEvent(btn, PressType::Double); + s.state = State::Idle; + } + break; + } +} + +void ButtonEventManager::update() { + for (int i = 0; i < NUM_BUTTONS; i++) { + processButton(i, ALL_BUTTONS[i]); + } +} diff --git a/src/ButtonEventManager.h b/src/ButtonEventManager.h new file mode 100644 index 00000000..f005e3e4 --- /dev/null +++ b/src/ButtonEventManager.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#include "MappedInputManager.h" + +// Forward declaration for the global accessor used by Activity.h. +// Defined in main.cpp alongside the ButtonEventManager instance. +class ButtonEventManager; +ButtonEventManager& globalButtonEvents(); + +// Classifies raw button edges into Short, Double, and Long press events. +// +// Per-button state machines run each loop() tick. The key latency rule: +// - If no double-click action is configured for a button, Short fires immediately +// on release (zero extra wait). +// - If a double-click action IS configured, Short is delayed by DOUBLE_WINDOW_MS +// to allow disambiguation. +// - Long fires on release once hold time >= LONG_PRESS_MS (no extra wait). +// - Double fires on the second release within DOUBLE_WINDOW_MS. +// +// Activities query consumeEvent() each loop tick to receive pending events. +// drain() resets all state machines — call it on activity transitions. + +class ButtonEventManager { + public: + using Button = MappedInputManager::Button; + + static constexpr int NUM_BUTTONS = 9; // matches MappedInputManager::Button count + + enum class PressType { Short, Double, Long }; + + struct ButtonEvent { + Button button; + PressType type; + }; + + // Timing constants (milliseconds) + static constexpr unsigned long LONG_PRESS_MS = 600; + static constexpr unsigned long DOUBLE_WINDOW_MS = 300; + + explicit ButtonEventManager(MappedInputManager& input) : input(input) {} + + // Call once per main loop tick, after MappedInputManager::update(). + void update(); + + // Returns the next pending event, or false if none. Call repeatedly until + // false to drain all events for this tick. + bool consumeEvent(ButtonEvent& out); + + // Reset all per-button FSMs. Call on activity transitions to prevent bleed-through. + void drain(); + + // Returns true if a double-click action is configured for this button. + // ButtonEventManager queries CrossPointSettings internally. + static bool hasDoubleAction(Button button); + + private: + static constexpr Button ALL_BUTTONS[NUM_BUTTONS] = { + Button::Back, Button::Confirm, Button::Left, Button::Right, Button::Up, + Button::Down, Button::PageBack, Button::PageForward, Button::Power, + }; + + enum class State { Idle, Pressed, ReleasedOnce, DoublePressed }; + + struct PerButton { + State state = State::Idle; + unsigned long pressDownTime = 0; // when the current (or first) press started + unsigned long releaseTime = 0; // when the first release happened (for double-click window) + }; + + PerButton buttons[NUM_BUTTONS]; + + // Pending events ring buffer (small — at most one event per button per tick) + static constexpr int EVENT_BUF = 16; + ButtonEvent eventBuf[EVENT_BUF] = {}; + int eventHead = 0; + int eventTail = 0; + + MappedInputManager& input; + + void pushEvent(Button button, PressType type); + void processButton(int idx, Button btn); +}; diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index aca87bdc..9245d1bc 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -177,7 +177,10 @@ bool CrossPointSettings::loadFromBinaryFile() { if (++settingsRead >= fileSettingsCount) break; readAndValidate(inputFile, hideBatteryPercentage, HIDE_BATTERY_PERCENTAGE_COUNT); if (++settingsRead >= fileSettingsCount) break; - serialization::readPod(inputFile, longPressChapterSkip); + { + uint8_t _unused; + serialization::readPod(inputFile, _unused); + } // was longPressChapterSkip if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, hyphenationEnabled); if (++settingsRead >= fileSettingsCount) break; diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index fa9f3720..65d42b08 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -249,8 +249,6 @@ class CrossPointSettings { char opdsPassword[64] = ""; // Hide battery percentage uint8_t hideBatteryPercentage = HIDE_NEVER; - // Long-press chapter skip on side buttons - uint8_t longPressChapterSkip = 1; // UI Theme uint8_t uiTheme = LYRA; // Sunlight fading compensation @@ -279,6 +277,59 @@ class CrossPointSettings { // Show the Weather home screen menu item (1 = enabled, 0 = hidden) uint8_t useWeather = 1; + // Configurable actions for short / double / long press on each logical button. + // BTN_DEFAULT means "use the button's normal built-in behaviour". + enum BUTTON_ACTION { + BTN_DEFAULT = 0, + BTN_PAGE_FORWARD, + BTN_PAGE_BACK, + BTN_PAGE_FORWARD_10, + BTN_PAGE_BACK_10, + BTN_GO_HOME, + BTN_SLEEP, + BTN_FORCE_REFRESH, + BTN_OPEN_TOC, + BTN_OPEN_BOOKMARKS, + BTN_STAR_PAGE, + BTN_FOOTNOTES, + BTN_NEXT_SECTION, + BTN_PREV_SECTION, + BUTTON_ACTION_COUNT + }; + + // Short-press actions (default: built-in) + uint8_t btnShortBack = BTN_DEFAULT; + uint8_t btnShortConfirm = BTN_DEFAULT; + uint8_t btnShortLeft = BTN_DEFAULT; + uint8_t btnShortRight = BTN_DEFAULT; + uint8_t btnShortUp = BTN_DEFAULT; + uint8_t btnShortDown = BTN_DEFAULT; + uint8_t btnShortPageBack = BTN_DEFAULT; + uint8_t btnShortPageForward = BTN_DEFAULT; + uint8_t btnShortPower = BTN_DEFAULT; + + // Double-press actions (default: BTN_DEFAULT = disabled, no disambiguation wait) + uint8_t btnDoubleBack = BTN_DEFAULT; + uint8_t btnDoubleConfirm = BTN_DEFAULT; + uint8_t btnDoubleLeft = BTN_DEFAULT; + uint8_t btnDoubleRight = BTN_DEFAULT; + uint8_t btnDoubleUp = BTN_DEFAULT; + uint8_t btnDoubleDown = BTN_DEFAULT; + uint8_t btnDoublePageBack = BTN_DEFAULT; + uint8_t btnDoublePageForward = BTN_DEFAULT; + uint8_t btnDoublePower = BTN_DEFAULT; + + // Long-press actions (default: built-in) + uint8_t btnLongBack = BTN_DEFAULT; + uint8_t btnLongConfirm = BTN_DEFAULT; + uint8_t btnLongLeft = BTN_DEFAULT; + uint8_t btnLongRight = BTN_DEFAULT; + uint8_t btnLongUp = BTN_DEFAULT; + uint8_t btnLongDown = BTN_DEFAULT; + uint8_t btnLongPageBack = BTN_DEFAULT; + uint8_t btnLongPageForward = BTN_DEFAULT; + uint8_t btnLongPower = BTN_DEFAULT; + ~CrossPointSettings() = default; // Get singleton instance diff --git a/src/SettingsList.h b/src/SettingsList.h index 03cb30f6..68ad73e5 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -133,13 +133,123 @@ inline const std::vector list = { // --- Controls --- SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), - SettingInfo::Toggle(StrId::STR_LONG_PRESS_SKIP, &CrossPointSettings::longPressChapterSkip, "longPressChapterSkip", - StrId::STR_CAT_CONTROLS), SettingInfo::Enum(StrId::STR_SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn, {StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN, StrId::STR_FORCE_REFRESH, StrId::STR_FOOTNOTES, StrId::STR_STAR_PAGE}, "shortPwrBtn", StrId::STR_CAT_CONTROLS), +// --- Button Actions (short / double / long press per logical button) --- +// All entries share the same ordered action-label list; the submenu groups them behind +// a single placeholder row in the device UI. +#define BTN_ACTION_ENUM_VALUES \ + {StrId::STR_BTN_ACT_DEFAULT, StrId::STR_BTN_ACT_PAGE_FORWARD, StrId::STR_BTN_ACT_PAGE_BACK, \ + StrId::STR_BTN_ACT_PAGE_FORWARD_10, StrId::STR_BTN_ACT_PAGE_BACK_10, StrId::STR_BTN_ACT_GO_HOME, \ + StrId::STR_BTN_ACT_SLEEP, StrId::STR_BTN_ACT_FORCE_REFRESH, StrId::STR_BTN_ACT_OPEN_TOC, \ + StrId::STR_BTN_ACT_OPEN_BOOKMARKS, StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, \ + StrId::STR_BTN_ACT_NEXT_SECTION, StrId::STR_BTN_ACT_PREV_SECTION} + + // Back button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, BTN_ACTION_ENUM_VALUES, + "btnShortBack", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_BACK) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleBack, BTN_ACTION_ENUM_VALUES, + "btnDoubleBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongBack, BTN_ACTION_ENUM_VALUES, + "btnLongBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Confirm button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortConfirm, BTN_ACTION_ENUM_VALUES, + "btnShortConfirm", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_CONFIRM) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleConfirm, BTN_ACTION_ENUM_VALUES, + "btnDoubleConfirm", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongConfirm, BTN_ACTION_ENUM_VALUES, + "btnLongConfirm", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Left button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortLeft, BTN_ACTION_ENUM_VALUES, + "btnShortLeft", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_LEFT) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleLeft, BTN_ACTION_ENUM_VALUES, + "btnDoubleLeft", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongLeft, BTN_ACTION_ENUM_VALUES, + "btnLongLeft", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Right button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortRight, BTN_ACTION_ENUM_VALUES, + "btnShortRight", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_RIGHT) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleRight, BTN_ACTION_ENUM_VALUES, + "btnDoubleRight", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongRight, BTN_ACTION_ENUM_VALUES, + "btnLongRight", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Up button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortUp, BTN_ACTION_ENUM_VALUES, "btnShortUp", + StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_UP) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleUp, BTN_ACTION_ENUM_VALUES, + "btnDoubleUp", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongUp, BTN_ACTION_ENUM_VALUES, "btnLongUp", + StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Down button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortDown, BTN_ACTION_ENUM_VALUES, + "btnShortDown", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_DOWN) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleDown, BTN_ACTION_ENUM_VALUES, + "btnDoubleDown", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongDown, BTN_ACTION_ENUM_VALUES, + "btnLongDown", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Page Back button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPageBack, BTN_ACTION_ENUM_VALUES, + "btnShortPageBack", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_PAGE_BACK) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePageBack, BTN_ACTION_ENUM_VALUES, + "btnDoublePageBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPageBack, BTN_ACTION_ENUM_VALUES, + "btnLongPageBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Page Forward button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPageForward, BTN_ACTION_ENUM_VALUES, + "btnShortPageForward", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_PAGE_FORWARD) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePageForward, BTN_ACTION_ENUM_VALUES, + "btnDoublePageForward", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPageForward, BTN_ACTION_ENUM_VALUES, + "btnLongPageForward", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + // Power button + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPower, BTN_ACTION_ENUM_VALUES, + "btnShortPower", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_BTN_POWER) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePower, BTN_ACTION_ENUM_VALUES, + "btnDoublePower", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPower, BTN_ACTION_ENUM_VALUES, + "btnLongPower", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + +#undef BTN_ACTION_ENUM_VALUES + // --- System --- SettingInfo::Toggle(StrId::STR_SHOW_HIDDEN_FILES, &CrossPointSettings::showHiddenFiles, "showHiddenFiles", StrId::STR_CAT_SYSTEM), diff --git a/src/activities/Activity.h b/src/activities/Activity.h index c5e4949e..2eebecea 100644 --- a/src/activities/Activity.h +++ b/src/activities/Activity.h @@ -8,6 +8,7 @@ #include "ActivityManager.h" // for using the ActivityManager singleton #include "ActivityResult.h" +#include "ButtonEventManager.h" #include "GfxRenderer.h" #include "MappedInputManager.h" #include "RenderLock.h" @@ -19,13 +20,14 @@ class Activity { std::string name; GfxRenderer& renderer; MappedInputManager& mappedInput; + ButtonEventManager& buttonEvents; ActivityResultHandler resultHandler; ActivityResult result; public: explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput) - : name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {} + : name(std::move(name)), renderer(renderer), mappedInput(mappedInput), buttonEvents(globalButtonEvents()) {} virtual ~Activity() = default; const std::string& getName() const { return name; } virtual void onEnter(); @@ -45,6 +47,11 @@ class Activity { virtual bool preventAutoSleep() { return false; } virtual bool isReaderActivity() const { return false; } + // Called by ActivityManager when a globally-configured button action targets the + // current activity. Override in reader activities to handle reader-specific actions. + // Non-reader activities can ignore this (default is no-op). + virtual void onButtonAction(CrossPointSettings::BUTTON_ACTION) {} + // Start a new activity without destroying the current one // Note: requestUpdate() will be invoked automatically once resultHandler finishes void startActivityForResult(std::unique_ptr&& activity, ActivityResultHandler resultHandler); diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index d81cb491..47b67a56 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -154,6 +154,7 @@ void ActivityManager::loop() { // Arm input drain so the button that triggered the pop doesn't bleed into the // restored activity (or into a new activity the handler just pushed). drainInput = true; + buttonEvents.drain(); // Request an update to ensure the popped activity gets re-rendered if (pendingAction == PendingAction::None) { @@ -204,6 +205,7 @@ void ActivityManager::loop() { // Arm input drain so the button that triggered the transition doesn't bleed // into the new activity. drainInput = true; + buttonEvents.drain(); // onEnter may request another pending action, we will handle it in the next loop iteration continue; @@ -387,6 +389,12 @@ bool ActivityManager::isReaderActivity() const { return currentActivity && curre bool ActivityManager::skipLoopDelay() const { return currentActivity && currentActivity->skipLoopDelay(); } +void ActivityManager::dispatchButtonAction(const CrossPointSettings::BUTTON_ACTION action) { + if (currentActivity) { + currentActivity->onButtonAction(action); + } +} + void ActivityManager::requestUpdate(bool immediate) { if (immediate) { if (renderTaskHandle) { diff --git a/src/activities/ActivityManager.h b/src/activities/ActivityManager.h index 5ab9d04f..1d763206 100644 --- a/src/activities/ActivityManager.h +++ b/src/activities/ActivityManager.h @@ -9,6 +9,8 @@ #include #include +#include "ButtonEventManager.h" +#include "CrossPointSettings.h" #include "GfxRenderer.h" #include "MappedInputManager.h" @@ -52,6 +54,7 @@ class ActivityManager { protected: GfxRenderer& renderer; MappedInputManager& mappedInput; + ButtonEventManager& buttonEvents; std::vector> stackActivities; std::unique_ptr currentActivity; @@ -96,7 +99,10 @@ class ActivityManager { public: explicit ActivityManager(GfxRenderer& renderer, MappedInputManager& mappedInput) - : renderer(renderer), mappedInput(mappedInput), renderingMutex(xSemaphoreCreateMutex()) { + : renderer(renderer), + mappedInput(mappedInput), + buttonEvents(globalButtonEvents()), + renderingMutex(xSemaphoreCreateMutex()) { assert(renderingMutex != nullptr && "Failed to create rendering mutex"); stackActivities.reserve(10); } @@ -160,6 +166,11 @@ class ActivityManager { bool isReaderActivity() const; bool skipLoopDelay() const; + // Dispatch a globally-configured button action to the current activity. + // Reader-specific actions (page navigation, TOC, bookmarks, footnotes) are forwarded + // only when the current activity is a reader; others are no-ops in other contexts. + void dispatchButtonAction(CrossPointSettings::BUTTON_ACTION action); + // If immediate is true, the update will be triggered immediately. // Otherwise, it will be deferred until the end of the current loop iteration. void requestUpdate(bool immediate = false); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 83bcf765..5abbaa56 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -343,7 +343,7 @@ void EpubReaderActivity::loop() { return; } - const bool skipChapter = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > skipChapterMs; + const bool skipChapter = mappedInput.getHeldTime() > skipChapterMs; // Chapter skip navigates by TOC entries, not spine boundaries. // Spine items without their own TOC entry inherit the previous spine's tocIndex @@ -1795,3 +1795,117 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf // No displayBuffer call — caller (SleepActivity) handles that after compositing the overlay return true; } + +void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION action) { + using BA = CrossPointSettings::BUTTON_ACTION; + switch (action) { + case BA::BTN_PAGE_FORWARD: + pageTurn(true); + break; + case BA::BTN_PAGE_BACK: + pageTurn(false); + break; + case BA::BTN_PAGE_FORWARD_10: + for (int i = 0; i < 10; i++) { + if (!stepPageState(true)) break; + } + requestUpdate(); + break; + case BA::BTN_PAGE_BACK_10: + for (int i = 0; i < 10; i++) { + if (!stepPageState(false)) break; + } + requestUpdate(); + break; + case BA::BTN_STAR_PAGE: + if (section) { + bookmarkStore.toggle(static_cast(currentSpineIndex), static_cast(section->currentPage)); + requestUpdate(); + } + break; + case BA::BTN_FOOTNOTES: + if (!currentPageFootnotes.empty()) { + if (currentPageFootnotes.size() == 1) { + navigateToHref(currentPageFootnotes[0].href, true); + } else { + startActivityForResult( + std::make_unique(renderer, mappedInput, currentPageFootnotes), + [this](const ActivityResult& result) { + if (!result.isCancelled) { + const auto& footnoteResult = std::get(result.data); + navigateToHref(footnoteResult.href, true); + } + }); + } + } + break; + case BA::BTN_OPEN_TOC: + if (epub) { + const int spineIdx = currentSpineIndex; + const int tocIdx = section ? section->getTocIndexForPage(section->currentPage) + : epub->getTocIndexForSpineIndex(currentSpineIndex); + startActivityForResult(std::make_unique(renderer, mappedInput, epub, + epub->getPath(), spineIdx, tocIdx), + [this](const ActivityResult& result) { + if (result.isCancelled) return; + RenderLock lock(*this); + const auto& chapter = std::get(result.data); + auto resolvedPage = + (chapter.tocIndex && chapter.spineIndex == currentSpineIndex && section) + ? section->getPageForTocIndex(*chapter.tocIndex) + : std::nullopt; + if (resolvedPage) { + section->currentPage = *resolvedPage; + } else { + pendingTocIndex = chapter.tocIndex; + currentSpineIndex = chapter.spineIndex; + nextPageNumber = 0; + section.reset(); + } + }); + } + break; + case BA::BTN_NEXT_SECTION: + case BA::BTN_PREV_SECTION: { + const bool forward = (action == BA::BTN_NEXT_SECTION); + RenderLock lock(*this); + if (section && section->pageCount > 0) { + const int curTocIndex = section->getTocIndexForPage(section->currentPage); + const int nextTocIndex = forward ? curTocIndex + 1 : curTocIndex - 1; + if (curTocIndex < 0) { + nextPageNumber = 0; + currentSpineIndex = forward ? currentSpineIndex + 1 : currentSpineIndex - 1; + section.reset(); + } else if (nextTocIndex >= 0 && nextTocIndex < epub->getTocItemsCount()) { + const int newSpineIndex = epub->getSpineIndexForTocIndex(nextTocIndex); + if (newSpineIndex == currentSpineIndex) { + if (const auto resolvedPage = section->getPageForTocIndex(nextTocIndex)) { + section->currentPage = *resolvedPage; + } + } else { + pendingTocIndex = nextTocIndex; + nextPageNumber = 0; + currentSpineIndex = newSpineIndex; + section.reset(); + } + } else if (forward) { + nextPageNumber = 0; + currentSpineIndex = epub->getSpineItemsCount(); + section.reset(); + } else { + nextPageNumber = 0; + currentSpineIndex = epub->getTocItem(curTocIndex).spineIndex - 1; + section.reset(); + } + } else { + nextPageNumber = 0; + currentSpineIndex = forward ? currentSpineIndex + 1 : currentSpineIndex - 1; + section.reset(); + } + requestUpdate(); + break; + } + default: + break; + } +} diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 8185139f..42c8e332 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -188,6 +188,7 @@ class EpubReaderActivity final : public Activity { void loop() override; void render(RenderLock&& lock) override; bool isReaderActivity() const override { return true; } + void onButtonAction(CrossPointSettings::BUTTON_ACTION action) override; // Renders the last saved page to the frame buffer without flushing to display. // Used by SleepActivity to prepare the background for the overlay sleep mode. diff --git a/src/activities/reader/MdReaderActivity.cpp b/src/activities/reader/MdReaderActivity.cpp index 9773a950..a2d1001d 100644 --- a/src/activities/reader/MdReaderActivity.cpp +++ b/src/activities/reader/MdReaderActivity.cpp @@ -253,7 +253,7 @@ void MdReaderActivity::loop() { return; } - const bool headingSkip = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > HEADING_SKIP_MS; + const bool headingSkip = mappedInput.getHeldTime() > HEADING_SKIP_MS; if (headingSkip && !headings.empty()) { jumpToHeading(nextTriggered); return; @@ -892,4 +892,62 @@ void MdReaderActivity::savePageIndexCache() const { } LOG_DBG("MDR", "Saved page index cache: %d pages", totalPages); -} \ No newline at end of file +} + +void MdReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION action) { + using BA = CrossPointSettings::BUTTON_ACTION; + auto clampPage = [this]() { + if (currentPage < 0) currentPage = 0; + if (currentPage >= totalPages) currentPage = totalPages - 1; + }; + switch (action) { + case BA::BTN_PAGE_FORWARD: + if (currentPage < totalPages - 1) { + currentPage++; + currentHeadingIndex = pageOffsets.empty() ? -1 : getHeadingIndexForOffset(pageOffsets[currentPage]); + requestUpdate(); + } + break; + case BA::BTN_PAGE_BACK: + if (currentPage > 0) { + currentPage--; + currentHeadingIndex = pageOffsets.empty() ? -1 : getHeadingIndexForOffset(pageOffsets[currentPage]); + requestUpdate(); + } + break; + case BA::BTN_PAGE_FORWARD_10: + currentPage += 10; + clampPage(); + currentHeadingIndex = pageOffsets.empty() ? -1 : getHeadingIndexForOffset(pageOffsets[currentPage]); + requestUpdate(); + break; + case BA::BTN_PAGE_BACK_10: + currentPage -= 10; + clampPage(); + currentHeadingIndex = pageOffsets.empty() ? -1 : getHeadingIndexForOffset(pageOffsets[currentPage]); + requestUpdate(); + break; + case BA::BTN_NEXT_SECTION: + jumpToHeading(true); + break; + case BA::BTN_PREV_SECTION: + jumpToHeading(false); + break; + case BA::BTN_OPEN_TOC: + if (!headings.empty()) { + currentHeadingIndex = pageOffsets.empty() ? -1 : getHeadingIndexForOffset(pageOffsets[currentPage]); + startActivityForResult( + std::make_unique(renderer, mappedInput, headings, currentHeadingIndex), + [this](const ActivityResult& result) { + if (!result.isCancelled) { + currentPage = std::get(result.data).page; + currentHeadingIndex = pageOffsets.empty() ? -1 : getHeadingIndexForOffset(pageOffsets[currentPage]); + requestUpdate(); + } + }); + } + break; + default: + break; + } +} diff --git a/src/activities/reader/MdReaderActivity.h b/src/activities/reader/MdReaderActivity.h index 604fc532..c262e9c6 100644 --- a/src/activities/reader/MdReaderActivity.h +++ b/src/activities/reader/MdReaderActivity.h @@ -89,4 +89,5 @@ class MdReaderActivity final : public Activity { void loop() override; void render(RenderLock&&) override; bool isReaderActivity() const override { return true; } + void onButtonAction(CrossPointSettings::BUTTON_ACTION action) override; }; \ No newline at end of file diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index 7eb88c32..af8c9bc7 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -31,8 +31,8 @@ inline void applyOrientation(GfxRenderer& renderer, const uint8_t orientation) { // Suppresses input processing on activity entry until the user has released all buttons and a // clean frame (no pending press/release events) has been observed. Without this, the power-button -// hold used to wake the device leaks into detectPageTurn() and triggers a page turn or, with -// longPressChapterSkip enabled, a chapter skip (the wake-hold easily exceeds skipChapterMs). +// hold used to wake the device leaks into detectPageTurn() and triggers a page turn or chapter +// skip (the wake-hold easily exceeds skipChapterMs). // Each reader holds an instance, calls arm() in onEnter(), and calls shouldDrain() at the top // of loop() — returning early when it returns true. struct InputDrainGuard { @@ -62,17 +62,12 @@ struct PageTurnResult { }; inline PageTurnResult detectPageTurn(const MappedInputManager& input) { - const bool usePress = !SETTINGS.longPressChapterSkip; - const bool prev = usePress ? (input.wasPressed(MappedInputManager::Button::PageBack) || - input.wasPressed(MappedInputManager::Button::Left)) - : (input.wasReleased(MappedInputManager::Button::PageBack) || - input.wasReleased(MappedInputManager::Button::Left)); + const bool prev = + input.wasReleased(MappedInputManager::Button::PageBack) || input.wasReleased(MappedInputManager::Button::Left); const bool powerTurn = SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && input.wasReleased(MappedInputManager::Button::Power); - const bool next = usePress ? (input.wasPressed(MappedInputManager::Button::PageForward) || powerTurn || - input.wasPressed(MappedInputManager::Button::Right)) - : (input.wasReleased(MappedInputManager::Button::PageForward) || powerTurn || - input.wasReleased(MappedInputManager::Button::Right)); + const bool next = input.wasReleased(MappedInputManager::Button::PageForward) || powerTurn || + input.wasReleased(MappedInputManager::Button::Right); return {prev, next}; } diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index f3f5cdc8..7e46540d 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -782,3 +782,51 @@ bool TxtReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gfx } return true; } + +void TxtReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION action) { + using BA = CrossPointSettings::BUTTON_ACTION; + auto clampPage = [this]() { + if (currentPage < 0) currentPage = 0; + if (currentPage >= totalPages) currentPage = totalPages - 1; + }; + switch (action) { + case BA::BTN_PAGE_FORWARD: + if (currentPage < totalPages - 1) { + currentPage++; + requestUpdate(); + } + break; + case BA::BTN_PAGE_BACK: + if (currentPage > 0) { + currentPage--; + requestUpdate(); + } + break; + case BA::BTN_PAGE_FORWARD_10: + currentPage += 10; + clampPage(); + requestUpdate(); + break; + case BA::BTN_PAGE_BACK_10: + currentPage -= 10; + clampPage(); + requestUpdate(); + break; + case BA::BTN_STAR_PAGE: + bookmarkStore.toggle(0, static_cast(currentPage)); + requestUpdate(); + break; + case BA::BTN_NEXT_SECTION: + currentPage += 10; + clampPage(); + requestUpdate(); + break; + case BA::BTN_PREV_SECTION: + currentPage -= 10; + clampPage(); + requestUpdate(); + break; + default: + break; + } +} diff --git a/src/activities/reader/TxtReaderActivity.h b/src/activities/reader/TxtReaderActivity.h index 03449fe7..60615e7a 100644 --- a/src/activities/reader/TxtReaderActivity.h +++ b/src/activities/reader/TxtReaderActivity.h @@ -58,6 +58,7 @@ class TxtReaderActivity final : public Activity { void loop() override; void render(RenderLock&&) override; bool isReaderActivity() const override { return true; } + void onButtonAction(CrossPointSettings::BUTTON_ACTION action) override; // Renders the last saved page to the frame buffer without flushing to display. // Used by SleepActivity to prepare the background for the overlay sleep mode. diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index d5a631fc..cb83bf07 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -92,19 +92,12 @@ void XtcReaderActivity::loop() { return; } - // When long-press chapter skip is disabled, turn pages on press instead of release. - const bool usePressForPageTurn = !SETTINGS.longPressChapterSkip; - const bool prevTriggered = usePressForPageTurn ? (mappedInput.wasPressed(MappedInputManager::Button::PageBack) || - mappedInput.wasPressed(MappedInputManager::Button::Left)) - : (mappedInput.wasReleased(MappedInputManager::Button::PageBack) || - mappedInput.wasReleased(MappedInputManager::Button::Left)); + const bool prevTriggered = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || + mappedInput.wasReleased(MappedInputManager::Button::Left); const bool powerPageTurn = SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && mappedInput.wasReleased(MappedInputManager::Button::Power); - const bool nextTriggered = usePressForPageTurn - ? (mappedInput.wasPressed(MappedInputManager::Button::PageForward) || powerPageTurn || - mappedInput.wasPressed(MappedInputManager::Button::Right)) - : (mappedInput.wasReleased(MappedInputManager::Button::PageForward) || powerPageTurn || - mappedInput.wasReleased(MappedInputManager::Button::Right)); + const bool nextTriggered = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || powerPageTurn || + mappedInput.wasReleased(MappedInputManager::Button::Right); if (!prevTriggered && !nextTriggered) { return; @@ -121,7 +114,7 @@ void XtcReaderActivity::loop() { return; } - const bool skipPages = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > skipPageMs; + const bool skipPages = mappedInput.getHeldTime() > skipPageMs; const int skipAmount = skipPages ? 10 : 1; if (prevTriggered) { @@ -442,3 +435,41 @@ bool XtcReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gfx free(pageBuffer); return true; } + +void XtcReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION action) { + using BA = CrossPointSettings::BUTTON_ACTION; + if (!xtc) return; + const uint32_t pageCount = xtc->getPageCount(); + switch (action) { + case BA::BTN_PAGE_FORWARD: + if (currentPage + 1 < pageCount) { + currentPage++; + requestUpdate(); + } + break; + case BA::BTN_PAGE_BACK: + if (currentPage > 0) { + currentPage--; + requestUpdate(); + } + break; + case BA::BTN_PAGE_FORWARD_10: + currentPage = (currentPage + 10 < pageCount) ? currentPage + 10 : pageCount - 1; + requestUpdate(); + break; + case BA::BTN_PAGE_BACK_10: + currentPage = (currentPage >= 10) ? currentPage - 10 : 0; + requestUpdate(); + break; + case BA::BTN_NEXT_SECTION: + currentPage = (currentPage + 10 < pageCount) ? currentPage + 10 : pageCount - 1; + requestUpdate(); + break; + case BA::BTN_PREV_SECTION: + currentPage = (currentPage >= 10) ? currentPage - 10 : 0; + requestUpdate(); + break; + default: + break; + } +} diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 46fcabf1..b4938a52 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -31,6 +31,7 @@ class XtcReaderActivity final : public Activity { void loop() override; void render(RenderLock&&) override; bool isReaderActivity() const override { return true; } + void onButtonAction(CrossPointSettings::BUTTON_ACTION action) override; // Renders the last saved page to the frame buffer without flushing to display. // Used by SleepActivity to prepare the background for the overlay sleep mode. diff --git a/src/main.cpp b/src/main.cpp index e79a2bf0..cc93abe2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,7 @@ #include +#include "ButtonEventManager.h" #include "CrossPointSettings.h" #include "CrossPointState.h" #include "GlobalBookmarkIndex.h" @@ -33,6 +34,8 @@ #include "util/ScreenshotUtil.h" MappedInputManager mappedInputManager(gpio); +ButtonEventManager buttonEventManager(mappedInputManager); +ButtonEventManager& globalButtonEvents() { return buttonEventManager; } GfxRenderer renderer(display); ActivityManager activityManager(renderer, mappedInputManager); FontDecompressor fontDecompressor; @@ -284,6 +287,7 @@ void loop() { static unsigned long lastMemPrint = 0; gpio.update(); + buttonEventManager.update(); HalClock::updatePeriodic(); renderer.setFadingFix(SETTINGS.fadingFix); @@ -384,6 +388,130 @@ void loop() { activityManager.requestUpdate(); } + // Dispatch globally-configured button actions before handing control to the activity. + // Only non-Default actions are intercepted here; Default falls through to the activity. + { + using BA = CrossPointSettings::BUTTON_ACTION; + using B = MappedInputManager::Button; + ButtonEventManager::ButtonEvent ev; + while (buttonEventManager.consumeEvent(ev)) { + auto actionFor = [&](B btn) -> uint8_t { + switch (ev.type) { + case ButtonEventManager::PressType::Short: + switch (btn) { + case B::Back: + return SETTINGS.btnShortBack; + case B::Confirm: + return SETTINGS.btnShortConfirm; + case B::Left: + return SETTINGS.btnShortLeft; + case B::Right: + return SETTINGS.btnShortRight; + case B::Up: + return SETTINGS.btnShortUp; + case B::Down: + return SETTINGS.btnShortDown; + case B::PageBack: + return SETTINGS.btnShortPageBack; + case B::PageForward: + return SETTINGS.btnShortPageForward; + case B::Power: + return SETTINGS.btnShortPower; + } + break; + case ButtonEventManager::PressType::Double: + switch (btn) { + case B::Back: + return SETTINGS.btnDoubleBack; + case B::Confirm: + return SETTINGS.btnDoubleConfirm; + case B::Left: + return SETTINGS.btnDoubleLeft; + case B::Right: + return SETTINGS.btnDoubleRight; + case B::Up: + return SETTINGS.btnDoubleUp; + case B::Down: + return SETTINGS.btnDoubleDown; + case B::PageBack: + return SETTINGS.btnDoublePageBack; + case B::PageForward: + return SETTINGS.btnDoublePageForward; + case B::Power: + return SETTINGS.btnDoublePower; + } + break; + case ButtonEventManager::PressType::Long: + switch (btn) { + case B::Back: + return SETTINGS.btnLongBack; + case B::Confirm: + return SETTINGS.btnLongConfirm; + case B::Left: + return SETTINGS.btnLongLeft; + case B::Right: + return SETTINGS.btnLongRight; + case B::Up: + return SETTINGS.btnLongUp; + case B::Down: + return SETTINGS.btnLongDown; + case B::PageBack: + return SETTINGS.btnLongPageBack; + case B::PageForward: + return SETTINGS.btnLongPageForward; + case B::Power: + return SETTINGS.btnLongPower; + } + break; + } + return BA::BTN_DEFAULT; + }; + + const uint8_t action = actionFor(ev.button); + if (action == BA::BTN_DEFAULT) continue; + + switch (static_cast(action)) { + case BA::BTN_PAGE_FORWARD: + activityManager.dispatchButtonAction(BA::BTN_PAGE_FORWARD); + break; + case BA::BTN_PAGE_BACK: + activityManager.dispatchButtonAction(BA::BTN_PAGE_BACK); + break; + case BA::BTN_PAGE_FORWARD_10: + activityManager.dispatchButtonAction(BA::BTN_PAGE_FORWARD_10); + break; + case BA::BTN_PAGE_BACK_10: + activityManager.dispatchButtonAction(BA::BTN_PAGE_BACK_10); + break; + case BA::BTN_GO_HOME: + activityManager.goHome(); + break; + case BA::BTN_SLEEP: + activityManager.goToSleep(); + break; + case BA::BTN_FORCE_REFRESH: { + RenderLock lock; + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + break; + } + case BA::BTN_OPEN_TOC: + activityManager.dispatchButtonAction(BA::BTN_OPEN_TOC); + break; + case BA::BTN_OPEN_BOOKMARKS: + activityManager.goToGlobalBookmarks(); + break; + case BA::BTN_STAR_PAGE: + activityManager.dispatchButtonAction(BA::BTN_STAR_PAGE); + break; + case BA::BTN_FOOTNOTES: + activityManager.dispatchButtonAction(BA::BTN_FOOTNOTES); + break; + default: + break; + } + } + } + const unsigned long activityStartTime = millis(); activityManager.loop(); const unsigned long activityDuration = millis() - activityStartTime; From f0fe02d77a17d064d59e7e54187d9d39b0e525ce Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sun, 26 Apr 2026 16:46:22 +0200 Subject: [PATCH 2/5] Update logic to avoid collision Co-authored-by: Copilot --- lib/I18n/translations/belarusian.yaml | 4 - lib/I18n/translations/catalan.yaml | 4 - lib/I18n/translations/czech.yaml | 4 - lib/I18n/translations/danish.yaml | 4 - lib/I18n/translations/dutch.yaml | 4 - lib/I18n/translations/english.yaml | 18 +- lib/I18n/translations/finnish.yaml | 4 - lib/I18n/translations/french.yaml | 4 - lib/I18n/translations/german.yaml | 4 - lib/I18n/translations/hungarian.yaml | 4 - lib/I18n/translations/italian.yaml | 4 - lib/I18n/translations/kazakh.yaml | 4 - lib/I18n/translations/lithuanian.yaml | 4 - lib/I18n/translations/polish.yaml | 4 - lib/I18n/translations/portuguese_br.yaml | 4 - lib/I18n/translations/portuguese_pt.yaml | 4 - lib/I18n/translations/romanian.yaml | 4 - lib/I18n/translations/russian.yaml | 4 - lib/I18n/translations/slovenian.yaml | 4 - lib/I18n/translations/spanish.yaml | 4 - lib/I18n/translations/swedish.yaml | 4 - lib/I18n/translations/turkish.yaml | 4 - lib/I18n/translations/ukrainian.yaml | 4 - src/ButtonEventManager.cpp | 18 +- src/ButtonEventManager.h | 10 +- src/CrossPointSettings.cpp | 5 +- src/CrossPointSettings.h | 20 +- src/SettingsList.h | 207 +++++++++---------- src/activities/reader/EpubReaderActivity.cpp | 71 ++++--- src/activities/reader/MdReaderActivity.cpp | 4 + src/activities/reader/ReaderUtils.h | 4 +- src/activities/reader/TxtReaderActivity.cpp | 14 +- src/activities/reader/XtcReaderActivity.cpp | 8 +- src/main.cpp | 60 ++++-- 34 files changed, 233 insertions(+), 294 deletions(-) diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index e4318605..366ee3d8 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -66,7 +66,6 @@ STR_SLEEP_COVER_MODE: "Рэжым вокладкі сну" STR_HIDE_BATTERY: "Схаваць % батарэі" STR_EXTRA_SPACING: "Дадат. інтэрвал абзаца" STR_TEXT_AA: "Згладжванне тэксту" -STR_SHORT_PWR_BTN: "Кароткае націсканне PWR" STR_ORIENTATION: "Арыентацыя чытання" STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі" STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела" @@ -119,9 +118,6 @@ STR_CROP: "Абрэзаць" STR_NEVER: "Ніколі" STR_IN_READER: "У рэжыме чытання" STR_ALWAYS: "Заўсёды" -STR_IGNORE: "Ігнараваць" -STR_SLEEP: "Сон" -STR_PAGE_TURN: "Перагортванне" STR_PORTRAIT: "Партрэт" STR_LANDSCAPE_CW: "Ландшафт (CW)" STR_INVERTED: "Інверсія" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index 614821a2..49d5b424 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -70,7 +70,6 @@ STR_IMAGES: "Imatges" STR_IMAGES_DISPLAY: "Mostrar" STR_IMAGES_PLACEHOLDER: "Text de mostra" STR_IMAGES_SUPPRESS: "Suprimir" -STR_SHORT_PWR_BTN: "Clic curt del botó d'engegada" STR_ORIENTATION: "Orientació de lectura" STR_SIDE_BTN_LAYOUT: "Disposició botons laterals" STR_LONG_PRESS_SKIP: "Pressió llarga omet el capítol" @@ -124,9 +123,6 @@ STR_CROP: "Retallar" STR_NEVER: "Mai" STR_IN_READER: "Al lector" STR_ALWAYS: "Sempre" -STR_IGNORE: "Ignora" -STR_SLEEP: "Dormir" -STR_PAGE_TURN: "Canvi de pàgina" STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horitzontal horari" STR_INVERTED: "Invertit" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index b655a34f..c9f629ce 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -66,7 +66,6 @@ STR_SLEEP_COVER_MODE: "Obrazovka spánku Režim krytu" STR_HIDE_BATTERY: "Skrýt baterii %" STR_EXTRA_SPACING: "Extra mezery mezi odstavci" STR_TEXT_AA: "Vyhlazování textu" -STR_SHORT_PWR_BTN: "Krátké stisknutí tlačítka napájení" STR_ORIENTATION: "Orientace čtení" STR_SIDE_BTN_LAYOUT: "Rozvržení bočních tlačítek (čtečka)" STR_LONG_PRESS_SKIP: "Dlouhé stisknutí Přeskočit kapitolu" @@ -119,9 +118,6 @@ STR_CROP: "Oříznout" STR_NEVER: "Nikdy" STR_IN_READER: "Ve čtečce" STR_ALWAYS: "Vždy" -STR_IGNORE: "Ignorovat" -STR_SLEEP: "Spánek" -STR_PAGE_TURN: "Otáčení stránek" STR_PORTRAIT: "Na výšku" STR_LANDSCAPE_CW: "Na šířku po směru hod. ručiček" STR_INVERTED: "Invertovaný" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index 807d9831..86b60de8 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -70,7 +70,6 @@ STR_IMAGES: "Billeder" STR_IMAGES_DISPLAY: "Vis" STR_IMAGES_PLACEHOLDER: "Pladsholder" STR_IMAGES_SUPPRESS: "Skjul" -STR_SHORT_PWR_BTN: "Kort tryk på tænd/sluk-knap" STR_ORIENTATION: "Læseretning" STR_SIDE_BTN_LAYOUT: "Knaplayout på siden (læser)" STR_LONG_PRESS_SKIP: "Langt tryk spring kapitel over" @@ -124,9 +123,6 @@ STR_CROP: "Beskær" STR_NEVER: "Aldrig" STR_IN_READER: "I læseren" STR_ALWAYS: "Altid" -STR_IGNORE: "Ignorer" -STR_SLEEP: "Hvile" -STR_PAGE_TURN: "Sideskift" STR_PORTRAIT: "Portræt" STR_LANDSCAPE_CW: "Liggende med uret" STR_INVERTED: "Inverteret" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index 73f6b510..5e0e631f 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -96,7 +96,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Gediffuseerde Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Maak fallback voor ongeldige inhoudsopgave" -STR_SHORT_PWR_BTN: "Korte klik aan/uit-knop" STR_ORIENTATION: "Leesstand" STR_SIDE_BTN_LAYOUT: "Indeling zijknoppen (lezer)" STR_LONG_PRESS_SKIP: "Hoofdstuk overslaan (lang indrukken)" @@ -199,9 +198,6 @@ STR_CROP: "Bijsnijden" STR_NEVER: "Nooit" STR_IN_READER: "In lezer" STR_ALWAYS: "Altijd" -STR_IGNORE: "Negeren" -STR_SLEEP: "Slaap" -STR_PAGE_TURN: "Pagina omslaan" STR_PORTRAIT: "Staand" STR_LANDSCAPE_CW: "Liggend (rechtsom)" STR_INVERTED: "Omgekeerd" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index cc4eaa80..89b2ac37 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -96,7 +96,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Diffused Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Create fallback for invalid TOC" -STR_SHORT_PWR_BTN: "Short Power Button Click" STR_ORIENTATION: "Reading Orientation" STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)" STR_LONG_PRESS_SKIP: "Long-press Chapter Skip" @@ -199,9 +198,6 @@ STR_CROP: "Crop" STR_NEVER: "Never" STR_IN_READER: "In Reader" STR_ALWAYS: "Always" -STR_IGNORE: "Ignore" -STR_SLEEP: "Sleep" -STR_PAGE_TURN: "Page Turn" STR_PORTRAIT: "Portrait" STR_LANDSCAPE_CW: "Landscape CW" STR_INVERTED: "Inverted" @@ -548,6 +544,7 @@ STR_CAPTIVE_PORTAL_HINT_1: "Network requires browser login. On another device," STR_CAPTIVE_PORTAL_HINT_2: "visit the URL below to authorize, then press OK." STR_CAPTIVE_PORTAL_DONE: "I'm authorized" STR_MENU_BTN_ACTIONS: "Button Actions" +STR_MENU_BTN_PHYSICAL: "Physical Buttons" STR_BTN_SHORT_PRESS: "Short Press" STR_BTN_DOUBLE_PRESS: "Double Press" STR_BTN_LONG_PRESS: "Long Press" @@ -561,6 +558,16 @@ STR_BTN_PAGE_BACK: "Page Back Button" STR_BTN_PAGE_FORWARD: "Page Forward Button" STR_BTN_POWER: "Power Button" STR_BTN_ACT_DEFAULT: "Default" +STR_BTN_DEF_IGNORE: "Default (ignore)" +STR_BTN_DEF_EXIT_READER: "Default (exit reader)" +STR_BTN_DEF_GO_HOME: "Default (go home)" +STR_BTN_DEF_READER_MENU: "Default (reader menu)" +STR_BTN_DEF_KOREADER_SYNC: "Default (KOReader sync)" +STR_BTN_DEF_PREV_PAGE: "Default (previous page)" +STR_BTN_DEF_NEXT_PAGE: "Default (next page)" +STR_BTN_DEF_CHAPTER_BACK: "Default (chapter back)" +STR_BTN_DEF_CHAPTER_FORWARD: "Default (chapter forward)" +STR_BTN_DEF_SLEEP: "Default (sleep)" STR_BTN_ACT_PAGE_FORWARD: "Next Page" STR_BTN_ACT_PAGE_BACK: "Previous Page" STR_BTN_ACT_PAGE_FORWARD_10: "Skip 10 Pages Forward" @@ -574,3 +581,6 @@ STR_BTN_ACT_STAR_PAGE: "Star Page" STR_BTN_ACT_FOOTNOTES: "Footnotes" STR_BTN_ACT_NEXT_SECTION: "Next Section / Chapter" STR_BTN_ACT_PREV_SECTION: "Previous Section / Chapter" +STR_BTN_ACT_EXIT_READER: "Exit Reader" +STR_BTN_ACT_READER_MENU: "Reader Menu" +STR_BTN_ACT_KOREADER_SYNC: "KOReader Sync" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index e584d7a2..0c73b385 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -66,7 +66,6 @@ STR_SLEEP_COVER_MODE: "Lepotilanäytön kansitila" STR_HIDE_BATTERY: "Piilota akun %" STR_EXTRA_SPACING: "Kappaleiden lisäväli" STR_TEXT_AA: "Tekstin reunanpehmennys" -STR_SHORT_PWR_BTN: "Lyhyt virtapainikkeen painallus" STR_ORIENTATION: "Lukusuunta" STR_SIDE_BTN_LAYOUT: "Sivupainikkeiden asettelu (lukija)" STR_LONG_PRESS_SKIP: "Pitkä painallus: lukuhyppy" @@ -119,9 +118,6 @@ STR_CROP: "Rajaa" STR_NEVER: "Ei koskaan" STR_IN_READER: "Lukijassa" STR_ALWAYS: "Aina" -STR_IGNORE: "Ohita" -STR_SLEEP: "Lepotila" -STR_PAGE_TURN: "Sivunkääntö" STR_PORTRAIT: "Pysty" STR_LANDSCAPE_CW: "Vaaka myötäpäivään" STR_INVERTED: "Käännetty" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 1622549e..5b24dc33 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -91,7 +91,6 @@ STR_IMAGES: "Images" STR_IMAGES_DISPLAY: "Affichage" STR_IMAGES_PLACEHOLDER: "Espace réservé" STR_IMAGES_SUPPRESS: "Masquer" -STR_SHORT_PWR_BTN: "Appui court alim." STR_ORIENTATION: "Orientation de lecture" STR_SIDE_BTN_LAYOUT: "Boutons latéraux" STR_LONG_PRESS_SKIP: "Appui long saut de chapitre" @@ -147,9 +146,6 @@ STR_CROP: "Rogné" STR_NEVER: "Jamais" STR_IN_READER: "Dans le lecteur" STR_ALWAYS: "Toujours" -STR_IGNORE: "Ignorer" -STR_SLEEP: "Mise en veille" -STR_PAGE_TURN: "Page suivante" STR_PORTRAIT: "Portrait" STR_LANDSCAPE_CW: "Paysage" STR_INVERTED: "Inversé" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index d388acf6..81de9fbd 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -79,7 +79,6 @@ STR_IMAGE_DITHERING: "Bild-Dithering" STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Diffundiertes Bayer" -STR_SHORT_PWR_BTN: "An-Taste kurz drücken" STR_ORIENTATION: "Leseausrichtung" STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)" STR_LONG_PRESS_SKIP: "Langes Drücken springt Kap." @@ -138,9 +137,6 @@ STR_CROP: "Zuschnitt" STR_NEVER: "Nie" STR_IN_READER: "Beim Lesen" STR_ALWAYS: "Immer" -STR_IGNORE: "Ignorieren" -STR_SLEEP: "Standby" -STR_PAGE_TURN: "Umblättern" STR_PORTRAIT: "Hochformat" STR_LANDSCAPE_CW: "Querformat rechts" STR_INVERTED: "Invertiert" diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 70b0d8bf..95416711 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -70,7 +70,6 @@ STR_IMAGES: "Képek" STR_IMAGES_DISPLAY: "Megjelenítés" STR_IMAGES_PLACEHOLDER: "Helyőrző" STR_IMAGES_SUPPRESS: "Elnyomás" -STR_SHORT_PWR_BTN: "Rövid bekapcsológomb nyomás" STR_ORIENTATION: "Olvasási irány" STR_SIDE_BTN_LAYOUT: "Oldalsó gomb elrendezés (olvasó)" STR_LONG_PRESS_SKIP: "Hosszú nyomás - fejezet ugrás" @@ -124,9 +123,6 @@ STR_CROP: "Körbevágás" STR_NEVER: "Soha" STR_IN_READER: "Olvasóban" STR_ALWAYS: "Mindig" -STR_IGNORE: "Mellőzés" -STR_SLEEP: "Alvás" -STR_PAGE_TURN: "Lapozás" STR_PORTRAIT: "Álló" STR_LANDSCAPE_CW: "Fekvő jobbra" STR_INVERTED: "Fordított" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 4d531de3..eb9e4f00 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -91,7 +91,6 @@ STR_IMAGES: "Immagini" STR_IMAGES_DISPLAY: "Visualizza" STR_IMAGES_PLACEHOLDER: "Segnaposto" STR_IMAGES_SUPPRESS: "Nascondi" -STR_SHORT_PWR_BTN: "Pressione breve tasto accensione" STR_ORIENTATION: "Orientamento lettura" STR_SIDE_BTN_LAYOUT: "Pulsanti laterali (lettore)" STR_LONG_PRESS_SKIP: "Pressione lunga: salta capitolo" @@ -147,9 +146,6 @@ STR_CROP: "Ritaglia" STR_NEVER: "Mai" STR_IN_READER: "Nel lettore" STR_ALWAYS: "Sempre" -STR_IGNORE: "Ignora" -STR_SLEEP: "Sospendi" -STR_PAGE_TURN: "Cambio pagina" STR_PORTRAIT: "Verticale" STR_LANDSCAPE_CW: "Orizzontale ↻" STR_INVERTED: "Invertito" diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index 66c07712..623aa81e 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -65,7 +65,6 @@ STR_SLEEP_COVER_MODE: "Ұйқы экраны мұқаба режимі" STR_HIDE_BATTERY: "Батарея % жасыру" STR_EXTRA_SPACING: "Қосымша абзац аралығы" STR_TEXT_AA: "Мәтін сырғытпасы" -STR_SHORT_PWR_BTN: "Қуат түймесін қысқа басу" STR_ORIENTATION: "Оқу бағдары" STR_SIDE_BTN_LAYOUT: "Бүйірлік түймелер орналасуы (оқырман)" STR_LONG_PRESS_SKIP: "Ұзақ басу арқылы тарау өткізу" @@ -118,9 +117,6 @@ STR_CROP: "Кесу" STR_NEVER: "Ешқашан" STR_IN_READER: "Оқырманда" STR_ALWAYS: "Әрқашан" -STR_IGNORE: "Елемеу" -STR_SLEEP: "Ұйқы" -STR_PAGE_TURN: "Бет аудару" STR_PORTRAIT: "Тік бағдар" STR_LANDSCAPE_CW: "Көлденең (сағат бағытымен)" STR_INVERTED: "Төңкерілген" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index bb26d143..891a4fb4 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -70,7 +70,6 @@ STR_IMAGES: "Paveikslėliai" STR_IMAGES_DISPLAY: "Rodyti" STR_IMAGES_PLACEHOLDER: "Vietaženklis" STR_IMAGES_SUPPRESS: "Slėpti" -STR_SHORT_PWR_BTN: "Trumpas įjungimo pasp." STR_ORIENTATION: "Orientacija" STR_SIDE_BTN_LAYOUT: "Šoniniai mygtukai" STR_LONG_PRESS_SKIP: "Praleisti skyrių (ilgai)" @@ -124,9 +123,6 @@ STR_CROP: "Kirpti" STR_NEVER: "Niekada" STR_IN_READER: "Skaitytuve" STR_ALWAYS: "Visada" -STR_IGNORE: "Nepaisyti" -STR_SLEEP: "Miegas" -STR_PAGE_TURN: "Versti psl." STR_PORTRAIT: "Stačias" STR_LANDSCAPE_CW: "Gulsčias (P)" STR_INVERTED: "Apverstas" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index 1ef05908..6a948547 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -96,7 +96,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Dyfundowany Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Utwórz fallback dla nieprawidłowego spisu treści" -STR_SHORT_PWR_BTN: "Krótkie naciśnięcie zasilania" STR_ORIENTATION: "Układ czytania" STR_SIDE_BTN_LAYOUT: "Układ przycisków bocznych" STR_LONG_PRESS_SKIP: "Przytrzymaj aby przeskoczyć rozdział" @@ -199,9 +198,6 @@ STR_CROP: "Przytnij" STR_NEVER: "Nigdy" STR_IN_READER: "W czytniku" STR_ALWAYS: "Zawsze" -STR_IGNORE: "Ignoruj" -STR_SLEEP: "Uśpienie" -STR_PAGE_TURN: "Nast. str." STR_PORTRAIT: "Pionowo" STR_LANDSCAPE_CW: "Poziomo P" STR_INVERTED: "Odwrócony" diff --git a/lib/I18n/translations/portuguese_br.yaml b/lib/I18n/translations/portuguese_br.yaml index ca8bd7aa..ed483bcf 100644 --- a/lib/I18n/translations/portuguese_br.yaml +++ b/lib/I18n/translations/portuguese_br.yaml @@ -95,7 +95,6 @@ STR_IMAGE_DITHERING: "Dithering de imagem" STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Bayer difuso" -STR_SHORT_PWR_BTN: "Clique curto no botão de ligar" STR_ORIENTATION: "Orientação de leitura" STR_SIDE_BTN_LAYOUT: "Disposição dos botões laterais" STR_LONG_PRESS_SKIP: "Pular capítulo com pressão longa" @@ -151,9 +150,6 @@ STR_CROP: "Recortar" STR_NEVER: "Nunca" STR_IN_READER: "No leitor" STR_ALWAYS: "Sempre" -STR_IGNORE: "Ignorar" -STR_SLEEP: "Repouso" -STR_PAGE_TURN: "Virar página" STR_PORTRAIT: "Retrato" STR_LANDSCAPE_CW: "Paisagem H" STR_INVERTED: "Invertido" diff --git a/lib/I18n/translations/portuguese_pt.yaml b/lib/I18n/translations/portuguese_pt.yaml index 973a1e0b..f4c993bd 100644 --- a/lib/I18n/translations/portuguese_pt.yaml +++ b/lib/I18n/translations/portuguese_pt.yaml @@ -87,7 +87,6 @@ STR_TEXT_AA: "Suavização do texto" STR_TEXT_DARKNESS: "Escuridão do texto" STR_EXTRA_DARK: "Extra escuro" STR_MAX_DARK: "Máximo" -STR_SHORT_PWR_BTN: "Pressão curta do botão de energia" STR_ORIENTATION: "Orientação de leitura" STR_SIDE_BTN_LAYOUT: "Disposição dos botões laterais" STR_LONG_PRESS_SKIP: "Saltar capítulo com pressão longa" @@ -146,9 +145,6 @@ STR_CROP: "Recortar" STR_NEVER: "Nunca" STR_IN_READER: "No leitor" STR_ALWAYS: "Sempre" -STR_IGNORE: "Ignorar" -STR_SLEEP: "Repouso" -STR_PAGE_TURN: "Virar página" STR_PORTRAIT: "Retrato" STR_LANDSCAPE_CW: "Paisagem H" STR_INVERTED: "Invertido" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index b41caa60..41e9a26a 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -70,7 +70,6 @@ STR_IMAGES: "Imagini" STR_IMAGES_DISPLAY: "Afişare" STR_IMAGES_PLACEHOLDER: "Substituent" STR_IMAGES_SUPPRESS: "Suprimare" -STR_SHORT_PWR_BTN: "Apăsare scurtă întrerupător" STR_ORIENTATION: "Orientare lectură" STR_SIDE_BTN_LAYOUT: "Aspect butoane laterale (lectură)" STR_LONG_PRESS_SKIP: "Sărire capitol la apăsare lungă" @@ -124,9 +123,6 @@ STR_CROP: "Decupat" STR_NEVER: "Niciodată" STR_IN_READER: "În lectură" STR_ALWAYS: "Întotdeauna" -STR_IGNORE: "Ignoră" -STR_SLEEP: "Repaus" -STR_PAGE_TURN: "Răsfoire pagină" STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Orizontal dreapta" STR_INVERTED: "Invers" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index 8268e2ee..7e464cac 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -88,7 +88,6 @@ STR_IMAGES: "Изображения" STR_IMAGES_DISPLAY: "Показать" STR_IMAGES_PLACEHOLDER: "Заглушки" STR_IMAGES_SUPPRESS: "Скрыть" -STR_SHORT_PWR_BTN: "Короткое нажатие PWR" STR_ORIENTATION: "Ориентация чтения" STR_SIDE_BTN_LAYOUT: "Боковые кнопки" STR_LONG_PRESS_SKIP: "Долгое нажатие - смена главы" @@ -144,9 +143,6 @@ STR_CROP: "Обрезать" STR_NEVER: "Никогда" STR_IN_READER: "В режиме чтения" STR_ALWAYS: "Всегда" -STR_IGNORE: "Игнорировать" -STR_SLEEP: "Сон" -STR_PAGE_TURN: "Перелистывание" STR_PORTRAIT: "Портрет" STR_LANDSCAPE_CW: "Ландшафт (CW)" STR_INVERTED: "Инверсия" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index 5b9b753e..b3147081 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -84,7 +84,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Difuzni Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Ustvari rezervo za neveljavno kazalo" -STR_SHORT_PWR_BTN: "Kratek pritisk na gumb za vklop" STR_ORIENTATION: "Orientacija branja" STR_SIDE_BTN_LAYOUT: "Razpored stranskih gumbov" STR_LONG_PRESS_SKIP: "Dolgi pritisk za preskok poglavja" @@ -181,9 +180,6 @@ STR_CROP: "Obreži" STR_NEVER: "Nikoli" STR_IN_READER: "V bralniku" STR_ALWAYS: "Vedno" -STR_IGNORE: "Prezri" -STR_SLEEP: "Spanje" -STR_PAGE_TURN: "Obračanje strani" STR_PORTRAIT: "Pokončno" STR_LANDSCAPE_CW: "Ležeče (v smeri urinega kazalca)" STR_INVERTED: "Obrnjeno" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 047f689e..4b8d2321 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -85,7 +85,6 @@ STR_IMAGES: "Imágenes" STR_IMAGES_DISPLAY: "Mostrar" STR_IMAGES_PLACEHOLDER: "Reemplazar" STR_IMAGES_SUPPRESS: "Ocultar" -STR_SHORT_PWR_BTN: "Toque corto botón encendido" STR_ORIENTATION: "Orientación" STR_SIDE_BTN_LAYOUT: "Función botones laterales (lector)" STR_LONG_PRESS_SKIP: "Saltar capítulo (pulsación larga)" @@ -141,9 +140,6 @@ STR_CROP: "Recortar" STR_NEVER: "Nunca" STR_IN_READER: "En el lector" STR_ALWAYS: "Siempre" -STR_IGNORE: "Ignorar" -STR_SLEEP: "Suspender" -STR_PAGE_TURN: "Pasar página" STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horizontal (horario)" STR_INVERTED: "Invertido" diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index da6099a3..d366dfd1 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -96,7 +96,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Diffus Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Skapa fallback för ogiltig innehållsförteckning" -STR_SHORT_PWR_BTN: "Kort strömknappsklick" STR_ORIENTATION: "Läsrikting" STR_SIDE_BTN_LAYOUT: "Sidoknappslayout (Läsare)" STR_LONG_PRESS_SKIP: "Lång-tryck Kapitelskippning" @@ -199,9 +198,6 @@ STR_CROP: "Beskär" STR_NEVER: "Aldrig" STR_IN_READER: "I Eboksläsare" STR_ALWAYS: "Alltid" -STR_IGNORE: "Ignorera" -STR_SLEEP: "Vila" -STR_PAGE_TURN: "Sidvändning" STR_PORTRAIT: "Porträtt" STR_LANDSCAPE_CW: "Landskap medurs" STR_INVERTED: "Inverterad" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index f23d2420..69e04311 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -65,7 +65,6 @@ STR_SLEEP_COVER_MODE: "Uyku Ekranı Kapak Modu" STR_HIDE_BATTERY: "Pil Yüzdesini Gizle" STR_EXTRA_SPACING: "Ekstra Paragraf Boşluğu" STR_TEXT_AA: "Metin Yumuşatma (AA)" -STR_SHORT_PWR_BTN: "Kısa Güç Tuşu Tıklaması" STR_ORIENTATION: "Okuma Yönü" STR_SIDE_BTN_LAYOUT: "Yan Tuş Dizilimi (okuyucu)" STR_LONG_PRESS_SKIP: "Uzun Basışla Bölüm Atla" @@ -120,9 +119,6 @@ STR_CROP: "Kırp" STR_NEVER: "Asla" STR_IN_READER: "Okuyucuda" STR_ALWAYS: "Her Zaman" -STR_IGNORE: "Yoksay" -STR_SLEEP: "Uyku" -STR_PAGE_TURN: "Sayfa Çevirme" STR_PORTRAIT: "Dikey" STR_LANDSCAPE_CW: "Yatay (Saat Yönü)" STR_INVERTED: "Ters" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index ef942ccb..014744ef 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -95,7 +95,6 @@ STR_IMAGE_DITHERING: "Дітеринг зображення" STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Дифузний Bayer" -STR_SHORT_PWR_BTN: "Коротке натискання кнопки живлення" STR_ORIENTATION: "Орієнтація читання" STR_SIDE_BTN_LAYOUT: "Розташування бічних кнопок (читач)" STR_LONG_PRESS_SKIP: "Пропуск розділу при довгому натисканні" @@ -151,9 +150,6 @@ STR_CROP: "Обрізати" STR_NEVER: "Ніколи" STR_IN_READER: "В читачі" STR_ALWAYS: "Завжди" -STR_IGNORE: "Ігнорувати" -STR_SLEEP: "Сон" -STR_PAGE_TURN: "Перегортання сторінки" STR_PORTRAIT: "Портрет" STR_LANDSCAPE_CW: "Альбомний за годинниковою" STR_INVERTED: "Перевернутий" diff --git a/src/ButtonEventManager.cpp b/src/ButtonEventManager.cpp index 16345b55..05f02516 100644 --- a/src/ButtonEventManager.cpp +++ b/src/ButtonEventManager.cpp @@ -2,11 +2,16 @@ #include "CrossPointSettings.h" -// Required for constexpr array definition in .cpp +// Required for constexpr array out-of-class definition (C++14). constexpr ButtonEventManager::Button ButtonEventManager::ALL_BUTTONS[ButtonEventManager::NUM_BUTTONS]; bool ButtonEventManager::hasDoubleAction(const Button button) { using BA = CrossPointSettings::BUTTON_ACTION; + // Up/Down share physical pins with PageBack/PageForward via sideButtonLayout. + // Their double-action settings must be consulted when checking the PageBack/PageForward FSMs + // so that the disambiguation delay is applied when either role has a double action. + const bool prevNext = static_cast(SETTINGS.sideButtonLayout) == + CrossPointSettings::SIDE_BUTTON_LAYOUT::PREV_NEXT; switch (button) { case Button::Back: return SETTINGS.btnDoubleBack != BA::BTN_DEFAULT; @@ -17,13 +22,16 @@ bool ButtonEventManager::hasDoubleAction(const Button button) { case Button::Right: return SETTINGS.btnDoubleRight != BA::BTN_DEFAULT; case Button::Up: - return SETTINGS.btnDoubleUp != BA::BTN_DEFAULT; case Button::Down: - return SETTINGS.btnDoubleDown != BA::BTN_DEFAULT; + return false; // Up/Down have no dedicated FSM; handled via PageBack/PageForward case Button::PageBack: - return SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT; + // PREV_NEXT: BTN_UP = PageBack; NEXT_PREV: BTN_DOWN = PageBack + return SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT || + (prevNext ? SETTINGS.btnDoubleUp : SETTINGS.btnDoubleDown) != BA::BTN_DEFAULT; case Button::PageForward: - return SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT; + // PREV_NEXT: BTN_DOWN = PageForward; NEXT_PREV: BTN_UP = PageForward + return SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT || + (prevNext ? SETTINGS.btnDoubleDown : SETTINGS.btnDoubleUp) != BA::BTN_DEFAULT; case Button::Power: return SETTINGS.btnDoublePower != BA::BTN_DEFAULT; } diff --git a/src/ButtonEventManager.h b/src/ButtonEventManager.h index f005e3e4..f32f8f00 100644 --- a/src/ButtonEventManager.h +++ b/src/ButtonEventManager.h @@ -26,8 +26,6 @@ class ButtonEventManager { public: using Button = MappedInputManager::Button; - static constexpr int NUM_BUTTONS = 9; // matches MappedInputManager::Button count - enum class PressType { Short, Double, Long }; struct ButtonEvent { @@ -56,9 +54,13 @@ class ButtonEventManager { static bool hasDoubleAction(Button button); private: + // Up and Down alias the same physical GPIO pins as PageBack/PageForward (via sideButtonLayout). + // Running separate FSMs for both would double-fire on every side button press. + // Up/Down are therefore excluded here; their configurable actions are resolved in main.cpp + // by treating a PageBack/PageForward event as the canonical side-button event. + static constexpr int NUM_BUTTONS = 7; static constexpr Button ALL_BUTTONS[NUM_BUTTONS] = { - Button::Back, Button::Confirm, Button::Left, Button::Right, Button::Up, - Button::Down, Button::PageBack, Button::PageForward, Button::Power, + Button::Back, Button::Confirm, Button::Left, Button::Right, Button::PageBack, Button::PageForward, Button::Power, }; enum class State { Idle, Pressed, ReleasedOnce, DoublePressed }; diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 9245d1bc..84d4f452 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -140,7 +140,10 @@ bool CrossPointSettings::loadFromBinaryFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, extraParagraphSpacing); if (++settingsRead >= fileSettingsCount) break; - readAndValidate(inputFile, shortPwrBtn, SHORT_PWRBTN_COUNT); + { + uint8_t ignored; + serialization::readPod(inputFile, ignored); + } // legacy shortPwrBtn field if (++settingsRead >= fileSettingsCount) break; readAndValidate(inputFile, statusBar, STATUS_BAR_MODE_COUNT); // legacy if (++settingsRead >= fileSettingsCount) break; diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 65d42b08..eedc674c 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -127,17 +127,6 @@ class CrossPointSettings { REFRESH_FREQUENCY_COUNT }; - // Short power button press actions - enum SHORT_PWRBTN { - IGNORE = 0, - SLEEP = 1, - PAGE_TURN = 2, - FORCE_REFRESH = 3, - FOOTNOTES = 4, - STAR_PAGE = 5, - SHORT_PWRBTN_COUNT - }; - // Hide battery percentage enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT }; @@ -214,8 +203,6 @@ class CrossPointSettings { // Text darkness (0 = normal, 1 = dark, 2 = extra dark). Default 1 preserves // historical AA rendering (both grayscale shades drawn in the MSB pass). uint8_t textDarkness = DARKNESS_DARK; - // Short power button click behaviour - uint8_t shortPwrBtn = IGNORE; // EPUB reading orientation settings // 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise uint8_t orientation = PORTRAIT; @@ -294,6 +281,9 @@ class CrossPointSettings { BTN_FOOTNOTES, BTN_NEXT_SECTION, BTN_PREV_SECTION, + BTN_EXIT_READER, + BTN_READER_MENU, + BTN_KOREADER_SYNC, BUTTON_ACTION_COUNT }; @@ -335,9 +325,7 @@ class CrossPointSettings { // Get singleton instance static CrossPointSettings& getInstance() { return instance; } - uint16_t getPowerButtonDuration() const { - return (shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP) ? 10 : 400; - } + uint16_t getPowerButtonDuration() const { return 400; } int getReaderFontId() const; // If count_only is true, returns the number of settings items that would be written. diff --git a/src/SettingsList.h b/src/SettingsList.h index 68ad73e5..e6bd2834 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -132,123 +132,112 @@ inline const std::vector list = { // --- Controls --- SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, - {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), - SettingInfo::Enum(StrId::STR_SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn, - {StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN, StrId::STR_FORCE_REFRESH, - StrId::STR_FOOTNOTES, StrId::STR_STAR_PAGE}, - "shortPwrBtn", StrId::STR_CAT_CONTROLS), + {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_MENU_BTN_PHYSICAL), // --- Button Actions (short / double / long press per logical button) --- // All entries share the same ordered action-label list; the submenu groups them behind // a single placeholder row in the device UI. -#define BTN_ACTION_ENUM_VALUES \ - {StrId::STR_BTN_ACT_DEFAULT, StrId::STR_BTN_ACT_PAGE_FORWARD, StrId::STR_BTN_ACT_PAGE_BACK, \ - StrId::STR_BTN_ACT_PAGE_FORWARD_10, StrId::STR_BTN_ACT_PAGE_BACK_10, StrId::STR_BTN_ACT_GO_HOME, \ - StrId::STR_BTN_ACT_SLEEP, StrId::STR_BTN_ACT_FORCE_REFRESH, StrId::STR_BTN_ACT_OPEN_TOC, \ - StrId::STR_BTN_ACT_OPEN_BOOKMARKS, StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, \ - StrId::STR_BTN_ACT_NEXT_SECTION, StrId::STR_BTN_ACT_PREV_SECTION} +// Shared action options (everything except the first "default" entry). +#define BTN_ACT_OPTIONS \ + StrId::STR_BTN_ACT_PAGE_FORWARD, StrId::STR_BTN_ACT_PAGE_BACK, StrId::STR_BTN_ACT_PAGE_FORWARD_10, \ + StrId::STR_BTN_ACT_PAGE_BACK_10, StrId::STR_BTN_ACT_GO_HOME, StrId::STR_BTN_ACT_SLEEP, \ + StrId::STR_BTN_ACT_FORCE_REFRESH, StrId::STR_BTN_ACT_OPEN_TOC, StrId::STR_BTN_ACT_OPEN_BOOKMARKS, \ + StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, StrId::STR_BTN_ACT_NEXT_SECTION, \ + StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, StrId::STR_BTN_ACT_READER_MENU, \ + StrId::STR_BTN_ACT_KOREADER_SYNC - // Back button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, BTN_ACTION_ENUM_VALUES, - "btnShortBack", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_BACK) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleBack, BTN_ACTION_ENUM_VALUES, - "btnDoubleBack", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongBack, BTN_ACTION_ENUM_VALUES, - "btnLongBack", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Confirm button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortConfirm, BTN_ACTION_ENUM_VALUES, - "btnShortConfirm", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_CONFIRM) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleConfirm, BTN_ACTION_ENUM_VALUES, - "btnDoubleConfirm", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongConfirm, BTN_ACTION_ENUM_VALUES, - "btnLongConfirm", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Left button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortLeft, BTN_ACTION_ENUM_VALUES, - "btnShortLeft", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_LEFT) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleLeft, BTN_ACTION_ENUM_VALUES, - "btnDoubleLeft", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongLeft, BTN_ACTION_ENUM_VALUES, - "btnLongLeft", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Right button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortRight, BTN_ACTION_ENUM_VALUES, - "btnShortRight", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_RIGHT) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleRight, BTN_ACTION_ENUM_VALUES, - "btnDoubleRight", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongRight, BTN_ACTION_ENUM_VALUES, - "btnLongRight", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Up button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortUp, BTN_ACTION_ENUM_VALUES, "btnShortUp", + // Back button: short=exit reader, double=ignore, long=go home + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, + {StrId::STR_BTN_DEF_EXIT_READER, BTN_ACT_OPTIONS}, "btnShortBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_BACK), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleBack, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_BACK), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongBack, + {StrId::STR_BTN_DEF_GO_HOME, BTN_ACT_OPTIONS}, "btnLongBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_BACK), + // Confirm button: short=reader menu, double=ignore, long=KOReader sync + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortConfirm, + {StrId::STR_BTN_DEF_READER_MENU, BTN_ACT_OPTIONS}, "btnShortConfirm", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_CONFIRM), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleConfirm, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleConfirm", StrId::STR_CAT_CONTROLS), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongConfirm, + {StrId::STR_BTN_DEF_KOREADER_SYNC, BTN_ACT_OPTIONS}, "btnLongConfirm", StrId::STR_CAT_CONTROLS), + // Left button: short=previous page, double=ignore, long=chapter back + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortLeft, + {StrId::STR_BTN_DEF_PREV_PAGE, BTN_ACT_OPTIONS}, "btnShortLeft", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_LEFT), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleLeft, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleLeft", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_LEFT), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongLeft, + {StrId::STR_BTN_DEF_CHAPTER_BACK, BTN_ACT_OPTIONS}, "btnLongLeft", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_LEFT), + // Right button: short=next page, double=ignore, long=chapter forward + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortRight, + {StrId::STR_BTN_DEF_NEXT_PAGE, BTN_ACT_OPTIONS}, "btnShortRight", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_RIGHT), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleRight, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleRight", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_RIGHT), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongRight, + {StrId::STR_BTN_DEF_CHAPTER_FORWARD, BTN_ACT_OPTIONS}, "btnLongRight", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_RIGHT), + // Up button: short=previous page, double=ignore, long=chapter back + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortUp, + {StrId::STR_BTN_DEF_PREV_PAGE, BTN_ACT_OPTIONS}, "btnShortUp", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_UP), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleUp, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleUp", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_UP), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongUp, + {StrId::STR_BTN_DEF_CHAPTER_BACK, BTN_ACT_OPTIONS}, "btnLongUp", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_UP), + // Down button: short=next page, double=ignore, long=chapter forward + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortDown, + {StrId::STR_BTN_DEF_NEXT_PAGE, BTN_ACT_OPTIONS}, "btnShortDown", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_DOWN), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleDown, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleDown", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_DOWN), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongDown, + {StrId::STR_BTN_DEF_CHAPTER_FORWARD, BTN_ACT_OPTIONS}, "btnLongDown", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_DOWN), + // Page Back button: short=previous page, double=ignore, long=chapter back + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPageBack, + {StrId::STR_BTN_DEF_PREV_PAGE, BTN_ACT_OPTIONS}, "btnShortPageBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_PAGE_BACK), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePageBack, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoublePageBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_PAGE_BACK), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPageBack, + {StrId::STR_BTN_DEF_CHAPTER_BACK, BTN_ACT_OPTIONS}, "btnLongPageBack", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_PAGE_BACK), + // Page Forward button: short=next page, double=ignore, long=chapter forward + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPageForward, + {StrId::STR_BTN_DEF_NEXT_PAGE, BTN_ACT_OPTIONS}, "btnShortPageForward", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_PAGE_FORWARD), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePageForward, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoublePageForward", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_PAGE_FORWARD), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPageForward, + {StrId::STR_BTN_DEF_CHAPTER_FORWARD, BTN_ACT_OPTIONS}, "btnLongPageForward", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_UP) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleUp, BTN_ACTION_ENUM_VALUES, - "btnDoubleUp", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongUp, BTN_ACTION_ENUM_VALUES, "btnLongUp", - StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Down button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortDown, BTN_ACTION_ENUM_VALUES, - "btnShortDown", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_DOWN) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleDown, BTN_ACTION_ENUM_VALUES, - "btnDoubleDown", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongDown, BTN_ACTION_ENUM_VALUES, - "btnLongDown", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Page Back button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPageBack, BTN_ACTION_ENUM_VALUES, - "btnShortPageBack", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_PAGE_BACK) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePageBack, BTN_ACTION_ENUM_VALUES, - "btnDoublePageBack", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPageBack, BTN_ACTION_ENUM_VALUES, - "btnLongPageBack", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Page Forward button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPageForward, BTN_ACTION_ENUM_VALUES, - "btnShortPageForward", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_PAGE_FORWARD) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePageForward, BTN_ACTION_ENUM_VALUES, - "btnDoublePageForward", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPageForward, BTN_ACTION_ENUM_VALUES, - "btnLongPageForward", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - // Power button - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPower, BTN_ACTION_ENUM_VALUES, - "btnShortPower", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_BTN_POWER) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePower, BTN_ACTION_ENUM_VALUES, - "btnDoublePower", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPower, BTN_ACTION_ENUM_VALUES, + .withSubmenu(StrId::STR_BTN_PAGE_FORWARD), + // Power button: short=ignore, double=ignore, long=sleep (via hold timer, not event system) + SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPower, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnShortPower", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_POWER), + SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoublePower, + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoublePower", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_POWER), + SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongPower, {StrId::STR_BTN_DEF_SLEEP}, "btnLongPower", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_MENU_BTN_ACTIONS), + .withSubmenu(StrId::STR_BTN_POWER), -#undef BTN_ACTION_ENUM_VALUES +#undef BTN_ACT_OPTIONS // --- System --- SettingInfo::Toggle(StrId::STR_SHOW_HIDDEN_FILES, &CrossPointSettings::showHiddenFiles, "showHiddenFiles", diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 5abbaa56..d80e9729 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -295,37 +295,6 @@ void EpubReaderActivity::loop() { return; } - const bool screenshotChordReleased = gpio.wasReleased(HalGPIO::BTN_POWER) && gpio.wasReleased(HalGPIO::BTN_DOWN); - - // Handle short power button press for footnotes - if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::FOOTNOTES && - mappedInput.wasReleased(MappedInputManager::Button::Power) && !screenshotChordReleased) { - if (currentPageFootnotes.size() == 1) { - navigateToHref(currentPageFootnotes[0].href, true); - } else if (currentPageFootnotes.size() > 1) { - ReaderUtils::enforceExitFullRefresh(renderer); - startActivityForResult(std::make_unique(renderer, mappedInput, currentPageFootnotes), - [this](const ActivityResult& result) { - if (!result.isCancelled) { - const auto& footnoteResult = std::get(result.data); - navigateToHref(footnoteResult.href, true); - } - requestUpdate(); - }); - } - return; - } - - // Star page toggle via short power button press - if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::STAR_PAGE && - mappedInput.wasReleased(MappedInputManager::Button::Power)) { - if (section && section->currentPage >= 0 && section->currentPage < section->pageCount) { - bookmarkStore.toggle(static_cast(currentSpineIndex), static_cast(section->currentPage)); - requestUpdate(); - } - return; - } - auto [prevTriggered, nextTriggered] = ReaderUtils::detectPageTurn(mappedInput); if (!prevTriggered && !nextTriggered) { return; @@ -1905,6 +1874,46 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION requestUpdate(); break; } + case BA::BTN_EXIT_READER: + ReaderUtils::enforceExitFullRefresh(renderer); + finish(); + break; + case BA::BTN_READER_MENU: + if (epub) { + const int currentPage = section ? section->currentPage + 1 : 0; + const int totalPages = section ? section->pageCount : 0; + float bookProgress = 0.0f; + if (epub->getBookSize() > 0 && section && section->pageCount > 0) { + const float chapterProgress = + static_cast(section->currentPage) / static_cast(section->pageCount); + bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f; + } + const int bookProgressPercent = clampPercent(static_cast(bookProgress + 0.5f)); + const bool isCurrentPageStarred = section && bookmarkStore.has(static_cast(currentSpineIndex), + static_cast(section->currentPage)); + ReaderUtils::enforceExitFullRefresh(renderer); + startActivityForResult( + std::make_unique( + renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, + SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride, + bookImageRenderingOverride, bookFontFamilyOverride, bookFontSizeOverride, SETTINGS.textDarkness, + !bookmarkStore.isEmpty(), isCurrentPageStarred), + [this](const ActivityResult& result) { + const auto& menu = std::get(result.data); + applyOrientation(menu.orientation); + applyTextDarkness(menu.textDarkness); + toggleAutoPageTurn(menu.pageTurnOption); + applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride, + menu.fontSizeOverride); + if (!result.isCancelled) { + onReaderMenuConfirm(static_cast(menu.action)); + } + }); + } + break; + case BA::BTN_KOREADER_SYNC: + launchKOReaderSync(SyncLaunchMode::COMPARE); + break; default: break; } diff --git a/src/activities/reader/MdReaderActivity.cpp b/src/activities/reader/MdReaderActivity.cpp index a2d1001d..98aaa51e 100644 --- a/src/activities/reader/MdReaderActivity.cpp +++ b/src/activities/reader/MdReaderActivity.cpp @@ -947,6 +947,10 @@ void MdReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION ac }); } break; + case BA::BTN_EXIT_READER: + ReaderUtils::enforceExitFullRefresh(renderer); + finish(); + break; default: break; } diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index af8c9bc7..da5da0b5 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -64,9 +64,7 @@ struct PageTurnResult { inline PageTurnResult detectPageTurn(const MappedInputManager& input) { const bool prev = input.wasReleased(MappedInputManager::Button::PageBack) || input.wasReleased(MappedInputManager::Button::Left); - const bool powerTurn = SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && - input.wasReleased(MappedInputManager::Button::Power); - const bool next = input.wasReleased(MappedInputManager::Button::PageForward) || powerTurn || + const bool next = input.wasReleased(MappedInputManager::Button::PageForward) || input.wasReleased(MappedInputManager::Button::Right); return {prev, next}; } diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 7e46540d..4e87e54d 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -155,16 +155,6 @@ void TxtReaderActivity::loop() { return; } - // Star page toggle via short power button press - if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::STAR_PAGE && - mappedInput.wasReleased(MappedInputManager::Button::Power)) { - if (currentPage >= 0) { - bookmarkStore.toggle(0, static_cast(currentPage)); - } - requestUpdate(); - return; - } - // Open starred pages list via Confirm button if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) && !bookmarkStore.isEmpty()) { ReaderUtils::enforceExitFullRefresh(renderer); @@ -826,6 +816,10 @@ void TxtReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a clampPage(); requestUpdate(); break; + case BA::BTN_EXIT_READER: + ReaderUtils::enforceExitFullRefresh(renderer); + finish(); + break; default: break; } diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index cb83bf07..14b4e832 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -94,9 +94,7 @@ void XtcReaderActivity::loop() { const bool prevTriggered = mappedInput.wasReleased(MappedInputManager::Button::PageBack) || mappedInput.wasReleased(MappedInputManager::Button::Left); - const bool powerPageTurn = SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::PAGE_TURN && - mappedInput.wasReleased(MappedInputManager::Button::Power); - const bool nextTriggered = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || powerPageTurn || + const bool nextTriggered = mappedInput.wasReleased(MappedInputManager::Button::PageForward) || mappedInput.wasReleased(MappedInputManager::Button::Right); if (!prevTriggered && !nextTriggered) { @@ -469,6 +467,10 @@ void XtcReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a currentPage = (currentPage >= 10) ? currentPage - 10 : 0; requestUpdate(); break; + case BA::BTN_EXIT_READER: + ReaderUtils::enforceExitFullRefresh(renderer); + finish(); + break; default: break; } diff --git a/src/main.cpp b/src/main.cpp index cc93abe2..15296b51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -370,14 +370,6 @@ void loop() { } } - // Refresh screen when power button is short-pressed with FORCE_REFRESH setting. - if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::FORCE_REFRESH && - mappedInputManager.wasReleased(MappedInputManager::Button::Power)) { - LOG_DBG("MAIN", "Manual screen refresh triggered"); - RenderLock lock; - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - } - if (!gpio.isPressed(HalGPIO::BTN_POWER)) { powerHoldStart = 0; } @@ -395,6 +387,13 @@ void loop() { using B = MappedInputManager::Button; ButtonEventManager::ButtonEvent ev; while (buttonEventManager.consumeEvent(ev)) { + // Up/Down share physical pins with PageBack/PageForward via sideButtonLayout. + // ButtonEventManager only runs FSMs for PageBack/PageForward (not Up/Down) to avoid + // double-firing. Here we resolve Up/Down settings as aliases of the side-button events: + // with PREV_NEXT layout, PageBack = BTN_UP and PageForward = BTN_DOWN, so the + // btnShort/Double/LongUp/Down settings apply when PageBack/PageForward fires. + const bool prevNext = static_cast(SETTINGS.sideButtonLayout) == + CrossPointSettings::SIDE_BUTTON_LAYOUT::PREV_NEXT; auto actionFor = [&](B btn) -> uint8_t { switch (ev.type) { case ButtonEventManager::PressType::Short: @@ -408,13 +407,15 @@ void loop() { case B::Right: return SETTINGS.btnShortRight; case B::Up: - return SETTINGS.btnShortUp; + return BA::BTN_DEFAULT; // no dedicated FSM case B::Down: - return SETTINGS.btnShortDown; + return BA::BTN_DEFAULT; // no dedicated FSM case B::PageBack: - return SETTINGS.btnShortPageBack; + if (SETTINGS.btnShortPageBack != BA::BTN_DEFAULT) return SETTINGS.btnShortPageBack; + return prevNext ? SETTINGS.btnShortUp : SETTINGS.btnShortDown; case B::PageForward: - return SETTINGS.btnShortPageForward; + if (SETTINGS.btnShortPageForward != BA::BTN_DEFAULT) return SETTINGS.btnShortPageForward; + return prevNext ? SETTINGS.btnShortDown : SETTINGS.btnShortUp; case B::Power: return SETTINGS.btnShortPower; } @@ -430,13 +431,15 @@ void loop() { case B::Right: return SETTINGS.btnDoubleRight; case B::Up: - return SETTINGS.btnDoubleUp; + return BA::BTN_DEFAULT; // no dedicated FSM case B::Down: - return SETTINGS.btnDoubleDown; + return BA::BTN_DEFAULT; // no dedicated FSM case B::PageBack: - return SETTINGS.btnDoublePageBack; + if (SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT) return SETTINGS.btnDoublePageBack; + return prevNext ? SETTINGS.btnDoubleUp : SETTINGS.btnDoubleDown; case B::PageForward: - return SETTINGS.btnDoublePageForward; + if (SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT) return SETTINGS.btnDoublePageForward; + return prevNext ? SETTINGS.btnDoubleDown : SETTINGS.btnDoubleUp; case B::Power: return SETTINGS.btnDoublePower; } @@ -452,13 +455,15 @@ void loop() { case B::Right: return SETTINGS.btnLongRight; case B::Up: - return SETTINGS.btnLongUp; + return BA::BTN_DEFAULT; // no dedicated FSM case B::Down: - return SETTINGS.btnLongDown; + return BA::BTN_DEFAULT; // no dedicated FSM case B::PageBack: - return SETTINGS.btnLongPageBack; + if (SETTINGS.btnLongPageBack != BA::BTN_DEFAULT) return SETTINGS.btnLongPageBack; + return prevNext ? SETTINGS.btnLongUp : SETTINGS.btnLongDown; case B::PageForward: - return SETTINGS.btnLongPageForward; + if (SETTINGS.btnLongPageForward != BA::BTN_DEFAULT) return SETTINGS.btnLongPageForward; + return prevNext ? SETTINGS.btnLongDown : SETTINGS.btnLongUp; case B::Power: return SETTINGS.btnLongPower; } @@ -506,6 +511,21 @@ void loop() { case BA::BTN_FOOTNOTES: activityManager.dispatchButtonAction(BA::BTN_FOOTNOTES); break; + case BA::BTN_NEXT_SECTION: + activityManager.dispatchButtonAction(BA::BTN_NEXT_SECTION); + break; + case BA::BTN_PREV_SECTION: + activityManager.dispatchButtonAction(BA::BTN_PREV_SECTION); + break; + case BA::BTN_EXIT_READER: + activityManager.dispatchButtonAction(BA::BTN_EXIT_READER); + break; + case BA::BTN_READER_MENU: + activityManager.dispatchButtonAction(BA::BTN_READER_MENU); + break; + case BA::BTN_KOREADER_SYNC: + activityManager.dispatchButtonAction(BA::BTN_KOREADER_SYNC); + break; default: break; } From 3046a1a94a66448266f6332a579edd72c8787c00 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sun, 26 Apr 2026 17:12:52 +0200 Subject: [PATCH 3/5] More cleaningup Co-authored-by: Copilot --- lib/I18n/translations/belarusian.yaml | 3 - lib/I18n/translations/catalan.yaml | 3 - lib/I18n/translations/czech.yaml | 3 - lib/I18n/translations/danish.yaml | 3 - lib/I18n/translations/dutch.yaml | 3 - lib/I18n/translations/english.yaml | 5 - lib/I18n/translations/finnish.yaml | 3 - lib/I18n/translations/french.yaml | 3 - lib/I18n/translations/german.yaml | 3 - lib/I18n/translations/hungarian.yaml | 3 - lib/I18n/translations/italian.yaml | 3 - lib/I18n/translations/kazakh.yaml | 3 - lib/I18n/translations/lithuanian.yaml | 3 - lib/I18n/translations/polish.yaml | 3 - lib/I18n/translations/portuguese_br.yaml | 3 - lib/I18n/translations/portuguese_pt.yaml | 3 - lib/I18n/translations/romanian.yaml | 3 - lib/I18n/translations/russian.yaml | 3 - lib/I18n/translations/slovenian.yaml | 3 - lib/I18n/translations/spanish.yaml | 3 - lib/I18n/translations/swedish.yaml | 3 - lib/I18n/translations/turkish.yaml | 3 - lib/I18n/translations/ukrainian.yaml | 3 - src/ButtonEventManager.cpp | 16 +-- src/ButtonEventManager.h | 4 - src/CrossPointSettings.cpp | 5 +- src/CrossPointSettings.h | 12 -- src/MappedInputManager.cpp | 32 +---- src/SettingsList.h | 31 +--- src/activities/settings/SettingsActivity.cpp | 5 +- .../settings/SettingsSubmenuActivity.cpp | 4 +- src/main.cpp | 133 +++++++++--------- 32 files changed, 83 insertions(+), 230 deletions(-) diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index 366ee3d8..56cb0281 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -67,7 +67,6 @@ STR_HIDE_BATTERY: "Схаваць % батарэі" STR_EXTRA_SPACING: "Дадат. інтэрвал абзаца" STR_TEXT_AA: "Згладжванне тэксту" STR_ORIENTATION: "Арыентацыя чытання" -STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі" STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела" STR_FONT_FAMILY: "Шрыфт чытання" STR_FONT_SIZE: "Памер шрыфту інтэрфейсу" @@ -122,8 +121,6 @@ STR_PORTRAIT: "Партрэт" STR_LANDSCAPE_CW: "Ландшафт (CW)" STR_INVERTED: "Інверсія" STR_LANDSCAPE_CCW: "Ландшафт (CCW)" -STR_PREV_NEXT: "Назад/Наперад" -STR_NEXT_PREV: "Наперад/Назад" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index 49d5b424..01c0d705 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -71,7 +71,6 @@ STR_IMAGES_DISPLAY: "Mostrar" STR_IMAGES_PLACEHOLDER: "Text de mostra" STR_IMAGES_SUPPRESS: "Suprimir" STR_ORIENTATION: "Orientació de lectura" -STR_SIDE_BTN_LAYOUT: "Disposició botons laterals" STR_LONG_PRESS_SKIP: "Pressió llarga omet el capítol" STR_FONT_FAMILY: "Tipus de lletra" STR_FONT_SIZE: "Mida de la lletra (UI)" @@ -127,8 +126,6 @@ STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horitzontal horari" STR_INVERTED: "Invertit" STR_LANDSCAPE_CCW: "Horitzontal antihorari" -STR_PREV_NEXT: "Anterior/Següent" -STR_NEXT_PREV: "Següent/Anterior" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index c9f629ce..5da37a60 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -67,7 +67,6 @@ STR_HIDE_BATTERY: "Skrýt baterii %" STR_EXTRA_SPACING: "Extra mezery mezi odstavci" STR_TEXT_AA: "Vyhlazování textu" STR_ORIENTATION: "Orientace čtení" -STR_SIDE_BTN_LAYOUT: "Rozvržení bočních tlačítek (čtečka)" STR_LONG_PRESS_SKIP: "Dlouhé stisknutí Přeskočit kapitolu" STR_FONT_FAMILY: "Rodina písem čtečky" STR_FONT_SIZE: "Velikost písma rozhraní" @@ -122,8 +121,6 @@ STR_PORTRAIT: "Na výšku" STR_LANDSCAPE_CW: "Na šířku po směru hod. ručiček" STR_INVERTED: "Invertovaný" STR_LANDSCAPE_CCW: "Na šířku proti směru hod. ručiček" -STR_PREV_NEXT: "Předchozí/Další" -STR_NEXT_PREV: "Další/Předchozí" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index 86b60de8..9e540dca 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -71,7 +71,6 @@ STR_IMAGES_DISPLAY: "Vis" STR_IMAGES_PLACEHOLDER: "Pladsholder" STR_IMAGES_SUPPRESS: "Skjul" STR_ORIENTATION: "Læseretning" -STR_SIDE_BTN_LAYOUT: "Knaplayout på siden (læser)" STR_LONG_PRESS_SKIP: "Langt tryk spring kapitel over" STR_FONT_FAMILY: "Læser skrifttype" STR_FONT_SIZE: "Læser skriftstørrelse" @@ -127,8 +126,6 @@ STR_PORTRAIT: "Portræt" STR_LANDSCAPE_CW: "Liggende med uret" STR_INVERTED: "Inverteret" STR_LANDSCAPE_CCW: "Liggende mod uret" -STR_PREV_NEXT: "Forrige/Næste" -STR_NEXT_PREV: "Næste/Forrige" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index 5e0e631f..55d3814f 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -97,7 +97,6 @@ STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Gediffuseerde Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Maak fallback voor ongeldige inhoudsopgave" STR_ORIENTATION: "Leesstand" -STR_SIDE_BTN_LAYOUT: "Indeling zijknoppen (lezer)" STR_LONG_PRESS_SKIP: "Hoofdstuk overslaan (lang indrukken)" STR_FONT_FAMILY: "Lettertype lezer" STR_FONT_SIZE: "Lettergrootte lezer" @@ -202,8 +201,6 @@ STR_PORTRAIT: "Staand" STR_LANDSCAPE_CW: "Liggend (rechtsom)" STR_INVERTED: "Omgekeerd" STR_LANDSCAPE_CCW: "Liggend (linksom)" -STR_PREV_NEXT: "Vorige/Volgende" -STR_NEXT_PREV: "Volgende/Vorige" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 89b2ac37..5a929c21 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -97,7 +97,6 @@ STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Diffused Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Create fallback for invalid TOC" STR_ORIENTATION: "Reading Orientation" -STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)" STR_LONG_PRESS_SKIP: "Long-press Chapter Skip" STR_FONT_FAMILY: "Reader Font Family" STR_FONT_SIZE: "Reader Font Size" @@ -202,8 +201,6 @@ STR_PORTRAIT: "Portrait" STR_LANDSCAPE_CW: "Landscape CW" STR_INVERTED: "Inverted" STR_LANDSCAPE_CCW: "Landscape CCW" -STR_PREV_NEXT: "Prev/Next" -STR_NEXT_PREV: "Next/Prev" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" @@ -552,8 +549,6 @@ STR_BTN_BACK: "Back Button" STR_BTN_CONFIRM: "Confirm Button" STR_BTN_LEFT: "Left Button" STR_BTN_RIGHT: "Right Button" -STR_BTN_UP: "Up Button" -STR_BTN_DOWN: "Down Button" STR_BTN_PAGE_BACK: "Page Back Button" STR_BTN_PAGE_FORWARD: "Page Forward Button" STR_BTN_POWER: "Power Button" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index 0c73b385..50923526 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -67,7 +67,6 @@ STR_HIDE_BATTERY: "Piilota akun %" STR_EXTRA_SPACING: "Kappaleiden lisäväli" STR_TEXT_AA: "Tekstin reunanpehmennys" STR_ORIENTATION: "Lukusuunta" -STR_SIDE_BTN_LAYOUT: "Sivupainikkeiden asettelu (lukija)" STR_LONG_PRESS_SKIP: "Pitkä painallus: lukuhyppy" STR_FONT_FAMILY: "Lukijan fonttiperhe" STR_FONT_SIZE: "Käyttöliittymän fonttikoko" @@ -122,8 +121,6 @@ STR_PORTRAIT: "Pysty" STR_LANDSCAPE_CW: "Vaaka myötäpäivään" STR_INVERTED: "Käännetty" STR_LANDSCAPE_CCW: "Vaaka vastapäivään" -STR_PREV_NEXT: "Edell/Seur" -STR_NEXT_PREV: "Seur/Edell" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 5b24dc33..529475a4 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -92,7 +92,6 @@ STR_IMAGES_DISPLAY: "Affichage" STR_IMAGES_PLACEHOLDER: "Espace réservé" STR_IMAGES_SUPPRESS: "Masquer" STR_ORIENTATION: "Orientation de lecture" -STR_SIDE_BTN_LAYOUT: "Boutons latéraux" STR_LONG_PRESS_SKIP: "Appui long saut de chapitre" STR_FONT_FAMILY: "Police de caractères du lecteur" STR_FONT_SIZE: "Taille texte interface" @@ -150,8 +149,6 @@ STR_PORTRAIT: "Portrait" STR_LANDSCAPE_CW: "Paysage" STR_INVERTED: "Inversé" STR_LANDSCAPE_CCW: "Paysage inversé" -STR_PREV_NEXT: "Préc/Suiv" -STR_NEXT_PREV: "Suiv/Préc" STR_ABC: "abc" STR_FORCE_REFRESH: "Actualiser l'écran" STR_NEXT: "Suiv" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index 81de9fbd..883a77f7 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -80,7 +80,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Diffundiertes Bayer" STR_ORIENTATION: "Leseausrichtung" -STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)" STR_LONG_PRESS_SKIP: "Langes Drücken springt Kap." STR_FONT_FAMILY: "Lese-Schriftfamilie" STR_FONT_SIZE: "Schriftgröße" @@ -141,8 +140,6 @@ STR_PORTRAIT: "Hochformat" STR_LANDSCAPE_CW: "Querformat rechts" STR_INVERTED: "Invertiert" STR_LANDSCAPE_CCW: "Querformat links" -STR_PREV_NEXT: "Zurück/Weiter" -STR_NEXT_PREV: "Weiter/Zurück" STR_ABC: "abc" STR_FORCE_REFRESH: "Bildschirm aktualisieren" STR_NEXT: "Weiter" diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 95416711..a3090ed3 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -71,7 +71,6 @@ STR_IMAGES_DISPLAY: "Megjelenítés" STR_IMAGES_PLACEHOLDER: "Helyőrző" STR_IMAGES_SUPPRESS: "Elnyomás" STR_ORIENTATION: "Olvasási irány" -STR_SIDE_BTN_LAYOUT: "Oldalsó gomb elrendezés (olvasó)" STR_LONG_PRESS_SKIP: "Hosszú nyomás - fejezet ugrás" STR_FONT_FAMILY: "Olvasó betűkészlet" STR_FONT_SIZE: "Olvasó betűméret" @@ -127,8 +126,6 @@ STR_PORTRAIT: "Álló" STR_LANDSCAPE_CW: "Fekvő jobbra" STR_INVERTED: "Fordított" STR_LANDSCAPE_CCW: "Fekvő balra" -STR_PREV_NEXT: "Előző/Következő" -STR_NEXT_PREV: "Következő/Előző" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index eb9e4f00..add4d299 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -92,7 +92,6 @@ STR_IMAGES_DISPLAY: "Visualizza" STR_IMAGES_PLACEHOLDER: "Segnaposto" STR_IMAGES_SUPPRESS: "Nascondi" STR_ORIENTATION: "Orientamento lettura" -STR_SIDE_BTN_LAYOUT: "Pulsanti laterali (lettore)" STR_LONG_PRESS_SKIP: "Pressione lunga: salta capitolo" STR_FONT_FAMILY: "Font lettore" STR_FONT_SIZE: "Dimensione font lettore" @@ -150,8 +149,6 @@ STR_PORTRAIT: "Verticale" STR_LANDSCAPE_CW: "Orizzontale ↻" STR_INVERTED: "Invertito" STR_LANDSCAPE_CCW: "Orizzontale ↺" -STR_PREV_NEXT: "Prec/Succ" -STR_NEXT_PREV: "Succ/Prec" STR_ABC: "abc" STR_FORCE_REFRESH: "Aggiorna schermo" STR_NEXT: "Succ" diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index 623aa81e..f6d87b8a 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -66,7 +66,6 @@ STR_HIDE_BATTERY: "Батарея % жасыру" STR_EXTRA_SPACING: "Қосымша абзац аралығы" STR_TEXT_AA: "Мәтін сырғытпасы" STR_ORIENTATION: "Оқу бағдары" -STR_SIDE_BTN_LAYOUT: "Бүйірлік түймелер орналасуы (оқырман)" STR_LONG_PRESS_SKIP: "Ұзақ басу арқылы тарау өткізу" STR_FONT_FAMILY: "Оқырман қаріп тобы" STR_FONT_SIZE: "Интерфейс қаріп өлшемі" @@ -121,8 +120,6 @@ STR_PORTRAIT: "Тік бағдар" STR_LANDSCAPE_CW: "Көлденең (сағат бағытымен)" STR_INVERTED: "Төңкерілген" STR_LANDSCAPE_CCW: "Көлденең (сағат тіліне қарсы)" -STR_PREV_NEXT: "Алдыңғы/Келесі" -STR_NEXT_PREV: "Келесі/Алдыңғы" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index 891a4fb4..29c457db 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -71,7 +71,6 @@ STR_IMAGES_DISPLAY: "Rodyti" STR_IMAGES_PLACEHOLDER: "Vietaženklis" STR_IMAGES_SUPPRESS: "Slėpti" STR_ORIENTATION: "Orientacija" -STR_SIDE_BTN_LAYOUT: "Šoniniai mygtukai" STR_LONG_PRESS_SKIP: "Praleisti skyrių (ilgai)" STR_FONT_FAMILY: "Šriftas" STR_FONT_SIZE: "Šrifto dydis" @@ -127,8 +126,6 @@ STR_PORTRAIT: "Stačias" STR_LANDSCAPE_CW: "Gulsčias (P)" STR_INVERTED: "Apverstas" STR_LANDSCAPE_CCW: "Gulsčias (A)" -STR_PREV_NEXT: "Atgal/Pirmyn" -STR_NEXT_PREV: "Pirmyn/Atgal" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "O. Dyslexic" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index 6a948547..a20c0fb8 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -97,7 +97,6 @@ STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Dyfundowany Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Utwórz fallback dla nieprawidłowego spisu treści" STR_ORIENTATION: "Układ czytania" -STR_SIDE_BTN_LAYOUT: "Układ przycisków bocznych" STR_LONG_PRESS_SKIP: "Przytrzymaj aby przeskoczyć rozdział" STR_FONT_FAMILY: "Czcionka" STR_FONT_SIZE: "Rozmiar czcionki" @@ -202,8 +201,6 @@ STR_PORTRAIT: "Pionowo" STR_LANDSCAPE_CW: "Poziomo P" STR_INVERTED: "Odwrócony" STR_LANDSCAPE_CCW: "Poziomo L" -STR_PREV_NEXT: "Poprz./Nast." -STR_NEXT_PREV: "Nast./Poprz." STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/portuguese_br.yaml b/lib/I18n/translations/portuguese_br.yaml index ed483bcf..366f1210 100644 --- a/lib/I18n/translations/portuguese_br.yaml +++ b/lib/I18n/translations/portuguese_br.yaml @@ -96,7 +96,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Bayer difuso" STR_ORIENTATION: "Orientação de leitura" -STR_SIDE_BTN_LAYOUT: "Disposição dos botões laterais" STR_LONG_PRESS_SKIP: "Pular capítulo com pressão longa" STR_FONT_FAMILY: "Fonte do leitor" STR_FONT_SIZE: "Tam. da fonte da UI" @@ -154,8 +153,6 @@ STR_PORTRAIT: "Retrato" STR_LANDSCAPE_CW: "Paisagem H" STR_INVERTED: "Invertido" STR_LANDSCAPE_CCW: "Paisagem AH" -STR_PREV_NEXT: "Ant/Próx" -STR_NEXT_PREV: "Próx/Ant" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/portuguese_pt.yaml b/lib/I18n/translations/portuguese_pt.yaml index f4c993bd..1394e0d9 100644 --- a/lib/I18n/translations/portuguese_pt.yaml +++ b/lib/I18n/translations/portuguese_pt.yaml @@ -88,7 +88,6 @@ STR_TEXT_DARKNESS: "Escuridão do texto" STR_EXTRA_DARK: "Extra escuro" STR_MAX_DARK: "Máximo" STR_ORIENTATION: "Orientação de leitura" -STR_SIDE_BTN_LAYOUT: "Disposição dos botões laterais" STR_LONG_PRESS_SKIP: "Saltar capítulo com pressão longa" STR_FONT_FAMILY: "Tipo de letra do leitor" STR_FONT_SIZE: "Tamanho da letra do leitor" @@ -149,8 +148,6 @@ STR_PORTRAIT: "Retrato" STR_LANDSCAPE_CW: "Paisagem H" STR_INVERTED: "Invertido" STR_LANDSCAPE_CCW: "Paisagem AH" -STR_PREV_NEXT: "Ant./Próx." -STR_NEXT_PREV: "Próx./Ant." STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index 41e9a26a..4d935023 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -71,7 +71,6 @@ STR_IMAGES_DISPLAY: "Afişare" STR_IMAGES_PLACEHOLDER: "Substituent" STR_IMAGES_SUPPRESS: "Suprimare" STR_ORIENTATION: "Orientare lectură" -STR_SIDE_BTN_LAYOUT: "Aspect butoane laterale (lectură)" STR_LONG_PRESS_SKIP: "Sărire capitol la apăsare lungă" STR_FONT_FAMILY: "Familie font lectură" STR_FONT_SIZE: "Dimensiune font" @@ -127,8 +126,6 @@ STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Orizontal dreapta" STR_INVERTED: "Invers" STR_LANDSCAPE_CCW: "Orizontal stânga" -STR_PREV_NEXT: "Înainte/Înapoi" -STR_NEXT_PREV: "Înapoi/Înainte" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index 7e464cac..a8779835 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -89,7 +89,6 @@ STR_IMAGES_DISPLAY: "Показать" STR_IMAGES_PLACEHOLDER: "Заглушки" STR_IMAGES_SUPPRESS: "Скрыть" STR_ORIENTATION: "Ориентация чтения" -STR_SIDE_BTN_LAYOUT: "Боковые кнопки" STR_LONG_PRESS_SKIP: "Долгое нажатие - смена главы" STR_FONT_FAMILY: "Шрифт чтения" STR_FONT_SIZE: "Размер шрифта интерфейса" @@ -147,8 +146,6 @@ STR_PORTRAIT: "Портрет" STR_LANDSCAPE_CW: "Ландшафт (CW)" STR_INVERTED: "Инверсия" STR_LANDSCAPE_CCW: "Ландшафт (CCW)" -STR_PREV_NEXT: "Назад/Вперёд" -STR_NEXT_PREV: "Вперёд/Назад" STR_ABC: "abc" STR_FORCE_REFRESH: "Обновить экран" STR_NEXT: "Далее" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index b3147081..b8cf33f1 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -85,7 +85,6 @@ STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Difuzni Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Ustvari rezervo za neveljavno kazalo" STR_ORIENTATION: "Orientacija branja" -STR_SIDE_BTN_LAYOUT: "Razpored stranskih gumbov" STR_LONG_PRESS_SKIP: "Dolgi pritisk za preskok poglavja" STR_FONT_FAMILY: "Pisava bralnika" STR_FONT_SIZE: "Velikost pisave" @@ -184,8 +183,6 @@ STR_PORTRAIT: "Pokončno" STR_LANDSCAPE_CW: "Ležeče (v smeri urinega kazalca)" STR_INVERTED: "Obrnjeno" STR_LANDSCAPE_CCW: "Ležeče (proti smeri urinega kazalca)" -STR_PREV_NEXT: "Nazaj/Naprej" -STR_NEXT_PREV: "Naprej/Nazaj" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 4b8d2321..397ca2e4 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -86,7 +86,6 @@ STR_IMAGES_DISPLAY: "Mostrar" STR_IMAGES_PLACEHOLDER: "Reemplazar" STR_IMAGES_SUPPRESS: "Ocultar" STR_ORIENTATION: "Orientación" -STR_SIDE_BTN_LAYOUT: "Función botones laterales (lector)" STR_LONG_PRESS_SKIP: "Saltar capítulo (pulsación larga)" STR_FONT_FAMILY: "Tipografía" STR_FONT_SIZE: "Tamaño" @@ -144,8 +143,6 @@ STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horizontal (horario)" STR_INVERTED: "Invertido" STR_LANDSCAPE_CCW: "Horizontal (antihorario)" -STR_PREV_NEXT: "Ant./Sig." -STR_NEXT_PREV: "Sig./Ant." STR_ABC: "abc" STR_FORCE_REFRESH: "Actualizar pantalla" STR_NEXT: "Sig" diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index d366dfd1..16155cd1 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -97,7 +97,6 @@ STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Diffus Bayer" STR_CREATE_FALLBACK_FOR_INVALID_TOC: "Skapa fallback för ogiltig innehållsförteckning" STR_ORIENTATION: "Läsrikting" -STR_SIDE_BTN_LAYOUT: "Sidoknappslayout (Läsare)" STR_LONG_PRESS_SKIP: "Lång-tryck Kapitelskippning" STR_FONT_FAMILY: "Eboksläsarens typsnittsfamilj" STR_FONT_SIZE: "Eboksläsarens typsnittsstorlek" @@ -202,8 +201,6 @@ STR_PORTRAIT: "Porträtt" STR_LANDSCAPE_CW: "Landskap medurs" STR_INVERTED: "Inverterad" STR_LANDSCAPE_CCW: "Landskap moturs" -STR_PREV_NEXT: "Förra/Nästa" -STR_NEXT_PREV: "Nästa/Förra" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Öppen Dyslexic" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index 69e04311..1b28ab40 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -66,7 +66,6 @@ STR_HIDE_BATTERY: "Pil Yüzdesini Gizle" STR_EXTRA_SPACING: "Ekstra Paragraf Boşluğu" STR_TEXT_AA: "Metin Yumuşatma (AA)" STR_ORIENTATION: "Okuma Yönü" -STR_SIDE_BTN_LAYOUT: "Yan Tuş Dizilimi (okuyucu)" STR_LONG_PRESS_SKIP: "Uzun Basışla Bölüm Atla" STR_FONT_FAMILY: "Okuyucu Yazı Tipi Ailesi" STR_FONT_SIZE: "Arayüz Yazı Boyutu" @@ -123,8 +122,6 @@ STR_PORTRAIT: "Dikey" STR_LANDSCAPE_CW: "Yatay (Saat Yönü)" STR_INVERTED: "Ters" STR_LANDSCAPE_CCW: "Yatay (Saat Yönü Tersi)" -STR_PREV_NEXT: "Önceki/Sonraki" -STR_NEXT_PREV: "Sonraki/Önceki" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index 014744ef..0191fc5f 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -96,7 +96,6 @@ STR_IMAGE_DITHER_BAYER: "Bayer" STR_IMAGE_DITHER_ATKINSON: "Atkinson" STR_IMAGE_DITHER_DIFFUSED_BAYER: "Дифузний Bayer" STR_ORIENTATION: "Орієнтація читання" -STR_SIDE_BTN_LAYOUT: "Розташування бічних кнопок (читач)" STR_LONG_PRESS_SKIP: "Пропуск розділу при довгому натисканні" STR_FONT_FAMILY: "Сімейство шрифтів" STR_FONT_SIZE: "Розмір шрифту інтерфейсу" @@ -154,8 +153,6 @@ STR_PORTRAIT: "Портрет" STR_LANDSCAPE_CW: "Альбомний за годинниковою" STR_INVERTED: "Перевернутий" STR_LANDSCAPE_CCW: "Альбомний проти годинникової" -STR_PREV_NEXT: "Попер/Наст" -STR_NEXT_PREV: "Наст/Попер" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" diff --git a/src/ButtonEventManager.cpp b/src/ButtonEventManager.cpp index 05f02516..af344e09 100644 --- a/src/ButtonEventManager.cpp +++ b/src/ButtonEventManager.cpp @@ -7,11 +7,6 @@ constexpr ButtonEventManager::Button ButtonEventManager::ALL_BUTTONS[ButtonEvent bool ButtonEventManager::hasDoubleAction(const Button button) { using BA = CrossPointSettings::BUTTON_ACTION; - // Up/Down share physical pins with PageBack/PageForward via sideButtonLayout. - // Their double-action settings must be consulted when checking the PageBack/PageForward FSMs - // so that the disambiguation delay is applied when either role has a double action. - const bool prevNext = static_cast(SETTINGS.sideButtonLayout) == - CrossPointSettings::SIDE_BUTTON_LAYOUT::PREV_NEXT; switch (button) { case Button::Back: return SETTINGS.btnDoubleBack != BA::BTN_DEFAULT; @@ -21,17 +16,10 @@ bool ButtonEventManager::hasDoubleAction(const Button button) { return SETTINGS.btnDoubleLeft != BA::BTN_DEFAULT; case Button::Right: return SETTINGS.btnDoubleRight != BA::BTN_DEFAULT; - case Button::Up: - case Button::Down: - return false; // Up/Down have no dedicated FSM; handled via PageBack/PageForward case Button::PageBack: - // PREV_NEXT: BTN_UP = PageBack; NEXT_PREV: BTN_DOWN = PageBack - return SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT || - (prevNext ? SETTINGS.btnDoubleUp : SETTINGS.btnDoubleDown) != BA::BTN_DEFAULT; + return SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT; case Button::PageForward: - // PREV_NEXT: BTN_DOWN = PageForward; NEXT_PREV: BTN_UP = PageForward - return SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT || - (prevNext ? SETTINGS.btnDoubleDown : SETTINGS.btnDoubleUp) != BA::BTN_DEFAULT; + return SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT; case Button::Power: return SETTINGS.btnDoublePower != BA::BTN_DEFAULT; } diff --git a/src/ButtonEventManager.h b/src/ButtonEventManager.h index f32f8f00..59b7562d 100644 --- a/src/ButtonEventManager.h +++ b/src/ButtonEventManager.h @@ -54,10 +54,6 @@ class ButtonEventManager { static bool hasDoubleAction(Button button); private: - // Up and Down alias the same physical GPIO pins as PageBack/PageForward (via sideButtonLayout). - // Running separate FSMs for both would double-fire on every side button press. - // Up/Down are therefore excluded here; their configurable actions are resolved in main.cpp - // by treating a PageBack/PageForward event as the canonical side-button event. static constexpr int NUM_BUTTONS = 7; static constexpr Button ALL_BUTTONS[NUM_BUTTONS] = { Button::Back, Button::Confirm, Button::Left, Button::Right, Button::PageBack, Button::PageForward, Button::Power, diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 84d4f452..4f9932d6 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -151,7 +151,10 @@ bool CrossPointSettings::loadFromBinaryFile() { if (++settingsRead >= fileSettingsCount) break; readAndValidate(inputFile, frontButtonLayout, FRONT_BUTTON_LAYOUT_COUNT); if (++settingsRead >= fileSettingsCount) break; - readAndValidate(inputFile, sideButtonLayout, SIDE_BUTTON_LAYOUT_COUNT); + { + uint8_t ignored; + serialization::readPod(inputFile, ignored); + } // legacy sideButtonLayout field if (++settingsRead >= fileSettingsCount) break; readAndValidate(inputFile, fontFamily, FONT_FAMILY_COUNT); if (++settingsRead >= fileSettingsCount) break; diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index eedc674c..d81c8a4b 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -88,11 +88,6 @@ class CrossPointSettings { FRONT_BUTTON_HARDWARE_COUNT }; - // Side button layout options - // Default: Previous, Next - // Swapped: Next, Previous - enum SIDE_BUTTON_LAYOUT { PREV_NEXT = 0, NEXT_PREV = 1, SIDE_BUTTON_LAYOUT_COUNT }; - // Font family options enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, FONT_FAMILY_COUNT }; // Font size options @@ -208,7 +203,6 @@ class CrossPointSettings { uint8_t orientation = PORTRAIT; // 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; @@ -292,8 +286,6 @@ class CrossPointSettings { uint8_t btnShortConfirm = BTN_DEFAULT; uint8_t btnShortLeft = BTN_DEFAULT; uint8_t btnShortRight = BTN_DEFAULT; - uint8_t btnShortUp = BTN_DEFAULT; - uint8_t btnShortDown = BTN_DEFAULT; uint8_t btnShortPageBack = BTN_DEFAULT; uint8_t btnShortPageForward = BTN_DEFAULT; uint8_t btnShortPower = BTN_DEFAULT; @@ -303,8 +295,6 @@ class CrossPointSettings { uint8_t btnDoubleConfirm = BTN_DEFAULT; uint8_t btnDoubleLeft = BTN_DEFAULT; uint8_t btnDoubleRight = BTN_DEFAULT; - uint8_t btnDoubleUp = BTN_DEFAULT; - uint8_t btnDoubleDown = BTN_DEFAULT; uint8_t btnDoublePageBack = BTN_DEFAULT; uint8_t btnDoublePageForward = BTN_DEFAULT; uint8_t btnDoublePower = BTN_DEFAULT; @@ -314,8 +304,6 @@ class CrossPointSettings { uint8_t btnLongConfirm = BTN_DEFAULT; uint8_t btnLongLeft = BTN_DEFAULT; uint8_t btnLongRight = BTN_DEFAULT; - uint8_t btnLongUp = BTN_DEFAULT; - uint8_t btnLongDown = BTN_DEFAULT; uint8_t btnLongPageBack = BTN_DEFAULT; uint8_t btnLongPageForward = BTN_DEFAULT; uint8_t btnLongPower = BTN_DEFAULT; diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp index 467c0f75..6679990c 100644 --- a/src/MappedInputManager.cpp +++ b/src/MappedInputManager.cpp @@ -2,55 +2,27 @@ #include "CrossPointSettings.h" -namespace { -using ButtonIndex = uint8_t; - -struct SideLayoutMap { - ButtonIndex pageBack; - ButtonIndex pageForward; -}; - -// Order matches CrossPointSettings::SIDE_BUTTON_LAYOUT. -constexpr SideLayoutMap kSideLayouts[] = { - {HalGPIO::BTN_UP, HalGPIO::BTN_DOWN}, - {HalGPIO::BTN_DOWN, HalGPIO::BTN_UP}, -}; -} // namespace - bool MappedInputManager::mapButton(const Button button, bool (HalGPIO::*fn)(uint8_t) const) const { - const auto sideLayout = static_cast(SETTINGS.sideButtonLayout); - const auto& side = kSideLayouts[sideLayout]; - switch (button) { case Button::Back: - // Logical Back maps to user-configured front button. return (gpio.*fn)(SETTINGS.frontButtonBack); case Button::Confirm: - // Logical Confirm maps to user-configured front button. return (gpio.*fn)(SETTINGS.frontButtonConfirm); case Button::Left: - // Logical Left maps to user-configured front button. return (gpio.*fn)(SETTINGS.frontButtonLeft); case Button::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); + return (gpio.*fn)(HalGPIO::BTN_UP); case Button::PageForward: - // Reader page navigation uses side buttons and can be swapped via settings. - return (gpio.*fn)(side.pageForward); + return (gpio.*fn)(HalGPIO::BTN_DOWN); } - return false; } diff --git a/src/SettingsList.h b/src/SettingsList.h index e6bd2834..c833d4bc 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -131,9 +131,7 @@ inline const std::vector list = { .withSubcategory(StrId::STR_MENU_READER_TWEAKS), // --- Controls --- - SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, - {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS) - .withSubcategory(StrId::STR_MENU_BTN_PHYSICAL), + SettingInfo::Separator(StrId::STR_MENU_BTN_PHYSICAL), // --- Button Actions (short / double / long press per logical button) --- // All entries share the same ordered action-label list; the submenu groups them behind @@ -147,6 +145,7 @@ inline const std::vector list = { StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, StrId::STR_BTN_ACT_READER_MENU, \ StrId::STR_BTN_ACT_KOREADER_SYNC + SettingInfo::Separator(StrId::STR_MENU_BTN_ACTIONS), // Back button: short=exit reader, double=ignore, long=go home SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, {StrId::STR_BTN_DEF_EXIT_READER, BTN_ACT_OPTIONS}, "btnShortBack", StrId::STR_CAT_CONTROLS) @@ -162,9 +161,11 @@ inline const std::vector list = { {StrId::STR_BTN_DEF_READER_MENU, BTN_ACT_OPTIONS}, "btnShortConfirm", StrId::STR_CAT_CONTROLS) .withSubmenu(StrId::STR_BTN_CONFIRM), SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleConfirm, - {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleConfirm", StrId::STR_CAT_CONTROLS), + {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleConfirm", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_CONFIRM), SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongConfirm, - {StrId::STR_BTN_DEF_KOREADER_SYNC, BTN_ACT_OPTIONS}, "btnLongConfirm", StrId::STR_CAT_CONTROLS), + {StrId::STR_BTN_DEF_KOREADER_SYNC, BTN_ACT_OPTIONS}, "btnLongConfirm", StrId::STR_CAT_CONTROLS) + .withSubmenu(StrId::STR_BTN_CONFIRM), // Left button: short=previous page, double=ignore, long=chapter back SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortLeft, {StrId::STR_BTN_DEF_PREV_PAGE, BTN_ACT_OPTIONS}, "btnShortLeft", StrId::STR_CAT_CONTROLS) @@ -185,26 +186,6 @@ inline const std::vector list = { SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongRight, {StrId::STR_BTN_DEF_CHAPTER_FORWARD, BTN_ACT_OPTIONS}, "btnLongRight", StrId::STR_CAT_CONTROLS) .withSubmenu(StrId::STR_BTN_RIGHT), - // Up button: short=previous page, double=ignore, long=chapter back - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortUp, - {StrId::STR_BTN_DEF_PREV_PAGE, BTN_ACT_OPTIONS}, "btnShortUp", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_BTN_UP), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleUp, - {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleUp", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_BTN_UP), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongUp, - {StrId::STR_BTN_DEF_CHAPTER_BACK, BTN_ACT_OPTIONS}, "btnLongUp", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_BTN_UP), - // Down button: short=next page, double=ignore, long=chapter forward - SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortDown, - {StrId::STR_BTN_DEF_NEXT_PAGE, BTN_ACT_OPTIONS}, "btnShortDown", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_BTN_DOWN), - SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleDown, - {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleDown", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_BTN_DOWN), - SettingInfo::Enum(StrId::STR_BTN_LONG_PRESS, &CrossPointSettings::btnLongDown, - {StrId::STR_BTN_DEF_CHAPTER_FORWARD, BTN_ACT_OPTIONS}, "btnLongDown", StrId::STR_CAT_CONTROLS) - .withSubmenu(StrId::STR_BTN_DOWN), // Page Back button: short=previous page, double=ignore, long=chapter back SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortPageBack, {StrId::STR_BTN_DEF_PREV_PAGE, BTN_ACT_OPTIONS}, "btnShortPageBack", StrId::STR_CAT_CONTROLS) diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 01020f65..7a9253c2 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include "CrossPointSettings.h" @@ -277,6 +279,5 @@ void SettingsActivity::render(RenderLock&&) { const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirmLabel, tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - // Always use standard refresh for settings screen - renderer.displayBuffer(); + renderer.displayBuffer(gpio.deviceIsX3() ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH); } diff --git a/src/activities/settings/SettingsSubmenuActivity.cpp b/src/activities/settings/SettingsSubmenuActivity.cpp index 0fc68ff0..d6267d31 100644 --- a/src/activities/settings/SettingsSubmenuActivity.cpp +++ b/src/activities/settings/SettingsSubmenuActivity.cpp @@ -1,6 +1,8 @@ #include "SettingsSubmenuActivity.h" #include +#include +#include #include #include "CrossPointSettings.h" @@ -63,5 +65,5 @@ void SettingsSubmenuActivity::render(RenderLock&&) { const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - renderer.displayBuffer(); + renderer.displayBuffer(gpio.deviceIsX3() ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH); } diff --git a/src/main.cpp b/src/main.cpp index 15296b51..3a5d7d03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -387,87 +387,80 @@ void loop() { using B = MappedInputManager::Button; ButtonEventManager::ButtonEvent ev; while (buttonEventManager.consumeEvent(ev)) { - // Up/Down share physical pins with PageBack/PageForward via sideButtonLayout. - // ButtonEventManager only runs FSMs for PageBack/PageForward (not Up/Down) to avoid - // double-firing. Here we resolve Up/Down settings as aliases of the side-button events: - // with PREV_NEXT layout, PageBack = BTN_UP and PageForward = BTN_DOWN, so the - // btnShort/Double/LongUp/Down settings apply when PageBack/PageForward fires. - const bool prevNext = static_cast(SETTINGS.sideButtonLayout) == - CrossPointSettings::SIDE_BUTTON_LAYOUT::PREV_NEXT; auto actionFor = [&](B btn) -> uint8_t { - switch (ev.type) { - case ButtonEventManager::PressType::Short: - switch (btn) { - case B::Back: + switch (btn) { + case B::Back: + switch (ev.type) { + case ButtonEventManager::PressType::Short: return SETTINGS.btnShortBack; - case B::Confirm: - return SETTINGS.btnShortConfirm; - case B::Left: - return SETTINGS.btnShortLeft; - case B::Right: - return SETTINGS.btnShortRight; - case B::Up: - return BA::BTN_DEFAULT; // no dedicated FSM - case B::Down: - return BA::BTN_DEFAULT; // no dedicated FSM - case B::PageBack: - if (SETTINGS.btnShortPageBack != BA::BTN_DEFAULT) return SETTINGS.btnShortPageBack; - return prevNext ? SETTINGS.btnShortUp : SETTINGS.btnShortDown; - case B::PageForward: - if (SETTINGS.btnShortPageForward != BA::BTN_DEFAULT) return SETTINGS.btnShortPageForward; - return prevNext ? SETTINGS.btnShortDown : SETTINGS.btnShortUp; - case B::Power: - return SETTINGS.btnShortPower; - } - break; - case ButtonEventManager::PressType::Double: - switch (btn) { - case B::Back: + case ButtonEventManager::PressType::Double: return SETTINGS.btnDoubleBack; - case B::Confirm: - return SETTINGS.btnDoubleConfirm; - case B::Left: - return SETTINGS.btnDoubleLeft; - case B::Right: - return SETTINGS.btnDoubleRight; - case B::Up: - return BA::BTN_DEFAULT; // no dedicated FSM - case B::Down: - return BA::BTN_DEFAULT; // no dedicated FSM - case B::PageBack: - if (SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT) return SETTINGS.btnDoublePageBack; - return prevNext ? SETTINGS.btnDoubleUp : SETTINGS.btnDoubleDown; - case B::PageForward: - if (SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT) return SETTINGS.btnDoublePageForward; - return prevNext ? SETTINGS.btnDoubleDown : SETTINGS.btnDoubleUp; - case B::Power: - return SETTINGS.btnDoublePower; + case ButtonEventManager::PressType::Long: + return SETTINGS.btnLongBack; } break; - case ButtonEventManager::PressType::Long: - switch (btn) { - case B::Back: - return SETTINGS.btnLongBack; - case B::Confirm: + case B::Confirm: + switch (ev.type) { + case ButtonEventManager::PressType::Short: + return SETTINGS.btnShortConfirm; + case ButtonEventManager::PressType::Double: + return SETTINGS.btnDoubleConfirm; + case ButtonEventManager::PressType::Long: return SETTINGS.btnLongConfirm; - case B::Left: + } + break; + case B::Left: + switch (ev.type) { + case ButtonEventManager::PressType::Short: + return SETTINGS.btnShortLeft; + case ButtonEventManager::PressType::Double: + return SETTINGS.btnDoubleLeft; + case ButtonEventManager::PressType::Long: return SETTINGS.btnLongLeft; - case B::Right: + } + break; + case B::Right: + switch (ev.type) { + case ButtonEventManager::PressType::Short: + return SETTINGS.btnShortRight; + case ButtonEventManager::PressType::Double: + return SETTINGS.btnDoubleRight; + case ButtonEventManager::PressType::Long: return SETTINGS.btnLongRight; - case B::Up: - return BA::BTN_DEFAULT; // no dedicated FSM - case B::Down: - return BA::BTN_DEFAULT; // no dedicated FSM - case B::PageBack: - if (SETTINGS.btnLongPageBack != BA::BTN_DEFAULT) return SETTINGS.btnLongPageBack; - return prevNext ? SETTINGS.btnLongUp : SETTINGS.btnLongDown; - case B::PageForward: - if (SETTINGS.btnLongPageForward != BA::BTN_DEFAULT) return SETTINGS.btnLongPageForward; - return prevNext ? SETTINGS.btnLongDown : SETTINGS.btnLongUp; - case B::Power: + } + break; + case B::PageBack: + switch (ev.type) { + case ButtonEventManager::PressType::Short: + return SETTINGS.btnShortPageBack; + case ButtonEventManager::PressType::Double: + return SETTINGS.btnDoublePageBack; + case ButtonEventManager::PressType::Long: + return SETTINGS.btnLongPageBack; + } + break; + case B::PageForward: + switch (ev.type) { + case ButtonEventManager::PressType::Short: + return SETTINGS.btnShortPageForward; + case ButtonEventManager::PressType::Double: + return SETTINGS.btnDoublePageForward; + case ButtonEventManager::PressType::Long: + return SETTINGS.btnLongPageForward; + } + break; + case B::Power: + switch (ev.type) { + case ButtonEventManager::PressType::Short: + return SETTINGS.btnShortPower; + case ButtonEventManager::PressType::Double: + return SETTINGS.btnDoublePower; + case ButtonEventManager::PressType::Long: return SETTINGS.btnLongPower; } break; + default: + break; // Up/Down have no FSMs — ButtonEventManager never emits these } return BA::BTN_DEFAULT; }; From 812269057d8976a5a9ae22d735103067214d6a9a Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sun, 26 Apr 2026 17:20:16 +0200 Subject: [PATCH 4/5] X3 fixes --- src/SettingsList.h | 6 ++---- src/activities/settings/SettingInfo.h | 4 +++- src/activities/settings/SettingsActivity.cpp | 13 +++++++++++-- src/activities/settings/SettingsActivity.h | 1 + src/activities/settings/SettingsSubmenuActivity.cpp | 5 ++++- src/activities/settings/SettingsSubmenuActivity.h | 1 + 6 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/SettingsList.h b/src/SettingsList.h index c833d4bc..f106470d 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -130,9 +130,7 @@ inline const std::vector list = { "syntheticTocFallback", StrId::STR_CAT_READER) .withSubcategory(StrId::STR_MENU_READER_TWEAKS), - // --- Controls --- - SettingInfo::Separator(StrId::STR_MENU_BTN_PHYSICAL), - +// --- Controls --- // --- Button Actions (short / double / long press per logical button) --- // All entries share the same ordered action-label list; the submenu groups them behind // a single placeholder row in the device UI. @@ -145,10 +143,10 @@ inline const std::vector list = { StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, StrId::STR_BTN_ACT_READER_MENU, \ StrId::STR_BTN_ACT_KOREADER_SYNC - SettingInfo::Separator(StrId::STR_MENU_BTN_ACTIONS), // Back button: short=exit reader, double=ignore, long=go home SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, {StrId::STR_BTN_DEF_EXIT_READER, BTN_ACT_OPTIONS}, "btnShortBack", StrId::STR_CAT_CONTROLS) + .withSubcategory(StrId::STR_MENU_BTN_ACTIONS) .withSubmenu(StrId::STR_BTN_BACK), SettingInfo::Enum(StrId::STR_BTN_DOUBLE_PRESS, &CrossPointSettings::btnDoubleBack, {StrId::STR_BTN_DEF_IGNORE, BTN_ACT_OPTIONS}, "btnDoubleBack", StrId::STR_CAT_CONTROLS) diff --git a/src/activities/settings/SettingInfo.h b/src/activities/settings/SettingInfo.h index 77abbe13..489673ae 100644 --- a/src/activities/settings/SettingInfo.h +++ b/src/activities/settings/SettingInfo.h @@ -264,7 +264,9 @@ inline void SettingInfo::prepareSubmenus(std::vector& items, auto it = std::find_if(preparedSubmenus.begin(), preparedSubmenus.end(), [&item](const SubmenuData& d) { return d.id == item.submenu; }); if (it == preparedSubmenus.end()) { - preparedItems.push_back(SettingInfo::SubmenuEntry(item.submenu)); + auto placeholder = SettingInfo::SubmenuEntry(item.submenu); + placeholder.subcategory = item.subcategory; // inherit so addTo inserts the separator + preparedItems.push_back(std::move(placeholder)); preparedSubmenus.push_back({item.submenu, {}}); it = preparedSubmenus.end() - 1; } diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 7a9253c2..8f3726bb 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -23,6 +23,7 @@ bool SettingsActivity::isListItemSelectable(int settingIdx) const { void SettingsActivity::onEnter() { Activity::onEnter(); + needsHalfRefresh = true; // Build per-category vectors from the shared settings list. // addTo tracks the last subcategory per vector and automatically inserts a separator @@ -80,6 +81,8 @@ void SettingsActivity::onEnter() { // Device-only ACTION items — subcategory drives separator insertion automatically. controlsSettings.insert(controlsSettings.begin(), SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons)); + controlsSettings.insert(controlsSettings.begin(), SettingInfo::Separator(StrId::STR_MENU_BTN_PHYSICAL)); + lastControlsSub = StrId::STR_MENU_BTN_PHYSICAL; addToMoved(readerSettings, lastReaderSub, SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar)); @@ -217,11 +220,15 @@ void SettingsActivity::toggleCurrentSetting() { if (setting.type == SettingType::ACTION) { auto resultHandler = [this](const ActivityResult& result) { SETTINGS.saveToFile(); + needsHalfRefresh = true; const auto* menuResult = std::get_if(&result.data); if (menuResult && menuResult->action != -1) { auto activity = createActivityForAction(static_cast(menuResult->action), renderer, mappedInput); if (activity) { - startActivityForResult(std::move(activity), [this](const ActivityResult&) { SETTINGS.saveToFile(); }); + startActivityForResult(std::move(activity), [this](const ActivityResult&) { + SETTINGS.saveToFile(); + needsHalfRefresh = true; + }); } } }; @@ -279,5 +286,7 @@ void SettingsActivity::render(RenderLock&&) { const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirmLabel, tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - renderer.displayBuffer(gpio.deviceIsX3() ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH); + const bool halfRefresh = gpio.deviceIsX3() && needsHalfRefresh; + needsHalfRefresh = false; + renderer.displayBuffer(halfRefresh ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH); } diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index a84114b9..9514ae86 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -25,6 +25,7 @@ class SettingsActivity final : public Activity { static const StrId categoryNames[categoryCount]; std::vector submenuData; + bool needsHalfRefresh = false; void enterCategory(int categoryIndex); void toggleCurrentSetting(); diff --git a/src/activities/settings/SettingsSubmenuActivity.cpp b/src/activities/settings/SettingsSubmenuActivity.cpp index d6267d31..1dc4c5e8 100644 --- a/src/activities/settings/SettingsSubmenuActivity.cpp +++ b/src/activities/settings/SettingsSubmenuActivity.cpp @@ -13,6 +13,7 @@ void SettingsSubmenuActivity::onEnter() { Activity::onEnter(); + needsHalfRefresh = true; initMenuList(); requestUpdate(); } @@ -65,5 +66,7 @@ void SettingsSubmenuActivity::render(RenderLock&&) { const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - renderer.displayBuffer(gpio.deviceIsX3() ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH); + const bool halfRefresh = gpio.deviceIsX3() && needsHalfRefresh; + needsHalfRefresh = false; + renderer.displayBuffer(halfRefresh ? HalDisplay::HALF_REFRESH : HalDisplay::FAST_REFRESH); } diff --git a/src/activities/settings/SettingsSubmenuActivity.h b/src/activities/settings/SettingsSubmenuActivity.h index 32618941..91b0f4b6 100644 --- a/src/activities/settings/SettingsSubmenuActivity.h +++ b/src/activities/settings/SettingsSubmenuActivity.h @@ -12,6 +12,7 @@ class SettingsSubmenuActivity final : public MenuListActivity { StrId titleId; std::function itemValueStringOverride; + bool needsHalfRefresh = false; // MenuListActivity overrides void onEnter() override; From edf7ce1ae283cd57c237b7aad202f7a3faa8b2a6 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sun, 26 Apr 2026 17:57:26 +0200 Subject: [PATCH 5/5] Review comments --- src/CrossPointSettings.cpp | 4 +- src/CrossPointSettings.h | 2 +- src/activities/ActivityManager.cpp | 2 +- src/activities/reader/EpubReaderActivity.cpp | 61 ++++++++++---------- src/activities/reader/MdReaderActivity.cpp | 4 ++ src/activities/reader/ReaderUtils.h | 12 +++- src/activities/reader/TxtReaderActivity.cpp | 21 ++++--- src/activities/reader/XtcReaderActivity.cpp | 24 ++++++-- src/activities/settings/SettingsActivity.cpp | 1 - src/main.cpp | 2 + 10 files changed, 85 insertions(+), 48 deletions(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 4f9932d6..e6719399 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -184,8 +184,8 @@ bool CrossPointSettings::loadFromBinaryFile() { readAndValidate(inputFile, hideBatteryPercentage, HIDE_BATTERY_PERCENTAGE_COUNT); if (++settingsRead >= fileSettingsCount) break; { - uint8_t _unused; - serialization::readPod(inputFile, _unused); + uint8_t ignored; + serialization::readPod(inputFile, ignored); } // was longPressChapterSkip if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, hyphenationEnabled); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index d81c8a4b..75212422 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -313,7 +313,7 @@ class CrossPointSettings { // Get singleton instance static CrossPointSettings& getInstance() { return instance; } - uint16_t getPowerButtonDuration() const { return 400; } + static constexpr uint16_t getPowerButtonDuration() { return 400; } int getReaderFontId() const; // If count_only is true, returns the number of settings items that would be written. diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index 47b67a56..5fe41c90 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -390,7 +390,7 @@ bool ActivityManager::isReaderActivity() const { return currentActivity && curre bool ActivityManager::skipLoopDelay() const { return currentActivity && currentActivity->skipLoopDelay(); } void ActivityManager::dispatchButtonAction(const CrossPointSettings::BUTTON_ACTION action) { - if (currentActivity) { + if (currentActivity && currentActivity->isReaderActivity()) { currentActivity->onButtonAction(action); } } diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index d80e9729..96019f1b 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -1813,6 +1813,7 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION const int spineIdx = currentSpineIndex; const int tocIdx = section ? section->getTocIndexForPage(section->currentPage) : epub->getTocIndexForSpineIndex(currentSpineIndex); + ReaderUtils::enforceExitFullRefresh(renderer); startActivityForResult(std::make_unique(renderer, mappedInput, epub, epub->getPath(), spineIdx, tocIdx), [this](const ActivityResult& result) { @@ -1837,39 +1838,41 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION case BA::BTN_NEXT_SECTION: case BA::BTN_PREV_SECTION: { const bool forward = (action == BA::BTN_NEXT_SECTION); - RenderLock lock(*this); - if (section && section->pageCount > 0) { - const int curTocIndex = section->getTocIndexForPage(section->currentPage); - const int nextTocIndex = forward ? curTocIndex + 1 : curTocIndex - 1; - if (curTocIndex < 0) { + { + RenderLock lock(*this); + if (section && section->pageCount > 0) { + const int curTocIndex = section->getTocIndexForPage(section->currentPage); + const int nextTocIndex = forward ? curTocIndex + 1 : curTocIndex - 1; + if (curTocIndex < 0) { + nextPageNumber = 0; + currentSpineIndex = forward ? currentSpineIndex + 1 : currentSpineIndex - 1; + section.reset(); + } else if (nextTocIndex >= 0 && nextTocIndex < epub->getTocItemsCount()) { + const int newSpineIndex = epub->getSpineIndexForTocIndex(nextTocIndex); + if (newSpineIndex == currentSpineIndex) { + if (const auto resolvedPage = section->getPageForTocIndex(nextTocIndex)) { + section->currentPage = *resolvedPage; + } + } else { + pendingTocIndex = nextTocIndex; + nextPageNumber = 0; + currentSpineIndex = newSpineIndex; + section.reset(); + } + } else if (forward) { + nextPageNumber = 0; + currentSpineIndex = epub->getSpineItemsCount(); + section.reset(); + } else { + nextPageNumber = 0; + currentSpineIndex = epub->getTocItem(curTocIndex).spineIndex - 1; + section.reset(); + } + } else { nextPageNumber = 0; currentSpineIndex = forward ? currentSpineIndex + 1 : currentSpineIndex - 1; section.reset(); - } else if (nextTocIndex >= 0 && nextTocIndex < epub->getTocItemsCount()) { - const int newSpineIndex = epub->getSpineIndexForTocIndex(nextTocIndex); - if (newSpineIndex == currentSpineIndex) { - if (const auto resolvedPage = section->getPageForTocIndex(nextTocIndex)) { - section->currentPage = *resolvedPage; - } - } else { - pendingTocIndex = nextTocIndex; - nextPageNumber = 0; - currentSpineIndex = newSpineIndex; - section.reset(); - } - } else if (forward) { - nextPageNumber = 0; - currentSpineIndex = epub->getSpineItemsCount(); - section.reset(); - } else { - nextPageNumber = 0; - currentSpineIndex = epub->getTocItem(curTocIndex).spineIndex - 1; - section.reset(); } - } else { - nextPageNumber = 0; - currentSpineIndex = forward ? currentSpineIndex + 1 : currentSpineIndex - 1; - section.reset(); } requestUpdate(); break; diff --git a/src/activities/reader/MdReaderActivity.cpp b/src/activities/reader/MdReaderActivity.cpp index 98aaa51e..5310a938 100644 --- a/src/activities/reader/MdReaderActivity.cpp +++ b/src/activities/reader/MdReaderActivity.cpp @@ -897,6 +897,10 @@ void MdReaderActivity::savePageIndexCache() const { void MdReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION action) { using BA = CrossPointSettings::BUTTON_ACTION; auto clampPage = [this]() { + if (totalPages == 0) { + currentPage = 0; + return; + } if (currentPage < 0) currentPage = 0; if (currentPage >= totalPages) currentPage = totalPages - 1; }; diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index da5da0b5..1e326803 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -62,10 +62,16 @@ struct PageTurnResult { }; inline PageTurnResult detectPageTurn(const MappedInputManager& input) { + // Only treat wasReleased as a page turn when the button's short-press action is default. + // Non-default short-press actions are dispatched by the global dispatcher in main.cpp; + // counting wasReleased as well would double-fire the action. + using BA = CrossPointSettings::BUTTON_ACTION; const bool prev = - input.wasReleased(MappedInputManager::Button::PageBack) || input.wasReleased(MappedInputManager::Button::Left); - const bool next = input.wasReleased(MappedInputManager::Button::PageForward) || - input.wasReleased(MappedInputManager::Button::Right); + (SETTINGS.btnShortPageBack == BA::BTN_DEFAULT && input.wasReleased(MappedInputManager::Button::PageBack)) || + (SETTINGS.btnShortLeft == BA::BTN_DEFAULT && input.wasReleased(MappedInputManager::Button::Left)); + const bool next = + (SETTINGS.btnShortPageForward == BA::BTN_DEFAULT && input.wasReleased(MappedInputManager::Button::PageForward)) || + (SETTINGS.btnShortRight == BA::BTN_DEFAULT && input.wasReleased(MappedInputManager::Button::Right)); return {prev, next}; } diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 4e87e54d..14a88241 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -806,15 +806,22 @@ void TxtReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a bookmarkStore.toggle(0, static_cast(currentPage)); requestUpdate(); break; - case BA::BTN_NEXT_SECTION: - currentPage += 10; - clampPage(); - requestUpdate(); + case BA::BTN_OPEN_BOOKMARKS: + if (!bookmarkStore.isEmpty()) { + ReaderUtils::enforceExitFullRefresh(renderer); + startActivityForResult(std::make_unique(renderer, mappedInput, bookmarkStore), + [this](const ActivityResult& result) { + if (!result.isCancelled) { + const auto& starred = std::get(result.data); + currentPage = starred.pageNumber; + requestUpdate(); + } + }); + } break; + case BA::BTN_NEXT_SECTION: case BA::BTN_PREV_SECTION: - currentPage -= 10; - clampPage(); - requestUpdate(); + // TXT files have no headings/chapters; treat as unsupported (no-op). break; case BA::BTN_EXIT_READER: ReaderUtils::enforceExitFullRefresh(renderer); diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 14b4e832..d65bab38 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -460,12 +460,28 @@ void XtcReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION a requestUpdate(); break; case BA::BTN_NEXT_SECTION: - currentPage = (currentPage + 10 < pageCount) ? currentPage + 10 : pageCount - 1; - requestUpdate(); + if (xtc->hasChapters()) { + const auto& chapters = xtc->getChapters(); + for (const auto& ch : chapters) { + if (ch.startPage > currentPage) { + currentPage = ch.startPage; + requestUpdate(); + break; + } + } + } break; case BA::BTN_PREV_SECTION: - currentPage = (currentPage >= 10) ? currentPage - 10 : 0; - requestUpdate(); + if (xtc->hasChapters()) { + const auto& chapters = xtc->getChapters(); + for (int i = static_cast(chapters.size()) - 1; i >= 0; i--) { + if (chapters[i].startPage < currentPage) { + currentPage = chapters[i].startPage; + requestUpdate(); + break; + } + } + } break; case BA::BTN_EXIT_READER: ReaderUtils::enforceExitFullRefresh(renderer); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 8f3726bb..b8e3ffba 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -82,7 +82,6 @@ void SettingsActivity::onEnter() { controlsSettings.insert(controlsSettings.begin(), SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons)); controlsSettings.insert(controlsSettings.begin(), SettingInfo::Separator(StrId::STR_MENU_BTN_PHYSICAL)); - lastControlsSub = StrId::STR_MENU_BTN_PHYSICAL; addToMoved(readerSettings, lastReaderSub, SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar)); diff --git a/src/main.cpp b/src/main.cpp index 3a5d7d03..f857808e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -350,6 +350,8 @@ void loop() { // Track power button hold for sleep. We require a fresh press edge (wasPressed) // before starting to measure hold time, so that a hold carried over from boot // (wake-up press) is never misinterpreted as a "go to sleep" press. + // The power button long-press is not user-remappable, so this path always owns it. + // Sleep mapped to other buttons is handled by the dispatcher's BTN_SLEEP case below. static unsigned long powerHoldStart = 0; if (gpio.wasPressed(HalGPIO::BTN_POWER)) { powerHoldStart = millis();