Merge pull request #128 from jpirnay/feat-fontoverride

feat: Add font foverrides to reader menu
This commit is contained in:
jpirnay
2026-04-24 15:02:30 +02:00
committed by GitHub
16 changed files with 542 additions and 128 deletions
+1
View File
@@ -518,6 +518,7 @@ STR_IMAGE_DISPLAY_GRAYSCALE: ">> Gray"
STR_WEATHER_MOON_INFO: "Moon" STR_WEATHER_MOON_INFO: "Moon"
STR_WEATHER_SUN_INFO: "Sun" STR_WEATHER_SUN_INFO: "Sun"
STR_READER_BOOKMARKS: "Bookmarks & Footnotes" STR_READER_BOOKMARKS: "Bookmarks & Footnotes"
STR_READER_OVERRIDES: "Book-specific overrides"
STR_READER_UTILS: "Helper" STR_READER_UTILS: "Helper"
STR_READER_TOOLS: "Tools" STR_READER_TOOLS: "Tools"
STR_READER_NAVIGATION: "Navigation" STR_READER_NAVIGATION: "Navigation"
+5
View File
@@ -394,6 +394,8 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char*
obj["coverBmpPath"] = book.coverBmpPath; obj["coverBmpPath"] = book.coverBmpPath;
obj["embeddedStyleOverride"] = book.embeddedStyleOverride; obj["embeddedStyleOverride"] = book.embeddedStyleOverride;
obj["imageRenderingOverride"] = book.imageRenderingOverride; obj["imageRenderingOverride"] = book.imageRenderingOverride;
obj["fontFamilyOverride"] = book.fontFamilyOverride;
obj["fontSizeOverride"] = book.fontSizeOverride;
} }
String json; String json;
@@ -428,6 +430,9 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json)
book.coverBmpPath = obj["coverBmpPath"] | std::string(""); book.coverBmpPath = obj["coverBmpPath"] | std::string("");
book.embeddedStyleOverride = clampInt8(obj["embeddedStyleOverride"] | -1, -1, 1, -1); book.embeddedStyleOverride = clampInt8(obj["embeddedStyleOverride"] | -1, -1, 1, -1);
book.imageRenderingOverride = clampInt8(obj["imageRenderingOverride"] | -1, -1, 2, -1); book.imageRenderingOverride = clampInt8(obj["imageRenderingOverride"] | -1, -1, 2, -1);
book.fontFamilyOverride =
clampInt8(obj["fontFamilyOverride"] | -1, -1, CrossPointSettings::FONT_FAMILY_COUNT - 1, -1);
book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1);
store.recentBooks.push_back(book); store.recentBooks.push_back(book);
} }
+20 -2
View File
@@ -24,6 +24,8 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title
const std::string& series, const std::string& coverBmpPath) { const std::string& series, const std::string& coverBmpPath) {
int8_t embeddedStyleOverride = -1; int8_t embeddedStyleOverride = -1;
int8_t imageRenderingOverride = -1; int8_t imageRenderingOverride = -1;
int8_t fontFamilyOverride = -1;
int8_t fontSizeOverride = -1;
// Remove existing entry if present // Remove existing entry if present
auto it = auto it =
@@ -31,12 +33,14 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title
if (it != recentBooks.end()) { if (it != recentBooks.end()) {
embeddedStyleOverride = it->embeddedStyleOverride; embeddedStyleOverride = it->embeddedStyleOverride;
imageRenderingOverride = it->imageRenderingOverride; imageRenderingOverride = it->imageRenderingOverride;
fontFamilyOverride = it->fontFamilyOverride;
fontSizeOverride = it->fontSizeOverride;
recentBooks.erase(it); recentBooks.erase(it);
} }
// Add to front // Add to front
recentBooks.insert(recentBooks.begin(), recentBooks.insert(recentBooks.begin(), {path, title, author, series, coverBmpPath, embeddedStyleOverride,
{path, title, author, series, coverBmpPath, embeddedStyleOverride, imageRenderingOverride}); imageRenderingOverride, fontFamilyOverride, fontSizeOverride});
// Trim to max size // Trim to max size
if (recentBooks.size() > MAX_RECENT_BOOKS) { if (recentBooks.size() > MAX_RECENT_BOOKS) {
@@ -85,9 +89,23 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t
if (it == recentBooks.end()) { if (it == recentBooks.end()) {
return false; return false;
} }
return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride,
it->fontSizeOverride);
}
bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride,
const int8_t imageRenderingOverride, const int8_t fontFamilyOverride,
const int8_t fontSizeOverride) {
auto it =
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
if (it == recentBooks.end()) {
return false;
}
it->embeddedStyleOverride = embeddedStyleOverride; it->embeddedStyleOverride = embeddedStyleOverride;
it->imageRenderingOverride = imageRenderingOverride; it->imageRenderingOverride = imageRenderingOverride;
it->fontFamilyOverride = fontFamilyOverride;
it->fontSizeOverride = fontSizeOverride;
return saveToFile(); return saveToFile();
} }
+6
View File
@@ -13,6 +13,10 @@ struct RecentBook {
int8_t embeddedStyleOverride = -1; int8_t embeddedStyleOverride = -1;
// -1 = use global setting, otherwise CrossPointSettings::IMAGE_RENDERING value. // -1 = use global setting, otherwise CrossPointSettings::IMAGE_RENDERING value.
int8_t imageRenderingOverride = -1; int8_t imageRenderingOverride = -1;
// -1 = use global setting, otherwise CrossPointSettings::FONT_FAMILY value.
int8_t fontFamilyOverride = -1;
// -1 = use global setting, otherwise CrossPointSettings::FONT_SIZE value.
int8_t fontSizeOverride = -1;
bool operator==(const RecentBook& other) const { return path == other.path; } bool operator==(const RecentBook& other) const { return path == other.path; }
}; };
@@ -58,6 +62,8 @@ class RecentBooksStore {
RecentBook getDataFromBook(std::string path) const; RecentBook getDataFromBook(std::string path) const;
RecentBook getBookByPath(const std::string& path) const; RecentBook getBookByPath(const std::string& path) const;
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride); bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride);
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride,
int8_t fontFamilyOverride, int8_t fontSizeOverride);
private: private:
bool loadFromBinaryFile(); bool loadFromBinaryFile();
+3
View File
@@ -20,10 +20,13 @@ struct KeyboardResult {
struct MenuResult { struct MenuResult {
int action = -1; int action = -1;
int nameId = -1;
uint8_t orientation = 0; uint8_t orientation = 0;
uint8_t pageTurnOption = 0; uint8_t pageTurnOption = 0;
int8_t embeddedStyleOverride = -1; int8_t embeddedStyleOverride = -1;
int8_t imageRenderingOverride = -1; int8_t imageRenderingOverride = -1;
int8_t fontFamilyOverride = -1;
int8_t fontSizeOverride = -1;
uint8_t textDarkness = 1; uint8_t textDarkness = 1;
}; };
+21
View File
@@ -4,6 +4,7 @@
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "settings/SettingsSubmenuActivity.h"
void MenuListActivity::initMenuList() { void MenuListActivity::initMenuList() {
const int count = static_cast<int>(menuItems.size()); const int count = static_cast<int>(menuItems.size());
@@ -16,6 +17,10 @@ void MenuListActivity::initMenuList() {
void MenuListActivity::onEnter() { void MenuListActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
if (!submenusPrepared) {
prepareSubmenus();
submenusPrepared = true;
}
initMenuList(); initMenuList();
requestUpdate(); requestUpdate();
} }
@@ -37,6 +42,10 @@ void MenuListActivity::toggleCurrentItem() {
if (item.isSeparator) return; if (item.isSeparator) return;
if (item.type == SettingType::ACTION) { if (item.type == SettingType::ACTION) {
if (item.action == SettingAction::Submenu) {
openSubmenu(item);
return;
}
onActionSelected(selectedIndex); onActionSelected(selectedIndex);
return; return;
} }
@@ -55,6 +64,18 @@ void MenuListActivity::drawMenuList(const Rect& rect) {
[this](int index) { return getItemValueString(index); }, true); [this](int index) { return getItemValueString(index); }, true);
} }
void MenuListActivity::prepareSubmenus() { SettingInfo::prepareSubmenus(menuItems, submenuData); }
void MenuListActivity::openSubmenu(const SettingInfo& submenuEntry) {
auto it = std::find_if(submenuData.begin(), submenuData.end(),
[&submenuEntry](const SettingInfo::SubmenuData& d) { return d.id == submenuEntry.nameId; });
if (it == submenuData.end()) return;
startActivityForResult(
std::make_unique<SettingsSubmenuActivity>(renderer, mappedInput, submenuEntry.nameId, it->items),
[this](const ActivityResult&) { requestUpdate(); });
}
void MenuListActivity::loop() { void MenuListActivity::loop() {
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
onBackPressed(); onBackPressed();
+7 -1
View File
@@ -70,17 +70,23 @@ struct Rect;
class MenuListActivity : public Activity { class MenuListActivity : public Activity {
protected: protected:
std::vector<SettingInfo> menuItems; std::vector<SettingInfo> menuItems;
std::vector<SettingInfo::SubmenuData> submenuData;
int selectedIndex = 0; int selectedIndex = 0;
ButtonNavigator buttonNavigator; ButtonNavigator buttonNavigator;
bool submenusPrepared = false;
// Call after building/rebuilding menuItems to wire up the selectable predicate. // Call after building/rebuilding menuItems to wire up the selectable predicate.
void initMenuList(); void initMenuList();
// Process SettingInfo items marked with withSubmenu() into submenu placeholders.
void prepareSubmenus();
void openSubmenu(const SettingInfo& submenuEntry);
// Handle up/down navigation via buttonNavigator. Call from loop() if overriding. // Handle up/down navigation via buttonNavigator. Call from loop() if overriding.
void handleNavigation(); void handleNavigation();
// Toggle/cycle the currently selected item. For ACTION items, delegates to onActionSelected(). // Toggle/cycle the currently selected item. For ACTION items, delegates to onActionSelected().
void toggleCurrentItem(); virtual void toggleCurrentItem();
// Draw the list into the given rect using GUI.drawList(). // Draw the list into the given rect using GUI.drawList().
void drawMenuList(const Rect& rect); void drawMenuList(const Rect& rect);
+137 -37
View File
@@ -148,6 +148,8 @@ void EpubReaderActivity::onEnter() {
const RecentBook currentBook = RECENT_BOOKS.getBookByPath(epub->getPath()); const RecentBook currentBook = RECENT_BOOKS.getBookByPath(epub->getPath());
bookEmbeddedStyleOverride = currentBook.embeddedStyleOverride; bookEmbeddedStyleOverride = currentBook.embeddedStyleOverride;
bookImageRenderingOverride = currentBook.imageRenderingOverride; bookImageRenderingOverride = currentBook.imageRenderingOverride;
bookFontFamilyOverride = currentBook.fontFamilyOverride;
bookFontSizeOverride = currentBook.fontSizeOverride;
logReaderMemSnapshot("onEnter_after_recent_books"); logReaderMemSnapshot("onEnter_after_recent_books");
// Trigger first update // Trigger first update
@@ -239,22 +241,23 @@ void EpubReaderActivity::loop() {
const bool isCurrentPageStarred = section && bookmarkStore.has(static_cast<uint16_t>(currentSpineIndex), const bool isCurrentPageStarred = section && bookmarkStore.has(static_cast<uint16_t>(currentSpineIndex),
static_cast<uint16_t>(section->currentPage)); static_cast<uint16_t>(section->currentPage));
ReaderUtils::enforceExitFullRefresh(renderer); ReaderUtils::enforceExitFullRefresh(renderer);
startActivityForResult( startActivityForResult(std::make_unique<EpubReaderMenuActivity>(
std::make_unique<EpubReaderMenuActivity>( renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation, SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride,
!currentPageFootnotes.empty(), bookEmbeddedStyleOverride, bookImageRenderingOverride, SETTINGS.textDarkness, bookImageRenderingOverride, bookFontFamilyOverride, bookFontSizeOverride,
!bookmarkStore.isEmpty(), isCurrentPageStarred), SETTINGS.textDarkness, !bookmarkStore.isEmpty(), isCurrentPageStarred),
[this](const ActivityResult& result) { [this](const ActivityResult& result) {
// Always apply orientation/darkness change even if the menu was cancelled // Always apply orientation/darkness change 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);
applyTextDarkness(menu.textDarkness); applyTextDarkness(menu.textDarkness);
toggleAutoPageTurn(menu.pageTurnOption); toggleAutoPageTurn(menu.pageTurnOption);
applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride); applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride,
if (!result.isCancelled) { menu.fontFamilyOverride, menu.fontSizeOverride);
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action)); if (!result.isCancelled) {
} onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
}); }
});
} }
// Long press BACK (1s+) goes to home screen // Long press BACK (1s+) goes to home screen
@@ -814,18 +817,23 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption
} }
void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride, void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride,
const int8_t imageRenderingOverride) { const int8_t imageRenderingOverride, const int8_t fontFamilyOverride,
const int8_t fontSizeOverride) {
if (!epub) { if (!epub) {
return; return;
} }
if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride) { if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride &&
bookFontFamilyOverride == fontFamilyOverride && bookFontSizeOverride == fontSizeOverride) {
return; return;
} }
bookEmbeddedStyleOverride = embeddedStyleOverride; bookEmbeddedStyleOverride = embeddedStyleOverride;
bookImageRenderingOverride = imageRenderingOverride; bookImageRenderingOverride = imageRenderingOverride;
RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride); bookFontFamilyOverride = fontFamilyOverride;
bookFontSizeOverride = fontSizeOverride;
RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride,
bookFontFamilyOverride, bookFontSizeOverride);
RenderLock lock(*this); RenderLock lock(*this);
if (section) { if (section) {
@@ -850,6 +858,51 @@ uint8_t EpubReaderActivity::getEffectiveImageRendering() const {
return SETTINGS.imageRendering; return SETTINGS.imageRendering;
} }
int EpubReaderActivity::getEffectiveReaderFontId() const {
const uint8_t fontFamily =
(bookFontFamilyOverride >= 0) ? static_cast<uint8_t>(bookFontFamilyOverride) : SETTINGS.fontFamily;
const uint8_t fontSize = (bookFontSizeOverride >= 0) ? static_cast<uint8_t>(bookFontSizeOverride) : SETTINGS.fontSize;
switch (fontFamily) {
case CrossPointSettings::NOTOSANS:
switch (fontSize) {
case CrossPointSettings::SMALL:
return NOTOSANS_12_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return NOTOSANS_14_FONT_ID;
case CrossPointSettings::LARGE:
return NOTOSANS_16_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return NOTOSANS_18_FONT_ID;
}
case CrossPointSettings::OPENDYSLEXIC:
switch (fontSize) {
case CrossPointSettings::SMALL:
return OPENDYSLEXIC_8_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return OPENDYSLEXIC_10_FONT_ID;
case CrossPointSettings::LARGE:
return OPENDYSLEXIC_12_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return OPENDYSLEXIC_14_FONT_ID;
}
case CrossPointSettings::BOOKERLY:
default:
switch (fontSize) {
case CrossPointSettings::SMALL:
return BOOKERLY_12_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return BOOKERLY_14_FONT_ID;
case CrossPointSettings::LARGE:
return BOOKERLY_16_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return BOOKERLY_18_FONT_ID;
}
}
}
void EpubReaderActivity::pageTurn(bool isForwardTurn) { void EpubReaderActivity::pageTurn(bool isForwardTurn) {
if (isForwardTurn) { if (isForwardTurn) {
if (section->currentPage < section->pageCount - 1) { if (section->currentPage < section->pageCount - 1) {
@@ -934,7 +987,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
LOG_DBG("ERS", "Loading file: %s, index: %d", filepath.c_str(), currentSpineIndex); LOG_DBG("ERS", "Loading file: %s, index: %d", filepath.c_str(), currentSpineIndex);
section = std::make_unique<Section>(epub, currentSpineIndex, renderer); section = std::make_unique<Section>(epub, currentSpineIndex, renderer);
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (!section->loadSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
LOG_DBG("ERS", "Cache not found, building..."); LOG_DBG("ERS", "Cache not found, building...");
@@ -947,7 +1000,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
GUI.fillPopupProgress(renderer, popupRect, progress); GUI.fillPopupProgress(renderer, popupRect, progress);
}; };
if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (!section->createSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering, viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering,
progressFn)) { progressFn)) {
@@ -1090,14 +1143,14 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW
const uint8_t imageRendering = getEffectiveImageRendering(); const uint8_t imageRendering = getEffectiveImageRendering();
Section nextSection(epub, nextSpineIndex, renderer); Section nextSection(epub, nextSpineIndex, renderer);
if (nextSection.loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (nextSection.loadSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
return; return;
} }
LOG_DBG("ERS", "Silently indexing next chapter: %d", nextSpineIndex); LOG_DBG("ERS", "Silently indexing next chapter: %d", nextSpineIndex);
if (!nextSection.createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (!nextSection.createSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex); LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex);
@@ -1133,7 +1186,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
// Font prewarm: scan pass accumulates text, then prewarm, then real render // Font prewarm: scan pass accumulates text, then prewarm, then real render
const uint32_t heapBefore = esp_get_free_heap_size(); const uint32_t heapBefore = esp_get_free_heap_size();
auto scope = fcm->createPrewarmScope(); auto scope = fcm->createPrewarmScope();
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); // scan pass page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); // scan pass
scope.endScanAndPrewarm(); scope.endScanAndPrewarm();
const uint32_t heapAfter = esp_get_free_heap_size(); const uint32_t heapAfter = esp_get_free_heap_size();
fcm->logStats("prewarm"); fcm->logStats("prewarm");
@@ -1149,7 +1202,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
pendingHalfRefreshAfterImagePage = false; pendingHalfRefreshAfterImagePage = false;
logReaderMemSnapshot("before_bw_render"); logReaderMemSnapshot("before_bw_render");
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
renderStatusBar(); renderStatusBar();
fcm->logStats("bw_render"); fcm->logStats("bw_render");
const auto tBwRender = millis(); const auto tBwRender = millis();
@@ -1168,7 +1221,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
// Re-render page content to restore images into the blanked area // Re-render page content to restore images into the blanked area
// Status bar is not re-rendered here to avoid reading stale dynamic values (e.g. battery %) // Status bar is not re-rendered here to avoid reading stale dynamic values (e.g. battery %)
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
renderer.displayBuffer(HalDisplay::FAST_REFRESH); renderer.displayBuffer(HalDisplay::FAST_REFRESH);
} else { } else {
renderer.displayBuffer(HalDisplay::HALF_REFRESH); renderer.displayBuffer(HalDisplay::HALF_REFRESH);
@@ -1201,7 +1254,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
logReaderMemSnapshot("gray_lsb_begin"); logReaderMemSnapshot("gray_lsb_begin");
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
renderer.copyGrayscaleLsbBuffers(); renderer.copyGrayscaleLsbBuffers();
const auto tGrayLsb = millis(); const auto tGrayLsb = millis();
logReaderMemSnapshot("gray_lsb_end"); logReaderMemSnapshot("gray_lsb_end");
@@ -1210,7 +1263,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
logReaderMemSnapshot("gray_msb_begin"); logReaderMemSnapshot("gray_msb_begin");
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop);
renderer.copyGrayscaleMsbBuffers(); renderer.copyGrayscaleMsbBuffers();
const auto tGrayMsb = millis(); const auto tGrayMsb = millis();
logReaderMemSnapshot("gray_msb_end"); logReaderMemSnapshot("gray_msb_end");
@@ -1385,16 +1438,63 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf
// Load or rebuild the section cache. Rebuilding is needed when the cache is missing or stale // Load or rebuild the section cache. Rebuilding is needed when the cache is missing or stale
// (e.g. after a firmware update). A no-op popup callback avoids any UI during sleep preparation. // (e.g. after a firmware update). A no-op popup callback avoids any UI during sleep preparation.
const RecentBook currentBook = RECENT_BOOKS.getBookByPath(filePath);
const uint8_t effectiveFontFamily =
currentBook.fontFamilyOverride >= 0 ? static_cast<uint8_t>(currentBook.fontFamilyOverride) : SETTINGS.fontFamily;
const uint8_t effectiveFontSize =
currentBook.fontSizeOverride >= 0 ? static_cast<uint8_t>(currentBook.fontSizeOverride) : SETTINGS.fontSize;
auto getEffectiveFontId = [&](uint8_t family, uint8_t size) {
switch (family) {
case CrossPointSettings::NOTOSANS:
switch (size) {
case CrossPointSettings::SMALL:
return NOTOSANS_12_FONT_ID;
case CrossPointSettings::LARGE:
return NOTOSANS_16_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return NOTOSANS_18_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return NOTOSANS_14_FONT_ID;
}
case CrossPointSettings::OPENDYSLEXIC:
switch (size) {
case CrossPointSettings::SMALL:
return OPENDYSLEXIC_8_FONT_ID;
case CrossPointSettings::LARGE:
return OPENDYSLEXIC_12_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return OPENDYSLEXIC_14_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return OPENDYSLEXIC_10_FONT_ID;
}
case CrossPointSettings::BOOKERLY:
default:
switch (size) {
case CrossPointSettings::SMALL:
return BOOKERLY_12_FONT_ID;
case CrossPointSettings::LARGE:
return BOOKERLY_16_FONT_ID;
case CrossPointSettings::EXTRA_LARGE:
return BOOKERLY_18_FONT_ID;
case CrossPointSettings::MEDIUM:
default:
return BOOKERLY_14_FONT_ID;
}
}
};
auto section = std::make_unique<Section>(epub, spineIndex, renderer); auto section = std::make_unique<Section>(epub, spineIndex, renderer);
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (!section->loadSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing,
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled,
SETTINGS.imageRendering)) { SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
LOG_DBG("SLP", "EPUB: section cache not found for spine %d, rebuilding", spineIndex); LOG_DBG("SLP", "EPUB: section cache not found for spine %d, rebuilding", spineIndex);
if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (!section->createSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing,
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight,
SETTINGS.imageRendering)) { SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
LOG_ERR("SLP", "EPUB: failed to rebuild section cache for spine %d", spineIndex); LOG_ERR("SLP", "EPUB: failed to rebuild section cache for spine %d", spineIndex);
return false; return false;
} }
@@ -1410,7 +1510,7 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf
} }
renderer.clearScreen(); renderer.clearScreen();
page->render(renderer, SETTINGS.getReaderFontId(), marginLeft, marginTop); page->render(renderer, getEffectiveFontId(effectiveFontFamily, effectiveFontSize), marginLeft, marginTop);
// No displayBuffer call — caller (SleepActivity) handles that after compositing the overlay // No displayBuffer call — caller (SleepActivity) handles that after compositing the overlay
return true; return true;
} }
+5 -1
View File
@@ -53,6 +53,8 @@ class EpubReaderActivity final : public Activity {
// -1 means use global SETTINGS value. // -1 means use global SETTINGS value.
int8_t bookEmbeddedStyleOverride = -1; int8_t bookEmbeddedStyleOverride = -1;
int8_t bookImageRenderingOverride = -1; int8_t bookImageRenderingOverride = -1;
int8_t bookFontFamilyOverride = -1;
int8_t bookFontSizeOverride = -1;
// Bookmarks (starred pages) // Bookmarks (starred pages)
BookmarkStore bookmarkStore; BookmarkStore bookmarkStore;
@@ -88,9 +90,11 @@ class EpubReaderActivity final : public Activity {
void applyOrientation(uint8_t orientation); void applyOrientation(uint8_t orientation);
void applyTextDarkness(uint8_t textDarkness); void applyTextDarkness(uint8_t textDarkness);
void toggleAutoPageTurn(uint8_t selectedPageTurnOption); void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride); void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride,
int8_t fontSizeOverride);
bool getEffectiveEmbeddedStyle() const; bool getEffectiveEmbeddedStyle() const;
uint8_t getEffectiveImageRendering() const; uint8_t getEffectiveImageRendering() const;
int getEffectiveReaderFontId() const;
void pageTurn(bool isForwardTurn); void pageTurn(bool isForwardTurn);
// Footnote navigation // Footnote navigation
+219 -53
View File
@@ -5,6 +5,7 @@
#include "KOReaderCredentialStore.h" #include "KOReaderCredentialStore.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "activities/settings/SettingsSubmenuActivity.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
@@ -13,13 +14,16 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
const int bookProgressPercent, const uint8_t currentOrientation, const int bookProgressPercent, const uint8_t currentOrientation,
const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride, const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride,
const int8_t initialImageRenderingOverride, const int8_t initialImageRenderingOverride,
const uint8_t initialTextDarkness, const bool hasStarredPages, const int8_t initialFontFamilyOverride,
const bool isCurrentPageStarred) const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness,
const bool hasStarredPages, const bool isCurrentPageStarred)
: MenuListActivity("EpubReaderMenu", renderer, mappedInput), : MenuListActivity("EpubReaderMenu", renderer, mappedInput),
currentPageStarred(isCurrentPageStarred), currentPageStarred(isCurrentPageStarred),
pendingOrientation(currentOrientation), pendingOrientation(currentOrientation),
pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride), pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride),
pendingImageRenderingOverride(initialImageRenderingOverride), pendingImageRenderingOverride(initialImageRenderingOverride),
pendingFontFamilyOverride(initialFontFamilyOverride),
pendingFontSizeOverride(initialFontSizeOverride),
pendingTextDarkness(initialTextDarkness), pendingTextDarkness(initialTextDarkness),
title(title), title(title),
currentPage(currentPage), currentPage(currentPage),
@@ -51,50 +55,6 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_APPEARANCE)); menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_APPEARANCE));
auto* self = this; auto* self = this;
// Embedded style: cycles default(-1) -> ON(1) -> OFF(0) via DynamicEnum indices 0/1/2
menuItems.push_back(SettingInfo::DynamicEnumCtx(
StrId::STR_EMBEDDED_STYLE, {StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self,
[](const void* ctx) -> uint8_t {
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
if (s->pendingEmbeddedStyleOverride < 0) return 0;
if (s->pendingEmbeddedStyleOverride > 0) return 1;
return 2;
},
[](void* ctx, uint8_t v) {
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
if (v == 0)
s->pendingEmbeddedStyleOverride = -1;
else if (v == 1)
s->pendingEmbeddedStyleOverride = 1;
else
s->pendingEmbeddedStyleOverride = 0;
}));
// Image rendering: cycles default(-1) -> display(0) -> placeholder(1) -> suppress(2)
menuItems.push_back(SettingInfo::DynamicEnumCtx(
StrId::STR_IMAGES,
{StrId::STR_DEFAULT_VALUE, StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS},
self,
[](const void* ctx) -> uint8_t {
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
return (s->pendingImageRenderingOverride < 0) ? 0 : (s->pendingImageRenderingOverride + 1);
},
[](void* ctx, uint8_t v) {
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
s->pendingImageRenderingOverride = (v == 0) ? -1 : static_cast<int8_t>(v - 1);
}));
// Text darkness: straightforward 0-3 cycle
menuItems.push_back(SettingInfo::DynamicEnumCtx(
StrId::STR_TEXT_DARKNESS, {StrId::STR_NORMAL, StrId::STR_DARK, StrId::STR_EXTRA_DARK, StrId::STR_MAX_DARK}, self,
[](const void* ctx) -> uint8_t { return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingTextDarkness; },
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingTextDarkness = v; }));
// Helper functions, reading ruler, auto page turn, orientation
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS));
// Auto page turn: ACTION type with custom cycling in onActionSelected
menuItems.push_back(SettingInfo::Action(StrId::STR_AUTO_TURN_PAGES_PER_MIN, SettingAction::None));
// Orientation: straightforward 0-3 cycle // Orientation: straightforward 0-3 cycle
menuItems.push_back(SettingInfo::DynamicEnumCtx( menuItems.push_back(SettingInfo::DynamicEnumCtx(
StrId::STR_ORIENTATION, StrId::STR_ORIENTATION,
@@ -102,6 +62,89 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa
[](const void* ctx) -> uint8_t { return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingOrientation; }, [](const void* ctx) -> uint8_t { return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingOrientation; },
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingOrientation = v; })); [](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingOrientation = v; }));
// Embedded style: cycles default(-1) -> ON(1) -> OFF(0) via DynamicEnum indices 0/1/2
menuItems.push_back(SettingInfo::DynamicEnumCtx(
StrId::STR_EMBEDDED_STYLE,
{StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self,
[](const void* ctx) -> uint8_t {
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
if (s->pendingEmbeddedStyleOverride < 0) return 0;
if (s->pendingEmbeddedStyleOverride > 0) return 1;
return 2;
},
[](void* ctx, uint8_t v) {
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
if (v == 0)
s->pendingEmbeddedStyleOverride = -1;
else if (v == 1)
s->pendingEmbeddedStyleOverride = 1;
else
s->pendingEmbeddedStyleOverride = 0;
})
.withSubmenu(StrId::STR_READER_OVERRIDES));
// Image rendering: cycles default(-1) -> display(0) -> placeholder(1) -> suppress(2)
menuItems.push_back(SettingInfo::DynamicEnumCtx(
StrId::STR_IMAGES,
{StrId::STR_DEFAULT_VALUE, StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER,
StrId::STR_IMAGES_SUPPRESS},
self,
[](const void* ctx) -> uint8_t {
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
return (s->pendingImageRenderingOverride < 0) ? 0 : (s->pendingImageRenderingOverride + 1);
},
[](void* ctx, uint8_t v) {
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
s->pendingImageRenderingOverride = (v == 0) ? -1 : static_cast<int8_t>(v - 1);
})
.withSubmenu(StrId::STR_READER_OVERRIDES));
// Reader font family: cycles default(-1) -> Bookerly(0) -> Noto Sans(1) -> Open Dyslexic(2)
menuItems.push_back(
SettingInfo::DynamicEnumCtx(
StrId::STR_FONT_FAMILY,
{StrId::STR_DEFAULT_VALUE, StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}, self,
[](const void* ctx) -> uint8_t {
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
return (s->pendingFontFamilyOverride < 0) ? 0 : static_cast<uint8_t>(s->pendingFontFamilyOverride + 1);
},
[](void* ctx, uint8_t v) {
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
s->pendingFontFamilyOverride = (v == 0) ? -1 : static_cast<int8_t>(v - 1);
})
.withSubmenu(StrId::STR_READER_OVERRIDES));
// Reader font size: cycles default(-1) -> Small(0) -> Medium(1) -> Large(2) -> X Large(3)
menuItems.push_back(
SettingInfo::DynamicEnumCtx(
StrId::STR_FONT_SIZE,
{StrId::STR_DEFAULT_VALUE, StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, self,
[](const void* ctx) -> uint8_t {
const auto* s = static_cast<const EpubReaderMenuActivity*>(ctx);
return (s->pendingFontSizeOverride < 0) ? 0 : static_cast<uint8_t>(s->pendingFontSizeOverride + 1);
},
[](void* ctx, uint8_t v) {
auto* s = static_cast<EpubReaderMenuActivity*>(ctx);
s->pendingFontSizeOverride = (v == 0) ? -1 : static_cast<int8_t>(v - 1);
})
.withSubmenu(StrId::STR_READER_OVERRIDES));
// Text darkness: straightforward 0-3 cycle
menuItems.push_back(
SettingInfo::DynamicEnumCtx(
StrId::STR_TEXT_DARKNESS, {StrId::STR_NORMAL, StrId::STR_DARK, StrId::STR_EXTRA_DARK, StrId::STR_MAX_DARK},
self,
[](const void* ctx) -> uint8_t {
return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingTextDarkness;
},
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingTextDarkness = v; })
.withSubmenu(StrId::STR_READER_OVERRIDES));
// Helper functions, reading ruler, auto page turn, orientation
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS));
// Auto page turn: ACTION type with custom cycling in onActionSelected
menuItems.push_back(SettingInfo::Action(StrId::STR_AUTO_TURN_PAGES_PER_MIN, SettingAction::None));
// --- Synchronisation (only if credentials are set) --- // --- Synchronisation (only if credentials are set) ---
if (KOREADER_STORE.hasCredentials()) { if (KOREADER_STORE.hasCredentials()) {
menuItems.push_back(SettingInfo::Separator(StrId::STR_KOREADER_SYNC)); menuItems.push_back(SettingInfo::Separator(StrId::STR_KOREADER_SYNC));
@@ -111,9 +154,12 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa
// --- Tools --- // --- Tools ---
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_TOOLS)); menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_TOOLS));
menuItems.push_back(SettingInfo::Action(StrId::STR_SCREENSHOT_BUTTON, SettingAction::None)); menuItems.push_back(
menuItems.push_back(SettingInfo::Action(StrId::STR_DISPLAY_QR, SettingAction::None)); SettingInfo::Action(StrId::STR_SCREENSHOT_BUTTON, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
menuItems.push_back(SettingInfo::Action(StrId::STR_DELETE_CACHE, SettingAction::None)); menuItems.push_back(
SettingInfo::Action(StrId::STR_DISPLAY_QR, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
menuItems.push_back(
SettingInfo::Action(StrId::STR_DELETE_CACHE, SettingAction::None).withSubmenu(StrId::STR_READER_TOOLS));
menuItems.push_back(SettingInfo::Action(StrId::STR_GO_HOME_BUTTON, SettingAction::None)); menuItems.push_back(SettingInfo::Action(StrId::STR_GO_HOME_BUTTON, SettingAction::None));
} }
@@ -156,9 +202,20 @@ EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForNameId(StrId
} }
} }
EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForSettingAction(SettingAction action) {
switch (action) {
case SettingAction::None:
case SettingAction::Submenu:
return MenuAction::NONE;
default:
return MenuAction::NONE;
}
}
void EpubReaderMenuActivity::finishWithAction(MenuAction action) { void EpubReaderMenuActivity::finishWithAction(MenuAction action) {
setResult(MenuResult{static_cast<int>(action), pendingOrientation, selectedPageTurnOption, setResult(MenuResult{static_cast<int>(action), -1, pendingOrientation, selectedPageTurnOption,
pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingTextDarkness}); pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride,
pendingFontSizeOverride, pendingTextDarkness});
finish(); finish();
} }
@@ -184,10 +241,13 @@ void EpubReaderMenuActivity::onBackPressed() {
ActivityResult result; ActivityResult result;
result.isCancelled = true; result.isCancelled = true;
result.data = MenuResult{-1, result.data = MenuResult{-1,
-1,
pendingOrientation, pendingOrientation,
selectedPageTurnOption, selectedPageTurnOption,
pendingEmbeddedStyleOverride, pendingEmbeddedStyleOverride,
pendingImageRenderingOverride, pendingImageRenderingOverride,
pendingFontFamilyOverride,
pendingFontSizeOverride,
pendingTextDarkness}; pendingTextDarkness};
setResult(std::move(result)); setResult(std::move(result));
finish(); finish();
@@ -207,13 +267,119 @@ std::string EpubReaderMenuActivity::getItemValueString(int index) const {
return currentPageStarred ? std::string(tr(STR_STATE_ON)) : std::string(tr(STR_STATE_OFF)); return currentPageStarred ? std::string(tr(STR_STATE_ON)) : std::string(tr(STR_STATE_OFF));
} }
// Plain ACTION items (select chapter, screenshot, etc.) show no value if (item.type == SettingType::ACTION) {
if (item.type == SettingType::ACTION) return {}; if (item.action == SettingAction::Submenu) {
return MenuListActivity::getItemValueString(index);
}
return {};
}
if (item.type == SettingType::ENUM) {
if (item.nameId == StrId::STR_EMBEDDED_STYLE && pendingEmbeddedStyleOverride < 0) {
const auto defaultEffective = (SETTINGS.embeddedStyle != 0) ? tr(STR_STATE_ON) : tr(STR_STATE_OFF);
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + defaultEffective + ")";
}
if (item.nameId == StrId::STR_IMAGES && pendingImageRenderingOverride < 0) {
const auto defaultIndex = static_cast<size_t>(SETTINGS.imageRendering + 1);
if (defaultIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[defaultIndex]) + ")";
}
}
if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0) {
const auto defaultIndex = static_cast<size_t>(SETTINGS.fontFamily + 1);
if (defaultIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[defaultIndex]) + ")";
}
}
if (item.nameId == StrId::STR_FONT_SIZE && pendingFontSizeOverride < 0) {
const auto defaultIndex = static_cast<size_t>(SETTINGS.fontSize + 1);
if (defaultIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[defaultIndex]) + ")";
}
}
}
// DynamicEnum items use the standard display // DynamicEnum items use the standard display
return MenuListActivity::getItemValueString(index); return MenuListActivity::getItemValueString(index);
} }
void EpubReaderMenuActivity::openSubmenu(const SettingInfo& submenuEntry) {
auto it = std::find_if(submenuData.begin(), submenuData.end(),
[&submenuEntry](const SettingInfo::SubmenuData& d) { return d.id == submenuEntry.nameId; });
if (it == submenuData.end()) return;
auto itemValueStringOverride = [this](const SettingInfo& item) -> std::string {
if (item.nameId == StrId::STR_EMBEDDED_STYLE && pendingEmbeddedStyleOverride < 0) {
const auto defaultEffective = (SETTINGS.embeddedStyle != 0) ? tr(STR_STATE_ON) : tr(STR_STATE_OFF);
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + defaultEffective + ")";
}
if (item.nameId == StrId::STR_IMAGES && pendingImageRenderingOverride < 0) {
const auto valueIndex = static_cast<size_t>(SETTINGS.imageRendering + 1);
if (valueIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[valueIndex]) + ")";
}
}
if (item.nameId == StrId::STR_FONT_FAMILY && pendingFontFamilyOverride < 0) {
const auto valueIndex = static_cast<size_t>(SETTINGS.fontFamily + 1);
if (valueIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[valueIndex]) + ")";
}
}
if (item.nameId == StrId::STR_FONT_SIZE && pendingFontSizeOverride < 0) {
const auto valueIndex = static_cast<size_t>(SETTINGS.fontSize + 1);
if (valueIndex < item.enumValues.size()) {
return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[valueIndex]) + ")";
}
}
return item.getDisplayValue();
};
startActivityForResult(std::make_unique<SettingsSubmenuActivity>(renderer, mappedInput, submenuEntry.nameId,
it->items, std::move(itemValueStringOverride)),
[this](const ActivityResult& result) {
if (!result.isCancelled) {
const auto* menuResult = std::get_if<MenuResult>(&result.data);
if (menuResult) {
if (menuResult->action != -1) {
const auto action =
actionForSettingAction(static_cast<SettingAction>(menuResult->action));
if (action != MenuAction::NONE) {
finishWithAction(action);
return;
}
}
if (menuResult->nameId != -1) {
const auto action = actionForNameId(static_cast<StrId>(menuResult->nameId));
if (action != MenuAction::NONE) {
finishWithAction(action);
return;
}
}
}
}
requestUpdate();
});
}
void EpubReaderMenuActivity::toggleCurrentItem() {
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(menuItems.size())) return;
const auto& item = menuItems[selectedIndex];
if (item.isSeparator) return;
if (item.type == SettingType::ACTION) {
if (item.action == SettingAction::Submenu) {
openSubmenu(item);
return;
}
onActionSelected(selectedIndex);
return;
}
menuItems[selectedIndex].toggleValue();
onSettingToggled(selectedIndex);
requestUpdate();
}
void EpubReaderMenuActivity::onEnter() { MenuListActivity::onEnter(); } void EpubReaderMenuActivity::onEnter() { MenuListActivity::onEnter(); }
void EpubReaderMenuActivity::render(RenderLock&&) { void EpubReaderMenuActivity::render(RenderLock&&) {
@@ -35,6 +35,7 @@ class EpubReaderMenuActivity final : public MenuListActivity {
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,
const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride, const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride,
const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride,
const uint8_t initialTextDarkness, const bool hasStarredPages, const uint8_t initialTextDarkness, const bool hasStarredPages,
const bool isCurrentPageStarred); const bool isCurrentPageStarred);
@@ -52,15 +53,20 @@ class EpubReaderMenuActivity final : public MenuListActivity {
void onActionSelected(int index) override; void onActionSelected(int index) override;
void onBackPressed() override; void onBackPressed() override;
void onSettingToggled(int index) override; void onSettingToggled(int index) override;
void toggleCurrentItem() override;
void openSubmenu(const SettingInfo& submenuEntry);
// Map from StrId to MenuAction for result passing // Map from StrId to MenuAction for result passing
static MenuAction actionForNameId(StrId nameId); static MenuAction actionForNameId(StrId nameId);
static MenuAction actionForSettingAction(SettingAction action);
// Pending state (mutated locally, returned to parent on finish) // Pending state (mutated locally, returned to parent on finish)
uint8_t pendingOrientation = 0; uint8_t pendingOrientation = 0;
uint8_t selectedPageTurnOption = 0; uint8_t selectedPageTurnOption = 0;
int8_t pendingEmbeddedStyleOverride = -1; int8_t pendingEmbeddedStyleOverride = -1;
int8_t pendingImageRenderingOverride = -1; int8_t pendingImageRenderingOverride = -1;
int8_t pendingFontFamilyOverride = -1;
int8_t pendingFontSizeOverride = -1;
uint8_t pendingTextDarkness = 1; uint8_t pendingTextDarkness = 1;
static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"}; static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"};
+47
View File
@@ -1,7 +1,9 @@
#pragma once #pragma once
#include <I18n.h> #include <I18n.h>
#include <algorithm>
#include <cassert> #include <cassert>
#include <iterator>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -88,6 +90,13 @@ struct SettingInfo {
stringSetter(accessorCtx, v); stringSetter(accessorCtx, v);
} }
struct SubmenuData {
StrId id = StrId::STR_NONE_OPT;
std::vector<SettingInfo> items;
};
static void prepareSubmenus(std::vector<SettingInfo>& items, std::vector<SubmenuData>& submenuData);
SettingInfo& withObfuscated() { SettingInfo& withObfuscated() {
obfuscated = true; obfuscated = true;
return *this; return *this;
@@ -237,3 +246,41 @@ struct SettingInfo {
// not the SettingInfo itself. // not the SettingInfo itself.
void toggleValue() const; void toggleValue() const;
}; };
inline void SettingInfo::prepareSubmenus(std::vector<SettingInfo>& items,
std::vector<SettingInfo::SubmenuData>& submenuData) {
if (items.empty()) return;
std::vector<SettingInfo> preparedItems;
std::vector<SubmenuData> preparedSubmenus;
preparedItems.reserve(items.size());
for (auto& item : items) {
if (item.submenu == StrId::STR_NONE_OPT) {
preparedItems.push_back(std::move(item));
continue;
}
auto it = std::find_if(preparedSubmenus.begin(), preparedSubmenus.end(),
[&item](const SubmenuData& d) { return d.id == item.submenu; });
if (it == preparedSubmenus.end()) {
preparedItems.push_back(SettingInfo::SubmenuEntry(item.submenu));
preparedSubmenus.push_back({item.submenu, {}});
it = preparedSubmenus.end() - 1;
}
it->items.push_back(std::move(item));
}
items.swap(preparedItems);
for (auto& submenu : preparedSubmenus) {
auto it = std::find_if(submenuData.begin(), submenuData.end(),
[&submenu](const SubmenuData& d) { return d.id == submenu.id; });
if (it == submenuData.end()) {
submenuData.push_back(std::move(submenu));
} else {
it->items.insert(it->items.end(), std::make_move_iterator(submenu.items.begin()),
std::make_move_iterator(submenu.items.end()));
}
}
}
+24 -24
View File
@@ -41,33 +41,19 @@ void SettingsActivity::onEnter() {
StrId lastControlsSub = StrId::STR_NONE_OPT; StrId lastControlsSub = StrId::STR_NONE_OPT;
StrId lastSystemSub = StrId::STR_NONE_OPT; StrId lastSystemSub = StrId::STR_NONE_OPT;
// Shared placement logic — locates submenu target or inserts separator. auto addTo = [](std::vector<SettingInfo>& vec, StrId& lastSub, const SettingInfo& s) {
// Returns the vector the caller should push into (either `vec` or a submenu's items).
auto locateTarget = [this](std::vector<SettingInfo>& vec, StrId& lastSub,
const SettingInfo& s) -> std::vector<SettingInfo>* {
if (s.submenu != StrId::STR_NONE_OPT) {
auto it = std::find_if(submenuData.begin(), submenuData.end(),
[&s](const SubmenuData& d) { return d.id == s.submenu; });
if (it == submenuData.end()) {
vec.push_back(SettingInfo::SubmenuEntry(s.submenu));
submenuData.push_back({s.submenu, {}});
it = submenuData.end() - 1;
}
return &it->items;
}
if (s.subcategory != StrId::STR_NONE_OPT && s.subcategory != lastSub) { if (s.subcategory != StrId::STR_NONE_OPT && s.subcategory != lastSub) {
vec.push_back(SettingInfo::Separator(s.subcategory)); vec.push_back(SettingInfo::Separator(s.subcategory));
lastSub = s.subcategory; lastSub = s.subcategory;
} }
return &vec; vec.push_back(s);
}; };
auto addToMoved = [](std::vector<SettingInfo>& vec, StrId& lastSub, SettingInfo&& s) {
auto addTo = [&locateTarget](std::vector<SettingInfo>& vec, StrId& lastSub, const SettingInfo& s) { if (s.subcategory != StrId::STR_NONE_OPT && s.subcategory != lastSub) {
locateTarget(vec, lastSub, s)->push_back(s); vec.push_back(SettingInfo::Separator(s.subcategory));
}; lastSub = s.subcategory;
auto addToMoved = [&locateTarget](std::vector<SettingInfo>& vec, StrId& lastSub, SettingInfo&& s) { }
auto* target = locateTarget(vec, lastSub, s); vec.push_back(std::move(s));
target->push_back(std::move(s));
}; };
for (const auto& setting : getSettingsList()) { for (const auto& setting : getSettingsList()) {
@@ -122,6 +108,11 @@ void SettingsActivity::onEnter() {
std::move(SettingInfo::Action(StrId::STR_SYSTEM_INFO, SettingAction::SystemInfo) std::move(SettingInfo::Action(StrId::STR_SYSTEM_INFO, SettingAction::SystemInfo)
.withSubcategory(StrId::STR_MENU_SYS_SYSTEM))); .withSubcategory(StrId::STR_MENU_SYS_SYSTEM)));
SettingInfo::prepareSubmenus(displaySettings, submenuData);
SettingInfo::prepareSubmenus(readerSettings, submenuData);
SettingInfo::prepareSubmenus(controlsSettings, submenuData);
SettingInfo::prepareSubmenus(systemSettings, submenuData);
// Reset selection to first category // Reset selection to first category
selectedCategoryIndex = 0; selectedCategoryIndex = 0;
selectedSettingIndex = 0; selectedSettingIndex = 0;
@@ -222,11 +213,20 @@ void SettingsActivity::toggleCurrentSetting() {
if (setting.isSeparator) return; if (setting.isSeparator) return;
if (setting.type == SettingType::ACTION) { if (setting.type == SettingType::ACTION) {
auto resultHandler = [this](const ActivityResult&) { SETTINGS.saveToFile(); }; auto resultHandler = [this](const ActivityResult& result) {
SETTINGS.saveToFile();
const auto* menuResult = std::get_if<MenuResult>(&result.data);
if (menuResult && menuResult->action != -1) {
auto activity = createActivityForAction(static_cast<SettingAction>(menuResult->action), renderer, mappedInput);
if (activity) {
startActivityForResult(std::move(activity), [this](const ActivityResult&) { SETTINGS.saveToFile(); });
}
}
};
if (setting.action == SettingAction::Submenu) { if (setting.action == SettingAction::Submenu) {
auto it = std::find_if(submenuData.begin(), submenuData.end(), auto it = std::find_if(submenuData.begin(), submenuData.end(),
[&setting](const SubmenuData& d) { return d.id == setting.nameId; }); [&setting](const SettingInfo::SubmenuData& d) { return d.id == setting.nameId; });
if (it != submenuData.end()) { if (it != submenuData.end()) {
startActivityForResult( startActivityForResult(
std::make_unique<SettingsSubmenuActivity>(renderer, mappedInput, setting.nameId, it->items), resultHandler); std::make_unique<SettingsSubmenuActivity>(renderer, mappedInput, setting.nameId, it->items), resultHandler);
+1 -5
View File
@@ -24,11 +24,7 @@ class SettingsActivity final : public Activity {
static constexpr int categoryCount = 4; static constexpr int categoryCount = 4;
static const StrId categoryNames[categoryCount]; static const StrId categoryNames[categoryCount];
struct SubmenuData { std::vector<SettingInfo::SubmenuData> submenuData;
StrId id;
std::vector<SettingInfo> items;
};
std::vector<SubmenuData> submenuData;
void enterCategory(int categoryIndex); void enterCategory(int categoryIndex);
void toggleCurrentSetting(); void toggleCurrentSetting();
@@ -9,12 +9,40 @@
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
void SettingsSubmenuActivity::onEnter() {
Activity::onEnter();
initMenuList();
requestUpdate();
}
void SettingsSubmenuActivity::onActionSelected(int index) { void SettingsSubmenuActivity::onActionSelected(int index) {
const auto& setting = menuItems[index]; const auto& setting = menuItems[index];
auto resultHandler = [this](const ActivityResult&) { SETTINGS.saveToFile(); }; if (setting.isSeparator) return;
auto activity = createActivityForAction(setting.action, renderer, mappedInput); if (setting.type == SettingType::ACTION) {
if (activity) startActivityForResult(std::move(activity), resultHandler); MenuResult menuResult;
if (setting.action != SettingAction::None) {
menuResult.action = static_cast<int>(setting.action);
} else {
menuResult.nameId = static_cast<int>(setting.nameId);
}
setResult(ActivityResult(menuResult));
finish();
return;
}
onSettingToggled(index);
}
std::string SettingsSubmenuActivity::getItemValueString(int index) const {
const auto& item = menuItems[index];
if (item.type == SettingType::ACTION && item.action != SettingAction::Submenu) {
return {};
}
if (itemValueStringOverride) {
return itemValueStringOverride(item);
}
return MenuListActivity::getItemValueString(index);
} }
void SettingsSubmenuActivity::onSettingToggled(int /*index*/) { SETTINGS.saveToFile(); } void SettingsSubmenuActivity::onSettingToggled(int /*index*/) { SETTINGS.saveToFile(); }
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <I18n.h> #include <I18n.h>
#include <functional>
#include <vector> #include <vector>
#include "SettingInfo.h" #include "SettingInfo.h"
@@ -10,15 +11,21 @@
// Supports subcategory separators (withSubcategory) exactly as the parent settings tabs do. // Supports subcategory separators (withSubcategory) exactly as the parent settings tabs do.
class SettingsSubmenuActivity final : public MenuListActivity { class SettingsSubmenuActivity final : public MenuListActivity {
StrId titleId; StrId titleId;
std::function<std::string(const SettingInfo&)> itemValueStringOverride;
// MenuListActivity overrides // MenuListActivity overrides
void onEnter() override;
void onActionSelected(int index) override; void onActionSelected(int index) override;
void onSettingToggled(int index) override; void onSettingToggled(int index) override;
std::string getItemValueString(int index) const override;
public: public:
explicit SettingsSubmenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, StrId titleId, explicit SettingsSubmenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, StrId titleId,
std::vector<SettingInfo> items) std::vector<SettingInfo> items,
: MenuListActivity("SettingsSubmenu", renderer, mappedInput), titleId(titleId) { std::function<std::string(const SettingInfo&)> itemValueStringOverride = {})
: MenuListActivity("SettingsSubmenu", renderer, mappedInput),
titleId(titleId),
itemValueStringOverride(std::move(itemValueStringOverride)) {
menuItems = std::move(items); menuItems = std::move(items);
} }