A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: split status bar setting (#733)

## Summary

This PR aims to reduce the complexity of the status bar by splitting the
setting into 5:
- Chapter Page Count
- Book Progress %
- Progress Bar
- Chapter Title
- Battery Indicator

These are located within the new StausBarSettings activity, which also
shows a preview of the bar the user has created

<img width="513" height="806" alt="image"
src="https://github.com/user-attachments/assets/cdf852fb-15d8-4da2-a74f-fd69294d7b05"
/>


<img width="483" height="797" alt="image"
src="https://github.com/user-attachments/assets/66fc0c0d-ee51-4d31-b70d-e2bc043205d1"
/>


When updating from a previous version, the user's past settings are
honoured.

## Additional Context

The PR aims to remove any duplication of status bar code where possible,
and extracts the status bar rendering into a new component - StatusBar

It also adds a new (optional) padding option to the progress bar to
allow the status bar to be shifted upwards - this is only intended for
use in the settings.

---

### 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 - although did help to decode some C++ errors

---------

Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>

authored by

James Whyte
Arthur Tazhitdinov
and committed by
GitHub
2d49c7b7 cb729163

+505 -300
+1 -1
lib/I18n/I18n.cpp
··· 93 93 } 94 94 95 95 return CHARACTER_SETS[static_cast<size_t>(lang)]; 96 - } 96 + }
-4
lib/I18n/translations/catalan.yaml
··· 249 249 STR_ON_MARKER: "[ON]" 250 250 STR_SLEEP_COVER_FILTER: "Filtre de pantalla de repòs" 251 251 STR_FILTER_CONTRAST: "Contrast" 252 - STR_STATUS_BAR_FULL_PERCENT: "Amb percentatge" 253 - STR_STATUS_BAR_FULL_BOOK: "Amb progrés llibre" 254 - STR_STATUS_BAR_BOOK_ONLY: "Només progrés llibre" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Amb progrés capítol" 256 252 STR_UI_THEME: "Tema de la interfície" 257 253 STR_THEME_CLASSIC: "Clàssic" 258 254 STR_THEME_LYRA: "Lyra"
+1 -5
lib/I18n/translations/czech.yaml
··· 249 249 STR_ON_MARKER: "[ZAP]" 250 250 STR_SLEEP_COVER_FILTER: "Filtr obrazovky spánku" 251 251 STR_FILTER_CONTRAST: "Kontrast" 252 - STR_STATUS_BAR_FULL_PERCENT: "Plný s procenty" 253 - STR_STATUS_BAR_FULL_BOOK: "Plný s pruhem knih" 254 - STR_STATUS_BAR_BOOK_ONLY: "Pouze pruh knih" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Plná s pruhem kapitol" 256 252 STR_UI_THEME: "Šablona rozhraní" 257 253 STR_THEME_CLASSIC: "Klasická" 258 254 STR_THEME_LYRA: "Lyra" ··· 317 313 STR_BOOK_S_STYLE: "Styl knihy" 318 314 STR_EMBEDDED_STYLE: "Vložený styl" 319 315 STR_OPDS_SERVER_URL: "URL serveru OPDS" 320 - STR_SCREENSHOT_BUTTON: "Udělat snímek obrazovky" 316 + STR_SCREENSHOT_BUTTON: "Udělat snímek obrazovky"
+17 -4
lib/I18n/translations/english.yaml
··· 235 235 STR_RETRY: "Retry" 236 236 STR_YES: "Yes" 237 237 STR_NO: "No" 238 + STR_SHOW: "Show" 239 + STR_HIDE: "Hide" 238 240 STR_STATE_ON: "ON" 239 241 STR_STATE_OFF: "OFF" 240 242 STR_SET: "Set" ··· 249 251 STR_ON_MARKER: "[ON]" 250 252 STR_SLEEP_COVER_FILTER: "Sleep Screen Cover Filter" 251 253 STR_FILTER_CONTRAST: "Contrast" 252 - STR_STATUS_BAR_FULL_PERCENT: "Full w/ Percentage" 253 - STR_STATUS_BAR_FULL_BOOK: "Full w/ Book Bar" 254 - STR_STATUS_BAR_BOOK_ONLY: "Book Bar Only" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Full w/ Chapter Bar" 254 + STR_CUSTOMISE_STATUS_BAR: "Customise Status Bar" 255 + STR_CHAPTER_PAGE_COUNT: "Chapter Page Count" 256 + STR_BOOK_PROGRESS_PERCENTAGE: "Book Progress Percentage" 257 + STR_PROGRESS_BAR: "Progress Bar" 258 + STR_PROGRESS_BAR_THICKNESS: "Progress Bar Thickness" 259 + STR_PROGRESS_BAR_THIN: "Thin" 260 + STR_PROGRESS_BAR_MEDIUM: "Medium" 261 + STR_PROGRESS_BAR_THICK: "Thick" 262 + STR_BOOK: "Book" 263 + STR_CHAPTER: "Chapter" 264 + STR_EXAMPLE_CHAPTER: "Chapter 21" 265 + STR_EXAMPLE_BOOK: "Book Title" 266 + STR_PREVIEW: "Preview" 267 + STR_TITLE: "Title" 268 + STR_BATTERY: "Battery" 256 269 STR_UI_THEME: "UI Theme" 257 270 STR_THEME_CLASSIC: "Classic" 258 271 STR_THEME_LYRA: "Lyra"
-4
lib/I18n/translations/french.yaml
··· 249 249 STR_ON_MARKER: "[ON]" 250 250 STR_SLEEP_COVER_FILTER: "Filtre écran de veille" 251 251 STR_FILTER_CONTRAST: "Contraste" 252 - STR_STATUS_BAR_FULL_PERCENT: "Complète + %" 253 - STR_STATUS_BAR_FULL_BOOK: "Complète + barre livre" 254 - STR_STATUS_BAR_BOOK_ONLY: "Barre livre seule" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Complète + barre chap." 256 252 STR_UI_THEME: "Thème interface" 257 253 STR_THEME_CLASSIC: "Classique" 258 254 STR_THEME_LYRA: "Lyra"
+1 -5
lib/I18n/translations/german.yaml
··· 249 249 STR_ON_MARKER: "[AN]" 250 250 STR_SLEEP_COVER_FILTER: "Standby-Coverfilter" 251 251 STR_FILTER_CONTRAST: "Kontrast" 252 - STR_STATUS_BAR_FULL_PERCENT: "Komplett + Prozent" 253 - STR_STATUS_BAR_FULL_BOOK: "Komplett + Buch" 254 - STR_STATUS_BAR_BOOK_ONLY: "Nur Buch" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Komplett + Kapitel" 256 252 STR_UI_THEME: "System-Design" 257 253 STR_THEME_CLASSIC: "Klassisch" 258 254 STR_THEME_LYRA: "Lyra" ··· 317 313 STR_BOOK_S_STYLE: "Buch-Stil" 318 314 STR_EMBEDDED_STYLE: "Eingebetteter Stil" 319 315 STR_OPDS_SERVER_URL: "OPDS-Server-URL" 320 - STR_SCREENSHOT_BUTTON: "Screenshot aufnehmen" 316 + STR_SCREENSHOT_BUTTON: "Screenshot aufnehmen"
+1 -5
lib/I18n/translations/portuguese.yaml
··· 249 249 STR_ON_MARKER: "[LIGADO]" 250 250 STR_SLEEP_COVER_FILTER: "Filtro capa tela repouso" 251 251 STR_FILTER_CONTRAST: "Contraste" 252 - STR_STATUS_BAR_FULL_PERCENT: "Completa c/ porcentagem" 253 - STR_STATUS_BAR_FULL_BOOK: "Completa c/ barra livro" 254 - STR_STATUS_BAR_BOOK_ONLY: "Só barra do livro" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Completa c/ barra capítulo" 256 252 STR_UI_THEME: "Tema da interface" 257 253 STR_THEME_CLASSIC: "Clássico" 258 254 STR_THEME_LYRA: "Lyra" ··· 317 313 STR_BOOK_S_STYLE: "Estilo do livro" 318 314 STR_EMBEDDED_STYLE: "Estilo embutido" 319 315 STR_OPDS_SERVER_URL: "URL do servidor OPDS" 320 - STR_SCREENSHOT_BUTTON: "Capturar tela" 316 + STR_SCREENSHOT_BUTTON: "Capturar tela"
-4
lib/I18n/translations/romanian.yaml
··· 249 249 STR_ON_MARKER: "[ON]" 250 250 STR_SLEEP_COVER_FILTER: "Filtru ecran de repaus" 251 251 STR_FILTER_CONTRAST: "Contrast" 252 - STR_STATUS_BAR_FULL_PERCENT: "Complet cu procentaj" 253 - STR_STATUS_BAR_FULL_BOOK: "Complet cu bara de carte" 254 - STR_STATUS_BAR_BOOK_ONLY: "Doar bara de carte" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Complet cu bara de capitol" 256 252 STR_UI_THEME: "Tema UI" 257 253 STR_THEME_CLASSIC: "Clasic" 258 254 STR_THEME_LYRA: "Lyra"
+1 -5
lib/I18n/translations/russian.yaml
··· 249 249 STR_ON_MARKER: "[ВКЛ]" 250 250 STR_SLEEP_COVER_FILTER: "Фильтр экрана сна" 251 251 STR_FILTER_CONTRAST: "Контраст" 252 - STR_STATUS_BAR_FULL_PERCENT: "Полная + %" 253 - STR_STATUS_BAR_FULL_BOOK: "Полная + шкала книги" 254 - STR_STATUS_BAR_BOOK_ONLY: "Только шкала книги" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Полная + шкала главы" 256 252 STR_UI_THEME: "Тема интерфейса" 257 253 STR_THEME_CLASSIC: "Классическая" 258 254 STR_THEME_LYRA: "Lyra" ··· 317 313 STR_BOOK_S_STYLE: "Стиль книги" 318 314 STR_EMBEDDED_STYLE: "Встроенный стиль" 319 315 STR_OPDS_SERVER_URL: "URL OPDS сервера" 320 - STR_SCREENSHOT_BUTTON: "Сделать снимок экрана" 316 + STR_SCREENSHOT_BUTTON: "Сделать снимок экрана"
+1 -5
lib/I18n/translations/spanish.yaml
··· 249 249 STR_ON_MARKER: "[Activo]" 250 250 STR_SLEEP_COVER_FILTER: "Filtro de pantalla de suspensión" 251 251 STR_FILTER_CONTRAST: "Contraste" 252 - STR_STATUS_BAR_FULL_PERCENT: "Completa con %" 253 - STR_STATUS_BAR_FULL_BOOK: "Completa con progreso lect." 254 - STR_STATUS_BAR_BOOK_ONLY: "Solo progreso" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Completa con progreso cap." 256 252 STR_UI_THEME: "Interfaz" 257 253 STR_THEME_CLASSIC: "Clásico" 258 254 STR_THEME_LYRA: "Lyra" ··· 317 313 STR_BOOK_S_STYLE: "Estilo del libro" 318 314 STR_EMBEDDED_STYLE: "Estilo integrado" 319 315 STR_OPDS_SERVER_URL: "URL del servidor OPDS" 320 - STR_SCREENSHOT_BUTTON: "Tomar captura de pantalla" 316 + STR_SCREENSHOT_BUTTON: "Tomar captura de pantalla"
+1 -5
lib/I18n/translations/swedish.yaml
··· 249 249 STR_ON_MARKER: "[PÅ]" 250 250 STR_SLEEP_COVER_FILTER: "Viloskärmens omslagsfilter" 251 251 STR_FILTER_CONTRAST: "Kontrast" 252 - STR_STATUS_BAR_FULL_PERCENT: "Full w/ Procent" 253 - STR_STATUS_BAR_FULL_BOOK: "Full w/ Boklist" 254 - STR_STATUS_BAR_BOOK_ONLY: "Boklist enbart" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Full w/ Kapitellist" 256 252 STR_UI_THEME: "Användargränssnittstema" 257 253 STR_THEME_CLASSIC: "Klassisk" 258 254 STR_THEME_LYRA: "Lyra" ··· 317 313 STR_BOOK_S_STYLE: "Bokstil" 318 314 STR_EMBEDDED_STYLE: "Inbäddad stil" 319 315 STR_OPDS_SERVER_URL: "OPDS-serveradress" 320 - STR_SCREENSHOT_BUTTON: "Ta en skärmdump" 316 + STR_SCREENSHOT_BUTTON: "Ta en skärmdump"
-4
lib/I18n/translations/ukrainian.yaml
··· 249 249 STR_ON_MARKER: "[УВІМК]" 250 250 STR_SLEEP_COVER_FILTER: "Фільтр обкладинки екрана сну" 251 251 STR_FILTER_CONTRAST: "Контраст" 252 - STR_STATUS_BAR_FULL_PERCENT: "Повна з відсотками" 253 - STR_STATUS_BAR_FULL_BOOK: "Повна з прогресом книги" 254 - STR_STATUS_BAR_BOOK_ONLY: "Тільки прогрес книги" 255 - STR_STATUS_BAR_FULL_CHAPTER: "Повна з панеллю розділу" 256 252 STR_UI_THEME: "Тема інтерфейсу" 257 253 STR_THEME_CLASSIC: "Класична" 258 254 STR_THEME_LYRA: "Lyra"
+2 -1
src/CrossPointSettings.cpp
··· 57 57 break; 58 58 } 59 59 } 60 + 60 61 } // namespace 61 62 62 63 void CrossPointSettings::validateFrontButtonMapping(CrossPointSettings& settings) { ··· 141 142 if (++settingsRead >= fileSettingsCount) break; 142 143 readAndValidate(inputFile, shortPwrBtn, SHORT_PWRBTN_COUNT); 143 144 if (++settingsRead >= fileSettingsCount) break; 144 - readAndValidate(inputFile, statusBar, STATUS_BAR_MODE_COUNT); 145 + readAndValidate(inputFile, statusBar, STATUS_BAR_MODE_COUNT); // legacy 145 146 if (++settingsRead >= fileSettingsCount) break; 146 147 readAndValidate(inputFile, orientation, ORIENTATION_COUNT); 147 148 if (++settingsRead >= fileSettingsCount) break;
+21 -2
src/CrossPointSettings.h
··· 35 35 SLEEP_SCREEN_COVER_FILTER_COUNT 36 36 }; 37 37 38 - // Status bar display type enum 38 + // Status bar enum - legacy 39 39 enum STATUS_BAR_MODE { 40 40 NONE = 0, 41 41 NO_PROGRESS = 1, ··· 45 45 CHAPTER_PROGRESS_BAR = 5, 46 46 STATUS_BAR_MODE_COUNT 47 47 }; 48 + enum STATUS_BAR_PROGRESS_BAR { 49 + BOOK_PROGRESS = 0, 50 + CHAPTER_PROGRESS = 1, 51 + HIDE_PROGRESS = 2, 52 + STATUS_BAR_PROGRESS_BAR_COUNT 53 + }; 54 + enum STATUS_BAR_PROGRESS_BAR_THICKNESS { 55 + PROGRESS_BAR_THIN = 0, 56 + PROGRESS_BAR_NORMAL = 1, 57 + PROGRESS_BAR_THICK = 2, 58 + STATUS_BAR_PROGRESS_BAR_THICKNESS_COUNT 59 + }; 60 + enum STATUS_BAR_TITLE { BOOK_TITLE = 0, CHAPTER_TITLE = 1, HIDE_TITLE = 2, STATUS_BAR_TITLE_COUNT }; 48 61 49 62 enum ORIENTATION { 50 63 PORTRAIT = 0, // 480x800 logical coordinates (current default) ··· 128 141 uint8_t sleepScreenCoverMode = FIT; 129 142 // Sleep screen cover filter 130 143 uint8_t sleepScreenCoverFilter = NO_FILTER; 131 - // Status bar settings 144 + // Status bar settings (statusBar retained for migration only) 132 145 uint8_t statusBar = FULL; 146 + uint8_t statusBarChapterPageCount = 1; 147 + uint8_t statusBarBookProgressPercentage = 1; 148 + uint8_t statusBarProgressBar = HIDE_PROGRESS; 149 + uint8_t statusBarProgressBarThickness = PROGRESS_BAR_NORMAL; 150 + uint8_t statusBarTitle = CHAPTER_TITLE; 151 + uint8_t statusBarBattery = 1; 133 152 // Text rendering settings 134 153 uint8_t extraParagraphSpacing = 1; 135 154 uint8_t textAntiAliasing = 1;
+68
src/JsonSettingsIO.cpp
··· 13 13 #include "RecentBooksStore.h" 14 14 #include "WifiCredentialStore.h" 15 15 16 + // Convert legacy settings. 17 + void applyLegacyStatusBarSettings(CrossPointSettings& settings) { 18 + switch (static_cast<CrossPointSettings::STATUS_BAR_MODE>(settings.statusBar)) { 19 + case CrossPointSettings::NONE: 20 + settings.statusBarChapterPageCount = 0; 21 + settings.statusBarBookProgressPercentage = 0; 22 + settings.statusBarProgressBar = CrossPointSettings::HIDE_PROGRESS; 23 + settings.statusBarTitle = CrossPointSettings::HIDE_TITLE; 24 + settings.statusBarBattery = 0; 25 + break; 26 + case CrossPointSettings::NO_PROGRESS: 27 + settings.statusBarChapterPageCount = 0; 28 + settings.statusBarBookProgressPercentage = 0; 29 + settings.statusBarProgressBar = CrossPointSettings::HIDE_PROGRESS; 30 + settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; 31 + settings.statusBarBattery = 1; 32 + break; 33 + case CrossPointSettings::BOOK_PROGRESS_BAR: 34 + settings.statusBarChapterPageCount = 1; 35 + settings.statusBarBookProgressPercentage = 0; 36 + settings.statusBarProgressBar = CrossPointSettings::BOOK_PROGRESS; 37 + settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; 38 + settings.statusBarBattery = 1; 39 + break; 40 + case CrossPointSettings::ONLY_BOOK_PROGRESS_BAR: 41 + settings.statusBarChapterPageCount = 1; 42 + settings.statusBarBookProgressPercentage = 0; 43 + settings.statusBarProgressBar = CrossPointSettings::BOOK_PROGRESS; 44 + settings.statusBarTitle = CrossPointSettings::HIDE_TITLE; 45 + settings.statusBarBattery = 0; 46 + break; 47 + case CrossPointSettings::CHAPTER_PROGRESS_BAR: 48 + settings.statusBarChapterPageCount = 0; 49 + settings.statusBarBookProgressPercentage = 1; 50 + settings.statusBarProgressBar = CrossPointSettings::CHAPTER_PROGRESS; 51 + settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; 52 + settings.statusBarBattery = 1; 53 + break; 54 + case CrossPointSettings::FULL: 55 + default: 56 + settings.statusBarChapterPageCount = 1; 57 + settings.statusBarBookProgressPercentage = 1; 58 + settings.statusBarProgressBar = CrossPointSettings::HIDE_PROGRESS; 59 + settings.statusBarTitle = CrossPointSettings::CHAPTER_TITLE; 60 + settings.statusBarBattery = 1; 61 + break; 62 + } 63 + } 64 + 16 65 // ---- CrossPointState ---- 17 66 18 67 bool JsonSettingsIO::saveState(const CrossPointState& s, const char* path) { ··· 76 125 doc["uiTheme"] = s.uiTheme; 77 126 doc["fadingFix"] = s.fadingFix; 78 127 doc["embeddedStyle"] = s.embeddedStyle; 128 + doc["statusBarChapterPageCount"] = s.statusBarChapterPageCount; 129 + doc["statusBarBookProgressPercentage"] = s.statusBarBookProgressPercentage; 130 + doc["statusBarProgressBar"] = s.statusBarProgressBar; 131 + doc["statusBarTitle"] = s.statusBarTitle; 132 + doc["statusBarBattery"] = s.statusBarBattery; 133 + doc["statusBarProgressBarThickness"] = s.statusBarProgressBarThickness; 79 134 80 135 String json; 81 136 serializeJson(doc, json); ··· 149 204 strncpy(s.opdsPassword, pass.c_str(), sizeof(s.opdsPassword) - 1); 150 205 s.opdsPassword[sizeof(s.opdsPassword) - 1] = '\0'; 151 206 LOG_DBG("CPS", "Settings loaded from file"); 207 + 208 + if (doc.containsKey("statusBarChapterPageCount")) { 209 + s.statusBarChapterPageCount = doc["statusBarChapterPageCount"]; 210 + s.statusBarBookProgressPercentage = doc["statusBarBookProgressPercentage"]; 211 + s.statusBarProgressBar = doc["statusBarProgressBar"]; 212 + s.statusBarTitle = doc["statusBarTitle"]; 213 + s.statusBarBattery = doc["statusBarBattery"]; 214 + } else { 215 + applyLegacyStatusBarSettings(s); 216 + } 217 + 218 + s.statusBarProgressBarThickness = doc["statusBarProgressBarThickness"] | (uint8_t)S::PROGRESS_BAR_NORMAL; 219 + 152 220 return true; 153 221 } 154 222
+17 -7
src/SettingsList.h
··· 23 23 SettingInfo::Enum(StrId::STR_SLEEP_COVER_FILTER, &CrossPointSettings::sleepScreenCoverFilter, 24 24 {StrId::STR_NONE_OPT, StrId::STR_FILTER_CONTRAST, StrId::STR_INVERTED}, 25 25 "sleepScreenCoverFilter", StrId::STR_CAT_DISPLAY), 26 - SettingInfo::Enum( 27 - StrId::STR_STATUS_BAR, &CrossPointSettings::statusBar, 28 - {StrId::STR_NONE_OPT, StrId::STR_NO_PROGRESS, StrId::STR_STATUS_BAR_FULL_PERCENT, 29 - StrId::STR_STATUS_BAR_FULL_BOOK, StrId::STR_STATUS_BAR_BOOK_ONLY, StrId::STR_STATUS_BAR_FULL_CHAPTER}, 30 - "statusBar", StrId::STR_CAT_DISPLAY), 31 26 SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage, 32 27 {StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage", 33 28 StrId::STR_CAT_DISPLAY), ··· 67 62 StrId::STR_CAT_READER), 68 63 SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", 69 64 StrId::STR_CAT_READER), 70 - 71 65 // --- Controls --- 72 66 SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, 73 67 {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), ··· 120 114 StrId::STR_OPDS_BROWSER), 121 115 SettingInfo::String(StrId::STR_PASSWORD, SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword", 122 116 StrId::STR_OPDS_BROWSER), 117 + 118 + // --- Status Bar Settings (web-only, uses StatusBarSettingsActivity) --- 119 + SettingInfo::Toggle(StrId::STR_CHAPTER_PAGE_COUNT, &CrossPointSettings::statusBarChapterPageCount, 120 + "statusBarChapterPageCount", StrId::STR_CUSTOMISE_STATUS_BAR), 121 + SettingInfo::Toggle(StrId::STR_BOOK_PROGRESS_PERCENTAGE, &CrossPointSettings::statusBarBookProgressPercentage, 122 + "statusBarBookProgressPercentage", StrId::STR_CUSTOMISE_STATUS_BAR), 123 + SettingInfo::Enum(StrId::STR_PROGRESS_BAR, &CrossPointSettings::statusBarProgressBar, 124 + {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}, "statusBarProgressBar", 125 + StrId::STR_CUSTOMISE_STATUS_BAR), 126 + SettingInfo::Enum(StrId::STR_PROGRESS_BAR_THICKNESS, &CrossPointSettings::statusBarProgressBarThickness, 127 + {StrId::STR_PROGRESS_BAR_THIN, StrId::STR_PROGRESS_BAR_MEDIUM, StrId::STR_PROGRESS_BAR_THICK}), 128 + SettingInfo::Enum(StrId::STR_TITLE, &CrossPointSettings::statusBarTitle, 129 + {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}, "statusBarTitle", 130 + StrId::STR_CUSTOMISE_STATUS_BAR), 131 + SettingInfo::Toggle(StrId::STR_BATTERY, &CrossPointSettings::statusBarBattery, "statusBarBattery", 132 + StrId::STR_CUSTOMISE_STATUS_BAR), 123 133 }; 124 - } 134 + }
+19 -115
src/activities/reader/EpubReaderActivity.cpp
··· 25 25 // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() 26 26 constexpr unsigned long skipChapterMs = 700; 27 27 constexpr unsigned long goHomeMs = 1000; 28 - constexpr int statusBarMargin = 19; 29 - constexpr int progressBarMarginTop = 1; 30 28 31 29 int clampPercent(int percent) { 32 30 if (percent < 0) { ··· 558 556 orientedMarginTop += SETTINGS.screenMargin; 559 557 orientedMarginLeft += SETTINGS.screenMargin; 560 558 orientedMarginRight += SETTINGS.screenMargin; 561 - orientedMarginBottom += SETTINGS.screenMargin; 562 - 563 - const auto& metrics = UITheme::getInstance().getMetrics(); 564 - 565 - // Add status bar margin 566 - if (SETTINGS.statusBar != CrossPointSettings::STATUS_BAR_MODE::NONE) { 567 - // Add additional margin for status bar if progress bar is shown 568 - const bool showProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 569 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_BOOK_PROGRESS_BAR || 570 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 571 - orientedMarginBottom += statusBarMargin - SETTINGS.screenMargin + 572 - (showProgressBar ? (metrics.bookProgressBarHeight + progressBarMarginTop) : 0); 573 - } 559 + orientedMarginBottom += 560 + std::max(SETTINGS.screenMargin, static_cast<uint8_t>(UITheme::getInstance().getStatusBarHeight())); 574 561 575 562 if (!section) { 576 563 const auto filepath = epub->getSpineItem(currentSpineIndex).href; ··· 631 618 if (section->pageCount == 0) { 632 619 LOG_DBG("ERS", "No pages to render"); 633 620 renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_EMPTY_CHAPTER), true, EpdFontFamily::BOLD); 634 - renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); 621 + renderStatusBar(); 635 622 renderer.displayBuffer(); 636 623 return; 637 624 } ··· 639 626 if (section->currentPage < 0 || section->currentPage >= section->pageCount) { 640 627 LOG_DBG("ERS", "Page out of bounds: %d (max %d)", section->currentPage, section->pageCount); 641 628 renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_OUT_OF_BOUNDS), true, EpdFontFamily::BOLD); 642 - renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); 629 + renderStatusBar(); 643 630 renderer.displayBuffer(); 644 631 return; 645 632 } ··· 691 678 bool imagePageWithAA = page->hasImages() && SETTINGS.textAntiAliasing; 692 679 693 680 page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); 694 - renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); 681 + renderStatusBar(); 695 682 if (imagePageWithAA) { 696 683 // Double FAST_REFRESH with selective image blanking (pablohc's technique): 697 684 // HALF_REFRESH sets particles too firmly for the grayscale LUT to adjust. ··· 705 692 706 693 // Re-render page content to restore images into the blanked area 707 694 page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); 708 - renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); 695 + renderStatusBar(); 709 696 renderer.displayBuffer(HalDisplay::FAST_REFRESH); 710 697 } else { 711 698 renderer.displayBuffer(HalDisplay::HALF_REFRESH); ··· 745 732 renderer.restoreBwBuffer(); 746 733 } 747 734 748 - void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom, 749 - const int orientedMarginLeft) const { 750 - const auto& metrics = UITheme::getInstance().getMetrics(); 751 - 752 - // determine visible status bar elements 753 - const bool showProgressPercentage = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; 754 - const bool showBookProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 755 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_BOOK_PROGRESS_BAR; 756 - const bool showChapterProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 757 - const bool showProgressText = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || 758 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR; 759 - const bool showBookPercentage = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 760 - const bool showBattery = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || 761 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || 762 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 763 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 764 - const bool showChapterTitle = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || 765 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || 766 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 767 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 768 - const bool showBatteryPercentage = 769 - SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; 770 - 771 - // Position status bar near the bottom of the logical screen, regardless of orientation 772 - const auto screenHeight = renderer.getScreenHeight(); 773 - const auto textY = screenHeight - orientedMarginBottom - 4; 774 - int progressTextWidth = 0; 775 - 735 + void EpubReaderActivity::renderStatusBar() const { 776 736 // Calculate progress in book 777 - const float sectionChapterProg = static_cast<float>(section->currentPage) / section->pageCount; 737 + const int currentPage = section->currentPage + 1; 738 + const float pageCount = section->pageCount; 739 + const float sectionChapterProg = (pageCount > 0) ? (static_cast<float>(currentPage) / pageCount) : 0; 778 740 const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100; 779 741 780 - if (showProgressText || showProgressPercentage || showBookPercentage) { 781 - // Right aligned text for progress counter 782 - char progressStr[32]; 783 - 784 - // Hide percentage when progress bar is shown to reduce clutter 785 - if (showProgressPercentage) { 786 - snprintf(progressStr, sizeof(progressStr), "%d/%d %.0f%%", section->currentPage + 1, section->pageCount, 787 - bookProgress); 788 - } else if (showBookPercentage) { 789 - snprintf(progressStr, sizeof(progressStr), "%.0f%%", bookProgress); 790 - } else { 791 - snprintf(progressStr, sizeof(progressStr), "%d/%d", section->currentPage + 1, section->pageCount); 792 - } 793 - 794 - progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); 795 - renderer.drawText(SMALL_FONT_ID, renderer.getScreenWidth() - orientedMarginRight - progressTextWidth, textY, 796 - progressStr); 797 - } 798 - 799 - if (showBookProgressBar) { 800 - // Draw progress bar at the very bottom of the screen, from edge to edge of viewable area 801 - GUI.drawReadingProgressBar(renderer, static_cast<size_t>(bookProgress)); 802 - } 803 - 804 - if (showChapterProgressBar) { 805 - // Draw chapter progress bar at the very bottom of the screen, from edge to edge of viewable area 806 - const float chapterProgress = 807 - (section->pageCount > 0) ? (static_cast<float>(section->currentPage + 1) / section->pageCount) * 100 : 0; 808 - GUI.drawReadingProgressBar(renderer, static_cast<size_t>(chapterProgress)); 809 - } 810 - 811 - if (showBattery) { 812 - GUI.drawBatteryLeft(renderer, Rect{orientedMarginLeft + 1, textY, metrics.batteryWidth, metrics.batteryHeight}, 813 - showBatteryPercentage); 814 - } 815 - 816 - if (showChapterTitle) { 817 - // Centered chatper title text 818 - // Page width minus existing content with 30px padding on each side 819 - const int rendererableScreenWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; 820 - 821 - const int batterySize = showBattery ? (showBatteryPercentage ? 50 : 20) : 0; 822 - const int titleMarginLeft = batterySize + 30; 823 - const int titleMarginRight = progressTextWidth + 30; 742 + const int tocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex); 743 + std::string title; 824 744 825 - // Attempt to center title on the screen, but if title is too wide then later we will center it within the 826 - // available space. 827 - int titleMarginLeftAdjusted = std::max(titleMarginLeft, titleMarginRight); 828 - int availableTitleSpace = rendererableScreenWidth - 2 * titleMarginLeftAdjusted; 829 - const int tocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex); 830 - 831 - std::string title; 832 - int titleWidth; 745 + if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) { 833 746 if (tocIndex == -1) { 834 747 title = tr(STR_UNNAMED); 835 - titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 836 748 } else { 837 749 const auto tocItem = epub->getTocItem(tocIndex); 838 750 title = tocItem.title; 839 - titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 840 - if (titleWidth > availableTitleSpace) { 841 - // Not enough space to center on the screen, center it within the remaining space instead 842 - availableTitleSpace = rendererableScreenWidth - titleMarginLeft - titleMarginRight; 843 - titleMarginLeftAdjusted = titleMarginLeft; 844 - } 845 - if (titleWidth > availableTitleSpace) { 846 - title = renderer.truncatedText(SMALL_FONT_ID, title.c_str(), availableTitleSpace); 847 - titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 848 - } 849 751 } 850 - 851 - renderer.drawText(SMALL_FONT_ID, 852 - titleMarginLeftAdjusted + orientedMarginLeft + (availableTitleSpace - titleWidth) / 2, textY, 853 - title.c_str()); 752 + } else if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE) { 753 + title = epub->getTitle(); 754 + } else { 755 + title = ""; 854 756 } 757 + 758 + GUI.drawStatusBar(renderer, bookProgress, currentPage, pageCount, title); 855 759 }
+1 -1
src/activities/reader/EpubReaderActivity.h
··· 27 27 28 28 void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight, 29 29 int orientedMarginBottom, int orientedMarginLeft); 30 - void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; 30 + void renderStatusBar() const; 31 31 void saveProgress(int spineIndex, int currentPage, int pageCount); 32 32 // Jump to a percentage of the book (0-100), mapping it to spine and page. 33 33 void jumpToPercent(int percent);
+17 -106
src/activities/reader/TxtReaderActivity.cpp
··· 15 15 16 16 namespace { 17 17 constexpr unsigned long goHomeMs = 1000; 18 - constexpr int statusBarMargin = 25; 19 - constexpr int progressBarMarginTop = 1; 20 18 constexpr size_t CHUNK_SIZE = 8 * 1024; // 8KB chunk for reading 21 19 22 20 // Cache file magic and version ··· 131 129 cachedParagraphAlignment = SETTINGS.paragraphAlignment; 132 130 133 131 // Calculate viewport dimensions 134 - int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; 135 - renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, 136 - &orientedMarginLeft); 137 - orientedMarginTop += cachedScreenMargin; 138 - orientedMarginLeft += cachedScreenMargin; 139 - orientedMarginRight += cachedScreenMargin; 140 - orientedMarginBottom += cachedScreenMargin; 132 + renderer.getOrientedViewableTRBL(&cachedOrientedMarginTop, &cachedOrientedMarginRight, &cachedOrientedMarginBottom, 133 + &cachedOrientedMarginLeft); 134 + cachedOrientedMarginTop += cachedScreenMargin; 135 + cachedOrientedMarginLeft += cachedScreenMargin; 136 + cachedOrientedMarginRight += cachedScreenMargin; 137 + cachedOrientedMarginBottom += 138 + std::max(cachedScreenMargin, static_cast<uint8_t>(UITheme::getInstance().getStatusBarHeight())); 141 139 142 - const auto& metrics = UITheme::getInstance().getMetrics(); 143 - 144 - // Add status bar margin 145 - if (SETTINGS.statusBar != CrossPointSettings::STATUS_BAR_MODE::NONE) { 146 - // Add additional margin for status bar if progress bar is shown 147 - const bool showProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 148 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_BOOK_PROGRESS_BAR || 149 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 150 - orientedMarginBottom += statusBarMargin - cachedScreenMargin + 151 - (showProgressBar ? (metrics.bookProgressBarHeight + progressBarMarginTop) : 0); 152 - } 153 - 154 - viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; 155 - const int viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom; 140 + viewportWidth = renderer.getScreenWidth() - cachedOrientedMarginLeft - cachedOrientedMarginRight; 141 + const int viewportHeight = renderer.getScreenHeight() - cachedOrientedMarginTop - cachedOrientedMarginBottom; 156 142 const int lineHeight = renderer.getLineHeight(cachedFontId); 157 143 158 144 linesPerPage = viewportHeight / lineHeight; ··· 375 361 } 376 362 377 363 void TxtReaderActivity::renderPage() { 378 - int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; 379 - renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, 380 - &orientedMarginLeft); 381 - orientedMarginTop += cachedScreenMargin; 382 - orientedMarginLeft += cachedScreenMargin; 383 - orientedMarginRight += cachedScreenMargin; 384 - orientedMarginBottom += statusBarMargin; 385 - 386 364 const int lineHeight = renderer.getLineHeight(cachedFontId); 387 365 const int contentWidth = viewportWidth; 388 366 389 367 // Render text lines with alignment 390 368 auto renderLines = [&]() { 391 - int y = orientedMarginTop; 369 + int y = cachedOrientedMarginTop; 392 370 for (const auto& line : currentPageLines) { 393 371 if (!line.empty()) { 394 - int x = orientedMarginLeft; 372 + int x = cachedOrientedMarginLeft; 395 373 396 374 // Apply text alignment 397 375 switch (cachedParagraphAlignment) { ··· 401 379 break; 402 380 case CrossPointSettings::CENTER_ALIGN: { 403 381 int textWidth = renderer.getTextWidth(cachedFontId, line.c_str()); 404 - x = orientedMarginLeft + (contentWidth - textWidth) / 2; 382 + x = cachedOrientedMarginLeft + (contentWidth - textWidth) / 2; 405 383 break; 406 384 } 407 385 case CrossPointSettings::RIGHT_ALIGN: { 408 386 int textWidth = renderer.getTextWidth(cachedFontId, line.c_str()); 409 - x = orientedMarginLeft + contentWidth - textWidth; 387 + x = cachedOrientedMarginLeft + contentWidth - textWidth; 410 388 break; 411 389 } 412 390 case CrossPointSettings::JUSTIFIED: ··· 423 401 424 402 // First pass: BW rendering 425 403 renderLines(); 426 - renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); 404 + renderStatusBar(); 427 405 428 406 if (pagesUntilFullRefresh <= 1) { 429 407 renderer.displayBuffer(HalDisplay::HALF_REFRESH); ··· 456 434 } 457 435 } 458 436 459 - void TxtReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom, 460 - const int orientedMarginLeft) const { 461 - const bool showProgressPercentage = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL; 462 - const bool showProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 463 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::ONLY_BOOK_PROGRESS_BAR; 464 - const bool showChapterProgressBar = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 465 - const bool showProgressText = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || 466 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR; 467 - const bool showBookPercentage = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 468 - const bool showBattery = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || 469 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || 470 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 471 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 472 - const bool showTitle = SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::NO_PROGRESS || 473 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::FULL || 474 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::BOOK_PROGRESS_BAR || 475 - SETTINGS.statusBar == CrossPointSettings::STATUS_BAR_MODE::CHAPTER_PROGRESS_BAR; 476 - const bool showBatteryPercentage = 477 - SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; 478 - 479 - const auto& metrics = UITheme::getInstance().getMetrics(); 480 - const auto screenHeight = renderer.getScreenHeight(); 481 - // Adjust text position upward when progress bar is shown to avoid overlap 482 - const auto textY = screenHeight - orientedMarginBottom - 4; 483 - int progressTextWidth = 0; 484 - 437 + void TxtReaderActivity::renderStatusBar() const { 485 438 const float progress = totalPages > 0 ? (currentPage + 1) * 100.0f / totalPages : 0; 439 + std::string title = txt->getTitle(); 486 440 487 - if (showProgressText || showProgressPercentage || showBookPercentage) { 488 - char progressStr[32]; 489 - if (showProgressPercentage) { 490 - snprintf(progressStr, sizeof(progressStr), "%d/%d %.0f%%", currentPage + 1, totalPages, progress); 491 - } else if (showBookPercentage) { 492 - snprintf(progressStr, sizeof(progressStr), "%.0f%%", progress); 493 - } else { 494 - snprintf(progressStr, sizeof(progressStr), "%d/%d", currentPage + 1, totalPages); 495 - } 496 - 497 - progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); 498 - renderer.drawText(SMALL_FONT_ID, renderer.getScreenWidth() - orientedMarginRight - progressTextWidth, textY, 499 - progressStr); 500 - } 501 - 502 - if (showProgressBar) { 503 - // Draw progress bar at the very bottom of the screen, from edge to edge of viewable area 504 - GUI.drawReadingProgressBar(renderer, static_cast<size_t>(progress)); 505 - } 506 - 507 - if (showChapterProgressBar) { 508 - // For text mode, treat the entire book as one chapter, so chapter progress == book progress 509 - GUI.drawReadingProgressBar(renderer, static_cast<size_t>(progress)); 510 - } 511 - 512 - if (showBattery) { 513 - GUI.drawBatteryLeft(renderer, Rect{orientedMarginLeft, textY, metrics.batteryWidth, metrics.batteryHeight}, 514 - showBatteryPercentage); 515 - } 516 - 517 - if (showTitle) { 518 - const int titleMarginLeft = 50 + 30 + orientedMarginLeft; 519 - const int titleMarginRight = progressTextWidth + 30 + orientedMarginRight; 520 - const int availableTextWidth = renderer.getScreenWidth() - titleMarginLeft - titleMarginRight; 521 - 522 - std::string title = txt->getTitle(); 523 - int titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 524 - if (titleWidth > availableTextWidth) { 525 - title = renderer.truncatedText(SMALL_FONT_ID, title.c_str(), availableTextWidth); 526 - titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 527 - } 528 - 529 - renderer.drawText(SMALL_FONT_ID, titleMarginLeft + (availableTextWidth - titleWidth) / 2, textY, title.c_str()); 530 - } 441 + GUI.drawStatusBar(renderer, progress, currentPage + 1, totalPages, title); 531 442 } 532 443 533 444 void TxtReaderActivity::saveProgress() const {
+6 -2
src/activities/reader/TxtReaderActivity.h
··· 26 26 27 27 // Cached settings for cache validation (different fonts/margins require re-indexing) 28 28 int cachedFontId = 0; 29 - int cachedScreenMargin = 0; 29 + uint8_t cachedScreenMargin = 0; 30 30 uint8_t cachedParagraphAlignment = CrossPointSettings::LEFT_ALIGN; 31 + int cachedOrientedMarginTop = 0; 32 + int cachedOrientedMarginRight = 0; 33 + int cachedOrientedMarginBottom = 0; 34 + int cachedOrientedMarginLeft = 0; 31 35 32 36 void renderPage(); 33 - void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const; 37 + void renderStatusBar() const; 34 38 35 39 void initializeReader(); 36 40 bool loadPageAtOffset(size_t offset, std::vector<std::string>& outLines, size_t& nextOffset);
+6 -1
src/activities/settings/SettingsActivity.cpp
··· 12 12 #include "MappedInputManager.h" 13 13 #include "OtaUpdateActivity.h" 14 14 #include "SettingsList.h" 15 + #include "StatusBarSettingsActivity.h" 15 16 #include "activities/network/WifiSelectionActivity.h" 16 17 #include "components/UITheme.h" 17 18 #include "fontIds.h" ··· 51 52 systemSettings.push_back(SettingInfo::Action(StrId::STR_CLEAR_READING_CACHE, SettingAction::ClearCache)); 52 53 systemSettings.push_back(SettingInfo::Action(StrId::STR_CHECK_UPDATES, SettingAction::CheckForUpdates)); 53 54 systemSettings.push_back(SettingInfo::Action(StrId::STR_LANGUAGE, SettingAction::Language)); 55 + readerSettings.push_back(SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar)); 54 56 55 57 // Reset selection to first category 56 58 selectedCategoryIndex = 0; ··· 181 183 case SettingAction::RemapFrontButtons: 182 184 enterSubActivity(new ButtonRemapActivity(renderer, mappedInput, onComplete)); 183 185 break; 186 + case SettingAction::CustomiseStatusBar: 187 + enterSubActivity(new StatusBarSettingsActivity(renderer, mappedInput, onComplete)); 188 + break; 184 189 case SettingAction::KOReaderSync: 185 190 enterSubActivity(new KOReaderSettingsActivity(renderer, mappedInput, onComplete)); 186 191 break; ··· 259 264 260 265 // Always use standard refresh for settings screen 261 266 renderer.displayBuffer(); 262 - } 267 + }
+1
src/activities/settings/SettingsActivity.h
··· 15 15 enum class SettingAction { 16 16 None, 17 17 RemapFrontButtons, 18 + CustomiseStatusBar, 18 19 KOReaderSync, 19 20 OPDSBrowser, 20 21 Network,
+174
src/activities/settings/StatusBarSettingsActivity.cpp
··· 1 + #include "StatusBarSettingsActivity.h" 2 + 3 + #include <GfxRenderer.h> 4 + #include <I18n.h> 5 + 6 + #include <cstring> 7 + 8 + #include "CrossPointSettings.h" 9 + #include "MappedInputManager.h" 10 + #include "components/UITheme.h" 11 + #include "fontIds.h" 12 + 13 + namespace { 14 + constexpr int MENU_ITEMS = 6; 15 + const StrId menuNames[MENU_ITEMS] = {StrId::STR_CHAPTER_PAGE_COUNT, 16 + StrId::STR_BOOK_PROGRESS_PERCENTAGE, 17 + StrId::STR_PROGRESS_BAR, 18 + StrId::STR_PROGRESS_BAR_THICKNESS, 19 + StrId::STR_TITLE, 20 + StrId::STR_BATTERY}; 21 + constexpr int PROGRESS_BAR_ITEMS = 3; 22 + const StrId progressBarNames[PROGRESS_BAR_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}; 23 + 24 + constexpr int PROGRESS_BAR_THICKNESS_ITEMS = 3; 25 + const StrId progressBarThicknessNames[PROGRESS_BAR_THICKNESS_ITEMS] = { 26 + StrId::STR_PROGRESS_BAR_THIN, StrId::STR_PROGRESS_BAR_MEDIUM, StrId::STR_PROGRESS_BAR_THICK}; 27 + 28 + constexpr int TITLE_ITEMS = 3; 29 + const StrId titleNames[TITLE_ITEMS] = {StrId::STR_BOOK, StrId::STR_CHAPTER, StrId::STR_HIDE}; 30 + 31 + const char* translatedShow = tr(STR_SHOW); 32 + const char* translatedHide = tr(STR_HIDE); 33 + 34 + const int widthMargin = 10; 35 + const int verticalPreviewPadding = 50; 36 + const int verticalPreviewTextPadding = 40; 37 + } // namespace 38 + 39 + void StatusBarSettingsActivity::onEnter() { 40 + Activity::onEnter(); 41 + 42 + selectedIndex = 0; 43 + 44 + // Clamp statusBarProgressBar and statusBarTitle in case of corrupt/migrated data 45 + if (SETTINGS.statusBarProgressBar >= PROGRESS_BAR_ITEMS) { 46 + SETTINGS.statusBarProgressBar = CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; 47 + } 48 + 49 + if (SETTINGS.statusBarTitle >= PROGRESS_BAR_THICKNESS_ITEMS) { 50 + SETTINGS.statusBarTitle = CrossPointSettings::STATUS_BAR_PROGRESS_BAR_THICKNESS::PROGRESS_BAR_NORMAL; 51 + } 52 + 53 + if (SETTINGS.statusBarTitle >= TITLE_ITEMS) { 54 + SETTINGS.statusBarTitle = CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE; 55 + } 56 + 57 + requestUpdate(); 58 + } 59 + 60 + void StatusBarSettingsActivity::onExit() { Activity::onExit(); } 61 + 62 + void StatusBarSettingsActivity::loop() { 63 + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { 64 + onBack(); 65 + return; 66 + } 67 + 68 + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { 69 + handleSelection(); 70 + requestUpdate(); 71 + return; 72 + } 73 + 74 + // Handle navigation 75 + buttonNavigator.onNextRelease([this] { 76 + selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEMS); 77 + requestUpdate(); 78 + }); 79 + 80 + buttonNavigator.onPreviousRelease([this] { 81 + selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEMS); 82 + requestUpdate(); 83 + }); 84 + 85 + buttonNavigator.onNextContinuous([this] { 86 + selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEMS); 87 + requestUpdate(); 88 + }); 89 + 90 + buttonNavigator.onPreviousContinuous([this] { 91 + selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEMS); 92 + requestUpdate(); 93 + }); 94 + } 95 + 96 + void StatusBarSettingsActivity::handleSelection() { 97 + if (selectedIndex == 0) { 98 + // Chapter Page Count 99 + SETTINGS.statusBarChapterPageCount = (SETTINGS.statusBarChapterPageCount + 1) % 2; 100 + } else if (selectedIndex == 1) { 101 + // Book Progress % 102 + SETTINGS.statusBarBookProgressPercentage = (SETTINGS.statusBarBookProgressPercentage + 1) % 2; 103 + } else if (selectedIndex == 2) { 104 + // Progress Bar 105 + SETTINGS.statusBarProgressBar = (SETTINGS.statusBarProgressBar + 1) % PROGRESS_BAR_ITEMS; 106 + } else if (selectedIndex == 3) { 107 + // Progress Bar Thickness 108 + SETTINGS.statusBarProgressBarThickness = 109 + (SETTINGS.statusBarProgressBarThickness + 1) % PROGRESS_BAR_THICKNESS_ITEMS; 110 + } else if (selectedIndex == 4) { 111 + // Chapter Title 112 + SETTINGS.statusBarTitle = (SETTINGS.statusBarTitle + 1) % TITLE_ITEMS; 113 + } else if (selectedIndex == 5) { 114 + // Show Battery 115 + SETTINGS.statusBarBattery = (SETTINGS.statusBarBattery + 1) % 2; 116 + } 117 + SETTINGS.saveToFile(); 118 + } 119 + 120 + void StatusBarSettingsActivity::render(Activity::RenderLock&&) { 121 + renderer.clearScreen(); 122 + 123 + auto metrics = UITheme::getInstance().getMetrics(); 124 + const auto pageWidth = renderer.getScreenWidth(); 125 + const auto pageHeight = renderer.getScreenHeight(); 126 + 127 + GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_CUSTOMISE_STATUS_BAR)); 128 + 129 + const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; 130 + const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2; 131 + GUI.drawList( 132 + renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast<int>(MENU_ITEMS), 133 + static_cast<int>(selectedIndex), [](int index) { return std::string(I18N.get(menuNames[index])); }, nullptr, 134 + nullptr, 135 + [this](int index) { 136 + // Draw status for each setting 137 + if (index == 0) { 138 + return SETTINGS.statusBarChapterPageCount ? translatedShow : translatedHide; 139 + } else if (index == 1) { 140 + return SETTINGS.statusBarBookProgressPercentage ? translatedShow : translatedHide; 141 + } else if (index == 2) { 142 + return I18N.get(progressBarNames[SETTINGS.statusBarProgressBar]); 143 + } else if (index == 3) { 144 + return I18N.get(progressBarThicknessNames[SETTINGS.statusBarProgressBarThickness]); 145 + } else if (index == 4) { 146 + return I18N.get(titleNames[SETTINGS.statusBarTitle]); 147 + } else if (index == 5) { 148 + return SETTINGS.statusBarBattery ? translatedShow : translatedHide; 149 + } else { 150 + return translatedHide; 151 + } 152 + }, 153 + true); 154 + 155 + // Draw button hints 156 + const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_TOGGLE), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); 157 + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); 158 + 159 + std::string title; 160 + if (SETTINGS.statusBarTitle == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE) { 161 + title = tr(STR_EXAMPLE_BOOK); 162 + } else { 163 + title = tr(STR_EXAMPLE_CHAPTER); 164 + } 165 + 166 + GUI.drawStatusBar(renderer, 75, 8, 32, title, verticalPreviewPadding); 167 + 168 + renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, 169 + renderer.getScreenHeight() - UITheme::getInstance().getStatusBarHeight() - verticalPreviewPadding - 170 + verticalPreviewTextPadding, 171 + tr(STR_PREVIEW)); 172 + 173 + renderer.displayBuffer(); 174 + }
+30
src/activities/settings/StatusBarSettingsActivity.h
··· 1 + #pragma once 2 + #include <freertos/FreeRTOS.h> 3 + #include <freertos/semphr.h> 4 + #include <freertos/task.h> 5 + 6 + #include "activities/Activity.h" 7 + #include "util/ButtonNavigator.h" 8 + 9 + // Reader status bar configuration activity 10 + class StatusBarSettingsActivity final : public Activity { 11 + public: 12 + explicit StatusBarSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, 13 + const std::function<void()>& onBack) 14 + : Activity("StatusBarSettings", renderer, mappedInput), onBack(onBack) {} 15 + 16 + void onEnter() override; 17 + void onExit() override; 18 + void loop() override; 19 + void render(Activity::RenderLock&&) override; 20 + 21 + private: 22 + ButtonNavigator buttonNavigator; 23 + 24 + int selectedIndex = 0; 25 + 26 + const std::function<void()> onBack; 27 + 28 + static void taskTrampoline(void* param); 29 + void handleSelection(); 30 + };
+13
src/components/UITheme.cpp
··· 90 90 } 91 91 return File; 92 92 } 93 + 94 + int UITheme::getStatusBarHeight() { 95 + const ThemeMetrics& metrics = UITheme::getInstance().getMetrics(); 96 + 97 + // Add status bar margin 98 + const bool showStatusBar = SETTINGS.statusBarChapterPageCount || SETTINGS.statusBarBookProgressPercentage || 99 + SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE || 100 + SETTINGS.statusBarBattery; 101 + const bool showProgressBar = 102 + SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS; 103 + return (showStatusBar ? (metrics.statusBarVerticalMargin) : 0) + 104 + (showProgressBar ? (((SETTINGS.statusBarProgressBarThickness + 1) * 2) + metrics.progressBarMarginTop) : 0); 105 + }
+1
src/components/UITheme.h
··· 22 22 bool hasSubtitle); 23 23 static std::string getCoverThumbPath(std::string coverBmpPath, int coverHeight); 24 24 static UIIcon getFileIcon(std::string filename); 25 + static int getStatusBarHeight(); 25 26 26 27 private: 27 28 const ThemeMetrics* currentMetrics;
+91 -9
src/components/themes/BaseTheme.cpp
··· 628 628 renderer.displayBuffer(HalDisplay::FAST_REFRESH); 629 629 } 630 630 631 - void BaseTheme::drawReadingProgressBar(const GfxRenderer& renderer, const size_t bookProgress) const { 632 - int vieweableMarginTop, vieweableMarginRight, vieweableMarginBottom, vieweableMarginLeft; 633 - renderer.getOrientedViewableTRBL(&vieweableMarginTop, &vieweableMarginRight, &vieweableMarginBottom, 634 - &vieweableMarginLeft); 631 + void BaseTheme::drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, 632 + const int pageCount, std::string title, const int paddingBottom) const { 633 + auto metrics = UITheme::getInstance().getMetrics(); 634 + int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; 635 + renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, 636 + &orientedMarginLeft); 637 + 638 + // Draw Progress Text 639 + const auto screenHeight = renderer.getScreenHeight(); 640 + const auto textY = 641 + screenHeight - UITheme::getInstance().getStatusBarHeight() - orientedMarginBottom - paddingBottom - 4; 642 + int progressTextWidth = 0; 643 + 644 + if (SETTINGS.statusBarBookProgressPercentage || SETTINGS.statusBarChapterPageCount) { 645 + // Right aligned text for progress counter 646 + char progressStr[32]; 635 647 636 - const int progressBarMaxWidth = renderer.getScreenWidth() - vieweableMarginLeft - vieweableMarginRight; 637 - const int progressBarY = 638 - renderer.getScreenHeight() - vieweableMarginBottom - BaseMetrics::values.bookProgressBarHeight; 639 - const int barWidth = progressBarMaxWidth * bookProgress / 100; 640 - renderer.fillRect(vieweableMarginLeft, progressBarY, barWidth, BaseMetrics::values.bookProgressBarHeight, true); 648 + if (SETTINGS.statusBarBookProgressPercentage && SETTINGS.statusBarChapterPageCount) { 649 + snprintf(progressStr, sizeof(progressStr), "%d/%d %.0f%%", currentPage, pageCount, bookProgress); 650 + } else if (SETTINGS.statusBarBookProgressPercentage) { 651 + snprintf(progressStr, sizeof(progressStr), "%.0f%%", bookProgress); 652 + } else { 653 + snprintf(progressStr, sizeof(progressStr), "%d/%d", currentPage, pageCount); 654 + } 655 + 656 + progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progressStr); 657 + renderer.drawText( 658 + SMALL_FONT_ID, 659 + renderer.getScreenWidth() - metrics.statusBarHorizontalMargin - orientedMarginRight - progressTextWidth, textY, 660 + progressStr); 661 + } 662 + 663 + // Draw Progress Bar 664 + if (SETTINGS.statusBarProgressBar != CrossPointSettings::STATUS_BAR_PROGRESS_BAR::HIDE_PROGRESS) { 665 + const int progressBarMaxWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; 666 + const int progressBarY = renderer.getScreenHeight() - orientedMarginBottom - 667 + ((SETTINGS.statusBarProgressBarThickness + 1) * 2) - paddingBottom; 668 + size_t progress; 669 + if (SETTINGS.statusBarProgressBar == CrossPointSettings::STATUS_BAR_PROGRESS_BAR::BOOK_PROGRESS) { 670 + progress = static_cast<size_t>(bookProgress); 671 + } else { 672 + // Chapter progress 673 + progress = (pageCount > 0) ? (static_cast<float>(currentPage) / pageCount) * 100 : 0; 674 + } 675 + const int barWidth = progressBarMaxWidth * progress / 100; 676 + renderer.fillRect(orientedMarginLeft, progressBarY, barWidth, ((SETTINGS.statusBarProgressBarThickness + 1) * 2), 677 + true); 678 + } 679 + 680 + // Draw Battery 681 + const bool showBatteryPercentage = 682 + SETTINGS.hideBatteryPercentage == CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_NEVER; 683 + if (SETTINGS.statusBarBattery) { 684 + GUI.drawBatteryLeft(renderer, 685 + Rect{metrics.statusBarHorizontalMargin + orientedMarginLeft + 1, textY, metrics.batteryWidth, 686 + metrics.batteryHeight}, 687 + showBatteryPercentage); 688 + } 689 + 690 + // Draw Title 691 + if (SETTINGS.statusBarTitle != CrossPointSettings::STATUS_BAR_TITLE::HIDE_TITLE) { 692 + // Centered chapter title text 693 + // Page width minus existing content with 30px padding on each side 694 + const int rendererableScreenWidth = 695 + renderer.getScreenWidth() - (metrics.statusBarHorizontalMargin * 2) - orientedMarginLeft - orientedMarginRight; 696 + 697 + const int batterySize = SETTINGS.statusBarBattery ? (showBatteryPercentage ? 50 : 20) : 0; 698 + const int titleMarginLeft = batterySize + 30; 699 + const int titleMarginRight = progressTextWidth + 30; 700 + 701 + // Attempt to center title on the screen, but if title is too wide then later we will center it within the 702 + // available space. 703 + int titleMarginLeftAdjusted = std::max(titleMarginLeft, titleMarginRight); 704 + int availableTitleSpace = rendererableScreenWidth - 2 * titleMarginLeftAdjusted; 705 + 706 + int titleWidth; 707 + titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 708 + if (titleWidth > availableTitleSpace) { 709 + // Not enough space to center on the screen, center it within the remaining space instead 710 + availableTitleSpace = rendererableScreenWidth - titleMarginLeft - titleMarginRight; 711 + titleMarginLeftAdjusted = titleMarginLeft; 712 + } 713 + if (titleWidth > availableTitleSpace) { 714 + title = renderer.truncatedText(SMALL_FONT_ID, title.c_str(), availableTitleSpace); 715 + titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 716 + } 717 + 718 + renderer.drawText(SMALL_FONT_ID, 719 + titleMarginLeftAdjusted + metrics.statusBarHorizontalMargin + orientedMarginLeft + 720 + (availableTitleSpace - titleWidth) / 2, 721 + textY, title.c_str()); 722 + } 641 723 } 642 724 643 725 void BaseTheme::drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const {
+8 -3
src/components/themes/BaseTheme.h
··· 53 53 int sideButtonHintsWidth; 54 54 55 55 int progressBarHeight; 56 - int bookProgressBarHeight; 56 + int progressBarMarginTop; 57 + int statusBarHorizontalMargin; 58 + int statusBarVerticalMargin; 57 59 58 60 int keyboardKeyWidth; 59 61 int keyboardKeyHeight; ··· 90 92 .buttonHintsHeight = 40, 91 93 .sideButtonHintsWidth = 30, 92 94 .progressBarHeight = 16, 93 - .bookProgressBarHeight = 4, 95 + .progressBarMarginTop = 1, 96 + .statusBarHorizontalMargin = 5, 97 + .statusBarVerticalMargin = 19, 94 98 .keyboardKeyWidth = 22, 95 99 .keyboardKeyHeight = 30, 96 100 .keyboardKeySpacing = 10, ··· 131 135 const std::function<UIIcon(int index)>& rowIcon) const; 132 136 virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const; 133 137 virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; 134 - virtual void drawReadingProgressBar(const GfxRenderer& renderer, const size_t bookProgress) const; 138 + virtual void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, 139 + const int pageCount, std::string title, const int paddingBottom = 0) const; 135 140 virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const; 136 141 virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const; 137 142 virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected) const;
+3 -1
src/components/themes/lyra/Lyra3CoversTheme.h
··· 30 30 .buttonHintsHeight = 40, 31 31 .sideButtonHintsWidth = 30, 32 32 .progressBarHeight = 16, 33 - .bookProgressBarHeight = 4, 33 + .progressBarMarginTop = 1, 34 + .statusBarHorizontalMargin = 5, 35 + .statusBarVerticalMargin = 19, 34 36 .keyboardKeyWidth = 31, 35 37 .keyboardKeyHeight = 50, 36 38 .keyboardKeySpacing = 0,
+3 -1
src/components/themes/lyra/LyraTheme.h
··· 28 28 .buttonHintsHeight = 40, 29 29 .sideButtonHintsWidth = 30, 30 30 .progressBarHeight = 16, 31 - .bookProgressBarHeight = 4, 31 + .progressBarMarginTop = 1, 32 + .statusBarHorizontalMargin = 5, 33 + .statusBarVerticalMargin = 19, 32 34 .keyboardKeyWidth = 31, 33 35 .keyboardKeyHeight = 50, 34 36 .keyboardKeySpacing = 0,