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
#undef X
};
const char* const SCREEN_MARGIN_OPTIONS[] = {
#define X(val, str) str,
SCREEN_MARGIN_DATA
#undef X
};
} // namespace
const char* CrossPointSettings::getRefreshFrequencyString(uint8_t value) {
@ -299,10 +305,6 @@ const char* CrossPointSettings::getRefreshFrequencyString(uint8_t value) {
return REFRESH_FREQUENCY_OPTIONS[REFRESH_15];
}
size_t CrossPointSettings::getRefreshFrequencyCount() {
return REFRESH_FREQUENCY_COUNT;
}
const char* CrossPointSettings::getSleepScreenString(uint8_t value) {
if (value < SLEEP_SCREEN_MODE_COUNT) {
return SLEEP_SCREEN_OPTIONS[value];
@ -310,10 +312,6 @@ const char* CrossPointSettings::getSleepScreenString(uint8_t value) {
return SLEEP_SCREEN_OPTIONS[DARK];
}
size_t CrossPointSettings::getSleepScreenCount() {
return SLEEP_SCREEN_MODE_COUNT;
}
const char* CrossPointSettings::getSleepTimeoutString(uint8_t value) {
if (value < SLEEP_TIMEOUT_COUNT) {
return SLEEP_TIMEOUT_OPTIONS[value];
@ -321,6 +319,19 @@ const char* CrossPointSettings::getSleepTimeoutString(uint8_t value) {
return SLEEP_TIMEOUT_OPTIONS[SLEEP_10_MIN];
}
size_t CrossPointSettings::getSleepTimeoutCount() {
return SLEEP_TIMEOUT_COUNT;
const char* CrossPointSettings::getScreenMarginString(uint8_t index) {
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
};
// 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
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
static const char* getRefreshFrequencyString(uint8_t value);
static size_t getRefreshFrequencyCount();
static const char* getSleepScreenString(uint8_t value);
static size_t getSleepScreenCount();
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;
unsigned long getSleepTimeoutMs() const;

View File

@ -146,15 +146,9 @@ void ListSelectionActivity::render() const {
const bool isSelected = (i == selectorIndex);
const int itemY = START_Y + visibleIndex * LINE_HEIGHT;
if (customRenderItem) {
// Use custom renderer if provided
customRenderItem(i, 20, itemY, 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);
}
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++;
}

View File

@ -17,7 +17,6 @@
* - Automatic pagination based on screen size
* - Page skipping when holding navigation buttons
* - Configurable title, empty message, and button labels
* - Customizable item rendering
*/
class ListSelectionActivity : public Activity {
protected:
@ -36,8 +35,7 @@ class ListSelectionActivity : public Activity {
std::function<std::string(size_t)> getItemText;
std::function<void(size_t)> onItemSelected;
std::function<void()> onBack;
std::function<void(size_t, int, int, bool)> customRenderItem; // index, x, y, isSelected
// Constants
static constexpr int SKIP_PAGE_MS = 700;
static constexpr unsigned long IGNORE_INPUT_MS = 300;
@ -80,9 +78,4 @@ class ListSelectionActivity : public Activity {
// Allow subclasses to set initial selection
void setInitialSelection(size_t index) { selectorIndex = index; }
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();
}
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 {
auto dir = SdMan.open("/sleep");
if (dir && dir.isDirectory()) {
if (SETTINGS.selectedSleepBmp[0] != '\0') {
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());
}
if (renderSelectedSleepBmp(dir)) return;
std::vector<std::string> files;
char name[500];

View File

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

View File

@ -21,14 +21,12 @@ RefreshFrequencySelectionActivity::RefreshFrequencySelectionActivity(GfxRenderer
},
onBack, "No options available") {
// 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));
}
}
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()) {
selectorIndex = SETTINGS.refreshFrequency;
} else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,21 +14,17 @@ SleepScreenSelectionActivity::SleepScreenSelectionActivity(GfxRenderer& renderer
if (index >= options.size()) {
return;
}
// Map option index to enum value (index matches enum value)
SETTINGS.sleepScreen = static_cast<uint8_t>(index);
SETTINGS.saveToFile();
onBack();
},
onBack, "No options available") {
// Initialize options from enum
for (uint8_t i = 0; i < CrossPointSettings::getSleepScreenCount(); i++) {
for (uint8_t i = 0; i < CrossPointSettings::SLEEP_SCREEN_MODE_COUNT; i++) {
options.push_back(CrossPointSettings::getSleepScreenString(i));
}
}
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()) {
selectorIndex = SETTINGS.sleepScreen;
} else {

View File

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

View File

@ -14,21 +14,17 @@ SleepTimeoutSelectionActivity::SleepTimeoutSelectionActivity(GfxRenderer& render
if (index >= options.size()) {
return;
}
// Map option index to enum value (index matches enum value)
SETTINGS.sleepTimeout = static_cast<uint8_t>(index);
SETTINGS.saveToFile();
onBack();
},
onBack, "No options available") {
// Initialize options from enum
for (uint8_t i = 0; i < CrossPointSettings::getSleepTimeoutCount(); i++) {
for (uint8_t i = 0; i < CrossPointSettings::SLEEP_TIMEOUT_COUNT; i++) {
options.push_back(CrossPointSettings::getSleepTimeoutString(i));
}
}
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()) {
selectorIndex = SETTINGS.sleepTimeout;
} else {