Merge branch 'master' into ftp_server

This commit is contained in:
Richard Anderson 2026-01-05 21:19:11 -06:00 committed by GitHub
commit 6bcd01269e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 106 additions and 45 deletions

View File

@ -250,34 +250,29 @@ BmpReaderError Bitmap::parseHeaders() {
delete[] errorNextRow; delete[] errorNextRow;
errorCurRow = new int16_t[width + 2](); // +2 for boundary handling errorCurRow = new int16_t[width + 2](); // +2 for boundary handling
errorNextRow = new int16_t[width + 2](); errorNextRow = new int16_t[width + 2]();
lastRowY = -1; prevRowY = -1;
} }
return BmpReaderError::Ok; return BmpReaderError::Ok;
} }
// packed 2bpp output, 0 = black, 1 = dark gray, 2 = light gray, 3 = white // packed 2bpp output, 0 = black, 1 = dark gray, 2 = light gray, 3 = white
BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) const { BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const {
// Note: rowBuffer should be pre-allocated by the caller to size 'rowBytes' // Note: rowBuffer should be pre-allocated by the caller to size 'rowBytes'
if (file.read(rowBuffer, rowBytes) != rowBytes) return BmpReaderError::ShortReadRow; if (file.read(rowBuffer, rowBytes) != rowBytes) return BmpReaderError::ShortReadRow;
// Handle Floyd-Steinberg error buffer progression // Handle Floyd-Steinberg error buffer progression
const bool useFS = USE_FLOYD_STEINBERG && errorCurRow && errorNextRow; const bool useFS = USE_FLOYD_STEINBERG && errorCurRow && errorNextRow;
if (useFS) { if (useFS) {
// Check if we need to advance to next row (or reset if jumping) if (prevRowY != -1) {
if (rowY != lastRowY + 1 && rowY != 0) {
// Non-sequential row access - reset error buffers
memset(errorCurRow, 0, (width + 2) * sizeof(int16_t));
memset(errorNextRow, 0, (width + 2) * sizeof(int16_t));
} else if (rowY > 0) {
// Sequential access - swap buffers // Sequential access - swap buffers
int16_t* temp = errorCurRow; int16_t* temp = errorCurRow;
errorCurRow = errorNextRow; errorCurRow = errorNextRow;
errorNextRow = temp; errorNextRow = temp;
memset(errorNextRow, 0, (width + 2) * sizeof(int16_t)); memset(errorNextRow, 0, (width + 2) * sizeof(int16_t));
} }
lastRowY = rowY;
} }
prevRowY += 1;
uint8_t* outPtr = data; uint8_t* outPtr = data;
uint8_t currentOutByte = 0; uint8_t currentOutByte = 0;
@ -292,7 +287,7 @@ BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) cons
color = quantizeFloydSteinberg(lum, currentX, width, errorCurRow, errorNextRow, false); color = quantizeFloydSteinberg(lum, currentX, width, errorCurRow, errorNextRow, false);
} else { } else {
// Simple quantization or noise dithering // Simple quantization or noise dithering
color = quantize(lum, currentX, rowY); color = quantize(lum, currentX, prevRowY);
} }
currentOutByte |= (color << bitShift); currentOutByte |= (color << bitShift);
if (bitShift == 0) { if (bitShift == 0) {
@ -365,7 +360,7 @@ BmpReaderError Bitmap::rewindToData() const {
if (USE_FLOYD_STEINBERG && errorCurRow && errorNextRow) { if (USE_FLOYD_STEINBERG && errorCurRow && errorNextRow) {
memset(errorCurRow, 0, (width + 2) * sizeof(int16_t)); memset(errorCurRow, 0, (width + 2) * sizeof(int16_t));
memset(errorNextRow, 0, (width + 2) * sizeof(int16_t)); memset(errorNextRow, 0, (width + 2) * sizeof(int16_t));
lastRowY = -1; prevRowY = -1;
} }
return BmpReaderError::Ok; return BmpReaderError::Ok;

View File

@ -31,7 +31,7 @@ class Bitmap {
explicit Bitmap(FsFile& file) : file(file) {} explicit Bitmap(FsFile& file) : file(file) {}
~Bitmap(); ~Bitmap();
BmpReaderError parseHeaders(); BmpReaderError parseHeaders();
BmpReaderError readRow(uint8_t* data, uint8_t* rowBuffer, int rowY) const; BmpReaderError readNextRow(uint8_t* data, uint8_t* rowBuffer) const;
BmpReaderError rewindToData() const; BmpReaderError rewindToData() const;
int getWidth() const { return width; } int getWidth() const { return width; }
int getHeight() const { return height; } int getHeight() const { return height; }
@ -55,5 +55,5 @@ class Bitmap {
// Floyd-Steinberg dithering state (mutable for const methods) // Floyd-Steinberg dithering state (mutable for const methods)
mutable int16_t* errorCurRow = nullptr; mutable int16_t* errorCurRow = nullptr;
mutable int16_t* errorNextRow = nullptr; mutable int16_t* errorNextRow = nullptr;
mutable int lastRowY = -1; // Track row progression for error propagation mutable int prevRowY = -1; // Track row progression for error propagation
}; };

View File

@ -152,18 +152,24 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co
einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height); einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height);
} }
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
const int maxHeight) const { const float cropX, const float cropY) const {
float scale = 1.0f; float scale = 1.0f;
bool isScaled = false; bool isScaled = false;
if (maxWidth > 0 && bitmap.getWidth() > maxWidth) { int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f);
scale = static_cast<float>(maxWidth) / static_cast<float>(bitmap.getWidth()); int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f);
Serial.printf("[%lu] [GFX] Cropping %dx%d by %dx%d pix, is %s\n", millis(), bitmap.getWidth(), bitmap.getHeight(),
cropPixX, cropPixY, bitmap.isTopDown() ? "top-down" : "bottom-up");
if (maxWidth > 0 && (1.0f - cropX) * bitmap.getWidth() > maxWidth) {
scale = static_cast<float>(maxWidth) / static_cast<float>((1.0f - cropX) * bitmap.getWidth());
isScaled = true; isScaled = true;
} }
if (maxHeight > 0 && bitmap.getHeight() > maxHeight) { if (maxHeight > 0 && (1.0f - cropY) * bitmap.getHeight() > maxHeight) {
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>(bitmap.getHeight())); scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>((1.0f - cropY) * bitmap.getHeight()));
isScaled = true; isScaled = true;
} }
Serial.printf("[%lu] [GFX] Scaling by %f - %s\n", millis(), scale, isScaled ? "scaled" : "not scaled");
// Calculate output row size (2 bits per pixel, packed into bytes) // Calculate output row size (2 bits per pixel, packed into bytes)
// IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide // IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide
@ -178,29 +184,36 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
return; return;
} }
for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) { for (int bmpY = 0; bmpY < (bitmap.getHeight() - cropPixY); bmpY++) {
// The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative). // The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative).
// Screen's (0, 0) is the top-left corner. // Screen's (0, 0) is the top-left corner.
int screenY = y + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY); int screenY = -cropPixY + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY);
if (isScaled) { if (isScaled) {
screenY = std::floor(screenY * scale); screenY = std::floor(screenY * scale);
} }
screenY += y; // the offset should not be scaled
if (screenY >= getScreenHeight()) { if (screenY >= getScreenHeight()) {
break; break;
} }
if (bitmap.readRow(outputRow, rowBytes, bmpY) != BmpReaderError::Ok) { if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY); Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY);
free(outputRow); free(outputRow);
free(rowBytes); free(rowBytes);
return; return;
} }
for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) { if (bmpY < cropPixY) {
int screenX = x + bmpX; // Skip the row if it's outside the crop area
continue;
}
for (int bmpX = cropPixX; bmpX < bitmap.getWidth() - cropPixX; bmpX++) {
int screenX = bmpX - cropPixX;
if (isScaled) { if (isScaled) {
screenX = std::floor(screenX * scale); screenX = std::floor(screenX * scale);
} }
screenX += x; // the offset should not be scaled
if (screenX >= getScreenWidth()) { if (screenX >= getScreenWidth()) {
break; break;
} }

View File

@ -66,7 +66,8 @@ class GfxRenderer {
void drawRect(int x, int y, int width, int height, bool state = true) const; void drawRect(int x, int y, int width, int height, bool state = true) const;
void fillRect(int x, int y, int width, int height, bool state = true) const; void fillRect(int x, int y, int width, int height, bool state = true) const;
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const; void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0,
float cropY = 0) const;
// Text // Text
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;

View File

@ -468,7 +468,9 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
// Calculate scale to fit within target dimensions while maintaining aspect ratio // Calculate scale to fit within target dimensions while maintaining aspect ratio
const float scaleToFitWidth = static_cast<float>(TARGET_MAX_WIDTH) / imageInfo.m_width; const float scaleToFitWidth = static_cast<float>(TARGET_MAX_WIDTH) / imageInfo.m_width;
const float scaleToFitHeight = static_cast<float>(TARGET_MAX_HEIGHT) / imageInfo.m_height; const float scaleToFitHeight = static_cast<float>(TARGET_MAX_HEIGHT) / imageInfo.m_height;
const float scale = (scaleToFitWidth < scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight; // We scale to the smaller dimension, so we can potentially crop later.
// TODO: ideally, we already crop here.
const float scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
outWidth = static_cast<int>(imageInfo.m_width * scale); outWidth = static_cast<int>(imageInfo.m_width * scale);
outHeight = static_cast<int>(imageInfo.m_height * scale); outHeight = static_cast<int>(imageInfo.m_height * scale);

View File

@ -17,7 +17,8 @@ class CrossPointSettings {
CrossPointSettings& operator=(const CrossPointSettings&) = delete; CrossPointSettings& operator=(const CrossPointSettings&) = delete;
// Should match with SettingsActivity text // Should match with SettingsActivity text
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3 }; enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 };
// Status bar display type enum // Status bar display type enum
enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 }; enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 };
@ -54,6 +55,8 @@ class CrossPointSettings {
// Sleep screen settings // Sleep screen settings
uint8_t sleepScreen = DARK; uint8_t sleepScreen = DARK;
// Sleep screen cover mode settings
uint8_t sleepScreenCoverMode = FIT;
// Status bar settings // Status bar settings
uint8_t statusBar = FULL; uint8_t statusBar = FULL;
// Text rendering settings // Text rendering settings
@ -96,6 +99,7 @@ class CrossPointSettings {
float getReaderLineCompression() const; float getReaderLineCompression() const;
unsigned long getSleepTimeoutMs() const; unsigned long getSleepTimeoutMs() const;
int getRefreshFrequency() const; int getRefreshFrequency() const;
int getReaderScreenMargin() const;
}; };
// Helper macro to access settings // Helper macro to access settings

View File

@ -28,6 +28,10 @@ void SleepActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
renderPopup("Entering Sleep..."); renderPopup("Entering Sleep...");
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::BLANK) {
return renderBlankSleepScreen();
}
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) { if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) {
return renderCustomSleepScreen(); return renderCustomSleepScreen();
} }
@ -142,20 +146,36 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
int x, y; int x, y;
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
float cropX = 0, cropY = 0;
Serial.printf("[%lu] [SLP] bitmap %d x %d, screen %d x %d\n", millis(), bitmap.getWidth(), bitmap.getHeight(),
pageWidth, pageHeight);
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) { if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
// image will scale, make sure placement is right // image will scale, make sure placement is right
const float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight()); float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight); const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
Serial.printf("[%lu] [SLP] bitmap ratio: %f, screen ratio: %f\n", millis(), ratio, screenRatio);
if (ratio > screenRatio) { if (ratio > screenRatio) {
// image wider than viewport ratio, scaled down image needs to be centered vertically // image wider than viewport ratio, scaled down image needs to be centered vertically
if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
cropX = 1.0f - (screenRatio / ratio);
Serial.printf("[%lu] [SLP] Cropping bitmap x: %f\n", millis(), cropX);
ratio = (1.0f - cropX) * static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
}
x = 0; x = 0;
y = (pageHeight - pageWidth / ratio) / 2; y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
Serial.printf("[%lu] [SLP] Centering with ratio %f to y=%d\n", millis(), ratio, y);
} else { } else {
// image taller than viewport ratio, scaled down image needs to be centered horizontally // image taller than viewport ratio, scaled down image needs to be centered horizontally
x = (pageWidth - pageHeight * ratio) / 2; if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
cropY = 1.0f - (ratio / screenRatio);
Serial.printf("[%lu] [SLP] Cropping bitmap y: %f\n", millis(), cropY);
ratio = static_cast<float>(bitmap.getWidth()) / ((1.0f - cropY) * static_cast<float>(bitmap.getHeight()));
}
x = std::round((pageWidth - pageHeight * ratio) / 2);
y = 0; y = 0;
Serial.printf("[%lu] [SLP] Centering with ratio %f to x=%d\n", millis(), ratio, x);
} }
} else { } else {
// center the image // center the image
@ -163,21 +183,22 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
y = (pageHeight - bitmap.getHeight()) / 2; y = (pageHeight - bitmap.getHeight()) / 2;
} }
Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y);
renderer.clearScreen(); renderer.clearScreen();
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
renderer.displayBuffer(EInkDisplay::HALF_REFRESH); renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
if (bitmap.hasGreyscale()) { if (bitmap.hasGreyscale()) {
bitmap.rewindToData(); bitmap.rewindToData();
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
renderer.copyGrayscaleLsbBuffers(); renderer.copyGrayscaleLsbBuffers();
bitmap.rewindToData(); bitmap.rewindToData();
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
renderer.copyGrayscaleMsbBuffers(); renderer.copyGrayscaleMsbBuffers();
renderer.displayGrayBuffer(); renderer.displayGrayBuffer();
@ -234,3 +255,8 @@ void SleepActivity::renderCoverSleepScreen() const {
renderDefaultSleepScreen(); renderDefaultSleepScreen();
} }
void SleepActivity::renderBlankSleepScreen() const {
renderer.clearScreen();
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
}

View File

@ -15,4 +15,5 @@ class SleepActivity final : public Activity {
void renderCustomSleepScreen() const; void renderCustomSleepScreen() const;
void renderCoverSleepScreen() const; void renderCoverSleepScreen() const;
void renderBitmapSleepScreen(const Bitmap& bitmap) const; void renderBitmapSleepScreen(const Bitmap& bitmap) const;
void renderBlankSleepScreen() const;
}; };

View File

@ -16,8 +16,6 @@ namespace {
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
constexpr unsigned long skipChapterMs = 700; constexpr unsigned long skipChapterMs = 700;
constexpr unsigned long goHomeMs = 1000; constexpr unsigned long goHomeMs = 1000;
constexpr int topPadding = 5;
constexpr int horizontalPadding = 5;
constexpr int statusBarMargin = 19; constexpr int statusBarMargin = 19;
} // namespace } // namespace
@ -253,9 +251,9 @@ void EpubReaderActivity::renderScreen() {
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft;
renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom,
&orientedMarginLeft); &orientedMarginLeft);
orientedMarginTop += topPadding; orientedMarginTop += SETTINGS.screenMargin;
orientedMarginLeft += horizontalPadding; orientedMarginLeft += SETTINGS.screenMargin;
orientedMarginRight += horizontalPadding; orientedMarginRight += SETTINGS.screenMargin;
orientedMarginBottom += statusBarMargin; orientedMarginBottom += statusBarMargin;
if (!section) { if (!section) {

View File

@ -1,6 +1,7 @@
#include "SettingsActivity.h" #include "SettingsActivity.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HardwareSerial.h>
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
@ -66,7 +67,6 @@ void SettingsActivity::taskTrampoline(void* param) {
void SettingsActivity::onEnter() { void SettingsActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
renderingMutex = xSemaphoreCreateMutex(); renderingMutex = xSemaphoreCreateMutex();
// Reset selection to first item // Reset selection to first item
@ -76,7 +76,7 @@ void SettingsActivity::onEnter() {
updateRequired = true; updateRequired = true;
xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask", xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask",
2048, // Stack size 4096, // Stack size
this, // Parameters this, // Parameters
1, // Priority 1, // Priority
&displayTaskHandle // Task handle &displayTaskHandle // Task handle
@ -123,11 +123,9 @@ void SettingsActivity::loop() {
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)) {
// Move selection down // Move selection down (with wrap around)
if (selectedSettingIndex < settingsCount - 1) { selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0;
selectedSettingIndex++; updateRequired = true;
updateRequired = true;
}
} }
} }

View File

@ -20,6 +20,29 @@ struct SettingInfo {
uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM) uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM)
std::string CrossPointSettings::* stringValuePtr; // Pointer to string member (for TEXT) std::string CrossPointSettings::* stringValuePtr; // Pointer to string member (for TEXT)
std::vector<std::string> enumValues; std::vector<std::string> enumValues;
struct ValueRange {
uint8_t min;
uint8_t max;
uint8_t step;
};
// Bounds/step for VALUE type settings
ValueRange valueRange;
// Static constructors
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 SettingsActivity final : public ActivityWithSubactivity { class SettingsActivity final : public ActivityWithSubactivity {