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
13 changed files with 352 additions and 27 deletions

View File

@@ -5,6 +5,7 @@
#include <SD.h>
#include "Battery.h"
#include "CrossPointSettings.h"
#include "EpubReaderChapterSelectionScreen.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);
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
marginLeft)) {
marginLeft, SETTINGS.extraParagraphSpacing)) {
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
{
@@ -228,7 +229,7 @@ void EpubReaderScreen::renderScreen() {
section->setupCacheDir();
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());
section.reset();
return;

View File

@@ -91,11 +91,16 @@ void FileSelectionScreen::handleInput() {
} else {
onSelect(basepath + files[selectorIndex]);
}
} else if (inputManager.wasPressed(InputManager::BTN_BACK) && basepath != "/") {
basepath = basepath.substr(0, basepath.rfind('/'));
if (basepath.empty()) basepath = "/";
loadFiles();
updateRequired = true;
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
if (basepath != "/") {
basepath = basepath.substr(0, basepath.rfind('/'));
if (basepath.empty()) basepath = "/";
loadFiles();
updateRequired = true;
} else {
// At root level, go to settings
onSettingsOpen();
}
} else if (prevPressed) {
selectorIndex = (selectorIndex + files.size() - 1) % files.size();
updateRequired = true;
@@ -123,6 +128,10 @@ void FileSelectionScreen::render() const {
const auto pageWidth = GfxRenderer::getScreenWidth();
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()) {
renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found");
} else {

View File

@@ -17,6 +17,7 @@ class FileSelectionScreen final : public Screen {
int selectorIndex = 0;
bool updateRequired = false;
const std::function<void(const std::string&)> onSelect;
const std::function<void()> onSettingsOpen;
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
@@ -25,8 +26,9 @@ class FileSelectionScreen final : public Screen {
public:
explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
const std::function<void(const std::string&)>& onSelect)
: Screen(renderer, inputManager), onSelect(onSelect) {}
const std::function<void(const std::string&)>& onSelect,
const std::function<void()>& onSettingsOpen)
: Screen(renderer, inputManager), onSelect(onSelect), onSettingsOpen(onSettingsOpen) {}
void onEnter() override;
void onExit() 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 "images/CrossLarge.h"
#include "CrossPointSettings.h"
void SleepScreen::onEnter() {
const auto pageWidth = GfxRenderer::getScreenWidth();
@@ -13,6 +14,11 @@ void SleepScreen::onEnter() {
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
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);
}