From 7271c00d355ba8b9a4dd6a6f4fff1a23901d5333 Mon Sep 17 00:00:00 2001 From: Husam Younis Date: Thu, 25 Jun 2026 00:23:23 +0300 Subject: [PATCH 01/35] feat: Allow statusbar clock to be on the left (#2359) --- src/CrossPointSettings.h | 4 +- src/SettingsList.h | 5 +- .../settings/StatusBarSettingsActivity.cpp | 11 +++- src/components/UITheme.cpp | 7 +-- src/components/themes/BaseTheme.cpp | 54 ++++++++++++------- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index af274e3c..8a2cf217 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -65,6 +65,8 @@ class CrossPointSettings { XTC_STATUS_BAR_MODE_COUNT }; + enum STATUS_BAR_CLOCK_MODE { STATUS_BAR_CLOCK_HIDE = 0, STATUS_BAR_CLOCK_RIGHT = 1, STATUS_BAR_CLOCK_LEFT = 2 }; + enum ORIENTATION { PORTRAIT = 0, // 480x800 logical coordinates (current default) LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom) @@ -188,7 +190,7 @@ class CrossPointSettings { uint8_t statusBarBattery = 1; uint8_t xtcStatusBarMode = XTC_STATUS_BAR_HIDE; // Clock display in status bar (X3 only, requires DS3231 RTC) - uint8_t statusBarClock = 0; + uint8_t statusBarClock = STATUS_BAR_CLOCK_HIDE; // Clock UTC offset in quarter-hour steps, biased by 48 so it fits in uint8_t. // Value 48 = UTC+0, 0 = UTC-12:00, 104 = UTC+14:00. // Quarter-hour granularity supports oddball zones like Nepal (+5:45) and Chatham (+12:45). diff --git a/src/SettingsList.h b/src/SettingsList.h index a60303bc..695e8eec 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -244,8 +244,9 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist StrId::STR_CUSTOMISE_STATUS_BAR), // Clock entries (web settings only; device UI uses ClockOffsetActivity for the offset). // Range 0..104 = quarter-hour steps from UTC-12:00 to UTC+14:00, biased by 48. - SettingInfo::Toggle(StrId::STR_CLOCK, &CrossPointSettings::statusBarClock, "statusBarClock", - StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_CLOCK, &CrossPointSettings::statusBarClock, + {StrId::STR_HIDE, StrId::STR_DIR_LEFT, StrId::STR_DIR_RIGHT}, "statusBarClock", + StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Value(StrId::STR_CLOCK_UTC_OFFSET, &CrossPointSettings::clockUtcOffsetQ, {0, 104, 1}, "clockUtcOffsetQ", StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Enum(StrId::STR_CLOCK_FORMAT, &CrossPointSettings::clockFormat, diff --git a/src/activities/settings/StatusBarSettingsActivity.cpp b/src/activities/settings/StatusBarSettingsActivity.cpp index 2df09254..4046fcb1 100644 --- a/src/activities/settings/StatusBarSettingsActivity.cpp +++ b/src/activities/settings/StatusBarSettingsActivity.cpp @@ -77,6 +77,9 @@ const StrId titleNames[TITLE_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrI constexpr int XTC_STATUS_BAR_ITEMS = 3; const StrId xtcStatusBarNames[XTC_STATUS_BAR_ITEMS] = {StrId::STR_HIDE, StrId::STR_BOTTOM, StrId::STR_TOP}; +constexpr int STATUS_BAR_CLOCK_ITEMS = 3; +const StrId statusBarClockNames[STATUS_BAR_CLOCK_ITEMS] = {StrId::STR_HIDE, StrId::STR_DIR_RIGHT, StrId::STR_DIR_LEFT}; + const int verticalPreviewPadding = 50; const int verticalPreviewTextPadding = 40; } // namespace @@ -112,6 +115,10 @@ void StatusBarSettingsActivity::onEnter() { SETTINGS.clockFormat = 0; } + if (SETTINGS.statusBarClock >= STATUS_BAR_CLOCK_ITEMS) { + SETTINGS.statusBarClock = CrossPointSettings::STATUS_BAR_CLOCK_MODE::STATUS_BAR_CLOCK_HIDE; + } + requestUpdate(); } @@ -176,7 +183,7 @@ void StatusBarSettingsActivity::handleSelection() { SETTINGS.xtcStatusBarMode = (SETTINGS.xtcStatusBarMode + 1) % XTC_STATUS_BAR_ITEMS; break; case ITEM_CLOCK: - SETTINGS.statusBarClock = (SETTINGS.statusBarClock + 1) % 2; + SETTINGS.statusBarClock = (SETTINGS.statusBarClock + 1) % STATUS_BAR_CLOCK_ITEMS; break; case ITEM_CLOCK_FORMAT: SETTINGS.clockFormat = (SETTINGS.clockFormat + 1) % CLOCK_FORMAT_ITEMS; @@ -225,7 +232,7 @@ void StatusBarSettingsActivity::render(RenderLock&&) { case ITEM_XTC_STATUS_BAR: return I18N.get(xtcStatusBarNames[SETTINGS.xtcStatusBarMode]); case ITEM_CLOCK: - return SETTINGS.statusBarClock ? tr(STR_SHOW) : tr(STR_HIDE); + return I18N.get(statusBarClockNames[SETTINGS.statusBarClock]); case ITEM_CLOCK_FORMAT: { const uint8_t fmt = SETTINGS.clockFormat < CLOCK_FORMAT_ITEMS ? SETTINGS.clockFormat : 0; return std::string(I18N.get(clockFormatNames[fmt])); diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index 7de7f960..9220d43c 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -131,9 +131,10 @@ int UITheme::getStatusBarHeight() { 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; + const bool showStatusBar = + SETTINGS.statusBarChapterPageCount || SETTINGS.statusBarBookProgressPercentage || + SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || SETTINGS.statusBarBattery || + SETTINGS.statusBarClock != CrossPointSettings::STATUS_BAR_CLOCK_MODE::STATUS_BAR_CLOCK_HIDE; const bool showProgressBar = SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; return (showStatusBar ? (metrics.statusBarVerticalMargin) : 0) + diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index ff7e927d..15b2d569 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -759,7 +759,11 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c // Draw Progress Text const auto screenHeight = renderer.getScreenHeight(); auto textY = screenHeight - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - paddingBottom - 4; - int progressTextWidth = 0; + + const int leftClusterX = metrics.statusBarHorizontalMargin + orientedMarginLeft + 1; + const int rightClusterX = renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight; + int leftClusterWidth = 0; + int rightClusterWidth = 0; if (SETTINGS.statusBarBookProgressPercentage || SETTINGS.statusBarChapterPageCount) { // Right aligned text for progress counter @@ -773,11 +777,10 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c snprintf(progressStr, sizeof(progressStr), "%d/%d", currentPage, pageCount); } - progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); - renderer.drawText( - SMALL_FONT_ID, - renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - progressTextWidth, textY, - progressStr); + int progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); + renderer.drawText(SMALL_FONT_ID, rightClusterX - progressTextWidth, textY, progressStr); + + rightClusterWidth += progressTextWidth; } // Draw Progress Bar @@ -801,34 +804,46 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c } // Draw Bookmark - const int leftClusterX = metrics.statusBarHorizontalMargin + orientedMarginLeft + 1; - const bool showBookmarkIcon = showStatusBarTextLane && isPageBookmarked; - const int bookmarkReserveWidth = showBookmarkIcon ? (bookmarkStatusIconWidth + bookmarkStatusIconGap) : 0; - if (showBookmarkIcon) { + if (showStatusBarTextLane && isPageBookmarked) { const int bookmarkY = textY + 5; drawBookmarkStatusIcon(renderer, leftClusterX, bookmarkY); + leftClusterWidth += bookmarkStatusIconWidth + bookmarkStatusIconGap; } // Draw Battery const bool showBatteryPercentage = SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; - int leftClusterWidth = bookmarkReserveWidth; + if (SETTINGS.statusBarBattery) { GUI.drawBatteryLeft(renderer, - Rect{leftClusterX + bookmarkReserveWidth, textY, metrics.batteryWidth, metrics.batteryHeight}, + Rect{leftClusterX + leftClusterWidth, textY, metrics.batteryWidth, metrics.batteryHeight}, showBatteryPercentage); - leftClusterWidth += showBatteryPercentage ? 50 : 20; + int batteryWidth = metrics.batteryWidth; + + if (showBatteryPercentage) { + const uint16_t percentage = powerManager.getBatteryPercentage(); + // width of icon + spacing + text for layout purposes + batteryWidth += + batteryPercentSpacing + renderer.getTextWidth(SMALL_FONT_ID, (std::to_string(percentage) + "%").c_str()); + } + + leftClusterWidth += batteryWidth; } // Draw Clock (X3 only — DS3231 RTC) - int clockTextWidth = 0; if (SETTINGS.statusBarClock && halClock.isAvailable()) { char timeBuf[9]; if (halClock.formatTime(timeBuf, sizeof(timeBuf), SETTINGS.clockUtcOffsetQ, SETTINGS.clockFormat == 1)) { - clockTextWidth = renderer.getTextWidth(SMALL_FONT_ID, timeBuf); - // Position to the left of the progress text (with a small gap) - const int clockX = renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - - progressTextWidth - (progressTextWidth > 0 ? 10 : 0) - clockTextWidth; + int clockTextWidth = renderer.getTextWidth(SMALL_FONT_ID, timeBuf); + int clockX = 0; + // Position to the left or right of the progress text (with a small gap) + if (SETTINGS.statusBarClock == CrossPointSettings::STATUS_BAR_CLOCK_LEFT) { + clockX = leftClusterX + leftClusterWidth + (leftClusterWidth > 0 ? 10 : 0); + leftClusterWidth += clockTextWidth + 10; + } else if (SETTINGS.statusBarClock == CrossPointSettings::STATUS_BAR_CLOCK_RIGHT) { + clockX = rightClusterX - rightClusterWidth - (rightClusterWidth > 0 ? 10 : 0) - clockTextWidth; + rightClusterWidth += clockTextWidth + 10; + } renderer.drawText(SMALL_FONT_ID, clockX, textY, timeBuf); } } @@ -842,8 +857,7 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c renderer.getScreenWidth() - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight; const int titleMarginLeft = leftClusterWidth + 30; - const int clockReserve = clockTextWidth > 0 ? (clockTextWidth + 10) : 0; - const int titleMarginRight = progressTextWidth + clockReserve + 30; + const int titleMarginRight = rightClusterWidth + 30; // Attempt to center title on the screen, but if title is too wide then later we will center it within the // available space. From 8d5b119644405bdeaf1e8b56452fd15d202c78b1 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 24 Jun 2026 22:07:13 -0400 Subject: [PATCH 02/35] fix: sync master into develop (#2423) Sync develop branch with master Co-authored-by: Husam Younis --- src/CrossPointSettings.h | 4 +- src/SettingsList.h | 5 +- .../settings/StatusBarSettingsActivity.cpp | 11 +++- src/components/UITheme.cpp | 7 +-- src/components/themes/BaseTheme.cpp | 54 ++++++++++++------- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index af274e3c..8a2cf217 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -65,6 +65,8 @@ class CrossPointSettings { XTC_STATUS_BAR_MODE_COUNT }; + enum STATUS_BAR_CLOCK_MODE { STATUS_BAR_CLOCK_HIDE = 0, STATUS_BAR_CLOCK_RIGHT = 1, STATUS_BAR_CLOCK_LEFT = 2 }; + enum ORIENTATION { PORTRAIT = 0, // 480x800 logical coordinates (current default) LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom) @@ -188,7 +190,7 @@ class CrossPointSettings { uint8_t statusBarBattery = 1; uint8_t xtcStatusBarMode = XTC_STATUS_BAR_HIDE; // Clock display in status bar (X3 only, requires DS3231 RTC) - uint8_t statusBarClock = 0; + uint8_t statusBarClock = STATUS_BAR_CLOCK_HIDE; // Clock UTC offset in quarter-hour steps, biased by 48 so it fits in uint8_t. // Value 48 = UTC+0, 0 = UTC-12:00, 104 = UTC+14:00. // Quarter-hour granularity supports oddball zones like Nepal (+5:45) and Chatham (+12:45). diff --git a/src/SettingsList.h b/src/SettingsList.h index a60303bc..695e8eec 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -244,8 +244,9 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist StrId::STR_CUSTOMISE_STATUS_BAR), // Clock entries (web settings only; device UI uses ClockOffsetActivity for the offset). // Range 0..104 = quarter-hour steps from UTC-12:00 to UTC+14:00, biased by 48. - SettingInfo::Toggle(StrId::STR_CLOCK, &CrossPointSettings::statusBarClock, "statusBarClock", - StrId::STR_CUSTOMISE_STATUS_BAR), + SettingInfo::Enum(StrId::STR_CLOCK, &CrossPointSettings::statusBarClock, + {StrId::STR_HIDE, StrId::STR_DIR_LEFT, StrId::STR_DIR_RIGHT}, "statusBarClock", + StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Value(StrId::STR_CLOCK_UTC_OFFSET, &CrossPointSettings::clockUtcOffsetQ, {0, 104, 1}, "clockUtcOffsetQ", StrId::STR_CUSTOMISE_STATUS_BAR), SettingInfo::Enum(StrId::STR_CLOCK_FORMAT, &CrossPointSettings::clockFormat, diff --git a/src/activities/settings/StatusBarSettingsActivity.cpp b/src/activities/settings/StatusBarSettingsActivity.cpp index 2df09254..4046fcb1 100644 --- a/src/activities/settings/StatusBarSettingsActivity.cpp +++ b/src/activities/settings/StatusBarSettingsActivity.cpp @@ -77,6 +77,9 @@ const StrId titleNames[TITLE_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrI constexpr int XTC_STATUS_BAR_ITEMS = 3; const StrId xtcStatusBarNames[XTC_STATUS_BAR_ITEMS] = {StrId::STR_HIDE, StrId::STR_BOTTOM, StrId::STR_TOP}; +constexpr int STATUS_BAR_CLOCK_ITEMS = 3; +const StrId statusBarClockNames[STATUS_BAR_CLOCK_ITEMS] = {StrId::STR_HIDE, StrId::STR_DIR_RIGHT, StrId::STR_DIR_LEFT}; + const int verticalPreviewPadding = 50; const int verticalPreviewTextPadding = 40; } // namespace @@ -112,6 +115,10 @@ void StatusBarSettingsActivity::onEnter() { SETTINGS.clockFormat = 0; } + if (SETTINGS.statusBarClock >= STATUS_BAR_CLOCK_ITEMS) { + SETTINGS.statusBarClock = CrossPointSettings::STATUS_BAR_CLOCK_MODE::STATUS_BAR_CLOCK_HIDE; + } + requestUpdate(); } @@ -176,7 +183,7 @@ void StatusBarSettingsActivity::handleSelection() { SETTINGS.xtcStatusBarMode = (SETTINGS.xtcStatusBarMode + 1) % XTC_STATUS_BAR_ITEMS; break; case ITEM_CLOCK: - SETTINGS.statusBarClock = (SETTINGS.statusBarClock + 1) % 2; + SETTINGS.statusBarClock = (SETTINGS.statusBarClock + 1) % STATUS_BAR_CLOCK_ITEMS; break; case ITEM_CLOCK_FORMAT: SETTINGS.clockFormat = (SETTINGS.clockFormat + 1) % CLOCK_FORMAT_ITEMS; @@ -225,7 +232,7 @@ void StatusBarSettingsActivity::render(RenderLock&&) { case ITEM_XTC_STATUS_BAR: return I18N.get(xtcStatusBarNames[SETTINGS.xtcStatusBarMode]); case ITEM_CLOCK: - return SETTINGS.statusBarClock ? tr(STR_SHOW) : tr(STR_HIDE); + return I18N.get(statusBarClockNames[SETTINGS.statusBarClock]); case ITEM_CLOCK_FORMAT: { const uint8_t fmt = SETTINGS.clockFormat < CLOCK_FORMAT_ITEMS ? SETTINGS.clockFormat : 0; return std::string(I18N.get(clockFormatNames[fmt])); diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index 7de7f960..9220d43c 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -131,9 +131,10 @@ int UITheme::getStatusBarHeight() { 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; + const bool showStatusBar = + SETTINGS.statusBarChapterPageCount || SETTINGS.statusBarBookProgressPercentage || + SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || SETTINGS.statusBarBattery || + SETTINGS.statusBarClock != CrossPointSettings::STATUS_BAR_CLOCK_MODE::STATUS_BAR_CLOCK_HIDE; const bool showProgressBar = SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; return (showStatusBar ? (metrics.statusBarVerticalMargin) : 0) + diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index ff7e927d..15b2d569 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -759,7 +759,11 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c // Draw Progress Text const auto screenHeight = renderer.getScreenHeight(); auto textY = screenHeight - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - paddingBottom - 4; - int progressTextWidth = 0; + + const int leftClusterX = metrics.statusBarHorizontalMargin + orientedMarginLeft + 1; + const int rightClusterX = renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight; + int leftClusterWidth = 0; + int rightClusterWidth = 0; if (SETTINGS.statusBarBookProgressPercentage || SETTINGS.statusBarChapterPageCount) { // Right aligned text for progress counter @@ -773,11 +777,10 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c snprintf(progressStr, sizeof(progressStr), "%d/%d", currentPage, pageCount); } - progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); - renderer.drawText( - SMALL_FONT_ID, - renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - progressTextWidth, textY, - progressStr); + int progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); + renderer.drawText(SMALL_FONT_ID, rightClusterX - progressTextWidth, textY, progressStr); + + rightClusterWidth += progressTextWidth; } // Draw Progress Bar @@ -801,34 +804,46 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c } // Draw Bookmark - const int leftClusterX = metrics.statusBarHorizontalMargin + orientedMarginLeft + 1; - const bool showBookmarkIcon = showStatusBarTextLane && isPageBookmarked; - const int bookmarkReserveWidth = showBookmarkIcon ? (bookmarkStatusIconWidth + bookmarkStatusIconGap) : 0; - if (showBookmarkIcon) { + if (showStatusBarTextLane && isPageBookmarked) { const int bookmarkY = textY + 5; drawBookmarkStatusIcon(renderer, leftClusterX, bookmarkY); + leftClusterWidth += bookmarkStatusIconWidth + bookmarkStatusIconGap; } // Draw Battery const bool showBatteryPercentage = SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; - int leftClusterWidth = bookmarkReserveWidth; + if (SETTINGS.statusBarBattery) { GUI.drawBatteryLeft(renderer, - Rect{leftClusterX + bookmarkReserveWidth, textY, metrics.batteryWidth, metrics.batteryHeight}, + Rect{leftClusterX + leftClusterWidth, textY, metrics.batteryWidth, metrics.batteryHeight}, showBatteryPercentage); - leftClusterWidth += showBatteryPercentage ? 50 : 20; + int batteryWidth = metrics.batteryWidth; + + if (showBatteryPercentage) { + const uint16_t percentage = powerManager.getBatteryPercentage(); + // width of icon + spacing + text for layout purposes + batteryWidth += + batteryPercentSpacing + renderer.getTextWidth(SMALL_FONT_ID, (std::to_string(percentage) + "%").c_str()); + } + + leftClusterWidth += batteryWidth; } // Draw Clock (X3 only — DS3231 RTC) - int clockTextWidth = 0; if (SETTINGS.statusBarClock && halClock.isAvailable()) { char timeBuf[9]; if (halClock.formatTime(timeBuf, sizeof(timeBuf), SETTINGS.clockUtcOffsetQ, SETTINGS.clockFormat == 1)) { - clockTextWidth = renderer.getTextWidth(SMALL_FONT_ID, timeBuf); - // Position to the left of the progress text (with a small gap) - const int clockX = renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - - progressTextWidth - (progressTextWidth > 0 ? 10 : 0) - clockTextWidth; + int clockTextWidth = renderer.getTextWidth(SMALL_FONT_ID, timeBuf); + int clockX = 0; + // Position to the left or right of the progress text (with a small gap) + if (SETTINGS.statusBarClock == CrossPointSettings::STATUS_BAR_CLOCK_LEFT) { + clockX = leftClusterX + leftClusterWidth + (leftClusterWidth > 0 ? 10 : 0); + leftClusterWidth += clockTextWidth + 10; + } else if (SETTINGS.statusBarClock == CrossPointSettings::STATUS_BAR_CLOCK_RIGHT) { + clockX = rightClusterX - rightClusterWidth - (rightClusterWidth > 0 ? 10 : 0) - clockTextWidth; + rightClusterWidth += clockTextWidth + 10; + } renderer.drawText(SMALL_FONT_ID, clockX, textY, timeBuf); } } @@ -842,8 +857,7 @@ void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, c renderer.getScreenWidth() - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight; const int titleMarginLeft = leftClusterWidth + 30; - const int clockReserve = clockTextWidth > 0 ? (clockTextWidth + 10) : 0; - const int titleMarginRight = progressTextWidth + clockReserve + 30; + const int titleMarginRight = rightClusterWidth + 30; // Attempt to center title on the screen, but if title is too wide then later we will center it within the // available space. From 487613b08267df45c73819d3f5883ed92139bbd4 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 25 Jun 2026 00:47:06 -0400 Subject: [PATCH 03/35] fix: split inverted orientation label from color filter label (#2421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary * **What is the goal of this PR?** * Fix the “Inverted” translation so reader orientation and color/filter inversion can use separate labels. * **What changes are included?** * Adds `STR_ORIENTATION_INVERTED` for the inverted portrait orientation option. * Updates the reader orientation setting to use `STR_ORIENTATION_INVERTED` instead of reusing `STR_INVERTED`. * Leaves `STR_INVERTED` for the sleep cover filter and tilt page-turn mode * Adds the new orientation string across all 26 locale YAML files. ## Additional Context * The original issue was found by a user in German, where `STR_INVERTED` was translated as `Hochformat 180°`, which made sense for orientation but not for color filters. * This is a UI-label-only change. It does not change persisted orientation values or settings behavior. * Reviewer note: non-English wording may still benefit from native-speaker review, especially for the new orientation-specific labels and to verify the interchangeable usage between inverted color and inverted tilt page turn direction. --- ### 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 >**_ --- lib/I18n/translations/belarusian.yaml | 1 + lib/I18n/translations/catalan.yaml | 1 + lib/I18n/translations/czech.yaml | 1 + lib/I18n/translations/danish.yaml | 1 + lib/I18n/translations/dutch.yaml | 3 ++- lib/I18n/translations/english.yaml | 1 + lib/I18n/translations/finnish.yaml | 3 ++- lib/I18n/translations/french.yaml | 1 + lib/I18n/translations/german.yaml | 3 ++- lib/I18n/translations/hebrew.yaml | 3 ++- lib/I18n/translations/hungarian.yaml | 3 ++- lib/I18n/translations/italian.yaml | 3 ++- lib/I18n/translations/kazakh.yaml | 3 ++- lib/I18n/translations/lithuanian.yaml | 3 ++- lib/I18n/translations/polish.yaml | 3 ++- lib/I18n/translations/portuguese.yaml | 1 + lib/I18n/translations/romanian.yaml | 1 + lib/I18n/translations/russian.yaml | 1 + lib/I18n/translations/slovak.yaml | 3 ++- lib/I18n/translations/slovenian.yaml | 3 ++- lib/I18n/translations/spanish.yaml | 1 + lib/I18n/translations/swedish.yaml | 1 + lib/I18n/translations/turkish.yaml | 3 ++- lib/I18n/translations/ukrainian.yaml | 3 ++- lib/I18n/translations/valencian.yaml | 1 + lib/I18n/translations/vietnamese.yaml | 3 ++- src/SettingsList.h | 7 ++++--- 27 files changed, 44 insertions(+), 17 deletions(-) diff --git a/lib/I18n/translations/belarusian.yaml b/lib/I18n/translations/belarusian.yaml index 3ba890b8..de79ac91 100644 --- a/lib/I18n/translations/belarusian.yaml +++ b/lib/I18n/translations/belarusian.yaml @@ -126,6 +126,7 @@ STR_PAGE_TURN: "Перагортванне" STR_PORTRAIT: "Партрэт" STR_LANDSCAPE_CW: "Ландшафт (CW)" STR_INVERTED: "Інверсія" +STR_ORIENTATION_INVERTED: "Партрэт 180°" STR_LANDSCAPE_CCW: "Ландшафт (CCW)" STR_PREV_NEXT: "Назад/Наперад" STR_NEXT_PREV: "Наперад/Назад" diff --git a/lib/I18n/translations/catalan.yaml b/lib/I18n/translations/catalan.yaml index 7f3680cb..c1bf6c6b 100644 --- a/lib/I18n/translations/catalan.yaml +++ b/lib/I18n/translations/catalan.yaml @@ -136,6 +136,7 @@ STR_PAGE_TURN: "Canvi de pàgina" STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horitzontal horari" STR_INVERTED: "Invertit" +STR_ORIENTATION_INVERTED: "Vertical 180°" STR_LANDSCAPE_CCW: "Horitzontal antihorari" STR_PREV_NEXT: "Anterior/Següent" STR_NEXT_PREV: "Següent/Anterior" diff --git a/lib/I18n/translations/czech.yaml b/lib/I18n/translations/czech.yaml index c46b5ab9..696cdd33 100644 --- a/lib/I18n/translations/czech.yaml +++ b/lib/I18n/translations/czech.yaml @@ -131,6 +131,7 @@ STR_PAGE_TURN: "Otáčení stránek" STR_PORTRAIT: "Na výšku" STR_LANDSCAPE_CW: "Na šířku po směru hod. ručiček" STR_INVERTED: "Invertovaný" +STR_ORIENTATION_INVERTED: "Na výšku 180°" STR_LANDSCAPE_CCW: "Na šířku proti směru hod. ručiček" STR_PREV_NEXT: "Předchozí/Další" STR_NEXT_PREV: "Další/Předchozí" diff --git a/lib/I18n/translations/danish.yaml b/lib/I18n/translations/danish.yaml index e6f96b45..cd4ba1ba 100644 --- a/lib/I18n/translations/danish.yaml +++ b/lib/I18n/translations/danish.yaml @@ -136,6 +136,7 @@ STR_PAGE_TURN: "Sideskift" STR_PORTRAIT: "Portræt" STR_LANDSCAPE_CW: "Liggende med uret" STR_INVERTED: "Inverteret" +STR_ORIENTATION_INVERTED: "Portræt 180°" STR_LANDSCAPE_CCW: "Liggende mod uret" STR_PREV_NEXT: "Forrige/Næste" STR_NEXT_PREV: "Næste/Forrige" diff --git a/lib/I18n/translations/dutch.yaml b/lib/I18n/translations/dutch.yaml index d9461477..ee4ca64e 100644 --- a/lib/I18n/translations/dutch.yaml +++ b/lib/I18n/translations/dutch.yaml @@ -135,7 +135,8 @@ STR_SLEEP: "Slaap" STR_PAGE_TURN: "Pagina omslaan" STR_PORTRAIT: "Staand" STR_LANDSCAPE_CW: "Liggend (rechtsom)" -STR_INVERTED: "Omgekeerd" +STR_INVERTED: "Geïnverteerd" +STR_ORIENTATION_INVERTED: "Staand 180°" STR_LANDSCAPE_CCW: "Liggend (linksom)" STR_PREV_NEXT: "Vorige/Volgende" STR_NEXT_PREV: "Volgende/Vorige" diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 96ebbdd3..ecd7b0e0 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -139,6 +139,7 @@ STR_FORCE_REFRESH: "Refresh Screen" STR_PORTRAIT: "Portrait" STR_LANDSCAPE_CW: "Landscape CW" STR_INVERTED: "Inverted" +STR_ORIENTATION_INVERTED: "Portrait 180°" STR_LANDSCAPE_CCW: "Landscape CCW" STR_PREV_NEXT: "Prev/Next" STR_NEXT_PREV: "Next/Prev" diff --git a/lib/I18n/translations/finnish.yaml b/lib/I18n/translations/finnish.yaml index 3ccaa39c..ebe120c1 100644 --- a/lib/I18n/translations/finnish.yaml +++ b/lib/I18n/translations/finnish.yaml @@ -130,7 +130,8 @@ STR_SLEEP: "Lepotila" STR_PAGE_TURN: "Sivunkääntö" STR_PORTRAIT: "Pysty" STR_LANDSCAPE_CW: "Vaaka myötäpäivään" -STR_INVERTED: "Käännetty" +STR_INVERTED: "Käänteinen" +STR_ORIENTATION_INVERTED: "Pysty 180°" STR_LANDSCAPE_CCW: "Vaaka vastapäivään" STR_PREV_NEXT: "Edell/Seur" STR_NEXT_PREV: "Seur/Edell" diff --git a/lib/I18n/translations/french.yaml b/lib/I18n/translations/french.yaml index 9d9e32cd..db4db7ec 100644 --- a/lib/I18n/translations/french.yaml +++ b/lib/I18n/translations/french.yaml @@ -136,6 +136,7 @@ STR_PAGE_TURN: "Page suivante" STR_PORTRAIT: "Portrait" STR_LANDSCAPE_CW: "Paysage" STR_INVERTED: "Inversé" +STR_ORIENTATION_INVERTED: "Portrait 180°" STR_LANDSCAPE_CCW: "Paysage inversé" STR_PREV_NEXT: "Préc/Suiv" STR_NEXT_PREV: "Suiv/Préc" diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index a0f57fdd..e3770ae2 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -129,7 +129,8 @@ STR_PAGE_TURN: "Umblättern" STR_FORCE_REFRESH: "Bildschirm regenerieren" STR_PORTRAIT: "Hochformat" STR_LANDSCAPE_CW: "Querformat rechts" -STR_INVERTED: "Hochformat 180°" +STR_INVERTED: "Invertiert" +STR_ORIENTATION_INVERTED: "Hochformat 180°" STR_LANDSCAPE_CCW: "Querformat links" STR_PREV_NEXT: "Zurück/Weiter" STR_NEXT_PREV: "Weiter/Zurück" diff --git a/lib/I18n/translations/hebrew.yaml b/lib/I18n/translations/hebrew.yaml index 43a0beac..7d0835cf 100644 --- a/lib/I18n/translations/hebrew.yaml +++ b/lib/I18n/translations/hebrew.yaml @@ -134,7 +134,8 @@ STR_PAGE_TURN: "העברת דף" STR_FORCE_REFRESH: "רענון מסך מלא" STR_PORTRAIT: "לאורך" STR_LANDSCAPE_CW: "לרוחב (ימינה)" -STR_INVERTED: "הפוך" +STR_INVERTED: "היפוך צבעים" +STR_ORIENTATION_INVERTED: "לאורך 180°" STR_LANDSCAPE_CCW: "לרוחב (שמאלה)" STR_PREV_NEXT: "הקודם/הבא" STR_NEXT_PREV: "הבא/הקודם" diff --git a/lib/I18n/translations/hungarian.yaml b/lib/I18n/translations/hungarian.yaml index 14a5358e..b6f10036 100644 --- a/lib/I18n/translations/hungarian.yaml +++ b/lib/I18n/translations/hungarian.yaml @@ -132,7 +132,8 @@ STR_SLEEP: "Alvás" STR_PAGE_TURN: "Lapozás" STR_PORTRAIT: "Álló" STR_LANDSCAPE_CW: "Fekvő jobbra" -STR_INVERTED: "Fordított" +STR_INVERTED: "Invertált" +STR_ORIENTATION_INVERTED: "Álló 180°" STR_LANDSCAPE_CCW: "Fekvő balra" STR_PREV_NEXT: "Előző/Következő" STR_NEXT_PREV: "Következő/Előző" diff --git a/lib/I18n/translations/italian.yaml b/lib/I18n/translations/italian.yaml index 9ef433b6..f799e256 100644 --- a/lib/I18n/translations/italian.yaml +++ b/lib/I18n/translations/italian.yaml @@ -137,7 +137,8 @@ STR_PAGE_TURN: "Cambio pagina" STR_FORCE_REFRESH: "Refresh" STR_PORTRAIT: "Verticale" STR_LANDSCAPE_CW: "Orizzontale Dx" -STR_INVERTED: "Capovolto" +STR_INVERTED: "Invertito" +STR_ORIENTATION_INVERTED: "Verticale 180°" STR_LANDSCAPE_CCW: "Orizzontale Sx" STR_PREV_NEXT: "Prec/Succ" STR_NEXT_PREV: "Succ/Prec" diff --git a/lib/I18n/translations/kazakh.yaml b/lib/I18n/translations/kazakh.yaml index dfccc77b..067aaa56 100644 --- a/lib/I18n/translations/kazakh.yaml +++ b/lib/I18n/translations/kazakh.yaml @@ -126,7 +126,8 @@ STR_SLEEP: "Ұйқы" STR_PAGE_TURN: "Бет аудару" STR_PORTRAIT: "Тік бағдар" STR_LANDSCAPE_CW: "Көлденең (сағат бағытымен)" -STR_INVERTED: "Төңкерілген" +STR_INVERTED: "Инверсия" +STR_ORIENTATION_INVERTED: "Тік бағдар 180°" STR_LANDSCAPE_CCW: "Көлденең (сағат тіліне қарсы)" STR_PREV_NEXT: "Алдыңғы/Келесі" STR_NEXT_PREV: "Келесі/Алдыңғы" diff --git a/lib/I18n/translations/lithuanian.yaml b/lib/I18n/translations/lithuanian.yaml index d3c43ed3..f79a3222 100644 --- a/lib/I18n/translations/lithuanian.yaml +++ b/lib/I18n/translations/lithuanian.yaml @@ -132,7 +132,8 @@ STR_SLEEP: "Miegas" STR_PAGE_TURN: "Versti psl." STR_PORTRAIT: "Stačias" STR_LANDSCAPE_CW: "Gulsčias (P)" -STR_INVERTED: "Apverstas" +STR_INVERTED: "Invertuotas" +STR_ORIENTATION_INVERTED: "Stačias 180°" STR_LANDSCAPE_CCW: "Gulsčias (A)" STR_PREV_NEXT: "Atgal/Pirmyn" STR_NEXT_PREV: "Pirmyn/Atgal" diff --git a/lib/I18n/translations/polish.yaml b/lib/I18n/translations/polish.yaml index ba46e715..0cb8f698 100644 --- a/lib/I18n/translations/polish.yaml +++ b/lib/I18n/translations/polish.yaml @@ -136,7 +136,8 @@ STR_PAGE_TURN: "Nast. str." STR_FORCE_REFRESH: "Odśwież ekran" STR_PORTRAIT: "Pionowo" STR_LANDSCAPE_CW: "Poziomo P" -STR_INVERTED: "Odwrócony" +STR_INVERTED: "Inwersja" +STR_ORIENTATION_INVERTED: "Pionowo 180°" STR_LANDSCAPE_CCW: "Poziomo L" STR_PREV_NEXT: "Poprz./Nast." STR_NEXT_PREV: "Nast./Poprz." diff --git a/lib/I18n/translations/portuguese.yaml b/lib/I18n/translations/portuguese.yaml index 516651ab..7b68383c 100644 --- a/lib/I18n/translations/portuguese.yaml +++ b/lib/I18n/translations/portuguese.yaml @@ -131,6 +131,7 @@ STR_PAGE_TURN: "Virar página" STR_PORTRAIT: "Retrato" STR_LANDSCAPE_CW: "Paisagem H" STR_INVERTED: "Invertido" +STR_ORIENTATION_INVERTED: "Retrato 180°" STR_LANDSCAPE_CCW: "Paisagem AH" STR_PREV_NEXT: "Ant/Próx" STR_NEXT_PREV: "Próx/Ant" diff --git a/lib/I18n/translations/romanian.yaml b/lib/I18n/translations/romanian.yaml index 67052d10..0fbae3c4 100644 --- a/lib/I18n/translations/romanian.yaml +++ b/lib/I18n/translations/romanian.yaml @@ -136,6 +136,7 @@ STR_PAGE_TURN: "Răsfoire pagină" STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Orizontal dreapta" STR_INVERTED: "Invers" +STR_ORIENTATION_INVERTED: "Vertical 180°" STR_LANDSCAPE_CCW: "Orizontal stânga" STR_PREV_NEXT: "Înainte/Înapoi" STR_NEXT_PREV: "Înapoi/Înainte" diff --git a/lib/I18n/translations/russian.yaml b/lib/I18n/translations/russian.yaml index ff1187d4..bac25717 100644 --- a/lib/I18n/translations/russian.yaml +++ b/lib/I18n/translations/russian.yaml @@ -140,6 +140,7 @@ STR_FORCE_REFRESH: "Обновление экрана" STR_PORTRAIT: "Портрет" STR_LANDSCAPE_CW: "Ландшафт (CW)" STR_INVERTED: "Инверсия" +STR_ORIENTATION_INVERTED: "Портрет 180°" STR_LANDSCAPE_CCW: "Ландшафт (CCW)" STR_PREV_NEXT: "Назад/Вперёд" STR_NEXT_PREV: "Вперёд/Назад" diff --git a/lib/I18n/translations/slovak.yaml b/lib/I18n/translations/slovak.yaml index b3210422..f13d5b3e 100644 --- a/lib/I18n/translations/slovak.yaml +++ b/lib/I18n/translations/slovak.yaml @@ -137,7 +137,8 @@ STR_PAGE_TURN: "Otáčanie stránok" STR_FORCE_REFRESH: "Obnoviť obrazovku" STR_PORTRAIT: "Na výšku" STR_LANDSCAPE_CW: "Na šírku v smere hodinových ručičiek" -STR_INVERTED: "Obrátený" +STR_INVERTED: "Invertovaný" +STR_ORIENTATION_INVERTED: "Na výšku 180°" STR_LANDSCAPE_CCW: "Na šírku proti smeru hodinových ručičiek" STR_PREV_NEXT: "Predchádzajúca/Nasledujúca" STR_NEXT_PREV: "Nasledujúca/Predchádzajúca" diff --git a/lib/I18n/translations/slovenian.yaml b/lib/I18n/translations/slovenian.yaml index c01ebc14..317bcb43 100644 --- a/lib/I18n/translations/slovenian.yaml +++ b/lib/I18n/translations/slovenian.yaml @@ -132,7 +132,8 @@ STR_SLEEP: "Spanje" STR_PAGE_TURN: "Obračanje strani" STR_PORTRAIT: "Pokončno" STR_LANDSCAPE_CW: "Ležeče (v smeri urinega kazalca)" -STR_INVERTED: "Obrnjeno" +STR_INVERTED: "Invertirano" +STR_ORIENTATION_INVERTED: "Pokončno 180°" STR_LANDSCAPE_CCW: "Ležeče (proti smeri urinega kazalca)" STR_PREV_NEXT: "Nazaj/Naprej" STR_NEXT_PREV: "Naprej/Nazaj" diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index 8156f891..4ffe66e4 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -138,6 +138,7 @@ STR_FORCE_REFRESH: "Refrescar pant." STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horizontal (horario)" STR_INVERTED: "Invertido" +STR_ORIENTATION_INVERTED: "Al reves" STR_LANDSCAPE_CCW: "Horizontal (antihorario)" STR_PREV_NEXT: "Ant./Sig." STR_NEXT_PREV: "Sig./Ant." diff --git a/lib/I18n/translations/swedish.yaml b/lib/I18n/translations/swedish.yaml index ad588f19..19105830 100644 --- a/lib/I18n/translations/swedish.yaml +++ b/lib/I18n/translations/swedish.yaml @@ -138,6 +138,7 @@ STR_FORCE_REFRESH: "Uppdatera skärmen" STR_PORTRAIT: "Porträtt" STR_LANDSCAPE_CW: "Landskap medurs" STR_INVERTED: "Inverterad" +STR_ORIENTATION_INVERTED: "Porträtt 180°" STR_LANDSCAPE_CCW: "Landskap moturs" STR_PREV_NEXT: "Förra/Nästa" STR_NEXT_PREV: "Nästa/Förra" diff --git a/lib/I18n/translations/turkish.yaml b/lib/I18n/translations/turkish.yaml index 0faa13ec..ac6dd32c 100644 --- a/lib/I18n/translations/turkish.yaml +++ b/lib/I18n/translations/turkish.yaml @@ -130,7 +130,8 @@ STR_SLEEP: "Uyku" STR_PAGE_TURN: "Sayfa Çevirme" STR_PORTRAIT: "Dikey" STR_LANDSCAPE_CW: "Yatay (Saat Yönü)" -STR_INVERTED: "Ters" +STR_INVERTED: "Negatif" +STR_ORIENTATION_INVERTED: "Dikey 180°" STR_LANDSCAPE_CCW: "Yatay (Saat Yönü Tersi)" STR_PREV_NEXT: "Önceki/Sonraki" STR_NEXT_PREV: "Sonraki/Önceki" diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index c60de68f..6a34e1a9 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -137,7 +137,8 @@ STR_PAGE_TURN: "Наст. сторінка" STR_FORCE_REFRESH: "Оновити екран" STR_PORTRAIT: "Книжкова" STR_LANDSCAPE_CW: "Альбом. за год." -STR_INVERTED: "Перевернутий" +STR_INVERTED: "Інверсія" +STR_ORIENTATION_INVERTED: "Книжкова 180°" STR_LANDSCAPE_CCW: "Альбом. проти год." STR_PREV_NEXT: "Попер/Наст" STR_NEXT_PREV: "Наст/Попер" diff --git a/lib/I18n/translations/valencian.yaml b/lib/I18n/translations/valencian.yaml index 6dd28a25..89a7d962 100644 --- a/lib/I18n/translations/valencian.yaml +++ b/lib/I18n/translations/valencian.yaml @@ -141,6 +141,7 @@ STR_PAGE_TURN: "Canvi de pàgina" STR_PORTRAIT: "Vertical" STR_LANDSCAPE_CW: "Horitzontal horari" STR_INVERTED: "Invertit" +STR_ORIENTATION_INVERTED: "Vertical 180°" STR_LANDSCAPE_CCW: "Horitzontal antihorari" STR_PREV_NEXT: "Anterior/Següent" STR_NEXT_PREV: "Següent/Anterior" diff --git a/lib/I18n/translations/vietnamese.yaml b/lib/I18n/translations/vietnamese.yaml index 2551b275..e73385fc 100644 --- a/lib/I18n/translations/vietnamese.yaml +++ b/lib/I18n/translations/vietnamese.yaml @@ -137,7 +137,8 @@ STR_PAGE_TURN: "Lật trang" STR_FORCE_REFRESH: "Làm tươi màn hình" STR_PORTRAIT: "Dọc" STR_LANDSCAPE_CW: "Ngang (thuận)" -STR_INVERTED: "Lật ngược" +STR_INVERTED: "Đảo màu" +STR_ORIENTATION_INVERTED: "Dọc 180°" STR_LANDSCAPE_CCW: "Ngang (ngược)" STR_PREV_NEXT: "Trước/Sau" STR_NEXT_PREV: "Sau/Trước" diff --git a/src/SettingsList.h b/src/SettingsList.h index 695e8eec..a0e43cc8 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -151,9 +151,10 @@ inline std::vector getSettingsList(const SdCardFontRegistry* regist StrId::STR_CAT_READER), SettingInfo::Toggle(StrId::STR_HYPHENATION, &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled", StrId::STR_CAT_READER), - SettingInfo::Enum(StrId::STR_ORIENTATION, &CrossPointSettings::orientation, - {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW}, - "orientation", StrId::STR_CAT_READER), + SettingInfo::Enum( + StrId::STR_ORIENTATION, &CrossPointSettings::orientation, + {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_ORIENTATION_INVERTED, StrId::STR_LANDSCAPE_CCW}, + "orientation", StrId::STR_CAT_READER), SettingInfo::Toggle(StrId::STR_EXTRA_SPACING, &CrossPointSettings::extraParagraphSpacing, "extraParagraphSpacing", StrId::STR_CAT_READER), SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", From fc89e57e698a662d82d5d34ae77366c9752c355d Mon Sep 17 00:00:00 2001 From: Uri Tauber Date: Thu, 25 Jun 2026 09:01:50 +0300 Subject: [PATCH 04/35] perf: optimise `normalisePath` (#2162) --- lib/FsHelpers/FsHelpers.cpp | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/FsHelpers/FsHelpers.cpp b/lib/FsHelpers/FsHelpers.cpp index 1e3c68a6..6b315633 100644 --- a/lib/FsHelpers/FsHelpers.cpp +++ b/lib/FsHelpers/FsHelpers.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace FsHelpers { @@ -36,12 +37,14 @@ std::string decodeUriEscapes(const std::string& path) { } std::string normalisePath(const std::string& path) { - std::vector components; - std::string component; + std::vector components; + components.reserve(8); // Eight nested folders is more than we might expect - for (const auto c : path) { - if (c == '/') { - if (!component.empty()) { + size_t start = 0; + for (size_t i = 0; i <= path.length(); ++i) { + if (i == path.length() || path[i] == '/') { + if (i > start) { + std::string_view component(path.data() + start, i - start); if (component == "..") { if (!components.empty()) { components.pop_back(); @@ -49,23 +52,28 @@ std::string normalisePath(const std::string& path) { } else { components.push_back(component); } - component.clear(); } - } else { - component += c; + start = i + 1; } } - if (!component.empty()) { - components.push_back(component); + if (components.empty()) { + return ""; + } + + size_t total_len = 0; + for (const auto& c : components) { + total_len += c.length() + 1; } std::string result; - for (const auto& c : components) { - if (!result.empty()) { - result += "/"; + result.reserve(total_len - 1); + + for (size_t i = 0; i < components.size(); ++i) { + if (i > 0) { + result += '/'; } - result += c; + result.append(components[i].data(), components[i].length()); } return result; From 8626d69f46052b70f68dd5df3cc1caf27de3bf5d Mon Sep 17 00:00:00 2001 From: Bastian <391732+dasrecht@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:29:05 +0200 Subject: [PATCH 05/35] fix: small translation changes for german (#2420) --- lib/I18n/translations/german.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index e3770ae2..a7c1ad08 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -70,7 +70,7 @@ STR_ORIENTATION: "Leseausrichtung" STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)" STR_FRONT_BTN_FOLLOW_ORIENTATION: "Vordere Tasten ausrichten" STR_LONG_PRESS_BEHAVIOR: "Verhalten bei langem Tastendruck" -STR_LONG_PRESS_BEHAVIOR_OFF: "AUS" +STR_LONG_PRESS_BEHAVIOR_OFF: "Aus" STR_LONG_PRESS_BEHAVIOR_SKIP: "Kapitel überspringen" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ausrichtung ändern" STR_FONT_PREVIEW_TEXT: "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich" @@ -79,8 +79,9 @@ STR_FONT_SIZE: "Schriftgröße" STR_LINE_SPACING: "Lese-Zeilenabstand" STR_SCREEN_MARGIN: "Lese-Seitenränder" STR_PARA_ALIGNMENT: "Lese-Absatzausrichtung" +STR_LONG_PRESS_MENU: "Menütaste lang drücken" STR_HYPHENATION: "Silbentrennung" -STR_TIME_TO_SLEEP: "Standby nach" +STR_TIME_TO_SLEEP: "Standby-Modus nach" STR_REFRESH_FREQ: "Anti-Ghosting nach" STR_KOREADER_SYNC: "KOReader-Synchr." STR_CHECK_UPDATES: "Nach Updates suchen" From a09aef0889341673c242a6e886a9750a9c8ddae4 Mon Sep 17 00:00:00 2001 From: Uri Tauber Date: Thu, 25 Jun 2026 16:20:24 +0300 Subject: [PATCH 06/35] fix: Optimize Bookmark Rendering by Removing XPath Lookup (#2417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary fix #2414 ### Root Cause `updateBookmarkFlag()` was introduced in commit 1db1442 and is executed on every render (every page turn). The function calls: `ProgressMapper::toSavedProgress()` → `ChapterXPathResolver::findXPathForProgress()` This path decompresses the current EPUB section content twice: 1. To count visible characters. 2. To resolve the corresponding XPath. For larger sections (e.g. ~133 KB decompressed content), this adds approximately **1 second of I/O overhead per page turn**, with the cost increasing as chapter size grows. ### Fix `updateBookmarkFlag()` only needs to determine whether a bookmark falls within the currently displayed page range. The required information is already available during rendering: * `currentPage` * `section->pageCount` * `currentSpineIndex` Instead of converting the current location to a saved progress object (and resolving an XPath), the implementation now computes the current page's progress range directly and compares bookmark percentages against that range. This is effectively the same percentage-based matching logic already used as a fallback in `bookmarkMatchesProgress()` when XPath matching is unavailable. --- ### 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? _**< PARTIALLY >**_ --- src/activities/reader/EpubReaderActivity.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 3d2df14e..6e1a1978 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -1350,11 +1350,15 @@ void EpubReaderActivity::updateBookmarkFlag() { currentPageBookmarked = false; return; } - SavedProgressPosition progress = ProgressMapper::toSavedProgress(epub, getCurrentPosition()); const ProgressRange pageRange = getPageProgressRange(epub, currentSpineIndex, section->currentPage, section->pageCount); currentPageBookmarked = std::any_of(cachedBookmarks.begin(), cachedBookmarks.end(), [&](const BookmarkEntry& b) { - return bookmarkMatchesProgress(b, progress, pageRange); + if (b.computedSpineIndex == currentSpineIndex && b.computedChapterPageCount == section->pageCount && + b.computedChapterProgress == section->currentPage) { + return true; + } + const float bp = std::clamp(b.percentage, 0.0f, 1.0f); + return bp + bookmarkProgressEpsilon >= pageRange.start && bp - bookmarkProgressEpsilon <= pageRange.end; }); } From 0a57c0a5a7b96809418f244515999b31e2a8cd55 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Wed, 24 Jun 2026 21:26:57 -0400 Subject: [PATCH 07/35] Fix X3 display ghosting on cover screen transitions Force display resync on X3 when HALF refresh is requested to clear prior content before rendering. Add grayscale preconditioning for X3's UC81xx controller to even out single-pixel dithering artifacts that appear as speckle with its turbo BW waveform. --- lib/hal/HalDisplay.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index e3a5b644..f90e85c0 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -82,6 +82,17 @@ void HalDisplay::copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* m } void HalDisplay::displayGrayscaleBase(RefreshMode fallback, bool turnOffScreen) { + // X3: a HALF fallback means the caller wants a clean base (e.g. the sleep + // cover, a full-screen swap from arbitrary prior content). Without this, the + // X3 grayscale base takes its gentle differential happy path and the prior + // home/reader frame ghosts through the soft aa_pre_bw_mid waveform. Forcing a + // resync makes displayGrayscaleBase clear first, matching displayBuffer(HALF). + // The reader's FAST path is deliberately left on the differential path so + // per-page grayscale stays cheap. + if (gpio.deviceIsX3() && fallback == RefreshMode::HALF_REFRESH) { + einkDisplay.requestResync(1); + } + einkDisplay.displayGrayscaleBase(convertRefreshMode(fallback), turnOffScreen); } From 555f76da88da30eca475d8d5400314b913d5c036 Mon Sep 17 00:00:00 2001 From: Pietro Campagnano Date: Thu, 25 Jun 2026 23:45:43 +0200 Subject: [PATCH 08/35] feat: move file manager breadcrumb into contents card header (#2430) --- src/network/html/FilesPage.html | 39 +++++++++++---------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/src/network/html/FilesPage.html b/src/network/html/FilesPage.html index bec1ba33..36f38b0e 100644 --- a/src/network/html/FilesPage.html +++ b/src/network/html/FilesPage.html @@ -80,11 +80,11 @@ } .breadcrumb-inline .sep { margin: 0 6px; - color: var(--border-color); + color: var(--label-color); } .breadcrumb-inline .current { color: var(--title-color); - font-weight: 500; + font-weight: 600; } .nav-links { margin: 20px 0; @@ -960,12 +960,6 @@ align-items: center; margin-bottom: 12px; } - .contents-title { - font-size: 1.1em; - font-weight: 600; - color: var(--title-color); - margin: 0; - } .summary-inline { color: var(--label-color); font-size: 0.9em; @@ -1296,9 +1290,6 @@ flex-wrap: wrap; gap: 4px; } - .contents-title { - font-size: 1em; - } .summary-inline { font-size: 0.8em; } @@ -1501,7 +1492,6 @@