From e9c2fe1c8780c68af9bc0a7aca8801a3ddf58313 Mon Sep 17 00:00:00 2001 From: Alex Faria <3195321+alexfaria@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:25:44 +0000 Subject: [PATCH] feat: Add status bar option "Full w/ Progress Bar" (#438) ## Summary * **What is the goal of this PR?** This PR introduces a new "Status Bar" mode that displays a visual progress bar at the bottom of the screen, providing readers with a graphical indication of their position within the book. * **What changes are included?** * **Settings**: Updated SettingsActivity to expand the "Status Bar" configuration with a new option: Full w/ Progress Bar. * **EPUB Reader**: Modified EpubReaderActivity to calculate the global book progress and render a progress bar at the bottom of the viewable area when the new setting is active. * **TXT Reader**: Modified TxtReaderActivity to implement similar progress bar rendering logic based on the current page and total page count. ## Additional Context * The progress bar is rendered with a height of 4 pixels at the very bottom of the screen (adjusted for margins). * The feature reuses the existing renderStatusBar logic but conditionally draws the bar instead of (or in addition to) other elements depending on the specific implementation details in each reader. * Renamed existing 'Full' mode to 'Full w/ Percentage' * Added new 'Full w/ Progress Bar' option --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- src/CrossPointSettings.h | 9 +++- src/ScreenComponents.cpp | 11 ++++ src/ScreenComponents.h | 3 ++ src/activities/reader/EpubReaderActivity.cpp | 54 +++++++++++++++----- src/activities/reader/TxtReaderActivity.cpp | 51 ++++++++++++++---- src/activities/settings/SettingsActivity.cpp | 3 +- 6 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index e2883425..6385f4f1 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -19,7 +19,14 @@ class CrossPointSettings { enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT }; // Status bar display type enum - enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2, STATUS_BAR_MODE_COUNT }; + enum STATUS_BAR_MODE { + NONE = 0, + NO_PROGRESS = 1, + FULL = 2, + FULL_WITH_PROGRESS_BAR = 3, + ONLY_PROGRESS_BAR = 4, + STATUS_BAR_MODE_COUNT + }; enum ORIENTATION { PORTRAIT = 0, // 480x800 logical coordinates (current default) diff --git a/src/ScreenComponents.cpp b/src/ScreenComponents.cpp index 2e8d9e7c..ef47dfc5 100644 --- a/src/ScreenComponents.cpp +++ b/src/ScreenComponents.cpp @@ -42,6 +42,17 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); } +void ScreenComponents::drawBookProgressBar(const GfxRenderer& renderer, const size_t bookProgress) { + int vieweableMarginTop, vieweableMarginRight, vieweableMarginBottom, vieweableMarginLeft; + renderer.getOrientedViewableTRBL(&vieweableMarginTop, &vieweableMarginRight, &vieweableMarginBottom, + &vieweableMarginLeft); + + const int progressBarMaxWidth = renderer.getScreenWidth() - vieweableMarginLeft - vieweableMarginRight; + const int progressBarY = renderer.getScreenHeight() - vieweableMarginBottom - BOOK_PROGRESS_BAR_HEIGHT; + const int barWidth = progressBarMaxWidth * bookProgress / 100; + renderer.fillRect(vieweableMarginLeft, progressBarY, barWidth, BOOK_PROGRESS_BAR_HEIGHT, true); +} + int ScreenComponents::drawTabBar(const GfxRenderer& renderer, const int y, const std::vector& tabs) { constexpr int tabPadding = 20; // Horizontal padding between tabs constexpr int leftMargin = 20; // Left margin for first tab diff --git a/src/ScreenComponents.h b/src/ScreenComponents.h index 48c40f42..15403f60 100644 --- a/src/ScreenComponents.h +++ b/src/ScreenComponents.h @@ -13,7 +13,10 @@ struct TabInfo { class ScreenComponents { public: + static const int BOOK_PROGRESS_BAR_HEIGHT = 4; + static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true); + static void drawBookProgressBar(const GfxRenderer& renderer, size_t bookProgress); // Draw a horizontal tab bar with underline indicator for selected tab // Returns the height of the tab bar (for positioning content below) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index a6d27d34..bd9c1b1d 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -18,6 +18,8 @@ namespace { constexpr unsigned long skipChapterMs = 700; constexpr unsigned long goHomeMs = 1000; constexpr int statusBarMargin = 19; +constexpr int progressBarMarginTop = 1; + } // namespace void EpubReaderActivity::taskTrampoline(void* param) { @@ -275,7 +277,16 @@ void EpubReaderActivity::renderScreen() { orientedMarginTop += SETTINGS.screenMargin; orientedMarginLeft += SETTINGS.screenMargin; orientedMarginRight += SETTINGS.screenMargin; - orientedMarginBottom += statusBarMargin; + orientedMarginBottom += SETTINGS.screenMargin; + + // Add status bar margin + if (SETTINGS.statusBar != CrossPointSettings::STATUS_BAR_MODE::NONE) { + // Add additional margin for status bar if progress bar is shown + const bool showProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_PROGRESS_BAR; + orientedMarginBottom += statusBarMargin - SETTINGS.screenMargin + + (showProgressBar ? (ScreenComponents::BOOK_PROGRESS_BAR_HEIGHT + progressBarMarginTop) : 0); + } if (!section) { const auto filepath = epub->getSpineItem(currentSpineIndex).href; @@ -446,11 +457,17 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom, const int orientedMarginLeft) const { // determine visible status bar elements - const bool showProgress = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + const bool showProgressPercentage = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + const bool showProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_PROGRESS_BAR; + const bool showProgressText = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR; const bool showBattery = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR; const bool showChapterTitle = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR; const bool showBatteryPercentage = SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; @@ -459,19 +476,30 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in const auto textY = screenHeight - orientedMarginBottom - 4; int progressTextWidth = 0; - if (showProgress) { - // Calculate progress in book - const float sectionChapterProg = static_cast(section->currentPage) / section->pageCount; - const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100; + // Calculate progress in book + const float sectionChapterProg = static_cast(section->currentPage) / section->pageCount; + const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100; + if (showProgressText || showProgressPercentage) { // Right aligned text for progress counter char progressStr[32]; - snprintf(progressStr, sizeof(progressStr), "%d/%d %.0f%%", section->currentPage + 1, section->pageCount, - bookProgress); - const std::string progress = progressStr; - progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str()); + + // Hide percentage when progress bar is shown to reduce clutter + if (showProgressPercentage) { + snprintf(progressStr, sizeof(progressStr), "%d/%d %.0f%%", section->currentPage + 1, section->pageCount, + bookProgress); + } else { + snprintf(progressStr, sizeof(progressStr), "%d/%d", section->currentPage + 1, section->pageCount); + } + + progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); renderer.drawText(SMALL_FONT_ID, renderer.getScreenWidth() - orientedMarginRight - progressTextWidth, textY, - progress.c_str()); + progressStr); + } + + if (showProgressBar) { + // Draw progress bar at the very bottom of the screen, from edge to edge of viewable area + ScreenComponents::drawBookProgressBar(renderer, static_cast(bookProgress)); } if (showBattery) { diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index b7de16d8..cc2036b9 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -15,6 +15,7 @@ namespace { constexpr unsigned long goHomeMs = 1000; constexpr int statusBarMargin = 25; +constexpr int progressBarMarginTop = 1; constexpr size_t CHUNK_SIZE = 8 * 1024; // 8KB chunk for reading // Cache file magic and version @@ -158,7 +159,16 @@ void TxtReaderActivity::initializeReader() { orientedMarginTop += cachedScreenMargin; orientedMarginLeft += cachedScreenMargin; orientedMarginRight += cachedScreenMargin; - orientedMarginBottom += statusBarMargin; + orientedMarginBottom += cachedScreenMargin; + + // Add status bar margin + if (SETTINGS.statusBar != CrossPointSettings::STATUS_BAR_MODE::NONE) { + // Add additional margin for status bar if progress bar is shown + const bool showProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_PROGRESS_BAR; + orientedMarginBottom += statusBarMargin - cachedScreenMargin + + (showProgressBar ? (ScreenComponents::BOOK_PROGRESS_BAR_HEIGHT + progressBarMarginTop) : 0); + } viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; const int viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom; @@ -499,27 +509,46 @@ void TxtReaderActivity::renderPage() { void TxtReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom, const int orientedMarginLeft) const { - const bool showProgress = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + const bool showProgressPercentage = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + const bool showProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_PROGRESS_BAR; + const bool showProgressText = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR; const bool showBattery = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR; const bool showTitle = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || + SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL_WITH_PROGRESS_BAR; + const bool showBatteryPercentage = + SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; const auto screenHeight = renderer.getScreenHeight(); const auto textY = screenHeight - orientedMarginBottom - 4; int progressTextWidth = 0; - if (showProgress) { - const int progress = totalPages > 0 ? (currentPage + 1) * 100 / totalPages : 0; - const std::string progressStr = - std::to_string(currentPage + 1) + "/" + std::to_string(totalPages) + " " + std::to_string(progress) + "%"; - progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr.c_str()); + const float progress = totalPages > 0 ? (currentPage + 1) * 100.0f / totalPages : 0; + + if (showProgressText || showProgressPercentage) { + char progressStr[32]; + if (showProgressPercentage) { + snprintf(progressStr, sizeof(progressStr), "%d/%d %.0f%%", currentPage + 1, totalPages, progress); + } else { + snprintf(progressStr, sizeof(progressStr), "%d/%d", currentPage + 1, totalPages); + } + + progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); renderer.drawText(SMALL_FONT_ID, renderer.getScreenWidth() - orientedMarginRight - progressTextWidth, textY, - progressStr.c_str()); + progressStr); + } + + if (showProgressBar) { + // Draw progress bar at the very bottom of the screen, from edge to edge of viewable area + ScreenComponents::drawBookProgressBar(renderer, static_cast(progress)); } if (showBattery) { - ScreenComponents::drawBattery(renderer, orientedMarginLeft, textY); + ScreenComponents::drawBattery(renderer, orientedMarginLeft, textY, showBatteryPercentage); } if (showTitle) { diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 819115a5..a211e033 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -16,7 +16,8 @@ const SettingInfo displaySettings[displaySettingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), - SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}), + SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, + {"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}), SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})};