From 73959ea9ac8a2e41ee38821ccdbce63fb7271779 Mon Sep 17 00:00:00 2001 From: ratedcounsel Date: Wed, 31 Dec 2025 10:45:59 +0000 Subject: [PATCH] feat: add Refresh Frequency user preference setting --- lib/GfxRenderer/GfxRenderer.h | 1 + platformio.ini | 2 + src/CrossPointSettings.cpp | 21 +++++++++- src/CrossPointSettings.h | 6 +++ src/activities/reader/EpubReaderActivity.cpp | 14 +++---- .../EpubReaderChapterSelectionActivity.cpp | 4 +- .../reader/FileSelectionActivity.cpp | 3 +- src/activities/settings/SettingsActivity.cpp | 40 ++++++++++++++----- 8 files changed, 69 insertions(+), 22 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 7b0bcc00..90d2044c 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -4,6 +4,7 @@ #include #include +#include #include "Bitmap.h" diff --git a/platformio.ini b/platformio.ini index fad8c08c..dcc12349 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,6 +4,7 @@ default_envs = default [base] platform = espressif32 @ 6.12.0 +platform_packages = espressif/toolchain-riscv32-esp@11.2.0+2022r1 board = esp32-c3-devkitm-1 framework = arduino monitor_speed = 115200 @@ -43,6 +44,7 @@ lib_deps = InputManager=symlink://open-x4-sdk/libs/hardware/InputManager EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager + SdFat @ 2.2.3 ArduinoJson @ 7.4.2 QRCode @ 0.0.1 diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 1443b109..c67fbb55 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -12,7 +12,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 = 11; +constexpr uint8_t SETTINGS_COUNT = 12; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -38,6 +38,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, fontSize); serialization::writePod(outputFile, lineSpacing); serialization::writePod(outputFile, sleepTimeout); + serialization::writePod(outputFile, refreshFrequency); outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -86,6 +87,8 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, sleepTimeout); if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, refreshFrequency); + if (++settingsRead >= fileSettingsCount) break; } while (false); inputFile.close(); @@ -145,6 +148,22 @@ unsigned long CrossPointSettings::getSleepTimeoutMs() const { } } +int CrossPointSettings::getRefreshFrequency() const { + switch (refreshFrequency) { + case REFRESH_1: + return 1; + case REFRESH_5: + return 5; + case REFRESH_10: + return 10; + case REFRESH_15: + default: + return 15; + case REFRESH_30: + return 30; + } +} + int CrossPointSettings::getReaderFontId() const { switch (fontFamily) { case BOOKERLY: diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index ff91db62..ac7c5d50 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -47,6 +47,9 @@ class CrossPointSettings { // Auto-sleep timeout options (in minutes) enum SLEEP_TIMEOUT { SLEEP_1_MIN = 0, SLEEP_5_MIN = 1, SLEEP_10_MIN = 2, SLEEP_15_MIN = 3, SLEEP_30_MIN = 4 }; + // E-ink refresh frequency (pages between full refreshes) + enum REFRESH_FREQUENCY { REFRESH_1 = 0, REFRESH_5 = 1, REFRESH_10 = 2, REFRESH_15 = 3, REFRESH_30 = 4 }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Status bar settings @@ -67,6 +70,8 @@ class CrossPointSettings { uint8_t lineSpacing = NORMAL; // Auto-sleep timeout setting (default 10 minutes) uint8_t sleepTimeout = SLEEP_10_MIN; + // E-ink refresh frequency (default 15 pages) + uint8_t refreshFrequency = REFRESH_15; ~CrossPointSettings() = default; @@ -81,6 +86,7 @@ class CrossPointSettings { float getReaderLineCompression() const; unsigned long getSleepTimeoutMs() const; + int getRefreshFrequency() const; }; // Helper macro to access settings diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index e665721c..0f3398d8 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -13,7 +13,7 @@ #include "fontIds.h" namespace { -constexpr int pagesPerRefresh = 15; +// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() constexpr unsigned long skipChapterMs = 700; constexpr unsigned long goHomeMs = 1000; constexpr int topPadding = 5; @@ -22,9 +22,9 @@ constexpr int horizontalPadding = 5; // contentGap = space between last line of book text and delimiter line // lineToText = space between delimiter line and footer text // footerTextHeight ~= 12px for small font -constexpr int footerHeight = 34; // total footer area height (includes bottom margin) -constexpr int contentGap = 6; // gap above delimiter line -constexpr int lineToText = 6; // gap below delimiter line to text +constexpr int footerHeight = 34; // total footer area height (includes bottom margin) +constexpr int contentGap = 6; // gap above delimiter line +constexpr int lineToText = 6; // gap below delimiter line to text } // namespace void EpubReaderActivity::taskTrampoline(void* param) { @@ -384,7 +384,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(EInkDisplay::HALF_REFRESH); - pagesUntilFullRefresh = pagesPerRefresh; + pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayBuffer(); pagesUntilFullRefresh--; @@ -431,12 +431,12 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in // So book content ends at: screenHeight - orientedMarginBottom // Line should be at: screenHeight - orientedMarginBottom + contentGap (just below content) // Footer text at: lineY + lineToText + some offset for text baseline - + const auto screenHeight = renderer.getScreenHeight(); const int contentBottom = screenHeight - orientedMarginBottom; const int lineY = contentBottom + contentGap; const int textY = lineY + lineToText; - + renderer.drawLine(orientedMarginLeft, lineY, renderer.getScreenWidth() - orientedMarginRight, lineY); int progressTextWidth = 0; diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 0048e08b..6c73ee30 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -123,8 +123,8 @@ void EpubReaderChapterSelectionActivity::renderScreen() { const int pageItems = getPageItems(); // Draw header with book title - const std::string title = - renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - horizontalMargin * 2, EpdFontFamily::BOLD); + const std::string title = renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), + pageWidth - horizontalMargin * 2, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, headerY, title.c_str(), true, EpdFontFamily::BOLD); // Subtle separator line under header diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp index 951cabb8..3045314d 100644 --- a/src/activities/reader/FileSelectionActivity.cpp +++ b/src/activities/reader/FileSelectionActivity.cpp @@ -199,7 +199,8 @@ void FileSelectionActivity::render() const { renderer.fillRect(0, listStartY + (selectorIndex % PAGE_ITEMS) * rowHeight - 2, pageWidth - 1, rowHeight); for (int i = pageStartIndex; i < files.size() && i < pageStartIndex + PAGE_ITEMS; i++) { auto item = renderer.truncatedText(UI_10_FONT_ID, files[i].c_str(), pageWidth - horizontalMargin * 2 - 8); - renderer.drawText(UI_10_FONT_ID, horizontalMargin + 4, listStartY + (i % PAGE_ITEMS) * rowHeight, item.c_str(), i != selectorIndex); + renderer.drawText(UI_10_FONT_ID, horizontalMargin + 4, listStartY + (i % PAGE_ITEMS) * rowHeight, item.c_str(), + i != selectorIndex); } renderer.displayBuffer(); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 36cb1b80..56227f97 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -9,7 +9,7 @@ // Define the static settings list namespace { -constexpr int settingsCount = 12; +constexpr int settingsCount = 13; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}}, @@ -34,7 +34,14 @@ const SettingInfo settingsList[settingsCount] = { {"Bookerly", "Noto Sans", "Open Dyslexic"}}, {"Reader Font Size", SettingType::ENUM, &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}}, {"Reader Line Spacing", SettingType::ENUM, &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}}, - {"Time to Sleep", SettingType::ENUM, &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}}, + {"Time to Sleep", + SettingType::ENUM, + &CrossPointSettings::sleepTimeout, + {"1 min", "5 min", "10 min", "15 min", "30 min"}}, + {"Refresh Frequency", + SettingType::ENUM, + &CrossPointSettings::refreshFrequency, + {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}}, {"Check for updates", SettingType::ACTION, nullptr, {}}, }; } // namespace @@ -163,18 +170,28 @@ void SettingsActivity::render() const { const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); - // Draw header - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD); + // Layout constants + constexpr int headerY = 16; + constexpr int separatorY = 42; + constexpr int listStartY = 54; + constexpr int rowHeight = 28; + constexpr int horizontalMargin = 16; - // Draw selection - renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30); + // Draw header + renderer.drawCenteredText(UI_12_FONT_ID, headerY, "Settings", true, EpdFontFamily::BOLD); + + // Subtle separator line under header + renderer.drawLine(horizontalMargin, separatorY, pageWidth - horizontalMargin, separatorY); + + // Draw selection highlight + renderer.fillRect(0, listStartY + selectedSettingIndex * rowHeight - 2, pageWidth - 1, rowHeight); // Draw all settings for (int i = 0; i < settingsCount; i++) { - const int settingY = 60 + i * 30; // 30 pixels between settings + const int settingY = listStartY + i * rowHeight; // Draw setting name - renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, i != selectedSettingIndex); + renderer.drawText(UI_10_FONT_ID, horizontalMargin + 4, settingY, settingsList[i].name, i != selectedSettingIndex); // Draw value based on setting type std::string valueText = ""; @@ -186,12 +203,13 @@ void SettingsActivity::render() const { valueText = settingsList[i].enumValues[value]; } const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()); - renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), i != selectedSettingIndex); + renderer.drawText(UI_10_FONT_ID, pageWidth - horizontalMargin - 4 - width, settingY, valueText.c_str(), + i != selectedSettingIndex); } // Draw version text above button hints - renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), - pageHeight - 60, CROSSPOINT_VERSION); + renderer.drawText(SMALL_FONT_ID, pageWidth - 16 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), + pageHeight - 58, CROSSPOINT_VERSION); // Draw help text const auto labels = mappedInput.mapLabels("« Save", "Toggle", "", "");