feat: add sleep screen selection

This commit is contained in:
dpoulter 2026-02-01 11:08:35 +02:00
parent da142c362e
commit d4036bf1f7
15 changed files with 87 additions and 90 deletions

View File

@ -290,6 +290,12 @@ const char* const SLEEP_TIMEOUT_OPTIONS[] = {
TIMEOUT_DATA TIMEOUT_DATA
#undef X #undef X
}; };
const char* const SCREEN_MARGIN_OPTIONS[] = {
#define X(val, str) str,
SCREEN_MARGIN_DATA
#undef X
};
} // namespace } // namespace
const char* CrossPointSettings::getRefreshFrequencyString(uint8_t value) { const char* CrossPointSettings::getRefreshFrequencyString(uint8_t value) {
@ -299,10 +305,6 @@ const char* CrossPointSettings::getRefreshFrequencyString(uint8_t value) {
return REFRESH_FREQUENCY_OPTIONS[REFRESH_15]; return REFRESH_FREQUENCY_OPTIONS[REFRESH_15];
} }
size_t CrossPointSettings::getRefreshFrequencyCount() {
return REFRESH_FREQUENCY_COUNT;
}
const char* CrossPointSettings::getSleepScreenString(uint8_t value) { const char* CrossPointSettings::getSleepScreenString(uint8_t value) {
if (value < SLEEP_SCREEN_MODE_COUNT) { if (value < SLEEP_SCREEN_MODE_COUNT) {
return SLEEP_SCREEN_OPTIONS[value]; return SLEEP_SCREEN_OPTIONS[value];
@ -310,10 +312,6 @@ const char* CrossPointSettings::getSleepScreenString(uint8_t value) {
return SLEEP_SCREEN_OPTIONS[DARK]; return SLEEP_SCREEN_OPTIONS[DARK];
} }
size_t CrossPointSettings::getSleepScreenCount() {
return SLEEP_SCREEN_MODE_COUNT;
}
const char* CrossPointSettings::getSleepTimeoutString(uint8_t value) { const char* CrossPointSettings::getSleepTimeoutString(uint8_t value) {
if (value < SLEEP_TIMEOUT_COUNT) { if (value < SLEEP_TIMEOUT_COUNT) {
return SLEEP_TIMEOUT_OPTIONS[value]; return SLEEP_TIMEOUT_OPTIONS[value];
@ -321,6 +319,19 @@ const char* CrossPointSettings::getSleepTimeoutString(uint8_t value) {
return SLEEP_TIMEOUT_OPTIONS[SLEEP_10_MIN]; return SLEEP_TIMEOUT_OPTIONS[SLEEP_10_MIN];
} }
size_t CrossPointSettings::getSleepTimeoutCount() { const char* CrossPointSettings::getScreenMarginString(uint8_t index) {
return SLEEP_TIMEOUT_COUNT; if (index < SCREEN_MARGIN_COUNT) {
return SCREEN_MARGIN_OPTIONS[index];
}
return SCREEN_MARGIN_OPTIONS[MARGIN_5];
} }
int CrossPointSettings::getScreenMarginIndex(uint8_t pixelValue) {
for (size_t i = 0; i < SCREEN_MARGIN_COUNT; i++) {
if (SCREEN_MARGIN_VALUES[i] == pixelValue) {
return static_cast<int>(i);
}
}
return -1;
}

View File

@ -113,6 +113,23 @@ class CrossPointSettings {
SLEEP_TIMEOUT_COUNT SLEEP_TIMEOUT_COUNT
}; };
// Reader screen margin options (pixel values)
#define SCREEN_MARGIN_DATA \
X(5, "5 px") X(10, "10 px") X(15, "15 px") X(20, "20 px") \
X(25, "25 px") X(30, "30 px") X(35, "35 px") X(40, "40 px")
enum SCREEN_MARGIN {
#define X(val, str) MARGIN_##val,
SCREEN_MARGIN_DATA
#undef X
SCREEN_MARGIN_COUNT
};
static inline constexpr uint8_t SCREEN_MARGIN_VALUES[SCREEN_MARGIN_COUNT] = {
#define X(val, str) val,
SCREEN_MARGIN_DATA
#undef X
};
// Short power button press actions // Short power button press actions
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2, SHORT_PWRBTN_COUNT }; enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2, SHORT_PWRBTN_COUNT };
@ -177,11 +194,11 @@ class CrossPointSettings {
// Helper functions to get option strings from enum values // Helper functions to get option strings from enum values
static const char* getRefreshFrequencyString(uint8_t value); static const char* getRefreshFrequencyString(uint8_t value);
static size_t getRefreshFrequencyCount();
static const char* getSleepScreenString(uint8_t value); static const char* getSleepScreenString(uint8_t value);
static size_t getSleepScreenCount();
static const char* getSleepTimeoutString(uint8_t value); static const char* getSleepTimeoutString(uint8_t value);
static size_t getSleepTimeoutCount(); static const char* getScreenMarginString(uint8_t index);
/** Returns index for pixel value, or -1 if not in allowed list. */
static int getScreenMarginIndex(uint8_t pixelValue);
float getReaderLineCompression() const; float getReaderLineCompression() const;
unsigned long getSleepTimeoutMs() const; unsigned long getSleepTimeoutMs() const;

View File

@ -146,15 +146,9 @@ void ListSelectionActivity::render() const {
const bool isSelected = (i == selectorIndex); const bool isSelected = (i == selectorIndex);
const int itemY = START_Y + visibleIndex * LINE_HEIGHT; const int itemY = START_Y + visibleIndex * LINE_HEIGHT;
if (customRenderItem) { const std::string itemText = getItemText(i);
// Use custom renderer if provided auto truncated = renderer.truncatedText(UI_10_FONT_ID, itemText.c_str(), pageWidth - 40);
customRenderItem(i, 20, itemY, isSelected); renderer.drawText(UI_10_FONT_ID, 20, itemY, truncated.c_str(), !isSelected);
} else {
// Default rendering: truncate text and draw
const std::string itemText = getItemText(i);
auto truncated = renderer.truncatedText(UI_10_FONT_ID, itemText.c_str(), pageWidth - 40);
renderer.drawText(UI_10_FONT_ID, 20, itemY, truncated.c_str(), !isSelected);
}
visibleIndex++; visibleIndex++;
} }

View File

@ -17,7 +17,6 @@
* - Automatic pagination based on screen size * - Automatic pagination based on screen size
* - Page skipping when holding navigation buttons * - Page skipping when holding navigation buttons
* - Configurable title, empty message, and button labels * - Configurable title, empty message, and button labels
* - Customizable item rendering
*/ */
class ListSelectionActivity : public Activity { class ListSelectionActivity : public Activity {
protected: protected:
@ -36,7 +35,6 @@ class ListSelectionActivity : public Activity {
std::function<std::string(size_t)> getItemText; std::function<std::string(size_t)> getItemText;
std::function<void(size_t)> onItemSelected; std::function<void(size_t)> onItemSelected;
std::function<void()> onBack; std::function<void()> onBack;
std::function<void(size_t, int, int, bool)> customRenderItem; // index, x, y, isSelected
// Constants // Constants
static constexpr int SKIP_PAGE_MS = 700; static constexpr int SKIP_PAGE_MS = 700;
@ -80,9 +78,4 @@ class ListSelectionActivity : public Activity {
// Allow subclasses to set initial selection // Allow subclasses to set initial selection
void setInitialSelection(size_t index) { selectorIndex = index; } void setInitialSelection(size_t index) { selectorIndex = index; }
size_t getCurrentSelection() const { return selectorIndex; } size_t getCurrentSelection() const { return selectorIndex; }
// Allow custom item rendering
void setCustomItemRenderer(std::function<void(size_t, int, int, bool)> renderer) {
customRenderItem = renderer;
}
}; };

View File

@ -45,26 +45,30 @@ void SleepActivity::renderPopup(const char* message) const {
renderer.displayBuffer(); renderer.displayBuffer();
} }
bool SleepActivity::renderSelectedSleepBmp(FsFile& dir) const {
if (SETTINGS.selectedSleepBmp[0] == '\0') return false;
const std::string selectedFile = std::string(SETTINGS.selectedSleepBmp);
const std::string filename = "/sleep/" + selectedFile;
FsFile file;
if (!SdMan.openFileForRead("SLP", filename, file)) {
Serial.printf("[%lu] [SLP] Selected BMP not found or invalid, falling back to random\n", millis());
return false;
}
Bitmap bitmap(file);
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
file.close();
Serial.printf("[%lu] [SLP] Selected BMP not found or invalid, falling back to random\n", millis());
return false;
}
renderBitmapSleepScreen(bitmap);
dir.close();
return true;
}
void SleepActivity::renderCustomSleepScreen() const { void SleepActivity::renderCustomSleepScreen() const {
auto dir = SdMan.open("/sleep"); auto dir = SdMan.open("/sleep");
if (dir && dir.isDirectory()) { if (dir && dir.isDirectory()) {
if (SETTINGS.selectedSleepBmp[0] != '\0') { if (renderSelectedSleepBmp(dir)) return;
const std::string selectedFile = std::string(SETTINGS.selectedSleepBmp);
const std::string filename = "/sleep/" + selectedFile;
FsFile file;
if (SdMan.openFileForRead("SLP", filename, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
Serial.printf("[%lu] [SLP] Loading selected: /sleep/%s\n", millis(), selectedFile.c_str());
delay(100);
renderBitmapSleepScreen(bitmap);
dir.close();
return;
}
file.close();
}
Serial.printf("[%lu] [SLP] Selected BMP not found or invalid, falling back to random\n", millis());
}
std::vector<std::string> files; std::vector<std::string> files;
char name[500]; char name[500];

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "../Activity.h" #include "../Activity.h"
#include <SDCardManager.h>
class Bitmap; class Bitmap;
@ -16,4 +17,5 @@ class SleepActivity final : public Activity {
void renderCoverSleepScreen() const; void renderCoverSleepScreen() const;
void renderBitmapSleepScreen(const Bitmap& bitmap) const; void renderBitmapSleepScreen(const Bitmap& bitmap) const;
void renderBlankSleepScreen() const; void renderBlankSleepScreen() const;
bool renderSelectedSleepBmp(FsFile& dir) const;
}; };

View File

@ -21,14 +21,12 @@ RefreshFrequencySelectionActivity::RefreshFrequencySelectionActivity(GfxRenderer
}, },
onBack, "No options available") { onBack, "No options available") {
// Initialize options from enum // Initialize options from enum
for (uint8_t i = 0; i < CrossPointSettings::getRefreshFrequencyCount(); i++) { for (uint8_t i = 0; i < CrossPointSettings::REFRESH_FREQUENCY_COUNT; i++) {
options.push_back(CrossPointSettings::getRefreshFrequencyString(i)); options.push_back(CrossPointSettings::getRefreshFrequencyString(i));
} }
} }
void RefreshFrequencySelectionActivity::loadItems() { void RefreshFrequencySelectionActivity::loadItems() {
// Options are already set in constructor, just set initial selection
// Map current enum value to option index
if (SETTINGS.refreshFrequency < options.size()) { if (SETTINGS.refreshFrequency < options.size()) {
selectorIndex = SETTINGS.refreshFrequency; selectorIndex = SETTINGS.refreshFrequency;
} else { } else {

View File

@ -6,10 +6,10 @@
#include "../ListSelectionActivity.h" #include "../ListSelectionActivity.h"
class RefreshFrequencySelectionActivity final : public ListSelectionActivity { class RefreshFrequencySelectionActivity final : public ListSelectionActivity {
std::vector<std::string> options; // Refresh frequency options std::vector<std::string> options;
protected: protected:
void loadItems() override; // Called by base class onEnter void loadItems() override;
public: public:
explicit RefreshFrequencySelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, explicit RefreshFrequencySelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,

View File

@ -1,7 +1,6 @@
#include "ScreenMarginSelectionActivity.h" #include "ScreenMarginSelectionActivity.h"
#include <cstring> #include <cstring>
#include <sstream>
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
@ -15,33 +14,21 @@ ScreenMarginSelectionActivity::ScreenMarginSelectionActivity(GfxRenderer& render
if (index >= options.size()) { if (index >= options.size()) {
return; return;
} }
// Map option index to margin value SETTINGS.screenMargin = CrossPointSettings::SCREEN_MARGIN_VALUES[index];
// Options: "5 px", "10 px", "15 px", "20 px", "25 px", "30 px", "35 px", "40 px"
// Values: 5, 10, 15, 20, 25, 30, 35, 40
SETTINGS.screenMargin = static_cast<uint8_t>((index + 1) * 5);
SETTINGS.saveToFile(); SETTINGS.saveToFile();
onBack(); onBack();
}, },
onBack, "No options available") { onBack, "No options available") {
// Initialize options: 5 to 40 in steps of 5 for (uint8_t i = 0; i < CrossPointSettings::SCREEN_MARGIN_COUNT; i++) {
for (int i = 5; i <= 40; i += 5) { options.push_back(CrossPointSettings::getScreenMarginString(i));
std::ostringstream oss;
oss << i << " px";
options.push_back(oss.str());
} }
} }
void ScreenMarginSelectionActivity::loadItems() { void ScreenMarginSelectionActivity::loadItems() {
// Options are already set in constructor, just set initial selection const int idx = CrossPointSettings::getScreenMarginIndex(SETTINGS.screenMargin);
// Map current margin value to option index if (idx >= 0 && static_cast<size_t>(idx) < options.size()) {
// margin value / 5 - 1 = index (e.g., 5 -> 0, 10 -> 1, etc.) selectorIndex = static_cast<size_t>(idx);
if (SETTINGS.screenMargin >= 5 && SETTINGS.screenMargin <= 40) {
selectorIndex = (SETTINGS.screenMargin / 5) - 1;
// Ensure index is within bounds
if (selectorIndex >= options.size()) {
selectorIndex = 0; // Default to "5 px"
}
} else { } else {
selectorIndex = 0; // Default to "5 px" selectorIndex = 0;
} }
} }

View File

@ -6,10 +6,10 @@
#include "../ListSelectionActivity.h" #include "../ListSelectionActivity.h"
class ScreenMarginSelectionActivity final : public ListSelectionActivity { class ScreenMarginSelectionActivity final : public ListSelectionActivity {
std::vector<std::string> options; // Screen margin options std::vector<std::string> options;
protected: protected:
void loadItems() override; // Called by base class onEnter void loadItems() override;
public: public:
explicit ScreenMarginSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, explicit ScreenMarginSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,

View File

@ -11,7 +11,6 @@
#include "CategorySettingsActivity.h" #include "CategorySettingsActivity.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "OtaUpdateActivity.h"
#include "SleepBmpSelectionActivity.h" #include "SleepBmpSelectionActivity.h"
#include "fontIds.h" #include "fontIds.h"

View File

@ -6,11 +6,11 @@
#include "../ListSelectionActivity.h" #include "../ListSelectionActivity.h"
class SleepBmpSelectionActivity final : public ListSelectionActivity { class SleepBmpSelectionActivity final : public ListSelectionActivity {
std::vector<std::string> files; // Sorted list of valid BMP filenames ("Random" at index 0) std::vector<std::string> files;
void loadFiles(); // Load and sort all valid BMP files void loadFiles();
protected: protected:
void loadItems() override; // Called by base class onEnter void loadItems() override;
public: public:
explicit SleepBmpSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, explicit SleepBmpSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,

View File

@ -14,21 +14,17 @@ SleepScreenSelectionActivity::SleepScreenSelectionActivity(GfxRenderer& renderer
if (index >= options.size()) { if (index >= options.size()) {
return; return;
} }
// Map option index to enum value (index matches enum value)
SETTINGS.sleepScreen = static_cast<uint8_t>(index); SETTINGS.sleepScreen = static_cast<uint8_t>(index);
SETTINGS.saveToFile(); SETTINGS.saveToFile();
onBack(); onBack();
}, },
onBack, "No options available") { onBack, "No options available") {
// Initialize options from enum for (uint8_t i = 0; i < CrossPointSettings::SLEEP_SCREEN_MODE_COUNT; i++) {
for (uint8_t i = 0; i < CrossPointSettings::getSleepScreenCount(); i++) {
options.push_back(CrossPointSettings::getSleepScreenString(i)); options.push_back(CrossPointSettings::getSleepScreenString(i));
} }
} }
void SleepScreenSelectionActivity::loadItems() { void SleepScreenSelectionActivity::loadItems() {
// Options are already set in constructor, just set initial selection
// Map current enum value to option index
if (SETTINGS.sleepScreen < options.size()) { if (SETTINGS.sleepScreen < options.size()) {
selectorIndex = SETTINGS.sleepScreen; selectorIndex = SETTINGS.sleepScreen;
} else { } else {

View File

@ -6,10 +6,10 @@
#include "../ListSelectionActivity.h" #include "../ListSelectionActivity.h"
class SleepScreenSelectionActivity final : public ListSelectionActivity { class SleepScreenSelectionActivity final : public ListSelectionActivity {
std::vector<std::string> options; // Sleep screen mode options std::vector<std::string> options;
protected: protected:
void loadItems() override; // Called by base class onEnter void loadItems() override;
public: public:
explicit SleepScreenSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, explicit SleepScreenSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,

View File

@ -14,21 +14,17 @@ SleepTimeoutSelectionActivity::SleepTimeoutSelectionActivity(GfxRenderer& render
if (index >= options.size()) { if (index >= options.size()) {
return; return;
} }
// Map option index to enum value (index matches enum value)
SETTINGS.sleepTimeout = static_cast<uint8_t>(index); SETTINGS.sleepTimeout = static_cast<uint8_t>(index);
SETTINGS.saveToFile(); SETTINGS.saveToFile();
onBack(); onBack();
}, },
onBack, "No options available") { onBack, "No options available") {
// Initialize options from enum for (uint8_t i = 0; i < CrossPointSettings::SLEEP_TIMEOUT_COUNT; i++) {
for (uint8_t i = 0; i < CrossPointSettings::getSleepTimeoutCount(); i++) {
options.push_back(CrossPointSettings::getSleepTimeoutString(i)); options.push_back(CrossPointSettings::getSleepTimeoutString(i));
} }
} }
void SleepTimeoutSelectionActivity::loadItems() { void SleepTimeoutSelectionActivity::loadItems() {
// Options are already set in constructor, just set initial selection
// Map current enum value to option index
if (SETTINGS.sleepTimeout < options.size()) { if (SETTINGS.sleepTimeout < options.size()) {
selectorIndex = SETTINGS.sleepTimeout; selectorIndex = SETTINGS.sleepTimeout;
} else { } else {