From 7e15c9835f811b5df3baf9059c22e419b52e4dab Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 17 Feb 2026 01:22:49 -0500 Subject: [PATCH] feat: long-press Confirm to open Table of Contents directly Skip the reader menu when long-pressing Confirm (700ms) to jump straight to the chapter selection screen. Short press behavior (opening the menu) is unchanged. Extracts shared openChapterSelection() helper to eliminate duplicated construction across three call sites. Co-authored-by: Cursor --- src/activities/reader/EpubReaderActivity.cpp | 116 ++++++++---------- src/activities/reader/EpubReaderActivity.h | 8 +- .../EpubReaderChapterSelectionActivity.cpp | 12 +- .../EpubReaderChapterSelectionActivity.h | 5 +- 4 files changed, 70 insertions(+), 71 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 9f67bc50..041135d2 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -32,6 +32,7 @@ namespace { // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() constexpr unsigned long skipChapterMs = 700; constexpr unsigned long goHomeMs = 1000; +constexpr unsigned long longPressConfirmMs = 700; constexpr int statusBarMargin = 19; constexpr int progressBarMarginTop = 1; @@ -230,12 +231,27 @@ void EpubReaderActivity::loop() { !mappedInput.wasReleased(MappedInputManager::Button::Back); if (confirmCleared && backCleared) { skipNextButtonCheck = false; + ignoreNextConfirmRelease = false; } return; } - // Enter reader menu activity. + // Long press CONFIRM opens Table of Contents directly (skip menu) + if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && + mappedInput.getHeldTime() >= longPressConfirmMs) { + ignoreNextConfirmRelease = true; + if (epub && epub->getTocItemsCount() > 0) { + openChapterSelection(true); // skip the stale release from this long-press + } + return; + } + + // Short press CONFIRM opens reader menu if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + if (ignoreNextConfirmRelease) { + ignoreNextConfirmRelease = false; + return; + } const int currentPage = section ? section->currentPage + 1 : 0; const int totalPages = section ? section->pageCount : 0; float bookProgress = 0.0f; @@ -417,6 +433,39 @@ void EpubReaderActivity::jumpToPercent(int percent) { } } +void EpubReaderActivity::openChapterSelection(bool initialSkipRelease) { + const int currentP = section ? section->currentPage : 0; + const int totalP = section ? section->pageCount : 0; + const int spineIdx = currentSpineIndex; + const std::string path = epub->getPath(); + + enterNewActivity(new EpubReaderChapterSelectionActivity( + this->renderer, this->mappedInput, epub, path, spineIdx, currentP, totalP, + [this] { + exitActivity(); + requestUpdate(); + }, + [this](const int newSpineIndex) { + if (currentSpineIndex != newSpineIndex) { + currentSpineIndex = newSpineIndex; + nextPageNumber = 0; + section.reset(); + } + exitActivity(); + requestUpdate(); + }, + [this](const int newSpineIndex, const int newPage) { + if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { + currentSpineIndex = newSpineIndex; + nextPageNumber = newPage; + section.reset(); + } + exitActivity(); + requestUpdate(); + }, + initialSkipRelease)); +} + void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) { switch (action) { case EpubReaderMenuActivity::MenuAction::ADD_BOOKMARK: { @@ -512,36 +561,8 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction if (bookmarks.empty()) { // No bookmarks: fall back to Table of Contents if available, otherwise go back if (epub->getTocItemsCount() > 0) { - const int currentP = section ? section->currentPage : 0; - const int totalP = section ? section->pageCount : 0; - const int spineIdx = currentSpineIndex; - const std::string path = epub->getPath(); - exitActivity(); - enterNewActivity(new EpubReaderChapterSelectionActivity( - this->renderer, this->mappedInput, epub, path, spineIdx, currentP, totalP, - [this] { - exitActivity(); - requestUpdate(); - }, - [this](const int newSpineIndex) { - if (currentSpineIndex != newSpineIndex) { - currentSpineIndex = newSpineIndex; - nextPageNumber = 0; - section.reset(); - } - exitActivity(); - requestUpdate(); - }, - [this](const int newSpineIndex, const int newPage) { - if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { - currentSpineIndex = newSpineIndex; - nextPageNumber = newPage; - section.reset(); - } - exitActivity(); - requestUpdate(); - })); + openChapterSelection(); } // If no TOC either, just return to reader (menu already closed by callback) break; @@ -566,41 +587,8 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction break; } case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: { - // Calculate values BEFORE we start destroying things - const int currentP = section ? section->currentPage : 0; - const int totalP = section ? section->pageCount : 0; - const int spineIdx = currentSpineIndex; - const std::string path = epub->getPath(); - - // 1. Close the menu exitActivity(); - - // 2. Open the Chapter Selector - enterNewActivity(new EpubReaderChapterSelectionActivity( - this->renderer, this->mappedInput, epub, path, spineIdx, currentP, totalP, - [this] { - exitActivity(); - requestUpdate(); - }, - [this](const int newSpineIndex) { - if (currentSpineIndex != newSpineIndex) { - currentSpineIndex = newSpineIndex; - nextPageNumber = 0; - section.reset(); - } - exitActivity(); - requestUpdate(); - }, - [this](const int newSpineIndex, const int newPage) { - if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) { - currentSpineIndex = newSpineIndex; - nextPageNumber = newPage; - section.reset(); - } - exitActivity(); - requestUpdate(); - })); - + openChapterSelection(); break; } case EpubReaderMenuActivity::MenuAction::GO_TO_PERCENT: { diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 94ce2573..754f3756 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -22,8 +22,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity { float pendingSpineProgress = 0.0f; bool pendingSubactivityExit = false; // Defer subactivity exit to avoid use-after-free bool pendingGoHome = false; // Defer go home to avoid race condition with display task - bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit - volatile bool loadingSection = false; // True during the entire !section block (read from main loop) + bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit + bool ignoreNextConfirmRelease = false; // Suppress short-press after long-press Confirm + volatile bool loadingSection = false; // True during the entire !section block (read from main loop) const std::function onGoBack; const std::function onGoHome; @@ -33,6 +34,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity { void saveProgress(int spineIndex, int currentPage, int pageCount); // Jump to a percentage of the book (0-100), mapping it to spine and page. void jumpToPercent(int percent); + // Open the Table of Contents (chapter selection) as a subactivity. + // Pass initialSkipRelease=true when triggered by long-press to consume the stale release. + void openChapterSelection(bool initialSkipRelease = false); void onReaderMenuBack(uint8_t orientation, uint8_t fontSize); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); void applyOrientation(uint8_t orientation); diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 6089a7d2..dd1a320a 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -53,11 +53,15 @@ void EpubReaderChapterSelectionActivity::loop() { const int totalItems = getTotalItems(); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - const auto newSpineIndex = epub->getSpineIndexForTocIndex(selectorIndex); - if (newSpineIndex == -1) { - onGoBack(); + if (ignoreNextConfirmRelease) { + ignoreNextConfirmRelease = false; } else { - onSelectSpineIndex(newSpineIndex); + const auto newSpineIndex = epub->getSpineIndexForTocIndex(selectorIndex); + if (newSpineIndex == -1) { + onGoBack(); + } else { + onSelectSpineIndex(newSpineIndex); + } } } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoBack(); diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.h b/src/activities/reader/EpubReaderChapterSelectionActivity.h index 28a6f165..2eb8c61c 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.h +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.h @@ -14,6 +14,7 @@ class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity int currentPage = 0; int totalPagesInSpine = 0; int selectorIndex = 0; + bool ignoreNextConfirmRelease = false; const std::function onGoBack; const std::function onSelectSpineIndex; @@ -32,13 +33,15 @@ class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity const int currentSpineIndex, const int currentPage, const int totalPagesInSpine, const std::function& onGoBack, const std::function& onSelectSpineIndex, - const std::function& onSyncPosition) + const std::function& onSyncPosition, + bool initialSkipRelease = false) : ActivityWithSubactivity("EpubReaderChapterSelection", renderer, mappedInput), epub(epub), epubPath(epubPath), currentSpineIndex(currentSpineIndex), currentPage(currentPage), totalPagesInSpine(totalPagesInSpine), + ignoreNextConfirmRelease(initialSkipRelease), onGoBack(onGoBack), onSelectSpineIndex(onSelectSpineIndex), onSyncPosition(onSyncPosition) {}