mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-05 07:07:38 +03:00
fix: reset clang correctly
This commit is contained in:
parent
bf353e7d83
commit
f6469c48ba
@ -1,8 +1,10 @@
|
|||||||
#include "Bitmap.h"
|
#include "Bitmap.h"
|
||||||
#include "BitmapHelpers.h"
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "BitmapHelpers.h"
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// IMAGE PROCESSING OPTIONS
|
// IMAGE PROCESSING OPTIONS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@ -31,13 +33,12 @@ int Bitmap::readByte() const {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Bitmap::readBytes(void *buf, size_t count) const {
|
size_t Bitmap::readBytes(void* buf, size_t count) const {
|
||||||
if (file && *file) {
|
if (file && *file) {
|
||||||
return file->read(buf, count);
|
return file->read(buf, count);
|
||||||
} else if (memoryBuffer) {
|
} else if (memoryBuffer) {
|
||||||
size_t available = memorySize - bufferPos;
|
size_t available = memorySize - bufferPos;
|
||||||
if (count > available)
|
if (count > available) count = available;
|
||||||
count = available;
|
|
||||||
memcpy(buf, memoryBuffer + bufferPos, count);
|
memcpy(buf, memoryBuffer + bufferPos, count);
|
||||||
bufferPos += count;
|
bufferPos += count;
|
||||||
return count;
|
return count;
|
||||||
@ -74,8 +75,7 @@ bool Bitmap::seekCur(int32_t offset) const {
|
|||||||
uint16_t Bitmap::readLE16() {
|
uint16_t Bitmap::readLE16() {
|
||||||
const int c0 = readByte();
|
const int c0 = readByte();
|
||||||
const int c1 = readByte();
|
const int c1 = readByte();
|
||||||
return static_cast<uint16_t>(c0 & 0xFF) |
|
return static_cast<uint16_t>(c0 & 0xFF) | (static_cast<uint16_t>(c1 & 0xFF) << 8);
|
||||||
(static_cast<uint16_t>(c1 & 0xFF) << 8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Bitmap::readLE32() {
|
uint32_t Bitmap::readLE32() {
|
||||||
@ -83,64 +83,58 @@ uint32_t Bitmap::readLE32() {
|
|||||||
const int c1 = readByte();
|
const int c1 = readByte();
|
||||||
const int c2 = readByte();
|
const int c2 = readByte();
|
||||||
const int c3 = readByte();
|
const int c3 = readByte();
|
||||||
return static_cast<uint32_t>(c0 & 0xFF) |
|
return static_cast<uint32_t>(c0 & 0xFF) | (static_cast<uint32_t>(c1 & 0xFF) << 8) |
|
||||||
(static_cast<uint32_t>(c1 & 0xFF) << 8) |
|
(static_cast<uint32_t>(c2 & 0xFF) << 16) | (static_cast<uint32_t>(c3 & 0xFF) << 24);
|
||||||
(static_cast<uint32_t>(c2 & 0xFF) << 16) |
|
|
||||||
(static_cast<uint32_t>(c3 & 0xFF) << 24);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *Bitmap::errorToString(BmpReaderError err) {
|
const char* Bitmap::errorToString(BmpReaderError err) {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case BmpReaderError::Ok:
|
case BmpReaderError::Ok:
|
||||||
return "Ok";
|
return "Ok";
|
||||||
case BmpReaderError::FileInvalid:
|
case BmpReaderError::FileInvalid:
|
||||||
return "FileInvalid";
|
return "FileInvalid";
|
||||||
case BmpReaderError::SeekStartFailed:
|
case BmpReaderError::SeekStartFailed:
|
||||||
return "SeekStartFailed";
|
return "SeekStartFailed";
|
||||||
case BmpReaderError::NotBMP:
|
case BmpReaderError::NotBMP:
|
||||||
return "NotBMP";
|
return "NotBMP";
|
||||||
case BmpReaderError::DIBTooSmall:
|
case BmpReaderError::DIBTooSmall:
|
||||||
return "DIBTooSmall";
|
return "DIBTooSmall";
|
||||||
case BmpReaderError::BadPlanes:
|
case BmpReaderError::BadPlanes:
|
||||||
return "BadPlanes";
|
return "BadPlanes";
|
||||||
case BmpReaderError::UnsupportedBpp:
|
case BmpReaderError::UnsupportedBpp:
|
||||||
return "UnsupportedBpp";
|
return "UnsupportedBpp";
|
||||||
case BmpReaderError::UnsupportedCompression:
|
case BmpReaderError::UnsupportedCompression:
|
||||||
return "UnsupportedCompression";
|
return "UnsupportedCompression";
|
||||||
case BmpReaderError::BadDimensions:
|
case BmpReaderError::BadDimensions:
|
||||||
return "BadDimensions";
|
return "BadDimensions";
|
||||||
case BmpReaderError::ImageTooLarge:
|
case BmpReaderError::ImageTooLarge:
|
||||||
return "ImageTooLarge";
|
return "ImageTooLarge";
|
||||||
case BmpReaderError::PaletteTooLarge:
|
case BmpReaderError::PaletteTooLarge:
|
||||||
return "PaletteTooLarge";
|
return "PaletteTooLarge";
|
||||||
case BmpReaderError::SeekPixelDataFailed:
|
case BmpReaderError::SeekPixelDataFailed:
|
||||||
return "SeekPixelDataFailed";
|
return "SeekPixelDataFailed";
|
||||||
case BmpReaderError::BufferTooSmall:
|
case BmpReaderError::BufferTooSmall:
|
||||||
return "BufferTooSmall";
|
return "BufferTooSmall";
|
||||||
case BmpReaderError::OomRowBuffer:
|
case BmpReaderError::OomRowBuffer:
|
||||||
return "OomRowBuffer";
|
return "OomRowBuffer";
|
||||||
case BmpReaderError::ShortReadRow:
|
case BmpReaderError::ShortReadRow:
|
||||||
return "ShortReadRow";
|
return "ShortReadRow";
|
||||||
}
|
}
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
BmpReaderError Bitmap::parseHeaders() {
|
BmpReaderError Bitmap::parseHeaders() {
|
||||||
if (!file && !memoryBuffer)
|
if (!file && !memoryBuffer) return BmpReaderError::FileInvalid;
|
||||||
return BmpReaderError::FileInvalid;
|
if (!seekSet(0)) return BmpReaderError::SeekStartFailed;
|
||||||
if (!seekSet(0))
|
|
||||||
return BmpReaderError::SeekStartFailed;
|
|
||||||
|
|
||||||
const uint16_t bfType = readLE16();
|
const uint16_t bfType = readLE16();
|
||||||
if (bfType != 0x4D42)
|
if (bfType != 0x4D42) return BmpReaderError::NotBMP;
|
||||||
return BmpReaderError::NotBMP;
|
|
||||||
|
|
||||||
seekCur(8);
|
seekCur(8);
|
||||||
bfOffBits = readLE32();
|
bfOffBits = readLE32();
|
||||||
|
|
||||||
const uint32_t biSize = readLE32();
|
const uint32_t biSize = readLE32();
|
||||||
if (biSize < 40)
|
if (biSize < 40) return BmpReaderError::DIBTooSmall;
|
||||||
return BmpReaderError::DIBTooSmall;
|
|
||||||
|
|
||||||
width = static_cast<int32_t>(readLE32());
|
width = static_cast<int32_t>(readLE32());
|
||||||
const auto rawHeight = static_cast<int32_t>(readLE32());
|
const auto rawHeight = static_cast<int32_t>(readLE32());
|
||||||
@ -150,24 +144,18 @@ BmpReaderError Bitmap::parseHeaders() {
|
|||||||
const uint16_t planes = readLE16();
|
const uint16_t planes = readLE16();
|
||||||
bpp = readLE16();
|
bpp = readLE16();
|
||||||
const uint32_t comp = readLE32();
|
const uint32_t comp = readLE32();
|
||||||
const bool validBpp =
|
const bool validBpp = bpp == 1 || bpp == 2 || bpp == 8 || bpp == 24 || bpp == 32;
|
||||||
bpp == 1 || bpp == 2 || bpp == 8 || bpp == 24 || bpp == 32;
|
|
||||||
|
|
||||||
if (planes != 1)
|
if (planes != 1) return BmpReaderError::BadPlanes;
|
||||||
return BmpReaderError::BadPlanes;
|
if (!validBpp) return BmpReaderError::UnsupportedBpp;
|
||||||
if (!validBpp)
|
if (!(comp == 0 || (bpp == 32 && comp == 3))) return BmpReaderError::UnsupportedCompression;
|
||||||
return BmpReaderError::UnsupportedBpp;
|
|
||||||
if (!(comp == 0 || (bpp == 32 && comp == 3)))
|
|
||||||
return BmpReaderError::UnsupportedCompression;
|
|
||||||
|
|
||||||
seekCur(12);
|
seekCur(12);
|
||||||
const uint32_t colorsUsed = readLE32();
|
const uint32_t colorsUsed = readLE32();
|
||||||
if (colorsUsed > 256u)
|
if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge;
|
||||||
return BmpReaderError::PaletteTooLarge;
|
|
||||||
seekCur(4);
|
seekCur(4);
|
||||||
|
|
||||||
if (width <= 0 || height <= 0)
|
if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions;
|
||||||
return BmpReaderError::BadDimensions;
|
|
||||||
|
|
||||||
constexpr int MAX_IMAGE_WIDTH = 2048;
|
constexpr int MAX_IMAGE_WIDTH = 2048;
|
||||||
constexpr int MAX_IMAGE_HEIGHT = 3072;
|
constexpr int MAX_IMAGE_HEIGHT = 3072;
|
||||||
@ -177,8 +165,7 @@ BmpReaderError Bitmap::parseHeaders() {
|
|||||||
|
|
||||||
rowBytes = (width * bpp + 31) / 32 * 4;
|
rowBytes = (width * bpp + 31) / 32 * 4;
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++)
|
for (int i = 0; i < 256; i++) paletteLum[i] = static_cast<uint8_t>(i);
|
||||||
paletteLum[i] = static_cast<uint8_t>(i);
|
|
||||||
if (colorsUsed > 0) {
|
if (colorsUsed > 0) {
|
||||||
for (uint32_t i = 0; i < colorsUsed; i++) {
|
for (uint32_t i = 0; i < colorsUsed; i++) {
|
||||||
uint8_t rgb[4];
|
uint8_t rgb[4];
|
||||||
@ -187,8 +174,7 @@ BmpReaderError Bitmap::parseHeaders() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!seekSet(bfOffBits))
|
if (!seekSet(bfOffBits)) return BmpReaderError::SeekPixelDataFailed;
|
||||||
return BmpReaderError::SeekPixelDataFailed;
|
|
||||||
|
|
||||||
if (bpp > 2 && dithering) {
|
if (bpp > 2 && dithering) {
|
||||||
if (USE_ATKINSON) {
|
if (USE_ATKINSON) {
|
||||||
@ -201,12 +187,11 @@ BmpReaderError Bitmap::parseHeaders() {
|
|||||||
return BmpReaderError::Ok;
|
return BmpReaderError::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const {
|
BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const {
|
||||||
if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes)
|
if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes) return BmpReaderError::ShortReadRow;
|
||||||
return BmpReaderError::ShortReadRow;
|
|
||||||
|
|
||||||
prevRowY += 1;
|
prevRowY += 1;
|
||||||
uint8_t *outPtr = data;
|
uint8_t* outPtr = data;
|
||||||
uint8_t currentOutByte = 0;
|
uint8_t currentOutByte = 0;
|
||||||
int bitShift = 6;
|
int bitShift = 6;
|
||||||
int currentX = 0;
|
int currentX = 0;
|
||||||
@ -236,46 +221,44 @@ BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
switch (bpp) {
|
switch (bpp) {
|
||||||
case 32: {
|
case 32: {
|
||||||
const uint8_t *p = rowBuffer;
|
const uint8_t* p = rowBuffer;
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
|
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
|
||||||
packPixel(lum);
|
packPixel(lum);
|
||||||
p += 4;
|
p += 4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 24: {
|
||||||
}
|
const uint8_t* p = rowBuffer;
|
||||||
case 24: {
|
for (int x = 0; x < width; x++) {
|
||||||
const uint8_t *p = rowBuffer;
|
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
|
||||||
for (int x = 0; x < width; x++) {
|
packPixel(lum);
|
||||||
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
|
p += 3;
|
||||||
packPixel(lum);
|
}
|
||||||
p += 3;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 8: {
|
||||||
}
|
for (int x = 0; x < width; x++) packPixel(paletteLum[rowBuffer[x]]);
|
||||||
case 8: {
|
break;
|
||||||
for (int x = 0; x < width; x++)
|
|
||||||
packPixel(paletteLum[rowBuffer[x]]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
uint8_t lum =
|
|
||||||
paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03];
|
|
||||||
packPixel(lum);
|
|
||||||
}
|
}
|
||||||
break;
|
case 2: {
|
||||||
}
|
for (int x = 0; x < width; x++) {
|
||||||
case 1: {
|
uint8_t lum = paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03];
|
||||||
for (int x = 0; x < width; x++) {
|
packPixel(lum);
|
||||||
const uint8_t palIndex = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 1 : 0;
|
}
|
||||||
packPixel(paletteLum[palIndex]);
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 1: {
|
||||||
}
|
for (int x = 0; x < width; x++) {
|
||||||
default:
|
const uint8_t palIndex = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 1 : 0;
|
||||||
return BmpReaderError::UnsupportedBpp;
|
packPixel(paletteLum[palIndex]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return BmpReaderError::UnsupportedBpp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atkinsonDitherer)
|
if (atkinsonDitherer)
|
||||||
@ -283,17 +266,13 @@ BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const {
|
|||||||
else if (fsDitherer)
|
else if (fsDitherer)
|
||||||
fsDitherer->nextRow();
|
fsDitherer->nextRow();
|
||||||
|
|
||||||
if (bitShift != 6)
|
if (bitShift != 6) *outPtr = currentOutByte;
|
||||||
*outPtr = currentOutByte;
|
|
||||||
return BmpReaderError::Ok;
|
return BmpReaderError::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
BmpReaderError Bitmap::rewindToData() const {
|
BmpReaderError Bitmap::rewindToData() const {
|
||||||
if (!seekSet(bfOffBits))
|
if (!seekSet(bfOffBits)) return BmpReaderError::SeekPixelDataFailed;
|
||||||
return BmpReaderError::SeekPixelDataFailed;
|
if (fsDitherer) fsDitherer->reset();
|
||||||
if (fsDitherer)
|
if (atkinsonDitherer) atkinsonDitherer->reset();
|
||||||
fsDitherer->reset();
|
|
||||||
if (atkinsonDitherer)
|
|
||||||
atkinsonDitherer->reset();
|
|
||||||
return BmpReaderError::Ok;
|
return BmpReaderError::Ok;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,18 +29,16 @@ enum class BmpReaderError : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Bitmap {
|
class Bitmap {
|
||||||
public:
|
public:
|
||||||
static const char *errorToString(BmpReaderError err);
|
static const char* errorToString(BmpReaderError err);
|
||||||
|
|
||||||
explicit Bitmap(FsFile &file, bool dithering = false)
|
explicit Bitmap(FsFile& file, bool dithering = false) : file(&file), dithering(dithering) {}
|
||||||
: file(&file), dithering(dithering) {}
|
explicit Bitmap(const uint8_t* buffer, size_t size, bool dithering = false)
|
||||||
explicit Bitmap(const uint8_t *buffer, size_t size, bool dithering = false)
|
: file(nullptr), memoryBuffer(buffer), memorySize(size), dithering(dithering) {}
|
||||||
: file(nullptr), memoryBuffer(buffer), memorySize(size),
|
|
||||||
dithering(dithering) {}
|
|
||||||
|
|
||||||
~Bitmap();
|
~Bitmap();
|
||||||
BmpReaderError parseHeaders();
|
BmpReaderError parseHeaders();
|
||||||
BmpReaderError readNextRow(uint8_t *data, uint8_t *rowBuffer) const;
|
BmpReaderError readNextRow(uint8_t* data, uint8_t* rowBuffer) const;
|
||||||
BmpReaderError rewindToData() const;
|
BmpReaderError rewindToData() const;
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
@ -52,19 +50,19 @@ public:
|
|||||||
bool is1Bit() const { return bpp == 1; }
|
bool is1Bit() const { return bpp == 1; }
|
||||||
uint16_t getBpp() const { return bpp; }
|
uint16_t getBpp() const { return bpp; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Internal IO helpers
|
// Internal IO helpers
|
||||||
int readByte() const;
|
int readByte() const;
|
||||||
size_t readBytes(void *buf, size_t count) const;
|
size_t readBytes(void* buf, size_t count) const;
|
||||||
bool seekSet(uint32_t pos) const;
|
bool seekSet(uint32_t pos) const;
|
||||||
bool seekCur(int32_t offset) const; // Only needed for skip?
|
bool seekCur(int32_t offset) const; // Only needed for skip?
|
||||||
|
|
||||||
uint16_t readLE16();
|
uint16_t readLE16();
|
||||||
uint32_t readLE32();
|
uint32_t readLE32();
|
||||||
|
|
||||||
// Source (one is valid)
|
// Source (one is valid)
|
||||||
FsFile *file = nullptr;
|
FsFile* file = nullptr;
|
||||||
const uint8_t *memoryBuffer = nullptr;
|
const uint8_t* memoryBuffer = nullptr;
|
||||||
size_t memorySize = 0;
|
size_t memorySize = 0;
|
||||||
mutable size_t bufferPos = 0;
|
mutable size_t bufferPos = 0;
|
||||||
|
|
||||||
@ -78,10 +76,10 @@ private:
|
|||||||
uint8_t paletteLum[256] = {};
|
uint8_t paletteLum[256] = {};
|
||||||
|
|
||||||
// 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 prevRowY = -1; // Track row progression for error propagation
|
mutable int prevRowY = -1; // Track row progression for error propagation
|
||||||
|
|
||||||
mutable AtkinsonDitherer *atkinsonDitherer = nullptr;
|
mutable AtkinsonDitherer* atkinsonDitherer = nullptr;
|
||||||
mutable FloydSteinbergDitherer *fsDitherer = nullptr;
|
mutable FloydSteinbergDitherer* fsDitherer = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -8,42 +8,37 @@
|
|||||||
#include "Bitmap.h"
|
#include "Bitmap.h"
|
||||||
|
|
||||||
class GfxRenderer {
|
class GfxRenderer {
|
||||||
public:
|
public:
|
||||||
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||||
|
|
||||||
// Logical screen orientation from the perspective of callers
|
// Logical screen orientation from the perspective of callers
|
||||||
enum Orientation {
|
enum Orientation {
|
||||||
Portrait, // 480x800 logical coordinates (current default)
|
Portrait, // 480x800 logical coordinates (current default)
|
||||||
LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap
|
LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap
|
||||||
// top/bottom)
|
// top/bottom)
|
||||||
PortraitInverted, // 480x800 logical coordinates, inverted
|
PortraitInverted, // 480x800 logical coordinates, inverted
|
||||||
LandscapeCounterClockwise // 800x480 logical coordinates, native panel
|
LandscapeCounterClockwise // 800x480 logical coordinates, native panel
|
||||||
// orientation
|
// orientation
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t BW_BUFFER_CHUNK_SIZE =
|
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
||||||
8000; // 8KB chunks to allow for non-contiguous memory
|
static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
||||||
static constexpr size_t BW_BUFFER_NUM_CHUNKS =
|
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
|
||||||
EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
|
||||||
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS ==
|
|
||||||
EInkDisplay::BUFFER_SIZE,
|
|
||||||
"BW buffer chunking does not line up with display buffer size");
|
"BW buffer chunking does not line up with display buffer size");
|
||||||
|
|
||||||
EInkDisplay &einkDisplay;
|
EInkDisplay& einkDisplay;
|
||||||
RenderMode renderMode;
|
RenderMode renderMode;
|
||||||
Orientation orientation;
|
Orientation orientation;
|
||||||
uint8_t *bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||||
std::map<int, EpdFontFamily> fontMap;
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
void renderChar(const EpdFontFamily &fontFamily, uint32_t cp, int *x,
|
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
||||||
const int *y, bool pixelState,
|
|
||||||
EpdFontFamily::Style style) const;
|
EpdFontFamily::Style style) const;
|
||||||
void freeBwBufferChunks();
|
void freeBwBufferChunks();
|
||||||
void rotateCoordinates(int x, int y, int *rotatedX, int *rotatedY) const;
|
void rotateCoordinates(int x, int y, int* rotatedX, int* rotatedY) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GfxRenderer(EInkDisplay &einkDisplay)
|
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
|
||||||
: einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
|
|
||||||
~GfxRenderer() { freeBwBufferChunks(); }
|
~GfxRenderer() { freeBwBufferChunks(); }
|
||||||
|
|
||||||
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
||||||
@ -62,8 +57,7 @@ public:
|
|||||||
// Screen ops
|
// Screen ops
|
||||||
int getScreenWidth() const;
|
int getScreenWidth() const;
|
||||||
int getScreenHeight() const;
|
int getScreenHeight() const;
|
||||||
void displayBuffer(
|
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||||
EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
|
||||||
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
||||||
void displayWindow(int x, int y, int width, int height) const;
|
void displayWindow(int x, int y, int width, int height) const;
|
||||||
void invertScreen() const;
|
void invertScreen() const;
|
||||||
@ -71,7 +65,7 @@ public:
|
|||||||
|
|
||||||
// Drawing
|
// Drawing
|
||||||
void drawPixel(int x, int y, bool state = true) const;
|
void drawPixel(int x, int y, bool state = true) const;
|
||||||
bool readPixel(int x, int y) const; // Returns true if pixel is black
|
bool readPixel(int x, int y) const; // Returns true if pixel is black
|
||||||
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
||||||
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;
|
||||||
@ -79,67 +73,55 @@ public:
|
|||||||
void drawRoundedRect(int x, int y, int width, int height, int radius, bool state = true) const;
|
void drawRoundedRect(int x, int y, int width, int height, int radius, bool state = true) const;
|
||||||
void fillRoundedRect(int x, int y, int width, int height, int radius, bool state = true) const;
|
void fillRoundedRect(int x, int y, int width, int height, int radius, bool state = true) const;
|
||||||
void fillRoundedRectDithered(int x, int y, int width, int height, int radius, uint8_t grayLevel) const;
|
void fillRoundedRectDithered(int x, int y, int width, int height, int radius, uint8_t grayLevel) const;
|
||||||
void drawImage(const uint8_t bitmap[], int x, int y, int width,
|
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||||
int height) const;
|
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0,
|
||||||
void drawBitmap(const Bitmap &bitmap, int x, int y, int maxWidth,
|
float cropY = 0) const;
|
||||||
int maxHeight, float cropX = 0, float cropY = 0) const;
|
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
||||||
void drawBitmap1Bit(const Bitmap &bitmap, int x, int y, int maxWidth,
|
|
||||||
int maxHeight) const;
|
|
||||||
void draw2BitImage(const uint8_t data[], int x, int y, int w, int h) const;
|
void draw2BitImage(const uint8_t data[], int x, int y, int w, int h) const;
|
||||||
void fillPolygon(const int *xPoints, const int *yPoints, int numPoints,
|
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
|
||||||
bool state = true) const;
|
|
||||||
|
|
||||||
// Region caching - copies a rectangular region to/from a buffer
|
// Region caching - copies a rectangular region to/from a buffer
|
||||||
// Returns allocated buffer on success, nullptr on failure. Caller owns the
|
// Returns allocated buffer on success, nullptr on failure. Caller owns the
|
||||||
// memory.
|
// memory.
|
||||||
uint8_t *captureRegion(int x, int y, int width, int height,
|
uint8_t* captureRegion(int x, int y, int width, int height, size_t* outSize) const;
|
||||||
size_t *outSize) const;
|
|
||||||
// Restores a previously captured region. Buffer must match dimensions.
|
// Restores a previously captured region. Buffer must match dimensions.
|
||||||
void restoreRegion(const uint8_t *buffer, int x, int y, int width,
|
void restoreRegion(const uint8_t* buffer, int x, int y, int width, int height) const;
|
||||||
int height) const;
|
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
int getTextWidth(int fontId, const char *text,
|
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
void drawCenteredText(int fontId, int y, const char* text, bool black = true,
|
||||||
void
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
drawCenteredText(int fontId, int y, const char *text, bool black = true,
|
void drawText(int fontId, int x, int y, const char* text, bool black = true,
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
||||||
void drawText(int fontId, int x, int y, const char *text, bool black = true,
|
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
int getSpaceWidth(int fontId) const;
|
int getSpaceWidth(int fontId) const;
|
||||||
int getFontAscenderSize(int fontId) const;
|
int getFontAscenderSize(int fontId) const;
|
||||||
int getLineHeight(int fontId) const;
|
int getLineHeight(int fontId) const;
|
||||||
std::string
|
std::string truncatedText(int fontId, const char* text, int maxWidth,
|
||||||
truncatedText(int fontId, const char *text, int maxWidth,
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
void drawButtonHints(int fontId, const char *btn1, const char *btn2,
|
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4);
|
||||||
const char *btn3, const char *btn4);
|
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
|
||||||
void drawSideButtonHints(int fontId, const char *topBtn,
|
|
||||||
const char *bottomBtn) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
|
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
|
||||||
void drawTextRotated90CW(
|
void drawTextRotated90CW(int fontId, int x, int y, const char* text, bool black = true,
|
||||||
int fontId, int x, int y, const char *text, bool black = true,
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
||||||
int getTextHeight(int fontId) const;
|
int getTextHeight(int fontId) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Grayscale functions
|
// Grayscale functions
|
||||||
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
||||||
void copyGrayscaleLsbBuffers() const;
|
void copyGrayscaleLsbBuffers() const;
|
||||||
void copyGrayscaleMsbBuffers() const;
|
void copyGrayscaleMsbBuffers() const;
|
||||||
void displayGrayBuffer() const;
|
void displayGrayBuffer() const;
|
||||||
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
||||||
void restoreBwBuffer(); // Restore and free the stored buffer
|
void restoreBwBuffer(); // Restore and free the stored buffer
|
||||||
void cleanupGrayscaleWithFrameBuffer() const;
|
void cleanupGrayscaleWithFrameBuffer() const;
|
||||||
|
|
||||||
// Low level functions
|
// Low level functions
|
||||||
uint8_t *getFrameBuffer() const;
|
uint8_t* getFrameBuffer() const;
|
||||||
static size_t getBufferSize();
|
static size_t getBufferSize();
|
||||||
void grayscaleRevert() const;
|
void grayscaleRevert() const;
|
||||||
void getOrientedViewableTRBL(int *outTop, int *outRight, int *outBottom,
|
void getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const;
|
||||||
int *outLeft) const;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,43 +1,42 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Bitmap.h>
|
||||||
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "ThemeContext.h"
|
#include "ThemeContext.h"
|
||||||
#include "ThemeTypes.h"
|
#include "ThemeTypes.h"
|
||||||
#include "UIElement.h"
|
#include "UIElement.h"
|
||||||
#include <Bitmap.h>
|
|
||||||
#include <SDCardManager.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
// --- Container ---
|
// --- Container ---
|
||||||
class Container : public UIElement {
|
class Container : public UIElement {
|
||||||
protected:
|
protected:
|
||||||
std::vector<UIElement *> children;
|
std::vector<UIElement*> children;
|
||||||
Expression bgColorExpr;
|
Expression bgColorExpr;
|
||||||
bool hasBg = false;
|
bool hasBg = false;
|
||||||
bool border = false;
|
bool border = false;
|
||||||
Expression borderExpr; // Dynamic border based on expression
|
Expression borderExpr; // Dynamic border based on expression
|
||||||
int padding = 0; // Inner padding for children
|
int padding = 0; // Inner padding for children
|
||||||
int borderRadius = 0; // Corner radius (for future rounded rect support)
|
int borderRadius = 0; // Corner radius (for future rounded rect support)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Container(const std::string &id) : UIElement(id) {
|
Container(const std::string& id) : UIElement(id) { bgColorExpr = Expression::parse("0xFF"); }
|
||||||
bgColorExpr = Expression::parse("0xFF");
|
|
||||||
}
|
|
||||||
virtual ~Container() {
|
virtual ~Container() {
|
||||||
for (auto child : children)
|
for (auto child : children) delete child;
|
||||||
delete child;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Container *asContainer() override { return this; }
|
Container* asContainer() override { return this; }
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::Container; }
|
ElementType getType() const override { return ElementType::Container; }
|
||||||
|
|
||||||
void addChild(UIElement *child) { children.push_back(child); }
|
void addChild(UIElement* child) { children.push_back(child); }
|
||||||
|
|
||||||
const std::vector<UIElement *> &getChildren() const { return children; }
|
const std::vector<UIElement*>& getChildren() const { return children; }
|
||||||
|
|
||||||
void setBackgroundColorExpr(const std::string &expr) {
|
void setBackgroundColorExpr(const std::string& expr) {
|
||||||
bgColorExpr = Expression::parse(expr);
|
bgColorExpr = Expression::parse(expr);
|
||||||
hasBg = true;
|
hasBg = true;
|
||||||
markDirty();
|
markDirty();
|
||||||
@ -48,29 +47,28 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBorderExpr(const std::string &expr) {
|
void setBorderExpr(const std::string& expr) {
|
||||||
borderExpr = Expression::parse(expr);
|
borderExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasBorderExpr() const { return !borderExpr.empty(); }
|
bool hasBorderExpr() const { return !borderExpr.empty(); }
|
||||||
|
|
||||||
void setPadding(int p) {
|
void setPadding(int p) {
|
||||||
padding = p;
|
padding = p;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getPadding() const { return padding; }
|
int getPadding() const { return padding; }
|
||||||
|
|
||||||
void setBorderRadius(int r) {
|
void setBorderRadius(int r) {
|
||||||
borderRadius = r;
|
borderRadius = r;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getBorderRadius() const { return borderRadius; }
|
int getBorderRadius() const { return borderRadius; }
|
||||||
|
|
||||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||||
int parentW, int parentH) override {
|
|
||||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||||
// Children are laid out with padding offset
|
// Children are laid out with padding offset
|
||||||
int childX = absX + padding;
|
int childX = absX + padding;
|
||||||
@ -89,9 +87,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (hasBg) {
|
if (hasBg) {
|
||||||
std::string colStr = context.evaluatestring(bgColorExpr);
|
std::string colStr = context.evaluatestring(bgColorExpr);
|
||||||
@ -144,13 +141,11 @@ public:
|
|||||||
// --- Rectangle ---
|
// --- Rectangle ---
|
||||||
class Rectangle : public UIElement {
|
class Rectangle : public UIElement {
|
||||||
bool fill = false;
|
bool fill = false;
|
||||||
Expression fillExpr; // Dynamic fill based on expression
|
Expression fillExpr; // Dynamic fill based on expression
|
||||||
Expression colorExpr;
|
Expression colorExpr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Rectangle(const std::string &id) : UIElement(id) {
|
Rectangle(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
|
||||||
colorExpr = Expression::parse("0x00");
|
|
||||||
}
|
|
||||||
ElementType getType() const override { return ElementType::Rectangle; }
|
ElementType getType() const override { return ElementType::Rectangle; }
|
||||||
|
|
||||||
void setFill(bool f) {
|
void setFill(bool f) {
|
||||||
@ -158,19 +153,18 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFillExpr(const std::string &expr) {
|
void setFillExpr(const std::string& expr) {
|
||||||
fillExpr = Expression::parse(expr);
|
fillExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColorExpr(const std::string &c) {
|
void setColorExpr(const std::string& c) {
|
||||||
colorExpr = Expression::parse(c);
|
colorExpr = Expression::parse(c);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
std::string colStr = context.evaluatestring(colorExpr);
|
std::string colStr = context.evaluatestring(colorExpr);
|
||||||
uint8_t color = Color::parse(colStr).value;
|
uint8_t color = Color::parse(colStr).value;
|
||||||
@ -193,24 +187,22 @@ public:
|
|||||||
|
|
||||||
// --- Label ---
|
// --- Label ---
|
||||||
class Label : public UIElement {
|
class Label : public UIElement {
|
||||||
public:
|
public:
|
||||||
enum class Alignment { Left, Center, Right };
|
enum class Alignment { Left, Center, Right };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Expression textExpr;
|
Expression textExpr;
|
||||||
int fontId = 0;
|
int fontId = 0;
|
||||||
Alignment alignment = Alignment::Left;
|
Alignment alignment = Alignment::Left;
|
||||||
Expression colorExpr;
|
Expression colorExpr;
|
||||||
int maxLines = 1; // For multi-line support
|
int maxLines = 1; // For multi-line support
|
||||||
bool ellipsis = true; // Truncate with ... if too long
|
bool ellipsis = true; // Truncate with ... if too long
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Label(const std::string &id) : UIElement(id) {
|
Label(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
|
||||||
colorExpr = Expression::parse("0x00");
|
|
||||||
}
|
|
||||||
ElementType getType() const override { return ElementType::Label; }
|
ElementType getType() const override { return ElementType::Label; }
|
||||||
|
|
||||||
void setText(const std::string &expr) {
|
void setText(const std::string& expr) {
|
||||||
textExpr = Expression::parse(expr);
|
textExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -226,7 +218,7 @@ public:
|
|||||||
alignment = c ? Alignment::Center : Alignment::Left;
|
alignment = c ? Alignment::Center : Alignment::Left;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setColorExpr(const std::string &c) {
|
void setColorExpr(const std::string& c) {
|
||||||
colorExpr = Expression::parse(c);
|
colorExpr = Expression::parse(c);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -239,9 +231,8 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
std::string finalText = context.evaluatestring(textExpr);
|
std::string finalText = context.evaluatestring(textExpr);
|
||||||
if (finalText.empty()) {
|
if (finalText.empty()) {
|
||||||
@ -288,13 +279,13 @@ class BitmapElement : public UIElement {
|
|||||||
bool scaleToFit = true;
|
bool scaleToFit = true;
|
||||||
bool preserveAspect = true;
|
bool preserveAspect = true;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BitmapElement(const std::string &id) : UIElement(id) {
|
BitmapElement(const std::string& id) : UIElement(id) {
|
||||||
cacheable = true; // Bitmaps benefit from caching
|
cacheable = true; // Bitmaps benefit from caching
|
||||||
}
|
}
|
||||||
ElementType getType() const override { return ElementType::Bitmap; }
|
ElementType getType() const override { return ElementType::Bitmap; }
|
||||||
|
|
||||||
void setSrc(const std::string &src) {
|
void setSrc(const std::string& src) {
|
||||||
srcExpr = Expression::parse(src);
|
srcExpr = Expression::parse(src);
|
||||||
invalidateCache();
|
invalidateCache();
|
||||||
}
|
}
|
||||||
@ -309,41 +300,41 @@ public:
|
|||||||
invalidateCache();
|
invalidateCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override;
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- ProgressBar ---
|
// --- ProgressBar ---
|
||||||
class ProgressBar : public UIElement {
|
class ProgressBar : public UIElement {
|
||||||
Expression valueExpr; // Current value (0-100 or 0-max)
|
Expression valueExpr; // Current value (0-100 or 0-max)
|
||||||
Expression maxExpr; // Max value (default 100)
|
Expression maxExpr; // Max value (default 100)
|
||||||
Expression fgColorExpr; // Foreground color
|
Expression fgColorExpr; // Foreground color
|
||||||
Expression bgColorExpr; // Background color
|
Expression bgColorExpr; // Background color
|
||||||
bool showBorder = true;
|
bool showBorder = true;
|
||||||
int borderWidth = 1;
|
int borderWidth = 1;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ProgressBar(const std::string &id) : UIElement(id) {
|
ProgressBar(const std::string& id) : UIElement(id) {
|
||||||
valueExpr = Expression::parse("0");
|
valueExpr = Expression::parse("0");
|
||||||
maxExpr = Expression::parse("100");
|
maxExpr = Expression::parse("100");
|
||||||
fgColorExpr = Expression::parse("0x00"); // Black fill
|
fgColorExpr = Expression::parse("0x00"); // Black fill
|
||||||
bgColorExpr = Expression::parse("0xFF"); // White background
|
bgColorExpr = Expression::parse("0xFF"); // White background
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::ProgressBar; }
|
ElementType getType() const override { return ElementType::ProgressBar; }
|
||||||
|
|
||||||
void setValue(const std::string &expr) {
|
void setValue(const std::string& expr) {
|
||||||
valueExpr = Expression::parse(expr);
|
valueExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setMax(const std::string &expr) {
|
void setMax(const std::string& expr) {
|
||||||
maxExpr = Expression::parse(expr);
|
maxExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setFgColor(const std::string &expr) {
|
void setFgColor(const std::string& expr) {
|
||||||
fgColorExpr = Expression::parse(expr);
|
fgColorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setBgColor(const std::string &expr) {
|
void setBgColor(const std::string& expr) {
|
||||||
bgColorExpr = Expression::parse(expr);
|
bgColorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -352,23 +343,19 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
std::string valStr = context.evaluatestring(valueExpr);
|
std::string valStr = context.evaluatestring(valueExpr);
|
||||||
std::string maxStr = context.evaluatestring(maxExpr);
|
std::string maxStr = context.evaluatestring(maxExpr);
|
||||||
|
|
||||||
int value = valStr.empty() ? 0 : std::stoi(valStr);
|
int value = valStr.empty() ? 0 : std::stoi(valStr);
|
||||||
int maxVal = maxStr.empty() ? 100 : std::stoi(maxStr);
|
int maxVal = maxStr.empty() ? 100 : std::stoi(maxStr);
|
||||||
if (maxVal <= 0)
|
if (maxVal <= 0) maxVal = 100;
|
||||||
maxVal = 100;
|
|
||||||
|
|
||||||
float ratio = static_cast<float>(value) / static_cast<float>(maxVal);
|
float ratio = static_cast<float>(value) / static_cast<float>(maxVal);
|
||||||
if (ratio < 0)
|
if (ratio < 0) ratio = 0;
|
||||||
ratio = 0;
|
if (ratio > 1) ratio = 1;
|
||||||
if (ratio > 1)
|
|
||||||
ratio = 1;
|
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
std::string bgStr = context.evaluatestring(bgColorExpr);
|
std::string bgStr = context.evaluatestring(bgColorExpr);
|
||||||
@ -398,14 +385,12 @@ class Divider : public UIElement {
|
|||||||
bool horizontal = true;
|
bool horizontal = true;
|
||||||
int thickness = 1;
|
int thickness = 1;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Divider(const std::string &id) : UIElement(id) {
|
Divider(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
|
||||||
colorExpr = Expression::parse("0x00");
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::Divider; }
|
ElementType getType() const override { return ElementType::Divider; }
|
||||||
|
|
||||||
void setColorExpr(const std::string &expr) {
|
void setColorExpr(const std::string& expr) {
|
||||||
colorExpr = Expression::parse(expr);
|
colorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -418,9 +403,8 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
std::string colStr = context.evaluatestring(colorExpr);
|
std::string colStr = context.evaluatestring(colorExpr);
|
||||||
uint8_t color = Color::parse(colStr).value;
|
uint8_t color = Color::parse(colStr).value;
|
||||||
@ -445,27 +429,26 @@ class BatteryIcon : public UIElement {
|
|||||||
Expression valueExpr;
|
Expression valueExpr;
|
||||||
Expression colorExpr;
|
Expression colorExpr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BatteryIcon(const std::string &id) : UIElement(id) {
|
BatteryIcon(const std::string& id) : UIElement(id) {
|
||||||
valueExpr = Expression::parse("0");
|
valueExpr = Expression::parse("0");
|
||||||
colorExpr = Expression::parse("0x00"); // Black by default
|
colorExpr = Expression::parse("0x00"); // Black by default
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::BatteryIcon; }
|
ElementType getType() const override { return ElementType::BatteryIcon; }
|
||||||
|
|
||||||
void setValue(const std::string &expr) {
|
void setValue(const std::string& expr) {
|
||||||
valueExpr = Expression::parse(expr);
|
valueExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColor(const std::string &expr) {
|
void setColor(const std::string& expr) {
|
||||||
colorExpr = Expression::parse(expr);
|
colorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
std::string valStr = context.evaluatestring(valueExpr);
|
std::string valStr = context.evaluatestring(valueExpr);
|
||||||
int percentage = valStr.empty() ? 0 : std::stoi(valStr);
|
int percentage = valStr.empty() ? 0 : std::stoi(valStr);
|
||||||
@ -480,22 +463,17 @@ public:
|
|||||||
int x = absX;
|
int x = absX;
|
||||||
int y = absY;
|
int y = absY;
|
||||||
|
|
||||||
if (absW > batteryWidth)
|
if (absW > batteryWidth) x += (absW - batteryWidth) / 2;
|
||||||
x += (absW - batteryWidth) / 2;
|
if (absH > batteryHeight) y += (absH - batteryHeight) / 2;
|
||||||
if (absH > batteryHeight)
|
|
||||||
y += (absH - batteryHeight) / 2;
|
|
||||||
|
|
||||||
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y, black);
|
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y, black);
|
||||||
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3,
|
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1, black);
|
||||||
y + batteryHeight - 1, black);
|
|
||||||
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2, black);
|
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2, black);
|
||||||
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2,
|
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2, black);
|
||||||
y + batteryHeight - 2, black);
|
|
||||||
|
|
||||||
renderer.drawPixel(x + batteryWidth - 1, y + 3, black);
|
renderer.drawPixel(x + batteryWidth - 1, y + 3, black);
|
||||||
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4, black);
|
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4, black);
|
||||||
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0,
|
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5, black);
|
||||||
y + batteryHeight - 5, black);
|
|
||||||
|
|
||||||
if (percentage > 0) {
|
if (percentage > 0) {
|
||||||
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
|
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
|
||||||
@ -509,4 +487,4 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
// Use static function for C++14 ODR compatibility
|
// Use static function for C++14 ODR compatibility
|
||||||
static const char *getDefaultThemeIni() {
|
static const char* getDefaultThemeIni() {
|
||||||
static const char *theme = R"INI(
|
static const char* theme = R"INI(
|
||||||
; ============================================
|
; ============================================
|
||||||
; DEFAULT THEME - Original CrossPoint Reader
|
; DEFAULT THEME - Original CrossPoint Reader
|
||||||
; ============================================
|
; ============================================
|
||||||
@ -275,4 +275,4 @@ Align = center
|
|||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -24,17 +24,15 @@ struct IniSection {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class IniParser {
|
class IniParser {
|
||||||
public:
|
public:
|
||||||
// Parse a stream (File, Serial, etc.)
|
// Parse a stream (File, Serial, etc.)
|
||||||
static std::map<std::string, std::map<std::string, std::string>>
|
static std::map<std::string, std::map<std::string, std::string>> parse(Stream& stream);
|
||||||
parse(Stream &stream);
|
|
||||||
|
|
||||||
// Parse a string buffer (useful for testing)
|
// Parse a string buffer (useful for testing)
|
||||||
static std::map<std::string, std::map<std::string, std::string>>
|
static std::map<std::string, std::map<std::string, std::string>> parseString(const std::string& content);
|
||||||
parseString(const std::string &content);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void trim(std::string &s);
|
static void trim(std::string& s);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,22 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "BasicElements.h"
|
#include "BasicElements.h"
|
||||||
#include "ThemeContext.h"
|
#include "ThemeContext.h"
|
||||||
#include "ThemeTypes.h"
|
#include "ThemeTypes.h"
|
||||||
#include "UIElement.h"
|
#include "UIElement.h"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
// --- HStack: Horizontal Stack Layout ---
|
// --- HStack: Horizontal Stack Layout ---
|
||||||
// Children are arranged horizontally with optional spacing
|
// Children are arranged horizontally with optional spacing
|
||||||
class HStack : public Container {
|
class HStack : public Container {
|
||||||
int spacing = 0; // Gap between children
|
int spacing = 0; // Gap between children
|
||||||
int padding = 0; // Internal padding
|
int padding = 0; // Internal padding
|
||||||
bool centerVertical = false;
|
bool centerVertical = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HStack(const std::string &id) : Container(id) {}
|
HStack(const std::string& id) : Container(id) {}
|
||||||
ElementType getType() const override { return ElementType::HStack; }
|
ElementType getType() const override { return ElementType::HStack; }
|
||||||
|
|
||||||
void setSpacing(int s) {
|
void setSpacing(int s) {
|
||||||
@ -32,8 +33,7 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||||
int parentW, int parentH) override {
|
|
||||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||||
|
|
||||||
int currentX = absX + padding;
|
int currentX = absX + padding;
|
||||||
@ -68,8 +68,8 @@ class VStack : public Container {
|
|||||||
int padding = 0;
|
int padding = 0;
|
||||||
bool centerHorizontal = false;
|
bool centerHorizontal = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VStack(const std::string &id) : Container(id) {}
|
VStack(const std::string& id) : Container(id) {}
|
||||||
ElementType getType() const override { return ElementType::VStack; }
|
ElementType getType() const override { return ElementType::VStack; }
|
||||||
|
|
||||||
void setSpacing(int s) {
|
void setSpacing(int s) {
|
||||||
@ -85,8 +85,7 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||||
int parentW, int parentH) override {
|
|
||||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||||
|
|
||||||
int currentY = absY + padding;
|
int currentY = absY + padding;
|
||||||
@ -120,8 +119,8 @@ class Grid : public Container {
|
|||||||
int colSpacing = 10;
|
int colSpacing = 10;
|
||||||
int padding = 0;
|
int padding = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Grid(const std::string &id) : Container(id) {}
|
Grid(const std::string& id) : Container(id) {}
|
||||||
ElementType getType() const override { return ElementType::Grid; }
|
ElementType getType() const override { return ElementType::Grid; }
|
||||||
|
|
||||||
void setColumns(int c) {
|
void setColumns(int c) {
|
||||||
@ -141,12 +140,10 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||||
int parentW, int parentH) override {
|
|
||||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||||
|
|
||||||
if (children.empty())
|
if (children.empty()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
int availableW = absW - 2 * padding - (columns - 1) * colSpacing;
|
int availableW = absW - 2 * padding - (columns - 1) * colSpacing;
|
||||||
int cellW = availableW / columns;
|
int cellW = availableW / columns;
|
||||||
@ -162,8 +159,7 @@ public:
|
|||||||
// Pass cell dimensions to avoid clamping issues
|
// Pass cell dimensions to avoid clamping issues
|
||||||
child->layout(context, cellX, currentY, cellW, availableH);
|
child->layout(context, cellX, currentY, cellW, availableH);
|
||||||
int childH = child->getAbsH();
|
int childH = child->getAbsH();
|
||||||
if (childH > maxRowHeight)
|
if (childH > maxRowHeight) maxRowHeight = childH;
|
||||||
maxRowHeight = childH;
|
|
||||||
|
|
||||||
col++;
|
col++;
|
||||||
if (col >= columns) {
|
if (col >= columns) {
|
||||||
@ -184,27 +180,27 @@ class Badge : public UIElement {
|
|||||||
Expression bgColorExpr;
|
Expression bgColorExpr;
|
||||||
Expression fgColorExpr;
|
Expression fgColorExpr;
|
||||||
int fontId = 0;
|
int fontId = 0;
|
||||||
int paddingH = 8; // Horizontal padding
|
int paddingH = 8; // Horizontal padding
|
||||||
int paddingV = 4; // Vertical padding
|
int paddingV = 4; // Vertical padding
|
||||||
int cornerRadius = 0;
|
int cornerRadius = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Badge(const std::string &id) : UIElement(id) {
|
Badge(const std::string& id) : UIElement(id) {
|
||||||
bgColorExpr = Expression::parse("0x00"); // Black background
|
bgColorExpr = Expression::parse("0x00"); // Black background
|
||||||
fgColorExpr = Expression::parse("0xFF"); // White text
|
fgColorExpr = Expression::parse("0xFF"); // White text
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::Badge; }
|
ElementType getType() const override { return ElementType::Badge; }
|
||||||
|
|
||||||
void setText(const std::string &expr) {
|
void setText(const std::string& expr) {
|
||||||
textExpr = Expression::parse(expr);
|
textExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setBgColor(const std::string &expr) {
|
void setBgColor(const std::string& expr) {
|
||||||
bgColorExpr = Expression::parse(expr);
|
bgColorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setFgColor(const std::string &expr) {
|
void setFgColor(const std::string& expr) {
|
||||||
fgColorExpr = Expression::parse(expr);
|
fgColorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -221,9 +217,8 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
std::string text = context.evaluatestring(textExpr);
|
std::string text = context.evaluatestring(textExpr);
|
||||||
if (text.empty()) {
|
if (text.empty()) {
|
||||||
@ -238,10 +233,8 @@ public:
|
|||||||
int badgeH = textH + 2 * paddingV;
|
int badgeH = textH + 2 * paddingV;
|
||||||
|
|
||||||
// Use absX, absY as position, but we may auto-size
|
// Use absX, absY as position, but we may auto-size
|
||||||
if (absW == 0)
|
if (absW == 0) absW = badgeW;
|
||||||
absW = badgeW;
|
if (absH == 0) absH = badgeH;
|
||||||
if (absH == 0)
|
|
||||||
absH = badgeH;
|
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
std::string bgStr = context.evaluatestring(bgColorExpr);
|
std::string bgStr = context.evaluatestring(bgColorExpr);
|
||||||
@ -264,31 +257,31 @@ public:
|
|||||||
|
|
||||||
// --- Toggle: On/Off Switch ---
|
// --- Toggle: On/Off Switch ---
|
||||||
class Toggle : public UIElement {
|
class Toggle : public UIElement {
|
||||||
Expression valueExpr; // Boolean expression
|
Expression valueExpr; // Boolean expression
|
||||||
Expression onColorExpr;
|
Expression onColorExpr;
|
||||||
Expression offColorExpr;
|
Expression offColorExpr;
|
||||||
int trackWidth = 44;
|
int trackWidth = 44;
|
||||||
int trackHeight = 24;
|
int trackHeight = 24;
|
||||||
int knobSize = 20;
|
int knobSize = 20;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Toggle(const std::string &id) : UIElement(id) {
|
Toggle(const std::string& id) : UIElement(id) {
|
||||||
valueExpr = Expression::parse("false");
|
valueExpr = Expression::parse("false");
|
||||||
onColorExpr = Expression::parse("0x00"); // Black when on
|
onColorExpr = Expression::parse("0x00"); // Black when on
|
||||||
offColorExpr = Expression::parse("0xFF"); // White when off
|
offColorExpr = Expression::parse("0xFF"); // White when off
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::Toggle; }
|
ElementType getType() const override { return ElementType::Toggle; }
|
||||||
|
|
||||||
void setValue(const std::string &expr) {
|
void setValue(const std::string& expr) {
|
||||||
valueExpr = Expression::parse(expr);
|
valueExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setOnColor(const std::string &expr) {
|
void setOnColor(const std::string& expr) {
|
||||||
onColorExpr = Expression::parse(expr);
|
onColorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setOffColor(const std::string &expr) {
|
void setOffColor(const std::string& expr) {
|
||||||
offColorExpr = Expression::parse(expr);
|
offColorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -305,15 +298,13 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
bool isOn = context.evaluateBool(valueExpr.rawExpr);
|
bool isOn = context.evaluateBool(valueExpr.rawExpr);
|
||||||
|
|
||||||
// Get colors
|
// Get colors
|
||||||
std::string colorStr =
|
std::string colorStr = isOn ? context.evaluatestring(onColorExpr) : context.evaluatestring(offColorExpr);
|
||||||
isOn ? context.evaluatestring(onColorExpr) : context.evaluatestring(offColorExpr);
|
|
||||||
uint8_t trackColor = Color::parse(colorStr).value;
|
uint8_t trackColor = Color::parse(colorStr).value;
|
||||||
|
|
||||||
// Draw track
|
// Draw track
|
||||||
@ -324,8 +315,7 @@ public:
|
|||||||
|
|
||||||
// Draw knob
|
// Draw knob
|
||||||
int knobMargin = (trackHeight - knobSize) / 2;
|
int knobMargin = (trackHeight - knobSize) / 2;
|
||||||
int knobX = isOn ? (trackX + trackWidth - knobSize - knobMargin)
|
int knobX = isOn ? (trackX + trackWidth - knobSize - knobMargin) : (trackX + knobMargin);
|
||||||
: (trackX + knobMargin);
|
|
||||||
int knobY = trackY + knobMargin;
|
int knobY = trackY + knobMargin;
|
||||||
|
|
||||||
// Knob is opposite color of track
|
// Knob is opposite color of track
|
||||||
@ -338,17 +328,17 @@ public:
|
|||||||
|
|
||||||
// --- TabBar: Horizontal tab selection ---
|
// --- TabBar: Horizontal tab selection ---
|
||||||
class TabBar : public Container {
|
class TabBar : public Container {
|
||||||
Expression selectedExpr; // Currently selected tab index or name
|
Expression selectedExpr; // Currently selected tab index or name
|
||||||
int tabSpacing = 0;
|
int tabSpacing = 0;
|
||||||
int padding = 0;
|
int padding = 0;
|
||||||
int indicatorHeight = 3;
|
int indicatorHeight = 3;
|
||||||
bool showIndicator = true;
|
bool showIndicator = true;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TabBar(const std::string &id) : Container(id) {}
|
TabBar(const std::string& id) : Container(id) {}
|
||||||
ElementType getType() const override { return ElementType::TabBar; }
|
ElementType getType() const override { return ElementType::TabBar; }
|
||||||
|
|
||||||
void setSelected(const std::string &expr) {
|
void setSelected(const std::string& expr) {
|
||||||
selectedExpr = Expression::parse(expr);
|
selectedExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -369,12 +359,10 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||||
int parentW, int parentH) override {
|
|
||||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||||
|
|
||||||
if (children.empty())
|
if (children.empty()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// Distribute tabs evenly
|
// Distribute tabs evenly
|
||||||
int numTabs = children.size();
|
int numTabs = children.size();
|
||||||
@ -389,9 +377,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// Draw background if set
|
// Draw background if set
|
||||||
if (hasBg) {
|
if (hasBg) {
|
||||||
@ -419,7 +406,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectedIdx >= 0 && selectedIdx < static_cast<int>(children.size())) {
|
if (selectedIdx >= 0 && selectedIdx < static_cast<int>(children.size())) {
|
||||||
UIElement *tab = children[selectedIdx];
|
UIElement* tab = children[selectedIdx];
|
||||||
int indX = tab->getAbsX();
|
int indX = tab->getAbsX();
|
||||||
int indY = absY + absH - indicatorHeight;
|
int indY = absY + absH - indicatorHeight;
|
||||||
int indW = tab->getAbsW();
|
int indW = tab->getAbsW();
|
||||||
@ -437,25 +424,25 @@ public:
|
|||||||
// --- Icon: Small symbolic image ---
|
// --- Icon: Small symbolic image ---
|
||||||
// Can be a built-in icon name or a path to a BMP
|
// Can be a built-in icon name or a path to a BMP
|
||||||
class Icon : public UIElement {
|
class Icon : public UIElement {
|
||||||
Expression srcExpr; // Icon name or path
|
Expression srcExpr; // Icon name or path
|
||||||
Expression colorExpr;
|
Expression colorExpr;
|
||||||
int iconSize = 24;
|
int iconSize = 24;
|
||||||
|
|
||||||
// Built-in icon names and their simple representations
|
// Built-in icon names and their simple representations
|
||||||
// In a real implementation, these would be actual bitmap data
|
// In a real implementation, these would be actual bitmap data
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Icon(const std::string &id) : UIElement(id) {
|
Icon(const std::string& id) : UIElement(id) {
|
||||||
colorExpr = Expression::parse("0x00"); // Black by default
|
colorExpr = Expression::parse("0x00"); // Black by default
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::Icon; }
|
ElementType getType() const override { return ElementType::Icon; }
|
||||||
|
|
||||||
void setSrc(const std::string &expr) {
|
void setSrc(const std::string& expr) {
|
||||||
srcExpr = Expression::parse(expr);
|
srcExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setColorExpr(const std::string &expr) {
|
void setColorExpr(const std::string& expr) {
|
||||||
colorExpr = Expression::parse(expr);
|
colorExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -464,18 +451,18 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override;
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- ScrollIndicator: Visual scroll position ---
|
// --- ScrollIndicator: Visual scroll position ---
|
||||||
class ScrollIndicator : public UIElement {
|
class ScrollIndicator : public UIElement {
|
||||||
Expression positionExpr; // 0.0 to 1.0
|
Expression positionExpr; // 0.0 to 1.0
|
||||||
Expression totalExpr; // Total items
|
Expression totalExpr; // Total items
|
||||||
Expression visibleExpr; // Visible items
|
Expression visibleExpr; // Visible items
|
||||||
int trackWidth = 4;
|
int trackWidth = 4;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScrollIndicator(const std::string &id) : UIElement(id) {
|
ScrollIndicator(const std::string& id) : UIElement(id) {
|
||||||
positionExpr = Expression::parse("0");
|
positionExpr = Expression::parse("0");
|
||||||
totalExpr = Expression::parse("1");
|
totalExpr = Expression::parse("1");
|
||||||
visibleExpr = Expression::parse("1");
|
visibleExpr = Expression::parse("1");
|
||||||
@ -483,15 +470,15 @@ public:
|
|||||||
|
|
||||||
ElementType getType() const override { return ElementType::ScrollIndicator; }
|
ElementType getType() const override { return ElementType::ScrollIndicator; }
|
||||||
|
|
||||||
void setPosition(const std::string &expr) {
|
void setPosition(const std::string& expr) {
|
||||||
positionExpr = Expression::parse(expr);
|
positionExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setTotal(const std::string &expr) {
|
void setTotal(const std::string& expr) {
|
||||||
totalExpr = Expression::parse(expr);
|
totalExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setVisibleCount(const std::string &expr) {
|
void setVisibleCount(const std::string& expr) {
|
||||||
visibleExpr = Expression::parse(expr);
|
visibleExpr = Expression::parse(expr);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@ -500,9 +487,8 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override {
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||||
if (!isVisible(context))
|
if (!isVisible(context)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// Get values
|
// Get values
|
||||||
std::string posStr = context.evaluatestring(positionExpr);
|
std::string posStr = context.evaluatestring(positionExpr);
|
||||||
@ -526,8 +512,7 @@ public:
|
|||||||
// Calculate thumb size and position
|
// Calculate thumb size and position
|
||||||
float ratio = static_cast<float>(visible) / static_cast<float>(total);
|
float ratio = static_cast<float>(visible) / static_cast<float>(total);
|
||||||
int thumbH = static_cast<int>(absH * ratio);
|
int thumbH = static_cast<int>(absH * ratio);
|
||||||
if (thumbH < 20)
|
if (thumbH < 20) thumbH = 20; // Minimum thumb size
|
||||||
thumbH = 20; // Minimum thumb size
|
|
||||||
|
|
||||||
int maxScroll = total - visible;
|
int maxScroll = total - visible;
|
||||||
float scrollRatio = maxScroll > 0 ? position / maxScroll : 0;
|
float scrollRatio = maxScroll > 0 ? position / maxScroll : 0;
|
||||||
@ -540,4 +525,4 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,57 +1,58 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "BasicElements.h"
|
#include "BasicElements.h"
|
||||||
#include "UIElement.h"
|
#include "UIElement.h"
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
// --- List ---
|
// --- List ---
|
||||||
// Supports vertical, horizontal, and grid layouts
|
// Supports vertical, horizontal, and grid layouts
|
||||||
class List : public Container {
|
class List : public Container {
|
||||||
public:
|
public:
|
||||||
enum class Direction { Vertical, Horizontal };
|
enum class Direction { Vertical, Horizontal };
|
||||||
enum class LayoutMode { List, Grid };
|
enum class LayoutMode { List, Grid };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string source; // Data source name (e.g., "MainMenu", "FileList")
|
std::string source; // Data source name (e.g., "MainMenu", "FileList")
|
||||||
std::string itemTemplateId; // ID of the template element
|
std::string itemTemplateId; // ID of the template element
|
||||||
int itemWidth = 0; // Explicit item width (0 = auto)
|
int itemWidth = 0; // Explicit item width (0 = auto)
|
||||||
int itemHeight = 0; // Explicit item height (0 = auto from template)
|
int itemHeight = 0; // Explicit item height (0 = auto from template)
|
||||||
int scrollOffset = 0; // Scroll position for long lists
|
int scrollOffset = 0; // Scroll position for long lists
|
||||||
int visibleItems = -1; // Max visible items (-1 = auto)
|
int visibleItems = -1; // Max visible items (-1 = auto)
|
||||||
int spacing = 0; // Gap between items
|
int spacing = 0; // Gap between items
|
||||||
int columns = 1; // Number of columns (for grid mode)
|
int columns = 1; // Number of columns (for grid mode)
|
||||||
Direction direction = Direction::Vertical;
|
Direction direction = Direction::Vertical;
|
||||||
LayoutMode layoutMode = LayoutMode::List;
|
LayoutMode layoutMode = LayoutMode::List;
|
||||||
|
|
||||||
// Template element reference (resolved after loading)
|
// Template element reference (resolved after loading)
|
||||||
UIElement *itemTemplate = nullptr;
|
UIElement* itemTemplate = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
List(const std::string &id) : Container(id) {}
|
List(const std::string& id) : Container(id) {}
|
||||||
|
|
||||||
ElementType getType() const override { return ElementType::List; }
|
ElementType getType() const override { return ElementType::List; }
|
||||||
|
|
||||||
void setSource(const std::string &s) {
|
void setSource(const std::string& s) {
|
||||||
source = s;
|
source = s;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &getSource() const { return source; }
|
const std::string& getSource() const { return source; }
|
||||||
|
|
||||||
void setItemTemplateId(const std::string &id) {
|
void setItemTemplateId(const std::string& id) {
|
||||||
itemTemplateId = id;
|
itemTemplateId = id;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setItemTemplate(UIElement *elem) {
|
void setItemTemplate(UIElement* elem) {
|
||||||
itemTemplate = elem;
|
itemTemplate = elem;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
UIElement *getItemTemplate() const { return itemTemplate; }
|
UIElement* getItemTemplate() const { return itemTemplate; }
|
||||||
|
|
||||||
void setItemWidth(int w) {
|
void setItemWidth(int w) {
|
||||||
itemWidth = w;
|
itemWidth = w;
|
||||||
@ -64,18 +65,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
int getItemHeight() const {
|
int getItemHeight() const {
|
||||||
if (itemHeight > 0)
|
if (itemHeight > 0) return itemHeight;
|
||||||
return itemHeight;
|
if (itemTemplate) return itemTemplate->getAbsH() > 0 ? itemTemplate->getAbsH() : 45;
|
||||||
if (itemTemplate)
|
|
||||||
return itemTemplate->getAbsH() > 0 ? itemTemplate->getAbsH() : 45;
|
|
||||||
return 45;
|
return 45;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getItemWidth() const {
|
int getItemWidth() const {
|
||||||
if (itemWidth > 0)
|
if (itemWidth > 0) return itemWidth;
|
||||||
return itemWidth;
|
if (itemTemplate) return itemTemplate->getAbsW() > 0 ? itemTemplate->getAbsW() : 100;
|
||||||
if (itemTemplate)
|
|
||||||
return itemTemplate->getAbsW() > 0 ? itemTemplate->getAbsW() : 100;
|
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,8 +95,7 @@ public:
|
|||||||
|
|
||||||
void setColumns(int c) {
|
void setColumns(int c) {
|
||||||
columns = c > 0 ? c : 1;
|
columns = c > 0 ? c : 1;
|
||||||
if (columns > 1)
|
if (columns > 1) layoutMode = LayoutMode::Grid;
|
||||||
layoutMode = LayoutMode::Grid;
|
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +104,7 @@ public:
|
|||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDirectionFromString(const std::string &dir) {
|
void setDirectionFromString(const std::string& dir) {
|
||||||
if (dir == "Horizontal" || dir == "horizontal" || dir == "row") {
|
if (dir == "Horizontal" || dir == "horizontal" || dir == "row") {
|
||||||
direction = Direction::Horizontal;
|
direction = Direction::Horizontal;
|
||||||
} else {
|
} else {
|
||||||
@ -123,14 +119,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve template reference from element map
|
// Resolve template reference from element map
|
||||||
void resolveTemplate(const std::map<std::string, UIElement *> &elements) {
|
void resolveTemplate(const std::map<std::string, UIElement*>& elements) {
|
||||||
if (elements.count(itemTemplateId)) {
|
if (elements.count(itemTemplateId)) {
|
||||||
itemTemplate = elements.at(itemTemplateId);
|
itemTemplate = elements.at(itemTemplateId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||||
int parentW, int parentH) override {
|
|
||||||
// Layout self first (bounds)
|
// Layout self first (bounds)
|
||||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||||
|
|
||||||
@ -142,7 +137,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw is implemented in BasicElements.cpp
|
// Draw is implemented in BasicElements.cpp
|
||||||
void draw(const GfxRenderer &renderer, const ThemeContext &context) override;
|
void draw(const GfxRenderer& renderer, const ThemeContext& context) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -11,22 +11,21 @@ namespace ThemeEngine {
|
|||||||
struct ExpressionToken {
|
struct ExpressionToken {
|
||||||
enum Type { LITERAL, VARIABLE };
|
enum Type { LITERAL, VARIABLE };
|
||||||
Type type;
|
Type type;
|
||||||
std::string value; // Literal text or variable name
|
std::string value; // Literal text or variable name
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pre-parsed expression for efficient repeated evaluation
|
// Pre-parsed expression for efficient repeated evaluation
|
||||||
struct Expression {
|
struct Expression {
|
||||||
std::vector<ExpressionToken> tokens;
|
std::vector<ExpressionToken> tokens;
|
||||||
std::string rawExpr; // Original expression string for complex evaluation
|
std::string rawExpr; // Original expression string for complex evaluation
|
||||||
|
|
||||||
bool empty() const { return tokens.empty() && rawExpr.empty(); }
|
bool empty() const { return tokens.empty() && rawExpr.empty(); }
|
||||||
|
|
||||||
static Expression parse(const std::string &str) {
|
static Expression parse(const std::string& str) {
|
||||||
Expression expr;
|
Expression expr;
|
||||||
expr.rawExpr = str;
|
expr.rawExpr = str;
|
||||||
|
|
||||||
if (str.empty())
|
if (str.empty()) return expr;
|
||||||
return expr;
|
|
||||||
|
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
while (start < str.length()) {
|
while (start < str.length()) {
|
||||||
@ -39,8 +38,7 @@ struct Expression {
|
|||||||
|
|
||||||
if (open > start) {
|
if (open > start) {
|
||||||
// Literal before variable
|
// Literal before variable
|
||||||
expr.tokens.push_back(
|
expr.tokens.push_back({ExpressionToken::LITERAL, str.substr(start, open - start)});
|
||||||
{ExpressionToken::LITERAL, str.substr(start, open - start)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t close = str.find('}', open);
|
size_t close = str.find('}', open);
|
||||||
@ -51,8 +49,7 @@ struct Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Variable
|
// Variable
|
||||||
expr.tokens.push_back(
|
expr.tokens.push_back({ExpressionToken::VARIABLE, str.substr(open + 1, close - open - 1)});
|
||||||
{ExpressionToken::VARIABLE, str.substr(open + 1, close - open - 1)});
|
|
||||||
start = close + 1;
|
start = close + 1;
|
||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
@ -60,115 +57,94 @@ struct Expression {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ThemeContext {
|
class ThemeContext {
|
||||||
private:
|
private:
|
||||||
std::map<std::string, std::string> strings;
|
std::map<std::string, std::string> strings;
|
||||||
std::map<std::string, int> ints;
|
std::map<std::string, int> ints;
|
||||||
std::map<std::string, bool> bools;
|
std::map<std::string, bool> bools;
|
||||||
|
|
||||||
const ThemeContext *parent = nullptr;
|
const ThemeContext* parent = nullptr;
|
||||||
|
|
||||||
// Helper to trim whitespace
|
// Helper to trim whitespace
|
||||||
static std::string trim(const std::string &s) {
|
static std::string trim(const std::string& s) {
|
||||||
size_t start = s.find_first_not_of(" \t\n\r");
|
size_t start = s.find_first_not_of(" \t\n\r");
|
||||||
if (start == std::string::npos)
|
if (start == std::string::npos) return "";
|
||||||
return "";
|
|
||||||
size_t end = s.find_last_not_of(" \t\n\r");
|
size_t end = s.find_last_not_of(" \t\n\r");
|
||||||
return s.substr(start, end - start + 1);
|
return s.substr(start, end - start + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to check if string is a number
|
// Helper to check if string is a number
|
||||||
static bool isNumber(const std::string &s) {
|
static bool isNumber(const std::string& s) {
|
||||||
if (s.empty())
|
if (s.empty()) return false;
|
||||||
return false;
|
|
||||||
size_t start = (s[0] == '-') ? 1 : 0;
|
size_t start = (s[0] == '-') ? 1 : 0;
|
||||||
for (size_t i = start; i < s.length(); i++) {
|
for (size_t i = start; i < s.length(); i++) {
|
||||||
if (!isdigit(s[i]))
|
if (!isdigit(s[i])) return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return start < s.length();
|
return start < s.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ThemeContext(const ThemeContext *parent = nullptr) : parent(parent) {}
|
ThemeContext(const ThemeContext* parent = nullptr) : parent(parent) {}
|
||||||
|
|
||||||
void setString(const std::string &key, const std::string &value) {
|
void setString(const std::string& key, const std::string& value) { strings[key] = value; }
|
||||||
strings[key] = value;
|
void setInt(const std::string& key, int value) { ints[key] = value; }
|
||||||
}
|
void setBool(const std::string& key, bool value) { bools[key] = value; }
|
||||||
void setInt(const std::string &key, int value) { ints[key] = value; }
|
|
||||||
void setBool(const std::string &key, bool value) { bools[key] = value; }
|
|
||||||
|
|
||||||
std::string getString(const std::string &key,
|
std::string getString(const std::string& key, const std::string& defaultValue = "") const {
|
||||||
const std::string &defaultValue = "") const {
|
|
||||||
auto it = strings.find(key);
|
auto it = strings.find(key);
|
||||||
if (it != strings.end())
|
if (it != strings.end()) return it->second;
|
||||||
return it->second;
|
if (parent) return parent->getString(key, defaultValue);
|
||||||
if (parent)
|
|
||||||
return parent->getString(key, defaultValue);
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getInt(const std::string &key, int defaultValue = 0) const {
|
int getInt(const std::string& key, int defaultValue = 0) const {
|
||||||
auto it = ints.find(key);
|
auto it = ints.find(key);
|
||||||
if (it != ints.end())
|
if (it != ints.end()) return it->second;
|
||||||
return it->second;
|
if (parent) return parent->getInt(key, defaultValue);
|
||||||
if (parent)
|
|
||||||
return parent->getInt(key, defaultValue);
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getBool(const std::string &key, bool defaultValue = false) const {
|
bool getBool(const std::string& key, bool defaultValue = false) const {
|
||||||
auto it = bools.find(key);
|
auto it = bools.find(key);
|
||||||
if (it != bools.end())
|
if (it != bools.end()) return it->second;
|
||||||
return it->second;
|
if (parent) return parent->getBool(key, defaultValue);
|
||||||
if (parent)
|
|
||||||
return parent->getBool(key, defaultValue);
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasKey(const std::string &key) const {
|
bool hasKey(const std::string& key) const {
|
||||||
if (strings.count(key) || ints.count(key) || bools.count(key))
|
if (strings.count(key) || ints.count(key) || bools.count(key)) return true;
|
||||||
return true;
|
if (parent) return parent->hasKey(key);
|
||||||
if (parent)
|
|
||||||
return parent->hasKey(key);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get any value as string
|
// Get any value as string
|
||||||
std::string getAnyAsString(const std::string &key) const {
|
std::string getAnyAsString(const std::string& key) const {
|
||||||
// Check strings first
|
// Check strings first
|
||||||
auto sit = strings.find(key);
|
auto sit = strings.find(key);
|
||||||
if (sit != strings.end())
|
if (sit != strings.end()) return sit->second;
|
||||||
return sit->second;
|
|
||||||
|
|
||||||
// Check ints
|
// Check ints
|
||||||
auto iit = ints.find(key);
|
auto iit = ints.find(key);
|
||||||
if (iit != ints.end())
|
if (iit != ints.end()) return std::to_string(iit->second);
|
||||||
return std::to_string(iit->second);
|
|
||||||
|
|
||||||
// Check bools
|
// Check bools
|
||||||
auto bit = bools.find(key);
|
auto bit = bools.find(key);
|
||||||
if (bit != bools.end())
|
if (bit != bools.end()) return bit->second ? "true" : "false";
|
||||||
return bit->second ? "true" : "false";
|
|
||||||
|
|
||||||
// Check parent
|
// Check parent
|
||||||
if (parent)
|
if (parent) return parent->getAnyAsString(key);
|
||||||
return parent->getAnyAsString(key);
|
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate a complex boolean expression
|
// Evaluate a complex boolean expression
|
||||||
// Supports: !, &&, ||, ==, !=, <, >, <=, >=, parentheses
|
// Supports: !, &&, ||, ==, !=, <, >, <=, >=, parentheses
|
||||||
bool evaluateBool(const std::string &expression) const {
|
bool evaluateBool(const std::string& expression) const {
|
||||||
std::string expr = trim(expression);
|
std::string expr = trim(expression);
|
||||||
if (expr.empty())
|
if (expr.empty()) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
// Handle literal true/false
|
// Handle literal true/false
|
||||||
if (expr == "true" || expr == "1")
|
if (expr == "true" || expr == "1") return true;
|
||||||
return true;
|
if (expr == "false" || expr == "0") return false;
|
||||||
if (expr == "false" || expr == "0")
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Handle {var} wrapper
|
// Handle {var} wrapper
|
||||||
if (expr.size() > 2 && expr.front() == '{' && expr.back() == '}') {
|
if (expr.size() > 2 && expr.front() == '{' && expr.back() == '}') {
|
||||||
@ -185,10 +161,8 @@ public:
|
|||||||
int depth = 1;
|
int depth = 1;
|
||||||
size_t closePos = 1;
|
size_t closePos = 1;
|
||||||
while (closePos < expr.length() && depth > 0) {
|
while (closePos < expr.length() && depth > 0) {
|
||||||
if (expr[closePos] == '(')
|
if (expr[closePos] == '(') depth++;
|
||||||
depth++;
|
if (expr[closePos] == ')') depth--;
|
||||||
if (expr[closePos] == ')')
|
|
||||||
depth--;
|
|
||||||
closePos++;
|
closePos++;
|
||||||
}
|
}
|
||||||
if (closePos <= expr.length()) {
|
if (closePos <= expr.length()) {
|
||||||
@ -212,14 +186,11 @@ public:
|
|||||||
size_t orPos = expr.find("||");
|
size_t orPos = expr.find("||");
|
||||||
|
|
||||||
// Process || first (lower precedence than &&)
|
// Process || first (lower precedence than &&)
|
||||||
if (orPos != std::string::npos &&
|
if (orPos != std::string::npos && (andPos == std::string::npos || orPos < andPos)) {
|
||||||
(andPos == std::string::npos || orPos < andPos)) {
|
return evaluateBool(expr.substr(0, orPos)) || evaluateBool(expr.substr(orPos + 2));
|
||||||
return evaluateBool(expr.substr(0, orPos)) ||
|
|
||||||
evaluateBool(expr.substr(orPos + 2));
|
|
||||||
}
|
}
|
||||||
if (andPos != std::string::npos) {
|
if (andPos != std::string::npos) {
|
||||||
return evaluateBool(expr.substr(0, andPos)) &&
|
return evaluateBool(expr.substr(0, andPos)) && evaluateBool(expr.substr(andPos + 2));
|
||||||
evaluateBool(expr.substr(andPos + 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle comparisons
|
// Handle comparisons
|
||||||
@ -270,7 +241,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compare two values (handles variables, numbers, strings)
|
// Compare two values (handles variables, numbers, strings)
|
||||||
int compareValues(const std::string &left, const std::string &right) const {
|
int compareValues(const std::string& left, const std::string& right) const {
|
||||||
std::string leftVal = resolveValue(left);
|
std::string leftVal = resolveValue(left);
|
||||||
std::string rightVal = resolveValue(right);
|
std::string rightVal = resolveValue(right);
|
||||||
|
|
||||||
@ -286,7 +257,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve a value (variable name -> value, or literal)
|
// Resolve a value (variable name -> value, or literal)
|
||||||
std::string resolveValue(const std::string &val) const {
|
std::string resolveValue(const std::string& val) const {
|
||||||
std::string v = trim(val);
|
std::string v = trim(val);
|
||||||
|
|
||||||
// Remove quotes for string literals
|
// Remove quotes for string literals
|
||||||
@ -298,8 +269,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If it's a number, return as-is
|
// If it's a number, return as-is
|
||||||
if (isNumber(v))
|
if (isNumber(v)) return v;
|
||||||
return v;
|
|
||||||
|
|
||||||
// Check for hex color literals (0x00, 0xFF, etc.)
|
// Check for hex color literals (0x00, 0xFF, etc.)
|
||||||
if (v.size() > 2 && v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) {
|
if (v.size() > 2 && v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) {
|
||||||
@ -326,12 +296,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate a string expression with variable substitution
|
// Evaluate a string expression with variable substitution
|
||||||
std::string evaluatestring(const Expression &expr) const {
|
std::string evaluatestring(const Expression& expr) const {
|
||||||
if (expr.empty())
|
if (expr.empty()) return "";
|
||||||
return "";
|
|
||||||
|
|
||||||
std::string result;
|
std::string result;
|
||||||
for (const auto &token : expr.tokens) {
|
for (const auto& token : expr.tokens) {
|
||||||
if (token.type == ExpressionToken::LITERAL) {
|
if (token.type == ExpressionToken::LITERAL) {
|
||||||
result += token.value;
|
result += token.value;
|
||||||
} else {
|
} else {
|
||||||
@ -339,10 +308,8 @@ public:
|
|||||||
std::string varName = token.value;
|
std::string varName = token.value;
|
||||||
|
|
||||||
// If the variable contains comparison operators, evaluate as condition
|
// If the variable contains comparison operators, evaluate as condition
|
||||||
if (varName.find("==") != std::string::npos ||
|
if (varName.find("==") != std::string::npos || varName.find("!=") != std::string::npos ||
|
||||||
varName.find("!=") != std::string::npos ||
|
varName.find("&&") != std::string::npos || varName.find("||") != std::string::npos) {
|
||||||
varName.find("&&") != std::string::npos ||
|
|
||||||
varName.find("||") != std::string::npos) {
|
|
||||||
result += evaluateBool(varName) ? "true" : "false";
|
result += evaluateBool(varName) ? "true" : "false";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -355,7 +322,7 @@ public:
|
|||||||
std::string condition = trim(varName.substr(0, qPos));
|
std::string condition = trim(varName.substr(0, qPos));
|
||||||
std::string trueVal = trim(varName.substr(qPos + 1, cPos - qPos - 1));
|
std::string trueVal = trim(varName.substr(qPos + 1, cPos - qPos - 1));
|
||||||
std::string falseVal = trim(varName.substr(cPos + 1));
|
std::string falseVal = trim(varName.substr(cPos + 1));
|
||||||
|
|
||||||
bool condResult = evaluateBool(condition);
|
bool condResult = evaluateBool(condition);
|
||||||
result += resolveValue(condResult ? trueVal : falseVal);
|
result += resolveValue(condResult ? trueVal : falseVal);
|
||||||
continue;
|
continue;
|
||||||
@ -371,12 +338,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Legacy method for backward compatibility
|
// Legacy method for backward compatibility
|
||||||
std::string evaluateString(const std::string &expression) const {
|
std::string evaluateString(const std::string& expression) const {
|
||||||
if (expression.empty())
|
if (expression.empty()) return "";
|
||||||
return "";
|
|
||||||
Expression expr = Expression::parse(expression);
|
Expression expr = Expression::parse(expression);
|
||||||
return evaluatestring(expr);
|
return evaluatestring(expr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "BasicElements.h"
|
#include "BasicElements.h"
|
||||||
#include "IniParser.h"
|
#include "IniParser.h"
|
||||||
#include "ThemeContext.h"
|
#include "ThemeContext.h"
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
@ -18,10 +20,10 @@ struct ProcessedAsset {
|
|||||||
|
|
||||||
// Screen render cache - stores full screen state for quick restore
|
// Screen render cache - stores full screen state for quick restore
|
||||||
struct ScreenCache {
|
struct ScreenCache {
|
||||||
uint8_t *buffer = nullptr;
|
uint8_t* buffer = nullptr;
|
||||||
size_t bufferSize = 0;
|
size_t bufferSize = 0;
|
||||||
std::string screenName;
|
std::string screenName;
|
||||||
uint32_t contextHash = 0; // Hash of context data to detect changes
|
uint32_t contextHash = 0; // Hash of context data to detect changes
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
|
|
||||||
~ScreenCache() {
|
~ScreenCache() {
|
||||||
@ -35,8 +37,8 @@ struct ScreenCache {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ThemeManager {
|
class ThemeManager {
|
||||||
private:
|
private:
|
||||||
std::map<std::string, UIElement *> elements; // All elements by ID
|
std::map<std::string, UIElement*> elements; // All elements by ID
|
||||||
std::string currentThemeName;
|
std::string currentThemeName;
|
||||||
int navBookCount = 1; // Number of navigable book slots (from theme [Global] section)
|
int navBookCount = 1; // Number of navigable book slots (from theme [Global] section)
|
||||||
std::map<std::string, int> fontMap;
|
std::map<std::string, int> fontMap;
|
||||||
@ -49,12 +51,11 @@ private:
|
|||||||
std::map<std::string, bool> elementDependsOnData;
|
std::map<std::string, bool> elementDependsOnData;
|
||||||
|
|
||||||
// Factory and property methods
|
// Factory and property methods
|
||||||
UIElement *createElement(const std::string &id, const std::string &type);
|
UIElement* createElement(const std::string& id, const std::string& type);
|
||||||
void applyProperties(UIElement *elem,
|
void applyProperties(UIElement* elem, const std::map<std::string, std::string>& props);
|
||||||
const std::map<std::string, std::string> &props);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ThemeManager &get() {
|
static ThemeManager& get() {
|
||||||
static ThemeManager instance;
|
static ThemeManager instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -63,66 +64,59 @@ public:
|
|||||||
void begin();
|
void begin();
|
||||||
|
|
||||||
// Register a font ID mapping (e.g. "UI_12" -> 0)
|
// Register a font ID mapping (e.g. "UI_12" -> 0)
|
||||||
void registerFont(const std::string &name, int id);
|
void registerFont(const std::string& name, int id);
|
||||||
|
|
||||||
// Theme loading
|
// Theme loading
|
||||||
void loadTheme(const std::string &themeName);
|
void loadTheme(const std::string& themeName);
|
||||||
void unloadTheme();
|
void unloadTheme();
|
||||||
|
|
||||||
// Get current theme name
|
// Get current theme name
|
||||||
const std::string &getCurrentTheme() const { return currentThemeName; }
|
const std::string& getCurrentTheme() const { return currentThemeName; }
|
||||||
|
|
||||||
// Get number of navigable book slots (from theme config, default 1)
|
// Get number of navigable book slots (from theme config, default 1)
|
||||||
int getNavBookCount() const { return navBookCount; }
|
int getNavBookCount() const { return navBookCount; }
|
||||||
|
|
||||||
// Render a screen
|
// Render a screen
|
||||||
void renderScreen(const std::string &screenName, const GfxRenderer &renderer,
|
void renderScreen(const std::string& screenName, const GfxRenderer& renderer, const ThemeContext& context);
|
||||||
const ThemeContext &context);
|
|
||||||
|
|
||||||
// Render with dirty tracking (only redraws changed regions)
|
// Render with dirty tracking (only redraws changed regions)
|
||||||
void renderScreenOptimized(const std::string &screenName,
|
void renderScreenOptimized(const std::string& screenName, const GfxRenderer& renderer, const ThemeContext& context,
|
||||||
const GfxRenderer &renderer,
|
const ThemeContext* prevContext = nullptr);
|
||||||
const ThemeContext &context,
|
|
||||||
const ThemeContext *prevContext = nullptr);
|
|
||||||
|
|
||||||
// Invalidate all caches (call when theme changes or screen switches)
|
// Invalidate all caches (call when theme changes or screen switches)
|
||||||
void invalidateAllCaches();
|
void invalidateAllCaches();
|
||||||
|
|
||||||
// Invalidate specific screen cache
|
// Invalidate specific screen cache
|
||||||
void invalidateScreenCache(const std::string &screenName);
|
void invalidateScreenCache(const std::string& screenName);
|
||||||
|
|
||||||
// Enable/disable caching
|
// Enable/disable caching
|
||||||
void setCachingEnabled(bool enabled) { useCaching = enabled; }
|
void setCachingEnabled(bool enabled) { useCaching = enabled; }
|
||||||
bool isCachingEnabled() const { return useCaching; }
|
bool isCachingEnabled() const { return useCaching; }
|
||||||
|
|
||||||
// Asset path resolution
|
// Asset path resolution
|
||||||
std::string getAssetPath(const std::string &assetName);
|
std::string getAssetPath(const std::string& assetName);
|
||||||
|
|
||||||
// Asset caching
|
// Asset caching
|
||||||
const std::vector<uint8_t> *getCachedAsset(const std::string &path);
|
const std::vector<uint8_t>* getCachedAsset(const std::string& path);
|
||||||
const ProcessedAsset *getProcessedAsset(const std::string &path,
|
const ProcessedAsset* getProcessedAsset(const std::string& path, GfxRenderer::Orientation orientation,
|
||||||
GfxRenderer::Orientation orientation,
|
|
||||||
int targetW = 0, int targetH = 0);
|
int targetW = 0, int targetH = 0);
|
||||||
void cacheProcessedAsset(const std::string &path,
|
void cacheProcessedAsset(const std::string& path, const ProcessedAsset& asset, int targetW = 0, int targetH = 0);
|
||||||
const ProcessedAsset &asset,
|
|
||||||
int targetW = 0, int targetH = 0);
|
|
||||||
|
|
||||||
// Clear asset caches (for memory management)
|
// Clear asset caches (for memory management)
|
||||||
void clearAssetCaches();
|
void clearAssetCaches();
|
||||||
|
|
||||||
// Get element by ID (useful for direct manipulation)
|
// Get element by ID (useful for direct manipulation)
|
||||||
UIElement *getElement(const std::string &id) {
|
UIElement* getElement(const std::string& id) {
|
||||||
auto it = elements.find(id);
|
auto it = elements.find(id);
|
||||||
return it != elements.end() ? it->second : nullptr;
|
return it != elements.end() ? it->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<std::string, std::vector<uint8_t>> assetCache;
|
std::map<std::string, std::vector<uint8_t>> assetCache;
|
||||||
std::map<std::string, ProcessedAsset> processedCache;
|
std::map<std::string, ProcessedAsset> processedCache;
|
||||||
|
|
||||||
// Compute a simple hash of context data for cache invalidation
|
// Compute a simple hash of context data for cache invalidation
|
||||||
uint32_t computeContextHash(const ThemeContext &context,
|
uint32_t computeContextHash(const ThemeContext& context, const std::string& screenName);
|
||||||
const std::string &screenName);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -14,13 +14,11 @@ struct Dimension {
|
|||||||
Dimension(int v, DimensionUnit u) : value(v), unit(u) {}
|
Dimension(int v, DimensionUnit u) : value(v), unit(u) {}
|
||||||
Dimension() : value(0), unit(DimensionUnit::PIXELS) {}
|
Dimension() : value(0), unit(DimensionUnit::PIXELS) {}
|
||||||
|
|
||||||
static Dimension parse(const std::string &str) {
|
static Dimension parse(const std::string& str) {
|
||||||
if (str.empty())
|
if (str.empty()) return Dimension(0, DimensionUnit::PIXELS);
|
||||||
return Dimension(0, DimensionUnit::PIXELS);
|
|
||||||
|
|
||||||
if (str.back() == '%') {
|
if (str.back() == '%') {
|
||||||
return Dimension(std::stoi(str.substr(0, str.length() - 1)),
|
return Dimension(std::stoi(str.substr(0, str.length() - 1)), DimensionUnit::PERCENT);
|
||||||
DimensionUnit::PERCENT);
|
|
||||||
}
|
}
|
||||||
return Dimension(std::stoi(str), DimensionUnit::PIXELS);
|
return Dimension(std::stoi(str), DimensionUnit::PIXELS);
|
||||||
}
|
}
|
||||||
@ -34,20 +32,16 @@ struct Dimension {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Color {
|
struct Color {
|
||||||
uint8_t value; // For E-Ink: 0 (Black) to 255 (White), or simplified palette
|
uint8_t value; // For E-Ink: 0 (Black) to 255 (White), or simplified palette
|
||||||
|
|
||||||
Color(uint8_t v) : value(v) {}
|
Color(uint8_t v) : value(v) {}
|
||||||
Color() : value(0) {}
|
Color() : value(0) {}
|
||||||
|
|
||||||
static Color parse(const std::string &str) {
|
static Color parse(const std::string& str) {
|
||||||
if (str.empty())
|
if (str.empty()) return Color(0);
|
||||||
return Color(0);
|
if (str == "black") return Color(0x00);
|
||||||
if (str == "black")
|
if (str == "white") return Color(0xFF);
|
||||||
return Color(0x00);
|
if (str == "gray" || str == "grey") return Color(0x80);
|
||||||
if (str == "white")
|
|
||||||
return Color(0xFF);
|
|
||||||
if (str == "gray" || str == "grey")
|
|
||||||
return Color(0x80);
|
|
||||||
if (str.size() > 2 && str.substr(0, 2) == "0x") {
|
if (str.size() > 2 && str.substr(0, 2) == "0x") {
|
||||||
return Color((uint8_t)std::strtol(str.c_str(), nullptr, 16));
|
return Color((uint8_t)std::strtol(str.c_str(), nullptr, 16));
|
||||||
}
|
}
|
||||||
@ -64,16 +58,13 @@ struct Rect {
|
|||||||
|
|
||||||
bool isEmpty() const { return w <= 0 || h <= 0; }
|
bool isEmpty() const { return w <= 0 || h <= 0; }
|
||||||
|
|
||||||
bool intersects(const Rect &other) const {
|
bool intersects(const Rect& other) const {
|
||||||
return !(x + w <= other.x || other.x + other.w <= x || y + h <= other.y ||
|
return !(x + w <= other.x || other.x + other.w <= x || y + h <= other.y || other.y + other.h <= y);
|
||||||
other.y + other.h <= y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect unite(const Rect &other) const {
|
Rect unite(const Rect& other) const {
|
||||||
if (isEmpty())
|
if (isEmpty()) return other;
|
||||||
return other;
|
if (other.isEmpty()) return *this;
|
||||||
if (other.isEmpty())
|
|
||||||
return *this;
|
|
||||||
int nx = std::min(x, other.x);
|
int nx = std::min(x, other.x);
|
||||||
int ny = std::min(y, other.y);
|
int ny = std::min(y, other.y);
|
||||||
int nx2 = std::max(x + w, other.x + other.w);
|
int nx2 = std::max(x + w, other.x + other.w);
|
||||||
@ -82,4 +73,4 @@ struct Rect {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,52 +1,51 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ThemeContext.h"
|
|
||||||
#include "ThemeTypes.h"
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ThemeContext.h"
|
||||||
|
#include "ThemeTypes.h"
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
class Container; // Forward declaration
|
class Container; // Forward declaration
|
||||||
|
|
||||||
class UIElement {
|
class UIElement {
|
||||||
public:
|
public:
|
||||||
int getAbsX() const { return absX; }
|
int getAbsX() const { return absX; }
|
||||||
int getAbsY() const { return absY; }
|
int getAbsY() const { return absY; }
|
||||||
int getAbsW() const { return absW; }
|
int getAbsW() const { return absW; }
|
||||||
int getAbsH() const { return absH; }
|
int getAbsH() const { return absH; }
|
||||||
const std::string &getId() const { return id; }
|
const std::string& getId() const { return id; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string id;
|
std::string id;
|
||||||
Dimension x, y, width, height;
|
Dimension x, y, width, height;
|
||||||
Expression visibleExpr;
|
Expression visibleExpr;
|
||||||
bool visibleExprIsStatic = true; // True if visibility doesn't depend on data
|
bool visibleExprIsStatic = true; // True if visibility doesn't depend on data
|
||||||
|
|
||||||
// Recomputed every layout pass
|
// Recomputed every layout pass
|
||||||
int absX = 0, absY = 0, absW = 0, absH = 0;
|
int absX = 0, absY = 0, absW = 0, absH = 0;
|
||||||
|
|
||||||
// Caching support
|
// Caching support
|
||||||
bool cacheable = false; // Set true for expensive elements like bitmaps
|
bool cacheable = false; // Set true for expensive elements like bitmaps
|
||||||
bool cacheValid = false; // Is the cached render still valid?
|
bool cacheValid = false; // Is the cached render still valid?
|
||||||
uint8_t *cachedRender = nullptr;
|
uint8_t* cachedRender = nullptr;
|
||||||
size_t cachedRenderSize = 0;
|
size_t cachedRenderSize = 0;
|
||||||
int cachedX = 0, cachedY = 0, cachedW = 0, cachedH = 0;
|
int cachedX = 0, cachedY = 0, cachedW = 0, cachedH = 0;
|
||||||
|
|
||||||
// Dirty tracking
|
// Dirty tracking
|
||||||
bool dirty = true; // Needs redraw
|
bool dirty = true; // Needs redraw
|
||||||
|
|
||||||
bool isVisible(const ThemeContext &context) const {
|
bool isVisible(const ThemeContext& context) const {
|
||||||
if (visibleExpr.empty())
|
if (visibleExpr.empty()) return true;
|
||||||
return true;
|
|
||||||
return context.evaluateBool(visibleExpr.rawExpr);
|
return context.evaluateBool(visibleExpr.rawExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UIElement(const std::string &id) : id(id) {
|
UIElement(const std::string& id) : id(id) { visibleExpr = Expression::parse("true"); }
|
||||||
visibleExpr = Expression::parse("true");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~UIElement() {
|
virtual ~UIElement() {
|
||||||
if (cachedRender) {
|
if (cachedRender) {
|
||||||
@ -71,11 +70,11 @@ public:
|
|||||||
height = val;
|
height = val;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setVisibleExpr(const std::string &expr) {
|
void setVisibleExpr(const std::string& expr) {
|
||||||
visibleExpr = Expression::parse(expr);
|
visibleExpr = Expression::parse(expr);
|
||||||
// Check if expression contains variables
|
// Check if expression contains variables
|
||||||
visibleExprIsStatic = (expr == "true" || expr == "false" || expr == "1" ||
|
visibleExprIsStatic =
|
||||||
expr == "0" || expr.find('{') == std::string::npos);
|
(expr == "true" || expr == "false" || expr == "1" || expr == "0" || expr.find('{') == std::string::npos);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,31 +96,24 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate absolute position based on parent
|
// Calculate absolute position based on parent
|
||||||
virtual void layout(const ThemeContext &context, int parentX, int parentY,
|
virtual void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) {
|
||||||
int parentW, int parentH) {
|
|
||||||
int newX = parentX + x.resolve(parentW);
|
int newX = parentX + x.resolve(parentW);
|
||||||
int newY = parentY + y.resolve(parentH);
|
int newY = parentY + y.resolve(parentH);
|
||||||
int newW = width.resolve(parentW);
|
int newW = width.resolve(parentW);
|
||||||
int newH = height.resolve(parentH);
|
int newH = height.resolve(parentH);
|
||||||
|
|
||||||
// Clamp to parent bounds
|
// Clamp to parent bounds
|
||||||
if (newX >= parentX + parentW)
|
if (newX >= parentX + parentW) newX = parentX + parentW - 1;
|
||||||
newX = parentX + parentW - 1;
|
if (newY >= parentY + parentH) newY = parentY + parentH - 1;
|
||||||
if (newY >= parentY + parentH)
|
|
||||||
newY = parentY + parentH - 1;
|
|
||||||
|
|
||||||
int maxX = parentX + parentW;
|
int maxX = parentX + parentW;
|
||||||
int maxY = parentY + parentH;
|
int maxY = parentY + parentH;
|
||||||
|
|
||||||
if (newX + newW > maxX)
|
if (newX + newW > maxX) newW = maxX - newX;
|
||||||
newW = maxX - newX;
|
if (newY + newH > maxY) newH = maxY - newY;
|
||||||
if (newY + newH > maxY)
|
|
||||||
newH = maxY - newY;
|
|
||||||
|
|
||||||
if (newW < 0)
|
if (newW < 0) newW = 0;
|
||||||
newW = 0;
|
if (newH < 0) newH = 0;
|
||||||
if (newH < 0)
|
|
||||||
newH = 0;
|
|
||||||
|
|
||||||
// Check if position changed
|
// Check if position changed
|
||||||
if (newX != absX || newY != absY || newW != absW || newH != absH) {
|
if (newX != absX || newY != absY || newW != absW || newH != absH) {
|
||||||
@ -133,7 +125,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual Container *asContainer() { return nullptr; }
|
virtual Container* asContainer() { return nullptr; }
|
||||||
|
|
||||||
enum class ElementType {
|
enum class ElementType {
|
||||||
Base,
|
Base,
|
||||||
@ -166,12 +158,11 @@ public:
|
|||||||
Rect getBounds() const { return Rect(absX, absY, absW, absH); }
|
Rect getBounds() const { return Rect(absX, absY, absW, absH); }
|
||||||
|
|
||||||
// Main draw method - handles caching automatically
|
// Main draw method - handles caching automatically
|
||||||
virtual void draw(const GfxRenderer &renderer,
|
virtual void draw(const GfxRenderer& renderer, const ThemeContext& context) = 0;
|
||||||
const ThemeContext &context) = 0;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Cache the rendered output
|
// Cache the rendered output
|
||||||
bool cacheRender(const GfxRenderer &renderer) {
|
bool cacheRender(const GfxRenderer& renderer) {
|
||||||
if (cachedRender) {
|
if (cachedRender) {
|
||||||
free(cachedRender);
|
free(cachedRender);
|
||||||
cachedRender = nullptr;
|
cachedRender = nullptr;
|
||||||
@ -190,16 +181,13 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore from cache
|
// Restore from cache
|
||||||
bool restoreFromCache(const GfxRenderer &renderer) const {
|
bool restoreFromCache(const GfxRenderer& renderer) const {
|
||||||
if (!cacheValid || !cachedRender)
|
if (!cacheValid || !cachedRender) return false;
|
||||||
return false;
|
if (absX != cachedX || absY != cachedY || absW != cachedW || absH != cachedH) return false;
|
||||||
if (absX != cachedX || absY != cachedY || absW != cachedW ||
|
|
||||||
absH != cachedH)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
renderer.restoreRegion(cachedRender, absX, absY, absW, absH);
|
renderer.restoreRegion(cachedRender, absX, absY, absW, absH);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include "BasicElements.h"
|
#include "BasicElements.h"
|
||||||
|
|
||||||
#include "Bitmap.h"
|
#include "Bitmap.h"
|
||||||
#include "ListElement.h"
|
#include "ListElement.h"
|
||||||
#include "ThemeManager.h"
|
#include "ThemeManager.h"
|
||||||
@ -7,8 +8,7 @@
|
|||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
// --- BitmapElement ---
|
// --- BitmapElement ---
|
||||||
void BitmapElement::draw(const GfxRenderer &renderer,
|
void BitmapElement::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
||||||
const ThemeContext &context) {
|
|
||||||
if (!isVisible(context)) {
|
if (!isVisible(context)) {
|
||||||
markClean();
|
markClean();
|
||||||
return;
|
return;
|
||||||
@ -21,13 +21,12 @@ void BitmapElement::draw(const GfxRenderer &renderer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a cached 1-bit render of this bitmap at this size
|
// Check if we have a cached 1-bit render of this bitmap at this size
|
||||||
const ProcessedAsset *processed =
|
const ProcessedAsset* processed = ThemeManager::get().getProcessedAsset(path, renderer.getOrientation(), absW, absH);
|
||||||
ThemeManager::get().getProcessedAsset(path, renderer.getOrientation(), absW, absH);
|
|
||||||
if (processed && processed->w == absW && processed->h == absH) {
|
if (processed && processed->w == absW && processed->h == absH) {
|
||||||
// Draw cached 1-bit data directly
|
// Draw cached 1-bit data directly
|
||||||
const int rowBytes = (absW + 7) / 8;
|
const int rowBytes = (absW + 7) / 8;
|
||||||
for (int y = 0; y < absH; y++) {
|
for (int y = 0; y < absH; y++) {
|
||||||
const uint8_t *srcRow = processed->data.data() + y * rowBytes;
|
const uint8_t* srcRow = processed->data.data() + y * rowBytes;
|
||||||
for (int x = 0; x < absW; x++) {
|
for (int x = 0; x < absW; x++) {
|
||||||
bool isBlack = !(srcRow[x / 8] & (1 << (7 - (x % 8))));
|
bool isBlack = !(srcRow[x / 8] & (1 << (7 - (x % 8))));
|
||||||
if (isBlack) {
|
if (isBlack) {
|
||||||
@ -40,7 +39,7 @@ void BitmapElement::draw(const GfxRenderer &renderer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load raw asset from cache (file data cached in memory)
|
// Load raw asset from cache (file data cached in memory)
|
||||||
const std::vector<uint8_t> *data = ThemeManager::get().getCachedAsset(path);
|
const std::vector<uint8_t>* data = ThemeManager::get().getCachedAsset(path);
|
||||||
if (!data || data->empty()) {
|
if (!data || data->empty()) {
|
||||||
markClean();
|
markClean();
|
||||||
return;
|
return;
|
||||||
@ -54,19 +53,19 @@ void BitmapElement::draw(const GfxRenderer &renderer,
|
|||||||
|
|
||||||
// Draw the bitmap (handles scaling internally)
|
// Draw the bitmap (handles scaling internally)
|
||||||
renderer.drawBitmap(bmp, absX, absY, absW, absH);
|
renderer.drawBitmap(bmp, absX, absY, absW, absH);
|
||||||
|
|
||||||
// Cache the result as 1-bit packed data for next time
|
// Cache the result as 1-bit packed data for next time
|
||||||
ProcessedAsset asset;
|
ProcessedAsset asset;
|
||||||
asset.w = absW;
|
asset.w = absW;
|
||||||
asset.h = absH;
|
asset.h = absH;
|
||||||
asset.orientation = renderer.getOrientation();
|
asset.orientation = renderer.getOrientation();
|
||||||
|
|
||||||
const int rowBytes = (absW + 7) / 8;
|
const int rowBytes = (absW + 7) / 8;
|
||||||
asset.data.resize(rowBytes * absH, 0xFF); // Initialize to white
|
asset.data.resize(rowBytes * absH, 0xFF); // Initialize to white
|
||||||
|
|
||||||
// Capture pixels using renderer's coordinate system
|
// Capture pixels using renderer's coordinate system
|
||||||
for (int y = 0; y < absH; y++) {
|
for (int y = 0; y < absH; y++) {
|
||||||
uint8_t *dstRow = asset.data.data() + y * rowBytes;
|
uint8_t* dstRow = asset.data.data() + y * rowBytes;
|
||||||
for (int x = 0; x < absW; x++) {
|
for (int x = 0; x < absW; x++) {
|
||||||
// Read pixel from framebuffer (this handles orientation)
|
// Read pixel from framebuffer (this handles orientation)
|
||||||
bool isBlack = renderer.readPixel(absX + x, absY + y);
|
bool isBlack = renderer.readPixel(absX + x, absY + y);
|
||||||
@ -75,14 +74,14 @@ void BitmapElement::draw(const GfxRenderer &renderer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeManager::get().cacheProcessedAsset(path, asset, absW, absH);
|
ThemeManager::get().cacheProcessedAsset(path, asset, absW, absH);
|
||||||
|
|
||||||
markClean();
|
markClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- List ---
|
// --- List ---
|
||||||
void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
void List::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
||||||
if (!isVisible(context)) {
|
if (!isVisible(context)) {
|
||||||
markClean();
|
markClean();
|
||||||
return;
|
return;
|
||||||
@ -148,8 +147,7 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
|||||||
currentX += itemW + spacing;
|
currentX += itemW + spacing;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (currentX > absX + absW)
|
if (currentX > absX + absW) break;
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
// Grid mode
|
// Grid mode
|
||||||
if (currentY + itemH < absY) {
|
if (currentY + itemH < absY) {
|
||||||
@ -162,8 +160,7 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
|||||||
currentX = absX + col * (itemW + spacing);
|
currentX = absX + col * (itemW + spacing);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (currentY > absY + absH)
|
if (currentY > absY + absH) break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout and draw
|
// Layout and draw
|
||||||
@ -225,4 +222,4 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
|||||||
markClean();
|
markClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
#include "IniParser.h"
|
#include "IniParser.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
void IniParser::trim(std::string &s) {
|
void IniParser::trim(std::string& s) {
|
||||||
if (s.empty())
|
if (s.empty()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// Trim left
|
// Trim left
|
||||||
size_t first = s.find_first_not_of(" \t\n\r");
|
size_t first = s.find_first_not_of(" \t\n\r");
|
||||||
@ -19,14 +19,13 @@ void IniParser::trim(std::string &s) {
|
|||||||
s = s.substr(first, (last - first + 1));
|
s = s.substr(first, (last - first + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, std::map<std::string, std::string>>
|
std::map<std::string, std::map<std::string, std::string>> IniParser::parse(Stream& stream) {
|
||||||
IniParser::parse(Stream &stream) {
|
|
||||||
std::map<std::string, std::map<std::string, std::string>> sections;
|
std::map<std::string, std::map<std::string, std::string>> sections;
|
||||||
// stream check not strictly possible like file, can rely on available()
|
// stream check not strictly possible like file, can rely on available()
|
||||||
|
|
||||||
std::string currentSection = "";
|
std::string currentSection = "";
|
||||||
String line; // Use Arduino String for easy file reading, then convert to
|
String line; // Use Arduino String for easy file reading, then convert to
|
||||||
// std::string
|
// std::string
|
||||||
|
|
||||||
while (stream.available()) {
|
while (stream.available()) {
|
||||||
line = stream.readStringUntil('\n');
|
line = stream.readStringUntil('\n');
|
||||||
@ -34,7 +33,7 @@ IniParser::parse(Stream &stream) {
|
|||||||
trim(sLine);
|
trim(sLine);
|
||||||
|
|
||||||
if (sLine.empty() || sLine[0] == ';' || sLine[0] == '#') {
|
if (sLine.empty() || sLine[0] == ';' || sLine[0] == '#') {
|
||||||
continue; // Skip comments and empty lines
|
continue; // Skip comments and empty lines
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sLine.front() == '[' && sLine.back() == ']') {
|
if (sLine.front() == '[' && sLine.back() == ']') {
|
||||||
@ -62,8 +61,7 @@ IniParser::parse(Stream &stream) {
|
|||||||
return sections;
|
return sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, std::map<std::string, std::string>>
|
std::map<std::string, std::map<std::string, std::string>> IniParser::parseString(const std::string& content) {
|
||||||
IniParser::parseString(const std::string &content) {
|
|
||||||
std::map<std::string, std::map<std::string, std::string>> sections;
|
std::map<std::string, std::map<std::string, std::string>> sections;
|
||||||
std::stringstream ss(content);
|
std::stringstream ss(content);
|
||||||
std::string line;
|
std::string line;
|
||||||
@ -101,4 +99,4 @@ IniParser::parseString(const std::string &content) {
|
|||||||
return sections;
|
return sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
#include "LayoutElements.h"
|
#include "LayoutElements.h"
|
||||||
#include "ThemeManager.h"
|
|
||||||
#include <Bitmap.h>
|
#include <Bitmap.h>
|
||||||
|
|
||||||
|
#include "ThemeManager.h"
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
// Built-in icon drawing
|
// Built-in icon drawing
|
||||||
// These are simple geometric representations of common icons
|
// These are simple geometric representations of common icons
|
||||||
void Icon::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
void Icon::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
||||||
if (!isVisible(context)) {
|
if (!isVisible(context)) {
|
||||||
markClean();
|
markClean();
|
||||||
return;
|
return;
|
||||||
@ -29,15 +31,14 @@ void Icon::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
|||||||
int cy = absY + h / 2;
|
int cy = absY + h / 2;
|
||||||
|
|
||||||
// Check if it's a path to a BMP file
|
// Check if it's a path to a BMP file
|
||||||
if (iconName.find('/') != std::string::npos ||
|
if (iconName.find('/') != std::string::npos || iconName.find('.') != std::string::npos) {
|
||||||
iconName.find('.') != std::string::npos) {
|
|
||||||
// Try to load as bitmap
|
// Try to load as bitmap
|
||||||
std::string path = iconName;
|
std::string path = iconName;
|
||||||
if (path[0] != '/') {
|
if (path[0] != '/') {
|
||||||
path = ThemeManager::get().getAssetPath(iconName);
|
path = ThemeManager::get().getAssetPath(iconName);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<uint8_t> *data = ThemeManager::get().getCachedAsset(path);
|
const std::vector<uint8_t>* data = ThemeManager::get().getCachedAsset(path);
|
||||||
if (data && !data->empty()) {
|
if (data && !data->empty()) {
|
||||||
Bitmap bmp(data->data(), data->size());
|
Bitmap bmp(data->data(), data->size());
|
||||||
if (bmp.parseHeaders() == BmpReaderError::Ok) {
|
if (bmp.parseHeaders() == BmpReaderError::Ok) {
|
||||||
@ -170,4 +171,4 @@ void Icon::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
|||||||
markClean();
|
markClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -1,82 +1,63 @@
|
|||||||
#include "ThemeManager.h"
|
#include "ThemeManager.h"
|
||||||
#include "DefaultTheme.h"
|
|
||||||
#include "LayoutElements.h"
|
|
||||||
#include "ListElement.h"
|
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "DefaultTheme.h"
|
||||||
|
#include "LayoutElements.h"
|
||||||
|
#include "ListElement.h"
|
||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
void ThemeManager::begin() {
|
void ThemeManager::begin() {
|
||||||
// Default fonts or setup
|
// Default fonts or setup
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::registerFont(const std::string &name, int id) {
|
void ThemeManager::registerFont(const std::string& name, int id) { fontMap[name] = id; }
|
||||||
fontMap[name] = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ThemeManager::getAssetPath(const std::string &assetName) {
|
std::string ThemeManager::getAssetPath(const std::string& assetName) {
|
||||||
// Check if absolute path
|
// Check if absolute path
|
||||||
if (!assetName.empty() && assetName[0] == '/')
|
if (!assetName.empty() && assetName[0] == '/') return assetName;
|
||||||
return assetName;
|
|
||||||
|
|
||||||
// Otherwise relative to theme assets
|
// Otherwise relative to theme assets
|
||||||
return "/themes/" + currentThemeName + "/assets/" + assetName;
|
return "/themes/" + currentThemeName + "/assets/" + assetName;
|
||||||
}
|
}
|
||||||
|
|
||||||
UIElement *ThemeManager::createElement(const std::string &id,
|
UIElement* ThemeManager::createElement(const std::string& id, const std::string& type) {
|
||||||
const std::string &type) {
|
|
||||||
// Basic elements
|
// Basic elements
|
||||||
if (type == "Container")
|
if (type == "Container") return new Container(id);
|
||||||
return new Container(id);
|
if (type == "Rectangle") return new Rectangle(id);
|
||||||
if (type == "Rectangle")
|
if (type == "Label") return new Label(id);
|
||||||
return new Rectangle(id);
|
if (type == "Bitmap") return new BitmapElement(id);
|
||||||
if (type == "Label")
|
if (type == "List") return new List(id);
|
||||||
return new Label(id);
|
if (type == "ProgressBar") return new ProgressBar(id);
|
||||||
if (type == "Bitmap")
|
if (type == "Divider") return new Divider(id);
|
||||||
return new BitmapElement(id);
|
|
||||||
if (type == "List")
|
|
||||||
return new List(id);
|
|
||||||
if (type == "ProgressBar")
|
|
||||||
return new ProgressBar(id);
|
|
||||||
if (type == "Divider")
|
|
||||||
return new Divider(id);
|
|
||||||
|
|
||||||
// Layout elements
|
// Layout elements
|
||||||
if (type == "HStack")
|
if (type == "HStack") return new HStack(id);
|
||||||
return new HStack(id);
|
if (type == "VStack") return new VStack(id);
|
||||||
if (type == "VStack")
|
if (type == "Grid") return new Grid(id);
|
||||||
return new VStack(id);
|
|
||||||
if (type == "Grid")
|
|
||||||
return new Grid(id);
|
|
||||||
|
|
||||||
// Advanced elements
|
// Advanced elements
|
||||||
if (type == "Badge")
|
if (type == "Badge") return new Badge(id);
|
||||||
return new Badge(id);
|
if (type == "Toggle") return new Toggle(id);
|
||||||
if (type == "Toggle")
|
if (type == "TabBar") return new TabBar(id);
|
||||||
return new Toggle(id);
|
if (type == "Icon") return new Icon(id);
|
||||||
if (type == "TabBar")
|
if (type == "ScrollIndicator") return new ScrollIndicator(id);
|
||||||
return new TabBar(id);
|
if (type == "BatteryIcon") return new BatteryIcon(id);
|
||||||
if (type == "Icon")
|
|
||||||
return new Icon(id);
|
|
||||||
if (type == "ScrollIndicator")
|
|
||||||
return new ScrollIndicator(id);
|
|
||||||
if (type == "BatteryIcon")
|
|
||||||
return new BatteryIcon(id);
|
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::applyProperties(
|
void ThemeManager::applyProperties(UIElement* elem, const std::map<std::string, std::string>& props) {
|
||||||
UIElement *elem, const std::map<std::string, std::string> &props) {
|
|
||||||
|
|
||||||
const auto elemType = elem->getType();
|
const auto elemType = elem->getType();
|
||||||
|
|
||||||
for (const auto &kv : props) {
|
for (const auto& kv : props) {
|
||||||
const std::string &key = kv.first;
|
const std::string& key = kv.first;
|
||||||
const std::string &val = kv.second;
|
const std::string& val = kv.second;
|
||||||
|
|
||||||
// ========== Common properties ==========
|
// ========== Common properties ==========
|
||||||
if (key == "X")
|
if (key == "X")
|
||||||
@ -95,7 +76,7 @@ void ThemeManager::applyProperties(
|
|||||||
// ========== Rectangle properties ==========
|
// ========== Rectangle properties ==========
|
||||||
else if (key == "Fill") {
|
else if (key == "Fill") {
|
||||||
if (elemType == UIElement::ElementType::Rectangle) {
|
if (elemType == UIElement::ElementType::Rectangle) {
|
||||||
auto rect = static_cast<Rectangle *>(elem);
|
auto rect = static_cast<Rectangle*>(elem);
|
||||||
if (val.find('{') != std::string::npos) {
|
if (val.find('{') != std::string::npos) {
|
||||||
rect->setFillExpr(val);
|
rect->setFillExpr(val);
|
||||||
} else {
|
} else {
|
||||||
@ -104,21 +85,19 @@ void ThemeManager::applyProperties(
|
|||||||
}
|
}
|
||||||
} else if (key == "Color") {
|
} else if (key == "Color") {
|
||||||
if (elemType == UIElement::ElementType::Rectangle) {
|
if (elemType == UIElement::ElementType::Rectangle) {
|
||||||
static_cast<Rectangle *>(elem)->setColorExpr(val);
|
static_cast<Rectangle*>(elem)->setColorExpr(val);
|
||||||
} else if (elemType == UIElement::ElementType::Container ||
|
} else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||||
elemType == UIElement::ElementType::HStack ||
|
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid ||
|
||||||
elemType == UIElement::ElementType::VStack ||
|
|
||||||
elemType == UIElement::ElementType::Grid ||
|
|
||||||
elemType == UIElement::ElementType::TabBar) {
|
elemType == UIElement::ElementType::TabBar) {
|
||||||
static_cast<Container *>(elem)->setBackgroundColorExpr(val);
|
static_cast<Container*>(elem)->setBackgroundColorExpr(val);
|
||||||
} else if (elemType == UIElement::ElementType::Label) {
|
} else if (elemType == UIElement::ElementType::Label) {
|
||||||
static_cast<Label *>(elem)->setColorExpr(val);
|
static_cast<Label*>(elem)->setColorExpr(val);
|
||||||
} else if (elemType == UIElement::ElementType::Divider) {
|
} else if (elemType == UIElement::ElementType::Divider) {
|
||||||
static_cast<Divider *>(elem)->setColorExpr(val);
|
static_cast<Divider*>(elem)->setColorExpr(val);
|
||||||
} else if (elemType == UIElement::ElementType::Icon) {
|
} else if (elemType == UIElement::ElementType::Icon) {
|
||||||
static_cast<Icon *>(elem)->setColorExpr(val);
|
static_cast<Icon*>(elem)->setColorExpr(val);
|
||||||
} else if (elemType == UIElement::ElementType::BatteryIcon) {
|
} else if (elemType == UIElement::ElementType::BatteryIcon) {
|
||||||
static_cast<BatteryIcon *>(elem)->setColor(val);
|
static_cast<BatteryIcon*>(elem)->setColor(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,278 +111,264 @@ void ThemeManager::applyProperties(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == "Padding") {
|
} else if (key == "Padding") {
|
||||||
if (elemType == UIElement::ElementType::Container ||
|
if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||||
elemType == UIElement::ElementType::HStack ||
|
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
|
||||||
elemType == UIElement::ElementType::VStack ||
|
static_cast<Container*>(elem)->setPadding(std::stoi(val));
|
||||||
elemType == UIElement::ElementType::Grid) {
|
|
||||||
static_cast<Container *>(elem)->setPadding(std::stoi(val));
|
|
||||||
} else if (elemType == UIElement::ElementType::TabBar) {
|
} else if (elemType == UIElement::ElementType::TabBar) {
|
||||||
static_cast<TabBar *>(elem)->setPadding(std::stoi(val));
|
static_cast<TabBar*>(elem)->setPadding(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "BorderRadius") {
|
} else if (key == "BorderRadius") {
|
||||||
if (elemType == UIElement::ElementType::Container ||
|
if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||||
elemType == UIElement::ElementType::HStack ||
|
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
|
||||||
elemType == UIElement::ElementType::VStack ||
|
static_cast<Container*>(elem)->setBorderRadius(std::stoi(val));
|
||||||
elemType == UIElement::ElementType::Grid) {
|
|
||||||
static_cast<Container *>(elem)->setBorderRadius(std::stoi(val));
|
|
||||||
}
|
}
|
||||||
} else if (key == "Spacing") {
|
} else if (key == "Spacing") {
|
||||||
if (elemType == UIElement::ElementType::HStack) {
|
if (elemType == UIElement::ElementType::HStack) {
|
||||||
static_cast<HStack *>(elem)->setSpacing(std::stoi(val));
|
static_cast<HStack*>(elem)->setSpacing(std::stoi(val));
|
||||||
} else if (elemType == UIElement::ElementType::VStack) {
|
} else if (elemType == UIElement::ElementType::VStack) {
|
||||||
static_cast<VStack *>(elem)->setSpacing(std::stoi(val));
|
static_cast<VStack*>(elem)->setSpacing(std::stoi(val));
|
||||||
} else if (elemType == UIElement::ElementType::List) {
|
} else if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List *>(elem)->setSpacing(std::stoi(val));
|
static_cast<List*>(elem)->setSpacing(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "CenterVertical") {
|
} else if (key == "CenterVertical") {
|
||||||
if (elemType == UIElement::ElementType::HStack) {
|
if (elemType == UIElement::ElementType::HStack) {
|
||||||
static_cast<HStack *>(elem)->setCenterVertical(val == "true" || val == "1");
|
static_cast<HStack*>(elem)->setCenterVertical(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
} else if (key == "CenterHorizontal") {
|
} else if (key == "CenterHorizontal") {
|
||||||
if (elemType == UIElement::ElementType::VStack) {
|
if (elemType == UIElement::ElementType::VStack) {
|
||||||
static_cast<VStack *>(elem)->setCenterHorizontal(val == "true" || val == "1");
|
static_cast<VStack*>(elem)->setCenterHorizontal(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Label properties ==========
|
// ========== Label properties ==========
|
||||||
else if (key == "Text") {
|
else if (key == "Text") {
|
||||||
if (elemType == UIElement::ElementType::Label) {
|
if (elemType == UIElement::ElementType::Label) {
|
||||||
static_cast<Label *>(elem)->setText(val);
|
static_cast<Label*>(elem)->setText(val);
|
||||||
} else if (elemType == UIElement::ElementType::Badge) {
|
} else if (elemType == UIElement::ElementType::Badge) {
|
||||||
static_cast<Badge *>(elem)->setText(val);
|
static_cast<Badge*>(elem)->setText(val);
|
||||||
}
|
}
|
||||||
} else if (key == "Font") {
|
} else if (key == "Font") {
|
||||||
if (elemType == UIElement::ElementType::Label) {
|
if (elemType == UIElement::ElementType::Label) {
|
||||||
if (fontMap.count(val)) {
|
if (fontMap.count(val)) {
|
||||||
static_cast<Label *>(elem)->setFont(fontMap[val]);
|
static_cast<Label*>(elem)->setFont(fontMap[val]);
|
||||||
}
|
}
|
||||||
} else if (elemType == UIElement::ElementType::Badge) {
|
} else if (elemType == UIElement::ElementType::Badge) {
|
||||||
if (fontMap.count(val)) {
|
if (fontMap.count(val)) {
|
||||||
static_cast<Badge *>(elem)->setFont(fontMap[val]);
|
static_cast<Badge*>(elem)->setFont(fontMap[val]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == "Centered") {
|
} else if (key == "Centered") {
|
||||||
if (elemType == UIElement::ElementType::Label) {
|
if (elemType == UIElement::ElementType::Label) {
|
||||||
static_cast<Label *>(elem)->setCentered(val == "true" || val == "1");
|
static_cast<Label*>(elem)->setCentered(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
} else if (key == "Align") {
|
} else if (key == "Align") {
|
||||||
if (elemType == UIElement::ElementType::Label) {
|
if (elemType == UIElement::ElementType::Label) {
|
||||||
Label::Alignment align = Label::Alignment::Left;
|
Label::Alignment align = Label::Alignment::Left;
|
||||||
if (val == "Center" || val == "center")
|
if (val == "Center" || val == "center") align = Label::Alignment::Center;
|
||||||
align = Label::Alignment::Center;
|
if (val == "Right" || val == "right") align = Label::Alignment::Right;
|
||||||
if (val == "Right" || val == "right")
|
static_cast<Label*>(elem)->setAlignment(align);
|
||||||
align = Label::Alignment::Right;
|
|
||||||
static_cast<Label *>(elem)->setAlignment(align);
|
|
||||||
}
|
}
|
||||||
} else if (key == "MaxLines") {
|
} else if (key == "MaxLines") {
|
||||||
if (elemType == UIElement::ElementType::Label) {
|
if (elemType == UIElement::ElementType::Label) {
|
||||||
static_cast<Label *>(elem)->setMaxLines(std::stoi(val));
|
static_cast<Label*>(elem)->setMaxLines(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "Ellipsis") {
|
} else if (key == "Ellipsis") {
|
||||||
if (elemType == UIElement::ElementType::Label) {
|
if (elemType == UIElement::ElementType::Label) {
|
||||||
static_cast<Label *>(elem)->setEllipsis(val == "true" || val == "1");
|
static_cast<Label*>(elem)->setEllipsis(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Bitmap/Icon properties ==========
|
// ========== Bitmap/Icon properties ==========
|
||||||
else if (key == "Src") {
|
else if (key == "Src") {
|
||||||
if (elemType == UIElement::ElementType::Bitmap) {
|
if (elemType == UIElement::ElementType::Bitmap) {
|
||||||
auto b = static_cast<BitmapElement *>(elem);
|
auto b = static_cast<BitmapElement*>(elem);
|
||||||
if (val.find('{') == std::string::npos &&
|
if (val.find('{') == std::string::npos && val.find('/') == std::string::npos) {
|
||||||
val.find('/') == std::string::npos) {
|
|
||||||
b->setSrc(getAssetPath(val));
|
b->setSrc(getAssetPath(val));
|
||||||
} else {
|
} else {
|
||||||
b->setSrc(val);
|
b->setSrc(val);
|
||||||
}
|
}
|
||||||
} else if (elemType == UIElement::ElementType::Icon) {
|
} else if (elemType == UIElement::ElementType::Icon) {
|
||||||
static_cast<Icon *>(elem)->setSrc(val);
|
static_cast<Icon*>(elem)->setSrc(val);
|
||||||
}
|
}
|
||||||
} else if (key == "ScaleToFit") {
|
} else if (key == "ScaleToFit") {
|
||||||
if (elemType == UIElement::ElementType::Bitmap) {
|
if (elemType == UIElement::ElementType::Bitmap) {
|
||||||
static_cast<BitmapElement *>(elem)->setScaleToFit(val == "true" ||
|
static_cast<BitmapElement*>(elem)->setScaleToFit(val == "true" || val == "1");
|
||||||
val == "1");
|
|
||||||
}
|
}
|
||||||
} else if (key == "PreserveAspect") {
|
} else if (key == "PreserveAspect") {
|
||||||
if (elemType == UIElement::ElementType::Bitmap) {
|
if (elemType == UIElement::ElementType::Bitmap) {
|
||||||
static_cast<BitmapElement *>(elem)->setPreserveAspect(val == "true" ||
|
static_cast<BitmapElement*>(elem)->setPreserveAspect(val == "true" || val == "1");
|
||||||
val == "1");
|
|
||||||
}
|
}
|
||||||
} else if (key == "IconSize") {
|
} else if (key == "IconSize") {
|
||||||
if (elemType == UIElement::ElementType::Icon) {
|
if (elemType == UIElement::ElementType::Icon) {
|
||||||
static_cast<Icon *>(elem)->setIconSize(std::stoi(val));
|
static_cast<Icon*>(elem)->setIconSize(std::stoi(val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== List properties ==========
|
// ========== List properties ==========
|
||||||
else if (key == "Source") {
|
else if (key == "Source") {
|
||||||
if (elemType == UIElement::ElementType::List) {
|
if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List *>(elem)->setSource(val);
|
static_cast<List*>(elem)->setSource(val);
|
||||||
}
|
}
|
||||||
} else if (key == "ItemTemplate") {
|
} else if (key == "ItemTemplate") {
|
||||||
if (elemType == UIElement::ElementType::List) {
|
if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List *>(elem)->setItemTemplateId(val);
|
static_cast<List*>(elem)->setItemTemplateId(val);
|
||||||
}
|
}
|
||||||
} else if (key == "ItemHeight") {
|
} else if (key == "ItemHeight") {
|
||||||
if (elemType == UIElement::ElementType::List) {
|
if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List *>(elem)->setItemHeight(std::stoi(val));
|
static_cast<List*>(elem)->setItemHeight(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "ItemWidth") {
|
} else if (key == "ItemWidth") {
|
||||||
if (elemType == UIElement::ElementType::List) {
|
if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List *>(elem)->setItemWidth(std::stoi(val));
|
static_cast<List*>(elem)->setItemWidth(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "Direction") {
|
} else if (key == "Direction") {
|
||||||
if (elemType == UIElement::ElementType::List) {
|
if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List *>(elem)->setDirectionFromString(val);
|
static_cast<List*>(elem)->setDirectionFromString(val);
|
||||||
}
|
}
|
||||||
} else if (key == "Columns") {
|
} else if (key == "Columns") {
|
||||||
if (elemType == UIElement::ElementType::List) {
|
if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List *>(elem)->setColumns(std::stoi(val));
|
static_cast<List*>(elem)->setColumns(std::stoi(val));
|
||||||
} else if (elemType == UIElement::ElementType::Grid) {
|
} else if (elemType == UIElement::ElementType::Grid) {
|
||||||
static_cast<Grid *>(elem)->setColumns(std::stoi(val));
|
static_cast<Grid*>(elem)->setColumns(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "RowSpacing") {
|
} else if (key == "RowSpacing") {
|
||||||
if (elemType == UIElement::ElementType::Grid) {
|
if (elemType == UIElement::ElementType::Grid) {
|
||||||
static_cast<Grid *>(elem)->setRowSpacing(std::stoi(val));
|
static_cast<Grid*>(elem)->setRowSpacing(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "ColSpacing") {
|
} else if (key == "ColSpacing") {
|
||||||
if (elemType == UIElement::ElementType::Grid) {
|
if (elemType == UIElement::ElementType::Grid) {
|
||||||
static_cast<Grid *>(elem)->setColSpacing(std::stoi(val));
|
static_cast<Grid*>(elem)->setColSpacing(std::stoi(val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== ProgressBar properties ==========
|
// ========== ProgressBar properties ==========
|
||||||
else if (key == "Value") {
|
else if (key == "Value") {
|
||||||
if (elemType == UIElement::ElementType::ProgressBar) {
|
if (elemType == UIElement::ElementType::ProgressBar) {
|
||||||
static_cast<ProgressBar *>(elem)->setValue(val);
|
static_cast<ProgressBar*>(elem)->setValue(val);
|
||||||
} else if (elemType == UIElement::ElementType::Toggle) {
|
} else if (elemType == UIElement::ElementType::Toggle) {
|
||||||
static_cast<Toggle *>(elem)->setValue(val);
|
static_cast<Toggle*>(elem)->setValue(val);
|
||||||
} else if (elemType == UIElement::ElementType::BatteryIcon) {
|
} else if (elemType == UIElement::ElementType::BatteryIcon) {
|
||||||
static_cast<BatteryIcon *>(elem)->setValue(val);
|
static_cast<BatteryIcon*>(elem)->setValue(val);
|
||||||
}
|
}
|
||||||
} else if (key == "Max") {
|
} else if (key == "Max") {
|
||||||
if (elemType == UIElement::ElementType::ProgressBar) {
|
if (elemType == UIElement::ElementType::ProgressBar) {
|
||||||
static_cast<ProgressBar *>(elem)->setMax(val);
|
static_cast<ProgressBar*>(elem)->setMax(val);
|
||||||
}
|
}
|
||||||
} else if (key == "FgColor") {
|
} else if (key == "FgColor") {
|
||||||
if (elemType == UIElement::ElementType::ProgressBar) {
|
if (elemType == UIElement::ElementType::ProgressBar) {
|
||||||
static_cast<ProgressBar *>(elem)->setFgColor(val);
|
static_cast<ProgressBar*>(elem)->setFgColor(val);
|
||||||
} else if (elemType == UIElement::ElementType::Badge) {
|
} else if (elemType == UIElement::ElementType::Badge) {
|
||||||
static_cast<Badge *>(elem)->setFgColor(val);
|
static_cast<Badge*>(elem)->setFgColor(val);
|
||||||
}
|
}
|
||||||
} else if (key == "BgColor") {
|
} else if (key == "BgColor") {
|
||||||
if (elemType == UIElement::ElementType::ProgressBar) {
|
if (elemType == UIElement::ElementType::ProgressBar) {
|
||||||
static_cast<ProgressBar *>(elem)->setBgColor(val);
|
static_cast<ProgressBar*>(elem)->setBgColor(val);
|
||||||
} else if (elemType == UIElement::ElementType::Badge) {
|
} else if (elemType == UIElement::ElementType::Badge) {
|
||||||
static_cast<Badge *>(elem)->setBgColor(val);
|
static_cast<Badge*>(elem)->setBgColor(val);
|
||||||
} else if (elemType == UIElement::ElementType::Container ||
|
} else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||||
elemType == UIElement::ElementType::HStack ||
|
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
|
||||||
elemType == UIElement::ElementType::VStack ||
|
static_cast<Container*>(elem)->setBackgroundColorExpr(val);
|
||||||
elemType == UIElement::ElementType::Grid) {
|
|
||||||
static_cast<Container *>(elem)->setBackgroundColorExpr(val);
|
|
||||||
}
|
}
|
||||||
} else if (key == "ShowBorder") {
|
} else if (key == "ShowBorder") {
|
||||||
if (elemType == UIElement::ElementType::ProgressBar) {
|
if (elemType == UIElement::ElementType::ProgressBar) {
|
||||||
static_cast<ProgressBar *>(elem)->setShowBorder(val == "true" ||
|
static_cast<ProgressBar*>(elem)->setShowBorder(val == "true" || val == "1");
|
||||||
val == "1");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Divider properties ==========
|
// ========== Divider properties ==========
|
||||||
else if (key == "Horizontal") {
|
else if (key == "Horizontal") {
|
||||||
if (elemType == UIElement::ElementType::Divider) {
|
if (elemType == UIElement::ElementType::Divider) {
|
||||||
static_cast<Divider *>(elem)->setHorizontal(val == "true" || val == "1");
|
static_cast<Divider*>(elem)->setHorizontal(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
} else if (key == "Thickness") {
|
} else if (key == "Thickness") {
|
||||||
if (elemType == UIElement::ElementType::Divider) {
|
if (elemType == UIElement::ElementType::Divider) {
|
||||||
static_cast<Divider *>(elem)->setThickness(std::stoi(val));
|
static_cast<Divider*>(elem)->setThickness(std::stoi(val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Toggle properties ==========
|
// ========== Toggle properties ==========
|
||||||
else if (key == "OnColor") {
|
else if (key == "OnColor") {
|
||||||
if (elemType == UIElement::ElementType::Toggle) {
|
if (elemType == UIElement::ElementType::Toggle) {
|
||||||
static_cast<Toggle *>(elem)->setOnColor(val);
|
static_cast<Toggle*>(elem)->setOnColor(val);
|
||||||
}
|
}
|
||||||
} else if (key == "OffColor") {
|
} else if (key == "OffColor") {
|
||||||
if (elemType == UIElement::ElementType::Toggle) {
|
if (elemType == UIElement::ElementType::Toggle) {
|
||||||
static_cast<Toggle *>(elem)->setOffColor(val);
|
static_cast<Toggle*>(elem)->setOffColor(val);
|
||||||
}
|
}
|
||||||
} else if (key == "TrackWidth") {
|
} else if (key == "TrackWidth") {
|
||||||
if (elemType == UIElement::ElementType::Toggle) {
|
if (elemType == UIElement::ElementType::Toggle) {
|
||||||
static_cast<Toggle *>(elem)->setTrackWidth(std::stoi(val));
|
static_cast<Toggle*>(elem)->setTrackWidth(std::stoi(val));
|
||||||
} else if (elemType == UIElement::ElementType::ScrollIndicator) {
|
} else if (elemType == UIElement::ElementType::ScrollIndicator) {
|
||||||
static_cast<ScrollIndicator *>(elem)->setTrackWidth(std::stoi(val));
|
static_cast<ScrollIndicator*>(elem)->setTrackWidth(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "TrackHeight") {
|
} else if (key == "TrackHeight") {
|
||||||
if (elemType == UIElement::ElementType::Toggle) {
|
if (elemType == UIElement::ElementType::Toggle) {
|
||||||
static_cast<Toggle *>(elem)->setTrackHeight(std::stoi(val));
|
static_cast<Toggle*>(elem)->setTrackHeight(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "KnobSize") {
|
} else if (key == "KnobSize") {
|
||||||
if (elemType == UIElement::ElementType::Toggle) {
|
if (elemType == UIElement::ElementType::Toggle) {
|
||||||
static_cast<Toggle *>(elem)->setKnobSize(std::stoi(val));
|
static_cast<Toggle*>(elem)->setKnobSize(std::stoi(val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== TabBar properties ==========
|
// ========== TabBar properties ==========
|
||||||
else if (key == "Selected") {
|
else if (key == "Selected") {
|
||||||
if (elemType == UIElement::ElementType::TabBar) {
|
if (elemType == UIElement::ElementType::TabBar) {
|
||||||
static_cast<TabBar *>(elem)->setSelected(val);
|
static_cast<TabBar*>(elem)->setSelected(val);
|
||||||
}
|
}
|
||||||
} else if (key == "TabSpacing") {
|
} else if (key == "TabSpacing") {
|
||||||
if (elemType == UIElement::ElementType::TabBar) {
|
if (elemType == UIElement::ElementType::TabBar) {
|
||||||
static_cast<TabBar *>(elem)->setTabSpacing(std::stoi(val));
|
static_cast<TabBar*>(elem)->setTabSpacing(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "IndicatorHeight") {
|
} else if (key == "IndicatorHeight") {
|
||||||
if (elemType == UIElement::ElementType::TabBar) {
|
if (elemType == UIElement::ElementType::TabBar) {
|
||||||
static_cast<TabBar *>(elem)->setIndicatorHeight(std::stoi(val));
|
static_cast<TabBar*>(elem)->setIndicatorHeight(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "ShowIndicator") {
|
} else if (key == "ShowIndicator") {
|
||||||
if (elemType == UIElement::ElementType::TabBar) {
|
if (elemType == UIElement::ElementType::TabBar) {
|
||||||
static_cast<TabBar *>(elem)->setShowIndicator(val == "true" || val == "1");
|
static_cast<TabBar*>(elem)->setShowIndicator(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== ScrollIndicator properties ==========
|
// ========== ScrollIndicator properties ==========
|
||||||
else if (key == "Position") {
|
else if (key == "Position") {
|
||||||
if (elemType == UIElement::ElementType::ScrollIndicator) {
|
if (elemType == UIElement::ElementType::ScrollIndicator) {
|
||||||
static_cast<ScrollIndicator *>(elem)->setPosition(val);
|
static_cast<ScrollIndicator*>(elem)->setPosition(val);
|
||||||
}
|
}
|
||||||
} else if (key == "Total") {
|
} else if (key == "Total") {
|
||||||
if (elemType == UIElement::ElementType::ScrollIndicator) {
|
if (elemType == UIElement::ElementType::ScrollIndicator) {
|
||||||
static_cast<ScrollIndicator *>(elem)->setTotal(val);
|
static_cast<ScrollIndicator*>(elem)->setTotal(val);
|
||||||
}
|
}
|
||||||
} else if (key == "VisibleCount") {
|
} else if (key == "VisibleCount") {
|
||||||
if (elemType == UIElement::ElementType::ScrollIndicator) {
|
if (elemType == UIElement::ElementType::ScrollIndicator) {
|
||||||
static_cast<ScrollIndicator *>(elem)->setVisibleCount(val);
|
static_cast<ScrollIndicator*>(elem)->setVisibleCount(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Badge properties ==========
|
// ========== Badge properties ==========
|
||||||
else if (key == "PaddingH") {
|
else if (key == "PaddingH") {
|
||||||
if (elemType == UIElement::ElementType::Badge) {
|
if (elemType == UIElement::ElementType::Badge) {
|
||||||
static_cast<Badge *>(elem)->setPaddingH(std::stoi(val));
|
static_cast<Badge*>(elem)->setPaddingH(std::stoi(val));
|
||||||
}
|
}
|
||||||
} else if (key == "PaddingV") {
|
} else if (key == "PaddingV") {
|
||||||
if (elemType == UIElement::ElementType::Badge) {
|
if (elemType == UIElement::ElementType::Badge) {
|
||||||
static_cast<Badge *>(elem)->setPaddingV(std::stoi(val));
|
static_cast<Badge*>(elem)->setPaddingV(std::stoi(val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<uint8_t> *
|
const std::vector<uint8_t>* ThemeManager::getCachedAsset(const std::string& path) {
|
||||||
ThemeManager::getCachedAsset(const std::string &path) {
|
|
||||||
if (assetCache.count(path)) {
|
if (assetCache.count(path)) {
|
||||||
return &assetCache.at(path);
|
return &assetCache.at(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SdMan.exists(path.c_str()))
|
if (!SdMan.exists(path.c_str())) return nullptr;
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (SdMan.openFileForRead("ThemeCache", path, file)) {
|
if (SdMan.openFileForRead("ThemeCache", path, file)) {
|
||||||
size_t size = file.size();
|
size_t size = file.size();
|
||||||
auto &buf = assetCache[path];
|
auto& buf = assetCache[path];
|
||||||
buf.resize(size);
|
buf.resize(size);
|
||||||
file.read(buf.data(), size);
|
file.read(buf.data(), size);
|
||||||
file.close();
|
file.close();
|
||||||
@ -412,18 +377,16 @@ ThemeManager::getCachedAsset(const std::string &path) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProcessedAsset *
|
const ProcessedAsset* ThemeManager::getProcessedAsset(const std::string& path, GfxRenderer::Orientation orientation,
|
||||||
ThemeManager::getProcessedAsset(const std::string &path,
|
int targetW, int targetH) {
|
||||||
GfxRenderer::Orientation orientation,
|
|
||||||
int targetW, int targetH) {
|
|
||||||
// Include dimensions in cache key for scaled images
|
// Include dimensions in cache key for scaled images
|
||||||
std::string cacheKey = path;
|
std::string cacheKey = path;
|
||||||
if (targetW > 0 && targetH > 0) {
|
if (targetW > 0 && targetH > 0) {
|
||||||
cacheKey += ":" + std::to_string(targetW) + "x" + std::to_string(targetH);
|
cacheKey += ":" + std::to_string(targetW) + "x" + std::to_string(targetH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processedCache.count(cacheKey)) {
|
if (processedCache.count(cacheKey)) {
|
||||||
const auto &asset = processedCache.at(cacheKey);
|
const auto& asset = processedCache.at(cacheKey);
|
||||||
if (asset.orientation == orientation) {
|
if (asset.orientation == orientation) {
|
||||||
return &asset;
|
return &asset;
|
||||||
}
|
}
|
||||||
@ -431,9 +394,7 @@ ThemeManager::getProcessedAsset(const std::string &path,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::cacheProcessedAsset(const std::string &path,
|
void ThemeManager::cacheProcessedAsset(const std::string& path, const ProcessedAsset& asset, int targetW, int targetH) {
|
||||||
const ProcessedAsset &asset,
|
|
||||||
int targetW, int targetH) {
|
|
||||||
std::string cacheKey = path;
|
std::string cacheKey = path;
|
||||||
if (targetW > 0 && targetH > 0) {
|
if (targetW > 0 && targetH > 0) {
|
||||||
cacheKey += ":" + std::to_string(targetW) + "x" + std::to_string(targetH);
|
cacheKey += ":" + std::to_string(targetW) + "x" + std::to_string(targetH);
|
||||||
@ -456,19 +417,18 @@ void ThemeManager::unloadTheme() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::invalidateAllCaches() {
|
void ThemeManager::invalidateAllCaches() {
|
||||||
for (auto &kv : screenCaches) {
|
for (auto& kv : screenCaches) {
|
||||||
kv.second.invalidate();
|
kv.second.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::invalidateScreenCache(const std::string &screenName) {
|
void ThemeManager::invalidateScreenCache(const std::string& screenName) {
|
||||||
if (screenCaches.count(screenName)) {
|
if (screenCaches.count(screenName)) {
|
||||||
screenCaches[screenName].invalidate();
|
screenCaches[screenName].invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ThemeManager::computeContextHash(const ThemeContext &context,
|
uint32_t ThemeManager::computeContextHash(const ThemeContext& context, const std::string& screenName) {
|
||||||
const std::string &screenName) {
|
|
||||||
uint32_t hash = 2166136261u;
|
uint32_t hash = 2166136261u;
|
||||||
for (char c : screenName) {
|
for (char c : screenName) {
|
||||||
hash ^= static_cast<uint32_t>(c);
|
hash ^= static_cast<uint32_t>(c);
|
||||||
@ -477,7 +437,7 @@ uint32_t ThemeManager::computeContextHash(const ThemeContext &context,
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::loadTheme(const std::string &themeName) {
|
void ThemeManager::loadTheme(const std::string& themeName) {
|
||||||
unloadTheme();
|
unloadTheme();
|
||||||
currentThemeName = themeName;
|
currentThemeName = themeName;
|
||||||
|
|
||||||
@ -500,7 +460,7 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
|||||||
} else {
|
} else {
|
||||||
std::string path = "/themes/" + themeName + "/theme.ini";
|
std::string path = "/themes/" + themeName + "/theme.ini";
|
||||||
Serial.printf("[ThemeManager] Checking path: %s\n", path.c_str());
|
Serial.printf("[ThemeManager] Checking path: %s\n", path.c_str());
|
||||||
|
|
||||||
if (!SdMan.exists(path.c_str())) {
|
if (!SdMan.exists(path.c_str())) {
|
||||||
Serial.printf("[ThemeManager] Theme %s not found, using Default\n", themeName.c_str());
|
Serial.printf("[ThemeManager] Theme %s not found, using Default\n", themeName.c_str());
|
||||||
sections = IniParser::parseString(getDefaultThemeIni());
|
sections = IniParser::parseString(getDefaultThemeIni());
|
||||||
@ -511,8 +471,7 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
|||||||
Serial.printf("[ThemeManager] Parsing theme file...\n");
|
Serial.printf("[ThemeManager] Parsing theme file...\n");
|
||||||
sections = IniParser::parse(file);
|
sections = IniParser::parse(file);
|
||||||
file.close();
|
file.close();
|
||||||
Serial.printf("[ThemeManager] Parsed %d sections from %s\n",
|
Serial.printf("[ThemeManager] Parsed %d sections from %s\n", (int)sections.size(), themeName.c_str());
|
||||||
(int)sections.size(), themeName.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[ThemeManager] Failed to open %s, using Default\n", path.c_str());
|
Serial.printf("[ThemeManager] Failed to open %s, using Default\n", path.c_str());
|
||||||
sections = IniParser::parseString(getDefaultThemeIni());
|
sections = IniParser::parseString(getDefaultThemeIni());
|
||||||
@ -522,63 +481,57 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read theme configuration from [Global] section
|
// Read theme configuration from [Global] section
|
||||||
navBookCount = 1; // Default
|
navBookCount = 1; // Default
|
||||||
if (sections.count("Global")) {
|
if (sections.count("Global")) {
|
||||||
const auto &global = sections.at("Global");
|
const auto& global = sections.at("Global");
|
||||||
if (global.count("NavBookCount")) {
|
if (global.count("NavBookCount")) {
|
||||||
navBookCount = std::stoi(global.at("NavBookCount"));
|
navBookCount = std::stoi(global.at("NavBookCount"));
|
||||||
if (navBookCount < 1) navBookCount = 1;
|
if (navBookCount < 1) navBookCount = 1;
|
||||||
if (navBookCount > 10) navBookCount = 10; // Reasonable max
|
if (navBookCount > 10) navBookCount = 10; // Reasonable max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 1: Creation
|
// Pass 1: Creation
|
||||||
for (const auto &sec : sections) {
|
for (const auto& sec : sections) {
|
||||||
std::string id = sec.first;
|
std::string id = sec.first;
|
||||||
const std::map<std::string, std::string> &props = sec.second;
|
const std::map<std::string, std::string>& props = sec.second;
|
||||||
|
|
||||||
if (id == "Global")
|
if (id == "Global") continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
auto it = props.find("Type");
|
auto it = props.find("Type");
|
||||||
if (it == props.end())
|
if (it == props.end()) continue;
|
||||||
continue;
|
|
||||||
std::string type = it->second;
|
std::string type = it->second;
|
||||||
if (type.empty())
|
if (type.empty()) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
UIElement *elem = createElement(id, type);
|
UIElement* elem = createElement(id, type);
|
||||||
if (elem) {
|
if (elem) {
|
||||||
elements[id] = elem;
|
elements[id] = elem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: Properties & Parent Wiring
|
// Pass 2: Properties & Parent Wiring
|
||||||
std::vector<List *> lists;
|
std::vector<List*> lists;
|
||||||
for (const auto &sec : sections) {
|
for (const auto& sec : sections) {
|
||||||
std::string id = sec.first;
|
std::string id = sec.first;
|
||||||
if (id == "Global")
|
if (id == "Global") continue;
|
||||||
continue;
|
if (elements.find(id) == elements.end()) continue;
|
||||||
if (elements.find(id) == elements.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
UIElement *elem = elements[id];
|
UIElement* elem = elements[id];
|
||||||
applyProperties(elem, sec.second);
|
applyProperties(elem, sec.second);
|
||||||
|
|
||||||
if (elem->getType() == UIElement::ElementType::List) {
|
if (elem->getType() == UIElement::ElementType::List) {
|
||||||
lists.push_back(static_cast<List *>(elem));
|
lists.push_back(static_cast<List*>(elem));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sec.second.count("Parent")) {
|
if (sec.second.count("Parent")) {
|
||||||
std::string parentId = sec.second.at("Parent");
|
std::string parentId = sec.second.at("Parent");
|
||||||
if (elements.count(parentId)) {
|
if (elements.count(parentId)) {
|
||||||
UIElement *parent = elements[parentId];
|
UIElement* parent = elements[parentId];
|
||||||
if (auto c = parent->asContainer()) {
|
if (auto c = parent->asContainer()) {
|
||||||
c->addChild(elem);
|
c->addChild(elem);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[ThemeManager] WARN: Parent %s not found for %s\n",
|
Serial.printf("[ThemeManager] WARN: Parent %s not found for %s\n", parentId.c_str(), id.c_str());
|
||||||
parentId.c_str(), id.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -588,31 +541,26 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
|||||||
l->resolveTemplate(elements);
|
l->resolveTemplate(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[ThemeManager] Theme loaded with %d elements\n",
|
Serial.printf("[ThemeManager] Theme loaded with %d elements\n", (int)elements.size());
|
||||||
(int)elements.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::renderScreen(const std::string &screenName,
|
void ThemeManager::renderScreen(const std::string& screenName, const GfxRenderer& renderer,
|
||||||
const GfxRenderer &renderer,
|
const ThemeContext& context) {
|
||||||
const ThemeContext &context) {
|
|
||||||
if (elements.count(screenName) == 0) {
|
if (elements.count(screenName) == 0) {
|
||||||
Serial.printf("[ThemeManager] Screen '%s' not found\n", screenName.c_str());
|
Serial.printf("[ThemeManager] Screen '%s' not found\n", screenName.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UIElement *root = elements[screenName];
|
UIElement* root = elements[screenName];
|
||||||
|
|
||||||
root->layout(context, 0, 0, renderer.getScreenWidth(),
|
root->layout(context, 0, 0, renderer.getScreenWidth(), renderer.getScreenHeight());
|
||||||
renderer.getScreenHeight());
|
|
||||||
|
|
||||||
root->draw(renderer, context);
|
root->draw(renderer, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::renderScreenOptimized(const std::string &screenName,
|
void ThemeManager::renderScreenOptimized(const std::string& screenName, const GfxRenderer& renderer,
|
||||||
const GfxRenderer &renderer,
|
const ThemeContext& context, const ThemeContext* prevContext) {
|
||||||
const ThemeContext &context,
|
|
||||||
const ThemeContext *prevContext) {
|
|
||||||
renderScreen(screenName, renderer, context);
|
renderScreen(screenName, renderer, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ThemeEngine
|
} // namespace ThemeEngine
|
||||||
|
|||||||
@ -16,7 +16,7 @@ constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
|||||||
// Increment this when adding new persisted settings fields
|
// Increment this when adding new persisted settings fields
|
||||||
constexpr uint8_t SETTINGS_COUNT = 21;
|
constexpr uint8_t SETTINGS_COUNT = 21;
|
||||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool CrossPointSettings::saveToFile() const {
|
bool CrossPointSettings::saveToFile() const {
|
||||||
// Make sure the directory exists
|
// Make sure the directory exists
|
||||||
@ -65,8 +65,7 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version != SETTINGS_FILE_VERSION) {
|
if (version != SETTINGS_FILE_VERSION) {
|
||||||
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n",
|
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||||
millis(), version);
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -78,78 +77,57 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
uint8_t settingsRead = 0;
|
uint8_t settingsRead = 0;
|
||||||
do {
|
do {
|
||||||
serialization::readPod(inputFile, sleepScreen);
|
serialization::readPod(inputFile, sleepScreen);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, extraParagraphSpacing);
|
serialization::readPod(inputFile, extraParagraphSpacing);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, shortPwrBtn);
|
serialization::readPod(inputFile, shortPwrBtn);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, statusBar);
|
serialization::readPod(inputFile, statusBar);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, orientation);
|
serialization::readPod(inputFile, orientation);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, frontButtonLayout);
|
serialization::readPod(inputFile, frontButtonLayout);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, sideButtonLayout);
|
serialization::readPod(inputFile, sideButtonLayout);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, fontFamily);
|
serialization::readPod(inputFile, fontFamily);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, fontSize);
|
serialization::readPod(inputFile, fontSize);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, lineSpacing);
|
serialization::readPod(inputFile, lineSpacing);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, paragraphAlignment);
|
serialization::readPod(inputFile, paragraphAlignment);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, sleepTimeout);
|
serialization::readPod(inputFile, sleepTimeout);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, refreshFrequency);
|
serialization::readPod(inputFile, refreshFrequency);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, screenMargin);
|
serialization::readPod(inputFile, screenMargin);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, sleepScreenCoverMode);
|
serialization::readPod(inputFile, sleepScreenCoverMode);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
{
|
{
|
||||||
std::string urlStr;
|
std::string urlStr;
|
||||||
serialization::readString(inputFile, urlStr);
|
serialization::readString(inputFile, urlStr);
|
||||||
strncpy(opdsServerUrl, urlStr.c_str(), sizeof(opdsServerUrl) - 1);
|
strncpy(opdsServerUrl, urlStr.c_str(), sizeof(opdsServerUrl) - 1);
|
||||||
opdsServerUrl[sizeof(opdsServerUrl) - 1] = '\0';
|
opdsServerUrl[sizeof(opdsServerUrl) - 1] = '\0';
|
||||||
}
|
}
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, textAntiAliasing);
|
serialization::readPod(inputFile, textAntiAliasing);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, hideBatteryPercentage);
|
serialization::readPod(inputFile, hideBatteryPercentage);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, longPressChapterSkip);
|
serialization::readPod(inputFile, longPressChapterSkip);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
serialization::readPod(inputFile, hyphenationEnabled);
|
serialization::readPod(inputFile, hyphenationEnabled);
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
{
|
{
|
||||||
std::string themeStr;
|
std::string themeStr;
|
||||||
serialization::readString(inputFile, themeStr);
|
serialization::readString(inputFile, themeStr);
|
||||||
strncpy(themeName, themeStr.c_str(), sizeof(themeName) - 1);
|
strncpy(themeName, themeStr.c_str(), sizeof(themeName) - 1);
|
||||||
themeName[sizeof(themeName) - 1] = '\0';
|
themeName[sizeof(themeName) - 1] = '\0';
|
||||||
}
|
}
|
||||||
if (++settingsRead >= fileSettingsCount)
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
break;
|
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
@ -159,110 +137,110 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
|
|
||||||
float CrossPointSettings::getReaderLineCompression() const {
|
float CrossPointSettings::getReaderLineCompression() const {
|
||||||
switch (fontFamily) {
|
switch (fontFamily) {
|
||||||
case BOOKERLY:
|
case BOOKERLY:
|
||||||
default:
|
|
||||||
switch (lineSpacing) {
|
|
||||||
case TIGHT:
|
|
||||||
return 0.95f;
|
|
||||||
case NORMAL:
|
|
||||||
default:
|
default:
|
||||||
return 1.0f;
|
switch (lineSpacing) {
|
||||||
case WIDE:
|
case TIGHT:
|
||||||
return 1.1f;
|
return 0.95f;
|
||||||
}
|
case NORMAL:
|
||||||
case NOTOSANS:
|
default:
|
||||||
switch (lineSpacing) {
|
return 1.0f;
|
||||||
case TIGHT:
|
case WIDE:
|
||||||
return 0.90f;
|
return 1.1f;
|
||||||
case NORMAL:
|
}
|
||||||
default:
|
case NOTOSANS:
|
||||||
return 0.95f;
|
switch (lineSpacing) {
|
||||||
case WIDE:
|
case TIGHT:
|
||||||
return 1.0f;
|
return 0.90f;
|
||||||
}
|
case NORMAL:
|
||||||
case OPENDYSLEXIC:
|
default:
|
||||||
switch (lineSpacing) {
|
return 0.95f;
|
||||||
case TIGHT:
|
case WIDE:
|
||||||
return 0.90f;
|
return 1.0f;
|
||||||
case NORMAL:
|
}
|
||||||
default:
|
case OPENDYSLEXIC:
|
||||||
return 0.95f;
|
switch (lineSpacing) {
|
||||||
case WIDE:
|
case TIGHT:
|
||||||
return 1.0f;
|
return 0.90f;
|
||||||
}
|
case NORMAL:
|
||||||
|
default:
|
||||||
|
return 0.95f;
|
||||||
|
case WIDE:
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long CrossPointSettings::getSleepTimeoutMs() const {
|
unsigned long CrossPointSettings::getSleepTimeoutMs() const {
|
||||||
switch (sleepTimeout) {
|
switch (sleepTimeout) {
|
||||||
case SLEEP_1_MIN:
|
case SLEEP_1_MIN:
|
||||||
return 1UL * 60 * 1000;
|
return 1UL * 60 * 1000;
|
||||||
case SLEEP_5_MIN:
|
case SLEEP_5_MIN:
|
||||||
return 5UL * 60 * 1000;
|
return 5UL * 60 * 1000;
|
||||||
case SLEEP_10_MIN:
|
case SLEEP_10_MIN:
|
||||||
default:
|
default:
|
||||||
return 10UL * 60 * 1000;
|
return 10UL * 60 * 1000;
|
||||||
case SLEEP_15_MIN:
|
case SLEEP_15_MIN:
|
||||||
return 15UL * 60 * 1000;
|
return 15UL * 60 * 1000;
|
||||||
case SLEEP_30_MIN:
|
case SLEEP_30_MIN:
|
||||||
return 30UL * 60 * 1000;
|
return 30UL * 60 * 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int CrossPointSettings::getRefreshFrequency() const {
|
int CrossPointSettings::getRefreshFrequency() const {
|
||||||
switch (refreshFrequency) {
|
switch (refreshFrequency) {
|
||||||
case REFRESH_1:
|
case REFRESH_1:
|
||||||
return 1;
|
return 1;
|
||||||
case REFRESH_5:
|
case REFRESH_5:
|
||||||
return 5;
|
return 5;
|
||||||
case REFRESH_10:
|
case REFRESH_10:
|
||||||
return 10;
|
return 10;
|
||||||
case REFRESH_15:
|
case REFRESH_15:
|
||||||
default:
|
default:
|
||||||
return 15;
|
return 15;
|
||||||
case REFRESH_30:
|
case REFRESH_30:
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int CrossPointSettings::getReaderFontId() const {
|
int CrossPointSettings::getReaderFontId() const {
|
||||||
switch (fontFamily) {
|
switch (fontFamily) {
|
||||||
case BOOKERLY:
|
case BOOKERLY:
|
||||||
default:
|
|
||||||
switch (fontSize) {
|
|
||||||
case SMALL:
|
|
||||||
return BOOKERLY_12_FONT_ID;
|
|
||||||
case MEDIUM:
|
|
||||||
default:
|
default:
|
||||||
return BOOKERLY_14_FONT_ID;
|
switch (fontSize) {
|
||||||
case LARGE:
|
case SMALL:
|
||||||
return BOOKERLY_16_FONT_ID;
|
return BOOKERLY_12_FONT_ID;
|
||||||
case EXTRA_LARGE:
|
case MEDIUM:
|
||||||
return BOOKERLY_18_FONT_ID;
|
default:
|
||||||
}
|
return BOOKERLY_14_FONT_ID;
|
||||||
case NOTOSANS:
|
case LARGE:
|
||||||
switch (fontSize) {
|
return BOOKERLY_16_FONT_ID;
|
||||||
case SMALL:
|
case EXTRA_LARGE:
|
||||||
return NOTOSANS_12_FONT_ID;
|
return BOOKERLY_18_FONT_ID;
|
||||||
case MEDIUM:
|
}
|
||||||
default:
|
case NOTOSANS:
|
||||||
return NOTOSANS_14_FONT_ID;
|
switch (fontSize) {
|
||||||
case LARGE:
|
case SMALL:
|
||||||
return NOTOSANS_16_FONT_ID;
|
return NOTOSANS_12_FONT_ID;
|
||||||
case EXTRA_LARGE:
|
case MEDIUM:
|
||||||
return NOTOSANS_18_FONT_ID;
|
default:
|
||||||
}
|
return NOTOSANS_14_FONT_ID;
|
||||||
case OPENDYSLEXIC:
|
case LARGE:
|
||||||
switch (fontSize) {
|
return NOTOSANS_16_FONT_ID;
|
||||||
case SMALL:
|
case EXTRA_LARGE:
|
||||||
return OPENDYSLEXIC_8_FONT_ID;
|
return NOTOSANS_18_FONT_ID;
|
||||||
case MEDIUM:
|
}
|
||||||
default:
|
case OPENDYSLEXIC:
|
||||||
return OPENDYSLEXIC_10_FONT_ID;
|
switch (fontSize) {
|
||||||
case LARGE:
|
case SMALL:
|
||||||
return OPENDYSLEXIC_12_FONT_ID;
|
return OPENDYSLEXIC_8_FONT_ID;
|
||||||
case EXTRA_LARGE:
|
case MEDIUM:
|
||||||
return OPENDYSLEXIC_14_FONT_ID;
|
default:
|
||||||
}
|
return OPENDYSLEXIC_10_FONT_ID;
|
||||||
|
case LARGE:
|
||||||
|
return OPENDYSLEXIC_12_FONT_ID;
|
||||||
|
case EXTRA_LARGE:
|
||||||
|
return OPENDYSLEXIC_14_FONT_ID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,47 +3,36 @@
|
|||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
|
|
||||||
class CrossPointSettings {
|
class CrossPointSettings {
|
||||||
private:
|
private:
|
||||||
// Private constructor for singleton
|
// Private constructor for singleton
|
||||||
CrossPointSettings() = default;
|
CrossPointSettings() = default;
|
||||||
|
|
||||||
// Static instance
|
// Static instance
|
||||||
static CrossPointSettings instance;
|
static CrossPointSettings instance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Delete copy constructor and assignment
|
// Delete copy constructor and assignment
|
||||||
CrossPointSettings(const CrossPointSettings &) = delete;
|
CrossPointSettings(const CrossPointSettings&) = delete;
|
||||||
CrossPointSettings &operator=(const CrossPointSettings &) = delete;
|
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
|
||||||
|
|
||||||
// Should match with SettingsActivity text
|
// Should match with SettingsActivity text
|
||||||
enum SLEEP_SCREEN_MODE {
|
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
|
||||||
DARK = 0,
|
|
||||||
LIGHT = 1,
|
|
||||||
CUSTOM = 2,
|
|
||||||
COVER = 3,
|
|
||||||
BLANK = 4
|
|
||||||
};
|
|
||||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 };
|
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 };
|
||||||
|
|
||||||
enum ORIENTATION {
|
enum ORIENTATION {
|
||||||
PORTRAIT = 0, // 480x800 logical coordinates (current default)
|
PORTRAIT = 0, // 480x800 logical coordinates (current default)
|
||||||
LANDSCAPE_CW =
|
LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
|
||||||
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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Front button layout options
|
// Front button layout options
|
||||||
// Default: Back, Confirm, Left, Right
|
// Default: Back, Confirm, Left, Right
|
||||||
// Swapped: Left, Right, Back, Confirm
|
// Swapped: Left, Right, Back, Confirm
|
||||||
enum FRONT_BUTTON_LAYOUT {
|
enum FRONT_BUTTON_LAYOUT { BACK_CONFIRM_LEFT_RIGHT = 0, LEFT_RIGHT_BACK_CONFIRM = 1, LEFT_BACK_CONFIRM_RIGHT = 2 };
|
||||||
BACK_CONFIRM_LEFT_RIGHT = 0,
|
|
||||||
LEFT_RIGHT_BACK_CONFIRM = 1,
|
|
||||||
LEFT_BACK_CONFIRM_RIGHT = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
// Side button layout options
|
// Side button layout options
|
||||||
// Default: Previous, Next
|
// Default: Previous, Next
|
||||||
@ -55,40 +44,19 @@ public:
|
|||||||
// Font size options
|
// Font size options
|
||||||
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3 };
|
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3 };
|
||||||
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2 };
|
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2 };
|
||||||
enum PARAGRAPH_ALIGNMENT {
|
enum PARAGRAPH_ALIGNMENT { JUSTIFIED = 0, LEFT_ALIGN = 1, CENTER_ALIGN = 2, RIGHT_ALIGN = 3 };
|
||||||
JUSTIFIED = 0,
|
|
||||||
LEFT_ALIGN = 1,
|
|
||||||
CENTER_ALIGN = 2,
|
|
||||||
RIGHT_ALIGN = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
// Auto-sleep timeout options (in minutes)
|
// Auto-sleep timeout options (in minutes)
|
||||||
enum SLEEP_TIMEOUT {
|
enum SLEEP_TIMEOUT { SLEEP_1_MIN = 0, SLEEP_5_MIN = 1, SLEEP_10_MIN = 2, SLEEP_15_MIN = 3, SLEEP_30_MIN = 4 };
|
||||||
SLEEP_1_MIN = 0,
|
|
||||||
SLEEP_5_MIN = 1,
|
|
||||||
SLEEP_10_MIN = 2,
|
|
||||||
SLEEP_15_MIN = 3,
|
|
||||||
SLEEP_30_MIN = 4
|
|
||||||
};
|
|
||||||
|
|
||||||
// E-ink refresh frequency (pages between full refreshes)
|
// E-ink refresh frequency (pages between full refreshes)
|
||||||
enum REFRESH_FREQUENCY {
|
enum REFRESH_FREQUENCY { REFRESH_1 = 0, REFRESH_5 = 1, REFRESH_10 = 2, REFRESH_15 = 3, REFRESH_30 = 4 };
|
||||||
REFRESH_1 = 0,
|
|
||||||
REFRESH_5 = 1,
|
|
||||||
REFRESH_10 = 2,
|
|
||||||
REFRESH_15 = 3,
|
|
||||||
REFRESH_30 = 4
|
|
||||||
};
|
|
||||||
|
|
||||||
// Short power button press actions
|
// Short power button press actions
|
||||||
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2 };
|
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2 };
|
||||||
|
|
||||||
// Hide battery percentage
|
// Hide battery percentage
|
||||||
enum HIDE_BATTERY_PERCENTAGE {
|
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
|
||||||
HIDE_NEVER = 0,
|
|
||||||
HIDE_READER = 1,
|
|
||||||
HIDE_ALWAYS = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sleep screen settings
|
// Sleep screen settings
|
||||||
uint8_t sleepScreen = DARK;
|
uint8_t sleepScreen = DARK;
|
||||||
@ -132,7 +100,7 @@ public:
|
|||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
// Get singleton instance
|
// Get singleton instance
|
||||||
static CrossPointSettings &getInstance() { return instance; }
|
static CrossPointSettings& getInstance() { return instance; }
|
||||||
|
|
||||||
uint16_t getPowerButtonDuration() const {
|
uint16_t getPowerButtonDuration() const {
|
||||||
return (shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP) ? 10 : 400;
|
return (shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP) ? 10 : 400;
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
#include <ThemeManager.h>
|
||||||
#include <Xtc.h>
|
#include <Xtc.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -17,17 +18,15 @@
|
|||||||
#include "ScreenComponents.h"
|
#include "ScreenComponents.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
#include "util/StringUtils.h"
|
#include "util/StringUtils.h"
|
||||||
#include <ThemeManager.h>
|
|
||||||
|
|
||||||
void HomeActivity::taskTrampoline(void *param) {
|
void HomeActivity::taskTrampoline(void* param) {
|
||||||
auto *self = static_cast<HomeActivity *>(param);
|
auto* self = static_cast<HomeActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
int HomeActivity::getMenuItemCount() const {
|
int HomeActivity::getMenuItemCount() const {
|
||||||
int count = 3; // Browse Files, File Transfer, Settings
|
int count = 3; // Browse Files, File Transfer, Settings
|
||||||
if (hasOpdsUrl)
|
if (hasOpdsUrl) count++; // + Calibre Library
|
||||||
count++; // + Calibre Library
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,8 +42,7 @@ void HomeActivity::onEnter() {
|
|||||||
selectorIndex = 0; // Start at first item (first book if any, else first menu)
|
selectorIndex = 0; // Start at first item (first book if any, else first menu)
|
||||||
|
|
||||||
// Check if we have a book to continue reading
|
// Check if we have a book to continue reading
|
||||||
hasContinueReading = !APP_STATE.openEpubPath.empty() &&
|
hasContinueReading = !APP_STATE.openEpubPath.empty() && SdMan.exists(APP_STATE.openEpubPath.c_str());
|
||||||
SdMan.exists(APP_STATE.openEpubPath.c_str());
|
|
||||||
|
|
||||||
// Check if OPDS browser URL is configured
|
// Check if OPDS browser URL is configured
|
||||||
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
|
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
|
||||||
@ -96,7 +94,7 @@ void HomeActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
lastBatteryCheck = 0; // Force update on first render
|
lastBatteryCheck = 0; // Force update on first render
|
||||||
coverRendered = false;
|
coverRendered = false;
|
||||||
coverBufferStored = false;
|
coverBufferStored = false;
|
||||||
|
|
||||||
@ -107,25 +105,25 @@ void HomeActivity::onEnter() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask",
|
xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask",
|
||||||
4096, // Stack size (increased for cover image rendering)
|
4096, // Stack size (increased for cover image rendering)
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
&displayTaskHandle // Task handle
|
&displayTaskHandle // Task handle
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeActivity::loadRecentBooksData() {
|
void HomeActivity::loadRecentBooksData() {
|
||||||
cachedRecentBooks.clear();
|
cachedRecentBooks.clear();
|
||||||
|
|
||||||
const auto &recentBooks = RECENT_BOOKS.getBooks();
|
const auto& recentBooks = RECENT_BOOKS.getBooks();
|
||||||
const int maxRecentBooks = 3;
|
const int maxRecentBooks = 3;
|
||||||
int recentCount = std::min(static_cast<int>(recentBooks.size()), maxRecentBooks);
|
int recentCount = std::min(static_cast<int>(recentBooks.size()), maxRecentBooks);
|
||||||
|
|
||||||
for (int i = 0; i < recentCount; i++) {
|
for (int i = 0; i < recentCount; i++) {
|
||||||
const std::string &bookPath = recentBooks[i];
|
const std::string& bookPath = recentBooks[i];
|
||||||
CachedBookInfo info;
|
CachedBookInfo info;
|
||||||
info.path = bookPath; // Store the full path
|
info.path = bookPath; // Store the full path
|
||||||
|
|
||||||
// Extract title from path
|
// Extract title from path
|
||||||
info.title = bookPath;
|
info.title = bookPath;
|
||||||
size_t lastSlash = info.title.find_last_of('/');
|
size_t lastSlash = info.title.find_last_of('/');
|
||||||
@ -136,7 +134,7 @@ void HomeActivity::loadRecentBooksData() {
|
|||||||
if (lastDot != std::string::npos) {
|
if (lastDot != std::string::npos) {
|
||||||
info.title = info.title.substr(0, lastDot);
|
info.title = info.title.substr(0, lastDot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils::checkFileExtension(bookPath, ".epub")) {
|
if (StringUtils::checkFileExtension(bookPath, ".epub")) {
|
||||||
Epub epub(bookPath, "/.crosspoint");
|
Epub epub(bookPath, "/.crosspoint");
|
||||||
epub.load(false);
|
epub.load(false);
|
||||||
@ -146,7 +144,7 @@ void HomeActivity::loadRecentBooksData() {
|
|||||||
if (epub.generateThumbBmp()) {
|
if (epub.generateThumbBmp()) {
|
||||||
info.coverPath = epub.getThumbBmpPath();
|
info.coverPath = epub.getThumbBmpPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read progress
|
// Read progress
|
||||||
FsFile f;
|
FsFile f;
|
||||||
if (SdMan.openFileForRead("HOME", epub.getCachePath() + "/progress.bin", f)) {
|
if (SdMan.openFileForRead("HOME", epub.getCachePath() + "/progress.bin", f)) {
|
||||||
@ -170,7 +168,7 @@ void HomeActivity::loadRecentBooksData() {
|
|||||||
if (xtc.generateThumbBmp()) {
|
if (xtc.generateThumbBmp()) {
|
||||||
info.coverPath = xtc.getThumbBmpPath();
|
info.coverPath = xtc.getThumbBmpPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read progress
|
// Read progress
|
||||||
FsFile f;
|
FsFile f;
|
||||||
if (SdMan.openFileForRead("HOME", xtc.getCachePath() + "/progress.bin", f)) {
|
if (SdMan.openFileForRead("HOME", xtc.getCachePath() + "/progress.bin", f)) {
|
||||||
@ -186,12 +184,12 @@ void HomeActivity::loadRecentBooksData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[HOME] Book %d: title='%s', cover='%s', progress=%d%%\n",
|
Serial.printf("[HOME] Book %d: title='%s', cover='%s', progress=%d%%\n", i, info.title.c_str(),
|
||||||
i, info.title.c_str(), info.coverPath.c_str(), info.progressPercent);
|
info.coverPath.c_str(), info.progressPercent);
|
||||||
cachedRecentBooks.push_back(info);
|
cachedRecentBooks.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[HOME] Loaded %d recent books\n", (int)cachedRecentBooks.size());
|
Serial.printf("[HOME] Loaded %d recent books\n", (int)cachedRecentBooks.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +211,7 @@ void HomeActivity::onExit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool HomeActivity::storeCoverBuffer() {
|
bool HomeActivity::storeCoverBuffer() {
|
||||||
uint8_t *frameBuffer = renderer.getFrameBuffer();
|
uint8_t* frameBuffer = renderer.getFrameBuffer();
|
||||||
if (!frameBuffer) {
|
if (!frameBuffer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -222,7 +220,7 @@ bool HomeActivity::storeCoverBuffer() {
|
|||||||
freeCoverBuffer();
|
freeCoverBuffer();
|
||||||
|
|
||||||
const size_t bufferSize = GfxRenderer::getBufferSize();
|
const size_t bufferSize = GfxRenderer::getBufferSize();
|
||||||
coverBuffer = static_cast<uint8_t *>(malloc(bufferSize));
|
coverBuffer = static_cast<uint8_t*>(malloc(bufferSize));
|
||||||
if (!coverBuffer) {
|
if (!coverBuffer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -236,7 +234,7 @@ bool HomeActivity::restoreCoverBuffer() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *frameBuffer = renderer.getFrameBuffer();
|
uint8_t* frameBuffer = renderer.getFrameBuffer();
|
||||||
if (!frameBuffer) {
|
if (!frameBuffer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -255,12 +253,10 @@ void HomeActivity::freeCoverBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HomeActivity::loop() {
|
void HomeActivity::loop() {
|
||||||
const bool prevPressed =
|
const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
mappedInput.wasPressed(MappedInputManager::Button::Left);
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left);
|
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
const bool nextPressed =
|
mappedInput.wasPressed(MappedInputManager::Button::Right);
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right);
|
|
||||||
const bool confirmPressed = mappedInput.wasReleased(MappedInputManager::Button::Confirm);
|
const bool confirmPressed = mappedInput.wasReleased(MappedInputManager::Button::Confirm);
|
||||||
|
|
||||||
// Navigation uses theme-configured book slots (limited by actual books available)
|
// Navigation uses theme-configured book slots (limited by actual books available)
|
||||||
@ -321,8 +317,7 @@ void HomeActivity::displayTaskLoop() {
|
|||||||
void HomeActivity::render() {
|
void HomeActivity::render() {
|
||||||
// Battery check logic (only update every 60 seconds)
|
// Battery check logic (only update every 60 seconds)
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
const bool needBatteryUpdate =
|
const bool needBatteryUpdate = (now - lastBatteryCheck > 60000) || (lastBatteryCheck == 0);
|
||||||
(now - lastBatteryCheck > 60000) || (lastBatteryCheck == 0);
|
|
||||||
if (needBatteryUpdate) {
|
if (needBatteryUpdate) {
|
||||||
cachedBatteryLevel = battery.readPercentage();
|
cachedBatteryLevel = battery.readPercentage();
|
||||||
lastBatteryCheck = now;
|
lastBatteryCheck = now;
|
||||||
@ -336,8 +331,7 @@ void HomeActivity::render() {
|
|||||||
// --- Bind Global Data ---
|
// --- Bind Global Data ---
|
||||||
context.setString("BatteryPercent", std::to_string(cachedBatteryLevel));
|
context.setString("BatteryPercent", std::to_string(cachedBatteryLevel));
|
||||||
context.setBool("ShowBatteryPercent",
|
context.setBool("ShowBatteryPercent",
|
||||||
SETTINGS.hideBatteryPercentage !=
|
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS);
|
||||||
CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS);
|
|
||||||
|
|
||||||
// --- Navigation counts (must match loop()) ---
|
// --- Navigation counts (must match loop()) ---
|
||||||
const int recentCount = static_cast<int>(cachedRecentBooks.size());
|
const int recentCount = static_cast<int>(cachedRecentBooks.size());
|
||||||
@ -351,9 +345,9 @@ void HomeActivity::render() {
|
|||||||
context.setInt("SelectedBookIndex", isBookSelected ? selectorIndex : -1);
|
context.setInt("SelectedBookIndex", isBookSelected ? selectorIndex : -1);
|
||||||
|
|
||||||
for (int i = 0; i < recentCount; i++) {
|
for (int i = 0; i < recentCount; i++) {
|
||||||
const auto &book = cachedRecentBooks[i];
|
const auto& book = cachedRecentBooks[i];
|
||||||
std::string prefix = "RecentBooks." + std::to_string(i) + ".";
|
std::string prefix = "RecentBooks." + std::to_string(i) + ".";
|
||||||
|
|
||||||
context.setString(prefix + "Title", book.title);
|
context.setString(prefix + "Title", book.title);
|
||||||
context.setString(prefix + "Image", book.coverPath);
|
context.setString(prefix + "Image", book.coverPath);
|
||||||
context.setString(prefix + "Progress", std::to_string(book.progressPercent));
|
context.setString(prefix + "Progress", std::to_string(book.progressPercent));
|
||||||
@ -367,14 +361,13 @@ void HomeActivity::render() {
|
|||||||
context.setString("BookTitle", lastBookTitle);
|
context.setString("BookTitle", lastBookTitle);
|
||||||
context.setString("BookAuthor", lastBookAuthor);
|
context.setString("BookAuthor", lastBookAuthor);
|
||||||
context.setString("BookCoverPath", coverBmpPath);
|
context.setString("BookCoverPath", coverBmpPath);
|
||||||
context.setBool("HasCover",
|
context.setBool("HasCover", hasContinueReading && hasCoverImage && !coverBmpPath.empty());
|
||||||
hasContinueReading && hasCoverImage && !coverBmpPath.empty());
|
|
||||||
context.setBool("ShowInfoBox", true);
|
context.setBool("ShowInfoBox", true);
|
||||||
|
|
||||||
// --- Main Menu Data ---
|
// --- Main Menu Data ---
|
||||||
// Menu items start after the book slot
|
// Menu items start after the book slot
|
||||||
const int menuStartIdx = navBookCount;
|
const int menuStartIdx = navBookCount;
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
const int myLibraryIdx = menuStartIdx + idx++;
|
const int myLibraryIdx = menuStartIdx + idx++;
|
||||||
const int opdsLibraryIdx = hasOpdsUrl ? menuStartIdx + idx++ : -1;
|
const int opdsLibraryIdx = hasOpdsUrl ? menuStartIdx + idx++ : -1;
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
// Cached data for a recent book
|
// Cached data for a recent book
|
||||||
struct CachedBookInfo {
|
struct CachedBookInfo {
|
||||||
std::string path; // Full path to the book file
|
std::string path; // Full path to the book file
|
||||||
std::string title;
|
std::string title;
|
||||||
std::string coverPath;
|
std::string coverPath;
|
||||||
int progressPercent = 0;
|
int progressPercent = 0;
|
||||||
@ -25,15 +25,15 @@ class HomeActivity final : public Activity {
|
|||||||
bool hasContinueReading = false;
|
bool hasContinueReading = false;
|
||||||
bool hasOpdsUrl = false;
|
bool hasOpdsUrl = false;
|
||||||
bool hasCoverImage = false;
|
bool hasCoverImage = false;
|
||||||
bool coverRendered = false; // Track if cover has been rendered once
|
bool coverRendered = false; // Track if cover has been rendered once
|
||||||
bool coverBufferStored = false; // Track if cover buffer is stored
|
bool coverBufferStored = false; // Track if cover buffer is stored
|
||||||
uint8_t *coverBuffer = nullptr; // HomeActivity's own buffer for cover image
|
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image
|
||||||
std::string lastBookTitle;
|
std::string lastBookTitle;
|
||||||
std::string lastBookAuthor;
|
std::string lastBookAuthor;
|
||||||
std::string coverBmpPath;
|
std::string coverBmpPath;
|
||||||
uint8_t cachedBatteryLevel = 0;
|
uint8_t cachedBatteryLevel = 0;
|
||||||
uint32_t lastBatteryCheck = 0;
|
uint32_t lastBatteryCheck = 0;
|
||||||
|
|
||||||
// Cached recent books data (loaded once in onEnter)
|
// Cached recent books data (loaded once in onEnter)
|
||||||
std::vector<CachedBookInfo> cachedRecentBooks;
|
std::vector<CachedBookInfo> cachedRecentBooks;
|
||||||
const std::function<void()> onContinueReading;
|
const std::function<void()> onContinueReading;
|
||||||
@ -42,25 +42,25 @@ class HomeActivity final : public Activity {
|
|||||||
const std::function<void()> onFileTransferOpen;
|
const std::function<void()> onFileTransferOpen;
|
||||||
const std::function<void()> onOpdsBrowserOpen;
|
const std::function<void()> onOpdsBrowserOpen;
|
||||||
|
|
||||||
static void taskTrampoline(void *param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
void render();
|
void render();
|
||||||
int getMenuItemCount() const;
|
int getMenuItemCount() const;
|
||||||
bool storeCoverBuffer(); // Store frame buffer for cover image
|
bool storeCoverBuffer(); // Store frame buffer for cover image
|
||||||
bool restoreCoverBuffer(); // Restore frame buffer from stored cover
|
bool restoreCoverBuffer(); // Restore frame buffer from stored cover
|
||||||
void freeCoverBuffer(); // Free the stored cover buffer
|
void freeCoverBuffer(); // Free the stored cover buffer
|
||||||
void loadRecentBooksData(); // Load and cache recent books data
|
void loadRecentBooksData(); // Load and cache recent books data
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HomeActivity(GfxRenderer &renderer, MappedInputManager &mappedInput,
|
explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::function<void()> &onContinueReading,
|
const std::function<void()>& onContinueReading, const std::function<void()>& onMyLibraryOpen,
|
||||||
const std::function<void()> &onMyLibraryOpen,
|
const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen,
|
||||||
const std::function<void()> &onSettingsOpen,
|
const std::function<void()>& onOpdsBrowserOpen)
|
||||||
const std::function<void()> &onFileTransferOpen,
|
|
||||||
const std::function<void()> &onOpdsBrowserOpen)
|
|
||||||
: Activity("Home", renderer, mappedInput),
|
: Activity("Home", renderer, mappedInput),
|
||||||
onContinueReading(onContinueReading), onMyLibraryOpen(onMyLibraryOpen),
|
onContinueReading(onContinueReading),
|
||||||
onSettingsOpen(onSettingsOpen), onFileTransferOpen(onFileTransferOpen),
|
onMyLibraryOpen(onMyLibraryOpen),
|
||||||
|
onSettingsOpen(onSettingsOpen),
|
||||||
|
onFileTransferOpen(onFileTransferOpen),
|
||||||
onOpdsBrowserOpen(onOpdsBrowserOpen) {}
|
onOpdsBrowserOpen(onOpdsBrowserOpen) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
|
|||||||
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
void CategorySettingsActivity::taskTrampoline(void *param) {
|
void CategorySettingsActivity::taskTrampoline(void* param) {
|
||||||
auto *self = static_cast<CategorySettingsActivity *>(param);
|
auto* self = static_cast<CategorySettingsActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,8 +30,7 @@ void CategorySettingsActivity::onEnter() {
|
|||||||
selectedSettingIndex = 0;
|
selectedSettingIndex = 0;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&CategorySettingsActivity::taskTrampoline,
|
xTaskCreate(&CategorySettingsActivity::taskTrampoline, "CategorySettingsActivityTask", 4096, this, 1,
|
||||||
"CategorySettingsActivityTask", 4096, this, 1,
|
|
||||||
&displayTaskHandle);
|
&displayTaskHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,15 +70,11 @@ void CategorySettingsActivity::loop() {
|
|||||||
// Handle navigation
|
// Handle navigation
|
||||||
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 = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1);
|
||||||
? (selectedSettingIndex - 1)
|
|
||||||
: (settingsCount - 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 = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0;
|
||||||
? (selectedSettingIndex + 1)
|
|
||||||
: 0;
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,7 +84,7 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &setting = settingsList[selectedSettingIndex];
|
const auto& setting = settingsList[selectedSettingIndex];
|
||||||
|
|
||||||
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
||||||
// Toggle the boolean value using the member pointer
|
// Toggle the boolean value using the member pointer
|
||||||
@ -97,10 +92,8 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
SETTINGS.*(setting.valuePtr) = !currentValue;
|
SETTINGS.*(setting.valuePtr) = !currentValue;
|
||||||
} 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) =
|
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
||||||
(currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
} 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) {
|
||||||
SETTINGS.*(setting.valuePtr) = setting.valueRange.min;
|
SETTINGS.*(setting.valuePtr) = setting.valueRange.min;
|
||||||
@ -111,20 +104,18 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
if (strcmp(setting.name, "KOReader Sync") == 0) {
|
if (strcmp(setting.name, "KOReader Sync") == 0) {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(
|
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
||||||
new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
exitActivity();
|
||||||
exitActivity();
|
updateRequired = true;
|
||||||
updateRequired = true;
|
}));
|
||||||
}));
|
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
} else if (strcmp(setting.name, "Calibre Settings") == 0) {
|
} else if (strcmp(setting.name, "Calibre Settings") == 0) {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(
|
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
||||||
new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
exitActivity();
|
||||||
exitActivity();
|
updateRequired = true;
|
||||||
updateRequired = true;
|
}));
|
||||||
}));
|
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
} else if (strcmp(setting.name, "Clear Cache") == 0) {
|
} else if (strcmp(setting.name, "Clear Cache") == 0) {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
@ -145,11 +136,10 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
} else if (strcmp(setting.name, "Theme") == 0) {
|
} else if (strcmp(setting.name, "Theme") == 0) {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(
|
enterNewActivity(new ThemeSelectionActivity(renderer, mappedInput, [this] {
|
||||||
new ThemeSelectionActivity(renderer, mappedInput, [this] {
|
exitActivity();
|
||||||
exitActivity();
|
updateRequired = true;
|
||||||
updateRequired = true;
|
}));
|
||||||
}));
|
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -177,51 +167,41 @@ void CategorySettingsActivity::render() const {
|
|||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true,
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD);
|
||||||
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 settings
|
||||||
for (int i = 0; i < settingsCount; i++) {
|
for (int i = 0; i < settingsCount; i++) {
|
||||||
const int settingY = 60 + i * 30; // 30 pixels between settings
|
const int settingY = 60 + i * 30; // 30 pixels between settings
|
||||||
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,
|
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected);
|
||||||
!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 &&
|
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||||
settingsList[i].valuePtr != nullptr) {
|
|
||||||
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||||
valueText = value ? "ON" : "OFF";
|
valueText = value ? "ON" : "OFF";
|
||||||
} else if (settingsList[i].type == SettingType::ENUM &&
|
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
||||||
settingsList[i].valuePtr != nullptr) {
|
|
||||||
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
||||||
valueText = settingsList[i].enumValues[value];
|
valueText = settingsList[i].enumValues[value];
|
||||||
} else if (settingsList[i].type == SettingType::VALUE &&
|
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||||
settingsList[i].valuePtr != nullptr) {
|
|
||||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||||
}
|
}
|
||||||
if (!valueText.empty()) {
|
if (!valueText.empty()) {
|
||||||
const auto width =
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||||
renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected);
|
||||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY,
|
|
||||||
valueText.c_str(), !isSelected);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawText(
|
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
||||||
SMALL_FONT_ID,
|
pageHeight - 60, CROSSPOINT_VERSION);
|
||||||
pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
|
||||||
pageHeight - 60, CROSSPOINT_VERSION);
|
|
||||||
|
|
||||||
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,
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
labels.btn4);
|
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,76 +8,53 @@
|
|||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
const char *SettingsActivity::categoryNames[categoryCount] = {
|
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
||||||
"Display", "Reader", "Controls", "System"};
|
|
||||||
|
|
||||||
namespace {
|
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,
|
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
||||||
{"Dark", "Light", "Custom", "Cover", "None"}),
|
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
||||||
SettingInfo::Enum("Sleep Screen Cover Mode",
|
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
||||||
&CrossPointSettings::sleepScreenCoverMode,
|
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
||||||
{"Fit", "Crop"}),
|
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||||
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar,
|
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||||
{"None", "No Progress", "Full"}),
|
|
||||||
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"}),
|
|
||||||
SettingInfo::Action("Theme")};
|
SettingInfo::Action("Theme")};
|
||||||
|
|
||||||
constexpr int readerSettingsCount = 9;
|
constexpr int readerSettingsCount = 9;
|
||||||
const SettingInfo readerSettings[readerSettingsCount] = {
|
const SettingInfo readerSettings[readerSettingsCount] = {
|
||||||
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily,
|
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
||||||
{"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
|
||||||
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize,
|
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
|
||||||
{"Small", "Medium", "Large", "X Large"}),
|
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
|
||||||
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing,
|
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
||||||
{"Tight", "Normal", "Wide"}),
|
|
||||||
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin,
|
|
||||||
{5, 40, 5}),
|
|
||||||
SettingInfo::Enum("Paragraph Alignment",
|
|
||||||
&CrossPointSettings::paragraphAlignment,
|
|
||||||
{"Justify", "Left", "Center", "Right"}),
|
{"Justify", "Left", "Center", "Right"}),
|
||||||
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
|
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
|
||||||
SettingInfo::Enum(
|
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
|
||||||
"Reading Orientation", &CrossPointSettings::orientation,
|
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
|
||||||
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
|
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
|
||||||
SettingInfo::Toggle("Extra Paragraph Spacing",
|
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)};
|
||||||
&CrossPointSettings::extraParagraphSpacing),
|
|
||||||
SettingInfo::Toggle("Text Anti-Aliasing",
|
|
||||||
&CrossPointSettings::textAntiAliasing)};
|
|
||||||
|
|
||||||
constexpr int controlsSettingsCount = 4;
|
constexpr int controlsSettingsCount = 4;
|
||||||
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
||||||
SettingInfo::Enum("Front Button Layout",
|
SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout,
|
||||||
&CrossPointSettings::frontButtonLayout,
|
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}),
|
||||||
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm",
|
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
|
||||||
"Lft, Bck, Cnfrm, Rght"}),
|
|
||||||
SettingInfo::Enum("Side Button Layout (reader)",
|
|
||||||
&CrossPointSettings::sideButtonLayout,
|
|
||||||
{"Prev, Next", "Next, Prev"}),
|
{"Prev, Next", "Next, Prev"}),
|
||||||
SettingInfo::Toggle("Long-press Chapter Skip",
|
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
||||||
&CrossPointSettings::longPressChapterSkip),
|
SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})};
|
||||||
SettingInfo::Enum("Short Power Button Click",
|
|
||||||
&CrossPointSettings::shortPwrBtn,
|
|
||||||
{"Ignore", "Sleep", "Page Turn"})};
|
|
||||||
|
|
||||||
constexpr int systemSettingsCount = 5;
|
constexpr int systemSettingsCount = 5;
|
||||||
const SettingInfo systemSettings[systemSettingsCount] = {
|
const SettingInfo systemSettings[systemSettingsCount] = {
|
||||||
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
||||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||||
SettingInfo::Action("KOReader Sync"),
|
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
||||||
SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
|
||||||
SettingInfo::Action("Check for updates")};
|
SettingInfo::Action("Check for updates")};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void *param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
auto *self = static_cast<SettingsActivity *>(param);
|
auto* self = static_cast<SettingsActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,10 +69,10 @@ void SettingsActivity::onEnter() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask",
|
xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask",
|
||||||
4096, // Stack size
|
4096, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
&displayTaskHandle // Task handle
|
&displayTaskHandle // Task handle
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,16 +112,12 @@ void SettingsActivity::loop() {
|
|||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
// Move selection up (with wrap-around)
|
// Move selection up (with wrap-around)
|
||||||
selectedCategoryIndex = (selectedCategoryIndex > 0)
|
selectedCategoryIndex = (selectedCategoryIndex > 0) ? (selectedCategoryIndex - 1) : (categoryCount - 1);
|
||||||
? (selectedCategoryIndex - 1)
|
|
||||||
: (categoryCount - 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)) {
|
||||||
// Move selection down (with wrap around)
|
// Move selection down (with wrap around)
|
||||||
selectedCategoryIndex = (selectedCategoryIndex < categoryCount - 1)
|
selectedCategoryIndex = (selectedCategoryIndex < categoryCount - 1) ? (selectedCategoryIndex + 1) : 0;
|
||||||
? (selectedCategoryIndex + 1)
|
|
||||||
: 0;
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,34 +130,33 @@ void SettingsActivity::enterCategory(int categoryIndex) {
|
|||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
|
|
||||||
const SettingInfo *settingsList = nullptr;
|
const SettingInfo* settingsList = nullptr;
|
||||||
int settingsCount = 0;
|
int settingsCount = 0;
|
||||||
|
|
||||||
switch (categoryIndex) {
|
switch (categoryIndex) {
|
||||||
case 0: // Display
|
case 0: // Display
|
||||||
settingsList = displaySettings;
|
settingsList = displaySettings;
|
||||||
settingsCount = displaySettingsCount;
|
settingsCount = displaySettingsCount;
|
||||||
break;
|
break;
|
||||||
case 1: // Reader
|
case 1: // Reader
|
||||||
settingsList = readerSettings;
|
settingsList = readerSettings;
|
||||||
settingsCount = readerSettingsCount;
|
settingsCount = readerSettingsCount;
|
||||||
break;
|
break;
|
||||||
case 2: // Controls
|
case 2: // Controls
|
||||||
settingsList = controlsSettings;
|
settingsList = controlsSettings;
|
||||||
settingsCount = controlsSettingsCount;
|
settingsCount = controlsSettingsCount;
|
||||||
break;
|
break;
|
||||||
case 3: // System
|
case 3: // System
|
||||||
settingsList = systemSettings;
|
settingsList = systemSettings;
|
||||||
settingsCount = systemSettingsCount;
|
settingsCount = systemSettingsCount;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
enterNewActivity(new CategorySettingsActivity(
|
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList,
|
||||||
renderer, mappedInput, categoryNames[categoryIndex], settingsList,
|
settingsCount, [this] {
|
||||||
settingsCount, [this] {
|
exitActivity();
|
||||||
exitActivity();
|
updateRequired = true;
|
||||||
updateRequired = true;
|
}));
|
||||||
}));
|
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,31 +179,26 @@ void SettingsActivity::render() const {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true,
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
|
||||||
EpdFontFamily::BOLD);
|
|
||||||
|
|
||||||
// Draw selection
|
// Draw selection
|
||||||
renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
// Draw all categories
|
// Draw all categories
|
||||||
for (int i = 0; i < categoryCount; i++) {
|
for (int i = 0; i < categoryCount; i++) {
|
||||||
const int categoryY = 60 + i * 30; // 30 pixels between categories
|
const int categoryY = 60 + i * 30; // 30 pixels between categories
|
||||||
|
|
||||||
// Draw category name
|
// Draw category name
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i],
|
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i], i != selectedCategoryIndex);
|
||||||
i != selectedCategoryIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw version text above button hints
|
// Draw version text above button hints
|
||||||
renderer.drawText(
|
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
||||||
SMALL_FONT_ID,
|
pageHeight - 60, CROSSPOINT_VERSION);
|
||||||
pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
|
||||||
pageHeight - 60, CROSSPOINT_VERSION);
|
|
||||||
|
|
||||||
// Draw help text
|
// Draw help text
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
labels.btn4);
|
|
||||||
|
|
||||||
// Always use standard refresh for settings screen
|
// Always use standard refresh for settings screen
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
#include "ThemeSelectionActivity.h"
|
#include "ThemeSelectionActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <SDCardManager.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
#include <GfxRenderer.h>
|
|
||||||
#include <SDCardManager.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <esp_system.h>
|
|
||||||
|
|
||||||
void ThemeSelectionActivity::taskTrampoline(void *param) {
|
void ThemeSelectionActivity::taskTrampoline(void* param) {
|
||||||
auto *self = static_cast<ThemeSelectionActivity *>(param);
|
auto* self = static_cast<ThemeSelectionActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +52,7 @@ void ThemeSelectionActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
xTaskCreate(&ThemeSelectionActivity::taskTrampoline, "ThemeSelTask", 4096,
|
xTaskCreate(&ThemeSelectionActivity::taskTrampoline, "ThemeSelTask", 4096, this, 1, &displayTaskHandle);
|
||||||
this, 1, &displayTaskHandle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeSelectionActivity::onExit() {
|
void ThemeSelectionActivity::onExit() {
|
||||||
@ -72,17 +74,14 @@ void ThemeSelectionActivity::loop() {
|
|||||||
|
|
||||||
// Only reboot if theme actually changed
|
// Only reboot if theme actually changed
|
||||||
if (selected != std::string(SETTINGS.themeName)) {
|
if (selected != std::string(SETTINGS.themeName)) {
|
||||||
strncpy(SETTINGS.themeName, selected.c_str(),
|
strncpy(SETTINGS.themeName, selected.c_str(), sizeof(SETTINGS.themeName) - 1);
|
||||||
sizeof(SETTINGS.themeName) - 1);
|
|
||||||
SETTINGS.themeName[sizeof(SETTINGS.themeName) - 1] = '\0';
|
SETTINGS.themeName[sizeof(SETTINGS.themeName) - 1] = '\0';
|
||||||
SETTINGS.saveToFile();
|
SETTINGS.saveToFile();
|
||||||
|
|
||||||
// Show reboot message
|
// Show reboot message
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() / 2 - 20,
|
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() / 2 - 20, "Applying theme...", true);
|
||||||
"Applying theme...", true);
|
renderer.drawCenteredText(UI_10_FONT_ID, renderer.getScreenHeight() / 2 + 10, "Device will restart", true);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, renderer.getScreenHeight() / 2 + 10,
|
|
||||||
"Device will restart", true);
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|
||||||
// Small delay to ensure display updates
|
// Small delay to ensure display updates
|
||||||
@ -103,13 +102,11 @@ void ThemeSelectionActivity::loop() {
|
|||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
selectedIndex =
|
selectedIndex = (selectedIndex > 0) ? (selectedIndex - 1) : (themeNames.size() - 1);
|
||||||
(selectedIndex > 0) ? (selectedIndex - 1) : (themeNames.size() - 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)) {
|
||||||
selectedIndex =
|
selectedIndex = (selectedIndex < themeNames.size() - 1) ? (selectedIndex + 1) : 0;
|
||||||
(selectedIndex < themeNames.size() - 1) ? (selectedIndex + 1) : 0;
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,8 +130,7 @@ void ThemeSelectionActivity::render() const {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Theme", true,
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Theme", true, EpdFontFamily::BOLD);
|
||||||
EpdFontFamily::BOLD);
|
|
||||||
|
|
||||||
// Layout constants
|
// Layout constants
|
||||||
const int entryHeight = 30;
|
const int entryHeight = 30;
|
||||||
@ -155,8 +151,7 @@ void ThemeSelectionActivity::render() const {
|
|||||||
// Draw Highlight
|
// Draw Highlight
|
||||||
int visibleIndex = selectedIndex - startIdx;
|
int visibleIndex = selectedIndex - startIdx;
|
||||||
if (visibleIndex >= 0 && visibleIndex < maxVisible) {
|
if (visibleIndex >= 0 && visibleIndex < maxVisible) {
|
||||||
renderer.fillRect(0, startY + visibleIndex * entryHeight - 2, pageWidth - 1,
|
renderer.fillRect(0, startY + visibleIndex * entryHeight - 2, pageWidth - 1, entryHeight);
|
||||||
entryHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw List
|
// Draw List
|
||||||
@ -176,15 +171,13 @@ void ThemeSelectionActivity::render() const {
|
|||||||
if (themeNames.size() > maxVisible) {
|
if (themeNames.size() > maxVisible) {
|
||||||
int barHeight = pageHeight - startY - 40;
|
int barHeight = pageHeight - startY - 40;
|
||||||
int thumbHeight = barHeight * maxVisible / themeNames.size();
|
int thumbHeight = barHeight * maxVisible / themeNames.size();
|
||||||
int thumbY = startY + (barHeight - thumbHeight) * startIdx /
|
int thumbY = startY + (barHeight - thumbHeight) * startIdx / (themeNames.size() - maxVisible);
|
||||||
(themeNames.size() - maxVisible);
|
|
||||||
renderer.fillRect(pageWidth - 5, startY, 2, barHeight, 0);
|
renderer.fillRect(pageWidth - 5, startY, 2, barHeight, 0);
|
||||||
renderer.fillRect(pageWidth - 7, thumbY, 6, thumbHeight, 1);
|
renderer.fillRect(pageWidth - 7, thumbY, 6, thumbHeight, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("Cancel", "Select", "", "");
|
const auto labels = mappedInput.mapLabels("Cancel", "Select", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
labels.btn4);
|
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "activities/Activity.h"
|
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "activities/Activity.h"
|
||||||
|
|
||||||
class ThemeSelectionActivity final : public Activity {
|
class ThemeSelectionActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
@ -15,13 +17,12 @@ class ThemeSelectionActivity final : public Activity {
|
|||||||
std::vector<std::string> themeNames;
|
std::vector<std::string> themeNames;
|
||||||
const std::function<void()> onGoBack;
|
const std::function<void()> onGoBack;
|
||||||
|
|
||||||
static void taskTrampoline(void *param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
void render() const;
|
void render() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ThemeSelectionActivity(GfxRenderer &renderer, MappedInputManager &mappedInput,
|
ThemeSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function<void()>& onGoBack)
|
||||||
const std::function<void()> &onGoBack)
|
|
||||||
: Activity("ThemeSelection", renderer, mappedInput), onGoBack(onGoBack) {}
|
: Activity("ThemeSelection", renderer, mappedInput), onGoBack(onGoBack) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
|
|||||||
157
src/main.cpp
157
src/main.cpp
@ -29,14 +29,14 @@
|
|||||||
|
|
||||||
#define SPI_FQ 40000000
|
#define SPI_FQ 40000000
|
||||||
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
||||||
#define EPD_SCLK 8 // SPI Clock
|
#define EPD_SCLK 8 // SPI Clock
|
||||||
#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In)
|
#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In)
|
||||||
#define EPD_CS 21 // Chip Select
|
#define EPD_CS 21 // Chip Select
|
||||||
#define EPD_DC 4 // Data/Command
|
#define EPD_DC 4 // Data/Command
|
||||||
#define EPD_RST 5 // Reset
|
#define EPD_RST 5 // Reset
|
||||||
#define EPD_BUSY 6 // Busy
|
#define EPD_BUSY 6 // Busy
|
||||||
|
|
||||||
#define UART0_RXD 20 // Used for USB connection detection
|
#define UART0_RXD 20 // Used for USB connection detection
|
||||||
|
|
||||||
#define SD_SPI_MISO 7
|
#define SD_SPI_MISO 7
|
||||||
|
|
||||||
@ -44,101 +44,85 @@ EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
|||||||
InputManager inputManager;
|
InputManager inputManager;
|
||||||
MappedInputManager mappedInputManager(inputManager);
|
MappedInputManager mappedInputManager(inputManager);
|
||||||
GfxRenderer renderer(einkDisplay);
|
GfxRenderer renderer(einkDisplay);
|
||||||
Activity *currentActivity;
|
Activity* currentActivity;
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
EpdFont bookerly14RegularFont(&bookerly_14_regular);
|
EpdFont bookerly14RegularFont(&bookerly_14_regular);
|
||||||
EpdFont bookerly14BoldFont(&bookerly_14_bold);
|
EpdFont bookerly14BoldFont(&bookerly_14_bold);
|
||||||
EpdFont bookerly14ItalicFont(&bookerly_14_italic);
|
EpdFont bookerly14ItalicFont(&bookerly_14_italic);
|
||||||
EpdFont bookerly14BoldItalicFont(&bookerly_14_bolditalic);
|
EpdFont bookerly14BoldItalicFont(&bookerly_14_bolditalic);
|
||||||
EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont,
|
EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont, &bookerly14ItalicFont,
|
||||||
&bookerly14ItalicFont,
|
|
||||||
&bookerly14BoldItalicFont);
|
&bookerly14BoldItalicFont);
|
||||||
#ifndef OMIT_FONTS
|
#ifndef OMIT_FONTS
|
||||||
EpdFont bookerly12RegularFont(&bookerly_12_regular);
|
EpdFont bookerly12RegularFont(&bookerly_12_regular);
|
||||||
EpdFont bookerly12BoldFont(&bookerly_12_bold);
|
EpdFont bookerly12BoldFont(&bookerly_12_bold);
|
||||||
EpdFont bookerly12ItalicFont(&bookerly_12_italic);
|
EpdFont bookerly12ItalicFont(&bookerly_12_italic);
|
||||||
EpdFont bookerly12BoldItalicFont(&bookerly_12_bolditalic);
|
EpdFont bookerly12BoldItalicFont(&bookerly_12_bolditalic);
|
||||||
EpdFontFamily bookerly12FontFamily(&bookerly12RegularFont, &bookerly12BoldFont,
|
EpdFontFamily bookerly12FontFamily(&bookerly12RegularFont, &bookerly12BoldFont, &bookerly12ItalicFont,
|
||||||
&bookerly12ItalicFont,
|
|
||||||
&bookerly12BoldItalicFont);
|
&bookerly12BoldItalicFont);
|
||||||
EpdFont bookerly16RegularFont(&bookerly_16_regular);
|
EpdFont bookerly16RegularFont(&bookerly_16_regular);
|
||||||
EpdFont bookerly16BoldFont(&bookerly_16_bold);
|
EpdFont bookerly16BoldFont(&bookerly_16_bold);
|
||||||
EpdFont bookerly16ItalicFont(&bookerly_16_italic);
|
EpdFont bookerly16ItalicFont(&bookerly_16_italic);
|
||||||
EpdFont bookerly16BoldItalicFont(&bookerly_16_bolditalic);
|
EpdFont bookerly16BoldItalicFont(&bookerly_16_bolditalic);
|
||||||
EpdFontFamily bookerly16FontFamily(&bookerly16RegularFont, &bookerly16BoldFont,
|
EpdFontFamily bookerly16FontFamily(&bookerly16RegularFont, &bookerly16BoldFont, &bookerly16ItalicFont,
|
||||||
&bookerly16ItalicFont,
|
|
||||||
&bookerly16BoldItalicFont);
|
&bookerly16BoldItalicFont);
|
||||||
EpdFont bookerly18RegularFont(&bookerly_18_regular);
|
EpdFont bookerly18RegularFont(&bookerly_18_regular);
|
||||||
EpdFont bookerly18BoldFont(&bookerly_18_bold);
|
EpdFont bookerly18BoldFont(&bookerly_18_bold);
|
||||||
EpdFont bookerly18ItalicFont(&bookerly_18_italic);
|
EpdFont bookerly18ItalicFont(&bookerly_18_italic);
|
||||||
EpdFont bookerly18BoldItalicFont(&bookerly_18_bolditalic);
|
EpdFont bookerly18BoldItalicFont(&bookerly_18_bolditalic);
|
||||||
EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont,
|
EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont, &bookerly18ItalicFont,
|
||||||
&bookerly18ItalicFont,
|
|
||||||
&bookerly18BoldItalicFont);
|
&bookerly18BoldItalicFont);
|
||||||
|
|
||||||
EpdFont notosans12RegularFont(¬osans_12_regular);
|
EpdFont notosans12RegularFont(¬osans_12_regular);
|
||||||
EpdFont notosans12BoldFont(¬osans_12_bold);
|
EpdFont notosans12BoldFont(¬osans_12_bold);
|
||||||
EpdFont notosans12ItalicFont(¬osans_12_italic);
|
EpdFont notosans12ItalicFont(¬osans_12_italic);
|
||||||
EpdFont notosans12BoldItalicFont(¬osans_12_bolditalic);
|
EpdFont notosans12BoldItalicFont(¬osans_12_bolditalic);
|
||||||
EpdFontFamily notosans12FontFamily(¬osans12RegularFont, ¬osans12BoldFont,
|
EpdFontFamily notosans12FontFamily(¬osans12RegularFont, ¬osans12BoldFont, ¬osans12ItalicFont,
|
||||||
¬osans12ItalicFont,
|
|
||||||
¬osans12BoldItalicFont);
|
¬osans12BoldItalicFont);
|
||||||
EpdFont notosans14RegularFont(¬osans_14_regular);
|
EpdFont notosans14RegularFont(¬osans_14_regular);
|
||||||
EpdFont notosans14BoldFont(¬osans_14_bold);
|
EpdFont notosans14BoldFont(¬osans_14_bold);
|
||||||
EpdFont notosans14ItalicFont(¬osans_14_italic);
|
EpdFont notosans14ItalicFont(¬osans_14_italic);
|
||||||
EpdFont notosans14BoldItalicFont(¬osans_14_bolditalic);
|
EpdFont notosans14BoldItalicFont(¬osans_14_bolditalic);
|
||||||
EpdFontFamily notosans14FontFamily(¬osans14RegularFont, ¬osans14BoldFont,
|
EpdFontFamily notosans14FontFamily(¬osans14RegularFont, ¬osans14BoldFont, ¬osans14ItalicFont,
|
||||||
¬osans14ItalicFont,
|
|
||||||
¬osans14BoldItalicFont);
|
¬osans14BoldItalicFont);
|
||||||
EpdFont notosans16RegularFont(¬osans_16_regular);
|
EpdFont notosans16RegularFont(¬osans_16_regular);
|
||||||
EpdFont notosans16BoldFont(¬osans_16_bold);
|
EpdFont notosans16BoldFont(¬osans_16_bold);
|
||||||
EpdFont notosans16ItalicFont(¬osans_16_italic);
|
EpdFont notosans16ItalicFont(¬osans_16_italic);
|
||||||
EpdFont notosans16BoldItalicFont(¬osans_16_bolditalic);
|
EpdFont notosans16BoldItalicFont(¬osans_16_bolditalic);
|
||||||
EpdFontFamily notosans16FontFamily(¬osans16RegularFont, ¬osans16BoldFont,
|
EpdFontFamily notosans16FontFamily(¬osans16RegularFont, ¬osans16BoldFont, ¬osans16ItalicFont,
|
||||||
¬osans16ItalicFont,
|
|
||||||
¬osans16BoldItalicFont);
|
¬osans16BoldItalicFont);
|
||||||
EpdFont notosans18RegularFont(¬osans_18_regular);
|
EpdFont notosans18RegularFont(¬osans_18_regular);
|
||||||
EpdFont notosans18BoldFont(¬osans_18_bold);
|
EpdFont notosans18BoldFont(¬osans_18_bold);
|
||||||
EpdFont notosans18ItalicFont(¬osans_18_italic);
|
EpdFont notosans18ItalicFont(¬osans_18_italic);
|
||||||
EpdFont notosans18BoldItalicFont(¬osans_18_bolditalic);
|
EpdFont notosans18BoldItalicFont(¬osans_18_bolditalic);
|
||||||
EpdFontFamily notosans18FontFamily(¬osans18RegularFont, ¬osans18BoldFont,
|
EpdFontFamily notosans18FontFamily(¬osans18RegularFont, ¬osans18BoldFont, ¬osans18ItalicFont,
|
||||||
¬osans18ItalicFont,
|
|
||||||
¬osans18BoldItalicFont);
|
¬osans18BoldItalicFont);
|
||||||
|
|
||||||
EpdFont opendyslexic8RegularFont(&opendyslexic_8_regular);
|
EpdFont opendyslexic8RegularFont(&opendyslexic_8_regular);
|
||||||
EpdFont opendyslexic8BoldFont(&opendyslexic_8_bold);
|
EpdFont opendyslexic8BoldFont(&opendyslexic_8_bold);
|
||||||
EpdFont opendyslexic8ItalicFont(&opendyslexic_8_italic);
|
EpdFont opendyslexic8ItalicFont(&opendyslexic_8_italic);
|
||||||
EpdFont opendyslexic8BoldItalicFont(&opendyslexic_8_bolditalic);
|
EpdFont opendyslexic8BoldItalicFont(&opendyslexic_8_bolditalic);
|
||||||
EpdFontFamily opendyslexic8FontFamily(&opendyslexic8RegularFont,
|
EpdFontFamily opendyslexic8FontFamily(&opendyslexic8RegularFont, &opendyslexic8BoldFont, &opendyslexic8ItalicFont,
|
||||||
&opendyslexic8BoldFont,
|
|
||||||
&opendyslexic8ItalicFont,
|
|
||||||
&opendyslexic8BoldItalicFont);
|
&opendyslexic8BoldItalicFont);
|
||||||
EpdFont opendyslexic10RegularFont(&opendyslexic_10_regular);
|
EpdFont opendyslexic10RegularFont(&opendyslexic_10_regular);
|
||||||
EpdFont opendyslexic10BoldFont(&opendyslexic_10_bold);
|
EpdFont opendyslexic10BoldFont(&opendyslexic_10_bold);
|
||||||
EpdFont opendyslexic10ItalicFont(&opendyslexic_10_italic);
|
EpdFont opendyslexic10ItalicFont(&opendyslexic_10_italic);
|
||||||
EpdFont opendyslexic10BoldItalicFont(&opendyslexic_10_bolditalic);
|
EpdFont opendyslexic10BoldItalicFont(&opendyslexic_10_bolditalic);
|
||||||
EpdFontFamily opendyslexic10FontFamily(&opendyslexic10RegularFont,
|
EpdFontFamily opendyslexic10FontFamily(&opendyslexic10RegularFont, &opendyslexic10BoldFont, &opendyslexic10ItalicFont,
|
||||||
&opendyslexic10BoldFont,
|
|
||||||
&opendyslexic10ItalicFont,
|
|
||||||
&opendyslexic10BoldItalicFont);
|
&opendyslexic10BoldItalicFont);
|
||||||
EpdFont opendyslexic12RegularFont(&opendyslexic_12_regular);
|
EpdFont opendyslexic12RegularFont(&opendyslexic_12_regular);
|
||||||
EpdFont opendyslexic12BoldFont(&opendyslexic_12_bold);
|
EpdFont opendyslexic12BoldFont(&opendyslexic_12_bold);
|
||||||
EpdFont opendyslexic12ItalicFont(&opendyslexic_12_italic);
|
EpdFont opendyslexic12ItalicFont(&opendyslexic_12_italic);
|
||||||
EpdFont opendyslexic12BoldItalicFont(&opendyslexic_12_bolditalic);
|
EpdFont opendyslexic12BoldItalicFont(&opendyslexic_12_bolditalic);
|
||||||
EpdFontFamily opendyslexic12FontFamily(&opendyslexic12RegularFont,
|
EpdFontFamily opendyslexic12FontFamily(&opendyslexic12RegularFont, &opendyslexic12BoldFont, &opendyslexic12ItalicFont,
|
||||||
&opendyslexic12BoldFont,
|
|
||||||
&opendyslexic12ItalicFont,
|
|
||||||
&opendyslexic12BoldItalicFont);
|
&opendyslexic12BoldItalicFont);
|
||||||
EpdFont opendyslexic14RegularFont(&opendyslexic_14_regular);
|
EpdFont opendyslexic14RegularFont(&opendyslexic_14_regular);
|
||||||
EpdFont opendyslexic14BoldFont(&opendyslexic_14_bold);
|
EpdFont opendyslexic14BoldFont(&opendyslexic_14_bold);
|
||||||
EpdFont opendyslexic14ItalicFont(&opendyslexic_14_italic);
|
EpdFont opendyslexic14ItalicFont(&opendyslexic_14_italic);
|
||||||
EpdFont opendyslexic14BoldItalicFont(&opendyslexic_14_bolditalic);
|
EpdFont opendyslexic14BoldItalicFont(&opendyslexic_14_bolditalic);
|
||||||
EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont,
|
EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont, &opendyslexic14BoldFont, &opendyslexic14ItalicFont,
|
||||||
&opendyslexic14BoldFont,
|
|
||||||
&opendyslexic14ItalicFont,
|
|
||||||
&opendyslexic14BoldItalicFont);
|
&opendyslexic14BoldItalicFont);
|
||||||
#endif // OMIT_FONTS
|
#endif // OMIT_FONTS
|
||||||
|
|
||||||
EpdFont smallFont(¬osans_8_regular);
|
EpdFont smallFont(¬osans_8_regular);
|
||||||
EpdFontFamily smallFontFamily(&smallFont);
|
EpdFontFamily smallFontFamily(&smallFont);
|
||||||
@ -163,7 +147,7 @@ void exitActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void enterNewActivity(Activity *activity) {
|
void enterNewActivity(Activity* activity) {
|
||||||
currentActivity = activity;
|
currentActivity = activity;
|
||||||
currentActivity->onEnter();
|
currentActivity->onEnter();
|
||||||
}
|
}
|
||||||
@ -180,16 +164,13 @@ void verifyWakeupLongPress() {
|
|||||||
// now from millis()==0 (i.e. device start time).
|
// now from millis()==0 (i.e. device start time).
|
||||||
const uint16_t calibration = start;
|
const uint16_t calibration = start;
|
||||||
const uint16_t calibratedPressDuration =
|
const uint16_t calibratedPressDuration =
|
||||||
(calibration < SETTINGS.getPowerButtonDuration())
|
(calibration < SETTINGS.getPowerButtonDuration()) ? SETTINGS.getPowerButtonDuration() - calibration : 1;
|
||||||
? SETTINGS.getPowerButtonDuration() - calibration
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
// Verify the user has actually pressed
|
// Verify the user has actually pressed
|
||||||
while (!inputManager.isPressed(InputManager::BTN_POWER) &&
|
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
|
||||||
millis() - start < 1000) {
|
delay(10); // only wait 10ms each iteration to not delay too much in case of
|
||||||
delay(10); // only wait 10ms each iteration to not delay too much in case of
|
// short configured duration.
|
||||||
// short configured duration.
|
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +179,7 @@ void verifyWakeupLongPress() {
|
|||||||
do {
|
do {
|
||||||
delay(10);
|
delay(10);
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
} while (inputManager.isPressed(InputManager::BTN_POWER) &&
|
} while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < calibratedPressDuration);
|
||||||
inputManager.getHeldTime() < calibratedPressDuration);
|
|
||||||
abort = inputManager.getHeldTime() < calibratedPressDuration;
|
abort = inputManager.getHeldTime() < calibratedPressDuration;
|
||||||
} else {
|
} else {
|
||||||
abort = true;
|
abort = true;
|
||||||
@ -208,8 +188,7 @@ void verifyWakeupLongPress() {
|
|||||||
if (abort) {
|
if (abort) {
|
||||||
// Button released too early. Returning to sleep.
|
// Button released too early. Returning to sleep.
|
||||||
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
||||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN,
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||||
ESP_GPIO_WAKEUP_GPIO_LOW);
|
|
||||||
esp_deep_sleep_start();
|
esp_deep_sleep_start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,11 +207,9 @@ void enterDeepSleep() {
|
|||||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||||
|
|
||||||
einkDisplay.deepSleep();
|
einkDisplay.deepSleep();
|
||||||
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n",
|
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
||||||
millis(), t2 - t1);
|
|
||||||
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
||||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN,
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||||
ESP_GPIO_WAKEUP_GPIO_LOW);
|
|
||||||
// Ensure that the power button has been released to avoid immediately turning
|
// Ensure that the power button has been released to avoid immediately turning
|
||||||
// back on if you're holding it
|
// back on if you're holding it
|
||||||
waitForPowerRelease();
|
waitForPowerRelease();
|
||||||
@ -241,55 +218,43 @@ void enterDeepSleep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome();
|
void onGoHome();
|
||||||
void onGoToMyLibraryWithTab(const std::string &path,
|
void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab);
|
||||||
MyLibraryActivity::Tab tab);
|
void onGoToReader(const std::string& initialEpubPath, MyLibraryActivity::Tab fromTab) {
|
||||||
void onGoToReader(const std::string &initialEpubPath,
|
|
||||||
MyLibraryActivity::Tab fromTab) {
|
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new ReaderActivity(renderer, mappedInputManager,
|
enterNewActivity(
|
||||||
initialEpubPath, fromTab, onGoHome,
|
new ReaderActivity(renderer, mappedInputManager, initialEpubPath, fromTab, onGoHome, onGoToMyLibraryWithTab));
|
||||||
onGoToMyLibraryWithTab));
|
|
||||||
}
|
|
||||||
void onContinueReading() {
|
|
||||||
onGoToReader(APP_STATE.openEpubPath, MyLibraryActivity::Tab::Recent);
|
|
||||||
}
|
}
|
||||||
|
void onContinueReading() { onGoToReader(APP_STATE.openEpubPath, MyLibraryActivity::Tab::Recent); }
|
||||||
|
|
||||||
void onGoToFileTransfer() {
|
void onGoToFileTransfer() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(
|
enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
|
||||||
new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoToSettings() {
|
void onGoToSettings() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(
|
enterNewActivity(new SettingsActivity(renderer, mappedInputManager, onGoHome));
|
||||||
new SettingsActivity(renderer, mappedInputManager, onGoHome));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoToMyLibrary() {
|
void onGoToMyLibrary() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome,
|
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader));
|
||||||
onGoToReader));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoToMyLibraryWithTab(const std::string &path,
|
void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab) {
|
||||||
MyLibraryActivity::Tab tab) {
|
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome,
|
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader, tab, path));
|
||||||
onGoToReader, tab, path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoToBrowser() {
|
void onGoToBrowser() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(
|
enterNewActivity(new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome));
|
||||||
new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome() {
|
void onGoHome() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new HomeActivity(
|
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToMyLibrary, onGoToSettings,
|
||||||
renderer, mappedInputManager, onContinueReading, onGoToMyLibrary,
|
onGoToFileTransfer, onGoToBrowser));
|
||||||
onGoToSettings, onGoToFileTransfer, onGoToBrowser));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupDisplayAndFonts() {
|
void setupDisplayAndFonts() {
|
||||||
@ -309,7 +274,7 @@ void setupDisplayAndFonts() {
|
|||||||
renderer.insertFont(OPENDYSLEXIC_10_FONT_ID, opendyslexic10FontFamily);
|
renderer.insertFont(OPENDYSLEXIC_10_FONT_ID, opendyslexic10FontFamily);
|
||||||
renderer.insertFont(OPENDYSLEXIC_12_FONT_ID, opendyslexic12FontFamily);
|
renderer.insertFont(OPENDYSLEXIC_12_FONT_ID, opendyslexic12FontFamily);
|
||||||
renderer.insertFont(OPENDYSLEXIC_14_FONT_ID, opendyslexic14FontFamily);
|
renderer.insertFont(OPENDYSLEXIC_14_FONT_ID, opendyslexic14FontFamily);
|
||||||
#endif // OMIT_FONTS
|
#endif // OMIT_FONTS
|
||||||
renderer.insertFont(UI_10_FONT_ID, ui10FontFamily);
|
renderer.insertFont(UI_10_FONT_ID, ui10FontFamily);
|
||||||
renderer.insertFont(UI_12_FONT_ID, ui12FontFamily);
|
renderer.insertFont(UI_12_FONT_ID, ui12FontFamily);
|
||||||
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
||||||
@ -325,13 +290,10 @@ bool isWakeupAfterFlashing() {
|
|||||||
const auto wakeupCause = esp_sleep_get_wakeup_cause();
|
const auto wakeupCause = esp_sleep_get_wakeup_cause();
|
||||||
const auto resetReason = esp_reset_reason();
|
const auto resetReason = esp_reset_reason();
|
||||||
|
|
||||||
return isUsbConnected() && (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED) &&
|
return isUsbConnected() && (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED) && (resetReason == ESP_RST_UNKNOWN);
|
||||||
(resetReason == ESP_RST_UNKNOWN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSoftwareRestart() {
|
bool isSoftwareRestart() { return esp_reset_reason() == ESP_RST_SW; }
|
||||||
return esp_reset_reason() == ESP_RST_SW;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
t1 = millis();
|
t1 = millis();
|
||||||
@ -360,8 +322,7 @@ void setup() {
|
|||||||
Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
|
Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
|
||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new FullScreenMessageActivity(
|
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
|
||||||
renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,9 +336,7 @@ void setup() {
|
|||||||
|
|
||||||
// First serial output only here to avoid timing inconsistencies for power
|
// First serial output only here to avoid timing inconsistencies for power
|
||||||
// button press duration verification
|
// button press duration verification
|
||||||
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION
|
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
||||||
"\n",
|
|
||||||
millis());
|
|
||||||
|
|
||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
|
|
||||||
@ -417,9 +376,8 @@ void loop() {
|
|||||||
inputManager.update();
|
inputManager.update();
|
||||||
|
|
||||||
if (Serial && millis() - lastMemPrint >= 10000) {
|
if (Serial && millis() - lastMemPrint >= 10000) {
|
||||||
Serial.printf(
|
Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
|
||||||
"[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n",
|
ESP.getHeapSize(), ESP.getMinFreeHeap());
|
||||||
millis(), ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getMinFreeHeap());
|
|
||||||
lastMemPrint = millis();
|
lastMemPrint = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,14 +386,12 @@ void loop() {
|
|||||||
static unsigned long lastActivityTime = millis();
|
static unsigned long lastActivityTime = millis();
|
||||||
if (inputManager.wasAnyPressed() || inputManager.wasAnyReleased() ||
|
if (inputManager.wasAnyPressed() || inputManager.wasAnyReleased() ||
|
||||||
(currentActivity && currentActivity->preventAutoSleep())) {
|
(currentActivity && currentActivity->preventAutoSleep())) {
|
||||||
lastActivityTime = millis(); // Reset inactivity timer
|
lastActivityTime = millis(); // Reset inactivity timer
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
|
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
|
||||||
if (millis() - lastActivityTime >= sleepTimeoutMs) {
|
if (millis() - lastActivityTime >= sleepTimeoutMs) {
|
||||||
Serial.printf(
|
Serial.printf("[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n", millis(), sleepTimeoutMs);
|
||||||
"[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n",
|
|
||||||
millis(), sleepTimeoutMs);
|
|
||||||
enterDeepSleep();
|
enterDeepSleep();
|
||||||
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
||||||
return;
|
return;
|
||||||
@ -458,9 +414,8 @@ void loop() {
|
|||||||
if (loopDuration > maxLoopDuration) {
|
if (loopDuration > maxLoopDuration) {
|
||||||
maxLoopDuration = loopDuration;
|
maxLoopDuration = loopDuration;
|
||||||
if (maxLoopDuration > 50) {
|
if (maxLoopDuration > 50) {
|
||||||
Serial.printf(
|
Serial.printf("[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n", millis(), maxLoopDuration,
|
||||||
"[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n",
|
activityDuration);
|
||||||
millis(), maxLoopDuration, activityDuration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,8 +423,8 @@ void loop() {
|
|||||||
// When an activity requests skip loop delay (e.g., webserver running), use
|
// When an activity requests skip loop delay (e.g., webserver running), use
|
||||||
// yield() for faster response Otherwise, use longer delay to save power
|
// yield() for faster response Otherwise, use longer delay to save power
|
||||||
if (currentActivity && currentActivity->skipLoopDelay()) {
|
if (currentActivity && currentActivity->skipLoopDelay()) {
|
||||||
yield(); // Give FreeRTOS a chance to run tasks, but return immediately
|
yield(); // Give FreeRTOS a chance to run tasks, but return immediately
|
||||||
} else {
|
} else {
|
||||||
delay(10); // Normal delay when no activity requires fast response
|
delay(10); // Normal delay when no activity requires fast response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user