From 655c18d89ccefdb4fa9e8835673d0663de539de4 Mon Sep 17 00:00:00 2001 From: Matthijs Mars Date: Fri, 9 Jan 2026 14:01:09 +0100 Subject: [PATCH 1/7] add time left in chapter feature --- lib/Epub/Epub/Page.cpp | 11 ++++ lib/Epub/Epub/Page.h | 2 + lib/Epub/Epub/Section.cpp | 61 ++++++++++++++++++++ lib/Epub/Epub/Section.h | 6 ++ lib/Epub/Epub/blocks/TextBlock.h | 1 + src/CrossPointSettings.cpp | 7 ++- src/CrossPointSettings.h | 4 ++ src/activities/reader/EpubReaderActivity.cpp | 39 ++++++++++++- src/activities/settings/SettingsActivity.cpp | 4 +- 9 files changed, 132 insertions(+), 3 deletions(-) diff --git a/lib/Epub/Epub/Page.cpp b/lib/Epub/Epub/Page.cpp index 92839eb7..a2d44ffa 100644 --- a/lib/Epub/Epub/Page.cpp +++ b/lib/Epub/Epub/Page.cpp @@ -31,6 +31,17 @@ void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, co } } +size_t Page::wordCount() const { + size_t count = 0; + for (const auto& element : elements) { + const auto* line = dynamic_cast(element.get()); + if (line) { + count += line->wordCount(); + } + } + return count; +} + bool Page::serialize(FsFile& file) const { const uint16_t count = elements.size(); serialization::writePod(file, count); diff --git a/lib/Epub/Epub/Page.h b/lib/Epub/Epub/Page.h index 20061941..1e65796b 100644 --- a/lib/Epub/Epub/Page.h +++ b/lib/Epub/Epub/Page.h @@ -29,6 +29,7 @@ class PageLine final : public PageElement { PageLine(std::shared_ptr block, const int16_t xPos, const int16_t yPos) : PageElement(xPos, yPos), block(std::move(block)) {} void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override; + size_t wordCount() const { return block ? block->wordCount() : 0; } bool serialize(FsFile& file) override; static std::unique_ptr deserialize(FsFile& file); }; @@ -38,6 +39,7 @@ class Page { // the list of block index and line numbers on this page std::vector> elements; void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const; + size_t wordCount() const; bool serialize(FsFile& file) const; static std::unique_ptr deserialize(FsFile& file); }; diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 18b81aae..059d095a 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -233,3 +233,64 @@ std::unique_ptr Section::loadPageFromSectionFile() { file.close(); return page; } + +std::unique_ptr Section::loadPageAt(const uint16_t pageIndex) const { + FsFile localFile; + if (!SdMan.openFileForRead("SCT", filePath, localFile)) { + return nullptr; + } + + localFile.seek(HEADER_SIZE - sizeof(uint32_t)); + uint32_t lutOffset; + serialization::readPod(localFile, lutOffset); + if (pageIndex >= pageCount) { + localFile.close(); + return nullptr; + } + + localFile.seek(lutOffset + sizeof(uint32_t) * pageIndex); + uint32_t pagePos; + serialization::readPod(localFile, pagePos); + localFile.seek(pagePos); + + auto page = Page::deserialize(localFile); + localFile.close(); + return page; +} + +bool Section::ensureWordCountsLoaded() const { + if (wordCountsLoaded) { + return true; + } + + pageWordCounts.clear(); + pageWordCounts.reserve(pageCount); + + for (uint16_t i = 0; i < pageCount; i++) { + auto page = loadPageAt(i); + if (!page) { + pageWordCounts.clear(); + return false; + } + pageWordCounts.push_back(static_cast(page->wordCount())); + } + + wordCountsLoaded = true; + return true; +} + +uint32_t Section::getWordsLeftFrom(const uint16_t pageIndex) const { + if (pageIndex >= pageCount) { + return 0; + } + + if (!ensureWordCountsLoaded()) { + return 0; + } + + uint32_t total = 0; + for (size_t i = pageIndex; i < pageWordCounts.size(); i++) { + total += pageWordCounts[i]; + } + return total; +} diff --git a/lib/Epub/Epub/Section.h b/lib/Epub/Epub/Section.h index bac95efd..6deaddc0 100644 --- a/lib/Epub/Epub/Section.h +++ b/lib/Epub/Epub/Section.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "Epub.h" @@ -13,6 +14,8 @@ class Section { GfxRenderer& renderer; std::string filePath; FsFile file; + mutable std::vector pageWordCounts; + mutable bool wordCountsLoaded = false; void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, uint16_t viewportWidth, uint16_t viewportHeight); @@ -36,4 +39,7 @@ class Section { const std::function& progressSetupFn = nullptr, const std::function& progressFn = nullptr); std::unique_ptr loadPageFromSectionFile(); + std::unique_ptr loadPageAt(uint16_t pageIndex) const; + bool ensureWordCountsLoaded() const; + uint32_t getWordsLeftFrom(uint16_t pageIndex) const; }; diff --git a/lib/Epub/Epub/blocks/TextBlock.h b/lib/Epub/Epub/blocks/TextBlock.h index 415a18f3..7535c888 100644 --- a/lib/Epub/Epub/blocks/TextBlock.h +++ b/lib/Epub/Epub/blocks/TextBlock.h @@ -36,6 +36,7 @@ class TextBlock final : public Block { // given a renderer works out where to break the words into lines void render(const GfxRenderer& renderer, int fontId, int x, int y) const; BlockType getType() override { return TEXT_BLOCK; } + size_t wordCount() const { return words.size(); } bool serialize(FsFile& file) const; static std::unique_ptr deserialize(FsFile& file); }; diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index cd8b56f7..53958236 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -14,7 +14,7 @@ CrossPointSettings CrossPointSettings::instance; namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 17; +constexpr uint8_t SETTINGS_COUNT = 19; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -46,6 +46,8 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, sleepScreenCoverMode); serialization::writeString(outputFile, std::string(opdsServerUrl)); serialization::writePod(outputFile, textAntiAliasing); + serialization::writePod(outputFile, readingSpeedWpm); + serialization::writePod(outputFile, showTimeLeftInChapter); outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -110,6 +112,9 @@ bool CrossPointSettings::loadFromFile() { } serialization::readPod(inputFile, textAntiAliasing); if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, readingSpeedWpm); + if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, showTimeLeftInChapter); } while (false); inputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 3a2a3503..a71499db 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -61,6 +61,10 @@ class CrossPointSettings { // Text rendering settings uint8_t extraParagraphSpacing = 1; uint8_t textAntiAliasing = 1; + // Reading speed (words per minute) for time-left estimate + uint8_t readingSpeedWpm = 200; + // Toggle to show time remaining in the current chapter + uint8_t showTimeLeftInChapter = 0; // Duration of the power button press uint8_t shortPwrBtn = 0; // EPUB reading orientation settings diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index ac0ffd51..60d32ecc 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -5,6 +5,10 @@ #include #include +#include +#include +#include + #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" @@ -17,6 +21,28 @@ namespace { constexpr unsigned long skipChapterMs = 700; constexpr unsigned long goHomeMs = 1000; constexpr int statusBarMargin = 19; + +std::string formatMinutes(const float minutes) { + if (minutes <= 0.0f) { + return ""; + } + + const int totalMinutes = static_cast(std::ceil(minutes)); + if (totalMinutes < 60) { + return std::to_string(totalMinutes) + "m"; + } + + const int hours = totalMinutes / 60; + const int mins = totalMinutes % 60; + + char buffer[12]; + if (mins == 0) { + std::snprintf(buffer, sizeof(buffer), "%dh", hours); + } else { + std::snprintf(buffer, sizeof(buffer), "%dh %02dm", hours, mins); + } + return std::string(buffer); +} } // namespace void EpubReaderActivity::taskTrampoline(void* param) { @@ -428,9 +454,20 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in const float sectionChapterProg = static_cast(section->currentPage) / section->pageCount; const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg); + std::string timeLeftText; + if (SETTINGS.showTimeLeftInChapter && SETTINGS.readingSpeedWpm > 0) { + const uint32_t wordsLeft = section->getWordsLeftFrom(section->currentPage); + if (wordsLeft > 0) { + const float minutesLeft = static_cast(wordsLeft) / + static_cast(std::max(1, SETTINGS.readingSpeedWpm)); + timeLeftText = formatMinutes(minutesLeft); + } + } + // Right aligned text for progress counter const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) + - " " + std::to_string(bookProgress) + "%"; + " " + std::to_string(bookProgress) + "%" + + (timeLeftText.empty() ? std::string() : " " + timeLeftText + " left"); progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str()); renderer.drawText(SMALL_FONT_ID, renderer.getScreenWidth() - orientedMarginRight - progressTextWidth, textY, progress.c_str()); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 702db172..cda78eb2 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -13,7 +13,7 @@ // Define the static settings list namespace { -constexpr int settingsCount = 18; +constexpr int settingsCount = 20; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), @@ -35,6 +35,8 @@ const SettingInfo settingsList[settingsCount] = { SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment, {"Justify", "Left", "Center", "Right"}), + SettingInfo::Value("Reading Speed (WPM)", &CrossPointSettings::readingSpeedWpm, {80, 240, 10}), + SettingInfo::Toggle("Show Time Left", &CrossPointSettings::showTimeLeftInChapter), SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, From 07d6ff18db1f2aa523e642075a6c963f7e010839 Mon Sep 17 00:00:00 2001 From: Matthijs Mars Date: Fri, 9 Jan 2026 14:07:52 +0100 Subject: [PATCH 2/7] move time left to after pages left in chapter --- src/activities/reader/EpubReaderActivity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 60d32ecc..8d9f0f50 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -452,8 +452,6 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in if (showProgress) { // Calculate progress in book const float sectionChapterProg = static_cast(section->currentPage) / section->pageCount; - const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg); - std::string timeLeftText; if (SETTINGS.showTimeLeftInChapter && SETTINGS.readingSpeedWpm > 0) { const uint32_t wordsLeft = section->getWordsLeftFrom(section->currentPage); @@ -463,6 +461,8 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in timeLeftText = formatMinutes(minutesLeft); } } + const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg); + // Right aligned text for progress counter const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) + From 81dd948a3923bd5c705553e9380168a19f493851 Mon Sep 17 00:00:00 2001 From: Matthijs Mars Date: Fri, 9 Jan 2026 14:15:18 +0100 Subject: [PATCH 3/7] fix clang formatting --- src/activities/reader/EpubReaderActivity.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 8d9f0f50..db898b47 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -456,14 +456,13 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in if (SETTINGS.showTimeLeftInChapter && SETTINGS.readingSpeedWpm > 0) { const uint32_t wordsLeft = section->getWordsLeftFrom(section->currentPage); if (wordsLeft > 0) { - const float minutesLeft = static_cast(wordsLeft) / - static_cast(std::max(1, SETTINGS.readingSpeedWpm)); + const float minutesLeft = + static_cast(wordsLeft) / static_cast(std::max(1, SETTINGS.readingSpeedWpm)); timeLeftText = formatMinutes(minutesLeft); } } const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg); - // Right aligned text for progress counter const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) + " " + std::to_string(bookProgress) + "%" + From 38f0b9678de8e8fe4f9079555e74cd7f42a37bac Mon Sep 17 00:00:00 2001 From: Matthijs Mars Date: Fri, 9 Jan 2026 14:22:26 +0100 Subject: [PATCH 4/7] move to static cast and change range for wpm settings --- lib/Epub/Epub/Page.cpp | 7 +++---- src/activities/settings/SettingsActivity.cpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/Epub/Epub/Page.cpp b/lib/Epub/Epub/Page.cpp index a2d44ffa..b8acd701 100644 --- a/lib/Epub/Epub/Page.cpp +++ b/lib/Epub/Epub/Page.cpp @@ -34,10 +34,9 @@ void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, co size_t Page::wordCount() const { size_t count = 0; for (const auto& element : elements) { - const auto* line = dynamic_cast(element.get()); - if (line) { - count += line->wordCount(); - } + // Only PageLine is stored in elements; avoid RTTI to stay compatible with -fno-rtti + const auto* line = static_cast(element.get()); + count += line->wordCount(); } return count; } diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index cda78eb2..bd306bff 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -35,7 +35,7 @@ const SettingInfo settingsList[settingsCount] = { SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment, {"Justify", "Left", "Center", "Right"}), - SettingInfo::Value("Reading Speed (WPM)", &CrossPointSettings::readingSpeedWpm, {80, 240, 10}), + SettingInfo::Value("Reading Speed (WPM)", &CrossPointSettings::readingSpeedWpm, {150, 300, 10}), SettingInfo::Toggle("Show Time Left", &CrossPointSettings::showTimeLeftInChapter), SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), From 3f9142fba7b772f28138afa2da3ec884090a260a Mon Sep 17 00:00:00 2001 From: Matthijs Mars Date: Fri, 9 Jan 2026 14:29:17 +0100 Subject: [PATCH 5/7] add 16 bit integer ranges to allow for larger ints in wpm --- src/CrossPointSettings.cpp | 12 ++++++++--- src/CrossPointSettings.h | 2 +- src/activities/settings/SettingsActivity.cpp | 19 +++++++++++++----- src/activities/settings/SettingsActivity.h | 21 ++++++++++++-------- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 53958236..ee90dbbf 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -12,7 +12,7 @@ CrossPointSettings CrossPointSettings::instance; namespace { -constexpr uint8_t SETTINGS_FILE_VERSION = 1; +constexpr uint8_t SETTINGS_FILE_VERSION = 2; // Increment this when adding new persisted settings fields constexpr uint8_t SETTINGS_COUNT = 19; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; @@ -62,7 +62,7 @@ bool CrossPointSettings::loadFromFile() { uint8_t version; serialization::readPod(inputFile, version); - if (version != SETTINGS_FILE_VERSION) { + if (version != SETTINGS_FILE_VERSION && version != 1) { Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version); inputFile.close(); return false; @@ -112,7 +112,13 @@ bool CrossPointSettings::loadFromFile() { } serialization::readPod(inputFile, textAntiAliasing); if (++settingsRead >= fileSettingsCount) break; - serialization::readPod(inputFile, readingSpeedWpm); + if (version == 1) { + uint8_t wpmV1; + serialization::readPod(inputFile, wpmV1); + readingSpeedWpm = wpmV1; + } else { + serialization::readPod(inputFile, readingSpeedWpm); + } if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, showTimeLeftInChapter); } while (false); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index a71499db..8d66a3e0 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -62,7 +62,7 @@ class CrossPointSettings { uint8_t extraParagraphSpacing = 1; uint8_t textAntiAliasing = 1; // Reading speed (words per minute) for time-left estimate - uint8_t readingSpeedWpm = 200; + uint16_t readingSpeedWpm = 200; // Toggle to show time remaining in the current chapter uint8_t showTimeLeftInChapter = 0; // Duration of the power button press diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index bd306bff..d57107a5 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -35,7 +35,7 @@ const SettingInfo settingsList[settingsCount] = { SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment, {"Justify", "Left", "Center", "Right"}), - SettingInfo::Value("Reading Speed (WPM)", &CrossPointSettings::readingSpeedWpm, {150, 300, 10}), + SettingInfo::Value("Reading Speed (WPM)", &CrossPointSettings::readingSpeedWpm, {80, 300, 10}), SettingInfo::Toggle("Show Time Left", &CrossPointSettings::showTimeLeftInChapter), SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), @@ -129,14 +129,21 @@ void SettingsActivity::toggleCurrentSetting() { } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { const uint8_t currentValue = SETTINGS.*(setting.valuePtr); SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast(setting.enumValues.size()); - } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { + } else if (setting.type == SettingType::VALUE && setting.valuePtr16 != nullptr) { // Decreasing would also be nice for large ranges I think but oh well can't have everything - const int8_t currentValue = SETTINGS.*(setting.valuePtr); + const uint16_t currentValue = SETTINGS.*(setting.valuePtr16); // Wrap to minValue if exceeding setting value boundary if (currentValue + setting.valueRange.step > setting.valueRange.max) { - SETTINGS.*(setting.valuePtr) = setting.valueRange.min; + SETTINGS.*(setting.valuePtr16) = setting.valueRange.min; } else { - SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; + SETTINGS.*(setting.valuePtr16) = currentValue + setting.valueRange.step; + } + } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { + const uint16_t currentValue = SETTINGS.*(setting.valuePtr); + if (currentValue + setting.valueRange.step > setting.valueRange.max) { + SETTINGS.*(setting.valuePtr) = static_cast(setting.valueRange.min); + } else { + SETTINGS.*(setting.valuePtr) = static_cast(currentValue + setting.valueRange.step); } } else if (setting.type == SettingType::ACTION) { if (strcmp(setting.name, "Calibre Settings") == 0) { @@ -204,6 +211,8 @@ void SettingsActivity::render() const { } else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) { const uint8_t value = SETTINGS.*(settingsList[i].valuePtr); valueText = settingsList[i].enumValues[value]; + } else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr16 != nullptr) { + valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr16)); } else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) { valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr)); } diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index 157689e3..e7313f64 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -17,30 +17,35 @@ enum class SettingType { TOGGLE, ENUM, ACTION, VALUE }; struct SettingInfo { const char* name; // Display name of the setting SettingType type; // Type of setting - uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM/VALUE) + uint8_t CrossPointSettings::* valuePtr; // Pointer for 8-bit settings (TOGGLE/ENUM) + uint16_t CrossPointSettings::* valuePtr16; // Pointer for 16-bit VALUE settings std::vector enumValues; struct ValueRange { - uint8_t min; - uint8_t max; - uint8_t step; + uint16_t min; + uint16_t max; + uint16_t step; }; // Bounds/step for VALUE type settings ValueRange valueRange; // Static constructors static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr) { - return {name, SettingType::TOGGLE, ptr}; + return {name, SettingType::TOGGLE, ptr, nullptr}; } static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector values) { - return {name, SettingType::ENUM, ptr, std::move(values)}; + return {name, SettingType::ENUM, ptr, nullptr, std::move(values)}; } - static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr}; } + static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr, nullptr}; } + + static SettingInfo Value(const char* name, uint16_t CrossPointSettings::* ptr, const ValueRange valueRange) { + return {name, SettingType::VALUE, nullptr, ptr, {}, valueRange}; + } static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) { - return {name, SettingType::VALUE, ptr, {}, valueRange}; + return {name, SettingType::VALUE, ptr, nullptr, {}, valueRange}; } }; From 993a52a63910343e104c3c1ce32aa458f90789f0 Mon Sep 17 00:00:00 2001 From: Matthijs Mars Date: Fri, 9 Jan 2026 14:46:01 +0100 Subject: [PATCH 6/7] fix clang formatting --- src/activities/settings/SettingsActivity.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index e7313f64..c4fb3da3 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -15,9 +15,9 @@ enum class SettingType { TOGGLE, ENUM, ACTION, VALUE }; // Structure to hold setting information struct SettingInfo { - const char* name; // Display name of the setting - SettingType type; // Type of setting - uint8_t CrossPointSettings::* valuePtr; // Pointer for 8-bit settings (TOGGLE/ENUM) + const char* name; // Display name of the setting + SettingType type; // Type of setting + uint8_t CrossPointSettings::* valuePtr; // Pointer for 8-bit settings (TOGGLE/ENUM) uint16_t CrossPointSettings::* valuePtr16; // Pointer for 16-bit VALUE settings std::vector enumValues; From 58985119f145e619b0cad5a9885b29580ecc61c4 Mon Sep 17 00:00:00 2001 From: Matthijs Mars Date: Fri, 9 Jan 2026 18:04:24 +0100 Subject: [PATCH 7/7] changed range of wpms, moved time left before pages in chapter, always shows minutes now (not hours) --- src/activities/reader/EpubReaderActivity.cpp | 27 +++++--------------- src/activities/settings/SettingsActivity.cpp | 4 +-- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index db898b47..203e802a 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -27,21 +27,8 @@ std::string formatMinutes(const float minutes) { return ""; } - const int totalMinutes = static_cast(std::ceil(minutes)); - if (totalMinutes < 60) { - return std::to_string(totalMinutes) + "m"; - } - - const int hours = totalMinutes / 60; - const int mins = totalMinutes % 60; - - char buffer[12]; - if (mins == 0) { - std::snprintf(buffer, sizeof(buffer), "%dh", hours); - } else { - std::snprintf(buffer, sizeof(buffer), "%dh %02dm", hours, mins); - } - return std::string(buffer); + const int totalMinutes = static_cast(std::floor(minutes)); + return std::to_string(totalMinutes) + "m"; } } // namespace @@ -453,6 +440,8 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in // Calculate progress in book const float sectionChapterProg = static_cast(section->currentPage) / section->pageCount; std::string timeLeftText; + const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg); + if (SETTINGS.showTimeLeftInChapter && SETTINGS.readingSpeedWpm > 0) { const uint32_t wordsLeft = section->getWordsLeftFrom(section->currentPage); if (wordsLeft > 0) { @@ -461,12 +450,10 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in timeLeftText = formatMinutes(minutesLeft); } } - const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg); - // Right aligned text for progress counter - const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) + - " " + std::to_string(bookProgress) + "%" + - (timeLeftText.empty() ? std::string() : " " + timeLeftText + " left"); + const std::string progress = (timeLeftText.empty() ? std::string() : timeLeftText + " ") + + std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) + + " " + std::to_string(bookProgress) + "%"; progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str()); renderer.drawText(SMALL_FONT_ID, renderer.getScreenWidth() - orientedMarginRight - progressTextWidth, textY, progress.c_str()); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index d57107a5..83c2e33a 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -35,8 +35,8 @@ const SettingInfo settingsList[settingsCount] = { SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment, {"Justify", "Left", "Center", "Right"}), - SettingInfo::Value("Reading Speed (WPM)", &CrossPointSettings::readingSpeedWpm, {80, 300, 10}), - SettingInfo::Toggle("Show Time Left", &CrossPointSettings::showTimeLeftInChapter), + SettingInfo::Value("Reading Speed (WPM)", &CrossPointSettings::readingSpeedWpm, {150, 300, 5}), + SettingInfo::Toggle("Show Time Left In Chapter", &CrossPointSettings::showTimeLeftInChapter), SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,