diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index f6965f2f..af4da25b 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -256,8 +256,16 @@ void EpubReaderActivity::loop() { requestUpdate(); } - // Enter reader menu activity. - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + // Touch reader controls mirror the side page buttons (left third = back, right + // third = forward, press-and-hold = long-press behavior) and the Confirm + // button (center press-and-hold = open menu). The top-left Back corner is + // consumed by wasReleased(Back) earlier, so it never reaches here. No-op on + // Xteink / when the setting is off. wasTouchTap is idempotent within a frame, + // so reading it here and again below is safe. + const auto touch = ReaderUtils::detectTouchPageTurn(renderer); + + // Enter reader menu activity (Confirm release, or a center touch-and-hold). + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || ReaderUtils::isTouchMenuGesture(touch)) { if (ignoreNextConfirmRelease) { ignoreNextConfirmRelease = false; } else { @@ -338,12 +346,6 @@ void EpubReaderActivity::loop() { return; } - // Touch reader controls mirror the side page buttons (left third = back, - // right third = forward, press-and-hold = long-press behavior). The top-left - // Back corner is consumed by wasReleased(Back) earlier, so it never reaches - // here. No-op on Xteink / when the setting is off. - const auto touch = ReaderUtils::detectTouchPageTurn(renderer); - auto [prevTriggered, nextTriggered, fromTilt] = ReaderUtils::detectPageTurn(mappedInput); prevTriggered = prevTriggered || touch.prev; nextTriggered = nextTriggered || touch.next; diff --git a/src/activities/reader/ReaderUtils.h b/src/activities/reader/ReaderUtils.h index 22b94a44..cf12006f 100644 --- a/src/activities/reader/ReaderUtils.h +++ b/src/activities/reader/ReaderUtils.h @@ -14,6 +14,9 @@ constexpr unsigned long GO_HOME_MS = 1000; constexpr unsigned long SKIP_HOLD_MS = 700; constexpr unsigned long BOOKMARK_HOLD_MS = 400; constexpr unsigned long BOOKMARK_MESSAGE_DURATION_MS = 2500; +// Press-and-hold in the center touch zone (see detectTouchPageTurn) opens the +// reader menu, the touch analogue of releasing the Confirm button. +constexpr unsigned long TOUCH_MENU_HOLD_MS = 400; inline void applyOrientation(GfxRenderer& renderer, const uint8_t orientation) { switch (orientation) { @@ -64,19 +67,21 @@ inline PageTurnResult detectPageTurn(const MappedInputManager& input) { // Touch reader controls: a tap on the left third of the (oriented) screen turns // back a page, the right third turns forward, mirroring the side page buttons. -// heldMs carries the contact duration so callers can apply the same long-press -// behavior (chapter skip / orientation change) as the buttons. The center column -// is left for the menu/Back gesture handled by each reader. Gated off the Xteink -// devices (no touch) and behind the touchReaderControls setting; returns all-false -// otherwise, so non-touch readers pay a single branch. +// The center third opens the reader menu on press-and-hold (see center/heldMs; +// the menu/Back gesture is handled by each reader). heldMs carries the contact +// duration so callers can apply the same long-press behavior (chapter skip / +// orientation change) as the buttons. Gated off the Xteink devices (no touch) +// and behind the touchReaderControls setting; returns all-false otherwise, so +// non-touch readers pay a single branch. struct TouchPageTurn { bool prev; bool next; + bool center; unsigned long heldMs; }; inline TouchPageTurn detectTouchPageTurn(GfxRenderer& renderer) { - TouchPageTurn result{false, false, 0}; + TouchPageTurn result{false, false, false, 0}; if (gpio.isXteinkDevice() || !SETTINGS.touchReaderControls) { return result; } @@ -91,11 +96,19 @@ inline TouchPageTurn detectTouchPageTurn(GfxRenderer& renderer) { result.prev = true; } else if (lx >= 2 * third) { result.next = true; + } else { + result.center = true; } result.heldMs = gpio.lastTouchHeldMs(); return result; } +// True when the center zone was pressed and held long enough to open the reader +// menu (touch analogue of a Confirm release). +inline bool isTouchMenuGesture(const TouchPageTurn& touch) { + return touch.center && touch.heldMs >= TOUCH_MENU_HOLD_MS; +} + inline void displayWithRefreshCycle(const GfxRenderer& renderer, int& pagesUntilFullRefresh) { if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(HalDisplay::HALF_REFRESH); diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 7fd51ab3..d398f2e5 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -53,8 +53,14 @@ void XtcReaderActivity::onExit() { } void XtcReaderActivity::loop() { - // Enter chapter selection activity - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + // Touch reader controls mirror the side page buttons (left third = back, right + // third = forward, press-and-hold = chapter skip) and the Confirm button + // (center press-and-hold = chapter selection). No-op on Xteink / when the + // setting is off. + const auto touch = ReaderUtils::detectTouchPageTurn(renderer); + + // Enter chapter selection activity (Confirm release, or a center touch-and-hold). + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || ReaderUtils::isTouchMenuGesture(touch)) { if (xtc && xtc->hasChapters() && !xtc->getChapters().empty()) { startActivityForResult( std::make_unique(renderer, mappedInput, xtc, currentPage), @@ -79,11 +85,6 @@ void XtcReaderActivity::loop() { return; } - // Touch reader controls mirror the side page buttons (left third = back, - // right third = forward, press-and-hold = chapter skip). No-op on Xteink / - // when the setting is off. - const auto touch = ReaderUtils::detectTouchPageTurn(renderer); - auto [prevTriggered, nextTriggered, fromTilt] = ReaderUtils::detectPageTurn(mappedInput); prevTriggered = prevTriggered || touch.prev; nextTriggered = nextTriggered || touch.next;