Compare commits

...

4 Commits

Author SHA1 Message Date
Boris Faure
addd1c8617
Merge 5ae10a7eb6 into 78d6e5931c 2026-02-04 09:18:11 +11:00
Jake Kenneally
78d6e5931c
fix: Correct debugging_monitor.py script instructions (#676)
Some checks are pending
CI / build (push) Waiting to run
## Summary

**What is the goal of this PR?**
- Minor correction to the `debugging_monitor.py` script instructions

**What changes are included?**
- `pyserial` should be installed, NOT `serial`, which is a [different
lib](https://pypi.org/project/serial/)
- Added macOS serial port

## Additional Context

- Just a minor docs update. I can confirm the debugging script is
working great on macOS

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-02-04 00:33:20 +03:00
Luke Stein
dac11c3fdd
fix: Correct instruction text to match actual button text (#672)
## Summary

* Instruction text says "Press OK to scan again" but button label is
actually "Connect" (not OK)
* Corrects instruction text

---

### AI Usage

Did you use AI tools to help write this code? **No**
2026-02-04 00:32:52 +03:00
Boris Faure
5ae10a7eb6
feat: rework CrossPointSettings
Multiple goals are achieved with this change:
- make descriptions of settings close to their definitions
- settings validation on loading with reset to default in case of bad
  value
- do not boot-loop on bad string values
- (de)serialization is based on a single list of descriptors: single point
  of truth
- possibly to have default values for the String configuration type
- more constexpr to reduce RAM usage
- less hardcoded values
- more maintainable

RAM Usage:
 From
RAM:   [===       ]  32.5% (used 106516 bytes from 327680 bytes)
Flash: [========= ]  94.9% (used 6217240 bytes from 6553600 bytes)
 To
RAM:   [===       ]  32.3% (used 105844 bytes from 327680 bytes)
Flash: [========= ]  94.8% (used 6213378 bytes from 6553600 bytes)

Boot config validation with a test where the status bar config is wrong:
[1256] [SD] SD card detected
[1256] [CPS] Loading settings from file
[1265] [CPS] Invalid value (0x3) for Status Bar, resetting to default
[1265] [CPS] Settings loaded from file
2026-01-28 16:10:45 +01:00
9 changed files with 429 additions and 290 deletions

View File

@ -102,13 +102,18 @@ After flashing the new features, its recommended to capture detailed logs fro
First, make sure all required Python packages are installed: First, make sure all required Python packages are installed:
```python ```python
python3 -m pip install serial colorama matplotlib python3 -m pip install pyserial colorama matplotlib
``` ```
after that run the script: after that run the script:
```sh ```sh
# For Linux
# This was tested on Debian and should work on most Linux systems.
python3 scripts/debugging_monitor.py python3 scripts/debugging_monitor.py
# For macOS
python3 scripts/debugging_monitor.py /dev/cu.usbmodem2101
``` ```
This was tested on Debian and should work on most Linux systems. Minor adjustments may be required for Windows or macOS. Minor adjustments may be required for Windows.
## Internals ## Internals

View File

@ -49,4 +49,12 @@ static void readString(FsFile& file, std::string& s) {
s.resize(len); s.resize(len);
file.read(&s[0], len); file.read(&s[0], len);
} }
static void readString(FsFile& file, char* buffer, size_t maxLen) {
uint32_t len;
readPod(file, len);
const uint32_t bytesToRead = (len < maxLen - 1) ? len : (maxLen - 1);
file.read(reinterpret_cast<uint8_t*>(buffer), bytesToRead);
buffer[bytesToRead] = '\0';
}
} // namespace serialization } // namespace serialization

View File

@ -11,21 +11,132 @@
// Initialize the static instance // Initialize the static instance
CrossPointSettings CrossPointSettings::instance; CrossPointSettings CrossPointSettings::instance;
void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) { // SettingDescriptor implementations
uint8_t tempValue; bool SettingDescriptor::validate(const CrossPointSettings& settings) const {
serialization::readPod(file, tempValue); if (type == SettingType::STRING) {
if (tempValue < maxValue) { return true; // Strings are always valid
member = tempValue;
} }
if (!validator) {
return true; // No validator means always valid
}
const uint8_t value = settings.*(memberPtr);
return validator(value);
}
uint8_t SettingDescriptor::getValue(const CrossPointSettings& settings) const { return settings.*(memberPtr); }
void SettingDescriptor::setValue(CrossPointSettings& settings, uint8_t value) const { settings.*(memberPtr) = value; }
void SettingDescriptor::resetToDefault(CrossPointSettings& settings) const {
if (type == SettingType::STRING) {
strncpy(stringPtr, stringData.defaultString, stringData.maxSize - 1);
stringPtr[stringData.maxSize - 1] = '\0';
return;
}
setValue(settings, defaultValue);
}
void SettingDescriptor::save(FsFile& file, const CrossPointSettings& settings) const {
if (type == SettingType::STRING) {
serialization::writeString(file, std::string(stringPtr));
return;
}
serialization::writePod(file, settings.*(memberPtr));
}
void SettingDescriptor::load(FsFile& file, CrossPointSettings& settings) const {
if (type == SettingType::STRING) {
serialization::readString(file, stringPtr, stringData.maxSize);
return;
}
uint8_t value;
serialization::readPod(file, value);
settings.*(memberPtr) = value;
} }
namespace { namespace {
constexpr uint8_t SETTINGS_FILE_VERSION = 1; constexpr uint8_t SETTINGS_FILE_VERSION = 1;
// Increment this when adding new persisted settings fields
constexpr uint8_t SETTINGS_COUNT = 23;
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
} // namespace } // namespace
// Define enum value arrays
namespace {
constexpr const char* sleepScreenValues[] = {"Dark", "Light", "Custom", "Cover", "None"};
constexpr const char* shortPwrBtnValues[] = {"Ignore", "Sleep", "Page Turn"};
constexpr const char* statusBarValues[] = {"None", "No Progress", "Full"};
constexpr const char* orientationValues[] = {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"};
constexpr const char* frontButtonLayoutValues[] = {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm",
"Lft, Bck, Cnfrm, Rght", "Bck, Cnfrm, Rght, Lft"};
constexpr const char* sideButtonLayoutValues[] = {"Prev/Next", "Next/Prev"};
constexpr const char* fontFamilyValues[] = {"Bookerly", "Noto Sans", "Open Dyslexic"};
constexpr const char* fontSizeValues[] = {"Small", "Medium", "Large", "X Large"};
constexpr const char* lineSpacingValues[] = {"Tight", "Normal", "Wide"};
constexpr const char* paragraphAlignmentValues[] = {"Justify", "Left", "Center", "Right"};
constexpr const char* sleepTimeoutValues[] = {"1 min", "5 min", "10 min", "15 min", "30 min"};
constexpr const char* refreshFrequencyValues[] = {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"};
constexpr const char* sleepScreenCoverModeValues[] = {"Fit", "Crop"};
constexpr const char* hideBatteryPercentageValues[] = {"Never", "In Reader", "Always"};
constexpr const char* sleepScreenCoverFilterValues[] = {"None", "Contrast", "Inverted"};
// Helper function template to deduce array size automatically
template <size_t N>
constexpr SettingDescriptor makeEnumDescriptor(const char* name, uint8_t CrossPointSettings::* ptr,
uint8_t defaultValue, const char* const (&enumValues)[N]) {
return SettingDescriptor(name, SettingType::ENUM, ptr, defaultValue, validateEnum<N>, enumValues, N);
}
// Helper macro to create STRING descriptors without repetition
#define makeStringDescriptor(name, member, defStr) \
SettingDescriptor(name, SettingType::STRING, CrossPointSettings::instance.member, defStr, \
sizeof(CrossPointSettings::member))
} // namespace
// Define static constexpr members (required in C++14 and earlier)
constexpr size_t CrossPointSettings::DESCRIPTOR_COUNT;
// Define the static constexpr array of all setting descriptors
// Order must match current serialization order for file format compatibility!
const std::array<SettingDescriptor, CrossPointSettings::DESCRIPTOR_COUNT> CrossPointSettings::descriptors = {{
makeEnumDescriptor("Sleep Screen", &CrossPointSettings::sleepScreen, CrossPointSettings::DARK, sleepScreenValues),
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, 1, validateToggle,
nullptr, 0},
makeEnumDescriptor("Short Power Button Click", &CrossPointSettings::shortPwrBtn, CrossPointSettings::IGNORE,
shortPwrBtnValues),
makeEnumDescriptor("Status Bar", &CrossPointSettings::statusBar, CrossPointSettings::FULL, statusBarValues),
makeEnumDescriptor("Reading Orientation", &CrossPointSettings::orientation, CrossPointSettings::PORTRAIT,
orientationValues),
makeEnumDescriptor("Front Button Layout", &CrossPointSettings::frontButtonLayout,
CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT, frontButtonLayoutValues),
makeEnumDescriptor("Side Button Layout", &CrossPointSettings::sideButtonLayout, CrossPointSettings::PREV_NEXT,
sideButtonLayoutValues),
makeEnumDescriptor("Reader Font Family", &CrossPointSettings::fontFamily, CrossPointSettings::BOOKERLY,
fontFamilyValues),
makeEnumDescriptor("Reader Font Size", &CrossPointSettings::fontSize, CrossPointSettings::MEDIUM, fontSizeValues),
makeEnumDescriptor("Reader Line Spacing", &CrossPointSettings::lineSpacing, CrossPointSettings::NORMAL,
lineSpacingValues),
makeEnumDescriptor("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
CrossPointSettings::JUSTIFIED, paragraphAlignmentValues),
makeEnumDescriptor("Time to Sleep", &CrossPointSettings::sleepTimeout, CrossPointSettings::SLEEP_10_MIN,
sleepTimeoutValues),
makeEnumDescriptor("Refresh Frequency", &CrossPointSettings::refreshFrequency, CrossPointSettings::REFRESH_15,
refreshFrequencyValues),
{"Reader Screen Margin", SettingType::VALUE, &CrossPointSettings::screenMargin, 5, validateRange<5, 40>,
ValueRange{5, 40, 5}},
makeEnumDescriptor("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, CrossPointSettings::FIT,
sleepScreenCoverModeValues),
makeStringDescriptor("OPDS Server URL", opdsServerUrl, ""),
{"Text Anti-Aliasing", SettingType::TOGGLE, &CrossPointSettings::textAntiAliasing, 1, validateToggle, nullptr, 0},
makeEnumDescriptor("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, CrossPointSettings::HIDE_NEVER,
hideBatteryPercentageValues),
{"Long-press Chapter Skip", SettingType::TOGGLE, &CrossPointSettings::longPressChapterSkip, 1, validateToggle,
nullptr, 0},
{"Hyphenation", SettingType::TOGGLE, &CrossPointSettings::hyphenationEnabled, 0, validateToggle, nullptr, 0},
makeStringDescriptor("Username", opdsUsername, ""),
makeStringDescriptor("Password", opdsPassword, ""),
makeEnumDescriptor("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter,
CrossPointSettings::NO_FILTER, sleepScreenCoverFilterValues),
}};
bool CrossPointSettings::saveToFile() const { bool CrossPointSettings::saveToFile() const {
// Make sure the directory exists // Make sure the directory exists
SdMan.mkdir("/.crosspoint"); SdMan.mkdir("/.crosspoint");
@ -36,31 +147,15 @@ bool CrossPointSettings::saveToFile() const {
} }
serialization::writePod(outputFile, SETTINGS_FILE_VERSION); serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
serialization::writePod(outputFile, SETTINGS_COUNT); serialization::writePod(outputFile, static_cast<uint8_t>(CrossPointSettings::DESCRIPTOR_COUNT));
serialization::writePod(outputFile, sleepScreen);
serialization::writePod(outputFile, extraParagraphSpacing); // Use descriptors to automatically serialize all uint8_t settings
serialization::writePod(outputFile, shortPwrBtn); uint8_t descriptorIndex = 0;
serialization::writePod(outputFile, statusBar); for (const auto& desc : descriptors) {
serialization::writePod(outputFile, orientation); desc.save(outputFile, *this);
serialization::writePod(outputFile, frontButtonLayout); descriptorIndex++;
serialization::writePod(outputFile, sideButtonLayout); }
serialization::writePod(outputFile, fontFamily);
serialization::writePod(outputFile, fontSize);
serialization::writePod(outputFile, lineSpacing);
serialization::writePod(outputFile, paragraphAlignment);
serialization::writePod(outputFile, sleepTimeout);
serialization::writePod(outputFile, refreshFrequency);
serialization::writePod(outputFile, screenMargin);
serialization::writePod(outputFile, sleepScreenCoverMode);
serialization::writeString(outputFile, std::string(opdsServerUrl));
serialization::writePod(outputFile, textAntiAliasing);
serialization::writePod(outputFile, hideBatteryPercentage);
serialization::writePod(outputFile, longPressChapterSkip);
serialization::writePod(outputFile, hyphenationEnabled);
serialization::writeString(outputFile, std::string(opdsUsername));
serialization::writeString(outputFile, std::string(opdsPassword));
serialization::writePod(outputFile, sleepScreenCoverFilter);
// New fields added at end for backward compatibility
outputFile.close(); outputFile.close();
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
@ -68,8 +163,10 @@ bool CrossPointSettings::saveToFile() const {
} }
bool CrossPointSettings::loadFromFile() { bool CrossPointSettings::loadFromFile() {
Serial.printf("[%lu] [CPS] Loading settings from file\n", millis());
FsFile inputFile; FsFile inputFile;
if (!SdMan.openFileForRead("CPS", SETTINGS_FILE, inputFile)) { if (!SdMan.openFileForRead("CPS", SETTINGS_FILE, inputFile)) {
Serial.printf("[%lu] [CPS] Deserialization failed: Could not open settings file\n", millis());
return false; return false;
} }
@ -84,74 +181,26 @@ bool CrossPointSettings::loadFromFile() {
uint8_t fileSettingsCount = 0; uint8_t fileSettingsCount = 0;
serialization::readPod(inputFile, fileSettingsCount); serialization::readPod(inputFile, fileSettingsCount);
// load settings that exist (support older files with fewer fields) // Use descriptors to automatically deserialize all uint8_t settings
uint8_t settingsRead = 0; uint8_t descriptorIndex = 0;
do { uint8_t filePosition = 0;
readAndValidate(inputFile, sleepScreen, SLEEP_SCREEN_MODE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, extraParagraphSpacing);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, shortPwrBtn, SHORT_PWRBTN_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, statusBar, STATUS_BAR_MODE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, orientation, ORIENTATION_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, frontButtonLayout, FRONT_BUTTON_LAYOUT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sideButtonLayout, SIDE_BUTTON_LAYOUT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, fontFamily, FONT_FAMILY_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, fontSize, FONT_SIZE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, lineSpacing, LINE_COMPRESSION_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, paragraphAlignment, PARAGRAPH_ALIGNMENT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sleepTimeout, SLEEP_TIMEOUT_COUNT);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, refreshFrequency, REFRESH_FREQUENCY_COUNT);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, screenMargin);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sleepScreenCoverMode, SLEEP_SCREEN_COVER_MODE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
{
std::string urlStr;
serialization::readString(inputFile, urlStr);
strncpy(opdsServerUrl, urlStr.c_str(), sizeof(opdsServerUrl) - 1);
opdsServerUrl[sizeof(opdsServerUrl) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, textAntiAliasing);
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, hideBatteryPercentage, HIDE_BATTERY_PERCENTAGE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, longPressChapterSkip);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, hyphenationEnabled);
if (++settingsRead >= fileSettingsCount) break;
{
std::string usernameStr;
serialization::readString(inputFile, usernameStr);
strncpy(opdsUsername, usernameStr.c_str(), sizeof(opdsUsername) - 1);
opdsUsername[sizeof(opdsUsername) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount) break;
{
std::string passwordStr;
serialization::readString(inputFile, passwordStr);
strncpy(opdsPassword, passwordStr.c_str(), sizeof(opdsPassword) - 1);
opdsPassword[sizeof(opdsPassword) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount) break;
readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT);
if (++settingsRead >= fileSettingsCount) break;
// New fields added at end for backward compatibility
} while (false);
for (const auto& desc : descriptors) {
if (filePosition >= fileSettingsCount) {
break; // File has fewer settings than current version
}
desc.load(inputFile, *this);
if (!desc.validate(*this)) {
Serial.printf("[%lu] [CPS] Invalid value (0x%X) for %s, resetting to default\n", millis(), desc.getValue(*this),
desc.name);
desc.resetToDefault(*this);
}
descriptorIndex++;
filePosition++;
}
inputFile.close(); inputFile.close();
Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis()); Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis());
return true; return true;
} }

View File

@ -1,6 +1,112 @@
#pragma once #pragma once
#include <array>
#include <cstdint> #include <cstdint>
#include <iosfwd> #include <iosfwd>
#include <string>
#include <vector>
// Setting descriptor infrastructure
enum class SettingType { TOGGLE, ENUM, VALUE, STRING };
// Validator function pointer (not std::function to save memory)
using SettingValidator = bool (*)(uint8_t);
// Forward declare for descriptors
class CrossPointSettings;
// Forward declare file type
class FsFile;
// Base descriptor for all settings (non-virtual for constexpr)
struct SettingDescriptorBase {
const char* name; // Display name
SettingType type;
};
// Value range for VALUE type settings
struct ValueRange {
uint8_t min, max, step;
};
// Concrete descriptor for uint8_t settings (constexpr-compatible)
struct SettingDescriptor : public SettingDescriptorBase {
union {
uint8_t CrossPointSettings::* memberPtr; // For TOGGLE/ENUM/VALUE types
char* stringPtr; // For STRING type
};
uint8_t defaultValue;
SettingValidator validator; // Optional validator function
union {
// For ENUM types
struct {
const char* const* values;
uint8_t count;
} enumData;
// For VALUE types
ValueRange valueRange;
// For STRING types
struct {
const char* defaultString; // Default string value
size_t maxSize; // Max size of the string buffer
} stringData;
};
// Constexpr constructors for different setting types
// TOGGLE/ENUM constructor
constexpr SettingDescriptor(const char* name_, SettingType type_, uint8_t CrossPointSettings::* ptr, uint8_t defVal,
SettingValidator val, const char* const* enumVals, uint8_t enumCnt)
: SettingDescriptorBase{name_, type_},
memberPtr(ptr),
defaultValue(defVal),
validator(val),
enumData{enumVals, enumCnt} {}
// VALUE constructor
constexpr SettingDescriptor(const char* name_, SettingType type_, uint8_t CrossPointSettings::* ptr, uint8_t defVal,
SettingValidator val, ValueRange valRange)
: SettingDescriptorBase{name_, type_},
memberPtr(ptr),
defaultValue(defVal),
validator(val),
valueRange(valRange) {}
// STRING constructor
constexpr SettingDescriptor(const char* name_, SettingType type_, char* strPtr, const char* defStr, size_t maxSz)
: SettingDescriptorBase{name_, type_},
stringPtr(strPtr),
defaultValue(0),
validator(nullptr),
stringData{defStr, maxSz} {}
bool validate(const CrossPointSettings& settings) const;
uint8_t getValue(const CrossPointSettings& settings) const;
void setValue(CrossPointSettings& settings, uint8_t value) const;
void resetToDefault(CrossPointSettings& settings) const;
void save(FsFile& file, const CrossPointSettings& settings) const;
void load(FsFile& file, CrossPointSettings& settings) const;
// Helper to get enum value as string
const char* getEnumValueString(uint8_t index) const {
if (index < enumData.count && enumData.values) {
return enumData.values[index];
}
return "";
}
};
// Validator functions (constexpr for compile-time optimization)
constexpr bool validateToggle(uint8_t v) { return v <= 1; }
template <uint8_t MAX>
constexpr bool validateEnum(uint8_t v) {
return v < MAX;
}
template <uint8_t MIN, uint8_t MAX>
constexpr bool validateRange(uint8_t v) {
return v >= MIN && v <= MAX;
}
class CrossPointSettings { class CrossPointSettings {
private: private:
@ -15,13 +121,17 @@ class CrossPointSettings {
CrossPointSettings(const CrossPointSettings&) = delete; CrossPointSettings(const CrossPointSettings&) = delete;
CrossPointSettings& operator=(const CrossPointSettings&) = delete; CrossPointSettings& operator=(const CrossPointSettings&) = delete;
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4, SLEEP_SCREEN_MODE_COUNT }; // Static constexpr array of all setting descriptors
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT }; static constexpr size_t DESCRIPTOR_COUNT = 23;
static const std::array<SettingDescriptor, DESCRIPTOR_COUNT> descriptors;
// Should match with SettingsActivity text
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 };
enum SLEEP_SCREEN_COVER_FILTER { enum SLEEP_SCREEN_COVER_FILTER {
NO_FILTER = 0, NO_FILTER = 0,
BLACK_AND_WHITE = 1, BLACK_AND_WHITE = 1,
INVERTED_BLACK_AND_WHITE = 2, INVERTED_BLACK_AND_WHITE = 2,
SLEEP_SCREEN_COVER_FILTER_COUNT
}; };
// Status bar display type enum // Status bar display type enum
@ -31,7 +141,6 @@ class CrossPointSettings {
FULL = 2, FULL = 2,
FULL_WITH_PROGRESS_BAR = 3, FULL_WITH_PROGRESS_BAR = 3,
ONLY_PROGRESS_BAR = 4, ONLY_PROGRESS_BAR = 4,
STATUS_BAR_MODE_COUNT
}; };
enum ORIENTATION { enum ORIENTATION {
@ -39,7 +148,6 @@ class CrossPointSettings {
LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom) LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
INVERTED = 2, // 480x800 logical coordinates, inverted INVERTED = 2, // 480x800 logical coordinates, inverted
LANDSCAPE_CCW = 3, // 800x480 logical coordinates, native panel orientation LANDSCAPE_CCW = 3, // 800x480 logical coordinates, native panel orientation
ORIENTATION_COUNT
}; };
// Front button layout options // Front button layout options
@ -50,25 +158,23 @@ class CrossPointSettings {
LEFT_RIGHT_BACK_CONFIRM = 1, LEFT_RIGHT_BACK_CONFIRM = 1,
LEFT_BACK_CONFIRM_RIGHT = 2, LEFT_BACK_CONFIRM_RIGHT = 2,
BACK_CONFIRM_RIGHT_LEFT = 3, BACK_CONFIRM_RIGHT_LEFT = 3,
FRONT_BUTTON_LAYOUT_COUNT
}; };
// Side button layout options // Side button layout options
// Default: Previous, Next // Default: Previous, Next
// Swapped: Next, Previous // Swapped: Next, Previous
enum SIDE_BUTTON_LAYOUT { PREV_NEXT = 0, NEXT_PREV = 1, SIDE_BUTTON_LAYOUT_COUNT }; enum SIDE_BUTTON_LAYOUT { PREV_NEXT = 0, NEXT_PREV = 1 };
// Font family options // Font family options
enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, FONT_FAMILY_COUNT }; enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2 };
// Font size options // Font size options
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3, FONT_SIZE_COUNT }; enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3 };
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2, LINE_COMPRESSION_COUNT }; enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2 };
enum PARAGRAPH_ALIGNMENT { enum PARAGRAPH_ALIGNMENT {
JUSTIFIED = 0, JUSTIFIED = 0,
LEFT_ALIGN = 1, LEFT_ALIGN = 1,
CENTER_ALIGN = 2, CENTER_ALIGN = 2,
RIGHT_ALIGN = 3, RIGHT_ALIGN = 3,
PARAGRAPH_ALIGNMENT_COUNT
}; };
// Auto-sleep timeout options (in minutes) // Auto-sleep timeout options (in minutes)
@ -78,7 +184,6 @@ class CrossPointSettings {
SLEEP_10_MIN = 2, SLEEP_10_MIN = 2,
SLEEP_15_MIN = 3, SLEEP_15_MIN = 3,
SLEEP_30_MIN = 4, SLEEP_30_MIN = 4,
SLEEP_TIMEOUT_COUNT
}; };
// E-ink refresh frequency (pages between full refreshes) // E-ink refresh frequency (pages between full refreshes)
@ -88,14 +193,13 @@ class CrossPointSettings {
REFRESH_10 = 2, REFRESH_10 = 2,
REFRESH_15 = 3, REFRESH_15 = 3,
REFRESH_30 = 4, REFRESH_30 = 4,
REFRESH_FREQUENCY_COUNT
}; };
// 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 };
// Hide battery percentage // Hide battery percentage
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT }; enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
// Sleep screen settings // Sleep screen settings
uint8_t sleepScreen = DARK; uint8_t sleepScreen = DARK;

View File

@ -520,7 +520,7 @@ void WifiSelectionActivity::renderNetworkList() const {
const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto height = renderer.getLineHeight(UI_10_FONT_ID);
const auto top = (pageHeight - height) / 2; const auto top = (pageHeight - height) / 2;
renderer.drawCenteredText(UI_10_FONT_ID, top, "No networks found"); renderer.drawCenteredText(UI_10_FONT_ID, top, "No networks found");
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press OK to scan again"); renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press Connect to scan again");
} else { } else {
// Calculate how many networks we can display // Calculate how many networks we can display
constexpr int startY = 60; constexpr int startY = 60;

View File

@ -62,77 +62,84 @@ void CategorySettingsActivity::loop() {
} }
// Handle navigation // Handle navigation
const int totalItemsCount = descriptors.size() + actionItems.size();
if (mappedInput.wasPressed(MappedInputManager::Button::Up) || if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
mappedInput.wasPressed(MappedInputManager::Button::Left)) { mappedInput.wasPressed(MappedInputManager::Button::Left)) {
selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1); selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (totalItemsCount - 1);
updateRequired = true; updateRequired = true;
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
mappedInput.wasPressed(MappedInputManager::Button::Right)) { mappedInput.wasPressed(MappedInputManager::Button::Right)) {
selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0; selectedSettingIndex = (selectedSettingIndex < totalItemsCount - 1) ? (selectedSettingIndex + 1) : 0;
updateRequired = true; updateRequired = true;
} }
} }
void CategorySettingsActivity::toggleCurrentSetting() { void CategorySettingsActivity::toggleCurrentSetting() {
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) { const int totalItemsCount = descriptors.size() + actionItems.size();
if (selectedSettingIndex < 0 || selectedSettingIndex >= totalItemsCount) {
return; return;
} }
const auto& setting = settingsList[selectedSettingIndex]; // Check if it's a descriptor or an action item
if (selectedSettingIndex < static_cast<int>(descriptors.size())) {
// Handle descriptor
const auto* desc = descriptors[selectedSettingIndex];
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) { if (desc->type == SettingType::TOGGLE) {
// Toggle the boolean value using the member pointer uint8_t currentValue = desc->getValue(SETTINGS);
const bool currentValue = SETTINGS.*(setting.valuePtr); desc->setValue(SETTINGS, !currentValue);
SETTINGS.*(setting.valuePtr) = !currentValue; } else if (desc->type == SettingType::ENUM) {
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { uint8_t currentValue = desc->getValue(SETTINGS);
const uint8_t currentValue = SETTINGS.*(setting.valuePtr); desc->setValue(SETTINGS, (currentValue + 1) % desc->enumData.count);
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size()); } else if (desc->type == SettingType::VALUE) {
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { uint8_t currentValue = desc->getValue(SETTINGS);
const int8_t currentValue = SETTINGS.*(setting.valuePtr); if (currentValue + desc->valueRange.step > desc->valueRange.max) {
if (currentValue + setting.valueRange.step > setting.valueRange.max) { desc->setValue(SETTINGS, desc->valueRange.min);
SETTINGS.*(setting.valuePtr) = setting.valueRange.min; } else {
} else { desc->setValue(SETTINGS, currentValue + desc->valueRange.step);
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; }
}
} else if (setting.type == SettingType::ACTION) {
if (strcmp(setting.name, "KOReader Sync") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
xSemaphoreGive(renderingMutex);
} else if (strcmp(setting.name, "OPDS Browser") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
xSemaphoreGive(renderingMutex);
} else if (strcmp(setting.name, "Clear Cache") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
enterNewActivity(new ClearCacheActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
xSemaphoreGive(renderingMutex);
} else if (strcmp(setting.name, "Check for updates") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
xSemaphoreGive(renderingMutex);
} }
SETTINGS.saveToFile();
} else { } else {
return; // Handle action item
} const int actionIndex = selectedSettingIndex - descriptors.size();
const auto& action = actionItems[actionIndex];
SETTINGS.saveToFile(); xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
switch (action.type) {
case ActionItem::Type::KOREADER_SYNC:
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
break;
case ActionItem::Type::CALIBRE_SETTINGS:
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
break;
case ActionItem::Type::CLEAR_CACHE:
enterNewActivity(new ClearCacheActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
break;
case ActionItem::Type::CHECK_UPDATES:
enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
break;
}
xSemaphoreGive(renderingMutex);
}
} }
void CategorySettingsActivity::displayTaskLoop() { void CategorySettingsActivity::displayTaskLoop() {
@ -153,29 +160,31 @@ void CategorySettingsActivity::render() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Draw header with category name
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD);
// Draw selection highlight // Draw selection highlight
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30); renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
// Draw all settings // Draw all descriptors
for (int i = 0; i < settingsCount; i++) { for (size_t i = 0; i < descriptors.size(); i++) {
const int settingY = 60 + i * 30; // 30 pixels between settings const auto* desc = descriptors[i];
const int settingY = 60 + i * 30;
const bool isSelected = (i == selectedSettingIndex); const bool isSelected = (i == selectedSettingIndex);
// Draw setting name // Draw setting name
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected); renderer.drawText(UI_10_FONT_ID, 20, settingY, desc->name, !isSelected);
// Draw value based on setting type // Draw value based on setting type
std::string valueText; std::string valueText;
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) { if (desc->type == SettingType::TOGGLE) {
const bool value = SETTINGS.*(settingsList[i].valuePtr); const bool value = desc->getValue(SETTINGS);
valueText = value ? "ON" : "OFF"; valueText = value ? "ON" : "OFF";
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) { } else if (desc->type == SettingType::ENUM) {
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr); const uint8_t value = desc->getValue(SETTINGS);
valueText = settingsList[i].enumValues[value]; valueText = desc->getEnumValueString(value);
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) { } else if (desc->type == SettingType::VALUE) {
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr)); valueText = std::to_string(desc->getValue(SETTINGS));
} }
if (!valueText.empty()) { if (!valueText.empty()) {
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()); const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
@ -183,9 +192,22 @@ void CategorySettingsActivity::render() const {
} }
} }
// Draw all action items
for (size_t i = 0; i < actionItems.size(); i++) {
const auto& action = actionItems[i];
const int itemIndex = descriptors.size() + i;
const int settingY = 60 + itemIndex * 30;
const bool isSelected = (itemIndex == selectedSettingIndex);
// Draw action name
renderer.drawText(UI_10_FONT_ID, 20, settingY, action.name, !isSelected);
}
// Draw version text above button hints
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION); pageHeight - 60, CROSSPOINT_VERSION);
// Draw help text
const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", ""); const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@ -7,38 +7,14 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "CrossPointSettings.h"
#include "activities/ActivityWithSubactivity.h" #include "activities/ActivityWithSubactivity.h"
class CrossPointSettings; // Action items for the System category
struct ActionItem {
enum class SettingType { TOGGLE, ENUM, ACTION, VALUE };
struct SettingInfo {
const char* name; const char* name;
SettingType type; enum class Type { KOREADER_SYNC, CALIBRE_SETTINGS, CLEAR_CACHE, CHECK_UPDATES };
uint8_t CrossPointSettings::* valuePtr; Type type;
std::vector<std::string> enumValues;
struct ValueRange {
uint8_t min;
uint8_t max;
uint8_t step;
};
ValueRange valueRange;
static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr) {
return {name, SettingType::TOGGLE, ptr};
}
static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector<std::string> values) {
return {name, SettingType::ENUM, ptr, std::move(values)};
}
static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr}; }
static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) {
return {name, SettingType::VALUE, ptr, {}, valueRange};
}
}; };
class CategorySettingsActivity final : public ActivityWithSubactivity { class CategorySettingsActivity final : public ActivityWithSubactivity {
@ -47,8 +23,8 @@ class CategorySettingsActivity final : public ActivityWithSubactivity {
bool updateRequired = false; bool updateRequired = false;
int selectedSettingIndex = 0; int selectedSettingIndex = 0;
const char* categoryName; const char* categoryName;
const SettingInfo* settingsList; const std::vector<const SettingDescriptor*> descriptors;
int settingsCount; const std::vector<ActionItem> actionItems;
const std::function<void()> onGoBack; const std::function<void()> onGoBack;
static void taskTrampoline(void* param); static void taskTrampoline(void* param);
@ -58,11 +34,12 @@ class CategorySettingsActivity final : public ActivityWithSubactivity {
public: public:
CategorySettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const char* categoryName, CategorySettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const char* categoryName,
const SettingInfo* settingsList, int settingsCount, const std::function<void()>& onGoBack) const std::vector<const SettingDescriptor*>& descriptors,
const std::vector<ActionItem>& actionItems, const std::function<void()>& onGoBack)
: ActivityWithSubactivity("CategorySettings", renderer, mappedInput), : ActivityWithSubactivity("CategorySettings", renderer, mappedInput),
categoryName(categoryName), categoryName(categoryName),
settingsList(settingsList), descriptors(descriptors),
settingsCount(settingsCount), actionItems(actionItems),
onGoBack(onGoBack) {} onGoBack(onGoBack) {}
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;

View File

@ -3,6 +3,8 @@
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <algorithm>
#include "CategorySettingsActivity.h" #include "CategorySettingsActivity.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
@ -10,51 +12,12 @@
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"}; const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
namespace { // Helper function to find descriptor by member pointer
constexpr int displaySettingsCount = 6; static const SettingDescriptor* findDescriptor(uint8_t CrossPointSettings::* memberPtr) {
const SettingInfo displaySettings[displaySettingsCount] = { auto it = std::find_if(CrossPointSettings::descriptors.begin(), CrossPointSettings::descriptors.end(),
// Should match with SLEEP_SCREEN_MODE [memberPtr](const SettingDescriptor& desc) { return desc.memberPtr == memberPtr; });
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), return (it != CrossPointSettings::descriptors.end()) ? &(*it) : nullptr;
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), }
SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter,
{"None", "Contrast", "Inverted"}),
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar,
{"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}),
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})};
constexpr int readerSettingsCount = 9;
const SettingInfo readerSettings[readerSettingsCount] = {
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
{"Justify", "Left", "Center", "Right"}),
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)};
constexpr int controlsSettingsCount = 4;
const SettingInfo controlsSettings[controlsSettingsCount] = {
SettingInfo::Enum(
"Front Button Layout", &CrossPointSettings::frontButtonLayout,
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght", "Bck, Cnfrm, Rght, Lft"}),
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
{"Prev, Next", "Next, Prev"}),
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})};
constexpr int systemSettingsCount = 5;
const SettingInfo systemSettings[systemSettingsCount] = {
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("OPDS Browser"), SettingInfo::Action("Clear Cache"),
SettingInfo::Action("Check for updates")};
} // namespace
void SettingsActivity::taskTrampoline(void* param) { void SettingsActivity::taskTrampoline(void* param) {
auto* self = static_cast<SettingsActivity*>(param); auto* self = static_cast<SettingsActivity*>(param);
@ -125,37 +88,51 @@ void SettingsActivity::loop() {
} }
void SettingsActivity::enterCategory(int categoryIndex) { void SettingsActivity::enterCategory(int categoryIndex) {
if (categoryIndex < 0 || categoryIndex >= categoryCount) { std::vector<const SettingDescriptor*> descriptors;
return; std::vector<ActionItem> actionItems;
switch (categoryIndex) {
case 0: // Display
descriptors.push_back(findDescriptor(&CrossPointSettings::sleepScreen));
descriptors.push_back(findDescriptor(&CrossPointSettings::sleepScreenCoverMode));
descriptors.push_back(findDescriptor(&CrossPointSettings::sleepScreenCoverFilter));
descriptors.push_back(findDescriptor(&CrossPointSettings::statusBar));
descriptors.push_back(findDescriptor(&CrossPointSettings::hideBatteryPercentage));
descriptors.push_back(findDescriptor(&CrossPointSettings::refreshFrequency));
break;
case 1: // Reader
descriptors.push_back(findDescriptor(&CrossPointSettings::fontFamily));
descriptors.push_back(findDescriptor(&CrossPointSettings::fontSize));
descriptors.push_back(findDescriptor(&CrossPointSettings::lineSpacing));
descriptors.push_back(findDescriptor(&CrossPointSettings::screenMargin));
descriptors.push_back(findDescriptor(&CrossPointSettings::paragraphAlignment));
descriptors.push_back(findDescriptor(&CrossPointSettings::hyphenationEnabled));
descriptors.push_back(findDescriptor(&CrossPointSettings::orientation));
descriptors.push_back(findDescriptor(&CrossPointSettings::extraParagraphSpacing));
descriptors.push_back(findDescriptor(&CrossPointSettings::textAntiAliasing));
break;
case 2: // Controls
descriptors.push_back(findDescriptor(&CrossPointSettings::frontButtonLayout));
descriptors.push_back(findDescriptor(&CrossPointSettings::sideButtonLayout));
descriptors.push_back(findDescriptor(&CrossPointSettings::longPressChapterSkip));
descriptors.push_back(findDescriptor(&CrossPointSettings::shortPwrBtn));
break;
case 3: // System
descriptors.push_back(findDescriptor(&CrossPointSettings::sleepTimeout));
actionItems.push_back({"KOReader Sync", ActionItem::Type::KOREADER_SYNC});
actionItems.push_back({"Calibre Settings", ActionItem::Type::CALIBRE_SETTINGS});
actionItems.push_back({"Clear Cache", ActionItem::Type::CLEAR_CACHE});
actionItems.push_back({"Check for updates", ActionItem::Type::CHECK_UPDATES});
break;
} }
xSemaphoreTake(renderingMutex, portMAX_DELAY); xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity(); exitActivity();
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], descriptors,
const SettingInfo* settingsList = nullptr; actionItems, [this] {
int settingsCount = 0;
switch (categoryIndex) {
case 0: // Display
settingsList = displaySettings;
settingsCount = displaySettingsCount;
break;
case 1: // Reader
settingsList = readerSettings;
settingsCount = readerSettingsCount;
break;
case 2: // Controls
settingsList = controlsSettings;
settingsCount = controlsSettingsCount;
break;
case 3: // System
settingsList = systemSettings;
settingsCount = systemSettingsCount;
break;
}
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList,
settingsCount, [this] {
exitActivity(); exitActivity();
updateRequired = true; updateRequired = true;
})); }));

View File

@ -9,9 +9,6 @@
#include "activities/ActivityWithSubactivity.h" #include "activities/ActivityWithSubactivity.h"
class CrossPointSettings;
struct SettingInfo;
class SettingsActivity final : public ActivityWithSubactivity { class SettingsActivity final : public ActivityWithSubactivity {
TaskHandle_t displayTaskHandle = nullptr; TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr; SemaphoreHandle_t renderingMutex = nullptr;