From a03f5faad54723ffae97897cea6d9bb6b6aa5c7d Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sat, 23 May 2026 10:24:12 +0200 Subject: [PATCH] Expose override menu --- lib/I18n/translations/english.yaml | 2 + src/CrossPointSettings.h | 1 + src/JsonSettingsIO.cpp | 4 + src/RecentBooksStore.cpp | 48 ++-- src/RecentBooksStore.h | 10 + src/SettingsList.h | 2 +- src/activities/ActivityResult.h | 2 + src/activities/reader/EpubReaderActivity.cpp | 77 +++++-- src/activities/reader/EpubReaderActivity.h | 12 + .../reader/QuickOverridesActivity.cpp | 216 ++++++++++++++++++ .../reader/QuickOverridesActivity.h | 42 ++++ src/main.cpp | 3 + 12 files changed, 386 insertions(+), 33 deletions(-) create mode 100644 src/activities/reader/QuickOverridesActivity.cpp create mode 100644 src/activities/reader/QuickOverridesActivity.h diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index d5e99b9e..50a97db4 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -592,6 +592,7 @@ STR_WEATHER_MOON_INFO: "Moon" STR_WEATHER_SUN_INFO: "Sun" STR_READER_BOOKMARKS: "Bookmarks & Footnotes" STR_READER_OVERRIDES: "Book-specific overrides" +STR_QUICK_OVERRIDES: "Quick Overrides" STR_READER_UTILS: "Helper" STR_READER_TOOLS: "Tools" STR_READER_NAVIGATION: "Navigation" @@ -684,6 +685,7 @@ STR_BIONIC_READING: "Bionic Reading" STR_BTN_ACT_TOGGLE_BIONIC_READING: "Toggle Bionic Reading" STR_BTN_ACT_CYCLE_FONT_SIZE: "Change Font Size" STR_BTN_ACT_CYCLE_ORIENTATION: "Change Orientation" +STR_BTN_ACT_QUICK_OVERRIDES: "Quick Overrides" STR_RESTART_DEVICE: "Restart Device" STR_SD_FIRMWARE_UPDATE: "SD Card Firmware Update" STR_SELECT_FIRMWARE_FILE: "Select firmware file (.bin)" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 5cd6deed..e68c298e 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -342,6 +342,7 @@ class CrossPointSettings { BTN_KOREADER_SYNC, BTN_CYCLE_FONT_SIZE, BTN_CYCLE_ORIENTATION, + BTN_QUICK_OVERRIDES, BUTTON_ACTION_COUNT }; diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index f13161fa..4f655bc9 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -492,6 +492,8 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char* obj["fontSizeOverride"] = book.fontSizeOverride; obj["bionicReadingOverride"] = book.bionicReadingOverride; obj["paragraphAlignmentOverride"] = book.paragraphAlignmentOverride; + obj["textAntiAliasingOverride"] = book.textAntiAliasingOverride; + obj["hyphenationOverride"] = book.hyphenationOverride; } String json; @@ -537,6 +539,8 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json) book.bionicReadingOverride = clampInt8(obj["bionicReadingOverride"] | -1, -1, 1, -1); book.paragraphAlignmentOverride = clampInt8(obj["paragraphAlignmentOverride"] | -1, -1, CrossPointSettings::PARAGRAPH_ALIGNMENT_COUNT - 1, -1); + book.textAntiAliasingOverride = clampInt8(obj["textAntiAliasingOverride"] | -1, -1, 1, -1); + book.hyphenationOverride = clampInt8(obj["hyphenationOverride"] | -1, -1, 1, -1); store.recentBooks.push_back(book); } diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index bf53beea..a3db6d72 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -18,34 +18,27 @@ RecentBooksStore RecentBooksStore::instance; void RecentBooksStore::addBook(const std::string& path, const std::string& title, const std::string& author, const std::string& series, const std::string& coverBmpPath) { - int8_t embeddedStyleOverride = -1; - int8_t imageRenderingOverride = -1; - int8_t fontFamilyOverride = -1; - std::string sdFontFamilyOverride; - int8_t fontSizeOverride = -1; - int8_t bionicReadingOverride = -1; - int8_t paragraphAlignmentOverride = -1; + RecentBook newBook{path, title, author, series, coverBmpPath}; pruneMissing(); - // Remove existing entry if present + // Remove existing entry if present, preserving its per-book overrides. auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it != recentBooks.end()) { - embeddedStyleOverride = it->embeddedStyleOverride; - imageRenderingOverride = it->imageRenderingOverride; - fontFamilyOverride = it->fontFamilyOverride; - sdFontFamilyOverride = it->sdFontFamilyOverride; - fontSizeOverride = it->fontSizeOverride; - bionicReadingOverride = it->bionicReadingOverride; - paragraphAlignmentOverride = it->paragraphAlignmentOverride; + newBook.embeddedStyleOverride = it->embeddedStyleOverride; + newBook.imageRenderingOverride = it->imageRenderingOverride; + newBook.fontFamilyOverride = it->fontFamilyOverride; + newBook.sdFontFamilyOverride = it->sdFontFamilyOverride; + newBook.fontSizeOverride = it->fontSizeOverride; + newBook.bionicReadingOverride = it->bionicReadingOverride; + newBook.paragraphAlignmentOverride = it->paragraphAlignmentOverride; + newBook.textAntiAliasingOverride = it->textAntiAliasingOverride; + newBook.hyphenationOverride = it->hyphenationOverride; recentBooks.erase(it); } - // Add to front - recentBooks.insert(recentBooks.begin(), {path, title, author, series, coverBmpPath, embeddedStyleOverride, - imageRenderingOverride, fontFamilyOverride, sdFontFamilyOverride, - fontSizeOverride, bionicReadingOverride, paragraphAlignmentOverride}); + recentBooks.insert(recentBooks.begin(), std::move(newBook)); // Trim to max size if (recentBooks.size() > MAX_RECENT_BOOKS) { @@ -181,6 +174,21 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t if (it == recentBooks.end()) { return false; } + return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, + sdFontFamilyOverride, fontSizeOverride, static_cast(bionicReadingOverride ? 1 : 0), + paragraphAlignmentOverride, it->textAntiAliasingOverride, it->hyphenationOverride); +} + +bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, + const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const std::string& sdFontFamilyOverride, const int8_t fontSizeOverride, + const int8_t bionicReadingOverride, const int8_t paragraphAlignmentOverride, + const int8_t textAntiAliasingOverride, const int8_t hyphenationOverride) { + 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->imageRenderingOverride = imageRenderingOverride; @@ -189,6 +197,8 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t it->fontSizeOverride = fontSizeOverride; it->bionicReadingOverride = bionicReadingOverride; it->paragraphAlignmentOverride = paragraphAlignmentOverride; + it->textAntiAliasingOverride = textAntiAliasingOverride; + it->hyphenationOverride = hyphenationOverride; return saveToFile(); } diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index 6e7ae44f..d49510d0 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -23,6 +23,10 @@ struct RecentBook { int8_t bionicReadingOverride = -1; // -1 = use global setting, otherwise CrossPointSettings::PARAGRAPH_ALIGNMENT value. int8_t paragraphAlignmentOverride = -1; + // -1 = use global default, otherwise explicit per-book override (0 = off, 1 = on). + int8_t textAntiAliasingOverride = -1; + // -1 = use global default, otherwise explicit per-book override (0 = off, 1 = on). + int8_t hyphenationOverride = -1; bool operator==(const RecentBook& other) const { return path == other.path; } }; @@ -89,6 +93,12 @@ class RecentBooksStore { bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, bool bionicReadingOverride, int8_t paragraphAlignmentOverride); + // Master overload — covers every per-book override. The narrower overloads above + // all funnel through here, preserving any fields they don't take as arguments. + bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, + int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, + int8_t bionicReadingOverride, int8_t paragraphAlignmentOverride, + int8_t textAntiAliasingOverride, int8_t hyphenationOverride); }; // Helper macro to access recent books store diff --git a/src/SettingsList.h b/src/SettingsList.h index 6fd8bfce..00e9251c 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -179,7 +179,7 @@ inline const std::vector list = { StrId::STR_BTN_ACT_OPEN_BOOKMARKS, StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, \ StrId::STR_BTN_ACT_NEXT_SECTION, StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, \ StrId::STR_BTN_ACT_READER_MENU, StrId::STR_BTN_ACT_TOGGLE_BIONIC_READING, StrId::STR_BTN_ACT_KOREADER_SYNC, \ - StrId::STR_BTN_ACT_CYCLE_FONT_SIZE, StrId::STR_BTN_ACT_CYCLE_ORIENTATION + StrId::STR_BTN_ACT_CYCLE_FONT_SIZE, StrId::STR_BTN_ACT_CYCLE_ORIENTATION, StrId::STR_BTN_ACT_QUICK_OVERRIDES // Back button: short=exit reader, double=ignore, long=go home SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, {StrId::STR_BTN_DEF_EXIT_READER}, diff --git a/src/activities/ActivityResult.h b/src/activities/ActivityResult.h index 25e4a41e..daf9437e 100644 --- a/src/activities/ActivityResult.h +++ b/src/activities/ActivityResult.h @@ -31,6 +31,8 @@ struct MenuResult { uint8_t textDarkness = 1; uint8_t bionicReadingOverride = 0; int8_t paragraphAlignmentOverride = -1; + int8_t textAntiAliasingOverride = -1; + int8_t hyphenationOverride = -1; }; struct ChapterResult { diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index b6afdc34..96f42a4c 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -33,6 +33,7 @@ #include "KOReaderDocumentId.h" #include "MappedInputManager.h" #include "QrDisplayActivity.h" +#include "QuickOverridesActivity.h" #include "ReaderActivity.h" #include "ReaderUtils.h" #include "ReadingSessionTracker.h" @@ -333,6 +334,8 @@ void EpubReaderActivity::onEnter() { ? static_cast(currentBook.bionicReadingOverride) : static_cast(SETTINGS.bionicReading); bookParagraphAlignmentOverride = currentBook.paragraphAlignmentOverride; + bookTextAntiAliasingOverride = currentBook.textAntiAliasingOverride; + bookHyphenationOverride = currentBook.hyphenationOverride; logReaderMemSnapshot("onEnter_after_recent_books"); // Start a reading-stats session. We use the cheap filename-based hash here: @@ -1350,6 +1353,15 @@ void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOver const std::string& sdFontFamilyOverride, const int8_t fontSizeOverride, const bool bionicReadingOverride, const int8_t paragraphAlignmentOverride) { + applyBookReaderOverrides(embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, sdFontFamilyOverride, + fontSizeOverride, static_cast(bionicReadingOverride ? 1 : 0), + paragraphAlignmentOverride, bookTextAntiAliasingOverride, bookHyphenationOverride); +} + +void EpubReaderActivity::applyBookReaderOverrides( + const int8_t embeddedStyleOverride, const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const std::string& sdFontFamilyOverride, const int8_t fontSizeOverride, const int8_t bionicReadingOverride, + const int8_t paragraphAlignmentOverride, const int8_t textAntiAliasingOverride, const int8_t hyphenationOverride) { if (!epub) { return; } @@ -1363,11 +1375,12 @@ void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOver normalizedFontFamilyOverride = -1; } + const bool bionicAsBool = bionicReadingOverride > 0; if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride && bookFontFamilyOverride == normalizedFontFamilyOverride && bookSdFontFamilyOverride == normalizedSdFontFamilyOverride && bookFontSizeOverride == fontSizeOverride && - bookBionicReadingOverride == bionicReadingOverride && - bookParagraphAlignmentOverride == paragraphAlignmentOverride) { + bookBionicReadingOverride == bionicAsBool && bookParagraphAlignmentOverride == paragraphAlignmentOverride && + bookTextAntiAliasingOverride == textAntiAliasingOverride && bookHyphenationOverride == hyphenationOverride) { return; } @@ -1376,11 +1389,14 @@ void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOver bookFontFamilyOverride = normalizedFontFamilyOverride; bookSdFontFamilyOverride = normalizedSdFontFamilyOverride; bookFontSizeOverride = fontSizeOverride; - bookBionicReadingOverride = bionicReadingOverride; + bookBionicReadingOverride = bionicAsBool; bookParagraphAlignmentOverride = paragraphAlignmentOverride; + bookTextAntiAliasingOverride = textAntiAliasingOverride; + bookHyphenationOverride = hyphenationOverride; RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, bookSdFontFamilyOverride, bookFontSizeOverride, - bookBionicReadingOverride, bookParagraphAlignmentOverride); + bookBionicReadingOverride, bookParagraphAlignmentOverride, + bookTextAntiAliasingOverride, bookHyphenationOverride); RenderLock lock(*this); if (section) { @@ -1405,6 +1421,20 @@ uint8_t EpubReaderActivity::getEffectiveImageRendering() const { return SETTINGS.imageRendering; } +bool EpubReaderActivity::getEffectiveTextAntiAliasing() const { + if (bookTextAntiAliasingOverride >= 0) { + return bookTextAntiAliasingOverride != 0; + } + return SETTINGS.textAntiAliasing != 0; +} + +bool EpubReaderActivity::getEffectiveHyphenation() const { + if (bookHyphenationOverride >= 0) { + return bookHyphenationOverride != 0; + } + return SETTINGS.hyphenationEnabled != 0; +} + uint8_t EpubReaderActivity::getEffectiveParagraphAlignment() const { if (bookParagraphAlignmentOverride >= 0) { return static_cast(bookParagraphAlignmentOverride); @@ -1690,7 +1720,7 @@ void EpubReaderActivity::render(RenderLock&& lock) { lastRenderStats.embeddedStyle = getEffectiveEmbeddedStyle(); lastRenderStats.imageRendering = getEffectiveImageRendering(); lastRenderStats.effectiveFontId = getEffectiveReaderFontId(); - lastRenderStats.textAntiAliasing = SETTINGS.textAntiAliasing; + lastRenderStats.textAntiAliasing = getEffectiveTextAntiAliasing(); lastRenderStats.freeHeapBefore = esp_get_free_heap_size(); lastRenderStats.largestFreeBlockBefore = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT); showTruncatedSectionHintThisRender = false; @@ -1775,7 +1805,7 @@ void EpubReaderActivity::render(RenderLock&& lock) { if (!section->loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), SETTINGS.extraParagraphSpacing, getEffectiveParagraphAlignment(), viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, bookBionicReadingOverride, + viewportHeight, getEffectiveHyphenation(), embeddedStyle, bookBionicReadingOverride, imageRendering)) { LOG_DBG("ERS", "Cache not found, building..."); lastRenderStats.cacheRebuilt = true; @@ -1797,7 +1827,7 @@ void EpubReaderActivity::render(RenderLock&& lock) { renderer.clearSdCardFontAccumulation(); if (!section->createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), SETTINGS.extraParagraphSpacing, getEffectiveParagraphAlignment(), viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, + viewportHeight, getEffectiveHyphenation(), embeddedStyle, bookBionicReadingOverride, imageRendering, progressFn)) { LOG_ERR("ERS", "Failed to persist page data to SD"); section.reset(); @@ -1819,12 +1849,12 @@ void EpubReaderActivity::render(RenderLock&& lock) { renderer.clearSdCardFontAccumulation(); if (!section->createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), SETTINGS.extraParagraphSpacing, getEffectiveParagraphAlignment(), viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, + viewportHeight, getEffectiveHyphenation(), embeddedStyle, bookBionicReadingOverride, imageRendering, progressFn)) { LOG_ERR("ERS", "Failed to rebuild CSS section cache; keeping fallback"); section->loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), SETTINGS.extraParagraphSpacing, getEffectiveParagraphAlignment(), viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, bookBionicReadingOverride, + viewportHeight, getEffectiveHyphenation(), embeddedStyle, bookBionicReadingOverride, imageRendering); } } else { @@ -1945,7 +1975,7 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW Section nextSection(epub, nextSpineIndex, renderer); if (nextSection.loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, bookBionicReadingOverride, + viewportHeight, getEffectiveHyphenation(), embeddedStyle, bookBionicReadingOverride, imageRendering)) { return; } @@ -1955,7 +1985,7 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW renderer.clearSdCardFontAccumulation(); if (!nextSection.createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, + viewportHeight, getEffectiveHyphenation(), embeddedStyle, bookBionicReadingOverride, imageRendering)) { LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex); } @@ -2010,7 +2040,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or contigAfter, (int32_t)heapAfter - (int32_t)heapBefore); logReaderMemSnapshot("prewarm_end"); - const bool aaConfigured = SETTINGS.textAntiAliasing; + const bool aaConfigured = getEffectiveTextAntiAliasing(); bool aaEnabledForThisRender = aaConfigured; if (aaConfigured && antiAliasingSuspendedLowMemory) { const uint32_t freeHeapNow = esp_get_free_heap_size(); @@ -2288,7 +2318,7 @@ void EpubReaderActivity::displayPreRenderedPage(const Page& page, const int orie // Grayscale AA pass. Prefer the tiled strip path (no BW snapshot needed); // fall back to the legacy storeBwBufferRect path on controllers that don't // support strip grayscale or when the strip scratch can't be allocated. - const bool aaConfigured = SETTINGS.textAntiAliasing && !antiAliasingSuspendedLowMemory; + const bool aaConfigured = getEffectiveTextAntiAliasing() && !antiAliasingSuspendedLowMemory; if (aaConfigured) { if (runTiledGrayscalePass(renderer, page, getEffectiveReaderFontId(), orientedMarginLeft, contentTop, SETTINGS.fastAntiAliasing)) { @@ -2575,6 +2605,22 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf return true; } +void EpubReaderActivity::openQuickOverrides() { + ReaderUtils::enforceExitFullRefresh(renderer); + startActivityForResult( + std::make_unique( + renderer, mappedInput, bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, + bookSdFontFamilyOverride, bookFontSizeOverride, static_cast(bookBionicReadingOverride ? 1 : 0), + bookParagraphAlignmentOverride, bookTextAntiAliasingOverride, bookHyphenationOverride), + [this](const ActivityResult& result) { + const auto& menu = std::get(result.data); + applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride, + menu.sdFontFamilyOverride, menu.fontSizeOverride, + static_cast(menu.bionicReadingOverride), menu.paragraphAlignmentOverride, + menu.textAntiAliasingOverride, menu.hyphenationOverride); + }); +} + void EpubReaderActivity::openReaderMenu() { const int currentPage = section ? section->currentPage + 1 : 0; const int totalPages = section ? section->pageCount : 0; @@ -2762,6 +2808,11 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION case BA::BTN_KOREADER_SYNC: launchKOReaderSync(SyncLaunchMode::COMPARE); break; + case BA::BTN_QUICK_OVERRIDES: + if (epub) { + openQuickOverrides(); + } + break; default: break; } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 6bcafb1d..247c59bb 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -235,6 +235,8 @@ class EpubReaderActivity final : public Activity { int8_t bookFontSizeOverride = -1; bool bookBionicReadingOverride = false; int8_t bookParagraphAlignmentOverride = -1; + int8_t bookTextAntiAliasingOverride = -1; + int8_t bookHyphenationOverride = -1; // Bookmarks (starred pages) BookmarkStore bookmarkStore; @@ -299,10 +301,20 @@ class EpubReaderActivity final : public Activity { void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, bool bionicReadingOverride, int8_t paragraphAlignmentOverride); + // Wider variant that also covers AA and hyphenation. Used by QuickOverridesActivity; + // the narrower overload above funnels through here, preserving the AA/hyphenation + // values currently held on this activity. + void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, + const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, + int8_t bionicReadingOverride, int8_t paragraphAlignmentOverride, + int8_t textAntiAliasingOverride, int8_t hyphenationOverride); void openReaderMenu(); + void openQuickOverrides(); bool getEffectiveEmbeddedStyle() const; uint8_t getEffectiveImageRendering() const; uint8_t getEffectiveParagraphAlignment() const; + bool getEffectiveTextAntiAliasing() const; + bool getEffectiveHyphenation() const; int getEffectiveReaderFontId() const; float getEffectiveReaderLineCompression() const; bool stepPageState(bool isForwardTurn); diff --git a/src/activities/reader/QuickOverridesActivity.cpp b/src/activities/reader/QuickOverridesActivity.cpp new file mode 100644 index 00000000..7316e09f --- /dev/null +++ b/src/activities/reader/QuickOverridesActivity.cpp @@ -0,0 +1,216 @@ +#include "QuickOverridesActivity.h" + +#include +#include +#include + +#include "MappedInputManager.h" +#include "activities/ActivityResult.h" +#include "components/UITheme.h" +#include "fontIds.h" + +QuickOverridesActivity::QuickOverridesActivity( + GfxRenderer& renderer, MappedInputManager& mappedInput, const int8_t initialEmbeddedStyleOverride, + const int8_t initialImageRenderingOverride, const int8_t initialFontFamilyOverride, + const std::string& initialSdFontFamilyOverride, const int8_t initialFontSizeOverride, + const int8_t initialBionicReadingOverride, const int8_t initialParagraphAlignmentOverride, + const int8_t initialTextAntiAliasingOverride, const int8_t initialHyphenationOverride) + : MenuListActivity("QuickOverrides", renderer, mappedInput), + pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride), + pendingImageRenderingOverride(initialImageRenderingOverride), + pendingFontFamilyOverride(initialFontFamilyOverride), + pendingSdFontFamilyOverride(initialSdFontFamilyOverride), + pendingFontSizeOverride(initialFontSizeOverride), + pendingBionicReadingOverride(initialBionicReadingOverride), + pendingParagraphAlignmentOverride(initialParagraphAlignmentOverride), + pendingTextAntiAliasingOverride(initialTextAntiAliasingOverride), + pendingHyphenationOverride(initialHyphenationOverride) { + buildMenuItems(); +} + +namespace { + +// Three-state cycle helper for overrides represented as int8_t with -1 = default. +// slot 0 -> -1 (default) +// slot 1 -> 1 (on) +// slot 2 -> 0 (off) +uint8_t threeStateSlotFromOverride(int8_t value) { + if (value < 0) return 0; + if (value > 0) return 1; + return 2; +} + +int8_t threeStateOverrideFromSlot(uint8_t slot) { + if (slot == 0) return -1; + if (slot == 1) return 1; + return 0; +} + +} // namespace + +void QuickOverridesActivity::buildMenuItems() { + menuItems.reserve(8); + + auto* self = this; + + // Embedded style: default / on / off + 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 { + return threeStateSlotFromOverride(static_cast(ctx)->pendingEmbeddedStyleOverride); + }, + [](void* ctx, uint8_t v) { + static_cast(ctx)->pendingEmbeddedStyleOverride = threeStateOverrideFromSlot(v); + })); + + // Image rendering: 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(ctx); + return (s->pendingImageRenderingOverride < 0) ? 0 : (s->pendingImageRenderingOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingImageRenderingOverride = (v == 0) ? -1 : static_cast(v - 1); + })); + + // Font family: built-in only — preserves any SD-family override unless the user + // explicitly picks a built-in (which clears it, matching the storage semantics). + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_FONT_FAMILY, {StrId::STR_DEFAULT_VALUE, StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS}, self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingFontFamilyOverride < 0) ? 0 : static_cast(s->pendingFontFamilyOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + if (v == 0) { + s->pendingFontFamilyOverride = -1; + } else { + s->pendingFontFamilyOverride = static_cast(v - 1); + s->pendingSdFontFamilyOverride.clear(); + } + })); + + // Font size: default(-1) -> Small(0) -> Medium(1) -> Large(2) -> X Large(3) -> Tiny(4) + 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, + StrId::STR_TINY}, + self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingFontSizeOverride < 0) ? 0 : static_cast(s->pendingFontSizeOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingFontSizeOverride = (v == 0) ? -1 : static_cast(v - 1); + })); + + // Bionic: default / on / off + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_BIONIC_READING, {StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self, + [](const void* ctx) -> uint8_t { + return threeStateSlotFromOverride(static_cast(ctx)->pendingBionicReadingOverride); + }, + [](void* ctx, uint8_t v) { + static_cast(ctx)->pendingBionicReadingOverride = threeStateOverrideFromSlot(v); + })); + + // Paragraph alignment: default(-1) + the 5 global options + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_PARA_ALIGNMENT, + {StrId::STR_DEFAULT_VALUE, StrId::STR_JUSTIFY, StrId::STR_ALIGN_LEFT, StrId::STR_CENTER, StrId::STR_ALIGN_RIGHT, + StrId::STR_BOOK_S_STYLE}, + self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingParagraphAlignmentOverride < 0) ? 0 + : static_cast(s->pendingParagraphAlignmentOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingParagraphAlignmentOverride = (v == 0) ? -1 : static_cast(v - 1); + })); + + // Text anti-aliasing: default / on / off + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_TEXT_AA, {StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self, + [](const void* ctx) -> uint8_t { + return threeStateSlotFromOverride( + static_cast(ctx)->pendingTextAntiAliasingOverride); + }, + [](void* ctx, uint8_t v) { + static_cast(ctx)->pendingTextAntiAliasingOverride = threeStateOverrideFromSlot(v); + })); + + // Hyphenation: default / on / off + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_HYPHENATION, {StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self, + [](const void* ctx) -> uint8_t { + return threeStateSlotFromOverride(static_cast(ctx)->pendingHyphenationOverride); + }, + [](void* ctx, uint8_t v) { + static_cast(ctx)->pendingHyphenationOverride = threeStateOverrideFromSlot(v); + })); +} + +void QuickOverridesActivity::onEnter() { + MenuListActivity::onEnter(); + // Fast refresh keeps the menu feeling responsive when cycling options. The + // parent reader is responsible for its own full refresh on resume if any + // override actually changed (applyBookReaderOverrides short-circuits if not). + renderer.setNextDisplayRefreshMode(HalDisplay::FAST_REFRESH); +} + +void QuickOverridesActivity::onSettingToggled(int /*index*/) { + // No persistence on each toggle — overrides are committed on exit through + // the result handler in the parent reader activity. + renderer.setNextDisplayRefreshMode(HalDisplay::FAST_REFRESH); +} + +void QuickOverridesActivity::onBackPressed() { + finishWithResult(/*cancelled=*/false); +} + +void QuickOverridesActivity::finishWithResult(bool cancelled) { + ActivityResult result; + result.isCancelled = cancelled; + MenuResult payload; + payload.action = -1; + payload.nameId = -1; + payload.embeddedStyleOverride = pendingEmbeddedStyleOverride; + payload.imageRenderingOverride = pendingImageRenderingOverride; + payload.fontFamilyOverride = pendingFontFamilyOverride; + payload.sdFontFamilyOverride = pendingSdFontFamilyOverride; + payload.fontSizeOverride = pendingFontSizeOverride; + payload.bionicReadingOverride = (pendingBionicReadingOverride > 0) ? 1 : 0; + payload.paragraphAlignmentOverride = pendingParagraphAlignmentOverride; + payload.textAntiAliasingOverride = pendingTextAntiAliasingOverride; + payload.hyphenationOverride = pendingHyphenationOverride; + result.data = std::move(payload); + setResult(std::move(result)); + finish(); +} + +void QuickOverridesActivity::render(RenderLock&&) { + renderer.clearScreen(); + const Rect contentRect = UITheme::getContentRect(renderer, true, false); + + const std::string title = tr(STR_QUICK_OVERRIDES); + const int titleX = + contentRect.x + (contentRect.width - renderer.getTextWidth(UI_12_FONT_ID, title.c_str(), EpdFontFamily::BOLD)) / 2; + renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentRect.y, title.c_str(), true, EpdFontFamily::BOLD); + + const int startY = 50 + contentRect.y; + const int listHeight = contentRect.height - (startY - contentRect.y); + drawMenuList(Rect{contentRect.x, startY, contentRect.width, listHeight}); + + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(HalDisplay::FAST_REFRESH); +} diff --git a/src/activities/reader/QuickOverridesActivity.h b/src/activities/reader/QuickOverridesActivity.h new file mode 100644 index 00000000..25ff4368 --- /dev/null +++ b/src/activities/reader/QuickOverridesActivity.h @@ -0,0 +1,42 @@ +#pragma once +#include + +#include + +#include "../MenuListActivity.h" + +// Bottom-of-the-toolbox quick-overrides menu: shows only the per-book overrides +// (font family, size, embedded style, image rendering, bionic, paragraph +// alignment, anti-aliasing, hyphenation). Optimised for snappy in-book toggling: +// renders in FAST_REFRESH mode and reports back through MenuResult; the parent +// reader applies the result through its existing applyBookReaderOverrides() +// path, which itself short-circuits when nothing changed. +class QuickOverridesActivity final : public MenuListActivity { + public: + QuickOverridesActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + int8_t initialEmbeddedStyleOverride, int8_t initialImageRenderingOverride, + int8_t initialFontFamilyOverride, const std::string& initialSdFontFamilyOverride, + int8_t initialFontSizeOverride, int8_t initialBionicReadingOverride, + int8_t initialParagraphAlignmentOverride, int8_t initialTextAntiAliasingOverride, + int8_t initialHyphenationOverride); + + void onEnter() override; + void render(RenderLock&&) override; + + private: + void buildMenuItems(); + void finishWithResult(bool cancelled); + + void onBackPressed() override; + void onSettingToggled(int index) override; + + int8_t pendingEmbeddedStyleOverride = -1; + int8_t pendingImageRenderingOverride = -1; + int8_t pendingFontFamilyOverride = -1; + std::string pendingSdFontFamilyOverride; + int8_t pendingFontSizeOverride = -1; + int8_t pendingBionicReadingOverride = -1; + int8_t pendingParagraphAlignmentOverride = -1; + int8_t pendingTextAntiAliasingOverride = -1; + int8_t pendingHyphenationOverride = -1; +}; diff --git a/src/main.cpp b/src/main.cpp index 01fc2934..cc26b763 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -817,6 +817,9 @@ void loop() { case BA::BTN_CYCLE_ORIENTATION: activityManager.dispatchButtonAction(BA::BTN_CYCLE_ORIENTATION); break; + case BA::BTN_QUICK_OVERRIDES: + activityManager.dispatchButtonAction(BA::BTN_QUICK_OVERRIDES); + break; default: break; }