mod: Phase 2b - adapt HomeActivity, EpubReaderMenuActivity, EpubReaderActivity

HomeActivity: Add mod features on top of upstream ActivityManager pattern:
- Multi-server OPDS support (OpdsServerStore instead of single URL)
- Long-press recent book for BookManageMenuActivity
- Long-press Browse Files to open archive folder
- Placeholder cover generation for books without covers
- startActivityForResult pattern for manage menu

EpubReaderMenuActivity: Replace upstream menu items with mod menu:
- Add/Remove Bookmark, Lookup Word, Go to Bookmark, Lookup History
- Table of Contents, Toggle Orientation, Toggle Font Size
- Close Book, Delete Dictionary Cache
- Pass isBookmarked and currentFontSize to constructor
- Show current orientation/font size value inline

EpubReaderActivity: Add mod action handlers:
- Bookmark add/remove via BookmarkStore
- Go to Bookmark via EpubReaderBookmarkSelectionActivity
- Dictionary word lookup via DictionaryWordSelectActivity
- Lookup history via LookedUpWordsActivity
- Delete dictionary cache
- Font size toggle with section re-layout
- Close Book action

ActivityResult: Add fontSize field to MenuResult
Made-with: Cursor
This commit is contained in:
cottongin
2026-03-07 15:38:53 -05:00
parent 66a754dabd
commit bd2cea8b8d
8 changed files with 1774 additions and 74 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@ struct MenuResult {
int action = -1; int action = -1;
uint8_t orientation = 0; uint8_t orientation = 0;
uint8_t pageTurnOption = 0; uint8_t pageTurnOption = 0;
uint8_t fontSize = 0;
}; };
struct ChapterResult { struct ChapterResult {

View File

@@ -6,25 +6,32 @@
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HalStorage.h> #include <HalStorage.h>
#include <I18n.h> #include <I18n.h>
#include <PlaceholderCoverGenerator.h>
#include <Utf8.h> #include <Utf8.h>
#include <Xtc.h> #include <Xtc.h>
#include <cstdio>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include "BookManageMenuActivity.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "OpdsServerStore.h"
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "activities/ActivityResult.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
#include "util/BookManager.h"
#include "util/StringUtils.h"
int HomeActivity::getMenuItemCount() const { int HomeActivity::getMenuItemCount() const {
int count = 4; // File Browser, Recents, File transfer, Settings int count = 4; // File Browser, Recents, File transfer, Settings
if (!recentBooks.empty()) { if (!recentBooks.empty()) {
count += recentBooks.size(); count += recentBooks.size();
} }
if (hasOpdsUrl) { if (hasOpdsServers) {
count++; count++;
} }
return count; return count;
@@ -59,45 +66,54 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
for (RecentBook& book : recentBooks) { for (RecentBook& book : recentBooks) {
if (!book.coverBmpPath.empty()) { if (!book.coverBmpPath.empty()) {
std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight); std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight);
if (!Storage.exists(coverPath.c_str())) { if (!Epub::isValidThumbnailBmp(coverPath)) {
// If epub, try to load the metadata for title/author and cover if (!showingLoading) {
if (FsHelpers::hasEpubExtension(book.path)) { showingLoading = true;
Epub epub(book.path, "/.crosspoint"); popupRect = GUI.drawPopup(renderer, tr(STR_LOADING));
// Skip loading css since we only need metadata here }
epub.load(false, true); GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
// Try to generate thumbnail image for Continue Reading card bool success = false;
if (!showingLoading) {
showingLoading = true; if (StringUtils::checkFileExtension(book.path, ".epub")) {
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP)); Epub epub(book.path, "/.crosspoint");
if (!epub.load(false, true)) {
epub.load(true, true);
} }
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size())); success = epub.generateThumbBmp(coverHeight);
bool success = epub.generateThumbBmp(coverHeight); if (success) {
if (!success) { const std::string thumbPath = epub.getThumbBmpPath(coverHeight);
RECENT_BOOKS.updateBook(book.path, book.title, book.author, ""); RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
book.coverBmpPath = ""; book.coverBmpPath = thumbPath;
} else {
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
success = PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
if (!success) {
epub.generateInvalidFormatThumbBmp(coverHeight);
}
} }
coverRendered = false; } else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
requestUpdate(); StringUtils::checkFileExtension(book.path, ".xtc")) {
} else if (FsHelpers::hasXtcExtension(book.path)) {
// Handle XTC file
Xtc xtc(book.path, "/.crosspoint"); Xtc xtc(book.path, "/.crosspoint");
if (xtc.load()) { if (xtc.load()) {
// Try to generate thumbnail image for Continue Reading card success = xtc.generateThumbBmp(coverHeight);
if (!showingLoading) { if (success) {
showingLoading = true; const std::string thumbPath = xtc.getThumbBmpPath(coverHeight);
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP)); RECENT_BOOKS.updateBook(book.path, book.title, book.author, thumbPath);
book.coverBmpPath = thumbPath;
} }
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
bool success = xtc.generateThumbBmp(coverHeight);
if (!success) {
RECENT_BOOKS.updateBook(book.path, book.title, book.author, "");
book.coverBmpPath = "";
}
coverRendered = false;
requestUpdate();
} }
if (!success) {
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
}
} else {
const int thumbWidth = static_cast<int>(coverHeight * 0.6);
PlaceholderCoverGenerator::generate(coverPath, book.title, book.author, thumbWidth, coverHeight);
} }
coverRendered = false;
requestUpdate();
} }
} }
progress++; progress++;
@@ -110,8 +126,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
void HomeActivity::onEnter() { void HomeActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
// Check if OPDS browser URL is configured hasOpdsServers = OPDS_STORE.hasServers();
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
selectorIndex = 0; selectorIndex = 0;
@@ -184,17 +199,37 @@ void HomeActivity::loop() {
requestUpdate(); requestUpdate();
}); });
// Long-press Confirm: manage menu for recent books, or browse archive for Browse Files
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= LONG_PRESS_MS &&
!ignoreNextConfirmRelease) {
if (selectorIndex < static_cast<int>(recentBooks.size())) {
ignoreNextConfirmRelease = true;
openManageMenu(recentBooks[selectorIndex].path);
return;
}
const int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size());
if (menuSelectedIndex == 0) {
ignoreNextConfirmRelease = true;
activityManager.goToFileBrowser("/.archive");
return;
}
}
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
// Calculate dynamic indices based on which options are available if (ignoreNextConfirmRelease) {
ignoreNextConfirmRelease = false;
return;
}
int idx = 0; int idx = 0;
int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size()); int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size());
const int fileBrowserIdx = idx++; const int fileBrowserIdx = idx++;
const int recentsIdx = idx++; const int recentsIdx = idx++;
const int opdsLibraryIdx = hasOpdsUrl ? idx++ : -1; const int opdsLibraryIdx = hasOpdsServers ? idx++ : -1;
const int fileTransferIdx = idx++; const int fileTransferIdx = idx++;
const int settingsIdx = idx; const int settingsIdx = idx;
if (selectorIndex < recentBooks.size()) { if (selectorIndex < static_cast<int>(recentBooks.size())) {
onSelectBook(recentBooks[selectorIndex].path); onSelectBook(recentBooks[selectorIndex].path);
} else if (menuSelectedIndex == fileBrowserIdx) { } else if (menuSelectedIndex == fileBrowserIdx) {
onFileBrowserOpen(); onFileBrowserOpen();
@@ -229,7 +264,7 @@ void HomeActivity::render(RenderLock&&) {
tr(STR_SETTINGS_TITLE)}; tr(STR_SETTINGS_TITLE)};
std::vector<UIIcon> menuIcons = {Folder, Recent, Transfer, Settings}; std::vector<UIIcon> menuIcons = {Folder, Recent, Transfer, Settings};
if (hasOpdsUrl) { if (hasOpdsServers) {
// Insert OPDS Browser after File Browser // Insert OPDS Browser after File Browser
menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER)); menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER));
menuIcons.insert(menuIcons.begin() + 2, Library); menuIcons.insert(menuIcons.begin() + 2, Library);
@@ -269,3 +304,52 @@ void HomeActivity::onSettingsOpen() { activityManager.goToSettings(); }
void HomeActivity::onFileTransferOpen() { activityManager.goToFileTransfer(); } void HomeActivity::onFileTransferOpen() { activityManager.goToFileTransfer(); }
void HomeActivity::onOpdsBrowserOpen() { activityManager.goToBrowser(); } void HomeActivity::onOpdsBrowserOpen() { activityManager.goToBrowser(); }
void HomeActivity::openManageMenu(const std::string& bookPath) {
const bool isArchived = BookManager::isArchived(bookPath);
const std::string capturedPath = bookPath;
startActivityForResult(
std::make_unique<BookManageMenuActivity>(renderer, mappedInput, capturedPath, isArchived, true),
[this, capturedPath](const ActivityResult& result) {
if (result.isCancelled) {
requestUpdate();
return;
}
const auto& menuResult = std::get<MenuResult>(result.data);
auto action = static_cast<BookManageMenuActivity::Action>(menuResult.action);
bool success = false;
switch (action) {
case BookManageMenuActivity::Action::ARCHIVE:
success = BookManager::archiveBook(capturedPath);
break;
case BookManageMenuActivity::Action::UNARCHIVE:
success = BookManager::unarchiveBook(capturedPath);
break;
case BookManageMenuActivity::Action::DELETE:
success = BookManager::deleteBook(capturedPath);
break;
case BookManageMenuActivity::Action::DELETE_CACHE:
success = BookManager::deleteBookCache(capturedPath);
break;
case BookManageMenuActivity::Action::REINDEX:
success = BookManager::reindexBook(capturedPath, false);
break;
case BookManageMenuActivity::Action::REINDEX_FULL:
success = BookManager::reindexBook(capturedPath, true);
break;
}
{
RenderLock lock(*this);
GUI.drawPopup(renderer, success ? tr(STR_DONE) : tr(STR_ACTION_FAILED));
}
requestUpdateAndWait();
recentBooks.clear();
recentsLoaded = false;
recentsLoading = false;
coverRendered = false;
freeCoverBuffer();
selectorIndex = 0;
firstRenderDone = false;
requestUpdate();
});
}

View File

@@ -15,22 +15,27 @@ class HomeActivity final : public Activity {
bool recentsLoading = false; bool recentsLoading = false;
bool recentsLoaded = false; bool recentsLoaded = false;
bool firstRenderDone = false; bool firstRenderDone = false;
bool hasOpdsUrl = false; bool hasOpdsServers = false;
bool coverRendered = false; // Track if cover has been rendered once bool coverRendered = false;
bool coverBufferStored = false; // Track if cover buffer is stored bool coverBufferStored = false;
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image uint8_t* coverBuffer = nullptr;
std::vector<RecentBook> recentBooks; std::vector<RecentBook> recentBooks;
bool ignoreNextConfirmRelease = false;
static constexpr unsigned long LONG_PRESS_MS = 700;
void onSelectBook(const std::string& path); void onSelectBook(const std::string& path);
void onFileBrowserOpen(); void onFileBrowserOpen();
void onRecentsOpen(); void onRecentsOpen();
void onSettingsOpen(); void onSettingsOpen();
void onFileTransferOpen(); void onFileTransferOpen();
void onOpdsBrowserOpen(); void onOpdsBrowserOpen();
void openManageMenu(const std::string& bookPath);
int getMenuItemCount() const; int getMenuItemCount() const;
bool storeCoverBuffer(); // Store frame buffer for cover image bool storeCoverBuffer();
bool restoreCoverBuffer(); // Restore frame buffer from stored cover bool restoreCoverBuffer();
void freeCoverBuffer(); // Free the stored cover buffer void freeCoverBuffer();
void loadRecentBooks(int maxBooks); void loadRecentBooks(int maxBooks);
void loadRecentCovers(int coverHeight); void loadRecentCovers(int coverHeight);

View File

@@ -10,16 +10,21 @@
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "DictionaryWordSelectActivity.h"
#include "EpubReaderBookmarkSelectionActivity.h"
#include "EpubReaderChapterSelectionActivity.h" #include "EpubReaderChapterSelectionActivity.h"
#include "EpubReaderFootnotesActivity.h" #include "EpubReaderFootnotesActivity.h"
#include "EpubReaderPercentSelectionActivity.h" #include "EpubReaderPercentSelectionActivity.h"
#include "KOReaderCredentialStore.h" #include "KOReaderCredentialStore.h"
#include "KOReaderSyncActivity.h" #include "KOReaderSyncActivity.h"
#include "LookedUpWordsActivity.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "QrDisplayActivity.h" #include "QrDisplayActivity.h"
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
#include "util/BookmarkStore.h"
#include "util/Dictionary.h"
#include "util/ScreenshotUtil.h" #include "util/ScreenshotUtil.h"
namespace { namespace {
@@ -164,13 +169,16 @@ void EpubReaderActivity::loop() {
bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f; bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f;
} }
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f)); const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
const bool isBookmarked =
section ? BookmarkStore::hasBookmark(epub->getCachePath(), currentSpineIndex, section->currentPage) : false;
startActivityForResult(std::make_unique<EpubReaderMenuActivity>( startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
SETTINGS.orientation, !currentPageFootnotes.empty()), SETTINGS.orientation, !currentPageFootnotes.empty(), isBookmarked, SETTINGS.fontSize),
[this](const ActivityResult& result) { [this](const ActivityResult& result) {
// Always apply orientation change even if the menu was cancelled // Always apply orientation and font size even if the menu was cancelled
const auto& menu = std::get<MenuResult>(result.data); const auto& menu = std::get<MenuResult>(result.data);
applyOrientation(menu.orientation); applyOrientation(menu.orientation);
applyFontSize(menu.fontSize);
toggleAutoPageTurn(menu.pageTurnOption); toggleAutoPageTurn(menu.pageTurnOption);
if (!result.isCancelled) { if (!result.isCancelled) {
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action)); onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
@@ -313,6 +321,7 @@ void EpubReaderActivity::jumpToPercent(int percent) {
void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) { void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action) {
switch (action) { switch (action) {
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 std::string path = epub->getPath(); const std::string path = epub->getPath();
@@ -432,6 +441,79 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
} }
break; break;
} }
case EpubReaderMenuActivity::MenuAction::CLOSE_BOOK:
onGoHome();
return;
case EpubReaderMenuActivity::MenuAction::ADD_BOOKMARK: {
if (section && BookmarkStore::addBookmark(epub->getCachePath(), currentSpineIndex, section->currentPage, "")) {
requestUpdate();
}
break;
}
case EpubReaderMenuActivity::MenuAction::REMOVE_BOOKMARK: {
if (section && BookmarkStore::removeBookmark(epub->getCachePath(), currentSpineIndex, section->currentPage)) {
requestUpdate();
}
break;
}
case EpubReaderMenuActivity::MenuAction::GO_TO_BOOKMARK: {
auto bookmarks = BookmarkStore::load(epub->getCachePath());
startActivityForResult(
std::make_unique<EpubReaderBookmarkSelectionActivity>(renderer, mappedInput, epub, std::move(bookmarks),
epub->getCachePath()),
[this](const ActivityResult& result) {
if (!result.isCancelled) {
const auto& sync = std::get<SyncResult>(result.data);
if (currentSpineIndex != sync.spineIndex || (section && section->currentPage != sync.page)) {
RenderLock lock(*this);
currentSpineIndex = sync.spineIndex;
nextPageNumber = sync.page;
section.reset();
}
}
});
break;
}
case EpubReaderMenuActivity::MenuAction::LOOKUP_WORD: {
if (!section || !Dictionary::cacheExists()) {
requestUpdate();
break;
}
auto p = section->loadPageFromSectionFile();
if (!p) {
requestUpdate();
break;
}
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
&orientedMarginLeft);
orientedMarginTop += SETTINGS.screenMargin;
orientedMarginLeft += SETTINGS.screenMargin;
startActivityForResult(
std::make_unique<DictionaryWordSelectActivity>(
renderer, mappedInput, std::move(p), SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop,
epub->getCachePath(), SETTINGS.orientation, ""),
[this](const ActivityResult&) { requestUpdate(); });
break;
}
case EpubReaderMenuActivity::MenuAction::LOOKUP_HISTORY: {
if (!Dictionary::cacheExists()) {
requestUpdate();
break;
}
startActivityForResult(
std::make_unique<LookedUpWordsActivity>(renderer, mappedInput, epub->getCachePath(),
SETTINGS.getReaderFontId(), SETTINGS.orientation),
[this](const ActivityResult&) { requestUpdate(); });
break;
}
case EpubReaderMenuActivity::MenuAction::DELETE_DICT_CACHE: {
if (Dictionary::cacheExists()) {
Dictionary::deleteCache();
}
requestUpdate();
break;
}
} }
} }
@@ -462,6 +544,25 @@ void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
} }
} }
void EpubReaderActivity::applyFontSize(const uint8_t fontSize) {
if (fontSize >= CrossPointSettings::FONT_SIZE_COUNT || SETTINGS.fontSize == fontSize) {
return;
}
{
RenderLock lock(*this);
if (section) {
cachedSpineIndex = currentSpineIndex;
cachedChapterTotalPageCount = section->pageCount;
nextPageNumber = section->currentPage;
}
SETTINGS.fontSize = fontSize;
SETTINGS.saveToFile();
section.reset();
}
}
void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption) { void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption) {
if (selectedPageTurnOption == 0 || selectedPageTurnOption >= PAGE_TURN_LABELS.size()) { if (selectedPageTurnOption == 0 || selectedPageTurnOption >= PAGE_TURN_LABELS.size()) {
automaticPageTurnActive = false; automaticPageTurnActive = false;

View File

@@ -46,6 +46,7 @@ class EpubReaderActivity final : public Activity {
void jumpToPercent(int percent); void jumpToPercent(int percent);
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
void applyOrientation(uint8_t orientation); void applyOrientation(uint8_t orientation);
void applyFontSize(uint8_t fontSize);
void toggleAutoPageTurn(uint8_t selectedPageTurnOption); void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
void pageTurn(bool isForwardTurn); void pageTurn(bool isForwardTurn);

View File

@@ -3,6 +3,7 @@
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <I18n.h> #include <I18n.h>
#include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
@@ -10,30 +11,37 @@
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::string& title, const int currentPage, const int totalPages, const std::string& title, const int currentPage, const int totalPages,
const int bookProgressPercent, const uint8_t currentOrientation, const int bookProgressPercent, const uint8_t currentOrientation,
const bool hasFootnotes) const bool hasFootnotes, bool isBookmarked, uint8_t currentFontSize)
: Activity("EpubReaderMenu", renderer, mappedInput), : Activity("EpubReaderMenu", renderer, mappedInput),
menuItems(buildMenuItems(hasFootnotes)), menuItems(buildMenuItems(hasFootnotes, isBookmarked)),
title(title), title(title),
pendingOrientation(currentOrientation), pendingOrientation(currentOrientation),
pendingFontSize(currentFontSize < CrossPointSettings::FONT_SIZE_COUNT ? currentFontSize : 0),
currentPage(currentPage), currentPage(currentPage),
totalPages(totalPages), totalPages(totalPages),
bookProgressPercent(bookProgressPercent) {} bookProgressPercent(bookProgressPercent) {}
std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes) { std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes,
bool isBookmarked) {
std::vector<MenuItem> items; std::vector<MenuItem> items;
items.reserve(10); items.reserve(13);
items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER}); // Mod menu order
if (hasFootnotes) { if (isBookmarked) {
items.push_back({MenuAction::FOOTNOTES, StrId::STR_FOOTNOTES}); items.push_back({MenuAction::REMOVE_BOOKMARK, StrId::STR_REMOVE_BOOKMARK});
} else {
items.push_back({MenuAction::ADD_BOOKMARK, StrId::STR_ADD_BOOKMARK});
} }
items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION}); items.push_back({MenuAction::LOOKUP_WORD, StrId::STR_LOOKUP_WORD});
items.push_back({MenuAction::AUTO_PAGE_TURN, StrId::STR_AUTO_TURN_PAGES_PER_MIN}); items.push_back({MenuAction::GO_TO_BOOKMARK, StrId::STR_GO_TO_BOOKMARK});
items.push_back({MenuAction::LOOKUP_HISTORY, StrId::STR_LOOKUP_HISTORY});
items.push_back({MenuAction::TABLE_OF_CONTENTS, StrId::STR_TABLE_OF_CONTENTS});
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT}); items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
items.push_back({MenuAction::SCREENSHOT, StrId::STR_SCREENSHOT_BUTTON}); items.push_back({MenuAction::TOGGLE_ORIENTATION, StrId::STR_TOGGLE_ORIENTATION});
items.push_back({MenuAction::DISPLAY_QR, StrId::STR_DISPLAY_QR}); items.push_back({MenuAction::TOGGLE_FONT_SIZE, StrId::STR_TOGGLE_FONT_SIZE});
items.push_back({MenuAction::GO_HOME, StrId::STR_GO_HOME_BUTTON});
items.push_back({MenuAction::SYNC, StrId::STR_SYNC_PROGRESS}); items.push_back({MenuAction::SYNC, StrId::STR_SYNC_PROGRESS});
items.push_back({MenuAction::CLOSE_BOOK, StrId::STR_CLOSE_BOOK});
items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE}); items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE});
items.push_back({MenuAction::DELETE_DICT_CACHE, StrId::STR_DELETE_DICT_CACHE});
return items; return items;
} }
@@ -58,26 +66,28 @@ void EpubReaderMenuActivity::loop() {
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
const auto selectedAction = menuItems[selectedIndex].action; const auto selectedAction = menuItems[selectedIndex].action;
if (selectedAction == MenuAction::ROTATE_SCREEN) { if (selectedAction == MenuAction::TOGGLE_ORIENTATION) {
// Cycle orientation preview locally; actual rotation happens on menu exit. // Toggle between preferred portrait and preferred landscape.
pendingOrientation = (pendingOrientation + 1) % orientationLabels.size(); const bool isCurrentlyPortrait =
(pendingOrientation == CrossPointSettings::PORTRAIT || pendingOrientation == CrossPointSettings::INVERTED);
pendingOrientation = isCurrentlyPortrait ? SETTINGS.preferredLandscape : SETTINGS.preferredPortrait;
requestUpdate(); requestUpdate();
return; return;
} }
if (selectedAction == MenuAction::AUTO_PAGE_TURN) { if (selectedAction == MenuAction::TOGGLE_FONT_SIZE) {
selectedPageTurnOption = (selectedPageTurnOption + 1) % pageTurnLabels.size(); pendingFontSize = (pendingFontSize + 1) % CrossPointSettings::FONT_SIZE_COUNT;
requestUpdate(); requestUpdate();
return; return;
} }
setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption}); setResult(MenuResult{static_cast<int>(selectedAction), pendingOrientation, selectedPageTurnOption, pendingFontSize});
finish(); finish();
return; return;
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
ActivityResult result; ActivityResult result;
result.isCancelled = true; result.isCancelled = true;
result.data = MenuResult{-1, pendingOrientation, selectedPageTurnOption}; result.data = MenuResult{-1, pendingOrientation, selectedPageTurnOption, pendingFontSize};
setResult(std::move(result)); setResult(std::move(result));
finish(); finish();
return; return;
@@ -134,16 +144,14 @@ void EpubReaderMenuActivity::render(RenderLock&&) {
renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, I18N.get(menuItems[i].labelId), !isSelected); renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, I18N.get(menuItems[i].labelId), !isSelected);
if (menuItems[i].action == MenuAction::ROTATE_SCREEN) { if (menuItems[i].action == MenuAction::TOGGLE_ORIENTATION) {
// Render current orientation value on the right edge of the content area.
const char* value = I18N.get(orientationLabels[pendingOrientation]); const char* value = I18N.get(orientationLabels[pendingOrientation]);
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
} }
if (menuItems[i].action == MenuAction::AUTO_PAGE_TURN) { if (menuItems[i].action == MenuAction::TOGGLE_FONT_SIZE) {
// Render current page turn value on the right edge of the content area. const char* value = I18N.get(fontSizeLabels[pendingFontSize]);
const auto value = pageTurnLabels[selectedPageTurnOption];
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
} }

View File

@@ -21,12 +21,24 @@ class EpubReaderMenuActivity final : public Activity {
DISPLAY_QR, DISPLAY_QR,
GO_HOME, GO_HOME,
SYNC, SYNC,
DELETE_CACHE DELETE_CACHE,
// Mod-specific actions
ADD_BOOKMARK,
REMOVE_BOOKMARK,
LOOKUP_WORD,
LOOKUP_HISTORY,
GO_TO_BOOKMARK,
TABLE_OF_CONTENTS,
TOGGLE_ORIENTATION,
TOGGLE_FONT_SIZE,
CLOSE_BOOK,
DELETE_DICT_CACHE
}; };
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
const int currentPage, const int totalPages, const int bookProgressPercent, const int currentPage, const int totalPages, const int bookProgressPercent,
const uint8_t currentOrientation, const bool hasFootnotes); const uint8_t currentOrientation, const bool hasFootnotes,
bool isBookmarked = false, uint8_t currentFontSize = 0);
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
@@ -39,7 +51,7 @@ class EpubReaderMenuActivity final : public Activity {
StrId labelId; StrId labelId;
}; };
static std::vector<MenuItem> buildMenuItems(bool hasFootnotes); static std::vector<MenuItem> buildMenuItems(bool hasFootnotes, bool isBookmarked);
// Fixed menu layout // Fixed menu layout
const std::vector<MenuItem> menuItems; const std::vector<MenuItem> menuItems;
@@ -49,9 +61,11 @@ class EpubReaderMenuActivity final : public Activity {
ButtonNavigator buttonNavigator; ButtonNavigator buttonNavigator;
std::string title = "Reader Menu"; std::string title = "Reader Menu";
uint8_t pendingOrientation = 0; uint8_t pendingOrientation = 0;
uint8_t pendingFontSize = 0;
uint8_t selectedPageTurnOption = 0; uint8_t selectedPageTurnOption = 0;
const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED,
StrId::STR_LANDSCAPE_CCW}; StrId::STR_LANDSCAPE_CCW};
const std::vector<StrId> fontSizeLabels = {StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE};
const std::vector<const char*> pageTurnLabels = {I18N.get(StrId::STR_STATE_OFF), "1", "3", "6", "12"}; const std::vector<const char*> pageTurnLabels = {I18N.get(StrId::STR_STATE_OFF), "1", "3", "6", "12"};
int currentPage = 0; int currentPage = 0;
int totalPages = 0; int totalPages = 0;