Settings Screen and first 2 settings (#18)

* white sleep screen

* quicker pwr button

* no extra spacing between paragraphs

* Added settings class with de/serialization and whiteSleepScreen setting to control inverting the sleep screen

* Added Settings screen for real, made settings a global singleton

* Added setting for extra paragraph spacing.

* fixed typo

* Rework after feedback

* Fixed type from bool to uint8
This commit is contained in:
Jonas Diemer 2025-12-15 13:16:46 +01:00 committed by GitHub
parent 7a5719b46d
commit a640fbecf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 352 additions and 27 deletions

View File

@ -9,7 +9,7 @@
#include "Page.h" #include "Page.h"
#include "parsers/ChapterHtmlSlimParser.h" #include "parsers/ChapterHtmlSlimParser.h"
constexpr uint8_t SECTION_FILE_VERSION = 4; constexpr uint8_t SECTION_FILE_VERSION = 5;
void Section::onPageComplete(std::unique_ptr<Page> page) { void Section::onPageComplete(std::unique_ptr<Page> page) {
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin"; const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
@ -24,7 +24,8 @@ void Section::onPageComplete(std::unique_ptr<Page> page) {
} }
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop, void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft) const { const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) const {
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str()); std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
serialization::writePod(outputFile, SECTION_FILE_VERSION); serialization::writePod(outputFile, SECTION_FILE_VERSION);
serialization::writePod(outputFile, fontId); serialization::writePod(outputFile, fontId);
@ -33,12 +34,14 @@ void Section::writeCacheMetadata(const int fontId, const float lineCompression,
serialization::writePod(outputFile, marginRight); serialization::writePod(outputFile, marginRight);
serialization::writePod(outputFile, marginBottom); serialization::writePod(outputFile, marginBottom);
serialization::writePod(outputFile, marginLeft); serialization::writePod(outputFile, marginLeft);
serialization::writePod(outputFile, extraParagraphSpacing);
serialization::writePod(outputFile, pageCount); serialization::writePod(outputFile, pageCount);
outputFile.close(); outputFile.close();
} }
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop, bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft) { const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) {
if (!SD.exists(cachePath.c_str())) { if (!SD.exists(cachePath.c_str())) {
return false; return false;
} }
@ -63,15 +66,18 @@ bool Section::loadCacheMetadata(const int fontId, const float lineCompression, c
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft; int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
float fileLineCompression; float fileLineCompression;
bool fileExtraParagraphSpacing;
serialization::readPod(inputFile, fileFontId); serialization::readPod(inputFile, fileFontId);
serialization::readPod(inputFile, fileLineCompression); serialization::readPod(inputFile, fileLineCompression);
serialization::readPod(inputFile, fileMarginTop); serialization::readPod(inputFile, fileMarginTop);
serialization::readPod(inputFile, fileMarginRight); serialization::readPod(inputFile, fileMarginRight);
serialization::readPod(inputFile, fileMarginBottom); serialization::readPod(inputFile, fileMarginBottom);
serialization::readPod(inputFile, fileMarginLeft); serialization::readPod(inputFile, fileMarginLeft);
serialization::readPod(inputFile, fileExtraParagraphSpacing);
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop || if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft) { marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft ||
extraParagraphSpacing != fileExtraParagraphSpacing) {
inputFile.close(); inputFile.close();
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis()); Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
clearCache(); clearCache();
@ -107,7 +113,8 @@ bool Section::clearCache() const {
} }
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop, bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft) { const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) {
const auto localPath = epub->getSpineItem(spineIndex); const auto localPath = epub->getSpineItem(spineIndex);
// TODO: Should we get rid of this file all together? // TODO: Should we get rid of this file all together?
@ -128,7 +135,7 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath; const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
ChapterHtmlSlimParser visitor(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight, ChapterHtmlSlimParser visitor(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight,
marginBottom, marginLeft, marginBottom, marginLeft, extraParagraphSpacing,
[this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); }); [this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); });
success = visitor.parseAndBuildPages(); success = visitor.parseAndBuildPages();
@ -138,7 +145,7 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
return false; return false;
} }
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft); writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing);
return true; return true;
} }

View File

@ -13,7 +13,7 @@ class Section {
std::string cachePath; std::string cachePath;
void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
int marginLeft) const; int marginLeft, bool extraParagraphSpacing) const;
void onPageComplete(std::unique_ptr<Page> page); void onPageComplete(std::unique_ptr<Page> page);
public: public:
@ -26,10 +26,10 @@ class Section {
} }
~Section() = default; ~Section() = default;
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
int marginLeft); int marginLeft, bool extraParagraphSpacing);
void setupCacheDir() const; void setupCacheDir() const;
bool clearCache() const; bool clearCache() const;
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
int marginLeft); int marginLeft, bool extraParagraphSpacing);
std::unique_ptr<Page> loadPageFromSD() const; std::unique_ptr<Page> loadPageFromSD() const;
}; };

View File

@ -274,6 +274,8 @@ void ChapterHtmlSlimParser::makePages() {
currentTextBlock->layoutAndExtractLines( currentTextBlock->layoutAndExtractLines(
renderer, fontId, marginLeft + marginRight, renderer, fontId, marginLeft + marginRight,
[this](const std::shared_ptr<TextBlock>& textBlock) { addLineToPage(textBlock); }); [this](const std::shared_ptr<TextBlock>& textBlock) { addLineToPage(textBlock); });
// Extra paragrpah spacing // Extra paragraph spacing if enabled
currentPageNextY += lineHeight / 2; if (extraParagraphSpacing) {
currentPageNextY += lineHeight / 2;
}
} }

View File

@ -35,6 +35,7 @@ class ChapterHtmlSlimParser {
int marginRight; int marginRight;
int marginBottom; int marginBottom;
int marginLeft; int marginLeft;
bool extraParagraphSpacing;
void startNewTextBlock(TextBlock::BLOCK_STYLE style); void startNewTextBlock(TextBlock::BLOCK_STYLE style);
void makePages(); void makePages();
@ -46,7 +47,8 @@ class ChapterHtmlSlimParser {
public: public:
explicit ChapterHtmlSlimParser(const char* filepath, GfxRenderer& renderer, const int fontId, explicit ChapterHtmlSlimParser(const char* filepath, GfxRenderer& renderer, const int fontId,
const float lineCompression, const int marginTop, const int marginRight, const float lineCompression, const int marginTop, const int marginRight,
const int marginBottom, const int marginLeft, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing,
const std::function<void(std::unique_ptr<Page>)>& completePageFn) const std::function<void(std::unique_ptr<Page>)>& completePageFn)
: filepath(filepath), : filepath(filepath),
renderer(renderer), renderer(renderer),
@ -56,6 +58,7 @@ class ChapterHtmlSlimParser {
marginRight(marginRight), marginRight(marginRight),
marginBottom(marginBottom), marginBottom(marginBottom),
marginLeft(marginLeft), marginLeft(marginLeft),
extraParagraphSpacing(extraParagraphSpacing),
completePageFn(completePageFn) {} completePageFn(completePageFn) {}
~ChapterHtmlSlimParser() = default; ~ChapterHtmlSlimParser() = default;
bool parseAndBuildPages(); bool parseAndBuildPages();

View File

@ -0,0 +1,65 @@
#include "CrossPointSettings.h"
#include <HardwareSerial.h>
#include <SD.h>
#include <Serialization.h>
#include <cstdint>
#include <fstream>
// Initialize the static instance
CrossPointSettings CrossPointSettings::instance;
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
constexpr uint8_t SETTINGS_COUNT = 2;
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
bool CrossPointSettings::saveToFile() const {
// Make sure the directory exists
SD.mkdir("/.crosspoint");
std::ofstream outputFile(SETTINGS_FILE);
serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
serialization::writePod(outputFile, SETTINGS_COUNT);
serialization::writePod(outputFile, whiteSleepScreen);
serialization::writePod(outputFile, extraParagraphSpacing);
outputFile.close();
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
return true;
}
bool CrossPointSettings::loadFromFile() {
if (!SD.exists(SETTINGS_FILE + 3)) { // +3 to skip "/sd" prefix
Serial.printf("[%lu] [CPS] Settings file does not exist, using defaults\n", millis());
return false;
}
std::ifstream inputFile(SETTINGS_FILE);
uint8_t version;
serialization::readPod(inputFile, version);
if (version != SETTINGS_FILE_VERSION) {
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
inputFile.close();
return false;
}
uint8_t fileSettingsCount = 0;
serialization::readPod(inputFile, fileSettingsCount);
// load settings that exist
switch (fileSettingsCount) {
case 1:
serialization::readPod(inputFile, whiteSleepScreen);
break;
case 2:
serialization::readPod(inputFile, whiteSleepScreen);
serialization::readPod(inputFile, extraParagraphSpacing);
break;
}
inputFile.close();
Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis());
return true;
}

36
src/CrossPointSettings.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include <iosfwd>
class CrossPointSettings {
private:
// Private constructor for singleton
CrossPointSettings() = default;
// Static instance
static CrossPointSettings instance;
public:
// Delete copy constructor and assignment
CrossPointSettings(const CrossPointSettings&) = delete;
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
// Sleep screen settings
uint8_t whiteSleepScreen = 0;
// Text rendering settings
uint8_t extraParagraphSpacing = 1;
~CrossPointSettings() = default;
// Get singleton instance
static CrossPointSettings& getInstance() {
return instance;
}
bool saveToFile() const;
bool loadFromFile();
};
// Helper macro to access settings
#define SETTINGS CrossPointSettings::getInstance()

View File

@ -14,12 +14,14 @@
#include <builtinFonts/ubuntu_bold_10.h> #include <builtinFonts/ubuntu_bold_10.h>
#include "Battery.h" #include "Battery.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "config.h" #include "config.h"
#include "screens/BootLogoScreen.h" #include "screens/BootLogoScreen.h"
#include "screens/EpubReaderScreen.h" #include "screens/EpubReaderScreen.h"
#include "screens/FileSelectionScreen.h" #include "screens/FileSelectionScreen.h"
#include "screens/FullScreenMessageScreen.h" #include "screens/FullScreenMessageScreen.h"
#include "screens/SettingsScreen.h"
#include "screens/SleepScreen.h" #include "screens/SleepScreen.h"
#define SPI_FQ 40000000 #define SPI_FQ 40000000
@ -58,9 +60,9 @@ EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ubuntuBold10Font);
// Power button timing // Power button timing
// Time required to confirm boot from sleep // Time required to confirm boot from sleep
constexpr unsigned long POWER_BUTTON_WAKEUP_MS = 1000; constexpr unsigned long POWER_BUTTON_WAKEUP_MS = 500;
// Time required to enter sleep mode // Time required to enter sleep mode
constexpr unsigned long POWER_BUTTON_SLEEP_MS = 1000; constexpr unsigned long POWER_BUTTON_SLEEP_MS = 500;
std::unique_ptr<Epub> loadEpub(const std::string& path) { std::unique_ptr<Epub> loadEpub(const std::string& path) {
if (!SD.exists(path.c_str())) { if (!SD.exists(path.c_str())) {
@ -165,9 +167,14 @@ void onSelectEpubFile(const std::string& path) {
} }
} }
void onGoToSettings() {
exitScreen();
enterNewScreen(new SettingsScreen(renderer, inputManager, onGoHome));
}
void onGoHome() { void onGoHome() {
exitScreen(); exitScreen();
enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile)); enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoToSettings));
} }
void setup() { void setup() {
@ -199,6 +206,7 @@ void setup() {
// SD Card Initialization // SD Card Initialization
SD.begin(SD_SPI_CS, SPI, SPI_FQ); SD.begin(SD_SPI_CS, SPI, SPI_FQ);
SETTINGS.loadFromFile();
appState.loadFromFile(); appState.loadFromFile();
if (!appState.openEpubPath.empty()) { if (!appState.openEpubPath.empty()) {
auto epub = loadEpub(appState.openEpubPath); auto epub = loadEpub(appState.openEpubPath);
@ -212,7 +220,7 @@ void setup() {
} }
exitScreen(); exitScreen();
enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile)); enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoToSettings));
// Ensure we're not still holding the power button before leaving setup // Ensure we're not still holding the power button before leaving setup
waitForPowerRelease(); waitForPowerRelease();

View File

@ -5,6 +5,7 @@
#include <SD.h> #include <SD.h>
#include "Battery.h" #include "Battery.h"
#include "CrossPointSettings.h"
#include "EpubReaderChapterSelectionScreen.h" #include "EpubReaderChapterSelectionScreen.h"
#include "config.h" #include "config.h"
@ -205,7 +206,7 @@ void EpubReaderScreen::renderScreen() {
Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex); Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex);
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer)); section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
marginLeft)) { marginLeft, SETTINGS.extraParagraphSpacing)) {
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis()); Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
{ {
@ -228,7 +229,7 @@ void EpubReaderScreen::renderScreen() {
section->setupCacheDir(); section->setupCacheDir();
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
marginLeft)) { marginLeft, SETTINGS.extraParagraphSpacing)) {
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis()); Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
section.reset(); section.reset();
return; return;

View File

@ -91,11 +91,16 @@ void FileSelectionScreen::handleInput() {
} else { } else {
onSelect(basepath + files[selectorIndex]); onSelect(basepath + files[selectorIndex]);
} }
} else if (inputManager.wasPressed(InputManager::BTN_BACK) && basepath != "/") { } else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
basepath = basepath.substr(0, basepath.rfind('/')); if (basepath != "/") {
if (basepath.empty()) basepath = "/"; basepath = basepath.substr(0, basepath.rfind('/'));
loadFiles(); if (basepath.empty()) basepath = "/";
updateRequired = true; loadFiles();
updateRequired = true;
} else {
// At root level, go to settings
onSettingsOpen();
}
} else if (prevPressed) { } else if (prevPressed) {
selectorIndex = (selectorIndex + files.size() - 1) % files.size(); selectorIndex = (selectorIndex + files.size() - 1) % files.size();
updateRequired = true; updateRequired = true;
@ -123,6 +128,10 @@ void FileSelectionScreen::render() const {
const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageWidth = GfxRenderer::getScreenWidth();
renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD); renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
// Help text
renderer.drawText(SMALL_FONT_ID, 20, GfxRenderer::getScreenHeight() - 30,
"Press BACK for Settings");
if (files.empty()) { if (files.empty()) {
renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found"); renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found");
} else { } else {

View File

@ -17,6 +17,7 @@ class FileSelectionScreen final : public Screen {
int selectorIndex = 0; int selectorIndex = 0;
bool updateRequired = false; bool updateRequired = false;
const std::function<void(const std::string&)> onSelect; const std::function<void(const std::string&)> onSelect;
const std::function<void()> onSettingsOpen;
static void taskTrampoline(void* param); static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop(); [[noreturn]] void displayTaskLoop();
@ -25,8 +26,9 @@ class FileSelectionScreen final : public Screen {
public: public:
explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager, explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
const std::function<void(const std::string&)>& onSelect) const std::function<void(const std::string&)>& onSelect,
: Screen(renderer, inputManager), onSelect(onSelect) {} const std::function<void()>& onSettingsOpen)
: Screen(renderer, inputManager), onSelect(onSelect), onSettingsOpen(onSettingsOpen) {}
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
void handleInput() override; void handleInput() override;

View File

@ -0,0 +1,143 @@
#include "SettingsScreen.h"
#include <GfxRenderer.h>
#include "CrossPointSettings.h"
#include "config.h"
// Define the static settings list
const SettingInfo SettingsScreen::settingsList[SettingsScreen::settingsCount] = {
{"White Sleep Screen", &CrossPointSettings::whiteSleepScreen},
{"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing}
};
void SettingsScreen::taskTrampoline(void* param) {
auto* self = static_cast<SettingsScreen*>(param);
self->displayTaskLoop();
}
void SettingsScreen::onEnter() {
renderingMutex = xSemaphoreCreateMutex();
// Reset selection to first item
selectedSettingIndex = 0;
// Trigger first update
updateRequired = true;
xTaskCreate(&SettingsScreen::taskTrampoline, "SettingsScreenTask",
2048, // Stack size
this, // Parameters
1, // Priority
&displayTaskHandle // Task handle
);
}
void SettingsScreen::onExit() {
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr;
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
}
void SettingsScreen::handleInput() {
// Check for Confirm button to toggle setting
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
// Toggle the current setting
toggleCurrentSetting();
// Trigger a redraw of the entire screen
updateRequired = true;
return; // Return early to prevent further processing
}
// Check for Back button to exit settings
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
// Save settings and exit
SETTINGS.saveToFile();
onGoHome();
return;
}
// Handle UP/DOWN navigation for multiple settings
if (inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT)) {
// Move selection up
if (selectedSettingIndex > 0) {
selectedSettingIndex--;
updateRequired = true;
}
} else if (inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT)) {
// Move selection down
if (selectedSettingIndex < settingsCount - 1) {
selectedSettingIndex++;
updateRequired = true;
}
}
}
void SettingsScreen::toggleCurrentSetting() {
// Validate index
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
return;
}
// Toggle the boolean value using the member pointer
bool currentValue = SETTINGS.*(settingsList[selectedSettingIndex].valuePtr);
SETTINGS.*(settingsList[selectedSettingIndex].valuePtr) = !currentValue;
// Save settings when they change
SETTINGS.saveToFile();
}
void SettingsScreen::displayTaskLoop() {
while (true) {
if (updateRequired) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
render();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void SettingsScreen::render() const {
renderer.clearScreen();
const auto pageWidth = GfxRenderer::getScreenWidth();
const auto pageHeight = GfxRenderer::getScreenHeight();
// Draw header
renderer.drawCenteredText(READER_FONT_ID, 10, "Settings", true, BOLD);
// We always have at least one setting
// Draw all settings
for (int i = 0; i < settingsCount; i++) {
const int settingY = 60 + i * 30; // 30 pixels between settings
// Draw selection indicator for the selected setting
if (i == selectedSettingIndex) {
renderer.drawText(UI_FONT_ID, 5, settingY, ">");
}
// Draw setting name and value
renderer.drawText(UI_FONT_ID, 20, settingY, settingsList[i].name);
bool value = SETTINGS.*(settingsList[i].valuePtr);
renderer.drawText(UI_FONT_ID, pageWidth - 80, settingY, value ? "ON" : "OFF");
}
// Draw help text
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 30,
"Press OK to toggle, BACK to save & exit");
// Always use standard refresh for settings screen
renderer.displayBuffer();
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <string>
#include <vector>
#include "Screen.h"
class CrossPointSettings;
// Structure to hold setting information
struct SettingInfo {
const char* name; // Display name of the setting
uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings
};
class SettingsScreen final : public Screen {
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
bool updateRequired = false;
int selectedSettingIndex = 0; // Currently selected setting
const std::function<void()> onGoHome;
// Static settings list
static constexpr int settingsCount = 2; // Number of settings
static const SettingInfo settingsList[settingsCount];
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
void render() const;
void toggleCurrentSetting();
public:
explicit SettingsScreen(GfxRenderer& renderer, InputManager& inputManager,
const std::function<void()>& onGoHome)
: Screen(renderer, inputManager), onGoHome(onGoHome) {}
void onEnter() override;
void onExit() override;
void handleInput() override;
};

View File

@ -4,6 +4,7 @@
#include "config.h" #include "config.h"
#include "images/CrossLarge.h" #include "images/CrossLarge.h"
#include "CrossPointSettings.h"
void SleepScreen::onEnter() { void SleepScreen::onEnter() {
const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageWidth = GfxRenderer::getScreenWidth();
@ -13,6 +14,11 @@ void SleepScreen::onEnter() {
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128); renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD); renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING"); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
renderer.invertScreen();
// Apply white screen if enabled in settings
if (!SETTINGS.whiteSleepScreen) {
renderer.invertScreen();
}
renderer.displayBuffer(EInkDisplay::HALF_REFRESH); renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
} }