diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 83df3935..572bac41 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 = 14; +constexpr uint8_t SETTINGS_COUNT = 15; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -40,6 +40,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, paragraphAlignment); serialization::writePod(outputFile, sleepTimeout); serialization::writePod(outputFile, refreshFrequency); + serialization::writePod(outputFile, screenMargin); serialization::writePod(outputFile, sleepScreenCoverMode); outputFile.close(); @@ -93,7 +94,10 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, refreshFrequency); if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, screenMargin); + if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, sleepScreenCoverMode); + if (++settingsRead >= fileSettingsCount) break; } while (false); inputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 05cd7cc3..5394c4e3 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -16,7 +16,7 @@ class CrossPointSettings { CrossPointSettings& operator=(const CrossPointSettings&) = delete; // Should match with SettingsActivity text - enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3 }; + enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 }; enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 }; // Status bar display type enum @@ -78,6 +78,9 @@ class CrossPointSettings { // E-ink refresh frequency (default 15 pages) uint8_t refreshFrequency = REFRESH_15; + // Reader screen margin settings + uint8_t screenMargin = 5; + ~CrossPointSettings() = default; // Get singleton instance @@ -92,6 +95,7 @@ class CrossPointSettings { float getReaderLineCompression() const; unsigned long getSleepTimeoutMs() const; int getRefreshFrequency() const; + int getReaderScreenMargin() const; }; // Helper macro to access settings diff --git a/src/ScreenComponents.cpp b/src/ScreenComponents.cpp index 04265e2d..2900f3e4 100644 --- a/src/ScreenComponents.cpp +++ b/src/ScreenComponents.cpp @@ -15,21 +15,21 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, // 1 column on left, 2 columns on right, 5 columns of battery body constexpr int batteryWidth = 15; - constexpr int batteryHeight = 10; + constexpr int batteryHeight = 12; const int x = left; - const int y = top + 8; + const int y = top + 6; // Top line - renderer.drawLine(x, y, x + batteryWidth - 4, y); + renderer.drawLine(x + 1, y, x + batteryWidth - 3, y); // Bottom line - renderer.drawLine(x, y + batteryHeight - 1, x + batteryWidth - 4, y + batteryHeight - 1); + renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1); // Left line - renderer.drawLine(x, y, x, y + batteryHeight - 1); + renderer.drawLine(x, y + 1, x, y + batteryHeight - 2); // Battery end - renderer.drawLine(x + batteryWidth - 4, y, x + batteryWidth - 4, y + batteryHeight - 1); - renderer.drawLine(x + batteryWidth - 3, y + 2, x + batteryWidth - 1, y + 2); - renderer.drawLine(x + batteryWidth - 3, y + batteryHeight - 3, x + batteryWidth - 1, y + batteryHeight - 3); - renderer.drawLine(x + batteryWidth - 1, y + 2, x + batteryWidth - 1, y + batteryHeight - 3); + renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2); + renderer.drawPixel(x + batteryWidth - 1, y + 3); + renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4); + renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5); // The +1 is to round up, so that we always fill at least one pixel int filledWidth = percentage * (batteryWidth - 5) / 100 + 1; @@ -37,5 +37,5 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, filledWidth = batteryWidth - 5; // Ensure we don't overflow } - renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2); + renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); } diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index c072e4e0..8d8fd791 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -28,6 +28,10 @@ void SleepActivity::onEnter() { Activity::onEnter(); renderPopup("Entering Sleep..."); + if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::BLANK) { + return renderBlankSleepScreen(); + } + if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) { return renderCustomSleepScreen(); } @@ -251,3 +255,8 @@ void SleepActivity::renderCoverSleepScreen() const { renderDefaultSleepScreen(); } + +void SleepActivity::renderBlankSleepScreen() const { + renderer.clearScreen(); + renderer.displayBuffer(EInkDisplay::HALF_REFRESH); +} diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h index 3a77d33b..283220ce 100644 --- a/src/activities/boot_sleep/SleepActivity.h +++ b/src/activities/boot_sleep/SleepActivity.h @@ -15,4 +15,5 @@ class SleepActivity final : public Activity { void renderCustomSleepScreen() const; void renderCoverSleepScreen() const; void renderBitmapSleepScreen(const Bitmap& bitmap) const; + void renderBlankSleepScreen() const; }; diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index a7160137..dde05614 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -381,7 +381,7 @@ void CrossPointWebServerActivity::renderServerRunning() const { renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, "or scan QR code with your phone to connect to Wifi."); // Show QR code for URL - std::string wifiConfig = std::string("WIFI:T:WPA;S:") + connectedSSID + ";P:" + "" + ";;"; + const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;"; drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig); startY += 6 * 29 + 3 * LINE_SPACING; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index d3cd5016..1b5dc777 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -16,8 +16,6 @@ namespace { // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() constexpr unsigned long skipChapterMs = 700; constexpr unsigned long goHomeMs = 1000; -constexpr int topPadding = 5; -constexpr int horizontalPadding = 5; constexpr int statusBarMargin = 19; } // namespace @@ -253,9 +251,9 @@ void EpubReaderActivity::renderScreen() { int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, &orientedMarginLeft); - orientedMarginTop += topPadding; - orientedMarginLeft += horizontalPadding; - orientedMarginRight += horizontalPadding; + orientedMarginTop += SETTINGS.screenMargin; + orientedMarginLeft += SETTINGS.screenMargin; + orientedMarginRight += SETTINGS.screenMargin; orientedMarginBottom += statusBarMargin; if (!section) { diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 5665d9db..469c7bb3 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -1,6 +1,7 @@ #include "SettingsActivity.h" #include +#include #include "CrossPointSettings.h" #include "MappedInputManager.h" @@ -9,46 +10,32 @@ // Define the static settings list namespace { -constexpr int settingsCount = 15; +constexpr int settingsCount = 16; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE - {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}}, - {"Sleep Screen Cover Mode", SettingType::ENUM, &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}}, - {"Status Bar", SettingType::ENUM, &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}}, - {"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, {}}, - {"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn, {}}, - {"Reading Orientation", - SettingType::ENUM, - &CrossPointSettings::orientation, - {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}}, - {"Front Button Layout", - SettingType::ENUM, - &CrossPointSettings::frontButtonLayout, - {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}}, - {"Side Button Layout (reader)", - SettingType::ENUM, - &CrossPointSettings::sideButtonLayout, - {"Prev, Next", "Next, Prev"}}, - {"Reader Font Family", - SettingType::ENUM, - &CrossPointSettings::fontFamily, - {"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"}}, - {"Reader Paragraph Alignment", - SettingType::ENUM, - &CrossPointSettings::paragraphAlignment, - {"Justify", "Left", "Center", "Right"}}, - {"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, {}}, -}; + SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), + SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), + SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}), + SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing), + SettingInfo::Toggle("Short Power Button Click", &CrossPointSettings::shortPwrBtn), + SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation, + {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}), + SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout, + {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}), + SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout, + {"Prev, Next", "Next, Prev"}), + SettingInfo::Enum("Reader Font Family", &CrossPointSettings::fontFamily, + {"Bookerly", "Noto Sans", "Open Dyslexic"}), + SettingInfo::Enum("Reader Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}), + SettingInfo::Enum("Reader Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}), + SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), + SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment, + {"Justify", "Left", "Center", "Right"}), + SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, + {"1 min", "5 min", "10 min", "15 min", "30 min"}), + SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, + {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}), + SettingInfo::Action("Check for updates")}; } // namespace void SettingsActivity::taskTrampoline(void* param) { @@ -58,7 +45,6 @@ void SettingsActivity::taskTrampoline(void* param) { void SettingsActivity::onEnter() { Activity::onEnter(); - renderingMutex = xSemaphoreCreateMutex(); // Reset selection to first item @@ -68,7 +54,7 @@ void SettingsActivity::onEnter() { updateRequired = true; xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask", - 2048, // Stack size + 4096, // Stack size this, // Parameters 1, // Priority &displayTaskHandle // Task handle @@ -115,11 +101,9 @@ void SettingsActivity::loop() { updateRequired = true; } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || mappedInput.wasPressed(MappedInputManager::Button::Right)) { - // Move selection down - if (selectedSettingIndex < settingsCount - 1) { - selectedSettingIndex++; - updateRequired = true; - } + // Move selection down (with wrap around) + selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0; + updateRequired = true; } } @@ -138,6 +122,15 @@ 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) { + // Decreasing would also be nice for large ranges I think but oh well can't have everything + const int8_t currentValue = SETTINGS.*(setting.valuePtr); + // Wrap to minValue if exceeding setting value boundary + if (currentValue + setting.valueRange.step > setting.valueRange.max) { + SETTINGS.*(setting.valuePtr) = setting.valueRange.min; + } else { + SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; + } } else if (setting.type == SettingType::ACTION) { if (std::string(setting.name) == "Check for updates") { xSemaphoreTake(renderingMutex, portMAX_DELAY); @@ -196,6 +189,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].valuePtr != nullptr) { + valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr)); } 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); diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index 83beb9d9..157689e3 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -11,14 +11,37 @@ class CrossPointSettings; -enum class SettingType { TOGGLE, ENUM, ACTION }; +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 to member in CrossPointSettings (for TOGGLE/ENUM) + uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM/VALUE) std::vector enumValues; + + struct ValueRange { + uint8_t min; + uint8_t max; + uint8_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}; + } + + static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector values) { + return {name, SettingType::ENUM, ptr, std::move(values)}; + } + + static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr}; } + + static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) { + return {name, SettingType::VALUE, ptr, {}, valueRange}; + } }; class SettingsActivity final : public ActivityWithSubactivity {