From 29fd29f537742bbf82f10c28ed521b47a8524533 Mon Sep 17 00:00:00 2001 From: Chun Ming Lee <95391408+leecming82@users.noreply.github.com> Date: Sat, 9 May 2026 03:34:42 +0800 Subject: [PATCH] feat: Status bar for XTC files (#1849) ## Summary Add ability to show a status bar for 1-bit XTC files (closes #1848) Overlays a status bar (similar style to that for epubs) over the image. Defaults to hidden, users can set to show and either top or bottom of the screen within "Customize Status Bar" reader settings. I've leaned toward a simple overlay approach rather than trying anything clever e.g. resizing the original image or allowing for shifting the image around. I think it's more straight forward for the user to create a buffer zone when generating the XTC files. For 2-bit image files, it'd require more work to handle the multiple render passes so I'm holding off on that. Sample from a Japanese text XTC image ### 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? _**< YES | PARTIALLY | NO >**_ YES - Codex --------- Co-authored-by: Zach Nelson --- lib/I18n/translations/english.yaml | 3 + src/CrossPointSettings.h | 7 ++ src/SettingsList.h | 3 + src/activities/reader/XtcReaderActivity.cpp | 78 ++++++++++++++++++- src/activities/reader/XtcReaderActivity.h | 12 +++ .../settings/StatusBarSettingsActivity.cpp | 17 +++- 6 files changed, 117 insertions(+), 3 deletions(-) diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index b6623899..bcc1a20b 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -229,6 +229,9 @@ STR_EXAMPLE_BOOK: "Book Title" STR_PREVIEW: "Preview" STR_TITLE: "Title" STR_BATTERY: "Battery" +STR_XTC_STATUS_BAR: "XTC Status Bar" +STR_BOTTOM: "Bottom" +STR_TOP: "Top" STR_UI_THEME: "UI Theme" STR_THEME_CLASSIC: "Classic" STR_THEME_LYRA: "Lyra" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 01d7cc56..cf5c695d 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -57,6 +57,12 @@ 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 XTC_STATUS_BAR_MODE { + XTC_STATUS_BAR_HIDE = 0, + XTC_STATUS_BAR_BOTTOM = 1, + XTC_STATUS_BAR_TOP = 2, + XTC_STATUS_BAR_MODE_COUNT + }; enum ORIENTATION { PORTRAIT = 0, // 480x800 logical coordinates (current default) @@ -161,6 +167,7 @@ class CrossPointSettings { uint8_t statusBarProgressBarThickness = PROGRESS_BAR_NORMAL; uint8_t statusBarTitle = CHAPTER_TITLE; uint8_t statusBarBattery = 1; + uint8_t xtcStatusBarMode = XTC_STATUS_BAR_HIDE; // Text rendering settings uint8_t extraParagraphSpacing = 1; uint8_t textAntiAliasing = 1; diff --git a/src/SettingsList.h b/src/SettingsList.h index bbe9a774..cf2d02ed 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -131,6 +131,9 @@ inline const std::vector& getSettingsList() { StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Toggle(StrId::STR_BATTERY, &CrossPointSettings::statusBarBattery, "statusBarBattery", StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_XTC_STATUS_BAR, &CrossPointSettings::xtcStatusBarMode, + {StrId::STR_HIDE, StrId::STR_BOTTOM, StrId::STR_TOP}, "xtcStatusBarMode", + StrId::STR_CUSTOMISE_STATUS_BAR), }; // Only show tilt page turn setting when the QMI8658 IMU is present (X3) if (halTiltSensor.isAvailable()) { diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 8e904fb3..897d3f52 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "CrossPointSettings.h" #include "CrossPointState.h" #include "MappedInputManager.h" @@ -131,6 +133,76 @@ void XtcReaderActivity::render(RenderLock&&) { saveProgress(); } +XtcReaderActivity::StatusBarInfo XtcReaderActivity::getStatusBarInfo() const { + const int bookPageCount = static_cast(xtc->getPageCount()); + const int bookPage = static_cast(currentPage) + 1; + std::string title = + SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE ? xtc->getTitle() : ""; + + if (!xtc->hasChapters()) { + return StatusBarInfo{bookPage, bookPageCount, std::move(title)}; + } + + const auto& chapters = xtc->getChapters(); + const auto chapterIt = std::find_if(chapters.begin(), chapters.end(), [this](const xtc::ChapterInfo& chapter) { + return currentPage >= chapter.startPage && currentPage <= chapter.endPage; + }); + + if (chapterIt == chapters.end() || chapterIt->endPage < chapterIt->startPage) { + return StatusBarInfo{bookPage, bookPageCount, std::move(title)}; + } + + if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) { + title = chapterIt->name.empty() ? tr(STR_UNNAMED) : chapterIt->name; + } + + return StatusBarInfo{static_cast(currentPage - chapterIt->startPage) + 1, + static_cast(chapterIt->endPage - chapterIt->startPage) + 1, std::move(title)}; +} + +void XtcReaderActivity::renderStatusBarOverlay(const StatusBarOverlayPosition position) const { + const bool drawBottom = SETTINGS.xtcStatusBarMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_BOTTOM && + position == StatusBarOverlayPosition::Bottom; + const bool drawTop = SETTINGS.xtcStatusBarMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_TOP && + position == StatusBarOverlayPosition::Top; + if (!drawBottom && !drawTop) { + return; + } + + const int statusBarHeight = UITheme::getInstance().getStatusBarHeight(); + if (statusBarHeight <= 0) { + return; + } + + int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; + renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, + &orientedMarginLeft); + + int clearY; + int paddingBottom = 0; + if (position == StatusBarOverlayPosition::Bottom) { + clearY = renderer.getScreenHeight() - orientedMarginBottom - statusBarHeight - 4; + if (clearY < 0) { + clearY = 0; + } + } else { + clearY = orientedMarginTop; + paddingBottom = renderer.getScreenHeight() - statusBarHeight - orientedMarginBottom - orientedMarginTop - 4; + } + const int clearHeight = position == StatusBarOverlayPosition::Bottom + ? renderer.getScreenHeight() - orientedMarginBottom - clearY + : statusBarHeight + 4; + if (clearHeight > 0) { + renderer.fillRect(0, clearY, renderer.getScreenWidth(), clearHeight, false); + } + + const int pageCount = static_cast(xtc->getPageCount()); + const int displayPage = static_cast(currentPage) + 1; + const float progress = pageCount > 0 ? (static_cast(displayPage) * 100.0f) / pageCount : 0.0f; + const auto pageInfo = getStatusBarInfo(); + GUI.drawStatusBar(renderer, progress, pageInfo.currentPage, pageInfo.pageCount, pageInfo.title, paddingBottom); +} + void XtcReaderActivity::renderPage() { const uint16_t pageWidth = xtc->getPageWidth(); const uint16_t pageHeight = xtc->getPageHeight(); @@ -291,7 +363,11 @@ void XtcReaderActivity::renderPage() { free(pageBuffer); - // XTC pages already have status bar pre-rendered, no need to add our own + if (SETTINGS.xtcStatusBarMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_TOP) { + renderStatusBarOverlay(StatusBarOverlayPosition::Top); + } else { + renderStatusBarOverlay(StatusBarOverlayPosition::Bottom); + } ReaderUtils::displayWithRefreshCycle(renderer, pagesUntilFullRefresh); diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 282e8d2c..f020b0b0 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -9,6 +9,9 @@ #include +#include +#include + #include "activities/Activity.h" class XtcReaderActivity final : public Activity { @@ -17,7 +20,16 @@ class XtcReaderActivity final : public Activity { uint32_t currentPage = 0; int pagesUntilFullRefresh = 0; + enum class StatusBarOverlayPosition { Bottom, Top }; + struct StatusBarInfo { + int currentPage; + int pageCount; + std::string title; + }; + void renderPage(); + void renderStatusBarOverlay(StatusBarOverlayPosition position) const; + StatusBarInfo getStatusBarInfo() const; void saveProgress() const; void loadProgress(); diff --git a/src/activities/settings/StatusBarSettingsActivity.cpp b/src/activities/settings/StatusBarSettingsActivity.cpp index 6ff0b34d..f62ce4d4 100644 --- a/src/activities/settings/StatusBarSettingsActivity.cpp +++ b/src/activities/settings/StatusBarSettingsActivity.cpp @@ -11,13 +11,14 @@ #include "fontIds.h" namespace { -constexpr int MENU_ITEMS = 6; +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_BATTERY, + StrId::STR_XTC_STATUS_BAR}; constexpr int PROGRESS_BAR_ITEMS = 3; const StrId progressBarNames[PROGRESS_BAR_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}; @@ -28,6 +29,9 @@ 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}; +constexpr int XTC_STATUS_BAR_ITEMS = 3; +const StrId xtcStatusBarNames[XTC_STATUS_BAR_ITEMS] = {StrId::STR_HIDE, StrId::STR_BOTTOM, StrId::STR_TOP}; + const int widthMargin = 10; const int verticalPreviewPadding = 50; const int verticalPreviewTextPadding = 40; @@ -51,6 +55,10 @@ void StatusBarSettingsActivity::onEnter() { SETTINGS.statusBarTitle = CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE; } + if (SETTINGS.xtcStatusBarMode >= XTC_STATUS_BAR_ITEMS) { + SETTINGS.xtcStatusBarMode = CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_HIDE; + } + requestUpdate(); } @@ -110,6 +118,9 @@ void StatusBarSettingsActivity::handleSelection() { } else if (selectedIndex == 5) { // Show Battery SETTINGS.statusBarBattery = (SETTINGS.statusBarBattery + 1) % 2; + } else if (selectedIndex == 6) { + // XTC Status Bar + SETTINGS.xtcStatusBarMode = (SETTINGS.xtcStatusBarMode + 1) % XTC_STATUS_BAR_ITEMS; } SETTINGS.saveToFile(); } @@ -143,6 +154,8 @@ void StatusBarSettingsActivity::render(RenderLock&&) { return I18N.get(titleNames[SETTINGS.statusBarTitle]); } else if (index == 5) { return SETTINGS.statusBarBattery ? tr(STR_SHOW) : tr(STR_HIDE); + } else if (index == 6) { + return I18N.get(xtcStatusBarNames[SETTINGS.xtcStatusBarMode]); } else { return tr(STR_HIDE); }