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 <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-17 01:22:49 -05:00
parent 7b3de29c59
commit 7e15c9835f
4 changed files with 70 additions and 71 deletions

View File

@@ -32,6 +32,7 @@ namespace {
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
constexpr unsigned long skipChapterMs = 700; constexpr unsigned long skipChapterMs = 700;
constexpr unsigned long goHomeMs = 1000; constexpr unsigned long goHomeMs = 1000;
constexpr unsigned long longPressConfirmMs = 700;
constexpr int statusBarMargin = 19; constexpr int statusBarMargin = 19;
constexpr int progressBarMarginTop = 1; constexpr int progressBarMarginTop = 1;
@@ -230,12 +231,27 @@ void EpubReaderActivity::loop() {
!mappedInput.wasReleased(MappedInputManager::Button::Back); !mappedInput.wasReleased(MappedInputManager::Button::Back);
if (confirmCleared && backCleared) { if (confirmCleared && backCleared) {
skipNextButtonCheck = false; skipNextButtonCheck = false;
ignoreNextConfirmRelease = false;
} }
return; 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 (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (ignoreNextConfirmRelease) {
ignoreNextConfirmRelease = false;
return;
}
const int currentPage = section ? section->currentPage + 1 : 0; const int currentPage = section ? section->currentPage + 1 : 0;
const int totalPages = section ? section->pageCount : 0; const int totalPages = section ? section->pageCount : 0;
float bookProgress = 0.0f; 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) { void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) {
switch (action) { switch (action) {
case EpubReaderMenuActivity::MenuAction::ADD_BOOKMARK: { case EpubReaderMenuActivity::MenuAction::ADD_BOOKMARK: {
@@ -512,36 +561,8 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
if (bookmarks.empty()) { if (bookmarks.empty()) {
// No bookmarks: fall back to Table of Contents if available, otherwise go back // No bookmarks: fall back to Table of Contents if available, otherwise go back
if (epub->getTocItemsCount() > 0) { 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(); exitActivity();
enterNewActivity(new EpubReaderChapterSelectionActivity( openChapterSelection();
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();
}));
} }
// If no TOC either, just return to reader (menu already closed by callback) // If no TOC either, just return to reader (menu already closed by callback)
break; break;
@@ -566,41 +587,8 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
break; break;
} }
case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: { 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(); exitActivity();
openChapterSelection();
// 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();
}));
break; break;
} }
case EpubReaderMenuActivity::MenuAction::GO_TO_PERCENT: { case EpubReaderMenuActivity::MenuAction::GO_TO_PERCENT: {

View File

@@ -22,8 +22,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
float pendingSpineProgress = 0.0f; float pendingSpineProgress = 0.0f;
bool pendingSubactivityExit = false; // Defer subactivity exit to avoid use-after-free 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 pendingGoHome = false; // Defer go home to avoid race condition with display task
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit 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 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<void()> onGoBack; const std::function<void()> onGoBack;
const std::function<void()> onGoHome; const std::function<void()> onGoHome;
@@ -33,6 +34,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
void saveProgress(int spineIndex, int currentPage, int pageCount); void saveProgress(int spineIndex, int currentPage, int pageCount);
// Jump to a percentage of the book (0-100), mapping it to spine and page. // Jump to a percentage of the book (0-100), mapping it to spine and page.
void jumpToPercent(int percent); 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 onReaderMenuBack(uint8_t orientation, uint8_t fontSize);
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
void applyOrientation(uint8_t orientation); void applyOrientation(uint8_t orientation);

View File

@@ -53,11 +53,15 @@ void EpubReaderChapterSelectionActivity::loop() {
const int totalItems = getTotalItems(); const int totalItems = getTotalItems();
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
const auto newSpineIndex = epub->getSpineIndexForTocIndex(selectorIndex); if (ignoreNextConfirmRelease) {
if (newSpineIndex == -1) { ignoreNextConfirmRelease = false;
onGoBack();
} else { } else {
onSelectSpineIndex(newSpineIndex); const auto newSpineIndex = epub->getSpineIndexForTocIndex(selectorIndex);
if (newSpineIndex == -1) {
onGoBack();
} else {
onSelectSpineIndex(newSpineIndex);
}
} }
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
onGoBack(); onGoBack();

View File

@@ -14,6 +14,7 @@ class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity
int currentPage = 0; int currentPage = 0;
int totalPagesInSpine = 0; int totalPagesInSpine = 0;
int selectorIndex = 0; int selectorIndex = 0;
bool ignoreNextConfirmRelease = false;
const std::function<void()> onGoBack; const std::function<void()> onGoBack;
const std::function<void(int newSpineIndex)> onSelectSpineIndex; const std::function<void(int newSpineIndex)> onSelectSpineIndex;
@@ -32,13 +33,15 @@ class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity
const int currentSpineIndex, const int currentPage, const int currentSpineIndex, const int currentPage,
const int totalPagesInSpine, const std::function<void()>& onGoBack, const int totalPagesInSpine, const std::function<void()>& onGoBack,
const std::function<void(int newSpineIndex)>& onSelectSpineIndex, const std::function<void(int newSpineIndex)>& onSelectSpineIndex,
const std::function<void(int newSpineIndex, int newPage)>& onSyncPosition) const std::function<void(int newSpineIndex, int newPage)>& onSyncPosition,
bool initialSkipRelease = false)
: ActivityWithSubactivity("EpubReaderChapterSelection", renderer, mappedInput), : ActivityWithSubactivity("EpubReaderChapterSelection", renderer, mappedInput),
epub(epub), epub(epub),
epubPath(epubPath), epubPath(epubPath),
currentSpineIndex(currentSpineIndex), currentSpineIndex(currentSpineIndex),
currentPage(currentPage), currentPage(currentPage),
totalPagesInSpine(totalPagesInSpine), totalPagesInSpine(totalPagesInSpine),
ignoreNextConfirmRelease(initialSkipRelease),
onGoBack(onGoBack), onGoBack(onGoBack),
onSelectSpineIndex(onSelectSpineIndex), onSelectSpineIndex(onSelectSpineIndex),
onSyncPosition(onSyncPosition) {} onSyncPosition(onSyncPosition) {}