mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
feat: add sleep screen selection
This commit is contained in:
parent
78ebcf7856
commit
2d8e283b7b
@ -26,9 +26,9 @@ constexpr uint8_t SETTINGS_COUNT = 24;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
|
||||
const uint8_t SCREEN_MARGIN_PIXEL_VALUES[CrossPointSettings::SCREEN_MARGIN_COUNT] = {
|
||||
#define X(val, str) val,
|
||||
#define X(val, str) val,
|
||||
SCREEN_MARGIN_DATA
|
||||
#undef X
|
||||
#undef X
|
||||
};
|
||||
|
||||
int screenMarginPixelToIndex(uint8_t pixelValue) {
|
||||
@ -294,27 +294,27 @@ int CrossPointSettings::getReaderFontId() const {
|
||||
|
||||
namespace {
|
||||
const char* const REFRESH_FREQUENCY_OPTIONS[] = {
|
||||
#define X(name, val, str) str,
|
||||
#define X(name, val, str) str,
|
||||
REFRESH_DATA
|
||||
#undef X
|
||||
#undef X
|
||||
};
|
||||
|
||||
const char* const SLEEP_SCREEN_OPTIONS[] = {
|
||||
#define X(name, val, str) str,
|
||||
#define X(name, val, str) str,
|
||||
SLEEP_SCREEN_DATA
|
||||
#undef X
|
||||
#undef X
|
||||
};
|
||||
|
||||
const char* const SLEEP_TIMEOUT_OPTIONS[] = {
|
||||
#define X(name, val, str) str,
|
||||
#define X(name, val, str) str,
|
||||
TIMEOUT_DATA
|
||||
#undef X
|
||||
#undef X
|
||||
};
|
||||
|
||||
const char* const SCREEN_MARGIN_OPTIONS[] = {
|
||||
#define X(val, str) str,
|
||||
#define X(val, str) str,
|
||||
SCREEN_MARGIN_DATA
|
||||
#undef X
|
||||
#undef X
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@ -352,4 +352,3 @@ uint8_t CrossPointSettings::getScreenMarginPixels() const {
|
||||
}
|
||||
return SCREEN_MARGIN_PIXEL_VALUES[MARGIN_5];
|
||||
}
|
||||
|
||||
|
||||
@ -15,18 +15,18 @@ class CrossPointSettings {
|
||||
CrossPointSettings(const CrossPointSettings&) = delete;
|
||||
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
|
||||
|
||||
#define SLEEP_SCREEN_DATA \
|
||||
X(DARK, 0, "Dark") \
|
||||
X(LIGHT, 1, "Light") \
|
||||
X(CUSTOM, 2, "Custom") \
|
||||
X(COVER, 3, "Cover") \
|
||||
X(BLANK, 4, "None")
|
||||
#define SLEEP_SCREEN_DATA \
|
||||
X(DARK, 0, "Dark") \
|
||||
X(LIGHT, 1, "Light") \
|
||||
X(CUSTOM, 2, "Custom") \
|
||||
X(COVER, 3, "Cover") \
|
||||
X(BLANK, 4, "None")
|
||||
|
||||
enum SLEEP_SCREEN_MODE {
|
||||
#define X(name, val, str) name = val,
|
||||
SLEEP_SCREEN_DATA
|
||||
#undef X
|
||||
SLEEP_SCREEN_MODE_COUNT
|
||||
#define X(name, val, str) name = val,
|
||||
SLEEP_SCREEN_DATA
|
||||
#undef X
|
||||
SLEEP_SCREEN_MODE_COUNT
|
||||
};
|
||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT };
|
||||
enum SLEEP_SCREEN_COVER_FILTER {
|
||||
@ -83,46 +83,46 @@ class CrossPointSettings {
|
||||
PARAGRAPH_ALIGNMENT_COUNT
|
||||
};
|
||||
|
||||
// E-ink refresh frequency (pages between full refreshes)
|
||||
#define REFRESH_DATA \
|
||||
X(REFRESH_1, 0, "1 page") \
|
||||
X(REFRESH_5, 1, "5 pages") \
|
||||
X(REFRESH_10, 2, "10 pages") \
|
||||
X(REFRESH_15, 3, "15 pages") \
|
||||
X(REFRESH_30, 4, "30 pages")
|
||||
// E-ink refresh frequency (pages between full refreshes)
|
||||
#define REFRESH_DATA \
|
||||
X(REFRESH_1, 0, "1 page") \
|
||||
X(REFRESH_5, 1, "5 pages") \
|
||||
X(REFRESH_10, 2, "10 pages") \
|
||||
X(REFRESH_15, 3, "15 pages") \
|
||||
X(REFRESH_30, 4, "30 pages")
|
||||
|
||||
enum REFRESH_FREQUENCY {
|
||||
#define X(name, val, str) name = val,
|
||||
REFRESH_DATA
|
||||
#undef X
|
||||
REFRESH_FREQUENCY_COUNT
|
||||
#define X(name, val, str) name = val,
|
||||
REFRESH_DATA
|
||||
#undef X
|
||||
REFRESH_FREQUENCY_COUNT
|
||||
};
|
||||
|
||||
// Auto-sleep timeout options (in minutes)
|
||||
#define TIMEOUT_DATA \
|
||||
X(SLEEP_1_MIN, 0, "1 min") \
|
||||
X(SLEEP_5_MIN, 1, "5 min") \
|
||||
X(SLEEP_10_MIN, 2, "10 min") \
|
||||
X(SLEEP_15_MIN, 3, "15 min") \
|
||||
X(SLEEP_30_MIN, 4, "30 min")
|
||||
// Auto-sleep timeout options (in minutes)
|
||||
#define TIMEOUT_DATA \
|
||||
X(SLEEP_1_MIN, 0, "1 min") \
|
||||
X(SLEEP_5_MIN, 1, "5 min") \
|
||||
X(SLEEP_10_MIN, 2, "10 min") \
|
||||
X(SLEEP_15_MIN, 3, "15 min") \
|
||||
X(SLEEP_30_MIN, 4, "30 min")
|
||||
|
||||
enum SLEEP_TIMEOUT {
|
||||
#define X(name, val, str) name = val,
|
||||
TIMEOUT_DATA
|
||||
#undef X
|
||||
SLEEP_TIMEOUT_COUNT
|
||||
#define X(name, val, str) name = val,
|
||||
TIMEOUT_DATA
|
||||
#undef X
|
||||
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")
|
||||
// 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
|
||||
#define X(val, str) MARGIN_##val,
|
||||
SCREEN_MARGIN_DATA
|
||||
#undef X
|
||||
SCREEN_MARGIN_COUNT
|
||||
};
|
||||
|
||||
// Short power button press actions
|
||||
|
||||
@ -22,10 +22,10 @@ void ListSelectionActivity::onEnter() {
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
enterTime = millis();
|
||||
|
||||
|
||||
// Load items (allows subclasses to populate data)
|
||||
loadItems();
|
||||
|
||||
|
||||
// Ensure selector index is valid
|
||||
const size_t itemCount = getItemCount();
|
||||
if (selectorIndex >= itemCount && itemCount > 0) {
|
||||
@ -34,8 +34,7 @@ void ListSelectionActivity::onEnter() {
|
||||
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&ListSelectionActivity::taskTrampoline, "ListSelectionTask", 2048, this, 1,
|
||||
&displayTaskHandle);
|
||||
xTaskCreate(&ListSelectionActivity::taskTrampoline, "ListSelectionTask", 2048, this, 1, &displayTaskHandle);
|
||||
}
|
||||
|
||||
void ListSelectionActivity::onExit() {
|
||||
|
||||
@ -25,7 +25,7 @@ class ListSelectionActivity : public Activity {
|
||||
size_t selectorIndex = 0;
|
||||
bool updateRequired = false;
|
||||
unsigned long enterTime = 0;
|
||||
|
||||
|
||||
// Configuration
|
||||
std::string title;
|
||||
std::string emptyMessage;
|
||||
@ -51,14 +51,11 @@ class ListSelectionActivity : public Activity {
|
||||
|
||||
public:
|
||||
explicit ListSelectionActivity(const std::string& activityName, GfxRenderer& renderer,
|
||||
MappedInputManager& mappedInput, const std::string& title,
|
||||
std::function<size_t()> getItemCount,
|
||||
std::function<std::string(size_t)> getItemText,
|
||||
std::function<void(size_t)> onItemSelected,
|
||||
std::function<void()> onBack,
|
||||
const std::string& emptyMessage = "No items available",
|
||||
const std::string& backLabel = "« Back",
|
||||
const std::string& confirmLabel = "Select")
|
||||
MappedInputManager& mappedInput, const std::string& title,
|
||||
std::function<size_t()> getItemCount, std::function<std::string(size_t)> getItemText,
|
||||
std::function<void(size_t)> onItemSelected, std::function<void()> onBack,
|
||||
const std::string& emptyMessage = "No items available",
|
||||
const std::string& backLabel = "« Back", const std::string& confirmLabel = "Select")
|
||||
: Activity(activityName, renderer, mappedInput),
|
||||
title(title),
|
||||
emptyMessage(emptyMessage),
|
||||
@ -68,13 +65,13 @@ class ListSelectionActivity : public Activity {
|
||||
getItemText(getItemText),
|
||||
onItemSelected(onItemSelected),
|
||||
onBack(onBack) {}
|
||||
|
||||
|
||||
virtual ~ListSelectionActivity() = default;
|
||||
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
|
||||
|
||||
// Allow subclasses to set initial selection
|
||||
void setInitialSelection(size_t index) { selectorIndex = index; }
|
||||
size_t getCurrentSelection() const { return selectorIndex; }
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
#include "../Activity.h"
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include "../Activity.h"
|
||||
|
||||
class Bitmap;
|
||||
|
||||
class SleepActivity final : public Activity {
|
||||
|
||||
@ -71,7 +71,7 @@ void CategorySettingsActivity::loop() {
|
||||
if (visibleCount == 0) {
|
||||
return; // No visible settings
|
||||
}
|
||||
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
// Move to previous visible setting
|
||||
@ -213,8 +213,7 @@ bool CategorySettingsActivity::shouldShowSetting(int index) const {
|
||||
return false;
|
||||
}
|
||||
// Hide "Select Sleep BMP" if sleep screen is not set to CUSTOM
|
||||
if (settingsList[index].type == SettingType::ACTION &&
|
||||
strcmp(settingsList[index].name, "Select Sleep BMP") == 0) {
|
||||
if (settingsList[index].type == SettingType::ACTION && strcmp(settingsList[index].name, "Select Sleep BMP") == 0) {
|
||||
return SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM;
|
||||
}
|
||||
return true;
|
||||
@ -270,7 +269,7 @@ void CategorySettingsActivity::render() const {
|
||||
// Calculate visible settings count and map selection
|
||||
const int visibleCount = getVisibleSettingsCount();
|
||||
const int actualSelectedIndex = mapVisibleIndexToActualIndex(selectedSettingIndex);
|
||||
|
||||
|
||||
// Draw selection highlight
|
||||
int visibleIndex = 0;
|
||||
for (int i = 0; i < settingsCount; i++) {
|
||||
@ -289,7 +288,7 @@ void CategorySettingsActivity::render() const {
|
||||
if (!shouldShowSetting(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const int settingY = 60 + visibleIndex * 30; // 30 pixels between settings
|
||||
const bool isSelected = (i == actualSelectedIndex);
|
||||
|
||||
@ -329,7 +328,7 @@ void CategorySettingsActivity::render() const {
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected);
|
||||
}
|
||||
|
||||
|
||||
visibleIndex++;
|
||||
}
|
||||
|
||||
|
||||
@ -4,12 +4,12 @@
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
RefreshFrequencySelectionActivity::RefreshFrequencySelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
RefreshFrequencySelectionActivity::RefreshFrequencySelectionActivity(GfxRenderer& renderer,
|
||||
MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: ListSelectionActivity(
|
||||
"RefreshFrequencySelection", renderer, mappedInput, "Select Refresh Frequency",
|
||||
[this]() { return options.size(); },
|
||||
[this](size_t index) { return options[index]; },
|
||||
[this]() { return options.size(); }, [this](size_t index) { return options[index]; },
|
||||
[this, onBack](size_t index) {
|
||||
if (index >= options.size()) {
|
||||
return;
|
||||
|
||||
@ -7,8 +7,7 @@
|
||||
ScreenMarginSelectionActivity::ScreenMarginSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: ListSelectionActivity(
|
||||
"ScreenMarginSelection", renderer, mappedInput, "Select Screen Margin",
|
||||
[this]() { return options.size(); },
|
||||
"ScreenMarginSelection", renderer, mappedInput, "Select Screen Margin", [this]() { return options.size(); },
|
||||
[this](size_t index) { return options[index]; },
|
||||
[this, onBack](size_t index) {
|
||||
if (index >= options.size()) {
|
||||
|
||||
@ -13,5 +13,5 @@ class ScreenMarginSelectionActivity final : public ListSelectionActivity {
|
||||
|
||||
public:
|
||||
explicit ScreenMarginSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack);
|
||||
const std::function<void()>& onBack);
|
||||
};
|
||||
|
||||
@ -2,11 +2,10 @@
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include "CalibreSettingsActivity.h"
|
||||
#include "CategorySettingsActivity.h"
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
@ -16,7 +16,7 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
int selectedCategoryIndex = 0; // Currently selected category
|
||||
int selectedCategoryIndex = 0; // Currently selected category
|
||||
bool hasSleepBmpsCached = false; // Cached result of sleep BMP check
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
|
||||
@ -6,16 +6,15 @@
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "../../../lib/GfxRenderer/Bitmap.h"
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
namespace {
|
||||
void sortFileList(std::vector<std::string>& strs) {
|
||||
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
||||
return std::lexicographical_compare(begin(str1), end(str1), begin(str2), end(str2),
|
||||
[](const char& char1, const char& char2) {
|
||||
return std::tolower(char1) < std::tolower(char2);
|
||||
});
|
||||
return std::lexicographical_compare(
|
||||
begin(str1), end(str1), begin(str2), end(str2),
|
||||
[](const char& char1, const char& char2) { return std::tolower(char1) < std::tolower(char2); });
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
@ -23,8 +22,7 @@ void sortFileList(std::vector<std::string>& strs) {
|
||||
SleepBmpSelectionActivity::SleepBmpSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: ListSelectionActivity(
|
||||
"SleepBmpSelection", renderer, mappedInput, "Select Sleep BMP",
|
||||
[this]() { return files.size(); },
|
||||
"SleepBmpSelection", renderer, mappedInput, "Select Sleep BMP", [this]() { return files.size(); },
|
||||
[this](size_t index) { return files[index]; },
|
||||
[this, onBack](size_t index) {
|
||||
if (index >= files.size()) {
|
||||
@ -45,29 +43,28 @@ SleepBmpSelectionActivity::SleepBmpSelectionActivity(GfxRenderer& renderer, Mapp
|
||||
|
||||
void SleepBmpSelectionActivity::loadFiles() {
|
||||
files.clear();
|
||||
|
||||
|
||||
std::vector<std::string> bmpFiles;
|
||||
|
||||
|
||||
auto dir = SdMan.open("/sleep");
|
||||
if (dir && dir.isDirectory()) {
|
||||
dir.rewindDirectory();
|
||||
char name[500];
|
||||
|
||||
|
||||
for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) {
|
||||
if (file.isDirectory()) {
|
||||
file.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
file.getName(name, sizeof(name));
|
||||
auto filename = std::string(name);
|
||||
|
||||
if (filename[0] == '.' || filename.length() < 4 ||
|
||||
filename.substr(filename.length() - 4) != ".bmp") {
|
||||
|
||||
if (filename[0] == '.' || filename.length() < 4 || filename.substr(filename.length() - 4) != ".bmp") {
|
||||
file.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Validate BMP
|
||||
Bitmap bitmap(file);
|
||||
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
|
||||
@ -75,15 +72,15 @@ void SleepBmpSelectionActivity::loadFiles() {
|
||||
continue;
|
||||
}
|
||||
file.close();
|
||||
|
||||
|
||||
bmpFiles.emplace_back(filename);
|
||||
}
|
||||
dir.close();
|
||||
|
||||
|
||||
// Sort alphabetically (case-insensitive)
|
||||
sortFileList(bmpFiles);
|
||||
}
|
||||
|
||||
|
||||
// Add "Random" as first option, then sorted BMP files
|
||||
files.emplace_back("Random");
|
||||
files.insert(files.end(), bmpFiles.begin(), bmpFiles.end());
|
||||
@ -91,7 +88,7 @@ void SleepBmpSelectionActivity::loadFiles() {
|
||||
|
||||
void SleepBmpSelectionActivity::loadItems() {
|
||||
loadFiles();
|
||||
|
||||
|
||||
// Set initial selection based on saved setting
|
||||
if (SETTINGS.selectedSleepBmp[0] == '\0') {
|
||||
selectorIndex = 0; // "Random" is at index 0
|
||||
@ -111,4 +108,3 @@ void SleepBmpSelectionActivity::onExit() {
|
||||
ListSelectionActivity::onExit();
|
||||
files.clear();
|
||||
}
|
||||
|
||||
|
||||
@ -17,4 +17,3 @@ class SleepBmpSelectionActivity final : public ListSelectionActivity {
|
||||
const std::function<void()>& onBack);
|
||||
void onExit() override;
|
||||
};
|
||||
|
||||
|
||||
@ -7,8 +7,7 @@
|
||||
SleepScreenSelectionActivity::SleepScreenSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: ListSelectionActivity(
|
||||
"SleepScreenSelection", renderer, mappedInput, "Select Sleep Screen",
|
||||
[this]() { return options.size(); },
|
||||
"SleepScreenSelection", renderer, mappedInput, "Select Sleep Screen", [this]() { return options.size(); },
|
||||
[this](size_t index) { return options[index]; },
|
||||
[this, onBack](size_t index) {
|
||||
if (index >= options.size()) {
|
||||
|
||||
@ -5,10 +5,9 @@
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
SleepTimeoutSelectionActivity::SleepTimeoutSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
const std::function<void()>& onBack)
|
||||
: ListSelectionActivity(
|
||||
"SleepTimeoutSelection", renderer, mappedInput, "Select Time to Sleep",
|
||||
[this]() { return options.size(); },
|
||||
"SleepTimeoutSelection", renderer, mappedInput, "Select Time to Sleep", [this]() { return options.size(); },
|
||||
[this](size_t index) { return options[index]; },
|
||||
[this, onBack](size_t index) {
|
||||
if (index >= options.size()) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user