Add setting to allow for cover sleep screen

This commit is contained in:
Dave Allie 2025-12-21 18:24:56 +11:00
parent c50257c9da
commit a8350d46fd
No known key found for this signature in database
GPG Key ID: F2FDDB3AD8D0276F
6 changed files with 104 additions and 53 deletions

View File

@ -23,7 +23,7 @@ bool CrossPointSettings::saveToFile() const {
std::ofstream outputFile(SETTINGS_FILE); std::ofstream outputFile(SETTINGS_FILE);
serialization::writePod(outputFile, SETTINGS_FILE_VERSION); serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
serialization::writePod(outputFile, SETTINGS_COUNT); serialization::writePod(outputFile, SETTINGS_COUNT);
serialization::writePod(outputFile, whiteSleepScreen); serialization::writePod(outputFile, sleepScreen);
serialization::writePod(outputFile, extraParagraphSpacing); serialization::writePod(outputFile, extraParagraphSpacing);
serialization::writePod(outputFile, shortPwrBtn); serialization::writePod(outputFile, shortPwrBtn);
outputFile.close(); outputFile.close();
@ -54,7 +54,7 @@ bool CrossPointSettings::loadFromFile() {
// load settings that exist // load settings that exist
uint8_t settingsRead = 0; uint8_t settingsRead = 0;
do { do {
serialization::readPod(inputFile, whiteSleepScreen); serialization::readPod(inputFile, sleepScreen);
if (++settingsRead >= fileSettingsCount) break; if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, extraParagraphSpacing); serialization::readPod(inputFile, extraParagraphSpacing);
if (++settingsRead >= fileSettingsCount) break; if (++settingsRead >= fileSettingsCount) break;

View File

@ -15,8 +15,11 @@ class CrossPointSettings {
CrossPointSettings(const CrossPointSettings&) = delete; CrossPointSettings(const CrossPointSettings&) = delete;
CrossPointSettings& operator=(const CrossPointSettings&) = delete; CrossPointSettings& operator=(const CrossPointSettings&) = delete;
// Should match with SettingsActivity text
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3 };
// Sleep screen settings // Sleep screen settings
uint8_t whiteSleepScreen = 0; uint8_t sleepScreen = DARK;
// Text rendering settings // Text rendering settings
uint8_t extraParagraphSpacing = 1; uint8_t extraParagraphSpacing = 1;
// Duration of the power button press // Duration of the power button press

View File

@ -1,16 +1,45 @@
#include "SleepActivity.h" #include "SleepActivity.h"
#include <Epub.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <SD.h> #include <SD.h>
#include <vector> #include <vector>
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "config.h" #include "config.h"
#include "images/CrossLarge.h" #include "images/CrossLarge.h"
void SleepActivity::onEnter() { void SleepActivity::onEnter() {
renderPopup("Entering Sleep..."); renderPopup("Entering Sleep...");
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) {
return renderCustomSleepScreen();
}
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::COVER) {
return renderCoverSleepScreen();
}
renderDefaultSleepScreen();
}
void SleepActivity::renderPopup(const char* message) const {
const int textWidth = renderer.getTextWidth(READER_FONT_ID, message);
constexpr int margin = 20;
const int x = (renderer.getScreenWidth() - textWidth - margin * 2) / 2;
constexpr int y = 117;
const int w = textWidth + margin * 2;
const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2;
// renderer.clearScreen();
renderer.fillRect(x + 5, y + 5, w - 10, h - 10, false);
renderer.drawText(READER_FONT_ID, x + margin, y + margin, message);
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
renderer.displayBuffer();
}
void SleepActivity::renderCustomSleepScreen() const {
// Check if we have a /sleep directory // Check if we have a /sleep directory
auto dir = SD.open("/sleep"); auto dir = SD.open("/sleep");
if (dir && dir.isDirectory()) { if (dir && dir.isDirectory()) {
@ -28,31 +57,31 @@ void SleepActivity::onEnter() {
} }
if (filename.substr(filename.length() - 4) != ".bmp") { if (filename.substr(filename.length() - 4) != ".bmp") {
Serial.printf("[%lu] [Slp] Skipping non-.bmp file name: %s\n", millis(), file.name()); Serial.printf("[%lu] [SLP] Skipping non-.bmp file name: %s\n", millis(), file.name());
file.close(); file.close();
continue; continue;
} }
Bitmap bitmap(file); Bitmap bitmap(file);
if (bitmap.parseHeaders() != BmpReaderError::Ok) { if (bitmap.parseHeaders() != BmpReaderError::Ok) {
Serial.printf("[%lu] [Slp] Skipping invalid BMP file: %s\n", millis(), file.name()); Serial.printf("[%lu] [SLP] Skipping invalid BMP file: %s\n", millis(), file.name());
file.close(); file.close();
continue; continue;
} }
files.emplace_back(filename); files.emplace_back(filename);
file.close(); file.close();
} }
int numFiles = files.size(); const auto numFiles = files.size();
if (numFiles > 0) { if (numFiles > 0) {
// Generate a random number between 1 and numFiles // Generate a random number between 1 and numFiles
int randomFileIndex = random(numFiles); const auto randomFileIndex = random(numFiles);
auto filename = "/sleep/" + files[randomFileIndex]; const auto filename = "/sleep/" + files[randomFileIndex];
auto file = SD.open(filename.c_str()); auto file = SD.open(filename.c_str());
if (file) { if (file) {
Serial.printf("[%lu] [Slp] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str()); Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
delay(100); delay(100);
Bitmap bitmap(file); Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) { if (bitmap.parseHeaders() == BmpReaderError::Ok) {
renderCustomSleepScreen(bitmap); renderBitmapSleepScreen(bitmap);
dir.close(); dir.close();
return; return;
} }
@ -67,8 +96,8 @@ void SleepActivity::onEnter() {
if (file) { if (file) {
Bitmap bitmap(file); Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) { if (bitmap.parseHeaders() == BmpReaderError::Ok) {
Serial.printf("[%lu] [Slp] Loading: /sleep.bmp\n", millis()); Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis());
renderCustomSleepScreen(bitmap); renderBitmapSleepScreen(bitmap);
return; return;
} }
} }
@ -76,41 +105,27 @@ void SleepActivity::onEnter() {
renderDefaultSleepScreen(); renderDefaultSleepScreen();
} }
void SleepActivity::renderPopup(const char* message) const {
const int textWidth = renderer.getTextWidth(READER_FONT_ID, message);
constexpr int margin = 20;
const int x = (GfxRenderer::getScreenWidth() - textWidth - margin * 2) / 2;
constexpr int y = 117;
const int w = textWidth + margin * 2;
const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2;
// renderer.clearScreen();
renderer.fillRect(x + 5, y + 5, w - 10, h - 10, false);
renderer.drawText(READER_FONT_ID, x + margin, y + margin, message);
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
renderer.displayBuffer();
}
void SleepActivity::renderDefaultSleepScreen() const { void SleepActivity::renderDefaultSleepScreen() const {
const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = GfxRenderer::getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
renderer.clearScreen(); renderer.clearScreen();
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");
// Apply white screen if enabled in settings // Make sleep screen dark unless light is selected in settings
if (!SETTINGS.whiteSleepScreen) { if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {
renderer.invertScreen(); renderer.invertScreen();
} }
renderer.displayBuffer(EInkDisplay::HALF_REFRESH); renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
} }
void SleepActivity::renderCustomSleepScreen(const Bitmap& bitmap) const { void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
int x, y; int x, y;
const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = GfxRenderer::getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) { if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
// image will scale, make sure placement is right // image will scale, make sure placement is right
@ -153,3 +168,26 @@ void SleepActivity::renderCustomSleepScreen(const Bitmap& bitmap) const {
renderer.setRenderMode(GfxRenderer::BW); renderer.setRenderMode(GfxRenderer::BW);
} }
} }
void SleepActivity::renderCoverSleepScreen() const {
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
if (!lastEpub.load()) {
Serial.println("[SLP] Failed to load last epub");
return renderDefaultSleepScreen();
}
if (!lastEpub.generateCoverBmp()) {
Serial.println("[SLP] Failed to generate cover bmp");
return renderDefaultSleepScreen();
}
auto file = SD.open(lastEpub.getCoverBmpPath().c_str(), FILE_READ);
if (file) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
renderBitmapSleepScreen(bitmap);
return;
}
}
renderDefaultSleepScreen();
}

View File

@ -9,7 +9,9 @@ class SleepActivity final : public Activity {
void onEnter() override; void onEnter() override;
private: private:
void renderDefaultSleepScreen() const;
void renderCustomSleepScreen(const Bitmap& bitmap) const;
void renderPopup(const char* message) const; void renderPopup(const char* message) const;
void renderDefaultSleepScreen() const;
void renderCustomSleepScreen() const;
void renderCoverSleepScreen() const;
void renderBitmapSleepScreen(const Bitmap& bitmap) const;
}; };

View File

@ -6,11 +6,14 @@
#include "config.h" #include "config.h"
// Define the static settings list // Define the static settings list
namespace {
const SettingInfo SettingsActivity::settingsList[settingsCount] = { constexpr int settingsCount = 3;
{"White Sleep Screen", SettingType::TOGGLE, &CrossPointSettings::whiteSleepScreen}, const SettingInfo settingsList[settingsCount] = {
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing}, // Should match with SLEEP_SCREEN_MODE
{"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn}}; {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}},
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, {}},
{"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn, {}}};
} // namespace
void SettingsActivity::taskTrampoline(void* param) { void SettingsActivity::taskTrampoline(void* param) {
auto* self = static_cast<SettingsActivity*>(param); auto* self = static_cast<SettingsActivity*>(param);
@ -81,15 +84,18 @@ void SettingsActivity::toggleCurrentSetting() {
const auto& setting = settingsList[selectedSettingIndex]; const auto& setting = settingsList[selectedSettingIndex];
// Only toggle if it's a toggle type and has a value pointer if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
if (setting.type != SettingType::TOGGLE || setting.valuePtr == nullptr) { // Toggle the boolean value using the member pointer
const bool currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) = !currentValue;
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
} else {
// Only toggle if it's a toggle type and has a value pointer
return; return;
} }
// Toggle the boolean value using the member pointer
bool currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) = !currentValue;
// Save settings when they change // Save settings when they change
SETTINGS.saveToFile(); SETTINGS.saveToFile();
} }
@ -129,8 +135,13 @@ void SettingsActivity::render() const {
// Draw value based on setting type // Draw value based on setting type
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) { if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
bool value = SETTINGS.*(settingsList[i].valuePtr); const bool value = SETTINGS.*(settingsList[i].valuePtr);
renderer.drawText(UI_FONT_ID, pageWidth - 80, settingY, value ? "ON" : "OFF"); renderer.drawText(UI_FONT_ID, pageWidth - 80, settingY, value ? "ON" : "OFF");
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
auto valueText = settingsList[i].enumValues[value];
const auto width = renderer.getTextWidth(UI_FONT_ID, valueText.c_str());
renderer.drawText(UI_FONT_ID, pageWidth - 50 - width, settingY, valueText.c_str());
} }
} }

View File

@ -12,13 +12,14 @@
class CrossPointSettings; class CrossPointSettings;
enum class SettingType { TOGGLE }; enum class SettingType { TOGGLE, ENUM };
// Structure to hold setting information // Structure to hold setting information
struct SettingInfo { struct SettingInfo {
const char* name; // Display name of the setting const char* name; // Display name of the setting
SettingType type; // Type of setting SettingType type; // Type of setting
uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE) uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM)
std::vector<std::string> enumValues;
}; };
class SettingsActivity final : public Activity { class SettingsActivity final : public Activity {
@ -28,10 +29,6 @@ class SettingsActivity final : public Activity {
int selectedSettingIndex = 0; // Currently selected setting int selectedSettingIndex = 0; // Currently selected setting
const std::function<void()> onGoHome; const std::function<void()> onGoHome;
// Static settings list
static constexpr int settingsCount = 3; // Number of settings
static const SettingInfo settingsList[settingsCount];
static void taskTrampoline(void* param); static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop(); [[noreturn]] void displayTaskLoop();
void render() const; void render() const;