mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 07:37:37 +03:00
feat: add sleep screen selection
This commit is contained in:
parent
5c739fa530
commit
77ae2a8dc0
@ -244,3 +244,45 @@ int CrossPointSettings::getReaderFontId() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* CrossPointSettings::getRefreshFrequencyString(uint8_t value) {
|
||||||
|
static const char* options[] = {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"};
|
||||||
|
static constexpr size_t count = sizeof(options) / sizeof(options[0]);
|
||||||
|
if (value < count) {
|
||||||
|
return options[value];
|
||||||
|
}
|
||||||
|
return options[REFRESH_15]; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CrossPointSettings::getRefreshFrequencyCount() {
|
||||||
|
static const char* options[] = {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"};
|
||||||
|
return sizeof(options) / sizeof(options[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* CrossPointSettings::getSleepScreenString(uint8_t value) {
|
||||||
|
static const char* options[] = {"Dark", "Light", "Custom", "Cover", "None"};
|
||||||
|
static constexpr size_t count = sizeof(options) / sizeof(options[0]);
|
||||||
|
if (value < count) {
|
||||||
|
return options[value];
|
||||||
|
}
|
||||||
|
return options[DARK]; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CrossPointSettings::getSleepScreenCount() {
|
||||||
|
static const char* options[] = {"Dark", "Light", "Custom", "Cover", "None"};
|
||||||
|
return sizeof(options) / sizeof(options[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* CrossPointSettings::getSleepTimeoutString(uint8_t value) {
|
||||||
|
static const char* options[] = {"1 min", "5 min", "10 min", "15 min", "30 min"};
|
||||||
|
static constexpr size_t count = sizeof(options) / sizeof(options[0]);
|
||||||
|
if (value < count) {
|
||||||
|
return options[value];
|
||||||
|
}
|
||||||
|
return options[SLEEP_10_MIN]; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CrossPointSettings::getSleepTimeoutCount() {
|
||||||
|
static const char* options[] = {"1 min", "5 min", "10 min", "15 min", "30 min"};
|
||||||
|
return sizeof(options) / sizeof(options[0]);
|
||||||
|
}
|
||||||
|
|||||||
@ -110,6 +110,14 @@ class CrossPointSettings {
|
|||||||
bool saveToFile() const;
|
bool saveToFile() const;
|
||||||
bool loadFromFile();
|
bool loadFromFile();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
float getReaderLineCompression() const;
|
float getReaderLineCompression() const;
|
||||||
unsigned long getSleepTimeoutMs() const;
|
unsigned long getSleepTimeoutMs() const;
|
||||||
int getRefreshFrequency() const;
|
int getRefreshFrequency() const;
|
||||||
|
|||||||
162
src/activities/ListSelectionActivity.cpp
Normal file
162
src/activities/ListSelectionActivity.cpp
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#include "ListSelectionActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include "MappedInputManager.h"
|
||||||
|
#include "fontIds.h"
|
||||||
|
|
||||||
|
void ListSelectionActivity::taskTrampoline(void* param) {
|
||||||
|
auto* self = static_cast<ListSelectionActivity*>(param);
|
||||||
|
self->displayTaskLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ListSelectionActivity::getPageItems() const {
|
||||||
|
const int screenHeight = renderer.getScreenHeight();
|
||||||
|
const int availableHeight = screenHeight - START_Y - BOTTOM_BAR_HEIGHT;
|
||||||
|
const int pageItems = (availableHeight / LINE_HEIGHT);
|
||||||
|
return pageItems > 0 ? pageItems : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListSelectionActivity::onEnter() {
|
||||||
|
Activity::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) {
|
||||||
|
selectorIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRequired = true;
|
||||||
|
|
||||||
|
xTaskCreate(&ListSelectionActivity::taskTrampoline, "ListSelectionTask", 2048, this, 1,
|
||||||
|
&displayTaskHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListSelectionActivity::onExit() {
|
||||||
|
Activity::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 ListSelectionActivity::loop() {
|
||||||
|
const unsigned long timeSinceEnter = millis() - enterTime;
|
||||||
|
if (timeSinceEnter < IGNORE_INPUT_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t itemCount = getItemCount();
|
||||||
|
if (itemCount == 0) {
|
||||||
|
// Handle back button even when empty
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
|
onBack();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
||||||
|
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||||
|
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
||||||
|
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
|
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
||||||
|
const int pageItems = getPageItems();
|
||||||
|
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
|
if (selectorIndex < itemCount) {
|
||||||
|
onItemSelected(selectorIndex);
|
||||||
|
}
|
||||||
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
|
onBack();
|
||||||
|
} else if (prevReleased) {
|
||||||
|
if (skipPage) {
|
||||||
|
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + itemCount) % itemCount;
|
||||||
|
} else {
|
||||||
|
selectorIndex = (selectorIndex + itemCount - 1) % itemCount;
|
||||||
|
}
|
||||||
|
updateRequired = true;
|
||||||
|
} else if (nextReleased) {
|
||||||
|
if (skipPage) {
|
||||||
|
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % itemCount;
|
||||||
|
} else {
|
||||||
|
selectorIndex = (selectorIndex + 1) % itemCount;
|
||||||
|
}
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListSelectionActivity::displayTaskLoop() {
|
||||||
|
while (true) {
|
||||||
|
if (updateRequired) {
|
||||||
|
updateRequired = false;
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
render();
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
}
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListSelectionActivity::render() const {
|
||||||
|
renderer.clearScreen();
|
||||||
|
|
||||||
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, title.c_str(), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
|
// Help text
|
||||||
|
const auto labels = mappedInput.mapLabels(backLabel.c_str(), confirmLabel.c_str(), "", "");
|
||||||
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
|
const size_t itemCount = getItemCount();
|
||||||
|
if (itemCount == 0) {
|
||||||
|
renderer.drawText(UI_10_FONT_ID, 20, START_Y, emptyMessage.c_str());
|
||||||
|
renderer.displayBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate items per page based on screen height
|
||||||
|
const int screenHeight = renderer.getScreenHeight();
|
||||||
|
const int availableHeight = screenHeight - START_Y - BOTTOM_BAR_HEIGHT;
|
||||||
|
const int pageItems = (availableHeight / LINE_HEIGHT);
|
||||||
|
|
||||||
|
// Calculate page start index
|
||||||
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||||
|
|
||||||
|
// Draw selection highlight
|
||||||
|
const int visibleSelectedIndex = static_cast<int>(selectorIndex - pageStartIndex);
|
||||||
|
if (visibleSelectedIndex >= 0 && visibleSelectedIndex < pageItems && selectorIndex < itemCount) {
|
||||||
|
renderer.fillRect(0, START_Y + visibleSelectedIndex * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw visible items
|
||||||
|
int visibleIndex = 0;
|
||||||
|
for (size_t i = pageStartIndex; i < itemCount && visibleIndex < pageItems; i++) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
visibleIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.displayBuffer();
|
||||||
|
}
|
||||||
88
src/activities/ListSelectionActivity.h
Normal file
88
src/activities/ListSelectionActivity.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Activity.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ListSelectionActivity is a reusable base class for activities that display
|
||||||
|
* a scrollable list of items with selection capabilities.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - 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:
|
||||||
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
size_t selectorIndex = 0;
|
||||||
|
bool updateRequired = false;
|
||||||
|
unsigned long enterTime = 0;
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
std::string title;
|
||||||
|
std::string emptyMessage;
|
||||||
|
std::string backLabel;
|
||||||
|
std::string confirmLabel;
|
||||||
|
std::function<size_t()> getItemCount;
|
||||||
|
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;
|
||||||
|
static constexpr int LINE_HEIGHT = 30;
|
||||||
|
static constexpr int START_Y = 60;
|
||||||
|
static constexpr int BOTTOM_BAR_HEIGHT = 60;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* param);
|
||||||
|
[[noreturn]] void displayTaskLoop();
|
||||||
|
void render() const;
|
||||||
|
int getPageItems() const;
|
||||||
|
virtual void loadItems() {} // Override to load items on enter
|
||||||
|
|
||||||
|
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")
|
||||||
|
: Activity(activityName, renderer, mappedInput),
|
||||||
|
title(title),
|
||||||
|
emptyMessage(emptyMessage),
|
||||||
|
backLabel(backLabel),
|
||||||
|
confirmLabel(confirmLabel),
|
||||||
|
getItemCount(getItemCount),
|
||||||
|
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; }
|
||||||
|
|
||||||
|
// Allow custom item rendering
|
||||||
|
void setCustomItemRenderer(std::function<void(size_t, int, int, bool)> renderer) {
|
||||||
|
customRenderItem = renderer;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -11,7 +11,11 @@
|
|||||||
#include "KOReaderSettingsActivity.h"
|
#include "KOReaderSettingsActivity.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "OtaUpdateActivity.h"
|
#include "OtaUpdateActivity.h"
|
||||||
|
#include "RefreshFrequencySelectionActivity.h"
|
||||||
|
#include "ScreenMarginSelectionActivity.h"
|
||||||
#include "SleepBmpSelectionActivity.h"
|
#include "SleepBmpSelectionActivity.h"
|
||||||
|
#include "SleepScreenSelectionActivity.h"
|
||||||
|
#include "SleepTimeoutSelectionActivity.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
void CategorySettingsActivity::taskTrampoline(void* param) {
|
void CategorySettingsActivity::taskTrampoline(void* param) {
|
||||||
@ -104,16 +108,6 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
||||||
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
|
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||||
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
||||||
|
|
||||||
// If sleep screen changed away from CUSTOM, adjust selection if needed
|
|
||||||
if (setting.valuePtr == &CrossPointSettings::sleepScreen) {
|
|
||||||
const int visibleCount = getVisibleSettingsCount();
|
|
||||||
// If current selection is now hidden or out of bounds, adjust it
|
|
||||||
const int currentActual = mapVisibleIndexToActualIndex(selectedSettingIndex);
|
|
||||||
if (!shouldShowSetting(currentActual) || selectedSettingIndex >= visibleCount) {
|
|
||||||
selectedSettingIndex = visibleCount > 0 ? visibleCount - 1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
||||||
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
|
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||||
if (currentValue + setting.valueRange.step > setting.valueRange.max) {
|
if (currentValue + setting.valueRange.step > setting.valueRange.max) {
|
||||||
@ -154,6 +148,38 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
|
} else if (strcmp(setting.name, "Sleep Screen") == 0) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new SleepScreenSelectionActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
} else if (strcmp(setting.name, "Refresh Frequency") == 0) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new RefreshFrequencySelectionActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
} else if (strcmp(setting.name, "Screen Margin") == 0) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new ScreenMarginSelectionActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
} else if (strcmp(setting.name, "Time to Sleep") == 0) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new SleepTimeoutSelectionActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
} else if (strcmp(setting.name, "Select Sleep BMP") == 0) {
|
} else if (strcmp(setting.name, "Select Sleep BMP") == 0) {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
@ -280,6 +306,15 @@ void CategorySettingsActivity::render() const {
|
|||||||
valueText = settingsList[i].enumValues[value];
|
valueText = settingsList[i].enumValues[value];
|
||||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||||
|
} else if (settingsList[i].type == SettingType::ACTION && strcmp(settingsList[i].name, "Sleep Screen") == 0) {
|
||||||
|
valueText = CrossPointSettings::getSleepScreenString(SETTINGS.sleepScreen);
|
||||||
|
} else if (settingsList[i].type == SettingType::ACTION && strcmp(settingsList[i].name, "Refresh Frequency") == 0) {
|
||||||
|
valueText = CrossPointSettings::getRefreshFrequencyString(SETTINGS.refreshFrequency);
|
||||||
|
} else if (settingsList[i].type == SettingType::ACTION && strcmp(settingsList[i].name, "Screen Margin") == 0) {
|
||||||
|
// Format margin value as "X px"
|
||||||
|
valueText = std::to_string(SETTINGS.screenMargin) + " px";
|
||||||
|
} else if (settingsList[i].type == SettingType::ACTION && strcmp(settingsList[i].name, "Time to Sleep") == 0) {
|
||||||
|
valueText = CrossPointSettings::getSleepTimeoutString(SETTINGS.sleepTimeout);
|
||||||
} else if (settingsList[i].type == SettingType::ACTION && strcmp(settingsList[i].name, "Select Sleep BMP") == 0) {
|
} else if (settingsList[i].type == SettingType::ACTION && strcmp(settingsList[i].name, "Select Sleep BMP") == 0) {
|
||||||
if (SETTINGS.selectedSleepBmp[0] != '\0') {
|
if (SETTINGS.selectedSleepBmp[0] != '\0') {
|
||||||
valueText = SETTINGS.selectedSleepBmp;
|
valueText = SETTINGS.selectedSleepBmp;
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
#include "RefreshFrequencySelectionActivity.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
|
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, onBack](size_t index) {
|
||||||
|
if (index >= options.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Map option index to enum value (index matches enum value)
|
||||||
|
SETTINGS.refreshFrequency = static_cast<uint8_t>(index);
|
||||||
|
SETTINGS.saveToFile();
|
||||||
|
onBack();
|
||||||
|
},
|
||||||
|
onBack, "No options available") {
|
||||||
|
// Initialize options from enum
|
||||||
|
for (uint8_t i = 0; i < CrossPointSettings::getRefreshFrequencyCount(); 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 {
|
||||||
|
selectorIndex = 3; // Default to "15 pages" (REFRESH_15)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/activities/settings/RefreshFrequencySelectionActivity.h
Normal file
17
src/activities/settings/RefreshFrequencySelectionActivity.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../ListSelectionActivity.h"
|
||||||
|
|
||||||
|
class RefreshFrequencySelectionActivity final : public ListSelectionActivity {
|
||||||
|
std::vector<std::string> options; // Refresh frequency options
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void loadItems() override; // Called by base class onEnter
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RefreshFrequencySelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
|
const std::function<void()>& onBack);
|
||||||
|
};
|
||||||
47
src/activities/settings/ScreenMarginSelectionActivity.cpp
Normal file
47
src/activities/settings/ScreenMarginSelectionActivity.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "ScreenMarginSelectionActivity.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
|
ScreenMarginSelectionActivity::ScreenMarginSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
|
const std::function<void()>& onBack)
|
||||||
|
: ListSelectionActivity(
|
||||||
|
"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()) {
|
||||||
|
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.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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectorIndex = 0; // Default to "5 px"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/activities/settings/ScreenMarginSelectionActivity.h
Normal file
17
src/activities/settings/ScreenMarginSelectionActivity.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../ListSelectionActivity.h"
|
||||||
|
|
||||||
|
class ScreenMarginSelectionActivity final : public ListSelectionActivity {
|
||||||
|
std::vector<std::string> options; // Screen margin options
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void loadItems() override; // Called by base class onEnter
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ScreenMarginSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
|
const std::function<void()>& onBack);
|
||||||
|
};
|
||||||
@ -21,20 +21,19 @@ namespace {
|
|||||||
constexpr int displaySettingsCount = 6;
|
constexpr int displaySettingsCount = 6;
|
||||||
const SettingInfo displaySettings[displaySettingsCount] = {
|
const SettingInfo displaySettings[displaySettingsCount] = {
|
||||||
// Should match with SLEEP_SCREEN_MODE
|
// Should match with SLEEP_SCREEN_MODE
|
||||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
SettingInfo::Action("Sleep Screen"),
|
||||||
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
||||||
SettingInfo::Action("Select Sleep BMP"),
|
SettingInfo::Action("Select Sleep BMP"),
|
||||||
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
||||||
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
||||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
SettingInfo::Action("Refresh Frequency")};
|
||||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})};
|
|
||||||
|
|
||||||
constexpr int readerSettingsCount = 9;
|
constexpr int readerSettingsCount = 9;
|
||||||
const SettingInfo readerSettings[readerSettingsCount] = {
|
const SettingInfo readerSettings[readerSettingsCount] = {
|
||||||
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
||||||
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
|
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
|
||||||
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
|
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
|
||||||
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
|
SettingInfo::Action("Screen Margin"),
|
||||||
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
||||||
{"Justify", "Left", "Center", "Right"}),
|
{"Justify", "Left", "Center", "Right"}),
|
||||||
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
|
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
|
||||||
@ -54,8 +53,7 @@ const SettingInfo controlsSettings[controlsSettingsCount] = {
|
|||||||
|
|
||||||
constexpr int systemSettingsCount = 5;
|
constexpr int systemSettingsCount = 5;
|
||||||
const SettingInfo systemSettings[systemSettingsCount] = {
|
const SettingInfo systemSettings[systemSettingsCount] = {
|
||||||
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
SettingInfo::Action("Time to Sleep"),
|
||||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
|
||||||
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
||||||
SettingInfo::Action("Check for updates")};
|
SettingInfo::Action("Check for updates")};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@ -1,25 +1,15 @@
|
|||||||
#include "SleepBmpSelectionActivity.h"
|
#include "SleepBmpSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
|
||||||
#include "fontIds.h"
|
|
||||||
#include "util/StringUtils.h"
|
|
||||||
|
|
||||||
#include "../../../lib/GfxRenderer/Bitmap.h"
|
#include "../../../lib/GfxRenderer/Bitmap.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
|
||||||
constexpr unsigned long IGNORE_INPUT_MS = 300; // Ignore input for 300ms after entering
|
|
||||||
constexpr int LINE_HEIGHT = 30;
|
|
||||||
constexpr int START_Y = 60;
|
|
||||||
constexpr int BOTTOM_BAR_HEIGHT = 60; // Space for button hints
|
|
||||||
|
|
||||||
void sortFileList(std::vector<std::string>& strs) {
|
void sortFileList(std::vector<std::string>& strs) {
|
||||||
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
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),
|
return std::lexicographical_compare(begin(str1), end(str1), begin(str2), end(str2),
|
||||||
@ -30,10 +20,28 @@ void sortFileList(std::vector<std::string>& strs) {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void SleepBmpSelectionActivity::taskTrampoline(void* param) {
|
SleepBmpSelectionActivity::SleepBmpSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
auto* self = static_cast<SleepBmpSelectionActivity*>(param);
|
const std::function<void()>& onBack)
|
||||||
self->displayTaskLoop();
|
: ListSelectionActivity(
|
||||||
}
|
"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()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string selectedFile = files[index];
|
||||||
|
if (selectedFile == "Random") {
|
||||||
|
// Clear the selection to use random
|
||||||
|
SETTINGS.selectedSleepBmp[0] = '\0';
|
||||||
|
} else {
|
||||||
|
strncpy(SETTINGS.selectedSleepBmp, selectedFile.c_str(), sizeof(SETTINGS.selectedSleepBmp) - 1);
|
||||||
|
SETTINGS.selectedSleepBmp[sizeof(SETTINGS.selectedSleepBmp) - 1] = '\0';
|
||||||
|
}
|
||||||
|
SETTINGS.saveToFile();
|
||||||
|
onBack();
|
||||||
|
},
|
||||||
|
onBack, "No BMP files found in /sleep") {}
|
||||||
|
|
||||||
void SleepBmpSelectionActivity::loadFiles() {
|
void SleepBmpSelectionActivity::loadFiles() {
|
||||||
files.clear();
|
files.clear();
|
||||||
@ -81,14 +89,10 @@ void SleepBmpSelectionActivity::loadFiles() {
|
|||||||
files.insert(files.end(), bmpFiles.begin(), bmpFiles.end());
|
files.insert(files.end(), bmpFiles.begin(), bmpFiles.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SleepBmpSelectionActivity::onEnter() {
|
void SleepBmpSelectionActivity::loadItems() {
|
||||||
Activity::onEnter();
|
|
||||||
|
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
|
||||||
|
|
||||||
loadFiles();
|
loadFiles();
|
||||||
|
|
||||||
// Set initial selection: "Random" if no file selected, otherwise find the selected file
|
// Set initial selection based on saved setting
|
||||||
if (SETTINGS.selectedSleepBmp[0] == '\0') {
|
if (SETTINGS.selectedSleepBmp[0] == '\0') {
|
||||||
selectorIndex = 0; // "Random" is at index 0
|
selectorIndex = 0; // "Random" is at index 0
|
||||||
} else {
|
} else {
|
||||||
@ -101,150 +105,10 @@ void SleepBmpSelectionActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enterTime = millis();
|
|
||||||
|
|
||||||
updateRequired = true;
|
|
||||||
|
|
||||||
xTaskCreate(&SleepBmpSelectionActivity::taskTrampoline, "SleepBmpSelectionActivityTask",
|
|
||||||
2048, // Stack size
|
|
||||||
this, // Parameters
|
|
||||||
1, // Priority
|
|
||||||
&displayTaskHandle // Task handle
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SleepBmpSelectionActivity::onExit() {
|
void SleepBmpSelectionActivity::onExit() {
|
||||||
Activity::onExit();
|
ListSelectionActivity::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;
|
|
||||||
files.clear();
|
files.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SleepBmpSelectionActivity::loop() {
|
|
||||||
const unsigned long timeSinceEnter = millis() - enterTime;
|
|
||||||
if (timeSinceEnter < IGNORE_INPUT_MS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
|
||||||
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
|
||||||
|
|
||||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
|
||||||
if (files.empty() || selectorIndex >= files.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string selectedFile = files[selectorIndex];
|
|
||||||
if (selectedFile == "Random") {
|
|
||||||
// Clear the selection to use random
|
|
||||||
SETTINGS.selectedSleepBmp[0] = '\0';
|
|
||||||
} else {
|
|
||||||
strncpy(SETTINGS.selectedSleepBmp, selectedFile.c_str(), sizeof(SETTINGS.selectedSleepBmp) - 1);
|
|
||||||
SETTINGS.selectedSleepBmp[sizeof(SETTINGS.selectedSleepBmp) - 1] = '\0';
|
|
||||||
}
|
|
||||||
SETTINGS.saveToFile();
|
|
||||||
|
|
||||||
onBack();
|
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
|
||||||
onBack();
|
|
||||||
} else if (prevReleased) {
|
|
||||||
if (files.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate items per page dynamically
|
|
||||||
const int screenHeight = renderer.getScreenHeight();
|
|
||||||
const int availableHeight = screenHeight - START_Y - BOTTOM_BAR_HEIGHT;
|
|
||||||
const int pageItems = (availableHeight / LINE_HEIGHT);
|
|
||||||
|
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + files.size()) % files.size();
|
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + files.size() - 1) % files.size();
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (nextReleased) {
|
|
||||||
if (files.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate items per page dynamically
|
|
||||||
const int screenHeight = renderer.getScreenHeight();
|
|
||||||
const int availableHeight = screenHeight - START_Y - BOTTOM_BAR_HEIGHT;
|
|
||||||
const int pageItems = (availableHeight / LINE_HEIGHT);
|
|
||||||
|
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % files.size();
|
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + 1) % files.size();
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SleepBmpSelectionActivity::displayTaskLoop() {
|
|
||||||
while (true) {
|
|
||||||
if (updateRequired) {
|
|
||||||
updateRequired = false;
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
||||||
render();
|
|
||||||
xSemaphoreGive(renderingMutex);
|
|
||||||
}
|
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SleepBmpSelectionActivity::render() const {
|
|
||||||
renderer.clearScreen();
|
|
||||||
|
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Sleep BMP", true, EpdFontFamily::BOLD);
|
|
||||||
|
|
||||||
// Help text
|
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
||||||
|
|
||||||
if (files.empty()) {
|
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, 60, "No BMP files found in /sleep");
|
|
||||||
renderer.displayBuffer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate items per page based on screen height
|
|
||||||
const int screenHeight = renderer.getScreenHeight();
|
|
||||||
const int availableHeight = screenHeight - START_Y - BOTTOM_BAR_HEIGHT;
|
|
||||||
const int pageItems = (availableHeight / LINE_HEIGHT);
|
|
||||||
|
|
||||||
// Calculate page start index
|
|
||||||
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
|
||||||
|
|
||||||
// Draw selection highlight
|
|
||||||
const int visibleSelectedIndex = static_cast<int>(selectorIndex - pageStartIndex);
|
|
||||||
if (visibleSelectedIndex >= 0 && visibleSelectedIndex < pageItems && selectorIndex < files.size()) {
|
|
||||||
renderer.fillRect(0, START_Y + visibleSelectedIndex * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw visible files
|
|
||||||
int visibleIndex = 0;
|
|
||||||
for (size_t i = pageStartIndex; i < files.size() && visibleIndex < pageItems; i++) {
|
|
||||||
auto item = renderer.truncatedText(UI_10_FONT_ID, files[i].c_str(), renderer.getScreenWidth() - 40);
|
|
||||||
const bool isSelected = (i == selectorIndex);
|
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, START_Y + visibleIndex * LINE_HEIGHT, item.c_str(), !isSelected);
|
|
||||||
visibleIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.displayBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -1,34 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/semphr.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../Activity.h"
|
#include "../ListSelectionActivity.h"
|
||||||
|
|
||||||
class SleepBmpSelectionActivity final : public Activity {
|
class SleepBmpSelectionActivity final : public ListSelectionActivity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
|
||||||
std::vector<std::string> files; // Sorted list of valid BMP filenames ("Random" at index 0)
|
std::vector<std::string> files; // Sorted list of valid BMP filenames ("Random" at index 0)
|
||||||
size_t selectorIndex = 0;
|
|
||||||
bool updateRequired = false;
|
|
||||||
unsigned long enterTime = 0; // Time when activity was entered
|
|
||||||
const std::function<void()> onBack;
|
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
|
||||||
[[noreturn]] void displayTaskLoop();
|
|
||||||
void render() const;
|
|
||||||
void loadFiles(); // Load and sort all valid BMP files
|
void loadFiles(); // Load and sort all valid BMP files
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void loadItems() override; // Called by base class onEnter
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SleepBmpSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
explicit SleepBmpSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::function<void()>& onBack)
|
const std::function<void()>& onBack);
|
||||||
: Activity("SleepBmpSelection", renderer, mappedInput), onBack(onBack) {}
|
|
||||||
void onEnter() override;
|
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
37
src/activities/settings/SleepScreenSelectionActivity.cpp
Normal file
37
src/activities/settings/SleepScreenSelectionActivity.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "SleepScreenSelectionActivity.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
|
SleepScreenSelectionActivity::SleepScreenSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
|
const std::function<void()>& onBack)
|
||||||
|
: ListSelectionActivity(
|
||||||
|
"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()) {
|
||||||
|
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++) {
|
||||||
|
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 {
|
||||||
|
selectorIndex = 0; // Default to "Dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/activities/settings/SleepScreenSelectionActivity.h
Normal file
17
src/activities/settings/SleepScreenSelectionActivity.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../ListSelectionActivity.h"
|
||||||
|
|
||||||
|
class SleepScreenSelectionActivity final : public ListSelectionActivity {
|
||||||
|
std::vector<std::string> options; // Sleep screen mode options
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void loadItems() override; // Called by base class onEnter
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SleepScreenSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
|
const std::function<void()>& onBack);
|
||||||
|
};
|
||||||
37
src/activities/settings/SleepTimeoutSelectionActivity.cpp
Normal file
37
src/activities/settings/SleepTimeoutSelectionActivity.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "SleepTimeoutSelectionActivity.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
|
SleepTimeoutSelectionActivity::SleepTimeoutSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
|
const std::function<void()>& onBack)
|
||||||
|
: ListSelectionActivity(
|
||||||
|
"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()) {
|
||||||
|
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++) {
|
||||||
|
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 {
|
||||||
|
selectorIndex = 2; // Default to "10 min" (SLEEP_10_MIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/activities/settings/SleepTimeoutSelectionActivity.h
Normal file
17
src/activities/settings/SleepTimeoutSelectionActivity.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../ListSelectionActivity.h"
|
||||||
|
|
||||||
|
class SleepTimeoutSelectionActivity final : public ListSelectionActivity {
|
||||||
|
std::vector<std::string> options;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void loadItems() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SleepTimeoutSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
|
const std::function<void()>& onBack);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user