From 4f140afd1050564daf11269928164dfce3bf3950 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Wed, 6 May 2026 23:09:26 +0200 Subject: [PATCH] Movable status bar and dual-edge progress bars --- .gitmodules | 2 +- lib/I18n/translations/english.yaml | 7 + src/CrossPointSettings.h | 12 +- src/JsonSettingsIO.cpp | 37 +++ src/SettingsList.h | 20 +- src/activities/reader/EpubReaderActivity.cpp | 99 ++++--- src/activities/reader/EpubReaderActivity.h | 1 + src/activities/reader/MdReaderActivity.cpp | 5 +- src/activities/reader/TxtReaderActivity.cpp | 13 +- .../settings/StatusBarSettingsActivity.cpp | 265 +++++++++++++----- src/components/UITheme.cpp | 73 ++++- src/components/UITheme.h | 7 +- src/components/themes/BaseTheme.cpp | 100 ++++--- src/components/themes/BaseTheme.h | 2 +- 14 files changed, 475 insertions(+), 168 deletions(-) diff --git a/.gitmodules b/.gitmodules index ec93156b..dc2023c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "open-x4-sdk"] path = open-x4-sdk url = https://github.com/jpirnay/community-sdk.git - branch = main + branch = fear-sdcard-capacity diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 1f2abb41..274888bd 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -301,6 +301,11 @@ STR_CHAPTER_PAGE_COUNT: "Chapter Page Count" STR_BOOK_PROGRESS_PERCENTAGE: "Book Progress Percentage" STR_PROGRESS_BAR: "Progress Bar" STR_PROGRESS_BAR_THICKNESS: "Progress Bar Thickness" +STR_UPPER_PROGRESS_BAR: "Upper Progress Bar" +STR_UPPER_PROGRESS_BAR_THICKNESS: "Upper Progress Bar Thickness" +STR_LOWER_PROGRESS_BAR: "Lower Progress Bar" +STR_LOWER_PROGRESS_BAR_THICKNESS: "Lower Progress Bar Thickness" +STR_STATUS_ITEMS_POSITION: "Status Items Position" STR_PROGRESS_BAR_THIN: "Thin" STR_PROGRESS_BAR_MEDIUM: "Medium" STR_PROGRESS_BAR_THICK: "Thick" @@ -311,6 +316,8 @@ STR_EXAMPLE_BOOK: "Book Title" STR_PREVIEW: "Preview" STR_TITLE: "Title" STR_BATTERY: "Battery" +STR_TOP: "Top" +STR_BOTTOM: "Bottom" STR_UI_THEME: "UI Theme" STR_THEME_CLASSIC: "Classic" STR_THEME_LYRA: "Lyra" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index b9c44513..1dfe164f 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -59,6 +59,11 @@ class CrossPointSettings { STATUS_BAR_PROGRESS_BAR_THICKNESS_COUNT }; enum STATUS_BAR_TITLE { BOOK_TITLE = 0, CHAPTER_TITLE = 1, HIDE_TITLE = 2, STATUS_BAR_TITLE_COUNT }; + enum STATUS_BAR_ITEMS_POSITION { + STATUS_BAR_ITEMS_TOP = 0, + STATUS_BAR_ITEMS_BOTTOM = 1, + STATUS_BAR_ITEMS_POSITION_COUNT + }; enum ORIENTATION { PORTRAIT = 0, // 480x800 logical coordinates (current default) @@ -185,12 +190,17 @@ class CrossPointSettings { uint8_t sleepCoverOverlay = 0; // Sleep image pick mode (random vs sequential walk-through) uint8_t sleepImagePickMode = PICK_RANDOM; - // Status bar settings (statusBar retained for migration only) + // Status bar settings (statusBar, statusBarProgressBar, statusBarProgressBarThickness retained for migration only) uint8_t statusBar = FULL; uint8_t statusBarChapterPageCount = 1; uint8_t statusBarBookProgressPercentage = 1; uint8_t statusBarProgressBar = HIDE_PROGRESS; uint8_t statusBarProgressBarThickness = PROGRESS_BAR_NORMAL; + uint8_t statusBarUpperProgressBar = HIDE_PROGRESS; + uint8_t statusBarUpperProgressBarThickness = PROGRESS_BAR_NORMAL; + uint8_t statusBarLowerProgressBar = HIDE_PROGRESS; + uint8_t statusBarLowerProgressBarThickness = PROGRESS_BAR_NORMAL; + uint8_t statusBarItemsPosition = STATUS_BAR_ITEMS_BOTTOM; uint8_t statusBarTitle = CHAPTER_TITLE; uint8_t statusBarBattery = 1; // Text rendering settings diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index aaa1fa87..acac9edc 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -24,6 +24,7 @@ void applyLegacyStatusBarSettings(CrossPointSettings& settings) { settings.statusBarChapterPageCount = 0; settings.statusBarBookProgressPercentage = 0; settings.statusBarProgressBar = CrossPointSettings::HIDE_PROGRESS; + settings.statusBarLowerProgressBar = CrossPointSettings::HIDE_PROGRESS; settings.statusBarTitle = CrossPointSettings::HIDE_TITLE; settings.statusBarBattery = 0; break; @@ -31,6 +32,7 @@ void applyLegacyStatusBarSettings(CrossPointSettings& settings) { settings.statusBarChapterPageCount = 0; settings.statusBarBookProgressPercentage = 0; settings.statusBarProgressBar = CrossPointSettings::HIDE_PROGRESS; + settings.statusBarLowerProgressBar = CrossPointSettings::HIDE_PROGRESS; settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; settings.statusBarBattery = 1; break; @@ -38,6 +40,7 @@ void applyLegacyStatusBarSettings(CrossPointSettings& settings) { settings.statusBarChapterPageCount = 1; settings.statusBarBookProgressPercentage = 0; settings.statusBarProgressBar = CrossPointSettings::BOOK_PROGRESS; + settings.statusBarLowerProgressBar = CrossPointSettings::BOOK_PROGRESS; settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; settings.statusBarBattery = 1; break; @@ -45,6 +48,7 @@ void applyLegacyStatusBarSettings(CrossPointSettings& settings) { settings.statusBarChapterPageCount = 1; settings.statusBarBookProgressPercentage = 0; settings.statusBarProgressBar = CrossPointSettings::BOOK_PROGRESS; + settings.statusBarLowerProgressBar = CrossPointSettings::BOOK_PROGRESS; settings.statusBarTitle = CrossPointSettings::HIDE_TITLE; settings.statusBarBattery = 0; break; @@ -52,6 +56,7 @@ void applyLegacyStatusBarSettings(CrossPointSettings& settings) { settings.statusBarChapterPageCount = 0; settings.statusBarBookProgressPercentage = 1; settings.statusBarProgressBar = CrossPointSettings::CHAPTER_PROGRESS; + settings.statusBarLowerProgressBar = CrossPointSettings::CHAPTER_PROGRESS; settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; settings.statusBarBattery = 1; break; @@ -60,10 +65,16 @@ void applyLegacyStatusBarSettings(CrossPointSettings& settings) { settings.statusBarChapterPageCount = 1; settings.statusBarBookProgressPercentage = 1; settings.statusBarProgressBar = CrossPointSettings::HIDE_PROGRESS; + settings.statusBarLowerProgressBar = CrossPointSettings::HIDE_PROGRESS; settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; settings.statusBarBattery = 1; break; } + + settings.statusBarUpperProgressBar = CrossPointSettings::HIDE_PROGRESS; + settings.statusBarUpperProgressBarThickness = CrossPointSettings::PROGRESS_BAR_NORMAL; + settings.statusBarLowerProgressBarThickness = settings.statusBarProgressBarThickness; + settings.statusBarItemsPosition = CrossPointSettings::STATUS_BAR_ITEMS_BOTTOM; } // ---- CrossPointState ---- @@ -215,9 +226,35 @@ bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool* // Legacy migration: if statusBarChapterPageCount is absent this is a pre-refactor settings file. // Populate s with migrated values now so the generic loop below picks them up as defaults and clamps them. if (doc["statusBarChapterPageCount"].isNull()) { + s.statusBar = clamp(doc["statusBar"] | s.statusBar, CrossPointSettings::STATUS_BAR_MODE_COUNT, s.statusBar); applyLegacyStatusBarSettings(s); + if (needsResave) *needsResave = true; } + auto migrateMissingStatusSetting = [&doc, &needsResave, &clamp](const char* newKey, uint8_t& value, + const char* legacyKey, uint8_t defaultValue, + uint8_t count) { + if (!doc[newKey].isNull()) { + return; + } + value = clamp(doc[legacyKey] | defaultValue, count, defaultValue); + if (needsResave) *needsResave = true; + }; + + migrateMissingStatusSetting("statusBarUpperProgressBar", s.statusBarUpperProgressBar, "statusBarUpperProgressBar", + CrossPointSettings::HIDE_PROGRESS, CrossPointSettings::STATUS_BAR_PROGRESS_BAR_COUNT); + migrateMissingStatusSetting("statusBarUpperProgressBarThickness", s.statusBarUpperProgressBarThickness, + "statusBarUpperProgressBarThickness", CrossPointSettings::PROGRESS_BAR_NORMAL, + CrossPointSettings::STATUS_BAR_PROGRESS_BAR_THICKNESS_COUNT); + migrateMissingStatusSetting("statusBarLowerProgressBar", s.statusBarLowerProgressBar, "statusBarProgressBar", + s.statusBarProgressBar, CrossPointSettings::STATUS_BAR_PROGRESS_BAR_COUNT); + migrateMissingStatusSetting("statusBarLowerProgressBarThickness", s.statusBarLowerProgressBarThickness, + "statusBarProgressBarThickness", s.statusBarProgressBarThickness, + CrossPointSettings::STATUS_BAR_PROGRESS_BAR_THICKNESS_COUNT); + migrateMissingStatusSetting("statusBarItemsPosition", s.statusBarItemsPosition, "statusBarItemsPosition", + CrossPointSettings::STATUS_BAR_ITEMS_BOTTOM, + CrossPointSettings::STATUS_BAR_ITEMS_POSITION_COUNT); + for (const auto& info : getSettingsList()) { if (!info.key) continue; // Dynamic entries (KOReader etc.) are stored in their own files — skip. diff --git a/src/SettingsList.h b/src/SettingsList.h index c2ad45b0..2f52448c 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -278,16 +278,18 @@ inline const std::vector list = { StrId::STR_KOREADER_SYNC), // --- Status Bar Settings (web-only, uses StatusBarSettingsActivity) --- + SettingInfo::Enum(StrId::STR_UPPER_PROGRESS_BAR, &CrossPointSettings::statusBarUpperProgressBar, + {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}, "statusBarUpperProgressBar", + StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_UPPER_PROGRESS_BAR_THICKNESS, &CrossPointSettings::statusBarUpperProgressBarThickness, + {StrId::STR_PROGRESS_BAR_THIN, StrId::STR_PROGRESS_BAR_MEDIUM, StrId::STR_PROGRESS_BAR_THICK}, + "statusBarUpperProgressBarThickness", StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_STATUS_ITEMS_POSITION, &CrossPointSettings::statusBarItemsPosition, + {StrId::STR_TOP, StrId::STR_BOTTOM}, "statusBarItemsPosition", StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount, "statusBarChapterPageCount", StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Toggle(StrId::STR_BOOK_PROGRESS_PERCENTAGE, &CrossPointSettings::statusBarBookProgressPercentage, "statusBarBookProgressPercentage", StrId::STR_CUSTOMISE_STATUS_BAR), - SettingInfo::Enum(StrId::STR_PROGRESS_BAR, &CrossPointSettings::statusBarProgressBar, - {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}, "statusBarProgressBar", - StrId::STR_CUSTOMISE_STATUS_BAR), - SettingInfo::Enum(StrId::STR_PROGRESS_BAR_THICKNESS, &CrossPointSettings::statusBarProgressBarThickness, - {StrId::STR_PROGRESS_BAR_THIN, StrId::STR_PROGRESS_BAR_MEDIUM, StrId::STR_PROGRESS_BAR_THICK}, - "statusBarProgressBarThickness", StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Enum(StrId::STR_TITLE, &CrossPointSettings::statusBarTitle, {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}, "statusBarTitle", StrId::STR_CUSTOMISE_STATUS_BAR), @@ -295,6 +297,12 @@ inline const std::vector list = { StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Toggle(StrId::STR_CLOCK, &CrossPointSettings::statusBarClock, "statusBarClock", StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_LOWER_PROGRESS_BAR, &CrossPointSettings::statusBarLowerProgressBar, + {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}, "statusBarLowerProgressBar", + StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_LOWER_PROGRESS_BAR_THICKNESS, &CrossPointSettings::statusBarLowerProgressBarThickness, + {StrId::STR_PROGRESS_BAR_THIN, StrId::STR_PROGRESS_BAR_MEDIUM, StrId::STR_PROGRESS_BAR_THICK}, + "statusBarLowerProgressBarThickness", StrId::STR_CUSTOMISE_STATUS_BAR), }; } // namespace SettingsListDetail diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 0a03cda5..9edded0a 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include "CrossPointSettings.h" @@ -111,6 +112,27 @@ const char* orientationToString(const GfxRenderer::Orientation orientation) { return "Unknown"; } +int getImageOnlyPageYOffset(const Page& page, const int viewportHeight) { + if (viewportHeight <= 0 || page.elements.empty()) { + return 0; + } + + const bool imageOnlyPage = std::all_of( + page.elements.begin(), page.elements.end(), + [](const std::shared_ptr& element) { return element && element->getTag() == TAG_PageImage; }); + if (!imageOnlyPage) { + return 0; + } + + int16_t imgX, imgY, imgW, imgH; + if (!page.getImageBoundingBox(imgX, imgY, imgW, imgH) || imgH >= viewportHeight) { + return 0; + } + + const int centeredTop = (viewportHeight - imgH) / 2; + return std::max(0, centeredTop - static_cast(imgY)); +} + } // namespace void EpubReaderActivity::onEnter() { @@ -241,7 +263,7 @@ void EpubReaderActivity::loop() { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || mappedInput.wasReleased(MappedInputManager::Button::Back)) { buttonEvents.drain(); - automaticPageTurnActive = false; + stopAutomaticPageTurn(); // updates chapter title space to indicate page turn disabled requestUpdate(); return; @@ -1009,9 +1031,30 @@ void EpubReaderActivity::applyTextDarkness(const uint8_t textDarkness) { requestUpdate(); } +void EpubReaderActivity::stopAutomaticPageTurn() { + if (!automaticPageTurnActive) { + return; + } + + automaticPageTurnActive = false; + + if (UITheme::getStatusBarHeight(true) == UITheme::getStatusBarHeight()) { + return; + } + + // Preserve current reading position so we can restore after reflow. + RenderLock lock(*this); + if (section) { + cachedSpineIndex = currentSpineIndex; + cachedChapterTotalPageCount = section->pageCount; + nextPageNumber = section->currentPage; + } + section.reset(); +} + void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption) { if (selectedPageTurnOption == 0 || selectedPageTurnOption >= std::size(PAGE_TURN_LABELS)) { - automaticPageTurnActive = false; + stopAutomaticPageTurn(); return; } @@ -1020,9 +1063,8 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption pageTurnDuration = (1UL * 60 * 1000) / PAGE_TURN_LABELS[selectedPageTurnOption]; automaticPageTurnActive = true; - const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight(); - // resets cached section so that space is reserved for auto page turn indicator when None or progress bar only - if (statusBarHeight == 0 || statusBarHeight == UITheme::getInstance().getProgressBarHeight()) { + // Reset cached section when automatic page turn adds a forced status item band. + if (UITheme::getStatusBarHeight(true) != UITheme::getStatusBarHeight()) { // Preserve current reading position so we can restore after reflow. RenderLock lock(*this); if (section) { @@ -1223,21 +1265,13 @@ void EpubReaderActivity::render(RenderLock&& lock) { int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, &orientedMarginLeft); - orientedMarginTop += SETTINGS.screenMargin; + const int statusBarTopHeight = UITheme::getStatusBarTopHeight(automaticPageTurnActive); + const int statusBarBottomHeight = UITheme::getStatusBarBottomHeight(automaticPageTurnActive); + + orientedMarginTop += std::max(static_cast(SETTINGS.screenMargin), statusBarTopHeight); orientedMarginLeft += SETTINGS.screenMargin; orientedMarginRight += SETTINGS.screenMargin; - - const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight(); - - // reserves space for automatic page turn indicator when no status bar or progress bar only - if (automaticPageTurnActive && - (statusBarHeight == 0 || statusBarHeight == UITheme::getInstance().getProgressBarHeight())) { - orientedMarginBottom += - std::max(SETTINGS.screenMargin, - static_cast(statusBarHeight + UITheme::getInstance().getMetrics().statusBarVerticalMargin)); - } else { - orientedMarginBottom += std::max(SETTINGS.screenMargin, statusBarHeight); - } + orientedMarginBottom += std::max(static_cast(SETTINGS.screenMargin), statusBarBottomHeight); const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; const uint16_t viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom; @@ -1485,10 +1519,13 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or fcm->resetStats(); logReaderMemSnapshot("prewarm_begin"); + const int viewportHeight = std::max(0, renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom); + const int contentTop = orientedMarginTop + getImageOnlyPageYOffset(*page, viewportHeight); + // Font prewarm: scan pass accumulates text, then prewarm, then real render const uint32_t heapBefore = esp_get_free_heap_size(); auto scope = fcm->createPrewarmScope(); - page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); // scan pass + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop); // scan pass scope.endScanAndPrewarm(); const uint32_t heapAfter = esp_get_free_heap_size(); fcm->logStats("prewarm"); @@ -1506,7 +1543,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or lastRenderStats.forcedHalfRefresh = forceHalfRefreshThisPage; logReaderMemSnapshot("before_bw_render"); - page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop); renderStatusBar(); fcm->logStats("bw_render"); const auto tBwRender = millis(); @@ -1520,12 +1557,12 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or // Step 2: Re-render with images and display again (images appear clean) int16_t imgX, imgY, imgW, imgH; if (page->getImageBoundingBox(imgX, imgY, imgW, imgH)) { - renderer.fillRect(imgX + orientedMarginLeft, imgY + orientedMarginTop, imgW, imgH, false); + renderer.fillRect(imgX + orientedMarginLeft, imgY + contentTop, imgW, imgH, false); renderer.displayBuffer(HalDisplay::FAST_REFRESH); // 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 %) - page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop); renderer.displayBuffer(HalDisplay::FAST_REFRESH); } else { renderer.displayBuffer(HalDisplay::HALF_REFRESH); @@ -1558,7 +1595,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or logReaderMemSnapshot("gray_lsb_begin"); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); - page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop); renderer.copyGrayscaleLsbBuffers(); const auto tGrayLsb = millis(); logReaderMemSnapshot("gray_lsb_end"); @@ -1567,7 +1604,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or logReaderMemSnapshot("gray_msb_begin"); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); - page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop); renderer.copyGrayscaleMsbBuffers(); const auto tGrayMsb = millis(); logReaderMemSnapshot("gray_msb_end"); @@ -1638,16 +1675,9 @@ void EpubReaderActivity::renderStatusBar() const { const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100; std::string title; - int textYOffset = 0; if (automaticPageTurnActive) { title = tr(STR_AUTO_TURN_ENABLED) + std::to_string(60 * 1000 / pageTurnDuration); - - const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight(); - if (statusBarHeight == 0 || statusBarHeight == UITheme::getInstance().getProgressBarHeight()) { - textYOffset += UITheme::getInstance().getMetrics().statusBarVerticalMargin; - } - } else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) { const int tocIndex = section ? section->getTocIndexForPage(section->currentPage) : epub->getTocIndexForSpineIndex(currentSpineIndex); @@ -1663,7 +1693,7 @@ void EpubReaderActivity::renderStatusBar() const { const bool isStarred = section && bookmarkStore.has(static_cast(currentSpineIndex), static_cast(section->currentPage)); - GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, textYOffset, isStarred); + GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title, 0, isStarred); } void EpubReaderActivity::navigateToHref(const std::string& hrefStr, const bool savePosition) { @@ -1754,11 +1784,10 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf // Compute margins exactly as render() does int marginTop, marginRight, marginBottom, marginLeft; renderer.getOrientedViewableTRBL(&marginTop, &marginRight, &marginBottom, &marginLeft); - marginTop += SETTINGS.screenMargin; + marginTop += std::max(static_cast(SETTINGS.screenMargin), UITheme::getStatusBarTopHeight()); marginLeft += SETTINGS.screenMargin; marginRight += SETTINGS.screenMargin; - const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight(); - marginBottom += std::max(SETTINGS.screenMargin, statusBarHeight); + marginBottom += std::max(static_cast(SETTINGS.screenMargin), UITheme::getStatusBarBottomHeight()); const uint16_t viewportWidth = renderer.getScreenWidth() - marginLeft - marginRight; const uint16_t viewportHeight = renderer.getScreenHeight() - marginTop - marginBottom; diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 5c54058f..507bcb0b 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -177,6 +177,7 @@ class EpubReaderActivity final : public Activity { void applyOrientation(uint8_t orientation); void applyTextDarkness(uint8_t textDarkness); void toggleAutoPageTurn(uint8_t selectedPageTurnOption); + void stopAutomaticPageTurn(); void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, bool bionicReadingOverride); diff --git a/src/activities/reader/MdReaderActivity.cpp b/src/activities/reader/MdReaderActivity.cpp index 3b0a7f65..6c211221 100644 --- a/src/activities/reader/MdReaderActivity.cpp +++ b/src/activities/reader/MdReaderActivity.cpp @@ -292,11 +292,10 @@ void MdReaderActivity::initializeReader() { renderer.getOrientedViewableTRBL(&cachedOrientedMarginTop, &cachedOrientedMarginRight, &cachedOrientedMarginBottom, &cachedOrientedMarginLeft); - cachedOrientedMarginTop += cachedScreenMargin; + cachedOrientedMarginTop += std::max(static_cast(cachedScreenMargin), UITheme::getStatusBarTopHeight()); cachedOrientedMarginLeft += cachedScreenMargin; cachedOrientedMarginRight += cachedScreenMargin; - cachedOrientedMarginBottom += - std::max(cachedScreenMargin, static_cast(UITheme::getInstance().getStatusBarHeight())); + cachedOrientedMarginBottom += std::max(static_cast(cachedScreenMargin), UITheme::getStatusBarBottomHeight()); viewportWidth = renderer.getScreenWidth() - cachedOrientedMarginLeft - cachedOrientedMarginRight; const int viewportHeight = renderer.getScreenHeight() - cachedOrientedMarginTop - cachedOrientedMarginBottom; diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 4ba4f35a..0222a8cc 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "CrossPointSettings.h" #include "CrossPointState.h" #include "GlobalBookmarkIndex.h" @@ -207,11 +209,10 @@ void TxtReaderActivity::initializeReader() { // Calculate viewport dimensions renderer.getOrientedViewableTRBL(&cachedOrientedMarginTop, &cachedOrientedMarginRight, &cachedOrientedMarginBottom, &cachedOrientedMarginLeft); - cachedOrientedMarginTop += cachedScreenMargin; + cachedOrientedMarginTop += std::max(static_cast(cachedScreenMargin), UITheme::getStatusBarTopHeight()); cachedOrientedMarginLeft += cachedScreenMargin; cachedOrientedMarginRight += cachedScreenMargin; - cachedOrientedMarginBottom += - std::max(cachedScreenMargin, static_cast(UITheme::getInstance().getStatusBarHeight())); + cachedOrientedMarginBottom += std::max(static_cast(cachedScreenMargin), UITheme::getStatusBarBottomHeight()); viewportWidth = renderer.getScreenWidth() - cachedOrientedMarginLeft - cachedOrientedMarginRight; const int viewportHeight = renderer.getScreenHeight() - cachedOrientedMarginTop - cachedOrientedMarginBottom; @@ -407,7 +408,7 @@ void TxtReaderActivity::renderStatusBar() const { title = txt->getTitle(); } const bool isStarred = bookmarkStore.has(0, static_cast(currentPage)); - GUI.drawStatusBar(renderer, progress, currentPage + 1, totalPages, title, 0, 0, isStarred); + GUI.drawStatusBar(renderer, progress, currentPage + 1, totalPages, title, 0, isStarred); } void TxtReaderActivity::saveProgress() const { @@ -644,10 +645,10 @@ bool TxtReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gfx int marginTop, marginRight, marginBottom, marginLeft; renderer.getOrientedViewableTRBL(&marginTop, &marginRight, &marginBottom, &marginLeft); - marginTop += screenMargin; + marginTop += std::max(static_cast(screenMargin), UITheme::getStatusBarTopHeight()); marginLeft += screenMargin; marginRight += screenMargin; - marginBottom += std::max(screenMargin, static_cast(UITheme::getInstance().getStatusBarHeight())); + marginBottom += std::max(static_cast(screenMargin), UITheme::getStatusBarBottomHeight()); const int vw = renderer.getScreenWidth() - marginLeft - marginRight; const int vh = renderer.getScreenHeight() - marginTop - marginBottom; diff --git a/src/activities/settings/StatusBarSettingsActivity.cpp b/src/activities/settings/StatusBarSettingsActivity.cpp index 883a8b03..7d71a0f5 100644 --- a/src/activities/settings/StatusBarSettingsActivity.cpp +++ b/src/activities/settings/StatusBarSettingsActivity.cpp @@ -3,7 +3,10 @@ #include #include +#include +#include #include +#include #include "CrossPointSettings.h" #include "MappedInputManager.h" @@ -11,14 +14,19 @@ #include "fontIds.h" namespace { -constexpr int MENU_ITEMS = 7; -const StrId menuNames[MENU_ITEMS] = {StrId::STR_CHAPTER_PAGE_COUNT, - StrId::STR_BOOK_PROGRESS_PERCENTAGE, - StrId::STR_PROGRESS_BAR, - StrId::STR_PROGRESS_BAR_THICKNESS, - StrId::STR_TITLE, - StrId::STR_BATTERY, - StrId::STR_CLOCK}; +// Base menu items (without clock). Clock is appended conditionally at index 5. +constexpr int MENU_ITEMS_NO_CLOCK = 9; +constexpr int MENU_ITEMS_WITH_CLOCK = 10; +const StrId menuNames[MENU_ITEMS_WITH_CLOCK] = {StrId::STR_STATUS_ITEMS_POSITION, + StrId::STR_CHAPTER_PAGE_COUNT, + StrId::STR_BOOK_PROGRESS_PERCENTAGE, + StrId::STR_TITLE, + StrId::STR_BATTERY, + StrId::STR_CLOCK, + StrId::STR_UPPER_PROGRESS_BAR, + StrId::STR_UPPER_PROGRESS_BAR_THICKNESS, + StrId::STR_LOWER_PROGRESS_BAR, + StrId::STR_LOWER_PROGRESS_BAR_THICKNESS}; constexpr int PROGRESS_BAR_ITEMS = 3; const StrId progressBarNames[PROGRESS_BAR_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}; @@ -29,26 +37,130 @@ const StrId progressBarThicknessNames[PROGRESS_BAR_THICKNESS_ITEMS] = { constexpr int TITLE_ITEMS = 3; const StrId titleNames[TITLE_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}; -const int widthMargin = 10; -const int verticalPreviewPadding = 50; -const int verticalPreviewTextPadding = 40; +constexpr int STATUS_ITEMS_POSITION_ITEMS = 2; +const StrId statusItemsPositionNames[STATUS_ITEMS_POSITION_ITEMS] = {StrId::STR_TOP, StrId::STR_BOTTOM}; + +constexpr int previewHorizontalInset = 10; +constexpr int previewHeight = 78; +constexpr int previewInnerMargin = 4; + +void drawPreviewProgressBar(const GfxRenderer& renderer, const Rect& rect, const uint8_t progressBar, + const uint8_t thickness, const bool topEdge) { + if (progressBar == CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS) { + return; + } + + const int percent = progressBar == CrossPointSettings::STATUS_BAR_PROGRESS_BAR::BOOK_PROGRESS ? 75 : 25; + const int barHeight = (thickness + 1) * 2; + const int y = topEdge ? rect.y + previewInnerMargin : rect.y + rect.height - previewInnerMargin - barHeight; + const int barWidth = (rect.width - previewInnerMargin * 2) * percent / 100; + renderer.fillRect(rect.x + previewInnerMargin, y, barWidth, barHeight); +} + +void drawPreviewStatusItems(const GfxRenderer& renderer, const Rect& rect, const ThemeMetrics& metrics) { + const bool hasProgressText = SETTINGS.statusBarChapterPageCount || SETTINGS.statusBarBookProgressPercentage; + const bool hasTitle = SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE; + const bool hasStatusItems = + hasProgressText || hasTitle || SETTINGS.statusBarBattery || (SETTINGS.useClock && SETTINGS.statusBarClock); + if (!hasStatusItems) { + return; + } + + const bool statusItemsAtTop = + SETTINGS.statusBarItemsPosition == CrossPointSettings::STATUS_BAR_ITEMS_POSITION::STATUS_BAR_ITEMS_TOP; + const int adjacentProgressHeight = statusItemsAtTop + ? UITheme::getProgressBarHeight(SETTINGS.statusBarUpperProgressBar, + SETTINGS.statusBarUpperProgressBarThickness) + : UITheme::getProgressBarHeight(SETTINGS.statusBarLowerProgressBar, + SETTINGS.statusBarLowerProgressBarThickness); + const int statusItemsHeight = UITheme::getStatusBarItemsHeight(); + const int textY = statusItemsAtTop + ? rect.y + previewInnerMargin + adjacentProgressHeight + 4 + : rect.y + rect.height - previewInnerMargin - adjacentProgressHeight - statusItemsHeight + 4; + + const bool showBatteryPercentage = + SETTINGS.statusBarBattery && + SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; + if (SETTINGS.statusBarBattery) { + GUI.drawBatteryLeft(renderer, + Rect{rect.x + previewInnerMargin + 2, textY, metrics.batteryWidth, metrics.batteryHeight}, + showBatteryPercentage); + } + + int progressTextWidth = 0; + if (hasProgressText) { + char progressStr[32] = ""; + if (SETTINGS.statusBarChapterPageCount && SETTINGS.statusBarBookProgressPercentage) { + snprintf(progressStr, sizeof(progressStr), "%d/%d %d%%", 8, 32, 75); + } else if (SETTINGS.statusBarBookProgressPercentage) { + snprintf(progressStr, sizeof(progressStr), "%d%%", 75); + } else { + snprintf(progressStr, sizeof(progressStr), "%d/%d", 8, 32); + } + + progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); + renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - previewInnerMargin - 2 - progressTextWidth, textY, + progressStr); + } + + if (!hasTitle) { + return; + } + + const char* title = SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE + ? tr(STR_EXAMPLE_BOOK) + : tr(STR_EXAMPLE_CHAPTER); + int leftReserve = 6; + if (SETTINGS.statusBarBattery) { + leftReserve = metrics.batteryWidth + 28; + if (showBatteryPercentage) { + const int percentReserve = 2 + metrics.batteryWidth + BaseTheme::batteryPercentSpacing + + renderer.getTextWidth(SMALL_FONT_ID, "100%") + 8; + leftReserve = std::max(leftReserve, percentReserve); + } + } + const int rightReserve = progressTextWidth > 0 ? progressTextWidth + 18 : 6; + const int titleAreaWidth = rect.width - previewInnerMargin * 2 - leftReserve - rightReserve; + if (titleAreaWidth <= 0) { + return; + } + + std::string previewTitle = renderer.truncatedText(SMALL_FONT_ID, title, titleAreaWidth); + const int titleWidth = renderer.getTextWidth(SMALL_FONT_ID, previewTitle.c_str()); + renderer.drawText(SMALL_FONT_ID, rect.x + previewInnerMargin + leftReserve + (titleAreaWidth - titleWidth) / 2, textY, + previewTitle.c_str()); +} } // namespace void StatusBarSettingsActivity::onEnter() { Activity::onEnter(); - const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1; + const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; if (selectedIndex >= menuCount) { selectedIndex = 0; } - // Clamp statusBarProgressBar and statusBarTitle in case of corrupt/migrated data - if (SETTINGS.statusBarProgressBar >= PROGRESS_BAR_ITEMS) { - SETTINGS.statusBarProgressBar = CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; + // Clamp status bar settings in case of corrupt/migrated data. + if (SETTINGS.statusBarUpperProgressBar >= PROGRESS_BAR_ITEMS) { + SETTINGS.statusBarUpperProgressBar = CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; } - if (SETTINGS.statusBarTitle >= PROGRESS_BAR_THICKNESS_ITEMS) { - SETTINGS.statusBarTitle = CrossPointSettings::STATUS_BAR_PROGRESS_BAR_THICKNESS::PROGRESS_BAR_NORMAL; + if (SETTINGS.statusBarLowerProgressBar >= PROGRESS_BAR_ITEMS) { + SETTINGS.statusBarLowerProgressBar = CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; + } + + if (SETTINGS.statusBarUpperProgressBarThickness >= PROGRESS_BAR_THICKNESS_ITEMS) { + SETTINGS.statusBarUpperProgressBarThickness = + CrossPointSettings::STATUS_BAR_PROGRESS_BAR_THICKNESS::PROGRESS_BAR_NORMAL; + } + + if (SETTINGS.statusBarLowerProgressBarThickness >= PROGRESS_BAR_THICKNESS_ITEMS) { + SETTINGS.statusBarLowerProgressBarThickness = + CrossPointSettings::STATUS_BAR_PROGRESS_BAR_THICKNESS::PROGRESS_BAR_NORMAL; + } + + if (SETTINGS.statusBarItemsPosition >= STATUS_ITEMS_POSITION_ITEMS) { + SETTINGS.statusBarItemsPosition = CrossPointSettings::STATUS_BAR_ITEMS_POSITION::STATUS_BAR_ITEMS_BOTTOM; } if (SETTINGS.statusBarTitle >= TITLE_ITEMS) { @@ -74,53 +186,67 @@ void StatusBarSettingsActivity::loop() { // Handle navigation buttonNavigator.onNextRelease([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1; + const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; selectedIndex = ButtonNavigator::nextIndex(selectedIndex, menuCount); requestUpdate(); }); buttonNavigator.onPreviousRelease([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1; + const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; selectedIndex = ButtonNavigator::previousIndex(selectedIndex, menuCount); requestUpdate(); }); buttonNavigator.onNextContinuous([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1; + const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; selectedIndex = ButtonNavigator::nextIndex(selectedIndex, menuCount); requestUpdate(); }); buttonNavigator.onPreviousContinuous([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1; + const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; selectedIndex = ButtonNavigator::previousIndex(selectedIndex, menuCount); requestUpdate(); }); } void StatusBarSettingsActivity::handleSelection() { - if (selectedIndex == 0) { + // When clock is hidden, indices 6+ shift down by 1 (clock slot at index 5 is absent). + // We remap the logical index to match the menuNames array which always includes clock at slot 5. + const int logicalIndex = (!SETTINGS.useClock && selectedIndex >= 5) ? selectedIndex + 1 : selectedIndex; + + if (logicalIndex == 0) { + // Status Items Position + SETTINGS.statusBarItemsPosition = (SETTINGS.statusBarItemsPosition + 1) % STATUS_ITEMS_POSITION_ITEMS; + } else if (logicalIndex == 1) { // Chapter Page Count SETTINGS.statusBarChapterPageCount = (SETTINGS.statusBarChapterPageCount + 1) % 2; - } else if (selectedIndex == 1) { + } else if (logicalIndex == 2) { // Book Progress % SETTINGS.statusBarBookProgressPercentage = (SETTINGS.statusBarBookProgressPercentage + 1) % 2; - } else if (selectedIndex == 2) { - // Progress Bar - SETTINGS.statusBarProgressBar = (SETTINGS.statusBarProgressBar + 1) % PROGRESS_BAR_ITEMS; - } else if (selectedIndex == 3) { - // Progress Bar Thickness - SETTINGS.statusBarProgressBarThickness = - (SETTINGS.statusBarProgressBarThickness + 1) % PROGRESS_BAR_THICKNESS_ITEMS; - } else if (selectedIndex == 4) { - // Chapter Title + } else if (logicalIndex == 3) { + // Title SETTINGS.statusBarTitle = (SETTINGS.statusBarTitle + 1) % TITLE_ITEMS; - } else if (selectedIndex == 5) { - // Show Battery + } else if (logicalIndex == 4) { + // Battery SETTINGS.statusBarBattery = (SETTINGS.statusBarBattery + 1) % 2; - } else if (selectedIndex == 6 && SETTINGS.useClock) { - // Show Clock + } else if (logicalIndex == 5 && SETTINGS.useClock) { + // Clock SETTINGS.statusBarClock = (SETTINGS.statusBarClock + 1) % 2; + } else if (logicalIndex == 6) { + // Upper Progress Bar + SETTINGS.statusBarUpperProgressBar = (SETTINGS.statusBarUpperProgressBar + 1) % PROGRESS_BAR_ITEMS; + } else if (logicalIndex == 7) { + // Upper Progress Bar Thickness + SETTINGS.statusBarUpperProgressBarThickness = + (SETTINGS.statusBarUpperProgressBarThickness + 1) % PROGRESS_BAR_THICKNESS_ITEMS; + } else if (logicalIndex == 8) { + // Lower Progress Bar + SETTINGS.statusBarLowerProgressBar = (SETTINGS.statusBarLowerProgressBar + 1) % PROGRESS_BAR_ITEMS; + } else if (logicalIndex == 9) { + // Lower Progress Bar Thickness + SETTINGS.statusBarLowerProgressBarThickness = + (SETTINGS.statusBarLowerProgressBarThickness + 1) % PROGRESS_BAR_THICKNESS_ITEMS; } SETTINGS.saveToFile(); } @@ -137,28 +263,44 @@ void StatusBarSettingsActivity::render(RenderLock&&) { tr(STR_CUSTOMISE_STATUS_BAR)); const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; - const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2; - const int menuCount = SETTINGS.useClock ? MENU_ITEMS : MENU_ITEMS - 1; + const int previewLabelHeight = renderer.getLineHeight(UI_10_FONT_ID); + const int previewAreaHeight = previewLabelHeight + previewHeight + metrics.verticalSpacing * 2; + const int contentHeight = + pageHeight - contentTop - metrics.buttonHintsHeight - previewAreaHeight - metrics.verticalSpacing * 2; + const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; GUI.drawList( renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast(menuCount), - static_cast(selectedIndex), [](int index) { return std::string(I18N.get(menuNames[index])); }, nullptr, - nullptr, + static_cast(selectedIndex), + [](int index) { + const int logicalIndex = (!SETTINGS.useClock && index >= 5) ? index + 1 : index; + return std::string(I18N.get(menuNames[logicalIndex])); + }, + nullptr, nullptr, [this](int index) { - // Draw status for each setting - if (index == 0) { + // When clock is hidden, indices 6+ shift down by 1 in the display list but we still + // index into menuNames which has clock at slot 5. Remap for value display. + const int logicalIndex = (!SETTINGS.useClock && index >= 5) ? index + 1 : index; + + if (logicalIndex == 0) { + return I18N.get(statusItemsPositionNames[SETTINGS.statusBarItemsPosition]); + } else if (logicalIndex == 1) { return SETTINGS.statusBarChapterPageCount ? tr(STR_SHOW) : tr(STR_HIDE); - } else if (index == 1) { + } else if (logicalIndex == 2) { return SETTINGS.statusBarBookProgressPercentage ? tr(STR_SHOW) : tr(STR_HIDE); - } else if (index == 2) { - return I18N.get(progressBarNames[SETTINGS.statusBarProgressBar]); - } else if (index == 3) { - return I18N.get(progressBarThicknessNames[SETTINGS.statusBarProgressBarThickness]); - } else if (index == 4) { + } else if (logicalIndex == 3) { return I18N.get(titleNames[SETTINGS.statusBarTitle]); - } else if (index == 5) { + } else if (logicalIndex == 4) { return SETTINGS.statusBarBattery ? tr(STR_SHOW) : tr(STR_HIDE); - } else if (index == 6) { + } else if (logicalIndex == 5) { return SETTINGS.statusBarClock ? tr(STR_SHOW) : tr(STR_HIDE); + } else if (logicalIndex == 6) { + return I18N.get(progressBarNames[SETTINGS.statusBarUpperProgressBar]); + } else if (logicalIndex == 7) { + return I18N.get(progressBarThicknessNames[SETTINGS.statusBarUpperProgressBarThickness]); + } else if (logicalIndex == 8) { + return I18N.get(progressBarNames[SETTINGS.statusBarLowerProgressBar]); + } else if (logicalIndex == 9) { + return I18N.get(progressBarThicknessNames[SETTINGS.statusBarLowerProgressBarThickness]); } else { return tr(STR_HIDE); } @@ -169,19 +311,16 @@ void StatusBarSettingsActivity::render(RenderLock&&) { const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_TOGGLE), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); - std::string title; - if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE) { - title = tr(STR_EXAMPLE_BOOK); - } else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) { - title = tr(STR_EXAMPLE_CHAPTER); - } - - GUI.drawStatusBar(renderer, 75, 8, 32, title, verticalPreviewPadding); - - renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, - renderer.getScreenHeight() - UITheme::getInstance().getStatusBarHeight() - verticalPreviewPadding - - verticalPreviewTextPadding, - tr(STR_PREVIEW)); + const int previewLabelY = contentTop + contentHeight + metrics.verticalSpacing; + renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, previewLabelY, tr(STR_PREVIEW)); + const Rect previewRect{previewHorizontalInset, previewLabelY + previewLabelHeight + metrics.verticalSpacing / 2, + pageWidth - previewHorizontalInset * 2, previewHeight}; + renderer.drawRect(previewRect.x, previewRect.y, previewRect.width, previewRect.height); + drawPreviewProgressBar(renderer, previewRect, SETTINGS.statusBarUpperProgressBar, + SETTINGS.statusBarUpperProgressBarThickness, true); + drawPreviewProgressBar(renderer, previewRect, SETTINGS.statusBarLowerProgressBar, + SETTINGS.statusBarLowerProgressBarThickness, false); + drawPreviewStatusItems(renderer, previewRect, metrics); renderer.displayBuffer(); } diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index 7fa7abe4..5c3dfe03 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "MappedInputManager.h" @@ -16,6 +17,24 @@ namespace { constexpr int SKIP_PAGE_MS = 700; +constexpr int STATUS_BAR_ITEM_PADDING = 4; +constexpr int STATUS_BAR_DESCENDER_CLEARANCE = 4; + +uint8_t normalizeProgressBar(const uint8_t progressBar) { + return progressBar < CrossPointSettings::STATUS_BAR_PROGRESS_BAR_COUNT ? progressBar + : CrossPointSettings::HIDE_PROGRESS; +} + +uint8_t normalizeProgressBarThickness(const uint8_t thickness) { + return thickness < CrossPointSettings::STATUS_BAR_PROGRESS_BAR_THICKNESS_COUNT + ? thickness + : CrossPointSettings::PROGRESS_BAR_NORMAL; +} + +uint8_t normalizeStatusBarItemsPosition(const uint8_t position) { + return position < CrossPointSettings::STATUS_BAR_ITEMS_POSITION_COUNT ? position + : CrossPointSettings::STATUS_BAR_ITEMS_BOTTOM; +} } // namespace UITheme UITheme::instance; @@ -159,22 +178,48 @@ UIIcon UITheme::getFileIcon(const std::string& filename) { return File; } -int UITheme::getStatusBarHeight() { +int UITheme::getProgressBarHeight(const uint8_t progressBar, const uint8_t thickness) { const ThemeMetrics& metrics = UITheme::getInstance().getMetrics(); - - // Add status bar margin - const bool showStatusBar = SETTINGS.statusBarChapterPageCount || SETTINGS.statusBarBookProgressPercentage || - SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || - SETTINGS.statusBarBattery || (SETTINGS.useClock && SETTINGS.statusBarClock); - const bool showProgressBar = - SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; - return (showStatusBar ? (metrics.statusBarVerticalMargin) : 0) + - (showProgressBar ? (((SETTINGS.statusBarProgressBarThickness + 1) * 2) + metrics.progressBarMarginTop) : 0); + const uint8_t normalizedProgressBar = normalizeProgressBar(progressBar); + if (normalizedProgressBar == CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS) { + return 0; + } + const uint8_t normalizedThickness = normalizeProgressBarThickness(thickness); + return ((normalizedThickness + 1) * 2) + metrics.progressBarMarginTop; } -int UITheme::getProgressBarHeight() { +int UITheme::getStatusBarItemsHeight() { const ThemeMetrics& metrics = UITheme::getInstance().getMetrics(); - const bool showProgressBar = - SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; - return (showProgressBar ? (((SETTINGS.statusBarProgressBarThickness + 1) * 2) + metrics.progressBarMarginTop) : 0); + return std::max(metrics.statusBarVerticalMargin + (STATUS_BAR_ITEM_PADDING * 2) + STATUS_BAR_DESCENDER_CLEARANCE, + metrics.batteryHeight + (STATUS_BAR_ITEM_PADDING * 2) + STATUS_BAR_DESCENDER_CLEARANCE); +} + +int UITheme::getStatusBarTopHeight(const bool forceStatusItems) { + const bool showStatusItems = forceStatusItems || SETTINGS.statusBarChapterPageCount || + SETTINGS.statusBarBookProgressPercentage || + SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || + SETTINGS.statusBarBattery || (SETTINGS.useClock && SETTINGS.statusBarClock); + const uint8_t statusBarItemsPosition = normalizeStatusBarItemsPosition(SETTINGS.statusBarItemsPosition); + const bool statusItemsAtTop = + statusBarItemsPosition == CrossPointSettings::STATUS_BAR_ITEMS_POSITION::STATUS_BAR_ITEMS_TOP; + const int statusItemsHeight = showStatusItems && statusItemsAtTop ? getStatusBarItemsHeight() : 0; + return getProgressBarHeight(SETTINGS.statusBarUpperProgressBar, SETTINGS.statusBarUpperProgressBarThickness) + + statusItemsHeight; +} + +int UITheme::getStatusBarBottomHeight(const bool forceStatusItems) { + const bool showStatusItems = forceStatusItems || SETTINGS.statusBarChapterPageCount || + SETTINGS.statusBarBookProgressPercentage || + SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || + SETTINGS.statusBarBattery || (SETTINGS.useClock && SETTINGS.statusBarClock); + const uint8_t statusBarItemsPosition = normalizeStatusBarItemsPosition(SETTINGS.statusBarItemsPosition); + const bool statusItemsAtBottom = + statusBarItemsPosition == CrossPointSettings::STATUS_BAR_ITEMS_POSITION::STATUS_BAR_ITEMS_BOTTOM; + const int statusItemsHeight = showStatusItems && statusItemsAtBottom ? getStatusBarItemsHeight() : 0; + return getProgressBarHeight(SETTINGS.statusBarLowerProgressBar, SETTINGS.statusBarLowerProgressBarThickness) + + statusItemsHeight; +} + +int UITheme::getStatusBarHeight(const bool forceStatusItems) { + return getStatusBarTopHeight(forceStatusItems) + getStatusBarBottomHeight(forceStatusItems); } diff --git a/src/components/UITheme.h b/src/components/UITheme.h index ed63bd7b..da015093 100644 --- a/src/components/UITheme.h +++ b/src/components/UITheme.h @@ -61,8 +61,11 @@ class UITheme { static Rect getContentRect(const GfxRenderer& renderer, bool hasBottomHints, bool hasSideHints); static std::string getCoverThumbPath(std::string coverBmpPath, int coverHeight); static UIIcon getFileIcon(const std::string& filename); - static int getStatusBarHeight(); - static int getProgressBarHeight(); + static int getStatusBarTopHeight(bool forceStatusItems = false); + static int getStatusBarBottomHeight(bool forceStatusItems = false); + static int getStatusBarHeight(bool forceStatusItems = false); + static int getStatusBarItemsHeight(); + static int getProgressBarHeight(uint8_t progressBar, uint8_t thickness); private: const ThemeMetrics* currentMetrics; diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index 27d402ef..fbe5f3e7 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -52,6 +52,21 @@ void drawBatteryIcon(const GfxRenderer& renderer, int x, int y, int battWidth, i BaseTheme::drawBatteryLightningBolt(renderer, x + 4, y + 2); } } + +int progressBarPixelHeight(const uint8_t progressBar, const uint8_t thickness, const ThemeMetrics& metrics) { + const int reservedHeight = UITheme::getProgressBarHeight(progressBar, thickness); + return std::max(0, reservedHeight - metrics.progressBarMarginTop); +} + +int statusBarProgressPercent(const uint8_t progressBar, const float bookProgress, const int currentPage, + const int pageCount) { + if (progressBar == CrossPointSettings::STATUS_BAR_PROGRESS_BAR::BOOK_PROGRESS) { + return std::clamp(static_cast(bookProgress), 0, 100); + } + const int chapterProgress = + (pageCount > 0) ? static_cast((static_cast(currentPage) / pageCount) * 100) : 0; + return std::clamp(chapterProgress, 0, 100); +} } // namespace void BaseTheme::drawBatteryOutline(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight) { @@ -748,19 +763,56 @@ void BaseTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layou } void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, - const int pageCount, std::string title, const int paddingBottom, const int textYOffset, + const int pageCount, std::string title, const int paddingBottom, const bool isStarred) const { auto metrics = UITheme::getInstance().getMetrics(); int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, &orientedMarginLeft); - // Draw Progress Text const auto screenHeight = renderer.getScreenHeight(); - auto textY = screenHeight - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - paddingBottom - 4; + const auto screenWidth = renderer.getScreenWidth(); + const int progressBarMaxWidth = screenWidth - orientedMarginLeft - orientedMarginRight; + + auto drawEdgeProgressBar = [&](const uint8_t progressBar, const uint8_t thickness, const bool topEdge) { + const int barHeight = progressBarPixelHeight(progressBar, thickness, metrics); + if (barHeight <= 0) { + return; + } + const int progress = statusBarProgressPercent(progressBar, bookProgress, currentPage, pageCount); + const int barWidth = progressBarMaxWidth * progress / 100; + const int y = + topEdge ? orientedMarginTop + paddingBottom : screenHeight - orientedMarginBottom - paddingBottom - barHeight; + renderer.fillRect(orientedMarginLeft, y, barWidth, barHeight, true); + }; + + // Draw progress bars first; status items are then placed inside the reserved band for their selected edge. + drawEdgeProgressBar(SETTINGS.statusBarUpperProgressBar, SETTINGS.statusBarUpperProgressBarThickness, true); + drawEdgeProgressBar(SETTINGS.statusBarLowerProgressBar, SETTINGS.statusBarLowerProgressBarThickness, false); + + const bool hasProgressText = SETTINGS.statusBarBookProgressPercentage || SETTINGS.statusBarChapterPageCount; + const bool hasStatusItems = hasProgressText || SETTINGS.statusBarBattery || !title.empty() || + SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || + (SETTINGS.useClock && SETTINGS.statusBarClock); + if (!hasStatusItems) { + return; + } + + const bool statusItemsAtTop = + SETTINGS.statusBarItemsPosition == CrossPointSettings::STATUS_BAR_ITEMS_POSITION::STATUS_BAR_ITEMS_TOP; + const int adjacentProgressHeight = statusItemsAtTop + ? UITheme::getProgressBarHeight(SETTINGS.statusBarUpperProgressBar, + SETTINGS.statusBarUpperProgressBarThickness) + : UITheme::getProgressBarHeight(SETTINGS.statusBarLowerProgressBar, + SETTINGS.statusBarLowerProgressBarThickness); + const int statusItemsHeight = UITheme::getStatusBarItemsHeight(); + + const int textY = statusItemsAtTop ? orientedMarginTop + paddingBottom + adjacentProgressHeight + 4 + : screenHeight - orientedMarginBottom - paddingBottom - adjacentProgressHeight - + statusItemsHeight + 4; int progressTextWidth = 0; - if (SETTINGS.statusBarBookProgressPercentage || SETTINGS.statusBarChapterPageCount) { + if (hasProgressText) { // Right aligned text for progress counter char progressStr[32]; @@ -773,27 +825,9 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c } progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); - renderer.drawText( - SMALL_FONT_ID, - renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - progressTextWidth, textY, - progressStr); - } - - // Draw Progress Bar - if (SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS) { - const int progressBarMaxWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; - const int progressBarY = renderer.getScreenHeight() - orientedMarginBottom - - ((SETTINGS.statusBarProgressBarThickness + 1) * 2) - paddingBottom; - size_t progress; - if (SETTINGS.statusBarProgressBar == CrossPointSettings::STATUS_BAR_PROGRESS_BAR::BOOK_PROGRESS) { - progress = static_cast(bookProgress); - } else { - // Chapter progress - progress = (pageCount > 0) ? (static_cast(currentPage) / pageCount) * 100 : 0; - } - const int barWidth = progressBarMaxWidth * progress / 100; - renderer.fillRect(orientedMarginLeft, progressBarY, barWidth, ((SETTINGS.statusBarProgressBarThickness + 1) * 2), - true); + renderer.drawText(SMALL_FONT_ID, + screenWidth - metrics.statusBarHorizontalMargin - orientedMarginRight - progressTextWidth, textY, + progressStr); } // Draw Battery @@ -819,11 +853,8 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c // Draw Title if (!title.empty()) { - textY -= textYOffset; - // Centered chapter title text - // Page width minus existing content with 30px padding on each side const int rendererableScreenWidth = - renderer.getScreenWidth() - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight; + screenWidth - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight; const int batterySize = SETTINGS.statusBarBattery ? (showBatteryPercentage ? 50 : 20) : 0; const int starReserve = isStarred ? (renderer.getTextWidth(SMALL_FONT_ID, "*") + 6) : 0; @@ -836,8 +867,7 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c int titleMarginLeftAdjusted = std::max(titleMarginLeft, titleMarginRight); int availableTitleSpace = rendererableScreenWidth - 2 * titleMarginLeftAdjusted; - int titleWidth; - titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); + int titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); if (titleWidth > availableTitleSpace) { // Not enough space to center on the screen, center it within the remaining space instead availableTitleSpace = rendererableScreenWidth - titleMarginLeft - titleMarginRight; @@ -860,14 +890,12 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c int starX; if (progressTextWidth > 0) { // Place star just left of the progress text with a small gap - starX = renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - progressTextWidth - - starWidth - 6; + starX = screenWidth - metrics.statusBarHorizontalMargin - orientedMarginRight - progressTextWidth - starWidth - 6; } else { // No progress text, place star at right edge - starX = renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - starWidth; + starX = screenWidth - metrics.statusBarHorizontalMargin - orientedMarginRight - starWidth; } - const int starY = title.empty() ? textY : (textY + textYOffset); - renderer.drawText(SMALL_FONT_ID, starX, starY, "*"); + renderer.drawText(SMALL_FONT_ID, starX, textY, "*"); } } diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index b5c97031..a9923287 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -151,7 +151,7 @@ class BaseTheme { virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; virtual void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, const int pageCount, std::string title, const int paddingBottom = 0, - const int textYOffset = 0, const bool isStarred = false) const; + const bool isStarred = false) const; virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const; virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false, int contentStartX = 0, int contentWidth = 0) const;