feat: add TOC-aware navigation to EpubReaderActivity
Long-press chapter skip now walks by TOC entries instead of spine indices, enabling finer navigation in books with multi-chapter spines. Status bar chapter title now uses section-level getTocIndexForPage() for accurate subchapter display. Chapter selection passes tocIndex back so the reader can jump directly to the right page within a spine. Add pendingTocIndex to EpubReaderActivity for deferred cross-spine TOC navigation, resolved after the target section loads. Ported from upstream PRs #1143 and #1172, adapted to mod architecture. Made-with: Cursor
This commit is contained in:
@@ -26,6 +26,7 @@ struct MenuResult {
|
|||||||
|
|
||||||
struct ChapterResult {
|
struct ChapterResult {
|
||||||
int spineIndex = 0;
|
int spineIndex = 0;
|
||||||
|
int tocIndex = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PercentResult {
|
struct PercentResult {
|
||||||
|
|||||||
@@ -326,13 +326,38 @@ void EpubReaderActivity::loop() {
|
|||||||
|
|
||||||
if (skipChapter) {
|
if (skipChapter) {
|
||||||
lastPageTurnTime = millis();
|
lastPageTurnTime = millis();
|
||||||
// We don't want to delete the section mid-render, so grab the semaphore
|
|
||||||
{
|
int currentTocIdx = section ? section->getTocIndexForPage(section->currentPage)
|
||||||
|
: epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||||
|
const int nextTocIdx = nextTriggered ? currentTocIdx + 1 : currentTocIdx - 1;
|
||||||
|
|
||||||
|
if (nextTocIdx >= 0 && nextTocIdx < epub->getTocItemsCount()) {
|
||||||
|
const int targetSpine = epub->getSpineIndexForTocIndex(nextTocIdx);
|
||||||
|
if (targetSpine >= 0) {
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
|
if (targetSpine == currentSpineIndex && section) {
|
||||||
|
if (auto page = section->getPageForTocIndex(nextTocIdx)) {
|
||||||
|
section->currentPage = *page;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentSpineIndex = targetSpine;
|
||||||
|
pendingTocIndex = nextTocIdx;
|
||||||
nextPageNumber = 0;
|
nextPageNumber = 0;
|
||||||
currentSpineIndex = nextTriggered ? currentSpineIndex + 1 : currentSpineIndex - 1;
|
|
||||||
section.reset();
|
section.reset();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else if (nextTocIdx < 0) {
|
||||||
|
RenderLock lock(*this);
|
||||||
|
currentSpineIndex = 0;
|
||||||
|
nextPageNumber = 0;
|
||||||
|
section.reset();
|
||||||
|
} else {
|
||||||
|
RenderLock lock(*this);
|
||||||
|
currentSpineIndex = epub->getSpineItemsCount();
|
||||||
|
nextPageNumber = 0;
|
||||||
|
section.reset();
|
||||||
|
}
|
||||||
|
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -418,15 +443,24 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
|||||||
case EpubReaderMenuActivity::MenuAction::TABLE_OF_CONTENTS:
|
case EpubReaderMenuActivity::MenuAction::TABLE_OF_CONTENTS:
|
||||||
case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: {
|
case EpubReaderMenuActivity::MenuAction::SELECT_CHAPTER: {
|
||||||
const int spineIdx = currentSpineIndex;
|
const int spineIdx = currentSpineIndex;
|
||||||
|
const int tocIdx = section ? section->getTocIndexForPage(section->currentPage)
|
||||||
|
: epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||||
const std::string path = epub->getPath();
|
const std::string path = epub->getPath();
|
||||||
const bool consumeRelease = ignoreNextConfirmRelease;
|
const bool consumeRelease = ignoreNextConfirmRelease;
|
||||||
startActivityForResult(
|
startActivityForResult(
|
||||||
std::make_unique<EpubReaderChapterSelectionActivity>(renderer, mappedInput, epub, path, spineIdx,
|
std::make_unique<EpubReaderChapterSelectionActivity>(renderer, mappedInput, epub, path, spineIdx, tocIdx,
|
||||||
consumeRelease),
|
consumeRelease),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
if (!result.isCancelled && currentSpineIndex != std::get<ChapterResult>(result.data).spineIndex) {
|
if (result.isCancelled) return;
|
||||||
|
const auto& ch = std::get<ChapterResult>(result.data);
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
currentSpineIndex = std::get<ChapterResult>(result.data).spineIndex;
|
if (ch.spineIndex == currentSpineIndex && section && ch.tocIndex >= 0) {
|
||||||
|
if (auto page = section->getPageForTocIndex(ch.tocIndex)) {
|
||||||
|
section->currentPage = *page;
|
||||||
|
}
|
||||||
|
} else if (ch.spineIndex != currentSpineIndex) {
|
||||||
|
currentSpineIndex = ch.spineIndex;
|
||||||
|
pendingTocIndex = ch.tocIndex;
|
||||||
nextPageNumber = 0;
|
nextPageNumber = 0;
|
||||||
section.reset();
|
section.reset();
|
||||||
}
|
}
|
||||||
@@ -939,6 +973,14 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
section->currentPage = nextPageNumber;
|
section->currentPage = nextPageNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pendingTocIndex >= 0) {
|
||||||
|
if (auto page = section->getPageForTocIndex(pendingTocIndex)) {
|
||||||
|
section->currentPage = *page;
|
||||||
|
LOG_DBG("ERS", "Resolved TOC index %d to page %d", pendingTocIndex, *page);
|
||||||
|
}
|
||||||
|
pendingTocIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pendingAnchor.empty()) {
|
if (!pendingAnchor.empty()) {
|
||||||
if (const auto page = section->getPageForAnchor(pendingAnchor)) {
|
if (const auto page = section->getPageForAnchor(pendingAnchor)) {
|
||||||
section->currentPage = *page;
|
section->currentPage = *page;
|
||||||
@@ -1142,7 +1184,8 @@ void EpubReaderActivity::renderStatusBar() const {
|
|||||||
|
|
||||||
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) {
|
} else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) {
|
||||||
title = tr(STR_UNNAMED);
|
title = tr(STR_UNNAMED);
|
||||||
const int tocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
const int tocIndex = section ? section->getTocIndexForPage(section->currentPage)
|
||||||
|
: epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||||
if (tocIndex != -1) {
|
if (tocIndex != -1) {
|
||||||
const auto tocItem = epub->getTocItem(tocIndex);
|
const auto tocItem = epub->getTocItem(tocIndex);
|
||||||
title = tocItem.title;
|
title = tocItem.title;
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ class EpubReaderActivity final : public Activity {
|
|||||||
// Set when navigating to a footnote href with a fragment (e.g. #note1).
|
// Set when navigating to a footnote href with a fragment (e.g. #note1).
|
||||||
// Cleared on the next render after the new section loads and resolves it to a page.
|
// Cleared on the next render after the new section loads and resolves it to a page.
|
||||||
std::string pendingAnchor;
|
std::string pendingAnchor;
|
||||||
|
// Set when navigating to a specific TOC entry (e.g. from chapter selection or chapter skip).
|
||||||
|
// Resolved to a page number after the target section loads.
|
||||||
|
int pendingTocIndex = -1;
|
||||||
int pagesUntilFullRefresh = 0;
|
int pagesUntilFullRefresh = 0;
|
||||||
int cachedSpineIndex = 0;
|
int cachedSpineIndex = 0;
|
||||||
int cachedChapterTotalPageCount = 0;
|
int cachedChapterTotalPageCount = 0;
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ void EpubReaderChapterSelectionActivity::onEnter() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectorIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
selectorIndex = (currentTocIndex >= 0) ? currentTocIndex : epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||||
if (selectorIndex == -1) {
|
if (selectorIndex < 0) {
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ void EpubReaderChapterSelectionActivity::loop() {
|
|||||||
setResult(std::move(result));
|
setResult(std::move(result));
|
||||||
finish();
|
finish();
|
||||||
} else {
|
} else {
|
||||||
setResult(ChapterResult{newSpineIndex});
|
setResult(ChapterResult{newSpineIndex, selectorIndex});
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class EpubReaderChapterSelectionActivity final : public Activity {
|
|||||||
std::string epubPath;
|
std::string epubPath;
|
||||||
ButtonNavigator buttonNavigator;
|
ButtonNavigator buttonNavigator;
|
||||||
int currentSpineIndex = 0;
|
int currentSpineIndex = 0;
|
||||||
|
int currentTocIndex = -1;
|
||||||
int selectorIndex = 0;
|
int selectorIndex = 0;
|
||||||
bool ignoreNextConfirmRelease = false;
|
bool ignoreNextConfirmRelease = false;
|
||||||
|
|
||||||
@@ -24,11 +25,13 @@ class EpubReaderChapterSelectionActivity final : public Activity {
|
|||||||
public:
|
public:
|
||||||
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::shared_ptr<Epub>& epub, const std::string& epubPath,
|
const std::shared_ptr<Epub>& epub, const std::string& epubPath,
|
||||||
const int currentSpineIndex, bool consumeFirstRelease = false)
|
const int currentSpineIndex, const int currentTocIndex = -1,
|
||||||
|
bool consumeFirstRelease = false)
|
||||||
: Activity("EpubReaderChapterSelection", renderer, mappedInput),
|
: Activity("EpubReaderChapterSelection", renderer, mappedInput),
|
||||||
epub(epub),
|
epub(epub),
|
||||||
epubPath(epubPath),
|
epubPath(epubPath),
|
||||||
currentSpineIndex(currentSpineIndex),
|
currentSpineIndex(currentSpineIndex),
|
||||||
|
currentTocIndex(currentTocIndex),
|
||||||
ignoreNextConfirmRelease(consumeFirstRelease) {}
|
ignoreNextConfirmRelease(consumeFirstRelease) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
|
|||||||
Reference in New Issue
Block a user