fix: reset clang correctly

This commit is contained in:
Brackyt 2026-01-25 17:48:57 +01:00
parent bf353e7d83
commit f6469c48ba
26 changed files with 1298 additions and 1746 deletions

View File

@ -1,8 +1,10 @@
#include "Bitmap.h"
#include "BitmapHelpers.h"
#include <cstdlib>
#include <cstring>
#include "BitmapHelpers.h"
// ============================================================================
// IMAGE PROCESSING OPTIONS
// ============================================================================
@ -36,8 +38,7 @@ size_t Bitmap::readBytes(void *buf, size_t count) const {
return file->read(buf, count);
} else if (memoryBuffer) {
size_t available = memorySize - bufferPos;
if (count > available)
count = available;
if (count > available) count = available;
memcpy(buf, memoryBuffer + bufferPos, count);
bufferPos += count;
return count;
@ -74,8 +75,7 @@ bool Bitmap::seekCur(int32_t offset) const {
uint16_t Bitmap::readLE16() {
const int c0 = readByte();
const int c1 = readByte();
return static_cast<uint16_t>(c0 & 0xFF) |
(static_cast<uint16_t>(c1 & 0xFF) << 8);
return static_cast<uint16_t>(c0 & 0xFF) | (static_cast<uint16_t>(c1 & 0xFF) << 8);
}
uint32_t Bitmap::readLE32() {
@ -83,10 +83,8 @@ uint32_t Bitmap::readLE32() {
const int c1 = readByte();
const int c2 = readByte();
const int c3 = readByte();
return static_cast<uint32_t>(c0 & 0xFF) |
(static_cast<uint32_t>(c1 & 0xFF) << 8) |
(static_cast<uint32_t>(c2 & 0xFF) << 16) |
(static_cast<uint32_t>(c3 & 0xFF) << 24);
return static_cast<uint32_t>(c0 & 0xFF) | (static_cast<uint32_t>(c1 & 0xFF) << 8) |
(static_cast<uint32_t>(c2 & 0xFF) << 16) | (static_cast<uint32_t>(c3 & 0xFF) << 24);
}
const char* Bitmap::errorToString(BmpReaderError err) {
@ -126,21 +124,17 @@ const char *Bitmap::errorToString(BmpReaderError err) {
}
BmpReaderError Bitmap::parseHeaders() {
if (!file && !memoryBuffer)
return BmpReaderError::FileInvalid;
if (!seekSet(0))
return BmpReaderError::SeekStartFailed;
if (!file && !memoryBuffer) return BmpReaderError::FileInvalid;
if (!seekSet(0)) return BmpReaderError::SeekStartFailed;
const uint16_t bfType = readLE16();
if (bfType != 0x4D42)
return BmpReaderError::NotBMP;
if (bfType != 0x4D42) return BmpReaderError::NotBMP;
seekCur(8);
bfOffBits = readLE32();
const uint32_t biSize = readLE32();
if (biSize < 40)
return BmpReaderError::DIBTooSmall;
if (biSize < 40) return BmpReaderError::DIBTooSmall;
width = 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();
bpp = readLE16();
const uint32_t comp = readLE32();
const bool validBpp =
bpp == 1 || bpp == 2 || bpp == 8 || bpp == 24 || bpp == 32;
const bool validBpp = bpp == 1 || bpp == 2 || bpp == 8 || bpp == 24 || bpp == 32;
if (planes != 1)
return BmpReaderError::BadPlanes;
if (!validBpp)
return BmpReaderError::UnsupportedBpp;
if (!(comp == 0 || (bpp == 32 && comp == 3)))
return BmpReaderError::UnsupportedCompression;
if (planes != 1) return BmpReaderError::BadPlanes;
if (!validBpp) return BmpReaderError::UnsupportedBpp;
if (!(comp == 0 || (bpp == 32 && comp == 3))) return BmpReaderError::UnsupportedCompression;
seekCur(12);
const uint32_t colorsUsed = readLE32();
if (colorsUsed > 256u)
return BmpReaderError::PaletteTooLarge;
if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge;
seekCur(4);
if (width <= 0 || height <= 0)
return BmpReaderError::BadDimensions;
if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions;
constexpr int MAX_IMAGE_WIDTH = 2048;
constexpr int MAX_IMAGE_HEIGHT = 3072;
@ -177,8 +165,7 @@ BmpReaderError Bitmap::parseHeaders() {
rowBytes = (width * bpp + 31) / 32 * 4;
for (int i = 0; i < 256; i++)
paletteLum[i] = static_cast<uint8_t>(i);
for (int i = 0; i < 256; i++) paletteLum[i] = static_cast<uint8_t>(i);
if (colorsUsed > 0) {
for (uint32_t i = 0; i < colorsUsed; i++) {
uint8_t rgb[4];
@ -187,8 +174,7 @@ BmpReaderError Bitmap::parseHeaders() {
}
}
if (!seekSet(bfOffBits))
return BmpReaderError::SeekPixelDataFailed;
if (!seekSet(bfOffBits)) return BmpReaderError::SeekPixelDataFailed;
if (bpp > 2 && dithering) {
if (USE_ATKINSON) {
@ -202,8 +188,7 @@ BmpReaderError Bitmap::parseHeaders() {
}
BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const {
if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes)
return BmpReaderError::ShortReadRow;
if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes) return BmpReaderError::ShortReadRow;
prevRowY += 1;
uint8_t* outPtr = data;
@ -255,14 +240,12 @@ BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const {
break;
}
case 8: {
for (int x = 0; x < width; x++)
packPixel(paletteLum[rowBuffer[x]]);
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];
uint8_t lum = paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03];
packPixel(lum);
}
break;
@ -283,17 +266,13 @@ BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const {
else if (fsDitherer)
fsDitherer->nextRow();
if (bitShift != 6)
*outPtr = currentOutByte;
if (bitShift != 6) *outPtr = currentOutByte;
return BmpReaderError::Ok;
}
BmpReaderError Bitmap::rewindToData() const {
if (!seekSet(bfOffBits))
return BmpReaderError::SeekPixelDataFailed;
if (fsDitherer)
fsDitherer->reset();
if (atkinsonDitherer)
atkinsonDitherer->reset();
if (!seekSet(bfOffBits)) return BmpReaderError::SeekPixelDataFailed;
if (fsDitherer) fsDitherer->reset();
if (atkinsonDitherer) atkinsonDitherer->reset();
return BmpReaderError::Ok;
}

View File

@ -32,11 +32,9 @@ class Bitmap {
public:
static const char* errorToString(BmpReaderError err);
explicit Bitmap(FsFile &file, bool dithering = false)
: file(&file), dithering(dithering) {}
explicit Bitmap(FsFile& file, bool dithering = false) : file(&file), dithering(dithering) {}
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();
BmpReaderError parseHeaders();

View File

@ -1,14 +1,12 @@
#include "GfxRenderer.h"
#include <Utf8.h>
#include <algorithm>
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) {
fontMap.insert({fontId, font});
}
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
void GfxRenderer::rotateCoordinates(const int x, const int y, int *rotatedX,
int *rotatedY) const {
void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int* rotatedY) const {
switch (orientation) {
case Portrait: {
// Logical portrait (480x800) → panel (800x480)
@ -56,14 +54,12 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
// Bounds checking against physical panel dimensions
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n",
millis(), x, y, rotatedX, rotatedY);
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, rotatedX, rotatedY);
return;
}
// Calculate byte position and bit position
const uint16_t byteIndex =
rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
if (state) {
@ -84,21 +80,19 @@ bool GfxRenderer::readPixel(const int x, const int y) const {
rotateCoordinates(x, y, &rotatedX, &rotatedY);
// Bounds checking against physical panel dimensions
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH ||
rotatedY < 0 || rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
return false;
}
const uint16_t byteIndex =
rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
const uint8_t bitPosition = 7 - (rotatedX % 8);
// Bit cleared = black, bit set = white
return !(frameBuffer[byteIndex] & (1 << bitPosition));
}
int GfxRenderer::getTextWidth(const int fontId, const char *text,
const EpdFontFamily::Style style) const {
int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontFamily::Style style) const {
if (fontMap.count(fontId) == 0) {
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
return 0;
@ -109,15 +103,13 @@ int GfxRenderer::getTextWidth(const int fontId, const char *text,
return w;
}
void GfxRenderer::drawCenteredText(const int fontId, const int y,
const char *text, const bool black,
void GfxRenderer::drawCenteredText(const int fontId, const int y, const char* text, const bool black,
const EpdFontFamily::Style style) const {
const int x = (getScreenWidth() - getTextWidth(fontId, text, style)) / 2;
drawText(fontId, x, y, text, black, style);
}
void GfxRenderer::drawText(const int fontId, const int x, const int y,
const char *text, const bool black,
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
const EpdFontFamily::Style style) const {
const int yPos = y + getFontAscenderSize(fontId);
int xpos = x;
@ -144,8 +136,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y,
}
}
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2,
const bool state) const {
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
// Bresenham's line algorithm
int dx = abs(x2 - x1);
int dy = abs(y2 - y1);
@ -170,16 +161,14 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2,
}
}
void GfxRenderer::drawRect(const int x, const int y, const int width,
const int height, const bool state) const {
void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
drawLine(x, y, x + width - 1, y, state);
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
drawLine(x + width - 1, y + height - 1, x, y + height - 1, state);
drawLine(x, y, x, y + height - 1, state);
}
void GfxRenderer::fillRect(const int x, const int y, const int width,
const int height, const bool state) const {
void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
if (!frameBuffer) {
return;
@ -194,8 +183,7 @@ void GfxRenderer::fillRect(const int x, const int y, const int width,
const int x2 = std::min(screenWidth - 1, x + width - 1);
const int y2 = std::min(screenHeight - 1, y + height - 1);
if (x1 > x2 || y1 > y2)
return;
if (x1 > x2 || y1 > y2) return;
// Optimized path for Portrait mode (most common)
if (orientation == Portrait) {
@ -208,8 +196,7 @@ void GfxRenderer::fillRect(const int x, const int y, const int width,
for (int sx = x1; sx <= x2; sx++) {
const int physY = EInkDisplay::DISPLAY_HEIGHT - 1 - sx;
const uint16_t byteIndex =
physY * EInkDisplay::DISPLAY_WIDTH_BYTES + physXByte;
const uint16_t byteIndex = physY * EInkDisplay::DISPLAY_WIDTH_BYTES + physXByte;
if (state) {
frameBuffer[byteIndex] &= ~mask; // Black
@ -231,8 +218,7 @@ void GfxRenderer::fillRect(const int x, const int y, const int width,
for (int sx = x1; sx <= x2; sx++) {
const int physY = sx;
const uint16_t byteIndex =
physY * EInkDisplay::DISPLAY_WIDTH_BYTES + physXByte;
const uint16_t byteIndex = physY * EInkDisplay::DISPLAY_WIDTH_BYTES + physXByte;
if (state) {
frameBuffer[byteIndex] &= ~mask;
@ -286,8 +272,8 @@ void GfxRenderer::fillRect(const int x, const int y, const int width,
}
}
void GfxRenderer::fillRectDithered(const int x, const int y, const int width,
const int height, const uint8_t grayLevel) const {
void GfxRenderer::fillRectDithered(const int x, const int y, const int width, const int height,
const uint8_t grayLevel) const {
// Simulate grayscale using dithering patterns
// 0x00 = black, 0xFF = white, values in between = dithered
@ -322,10 +308,14 @@ void GfxRenderer::fillRectDithered(const int x, const int y, const int width,
int bayerValue;
int px = sx % 2;
int py = sy % 2;
if (px == 0 && py == 0) bayerValue = 0;
else if (px == 1 && py == 0) bayerValue = 2;
else if (px == 0 && py == 1) bayerValue = 3;
else bayerValue = 1;
if (px == 0 && py == 0)
bayerValue = 0;
else if (px == 1 && py == 0)
bayerValue = 2;
else if (px == 0 && py == 1)
bayerValue = 3;
else
bayerValue = 1;
// Draw black if bayer value < threshold (inverted for darker = more black)
bool isBlack = bayerValue >= threshold;
@ -334,8 +324,8 @@ void GfxRenderer::fillRectDithered(const int x, const int y, const int width,
}
}
void GfxRenderer::drawRoundedRect(const int x, const int y, const int width,
const int height, const int radius, const bool state) const {
void GfxRenderer::drawRoundedRect(const int x, const int y, const int width, const int height, const int radius,
const bool state) const {
if (radius <= 0) {
drawRect(x, y, width, height, state);
return;
@ -350,22 +340,26 @@ void GfxRenderer::drawRoundedRect(const int x, const int y, const int width,
while (px <= py) {
// Top-left corner
cx = x + r; cy = y + r;
cx = x + r;
cy = y + r;
drawPixel(cx - py, cy - px, state);
drawPixel(cx - px, cy - py, state);
// Top-right corner
cx = x + width - 1 - r; cy = y + r;
cx = x + width - 1 - r;
cy = y + r;
drawPixel(cx + py, cy - px, state);
drawPixel(cx + px, cy - py, state);
// Bottom-left corner
cx = x + r; cy = y + height - 1 - r;
cx = x + r;
cy = y + height - 1 - r;
drawPixel(cx - py, cy + px, state);
drawPixel(cx - px, cy + py, state);
// Bottom-right corner
cx = x + width - 1 - r; cy = y + height - 1 - r;
cx = x + width - 1 - r;
cy = y + height - 1 - r;
drawPixel(cx + py, cy + px, state);
drawPixel(cx + px, cy + py, state);
@ -385,8 +379,8 @@ void GfxRenderer::drawRoundedRect(const int x, const int y, const int width,
drawLine(x + width - 1, y + r, x + width - 1, y + height - 1 - r, state); // Right
}
void GfxRenderer::fillRoundedRect(const int x, const int y, const int width,
const int height, const int radius, const bool state) const {
void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, const int height, const int radius,
const bool state) const {
if (radius <= 0) {
fillRect(x, y, width, height, state);
return;
@ -429,8 +423,7 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width,
}
}
void GfxRenderer::fillRoundedRectDithered(const int x, const int y, const int width,
const int height, const int radius,
void GfxRenderer::fillRoundedRectDithered(const int x, const int y, const int width, const int height, const int radius,
const uint8_t grayLevel) const {
if (grayLevel == 0x00) {
fillRoundedRect(x, y, width, height, radius, true);
@ -495,10 +488,14 @@ void GfxRenderer::fillRoundedRectDithered(const int x, const int y, const int wi
int bayerValue;
int bx = sx % 2;
int by = sy % 2;
if (bx == 0 && by == 0) bayerValue = 0;
else if (bx == 1 && by == 0) bayerValue = 2;
else if (bx == 0 && by == 1) bayerValue = 3;
else bayerValue = 1;
if (bx == 0 && by == 0)
bayerValue = 0;
else if (bx == 1 && by == 0)
bayerValue = 2;
else if (bx == 0 && by == 1)
bayerValue = 3;
else
bayerValue = 1;
bool isBlack = bayerValue >= threshold;
drawPixel(sx, sy, isBlack);
@ -506,8 +503,7 @@ void GfxRenderer::fillRoundedRectDithered(const int x, const int y, const int wi
}
}
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y,
const int width, const int height) const {
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
// TODO: Rotate bits
int rotatedX = 0;
int rotatedY = 0;
@ -515,8 +511,7 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y,
einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height);
}
void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y,
const int maxWidth, const int maxHeight,
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
const float cropX, const float cropY) const {
// For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for
// 1-bit)
@ -531,14 +526,11 @@ void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y,
int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f);
if (maxWidth > 0 && (1.0f - cropX) * bitmap.getWidth() > maxWidth) {
scale = static_cast<float>(maxWidth) /
static_cast<float>((1.0f - cropX) * bitmap.getWidth());
scale = static_cast<float>(maxWidth) / static_cast<float>((1.0f - cropX) * bitmap.getWidth());
isScaled = true;
}
if (maxHeight > 0 && (1.0f - cropY) * bitmap.getHeight() > maxHeight) {
scale = std::min(
scale, static_cast<float>(maxHeight) /
static_cast<float>((1.0f - cropY) * bitmap.getHeight()));
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>((1.0f - cropY) * bitmap.getHeight()));
isScaled = true;
}
@ -550,8 +542,7 @@ void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y,
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
if (!outputRow || !rowBytes) {
Serial.printf("[%lu] [GFX] !! Failed to allocate BMP row buffers\n",
millis());
Serial.printf("[%lu] [GFX] !! Failed to allocate BMP row buffers\n", millis());
free(outputRow);
free(rowBytes);
return;
@ -560,8 +551,7 @@ void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y,
for (int bmpY = 0; bmpY < (bitmap.getHeight() - cropPixY); bmpY++) {
// The BMP's (0, 0) is the bottom-left corner (if the height is positive,
// top-left if negative). Screen's (0, 0) is the top-left corner.
int screenY =
-cropPixY + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY);
int screenY = -cropPixY + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY);
if (isScaled) {
screenY = std::floor(screenY * scale);
}
@ -571,8 +561,7 @@ void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y,
}
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(),
bmpY);
Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY);
free(outputRow);
free(rowBytes);
return;
@ -616,8 +605,7 @@ void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y,
free(rowBytes);
}
void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
int h) const {
void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w, int h) const {
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
if (!frameBuffer) {
return;
@ -634,8 +622,7 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
if (orientation == Portrait && renderMode == BW) {
for (int row = 0; row < h; row++) {
const int screenY = y + row;
if (screenY < 0 || screenY >= screenHeight)
continue;
if (screenY < 0 || screenY >= screenHeight) continue;
// In Portrait, screenY maps to physical X coordinate
const int physX = screenY;
@ -643,8 +630,7 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
for (int col = 0; col < w; col++) {
const int screenX = x + col;
if (screenX < 0 || screenX >= screenWidth)
continue;
if (screenX < 0 || screenX >= screenWidth) continue;
// Extract 2-bit value (4 pixels per byte)
const uint8_t val = (srcRow[col / 4] >> (6 - ((col % 4) * 2))) & 0x3;
@ -653,8 +639,7 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
if (val < 3) {
// In Portrait: physical Y = DISPLAY_HEIGHT - 1 - screenX
const int physY = EInkDisplay::DISPLAY_HEIGHT - 1 - screenX;
const uint16_t byteIndex =
physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint16_t byteIndex = physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint8_t bitPosition = 7 - (physX % 8);
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit = black
}
@ -667,15 +652,13 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
if (orientation == PortraitInverted && renderMode == BW) {
for (int row = 0; row < h; row++) {
const int screenY = y + row;
if (screenY < 0 || screenY >= screenHeight)
continue;
if (screenY < 0 || screenY >= screenHeight) continue;
const uint8_t* srcRow = data + row * srcRowBytes;
for (int col = 0; col < w; col++) {
const int screenX = x + col;
if (screenX < 0 || screenX >= screenWidth)
continue;
if (screenX < 0 || screenX >= screenWidth) continue;
const uint8_t val = (srcRow[col / 4] >> (6 - ((col % 4) * 2))) & 0x3;
@ -684,8 +667,7 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
// physical Y = screenX
const int physX = EInkDisplay::DISPLAY_WIDTH - 1 - screenY;
const int physY = screenX;
const uint16_t byteIndex =
physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint16_t byteIndex = physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint8_t bitPosition = 7 - (physX % 8);
frameBuffer[byteIndex] &= ~(1 << bitPosition);
}
@ -695,20 +677,16 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
}
// Optimized path for Landscape modes with BW rendering
if ((orientation == LandscapeClockwise ||
orientation == LandscapeCounterClockwise) &&
renderMode == BW) {
if ((orientation == LandscapeClockwise || orientation == LandscapeCounterClockwise) && renderMode == BW) {
for (int row = 0; row < h; row++) {
const int screenY = y + row;
if (screenY < 0 || screenY >= screenHeight)
continue;
if (screenY < 0 || screenY >= screenHeight) continue;
const uint8_t* srcRow = data + row * srcRowBytes;
for (int col = 0; col < w; col++) {
const int screenX = x + col;
if (screenX < 0 || screenX >= screenWidth)
continue;
if (screenX < 0 || screenX >= screenWidth) continue;
const uint8_t val = (srcRow[col / 4] >> (6 - ((col % 4) * 2))) & 0x3;
@ -721,8 +699,7 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
physX = screenX;
physY = screenY;
}
const uint16_t byteIndex =
physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint16_t byteIndex = physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint8_t bitPosition = 7 - (physX % 8);
frameBuffer[byteIndex] &= ~(1 << bitPosition);
}
@ -734,15 +711,13 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
// Fallback: generic path for grayscale modes
for (int row = 0; row < h; row++) {
const int screenY = y + row;
if (screenY < 0 || screenY >= screenHeight)
continue;
if (screenY < 0 || screenY >= screenHeight) continue;
const uint8_t* srcRow = data + row * srcRowBytes;
for (int col = 0; col < w; col++) {
const int screenX = x + col;
if (screenX < 0 || screenX >= screenWidth)
continue;
if (screenX < 0 || screenX >= screenWidth) continue;
const uint8_t val = (srcRow[col / 4] >> (6 - ((col % 4) * 2))) & 0x3;
@ -755,19 +730,16 @@ void GfxRenderer::draw2BitImage(const uint8_t data[], int x, int y, int w,
}
}
void GfxRenderer::drawBitmap1Bit(const Bitmap &bitmap, const int x, const int y,
const int maxWidth,
void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxWidth,
const int maxHeight) const {
float scale = 1.0f;
bool isScaled = false;
if (maxWidth > 0 && bitmap.getWidth() > maxWidth) {
scale =
static_cast<float>(maxWidth) / static_cast<float>(bitmap.getWidth());
scale = static_cast<float>(maxWidth) / static_cast<float>(bitmap.getWidth());
isScaled = true;
}
if (maxHeight > 0 && bitmap.getHeight() > maxHeight) {
scale = std::min(scale, static_cast<float>(maxHeight) /
static_cast<float>(bitmap.getHeight()));
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>(bitmap.getHeight()));
isScaled = true;
}
@ -778,8 +750,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap &bitmap, const int x, const int y,
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
if (!outputRow || !rowBytes) {
Serial.printf("[%lu] [GFX] !! Failed to allocate 1-bit BMP row buffers\n",
millis());
Serial.printf("[%lu] [GFX] !! Failed to allocate 1-bit BMP row buffers\n", millis());
free(outputRow);
free(rowBytes);
return;
@ -788,19 +759,15 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap &bitmap, const int x, const int y,
for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) {
// Read rows sequentially using readNextRow
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
Serial.printf("[%lu] [GFX] Failed to read row %d from 1-bit bitmap\n",
millis(), bmpY);
Serial.printf("[%lu] [GFX] Failed to read row %d from 1-bit bitmap\n", millis(), bmpY);
free(outputRow);
free(rowBytes);
return;
}
// Calculate screen Y based on whether BMP is top-down or bottom-up
const int bmpYOffset =
bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY;
int screenY =
y + (isScaled ? static_cast<int>(std::floor(bmpYOffset * scale))
: bmpYOffset);
const int bmpYOffset = bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY;
int screenY = y + (isScaled ? static_cast<int>(std::floor(bmpYOffset * scale)) : bmpYOffset);
if (screenY >= getScreenHeight()) {
continue; // Continue reading to keep row counter in sync
}
@ -809,8 +776,7 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap &bitmap, const int x, const int y,
}
for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) {
int screenX =
x + (isScaled ? static_cast<int>(std::floor(bmpX * scale)) : bmpX);
int screenX = x + (isScaled ? static_cast<int>(std::floor(bmpX * scale)) : bmpX);
if (screenX >= getScreenWidth()) {
break;
}
@ -834,31 +800,24 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap &bitmap, const int x, const int y,
free(rowBytes);
}
void GfxRenderer::fillPolygon(const int *xPoints, const int *yPoints,
int numPoints, bool state) const {
if (numPoints < 3)
return;
void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state) const {
if (numPoints < 3) return;
// Find bounding box
int minY = yPoints[0], maxY = yPoints[0];
for (int i = 1; i < numPoints; i++) {
if (yPoints[i] < minY)
minY = yPoints[i];
if (yPoints[i] > maxY)
maxY = yPoints[i];
if (yPoints[i] < minY) minY = yPoints[i];
if (yPoints[i] > maxY) maxY = yPoints[i];
}
// Clip to screen
if (minY < 0)
minY = 0;
if (maxY >= getScreenHeight())
maxY = getScreenHeight() - 1;
if (minY < 0) minY = 0;
if (maxY >= getScreenHeight()) maxY = getScreenHeight() - 1;
// Allocate node buffer for scanline algorithm
auto* nodeX = static_cast<int*>(malloc(numPoints * sizeof(int)));
if (!nodeX) {
Serial.printf("[%lu] [GFX] !! Failed to allocate polygon node buffer\n",
millis());
Serial.printf("[%lu] [GFX] !! Failed to allocate polygon node buffer\n", millis());
return;
}
@ -869,13 +828,11 @@ void GfxRenderer::fillPolygon(const int *xPoints, const int *yPoints,
// Find all intersection points with edges
int j = numPoints - 1;
for (int i = 0; i < numPoints; i++) {
if ((yPoints[i] < scanY && yPoints[j] >= scanY) ||
(yPoints[j] < scanY && yPoints[i] >= scanY)) {
if ((yPoints[i] < scanY && yPoints[j] >= scanY) || (yPoints[j] < scanY && yPoints[i] >= scanY)) {
// Calculate X intersection using fixed-point to avoid float
int dy = yPoints[j] - yPoints[i];
if (dy != 0) {
nodeX[nodes++] = xPoints[i] + (scanY - yPoints[i]) *
(xPoints[j] - xPoints[i]) / dy;
nodeX[nodes++] = xPoints[i] + (scanY - yPoints[i]) * (xPoints[j] - xPoints[i]) / dy;
}
}
j = i;
@ -898,10 +855,8 @@ void GfxRenderer::fillPolygon(const int *xPoints, const int *yPoints,
int endX = nodeX[i + 1];
// Clip to screen
if (startX < 0)
startX = 0;
if (endX >= getScreenWidth())
endX = getScreenWidth() - 1;
if (startX < 0) startX = 0;
if (endX >= getScreenWidth()) endX = getScreenWidth() - 1;
// Draw horizontal line
for (int x = startX; x <= endX; x++) {
@ -913,12 +868,10 @@ void GfxRenderer::fillPolygon(const int *xPoints, const int *yPoints,
free(nodeX);
}
uint8_t *GfxRenderer::captureRegion(int x, int y, int width, int height,
size_t *outSize) const {
uint8_t* GfxRenderer::captureRegion(int x, int y, int width, int height, size_t* outSize) const {
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
if (!frameBuffer || width <= 0 || height <= 0) {
if (outSize)
*outSize = 0;
if (outSize) *outSize = 0;
return nullptr;
}
@ -933,14 +886,11 @@ uint8_t *GfxRenderer::captureRegion(int x, int y, int width, int height,
height += y;
y = 0;
}
if (x + width > screenWidth)
width = screenWidth - x;
if (y + height > screenHeight)
height = screenHeight - y;
if (x + width > screenWidth) width = screenWidth - x;
if (y + height > screenHeight) height = screenHeight - y;
if (width <= 0 || height <= 0) {
if (outSize)
*outSize = 0;
if (outSize) *outSize = 0;
return nullptr;
}
@ -949,8 +899,7 @@ uint8_t *GfxRenderer::captureRegion(int x, int y, int width, int height,
const size_t bufferSize = rowBytes * height + 4 * sizeof(int); // +header
uint8_t* buffer = static_cast<uint8_t*>(malloc(bufferSize));
if (!buffer) {
if (outSize)
*outSize = 0;
if (outSize) *outSize = 0;
return nullptr;
}
@ -976,8 +925,7 @@ uint8_t *GfxRenderer::captureRegion(int x, int y, int width, int height,
rotateCoordinates(screenX, screenY, &physX, &physY);
// Read pixel from framebuffer
const uint16_t byteIndex =
physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint16_t byteIndex = physY * EInkDisplay::DISPLAY_WIDTH_BYTES + (physX / 8);
const uint8_t bitPosition = 7 - (physX % 8);
const bool isBlack = !(frameBuffer[byteIndex] & (1 << bitPosition));
@ -988,13 +936,11 @@ uint8_t *GfxRenderer::captureRegion(int x, int y, int width, int height,
}
}
if (outSize)
*outSize = bufferSize;
if (outSize) *outSize = bufferSize;
return buffer;
}
void GfxRenderer::restoreRegion(const uint8_t *buffer, int x, int y, int width,
int height) const {
void GfxRenderer::restoreRegion(const uint8_t* buffer, int x, int y, int width, int height) const {
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
if (!frameBuffer || !buffer || width <= 0 || height <= 0) {
return;
@ -1007,8 +953,7 @@ void GfxRenderer::restoreRegion(const uint8_t *buffer, int x, int y, int width,
if (orientation == Portrait) {
for (int row = 0; row < height; row++) {
const int screenY = y + row;
if (screenY < 0 || screenY >= getScreenHeight())
continue;
if (screenY < 0 || screenY >= getScreenHeight()) continue;
const uint8_t* srcRow = data + row * rowBytes;
const int physX = screenY;
@ -1018,13 +963,11 @@ void GfxRenderer::restoreRegion(const uint8_t *buffer, int x, int y, int width,
for (int col = 0; col < width; col++) {
const int screenX = x + col;
if (screenX < 0 || screenX >= getScreenWidth())
continue;
if (screenX < 0 || screenX >= getScreenWidth()) continue;
const bool isBlack = !(srcRow[col / 8] & (1 << (7 - (col % 8))));
const int physY = EInkDisplay::DISPLAY_HEIGHT - 1 - screenX;
const uint16_t byteIndex =
physY * EInkDisplay::DISPLAY_WIDTH_BYTES + physXByte;
const uint16_t byteIndex = physY * EInkDisplay::DISPLAY_WIDTH_BYTES + physXByte;
if (isBlack) {
frameBuffer[byteIndex] &= ~mask;
@ -1049,9 +992,7 @@ void GfxRenderer::restoreRegion(const uint8_t *buffer, int x, int y, int width,
}
}
void GfxRenderer::clearScreen(const uint8_t color) const {
einkDisplay.clearScreen(color);
}
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
void GfxRenderer::invertScreen() const {
uint8_t* buffer = einkDisplay.getFrameBuffer();
@ -1064,13 +1005,11 @@ void GfxRenderer::invertScreen() const {
}
}
void GfxRenderer::displayBuffer(
const EInkDisplay::RefreshMode refreshMode) const {
void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) const {
einkDisplay.displayBuffer(refreshMode);
}
std::string GfxRenderer::truncatedText(const int fontId, const char *text,
const int maxWidth,
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
const EpdFontFamily::Style style) const {
std::string item = text;
int itemWidth = getTextWidth(fontId, item.c_str(), style);
@ -1138,8 +1077,7 @@ int GfxRenderer::getLineHeight(const int fontId) const {
return fontMap.at(fontId).getData(EpdFontFamily::REGULAR)->advanceY;
}
void GfxRenderer::drawButtonHints(const int fontId, const char *btn1,
const char *btn2, const char *btn3,
void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) {
const Orientation orig_orientation = getOrientation();
setOrientation(Orientation::Portrait);
@ -1167,8 +1105,7 @@ void GfxRenderer::drawButtonHints(const int fontId, const char *btn1,
setOrientation(orig_orientation);
}
void GfxRenderer::drawSideButtonHints(const int fontId, const char *topBtn,
const char *bottomBtn) const {
void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) const {
const int screenWidth = getScreenWidth();
constexpr int buttonWidth = 40; // Width on screen (height when rotated)
constexpr int buttonHeight = 80; // Height on screen (width when rotated)
@ -1190,8 +1127,7 @@ void GfxRenderer::drawSideButtonHints(const int fontId, const char *topBtn,
}
// Draw shared middle border
if ((topBtn != nullptr && topBtn[0] != '\0') ||
(bottomBtn != nullptr && bottomBtn[0] != '\0')) {
if ((topBtn != nullptr && topBtn[0] != '\0') || (bottomBtn != nullptr && bottomBtn[0] != '\0')) {
drawLine(x, topButtonY + buttonHeight, x + buttonWidth - 1,
topButtonY + buttonHeight); // Shared border
}
@ -1200,8 +1136,7 @@ void GfxRenderer::drawSideButtonHints(const int fontId, const char *topBtn,
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
drawLine(x, topButtonY + buttonHeight, x,
topButtonY + 2 * buttonHeight - 1); // Left
drawLine(x + buttonWidth - 1, topButtonY + buttonHeight,
x + buttonWidth - 1,
drawLine(x + buttonWidth - 1, topButtonY + buttonHeight, x + buttonWidth - 1,
topButtonY + 2 * buttonHeight - 1); // Right
drawLine(x, topButtonY + 2 * buttonHeight - 1, x + buttonWidth - 1,
topButtonY + 2 * buttonHeight - 1); // Bottom
@ -1233,9 +1168,7 @@ int GfxRenderer::getTextHeight(const int fontId) const {
return fontMap.at(fontId).getData(EpdFontFamily::REGULAR)->ascender;
}
void GfxRenderer::drawTextRotated90CW(const int fontId, const int x,
const int y, const char *text,
const bool black,
void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y, const char* text, const bool black,
const EpdFontFamily::Style style) const {
// Cannot draw a NULL / empty string
if (text == nullptr || *text == '\0') {
@ -1286,8 +1219,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x,
// 90° clockwise rotation transformation:
// screenX = x + (ascender - top + glyphY)
// screenY = yPos - (left + glyphX)
const int screenX =
x + (font.getData(style)->ascender - top + glyphY);
const int screenX = x + (font.getData(style)->ascender - top + glyphY);
const int screenY = yPos - left - glyphX;
if (is2Bit) {
@ -1297,8 +1229,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x,
if (renderMode == BW && bmpVal < 3) {
drawPixel(screenX, screenY, black);
} else if (renderMode == GRAYSCALE_MSB &&
(bmpVal == 1 || bmpVal == 2)) {
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
drawPixel(screenX, screenY, false);
@ -1320,21 +1251,15 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x,
}
}
uint8_t *GfxRenderer::getFrameBuffer() const {
return einkDisplay.getFrameBuffer();
}
uint8_t* GfxRenderer::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
size_t GfxRenderer::getBufferSize() { return EInkDisplay::BUFFER_SIZE; }
void GfxRenderer::grayscaleRevert() const { einkDisplay.grayscaleRevert(); }
void GfxRenderer::copyGrayscaleLsbBuffers() const {
einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer());
}
void GfxRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
void GfxRenderer::copyGrayscaleMsbBuffers() const {
einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer());
}
void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
@ -1365,7 +1290,8 @@ bool GfxRenderer::storeBwBuffer() {
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
// Check if any chunks are already allocated
if (bwBufferChunks[i]) {
Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this "
Serial.printf(
"[%lu] [GFX] !! BW buffer chunk %zu already stored - this "
"is likely a bug, freeing chunk\n",
millis(), i);
free(bwBufferChunks[i]);
@ -1376,9 +1302,8 @@ bool GfxRenderer::storeBwBuffer() {
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(BW_BUFFER_CHUNK_SIZE));
if (!bwBufferChunks[i]) {
Serial.printf(
"[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n",
millis(), i, BW_BUFFER_CHUNK_SIZE);
Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i,
BW_BUFFER_CHUNK_SIZE);
// Free previously allocated chunks
freeBwBufferChunks();
return false;
@ -1387,8 +1312,8 @@ bool GfxRenderer::storeBwBuffer() {
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
}
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n",
millis(), BW_BUFFER_NUM_CHUNKS, BW_BUFFER_CHUNK_SIZE);
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS,
BW_BUFFER_CHUNK_SIZE);
return true;
}
@ -1414,8 +1339,7 @@ void GfxRenderer::restoreBwBuffer() {
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
if (!frameBuffer) {
Serial.printf("[%lu] [GFX] !! No framebuffer in restoreBwBuffer\n",
millis());
Serial.printf("[%lu] [GFX] !! No framebuffer in restoreBwBuffer\n", millis());
freeBwBufferChunks();
return;
}
@ -1423,9 +1347,7 @@ void GfxRenderer::restoreBwBuffer() {
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
// Check if chunk is missing
if (!bwBufferChunks[i]) {
Serial.printf(
"[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n",
millis());
Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis());
freeBwBufferChunks();
return;
}
@ -1451,9 +1373,8 @@ void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
}
}
void GfxRenderer::renderChar(const EpdFontFamily &fontFamily, const uint32_t cp,
int *x, const int *y, const bool pixelState,
const EpdFontFamily::Style style) const {
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
const bool pixelState, const EpdFontFamily::Style style) const {
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
if (!glyph) {
glyph = fontFamily.getGlyph(REPLACEMENT_GLYPH, style);
@ -1493,8 +1414,7 @@ void GfxRenderer::renderChar(const EpdFontFamily &fontFamily, const uint32_t cp,
if (renderMode == BW && bmpVal < 3) {
// Black (also paints over the grays in BW mode)
drawPixel(screenX, screenY, pixelState);
} else if (renderMode == GRAYSCALE_MSB &&
(bmpVal == 1 || bmpVal == 2)) {
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
// Light gray (also mark the MSB if it's going to be a dark gray
// too) We have to flag pixels in reverse for the gray buffers, as 0
// leave alone, 1 update
@ -1518,8 +1438,7 @@ void GfxRenderer::renderChar(const EpdFontFamily &fontFamily, const uint32_t cp,
*x += glyph->advanceX;
}
void GfxRenderer::getOrientedViewableTRBL(int *outTop, int *outRight,
int *outBottom, int *outLeft) const {
void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const {
switch (orientation) {
case Portrait:
*outTop = VIEWABLE_MARGIN_TOP;

View File

@ -22,12 +22,9 @@ public:
};
private:
static constexpr size_t BW_BUFFER_CHUNK_SIZE =
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_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS ==
EInkDisplay::BUFFER_SIZE,
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 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_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
"BW buffer chunking does not line up with display buffer size");
EInkDisplay& einkDisplay;
@ -35,15 +32,13 @@ private:
Orientation orientation;
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
std::map<int, EpdFontFamily> fontMap;
void renderChar(const EpdFontFamily &fontFamily, uint32_t cp, int *x,
const int *y, bool pixelState,
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
EpdFontFamily::Style style) const;
void freeBwBufferChunks();
void rotateCoordinates(int x, int y, int* rotatedX, int* rotatedY) const;
public:
explicit GfxRenderer(EInkDisplay &einkDisplay)
: einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
~GfxRenderer() { freeBwBufferChunks(); }
static constexpr int VIEWABLE_MARGIN_TOP = 9;
@ -62,8 +57,7 @@ public:
// Screen ops
int getScreenWidth() const;
int getScreenHeight() const;
void displayBuffer(
EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
// EXPERIMENTAL: Windowed update - display only a rectangular region
void displayWindow(int x, int y, int width, int height) const;
void invertScreen() const;
@ -79,50 +73,39 @@ public:
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 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,
int height) const;
void drawBitmap(const Bitmap &bitmap, int x, int y, int maxWidth,
int maxHeight, float cropX = 0, float cropY = 0) const;
void drawBitmap1Bit(const Bitmap &bitmap, int x, int y, int maxWidth,
int maxHeight) const;
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0,
float cropY = 0) 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 fillPolygon(const int *xPoints, const int *yPoints, int numPoints,
bool state = true) const;
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
// Region caching - copies a rectangular region to/from a buffer
// Returns allocated buffer on success, nullptr on failure. Caller owns the
// memory.
uint8_t *captureRegion(int x, int y, int width, int height,
size_t *outSize) const;
uint8_t* captureRegion(int x, int y, int width, int height, size_t* outSize) const;
// Restores a previously captured region. Buffer must match dimensions.
void restoreRegion(const uint8_t *buffer, int x, int y, int width,
int height) const;
void restoreRegion(const uint8_t* buffer, int x, int y, int width, int height) const;
// Text
int getTextWidth(int fontId, const char *text,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
void
drawCenteredText(int fontId, int y, const char *text, bool black = true,
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
void drawCenteredText(int fontId, 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;
int getSpaceWidth(int fontId) const;
int getFontAscenderSize(int fontId) const;
int getLineHeight(int fontId) const;
std::string
truncatedText(int fontId, const char *text, int maxWidth,
std::string truncatedText(int fontId, const char* text, int maxWidth,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
// UI Components
void drawButtonHints(int fontId, const char *btn1, const char *btn2,
const char *btn3, const char *btn4);
void drawSideButtonHints(int fontId, const char *topBtn,
const char *bottomBtn) const;
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4);
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
private:
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
void drawTextRotated90CW(
int fontId, int x, int y, const char *text, bool black = true,
void drawTextRotated90CW(int fontId, int x, int y, const char* text, bool black = true,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
int getTextHeight(int fontId) const;
@ -140,6 +123,5 @@ public:
uint8_t* getFrameBuffer() const;
static size_t getBufferSize();
void grayscaleRevert() const;
void getOrientedViewableTRBL(int *outTop, int *outRight, int *outBottom,
int *outLeft) const;
void getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const;
};

View File

@ -1,11 +1,13 @@
#pragma once
#include <Bitmap.h>
#include <SDCardManager.h>
#include <vector>
#include "ThemeContext.h"
#include "ThemeTypes.h"
#include "UIElement.h"
#include <Bitmap.h>
#include <SDCardManager.h>
#include <vector>
namespace ThemeEngine {
@ -21,12 +23,9 @@ protected:
int borderRadius = 0; // Corner radius (for future rounded rect support)
public:
Container(const std::string &id) : UIElement(id) {
bgColorExpr = Expression::parse("0xFF");
}
Container(const std::string& id) : UIElement(id) { bgColorExpr = Expression::parse("0xFF"); }
virtual ~Container() {
for (auto child : children)
delete child;
for (auto child : children) delete child;
}
Container* asContainer() override { return this; }
@ -69,8 +68,7 @@ public:
int getBorderRadius() const { return borderRadius; }
void layout(const ThemeContext &context, int parentX, int parentY,
int parentW, int parentH) override {
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
UIElement::layout(context, parentX, parentY, parentW, parentH);
// Children are laid out with padding offset
int childX = absX + padding;
@ -90,8 +88,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
if (hasBg) {
std::string colStr = context.evaluatestring(bgColorExpr);
@ -148,9 +145,7 @@ class Rectangle : public UIElement {
Expression colorExpr;
public:
Rectangle(const std::string &id) : UIElement(id) {
colorExpr = Expression::parse("0x00");
}
Rectangle(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
ElementType getType() const override { return ElementType::Rectangle; }
void setFill(bool f) {
@ -169,8 +164,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
std::string colStr = context.evaluatestring(colorExpr);
uint8_t color = Color::parse(colStr).value;
@ -205,9 +199,7 @@ private:
bool ellipsis = true; // Truncate with ... if too long
public:
Label(const std::string &id) : UIElement(id) {
colorExpr = Expression::parse("0x00");
}
Label(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
ElementType getType() const override { return ElementType::Label; }
void setText(const std::string& expr) {
@ -240,8 +232,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
std::string finalText = context.evaluatestring(textExpr);
if (finalText.empty()) {
@ -353,22 +344,18 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
std::string valStr = context.evaluatestring(valueExpr);
std::string maxStr = context.evaluatestring(maxExpr);
int value = valStr.empty() ? 0 : std::stoi(valStr);
int maxVal = maxStr.empty() ? 100 : std::stoi(maxStr);
if (maxVal <= 0)
maxVal = 100;
if (maxVal <= 0) maxVal = 100;
float ratio = static_cast<float>(value) / static_cast<float>(maxVal);
if (ratio < 0)
ratio = 0;
if (ratio > 1)
ratio = 1;
if (ratio < 0) ratio = 0;
if (ratio > 1) ratio = 1;
// Draw background
std::string bgStr = context.evaluatestring(bgColorExpr);
@ -399,9 +386,7 @@ class Divider : public UIElement {
int thickness = 1;
public:
Divider(const std::string &id) : UIElement(id) {
colorExpr = Expression::parse("0x00");
}
Divider(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
ElementType getType() const override { return ElementType::Divider; }
@ -419,8 +404,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
std::string colStr = context.evaluatestring(colorExpr);
uint8_t color = Color::parse(colStr).value;
@ -464,8 +448,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
std::string valStr = context.evaluatestring(valueExpr);
int percentage = valStr.empty() ? 0 : std::stoi(valStr);
@ -480,22 +463,17 @@ public:
int x = absX;
int y = absY;
if (absW > batteryWidth)
x += (absW - batteryWidth) / 2;
if (absH > batteryHeight)
y += (absH - batteryHeight) / 2;
if (absW > batteryWidth) x += (absW - batteryWidth) / 2;
if (absH > batteryHeight) y += (absH - batteryHeight) / 2;
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y, black);
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3,
y + batteryHeight - 1, black);
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1, black);
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2, black);
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2,
y + batteryHeight - 2, black);
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2, black);
renderer.drawPixel(x + batteryWidth - 1, y + 3, black);
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4, black);
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0,
y + batteryHeight - 5, black);
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5, black);
if (percentage > 0) {
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;

View File

@ -26,12 +26,10 @@ struct IniSection {
class IniParser {
public:
// Parse a stream (File, Serial, etc.)
static std::map<std::string, std::map<std::string, std::string>>
parse(Stream &stream);
static std::map<std::string, std::map<std::string, std::string>> parse(Stream& stream);
// Parse a string buffer (useful for testing)
static std::map<std::string, std::map<std::string, std::string>>
parseString(const std::string &content);
static std::map<std::string, std::map<std::string, std::string>> parseString(const std::string& content);
private:
static void trim(std::string& s);

View File

@ -1,10 +1,11 @@
#pragma once
#include <vector>
#include "BasicElements.h"
#include "ThemeContext.h"
#include "ThemeTypes.h"
#include "UIElement.h"
#include <vector>
namespace ThemeEngine {
@ -32,8 +33,7 @@ public:
markDirty();
}
void layout(const ThemeContext &context, int parentX, int parentY,
int parentW, int parentH) override {
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
UIElement::layout(context, parentX, parentY, parentW, parentH);
int currentX = absX + padding;
@ -85,8 +85,7 @@ public:
markDirty();
}
void layout(const ThemeContext &context, int parentX, int parentY,
int parentW, int parentH) override {
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
UIElement::layout(context, parentX, parentY, parentW, parentH);
int currentY = absY + padding;
@ -141,12 +140,10 @@ public:
markDirty();
}
void layout(const ThemeContext &context, int parentX, int parentY,
int parentW, int parentH) override {
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
UIElement::layout(context, parentX, parentY, parentW, parentH);
if (children.empty())
return;
if (children.empty()) return;
int availableW = absW - 2 * padding - (columns - 1) * colSpacing;
int cellW = availableW / columns;
@ -162,8 +159,7 @@ public:
// Pass cell dimensions to avoid clamping issues
child->layout(context, cellX, currentY, cellW, availableH);
int childH = child->getAbsH();
if (childH > maxRowHeight)
maxRowHeight = childH;
if (childH > maxRowHeight) maxRowHeight = childH;
col++;
if (col >= columns) {
@ -222,8 +218,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
std::string text = context.evaluatestring(textExpr);
if (text.empty()) {
@ -238,10 +233,8 @@ public:
int badgeH = textH + 2 * paddingV;
// Use absX, absY as position, but we may auto-size
if (absW == 0)
absW = badgeW;
if (absH == 0)
absH = badgeH;
if (absW == 0) absW = badgeW;
if (absH == 0) absH = badgeH;
// Draw background
std::string bgStr = context.evaluatestring(bgColorExpr);
@ -306,14 +299,12 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
bool isOn = context.evaluateBool(valueExpr.rawExpr);
// Get colors
std::string colorStr =
isOn ? context.evaluatestring(onColorExpr) : context.evaluatestring(offColorExpr);
std::string colorStr = isOn ? context.evaluatestring(onColorExpr) : context.evaluatestring(offColorExpr);
uint8_t trackColor = Color::parse(colorStr).value;
// Draw track
@ -324,8 +315,7 @@ public:
// Draw knob
int knobMargin = (trackHeight - knobSize) / 2;
int knobX = isOn ? (trackX + trackWidth - knobSize - knobMargin)
: (trackX + knobMargin);
int knobX = isOn ? (trackX + trackWidth - knobSize - knobMargin) : (trackX + knobMargin);
int knobY = trackY + knobMargin;
// Knob is opposite color of track
@ -369,12 +359,10 @@ public:
markDirty();
}
void layout(const ThemeContext &context, int parentX, int parentY,
int parentW, int parentH) override {
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
UIElement::layout(context, parentX, parentY, parentW, parentH);
if (children.empty())
return;
if (children.empty()) return;
// Distribute tabs evenly
int numTabs = children.size();
@ -390,8 +378,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
// Draw background if set
if (hasBg) {
@ -501,8 +488,7 @@ public:
}
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
if (!isVisible(context))
return;
if (!isVisible(context)) return;
// Get values
std::string posStr = context.evaluatestring(positionExpr);
@ -526,8 +512,7 @@ public:
// Calculate thumb size and position
float ratio = static_cast<float>(visible) / static_cast<float>(total);
int thumbH = static_cast<int>(absH * ratio);
if (thumbH < 20)
thumbH = 20; // Minimum thumb size
if (thumbH < 20) thumbH = 20; // Minimum thumb size
int maxScroll = total - visible;
float scrollRatio = maxScroll > 0 ? position / maxScroll : 0;

View File

@ -1,9 +1,10 @@
#pragma once
#include <map>
#include <vector>
#include "BasicElements.h"
#include "UIElement.h"
#include <map>
#include <vector>
namespace ThemeEngine {
@ -64,18 +65,14 @@ public:
}
int getItemHeight() const {
if (itemHeight > 0)
return itemHeight;
if (itemTemplate)
return itemTemplate->getAbsH() > 0 ? itemTemplate->getAbsH() : 45;
if (itemHeight > 0) return itemHeight;
if (itemTemplate) return itemTemplate->getAbsH() > 0 ? itemTemplate->getAbsH() : 45;
return 45;
}
int getItemWidth() const {
if (itemWidth > 0)
return itemWidth;
if (itemTemplate)
return itemTemplate->getAbsW() > 0 ? itemTemplate->getAbsW() : 100;
if (itemWidth > 0) return itemWidth;
if (itemTemplate) return itemTemplate->getAbsW() > 0 ? itemTemplate->getAbsW() : 100;
return 100;
}
@ -98,8 +95,7 @@ public:
void setColumns(int c) {
columns = c > 0 ? c : 1;
if (columns > 1)
layoutMode = LayoutMode::Grid;
if (columns > 1) layoutMode = LayoutMode::Grid;
markDirty();
}
@ -129,8 +125,7 @@ public:
}
}
void layout(const ThemeContext &context, int parentX, int parentY,
int parentW, int parentH) override {
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
// Layout self first (bounds)
UIElement::layout(context, parentX, parentY, parentW, parentH);

View File

@ -25,8 +25,7 @@ struct Expression {
Expression expr;
expr.rawExpr = str;
if (str.empty())
return expr;
if (str.empty()) return expr;
size_t start = 0;
while (start < str.length()) {
@ -39,8 +38,7 @@ struct Expression {
if (open > start) {
// Literal before variable
expr.tokens.push_back(
{ExpressionToken::LITERAL, str.substr(start, open - start)});
expr.tokens.push_back({ExpressionToken::LITERAL, str.substr(start, open - start)});
}
size_t close = str.find('}', open);
@ -51,8 +49,7 @@ struct Expression {
}
// Variable
expr.tokens.push_back(
{ExpressionToken::VARIABLE, str.substr(open + 1, close - open - 1)});
expr.tokens.push_back({ExpressionToken::VARIABLE, str.substr(open + 1, close - open - 1)});
start = close + 1;
}
return expr;
@ -70,20 +67,17 @@ private:
// Helper to trim whitespace
static std::string trim(const std::string& s) {
size_t start = s.find_first_not_of(" \t\n\r");
if (start == std::string::npos)
return "";
if (start == std::string::npos) return "";
size_t end = s.find_last_not_of(" \t\n\r");
return s.substr(start, end - start + 1);
}
// Helper to check if string is a number
static bool isNumber(const std::string& s) {
if (s.empty())
return false;
if (s.empty()) return false;
size_t start = (s[0] == '-') ? 1 : 0;
for (size_t i = start; i < s.length(); i++) {
if (!isdigit(s[i]))
return false;
if (!isdigit(s[i])) return false;
}
return start < s.length();
}
@ -91,45 +85,34 @@ private:
public:
ThemeContext(const ThemeContext* parent = nullptr) : parent(parent) {}
void setString(const std::string &key, const std::string &value) {
strings[key] = value;
}
void setString(const std::string& key, const std::string& 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; }
std::string getString(const std::string &key,
const std::string &defaultValue = "") const {
std::string getString(const std::string& key, const std::string& defaultValue = "") const {
auto it = strings.find(key);
if (it != strings.end())
return it->second;
if (parent)
return parent->getString(key, defaultValue);
if (it != strings.end()) return it->second;
if (parent) return parent->getString(key, defaultValue);
return defaultValue;
}
int getInt(const std::string& key, int defaultValue = 0) const {
auto it = ints.find(key);
if (it != ints.end())
return it->second;
if (parent)
return parent->getInt(key, defaultValue);
if (it != ints.end()) return it->second;
if (parent) return parent->getInt(key, defaultValue);
return defaultValue;
}
bool getBool(const std::string& key, bool defaultValue = false) const {
auto it = bools.find(key);
if (it != bools.end())
return it->second;
if (parent)
return parent->getBool(key, defaultValue);
if (it != bools.end()) return it->second;
if (parent) return parent->getBool(key, defaultValue);
return defaultValue;
}
bool hasKey(const std::string& key) const {
if (strings.count(key) || ints.count(key) || bools.count(key))
return true;
if (parent)
return parent->hasKey(key);
if (strings.count(key) || ints.count(key) || bools.count(key)) return true;
if (parent) return parent->hasKey(key);
return false;
}
@ -137,22 +120,18 @@ public:
std::string getAnyAsString(const std::string& key) const {
// Check strings first
auto sit = strings.find(key);
if (sit != strings.end())
return sit->second;
if (sit != strings.end()) return sit->second;
// Check ints
auto iit = ints.find(key);
if (iit != ints.end())
return std::to_string(iit->second);
if (iit != ints.end()) return std::to_string(iit->second);
// Check bools
auto bit = bools.find(key);
if (bit != bools.end())
return bit->second ? "true" : "false";
if (bit != bools.end()) return bit->second ? "true" : "false";
// Check parent
if (parent)
return parent->getAnyAsString(key);
if (parent) return parent->getAnyAsString(key);
return "";
}
@ -161,14 +140,11 @@ public:
// Supports: !, &&, ||, ==, !=, <, >, <=, >=, parentheses
bool evaluateBool(const std::string& expression) const {
std::string expr = trim(expression);
if (expr.empty())
return false;
if (expr.empty()) return false;
// Handle literal true/false
if (expr == "true" || expr == "1")
return true;
if (expr == "false" || expr == "0")
return false;
if (expr == "true" || expr == "1") return true;
if (expr == "false" || expr == "0") return false;
// Handle {var} wrapper
if (expr.size() > 2 && expr.front() == '{' && expr.back() == '}') {
@ -185,10 +161,8 @@ public:
int depth = 1;
size_t closePos = 1;
while (closePos < expr.length() && depth > 0) {
if (expr[closePos] == '(')
depth++;
if (expr[closePos] == ')')
depth--;
if (expr[closePos] == '(') depth++;
if (expr[closePos] == ')') depth--;
closePos++;
}
if (closePos <= expr.length()) {
@ -212,14 +186,11 @@ public:
size_t orPos = expr.find("||");
// Process || first (lower precedence than &&)
if (orPos != std::string::npos &&
(andPos == std::string::npos || orPos < andPos)) {
return evaluateBool(expr.substr(0, orPos)) ||
evaluateBool(expr.substr(orPos + 2));
if (orPos != std::string::npos && (andPos == std::string::npos || orPos < andPos)) {
return evaluateBool(expr.substr(0, orPos)) || evaluateBool(expr.substr(orPos + 2));
}
if (andPos != std::string::npos) {
return evaluateBool(expr.substr(0, andPos)) &&
evaluateBool(expr.substr(andPos + 2));
return evaluateBool(expr.substr(0, andPos)) && evaluateBool(expr.substr(andPos + 2));
}
// Handle comparisons
@ -298,8 +269,7 @@ public:
}
// If it's a number, return as-is
if (isNumber(v))
return v;
if (isNumber(v)) return v;
// Check for hex color literals (0x00, 0xFF, etc.)
if (v.size() > 2 && v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) {
@ -327,8 +297,7 @@ public:
// Evaluate a string expression with variable substitution
std::string evaluatestring(const Expression& expr) const {
if (expr.empty())
return "";
if (expr.empty()) return "";
std::string result;
for (const auto& token : expr.tokens) {
@ -339,10 +308,8 @@ public:
std::string varName = token.value;
// If the variable contains comparison operators, evaluate as condition
if (varName.find("==") != std::string::npos ||
varName.find("!=") != std::string::npos ||
varName.find("&&") != std::string::npos ||
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) {
result += evaluateBool(varName) ? "true" : "false";
continue;
}
@ -372,8 +339,7 @@ public:
// Legacy method for backward compatibility
std::string evaluateString(const std::string& expression) const {
if (expression.empty())
return "";
if (expression.empty()) return "";
Expression expr = Expression::parse(expression);
return evaluatestring(expr);
}

View File

@ -1,12 +1,14 @@
#pragma once
#include <GfxRenderer.h>
#include <map>
#include <string>
#include <vector>
#include "BasicElements.h"
#include "IniParser.h"
#include "ThemeContext.h"
#include <GfxRenderer.h>
#include <map>
#include <string>
#include <vector>
namespace ThemeEngine {
@ -50,8 +52,7 @@ private:
// Factory and property methods
UIElement* createElement(const std::string& id, const std::string& type);
void applyProperties(UIElement *elem,
const std::map<std::string, std::string> &props);
void applyProperties(UIElement* elem, const std::map<std::string, std::string>& props);
public:
static ThemeManager& get() {
@ -76,13 +77,10 @@ public:
int getNavBookCount() const { return navBookCount; }
// Render a screen
void renderScreen(const std::string &screenName, const GfxRenderer &renderer,
const ThemeContext &context);
void renderScreen(const std::string& screenName, const GfxRenderer& renderer, const ThemeContext& context);
// Render with dirty tracking (only redraws changed regions)
void renderScreenOptimized(const std::string &screenName,
const GfxRenderer &renderer,
const ThemeContext &context,
void renderScreenOptimized(const std::string& screenName, const GfxRenderer& renderer, const ThemeContext& context,
const ThemeContext* prevContext = nullptr);
// Invalidate all caches (call when theme changes or screen switches)
@ -100,12 +98,9 @@ public:
// Asset caching
const std::vector<uint8_t>* getCachedAsset(const std::string& path);
const ProcessedAsset *getProcessedAsset(const std::string &path,
GfxRenderer::Orientation orientation,
int targetW = 0, int targetH = 0);
void cacheProcessedAsset(const std::string &path,
const ProcessedAsset &asset,
const ProcessedAsset* getProcessedAsset(const std::string& path, GfxRenderer::Orientation orientation,
int targetW = 0, int targetH = 0);
void cacheProcessedAsset(const std::string& path, const ProcessedAsset& asset, int targetW = 0, int targetH = 0);
// Clear asset caches (for memory management)
void clearAssetCaches();
@ -121,8 +116,7 @@ private:
std::map<std::string, ProcessedAsset> processedCache;
// Compute a simple hash of context data for cache invalidation
uint32_t computeContextHash(const ThemeContext &context,
const std::string &screenName);
uint32_t computeContextHash(const ThemeContext& context, const std::string& screenName);
};
} // namespace ThemeEngine

View File

@ -15,12 +15,10 @@ struct Dimension {
Dimension() : value(0), unit(DimensionUnit::PIXELS) {}
static Dimension parse(const std::string& str) {
if (str.empty())
return Dimension(0, DimensionUnit::PIXELS);
if (str.empty()) return Dimension(0, DimensionUnit::PIXELS);
if (str.back() == '%') {
return Dimension(std::stoi(str.substr(0, str.length() - 1)),
DimensionUnit::PERCENT);
return Dimension(std::stoi(str.substr(0, str.length() - 1)), DimensionUnit::PERCENT);
}
return Dimension(std::stoi(str), DimensionUnit::PIXELS);
}
@ -40,14 +38,10 @@ struct Color {
Color() : value(0) {}
static Color parse(const std::string& str) {
if (str.empty())
return Color(0);
if (str == "black")
return Color(0x00);
if (str == "white")
return Color(0xFF);
if (str == "gray" || str == "grey")
return Color(0x80);
if (str.empty()) return Color(0);
if (str == "black") return Color(0x00);
if (str == "white") return Color(0xFF);
if (str == "gray" || str == "grey") return Color(0x80);
if (str.size() > 2 && str.substr(0, 2) == "0x") {
return Color((uint8_t)std::strtol(str.c_str(), nullptr, 16));
}
@ -65,15 +59,12 @@ struct Rect {
bool isEmpty() const { return w <= 0 || h <= 0; }
bool intersects(const Rect& other) const {
return !(x + w <= other.x || other.x + other.w <= x || y + h <= other.y ||
other.y + other.h <= y);
return !(x + w <= other.x || other.x + other.w <= x || y + h <= other.y || other.y + other.h <= y);
}
Rect unite(const Rect& other) const {
if (isEmpty())
return other;
if (other.isEmpty())
return *this;
if (isEmpty()) return other;
if (other.isEmpty()) return *this;
int nx = std::min(x, other.x);
int ny = std::min(y, other.y);
int nx2 = std::max(x + w, other.x + other.w);

View File

@ -1,10 +1,12 @@
#pragma once
#include <GfxRenderer.h>
#include <string>
#include <vector>
#include "ThemeContext.h"
#include "ThemeTypes.h"
#include <GfxRenderer.h>
#include <string>
#include <vector>
namespace ThemeEngine {
@ -38,15 +40,12 @@ protected:
bool dirty = true; // Needs redraw
bool isVisible(const ThemeContext& context) const {
if (visibleExpr.empty())
return true;
if (visibleExpr.empty()) return true;
return context.evaluateBool(visibleExpr.rawExpr);
}
public:
UIElement(const std::string &id) : id(id) {
visibleExpr = Expression::parse("true");
}
UIElement(const std::string& id) : id(id) { visibleExpr = Expression::parse("true"); }
virtual ~UIElement() {
if (cachedRender) {
@ -74,8 +73,8 @@ public:
void setVisibleExpr(const std::string& expr) {
visibleExpr = Expression::parse(expr);
// Check if expression contains variables
visibleExprIsStatic = (expr == "true" || expr == "false" || expr == "1" ||
expr == "0" || expr.find('{') == std::string::npos);
visibleExprIsStatic =
(expr == "true" || expr == "false" || expr == "1" || expr == "0" || expr.find('{') == std::string::npos);
markDirty();
}
@ -97,31 +96,24 @@ public:
}
// Calculate absolute position based on parent
virtual void layout(const ThemeContext &context, int parentX, int parentY,
int parentW, int parentH) {
virtual void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) {
int newX = parentX + x.resolve(parentW);
int newY = parentY + y.resolve(parentH);
int newW = width.resolve(parentW);
int newH = height.resolve(parentH);
// Clamp to parent bounds
if (newX >= parentX + parentW)
newX = parentX + parentW - 1;
if (newY >= parentY + parentH)
newY = parentY + parentH - 1;
if (newX >= parentX + parentW) newX = parentX + parentW - 1;
if (newY >= parentY + parentH) newY = parentY + parentH - 1;
int maxX = parentX + parentW;
int maxY = parentY + parentH;
if (newX + newW > maxX)
newW = maxX - newX;
if (newY + newH > maxY)
newH = maxY - newY;
if (newX + newW > maxX) newW = maxX - newX;
if (newY + newH > maxY) newH = maxY - newY;
if (newW < 0)
newW = 0;
if (newH < 0)
newH = 0;
if (newW < 0) newW = 0;
if (newH < 0) newH = 0;
// Check if position changed
if (newX != absX || newY != absY || newW != absW || newH != absH) {
@ -166,8 +158,7 @@ public:
Rect getBounds() const { return Rect(absX, absY, absW, absH); }
// Main draw method - handles caching automatically
virtual void draw(const GfxRenderer &renderer,
const ThemeContext &context) = 0;
virtual void draw(const GfxRenderer& renderer, const ThemeContext& context) = 0;
protected:
// Cache the rendered output
@ -191,11 +182,8 @@ protected:
// Restore from cache
bool restoreFromCache(const GfxRenderer& renderer) const {
if (!cacheValid || !cachedRender)
return false;
if (absX != cachedX || absY != cachedY || absW != cachedW ||
absH != cachedH)
return false;
if (!cacheValid || !cachedRender) return false;
if (absX != cachedX || absY != cachedY || absW != cachedW || absH != cachedH) return false;
renderer.restoreRegion(cachedRender, absX, absY, absW, absH);
return true;

View File

@ -1,4 +1,5 @@
#include "BasicElements.h"
#include "Bitmap.h"
#include "ListElement.h"
#include "ThemeManager.h"
@ -7,8 +8,7 @@
namespace ThemeEngine {
// --- BitmapElement ---
void BitmapElement::draw(const GfxRenderer &renderer,
const ThemeContext &context) {
void BitmapElement::draw(const GfxRenderer& renderer, const ThemeContext& context) {
if (!isVisible(context)) {
markClean();
return;
@ -21,8 +21,7 @@ void BitmapElement::draw(const GfxRenderer &renderer,
}
// Check if we have a cached 1-bit render of this bitmap at this size
const ProcessedAsset *processed =
ThemeManager::get().getProcessedAsset(path, renderer.getOrientation(), absW, absH);
const ProcessedAsset* processed = ThemeManager::get().getProcessedAsset(path, renderer.getOrientation(), absW, absH);
if (processed && processed->w == absW && processed->h == absH) {
// Draw cached 1-bit data directly
const int rowBytes = (absW + 7) / 8;
@ -148,8 +147,7 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
currentX += itemW + spacing;
continue;
}
if (currentX > absX + absW)
break;
if (currentX > absX + absW) break;
} else {
// Grid mode
if (currentY + itemH < absY) {
@ -162,8 +160,7 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
currentX = absX + col * (itemW + spacing);
continue;
}
if (currentY > absY + absH)
break;
if (currentY > absY + absH) break;
}
// Layout and draw

View File

@ -1,11 +1,11 @@
#include "IniParser.h"
#include <sstream>
namespace ThemeEngine {
void IniParser::trim(std::string& s) {
if (s.empty())
return;
if (s.empty()) return;
// Trim left
size_t first = s.find_first_not_of(" \t\n\r");
@ -19,8 +19,7 @@ void IniParser::trim(std::string &s) {
s = s.substr(first, (last - first + 1));
}
std::map<std::string, std::map<std::string, std::string>>
IniParser::parse(Stream &stream) {
std::map<std::string, std::map<std::string, std::string>> IniParser::parse(Stream& stream) {
std::map<std::string, std::map<std::string, std::string>> sections;
// stream check not strictly possible like file, can rely on available()
@ -62,8 +61,7 @@ IniParser::parse(Stream &stream) {
return sections;
}
std::map<std::string, std::map<std::string, std::string>>
IniParser::parseString(const std::string &content) {
std::map<std::string, std::map<std::string, std::string>> IniParser::parseString(const std::string& content) {
std::map<std::string, std::map<std::string, std::string>> sections;
std::stringstream ss(content);
std::string line;

View File

@ -1,7 +1,9 @@
#include "LayoutElements.h"
#include "ThemeManager.h"
#include <Bitmap.h>
#include "ThemeManager.h"
namespace ThemeEngine {
// Built-in icon drawing
@ -29,8 +31,7 @@ void Icon::draw(const GfxRenderer &renderer, const ThemeContext &context) {
int cy = absY + h / 2;
// Check if it's a path to a BMP file
if (iconName.find('/') != std::string::npos ||
iconName.find('.') != std::string::npos) {
if (iconName.find('/') != std::string::npos || iconName.find('.') != std::string::npos) {
// Try to load as bitmap
std::string path = iconName;
if (path[0] != '/') {

View File

@ -1,77 +1,58 @@
#include "ThemeManager.h"
#include "DefaultTheme.h"
#include "LayoutElements.h"
#include "ListElement.h"
#include <SDCardManager.h>
#include <algorithm>
#include <map>
#include <vector>
#include "DefaultTheme.h"
#include "LayoutElements.h"
#include "ListElement.h"
namespace ThemeEngine {
void ThemeManager::begin() {
// Default fonts or setup
}
void ThemeManager::registerFont(const std::string &name, int id) {
fontMap[name] = id;
}
void ThemeManager::registerFont(const std::string& name, int id) { fontMap[name] = id; }
std::string ThemeManager::getAssetPath(const std::string& assetName) {
// Check if absolute path
if (!assetName.empty() && assetName[0] == '/')
return assetName;
if (!assetName.empty() && assetName[0] == '/') return assetName;
// Otherwise relative to theme assets
return "/themes/" + currentThemeName + "/assets/" + assetName;
}
UIElement *ThemeManager::createElement(const std::string &id,
const std::string &type) {
UIElement* ThemeManager::createElement(const std::string& id, const std::string& type) {
// Basic elements
if (type == "Container")
return new Container(id);
if (type == "Rectangle")
return new Rectangle(id);
if (type == "Label")
return new Label(id);
if (type == "Bitmap")
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);
if (type == "Container") return new Container(id);
if (type == "Rectangle") return new Rectangle(id);
if (type == "Label") return new Label(id);
if (type == "Bitmap") 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
if (type == "HStack")
return new HStack(id);
if (type == "VStack")
return new VStack(id);
if (type == "Grid")
return new Grid(id);
if (type == "HStack") return new HStack(id);
if (type == "VStack") return new VStack(id);
if (type == "Grid") return new Grid(id);
// Advanced elements
if (type == "Badge")
return new Badge(id);
if (type == "Toggle")
return new Toggle(id);
if (type == "TabBar")
return new TabBar(id);
if (type == "Icon")
return new Icon(id);
if (type == "ScrollIndicator")
return new ScrollIndicator(id);
if (type == "BatteryIcon")
return new BatteryIcon(id);
if (type == "Badge") return new Badge(id);
if (type == "Toggle") return new Toggle(id);
if (type == "TabBar") return new TabBar(id);
if (type == "Icon") return new Icon(id);
if (type == "ScrollIndicator") return new ScrollIndicator(id);
if (type == "BatteryIcon") return new BatteryIcon(id);
return nullptr;
}
void ThemeManager::applyProperties(
UIElement *elem, const std::map<std::string, std::string> &props) {
void ThemeManager::applyProperties(UIElement* elem, const std::map<std::string, std::string>& props) {
const auto elemType = elem->getType();
for (const auto& kv : props) {
@ -105,10 +86,8 @@ void ThemeManager::applyProperties(
} else if (key == "Color") {
if (elemType == UIElement::ElementType::Rectangle) {
static_cast<Rectangle*>(elem)->setColorExpr(val);
} else if (elemType == UIElement::ElementType::Container ||
elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack ||
elemType == UIElement::ElementType::Grid ||
} else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid ||
elemType == UIElement::ElementType::TabBar) {
static_cast<Container*>(elem)->setBackgroundColorExpr(val);
} else if (elemType == UIElement::ElementType::Label) {
@ -132,19 +111,15 @@ void ThemeManager::applyProperties(
}
}
} else if (key == "Padding") {
if (elemType == UIElement::ElementType::Container ||
elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack ||
elemType == UIElement::ElementType::Grid) {
if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
static_cast<Container*>(elem)->setPadding(std::stoi(val));
} else if (elemType == UIElement::ElementType::TabBar) {
static_cast<TabBar*>(elem)->setPadding(std::stoi(val));
}
} else if (key == "BorderRadius") {
if (elemType == UIElement::ElementType::Container ||
elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack ||
elemType == UIElement::ElementType::Grid) {
if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
static_cast<Container*>(elem)->setBorderRadius(std::stoi(val));
}
} else if (key == "Spacing") {
@ -189,10 +164,8 @@ void ThemeManager::applyProperties(
} else if (key == "Align") {
if (elemType == UIElement::ElementType::Label) {
Label::Alignment align = Label::Alignment::Left;
if (val == "Center" || val == "center")
align = Label::Alignment::Center;
if (val == "Right" || val == "right")
align = Label::Alignment::Right;
if (val == "Center" || val == "center") align = Label::Alignment::Center;
if (val == "Right" || val == "right") align = Label::Alignment::Right;
static_cast<Label*>(elem)->setAlignment(align);
}
} else if (key == "MaxLines") {
@ -209,8 +182,7 @@ void ThemeManager::applyProperties(
else if (key == "Src") {
if (elemType == UIElement::ElementType::Bitmap) {
auto b = static_cast<BitmapElement*>(elem);
if (val.find('{') == std::string::npos &&
val.find('/') == std::string::npos) {
if (val.find('{') == std::string::npos && val.find('/') == std::string::npos) {
b->setSrc(getAssetPath(val));
} else {
b->setSrc(val);
@ -220,13 +192,11 @@ void ThemeManager::applyProperties(
}
} else if (key == "ScaleToFit") {
if (elemType == UIElement::ElementType::Bitmap) {
static_cast<BitmapElement *>(elem)->setScaleToFit(val == "true" ||
val == "1");
static_cast<BitmapElement*>(elem)->setScaleToFit(val == "true" || val == "1");
}
} else if (key == "PreserveAspect") {
if (elemType == UIElement::ElementType::Bitmap) {
static_cast<BitmapElement *>(elem)->setPreserveAspect(val == "true" ||
val == "1");
static_cast<BitmapElement*>(elem)->setPreserveAspect(val == "true" || val == "1");
}
} else if (key == "IconSize") {
if (elemType == UIElement::ElementType::Icon) {
@ -295,16 +265,13 @@ void ThemeManager::applyProperties(
static_cast<ProgressBar*>(elem)->setBgColor(val);
} else if (elemType == UIElement::ElementType::Badge) {
static_cast<Badge*>(elem)->setBgColor(val);
} else if (elemType == UIElement::ElementType::Container ||
elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack ||
elemType == UIElement::ElementType::Grid) {
} else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
static_cast<Container*>(elem)->setBackgroundColorExpr(val);
}
} else if (key == "ShowBorder") {
if (elemType == UIElement::ElementType::ProgressBar) {
static_cast<ProgressBar *>(elem)->setShowBorder(val == "true" ||
val == "1");
static_cast<ProgressBar*>(elem)->setShowBorder(val == "true" || val == "1");
}
}
@ -391,14 +358,12 @@ void ThemeManager::applyProperties(
}
}
const std::vector<uint8_t> *
ThemeManager::getCachedAsset(const std::string &path) {
const std::vector<uint8_t>* ThemeManager::getCachedAsset(const std::string& path) {
if (assetCache.count(path)) {
return &assetCache.at(path);
}
if (!SdMan.exists(path.c_str()))
return nullptr;
if (!SdMan.exists(path.c_str())) return nullptr;
FsFile file;
if (SdMan.openFileForRead("ThemeCache", path, file)) {
@ -412,9 +377,7 @@ ThemeManager::getCachedAsset(const std::string &path) {
return nullptr;
}
const ProcessedAsset *
ThemeManager::getProcessedAsset(const std::string &path,
GfxRenderer::Orientation orientation,
const ProcessedAsset* ThemeManager::getProcessedAsset(const std::string& path, GfxRenderer::Orientation orientation,
int targetW, int targetH) {
// Include dimensions in cache key for scaled images
std::string cacheKey = path;
@ -431,9 +394,7 @@ ThemeManager::getProcessedAsset(const std::string &path,
return nullptr;
}
void ThemeManager::cacheProcessedAsset(const std::string &path,
const ProcessedAsset &asset,
int targetW, int targetH) {
void ThemeManager::cacheProcessedAsset(const std::string& path, const ProcessedAsset& asset, int targetW, int targetH) {
std::string cacheKey = path;
if (targetW > 0 && targetH > 0) {
cacheKey += ":" + std::to_string(targetW) + "x" + std::to_string(targetH);
@ -467,8 +428,7 @@ void ThemeManager::invalidateScreenCache(const std::string &screenName) {
}
}
uint32_t ThemeManager::computeContextHash(const ThemeContext &context,
const std::string &screenName) {
uint32_t ThemeManager::computeContextHash(const ThemeContext& context, const std::string& screenName) {
uint32_t hash = 2166136261u;
for (char c : screenName) {
hash ^= static_cast<uint32_t>(c);
@ -511,8 +471,7 @@ void ThemeManager::loadTheme(const std::string &themeName) {
Serial.printf("[ThemeManager] Parsing theme file...\n");
sections = IniParser::parse(file);
file.close();
Serial.printf("[ThemeManager] Parsed %d sections from %s\n",
(int)sections.size(), themeName.c_str());
Serial.printf("[ThemeManager] Parsed %d sections from %s\n", (int)sections.size(), themeName.c_str());
} else {
Serial.printf("[ThemeManager] Failed to open %s, using Default\n", path.c_str());
sections = IniParser::parseString(getDefaultThemeIni());
@ -537,15 +496,12 @@ void ThemeManager::loadTheme(const std::string &themeName) {
std::string id = sec.first;
const std::map<std::string, std::string>& props = sec.second;
if (id == "Global")
continue;
if (id == "Global") continue;
auto it = props.find("Type");
if (it == props.end())
continue;
if (it == props.end()) continue;
std::string type = it->second;
if (type.empty())
continue;
if (type.empty()) continue;
UIElement* elem = createElement(id, type);
if (elem) {
@ -557,10 +513,8 @@ void ThemeManager::loadTheme(const std::string &themeName) {
std::vector<List*> lists;
for (const auto& sec : sections) {
std::string id = sec.first;
if (id == "Global")
continue;
if (elements.find(id) == elements.end())
continue;
if (id == "Global") continue;
if (elements.find(id) == elements.end()) continue;
UIElement* elem = elements[id];
applyProperties(elem, sec.second);
@ -577,8 +531,7 @@ void ThemeManager::loadTheme(const std::string &themeName) {
c->addChild(elem);
}
} else {
Serial.printf("[ThemeManager] WARN: Parent %s not found for %s\n",
parentId.c_str(), id.c_str());
Serial.printf("[ThemeManager] WARN: Parent %s not found for %s\n", parentId.c_str(), id.c_str());
}
}
}
@ -588,12 +541,10 @@ void ThemeManager::loadTheme(const std::string &themeName) {
l->resolveTemplate(elements);
}
Serial.printf("[ThemeManager] Theme loaded with %d elements\n",
(int)elements.size());
Serial.printf("[ThemeManager] Theme loaded with %d elements\n", (int)elements.size());
}
void ThemeManager::renderScreen(const std::string &screenName,
const GfxRenderer &renderer,
void ThemeManager::renderScreen(const std::string& screenName, const GfxRenderer& renderer,
const ThemeContext& context) {
if (elements.count(screenName) == 0) {
Serial.printf("[ThemeManager] Screen '%s' not found\n", screenName.c_str());
@ -602,16 +553,13 @@ void ThemeManager::renderScreen(const std::string &screenName,
UIElement* root = elements[screenName];
root->layout(context, 0, 0, renderer.getScreenWidth(),
renderer.getScreenHeight());
root->layout(context, 0, 0, renderer.getScreenWidth(), renderer.getScreenHeight());
root->draw(renderer, context);
}
void ThemeManager::renderScreenOptimized(const std::string &screenName,
const GfxRenderer &renderer,
const ThemeContext &context,
const ThemeContext *prevContext) {
void ThemeManager::renderScreenOptimized(const std::string& screenName, const GfxRenderer& renderer,
const ThemeContext& context, const ThemeContext* prevContext) {
renderScreen(screenName, renderer, context);
}

View File

@ -65,8 +65,7 @@ bool CrossPointSettings::loadFromFile() {
uint8_t version;
serialization::readPod(inputFile, version);
if (version != SETTINGS_FILE_VERSION) {
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n",
millis(), version);
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
inputFile.close();
return false;
}
@ -78,78 +77,57 @@ bool CrossPointSettings::loadFromFile() {
uint8_t settingsRead = 0;
do {
serialization::readPod(inputFile, sleepScreen);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, extraParagraphSpacing);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, shortPwrBtn);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, statusBar);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, orientation);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, frontButtonLayout);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, sideButtonLayout);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, fontFamily);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, fontSize);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, lineSpacing);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, paragraphAlignment);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, sleepTimeout);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, refreshFrequency);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, screenMargin);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, sleepScreenCoverMode);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
{
std::string urlStr;
serialization::readString(inputFile, urlStr);
strncpy(opdsServerUrl, urlStr.c_str(), sizeof(opdsServerUrl) - 1);
opdsServerUrl[sizeof(opdsServerUrl) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, textAntiAliasing);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, hideBatteryPercentage);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, longPressChapterSkip);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, hyphenationEnabled);
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
{
std::string themeStr;
serialization::readString(inputFile, themeStr);
strncpy(themeName, themeStr.c_str(), sizeof(themeName) - 1);
themeName[sizeof(themeName) - 1] = '\0';
}
if (++settingsRead >= fileSettingsCount)
break;
if (++settingsRead >= fileSettingsCount) break;
} while (false);
inputFile.close();

View File

@ -16,13 +16,7 @@ public:
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
// Should match with SettingsActivity text
enum SLEEP_SCREEN_MODE {
DARK = 0,
LIGHT = 1,
CUSTOM = 2,
COVER = 3,
BLANK = 4
};
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 };
// Status bar display type enum
@ -30,8 +24,7 @@ public:
enum ORIENTATION {
PORTRAIT = 0, // 480x800 logical coordinates (current default)
LANDSCAPE_CW =
1, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
INVERTED = 2, // 480x800 logical coordinates, inverted
LANDSCAPE_CCW = 3 // 800x480 logical coordinates, native panel orientation
};
@ -39,11 +32,7 @@ public:
// Front button layout options
// Default: Back, Confirm, Left, Right
// Swapped: Left, Right, Back, Confirm
enum FRONT_BUTTON_LAYOUT {
BACK_CONFIRM_LEFT_RIGHT = 0,
LEFT_RIGHT_BACK_CONFIRM = 1,
LEFT_BACK_CONFIRM_RIGHT = 2
};
enum FRONT_BUTTON_LAYOUT { BACK_CONFIRM_LEFT_RIGHT = 0, LEFT_RIGHT_BACK_CONFIRM = 1, LEFT_BACK_CONFIRM_RIGHT = 2 };
// Side button layout options
// Default: Previous, Next
@ -55,40 +44,19 @@ public:
// Font size options
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3 };
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2 };
enum PARAGRAPH_ALIGNMENT {
JUSTIFIED = 0,
LEFT_ALIGN = 1,
CENTER_ALIGN = 2,
RIGHT_ALIGN = 3
};
enum PARAGRAPH_ALIGNMENT { JUSTIFIED = 0, LEFT_ALIGN = 1, CENTER_ALIGN = 2, RIGHT_ALIGN = 3 };
// Auto-sleep timeout options (in minutes)
enum SLEEP_TIMEOUT {
SLEEP_1_MIN = 0,
SLEEP_5_MIN = 1,
SLEEP_10_MIN = 2,
SLEEP_15_MIN = 3,
SLEEP_30_MIN = 4
};
enum SLEEP_TIMEOUT { 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)
enum REFRESH_FREQUENCY {
REFRESH_1 = 0,
REFRESH_5 = 1,
REFRESH_10 = 2,
REFRESH_15 = 3,
REFRESH_30 = 4
};
enum REFRESH_FREQUENCY { REFRESH_1 = 0, REFRESH_5 = 1, REFRESH_10 = 2, REFRESH_15 = 3, REFRESH_30 = 4 };
// Short power button press actions
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2 };
// Hide battery percentage
enum HIDE_BATTERY_PERCENTAGE {
HIDE_NEVER = 0,
HIDE_READER = 1,
HIDE_ALWAYS = 2
};
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
// Sleep screen settings
uint8_t sleepScreen = DARK;

View File

@ -4,6 +4,7 @@
#include <Epub.h>
#include <GfxRenderer.h>
#include <SDCardManager.h>
#include <ThemeManager.h>
#include <Xtc.h>
#include <cstring>
@ -17,7 +18,6 @@
#include "ScreenComponents.h"
#include "fontIds.h"
#include "util/StringUtils.h"
#include <ThemeManager.h>
void HomeActivity::taskTrampoline(void* param) {
auto* self = static_cast<HomeActivity*>(param);
@ -26,8 +26,7 @@ void HomeActivity::taskTrampoline(void *param) {
int HomeActivity::getMenuItemCount() const {
int count = 3; // Browse Files, File Transfer, Settings
if (hasOpdsUrl)
count++; // + Calibre Library
if (hasOpdsUrl) count++; // + Calibre Library
return count;
}
@ -43,8 +42,7 @@ void HomeActivity::onEnter() {
selectorIndex = 0; // Start at first item (first book if any, else first menu)
// Check if we have a book to continue reading
hasContinueReading = !APP_STATE.openEpubPath.empty() &&
SdMan.exists(APP_STATE.openEpubPath.c_str());
hasContinueReading = !APP_STATE.openEpubPath.empty() && SdMan.exists(APP_STATE.openEpubPath.c_str());
// Check if OPDS browser URL is configured
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
@ -187,8 +185,8 @@ void HomeActivity::loadRecentBooksData() {
}
}
Serial.printf("[HOME] Book %d: title='%s', cover='%s', progress=%d%%\n",
i, info.title.c_str(), info.coverPath.c_str(), info.progressPercent);
Serial.printf("[HOME] Book %d: title='%s', cover='%s', progress=%d%%\n", i, info.title.c_str(),
info.coverPath.c_str(), info.progressPercent);
cachedRecentBooks.push_back(info);
}
@ -255,11 +253,9 @@ void HomeActivity::freeCoverBuffer() {
}
void HomeActivity::loop() {
const bool prevPressed =
mappedInput.wasPressed(MappedInputManager::Button::Up) ||
const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
mappedInput.wasPressed(MappedInputManager::Button::Left);
const bool nextPressed =
mappedInput.wasPressed(MappedInputManager::Button::Down) ||
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
mappedInput.wasPressed(MappedInputManager::Button::Right);
const bool confirmPressed = mappedInput.wasReleased(MappedInputManager::Button::Confirm);
@ -321,8 +317,7 @@ void HomeActivity::displayTaskLoop() {
void HomeActivity::render() {
// Battery check logic (only update every 60 seconds)
const uint32_t now = millis();
const bool needBatteryUpdate =
(now - lastBatteryCheck > 60000) || (lastBatteryCheck == 0);
const bool needBatteryUpdate = (now - lastBatteryCheck > 60000) || (lastBatteryCheck == 0);
if (needBatteryUpdate) {
cachedBatteryLevel = battery.readPercentage();
lastBatteryCheck = now;
@ -336,8 +331,7 @@ void HomeActivity::render() {
// --- Bind Global Data ---
context.setString("BatteryPercent", std::to_string(cachedBatteryLevel));
context.setBool("ShowBatteryPercent",
SETTINGS.hideBatteryPercentage !=
CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS);
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS);
// --- Navigation counts (must match loop()) ---
const int recentCount = static_cast<int>(cachedRecentBooks.size());
@ -367,8 +361,7 @@ void HomeActivity::render() {
context.setString("BookTitle", lastBookTitle);
context.setString("BookAuthor", lastBookAuthor);
context.setString("BookCoverPath", coverBmpPath);
context.setBool("HasCover",
hasContinueReading && hasCoverImage && !coverBmpPath.empty());
context.setBool("HasCover", hasContinueReading && hasCoverImage && !coverBmpPath.empty());
context.setBool("ShowInfoBox", true);
// --- Main Menu Data ---

View File

@ -53,14 +53,14 @@ class HomeActivity final : public Activity {
public:
explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()> &onContinueReading,
const std::function<void()> &onMyLibraryOpen,
const std::function<void()> &onSettingsOpen,
const std::function<void()> &onFileTransferOpen,
const std::function<void()>& onContinueReading, const std::function<void()>& onMyLibraryOpen,
const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen,
const std::function<void()>& onOpdsBrowserOpen)
: Activity("Home", renderer, mappedInput),
onContinueReading(onContinueReading), onMyLibraryOpen(onMyLibraryOpen),
onSettingsOpen(onSettingsOpen), onFileTransferOpen(onFileTransferOpen),
onContinueReading(onContinueReading),
onMyLibraryOpen(onMyLibraryOpen),
onSettingsOpen(onSettingsOpen),
onFileTransferOpen(onFileTransferOpen),
onOpdsBrowserOpen(onOpdsBrowserOpen) {}
void onEnter() override;
void onExit() override;

View File

@ -30,8 +30,7 @@ void CategorySettingsActivity::onEnter() {
selectedSettingIndex = 0;
updateRequired = true;
xTaskCreate(&CategorySettingsActivity::taskTrampoline,
"CategorySettingsActivityTask", 4096, this, 1,
xTaskCreate(&CategorySettingsActivity::taskTrampoline, "CategorySettingsActivityTask", 4096, this, 1,
&displayTaskHandle);
}
@ -71,15 +70,11 @@ void CategorySettingsActivity::loop() {
// Handle navigation
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
selectedSettingIndex = (selectedSettingIndex > 0)
? (selectedSettingIndex - 1)
: (settingsCount - 1);
selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1);
updateRequired = true;
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
selectedSettingIndex = (selectedSettingIndex < settingsCount - 1)
? (selectedSettingIndex + 1)
: 0;
selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0;
updateRequired = true;
}
}
@ -97,10 +92,8 @@ void CategorySettingsActivity::toggleCurrentSetting() {
SETTINGS.*(setting.valuePtr) = !currentValue;
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) =
(currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
} else if (setting.type == SettingType::VALUE &&
setting.valuePtr != nullptr) {
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
if (currentValue + setting.valueRange.step > setting.valueRange.max) {
SETTINGS.*(setting.valuePtr) = setting.valueRange.min;
@ -111,8 +104,7 @@ void CategorySettingsActivity::toggleCurrentSetting() {
if (strcmp(setting.name, "KOReader Sync") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
enterNewActivity(
new KOReaderSettingsActivity(renderer, mappedInput, [this] {
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
@ -120,8 +112,7 @@ void CategorySettingsActivity::toggleCurrentSetting() {
} else if (strcmp(setting.name, "Calibre Settings") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
enterNewActivity(
new CalibreSettingsActivity(renderer, mappedInput, [this] {
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
@ -145,8 +136,7 @@ void CategorySettingsActivity::toggleCurrentSetting() {
} else if (strcmp(setting.name, "Theme") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
enterNewActivity(
new ThemeSelectionActivity(renderer, mappedInput, [this] {
enterNewActivity(new ThemeSelectionActivity(renderer, mappedInput, [this] {
exitActivity();
updateRequired = true;
}));
@ -177,8 +167,7 @@ void CategorySettingsActivity::render() const {
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true,
EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD);
// Draw selection highlight
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
@ -189,39 +178,30 @@ void CategorySettingsActivity::render() const {
const bool isSelected = (i == selectedSettingIndex);
// Draw setting name
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name,
!isSelected);
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected);
// Draw value based on setting type
std::string valueText;
if (settingsList[i].type == SettingType::TOGGLE &&
settingsList[i].valuePtr != nullptr) {
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
const bool value = SETTINGS.*(settingsList[i].valuePtr);
valueText = value ? "ON" : "OFF";
} else if (settingsList[i].type == SettingType::ENUM &&
settingsList[i].valuePtr != nullptr) {
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
valueText = settingsList[i].enumValues[value];
} else if (settingsList[i].type == SettingType::VALUE &&
settingsList[i].valuePtr != nullptr) {
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
}
if (!valueText.empty()) {
const auto width =
renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY,
valueText.c_str(), !isSelected);
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected);
}
}
renderer.drawText(
SMALL_FONT_ID,
pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION);
const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
labels.btn4);
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}

View File

@ -8,71 +8,48 @@
#include "MappedInputManager.h"
#include "fontIds.h"
const char *SettingsActivity::categoryNames[categoryCount] = {
"Display", "Reader", "Controls", "System"};
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
namespace {
constexpr int displaySettingsCount = 6;
const SettingInfo displaySettings[displaySettingsCount] = {
// Should match with SLEEP_SCREEN_MODE
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen,
{"Dark", "Light", "Custom", "Cover", "None"}),
SettingInfo::Enum("Sleep Screen Cover Mode",
&CrossPointSettings::sleepScreenCoverMode,
{"Fit", "Crop"}),
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar,
{"None", "No Progress", "Full"}),
SettingInfo::Enum("Hide Battery %",
&CrossPointSettings::hideBatteryPercentage,
{"Never", "In Reader", "Always"}),
SettingInfo::Enum(
"Refresh Frequency", &CrossPointSettings::refreshFrequency,
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"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")};
constexpr int readerSettingsCount = 9;
const SettingInfo readerSettings[readerSettingsCount] = {
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily,
{"Bookerly", "Noto Sans", "Open Dyslexic"}),
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize,
{"Small", "Medium", "Large", "X Large"}),
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing,
{"Tight", "Normal", "Wide"}),
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin,
{5, 40, 5}),
SettingInfo::Enum("Paragraph Alignment",
&CrossPointSettings::paragraphAlignment,
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
{"Justify", "Left", "Center", "Right"}),
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
SettingInfo::Enum(
"Reading Orientation", &CrossPointSettings::orientation,
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
SettingInfo::Toggle("Extra Paragraph Spacing",
&CrossPointSettings::extraParagraphSpacing),
SettingInfo::Toggle("Text Anti-Aliasing",
&CrossPointSettings::textAntiAliasing)};
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)};
constexpr int controlsSettingsCount = 4;
const SettingInfo controlsSettings[controlsSettingsCount] = {
SettingInfo::Enum("Front Button Layout",
&CrossPointSettings::frontButtonLayout,
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm",
"Lft, Bck, Cnfrm, Rght"}),
SettingInfo::Enum("Side Button Layout (reader)",
&CrossPointSettings::sideButtonLayout,
SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout,
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}),
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
{"Prev, Next", "Next, Prev"}),
SettingInfo::Toggle("Long-press Chapter Skip",
&CrossPointSettings::longPressChapterSkip),
SettingInfo::Enum("Short Power Button Click",
&CrossPointSettings::shortPwrBtn,
{"Ignore", "Sleep", "Page Turn"})};
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})};
constexpr int systemSettingsCount = 5;
const SettingInfo systemSettings[systemSettingsCount] = {
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
SettingInfo::Action("KOReader Sync"),
SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
SettingInfo::Action("Check for updates")};
} // namespace
@ -135,16 +112,12 @@ void SettingsActivity::loop() {
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
// Move selection up (with wrap-around)
selectedCategoryIndex = (selectedCategoryIndex > 0)
? (selectedCategoryIndex - 1)
: (categoryCount - 1);
selectedCategoryIndex = (selectedCategoryIndex > 0) ? (selectedCategoryIndex - 1) : (categoryCount - 1);
updateRequired = true;
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
// Move selection down (with wrap around)
selectedCategoryIndex = (selectedCategoryIndex < categoryCount - 1)
? (selectedCategoryIndex + 1)
: 0;
selectedCategoryIndex = (selectedCategoryIndex < categoryCount - 1) ? (selectedCategoryIndex + 1) : 0;
updateRequired = true;
}
}
@ -179,8 +152,7 @@ void SettingsActivity::enterCategory(int categoryIndex) {
break;
}
enterNewActivity(new CategorySettingsActivity(
renderer, mappedInput, categoryNames[categoryIndex], settingsList,
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList,
settingsCount, [this] {
exitActivity();
updateRequired = true;
@ -207,8 +179,7 @@ void SettingsActivity::render() const {
const auto pageHeight = renderer.getScreenHeight();
// Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true,
EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
// Draw selection
renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30);
@ -218,20 +189,16 @@ void SettingsActivity::render() const {
const int categoryY = 60 + i * 30; // 30 pixels between categories
// Draw category name
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i],
i != selectedCategoryIndex);
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i], i != selectedCategoryIndex);
}
// Draw version text above button hints
renderer.drawText(
SMALL_FONT_ID,
pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION);
// Draw help text
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
labels.btn4);
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
// Always use standard refresh for settings screen
renderer.displayBuffer();

View File

@ -1,11 +1,14 @@
#include "ThemeSelectionActivity.h"
#include <GfxRenderer.h>
#include <SDCardManager.h>
#include <esp_system.h>
#include <cstring>
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "fontIds.h"
#include <GfxRenderer.h>
#include <SDCardManager.h>
#include <cstring>
#include <esp_system.h>
void ThemeSelectionActivity::taskTrampoline(void* param) {
auto* self = static_cast<ThemeSelectionActivity*>(param);
@ -49,8 +52,7 @@ void ThemeSelectionActivity::onEnter() {
}
updateRequired = true;
xTaskCreate(&ThemeSelectionActivity::taskTrampoline, "ThemeSelTask", 4096,
this, 1, &displayTaskHandle);
xTaskCreate(&ThemeSelectionActivity::taskTrampoline, "ThemeSelTask", 4096, this, 1, &displayTaskHandle);
}
void ThemeSelectionActivity::onExit() {
@ -72,17 +74,14 @@ void ThemeSelectionActivity::loop() {
// Only reboot if theme actually changed
if (selected != std::string(SETTINGS.themeName)) {
strncpy(SETTINGS.themeName, selected.c_str(),
sizeof(SETTINGS.themeName) - 1);
strncpy(SETTINGS.themeName, selected.c_str(), sizeof(SETTINGS.themeName) - 1);
SETTINGS.themeName[sizeof(SETTINGS.themeName) - 1] = '\0';
SETTINGS.saveToFile();
// Show reboot message
renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() / 2 - 20,
"Applying theme...", true);
renderer.drawCenteredText(UI_10_FONT_ID, renderer.getScreenHeight() / 2 + 10,
"Device will restart", true);
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() / 2 - 20, "Applying theme...", true);
renderer.drawCenteredText(UI_10_FONT_ID, renderer.getScreenHeight() / 2 + 10, "Device will restart", true);
renderer.displayBuffer();
// Small delay to ensure display updates
@ -103,13 +102,11 @@ void ThemeSelectionActivity::loop() {
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
selectedIndex =
(selectedIndex > 0) ? (selectedIndex - 1) : (themeNames.size() - 1);
selectedIndex = (selectedIndex > 0) ? (selectedIndex - 1) : (themeNames.size() - 1);
updateRequired = true;
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
selectedIndex =
(selectedIndex < themeNames.size() - 1) ? (selectedIndex + 1) : 0;
selectedIndex = (selectedIndex < themeNames.size() - 1) ? (selectedIndex + 1) : 0;
updateRequired = true;
}
}
@ -133,8 +130,7 @@ void ThemeSelectionActivity::render() const {
const auto pageHeight = renderer.getScreenHeight();
// Header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Theme", true,
EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Theme", true, EpdFontFamily::BOLD);
// Layout constants
const int entryHeight = 30;
@ -155,8 +151,7 @@ void ThemeSelectionActivity::render() const {
// Draw Highlight
int visibleIndex = selectedIndex - startIdx;
if (visibleIndex >= 0 && visibleIndex < maxVisible) {
renderer.fillRect(0, startY + visibleIndex * entryHeight - 2, pageWidth - 1,
entryHeight);
renderer.fillRect(0, startY + visibleIndex * entryHeight - 2, pageWidth - 1, entryHeight);
}
// Draw List
@ -176,15 +171,13 @@ void ThemeSelectionActivity::render() const {
if (themeNames.size() > maxVisible) {
int barHeight = pageHeight - startY - 40;
int thumbHeight = barHeight * maxVisible / themeNames.size();
int thumbY = startY + (barHeight - thumbHeight) * startIdx /
(themeNames.size() - maxVisible);
int thumbY = startY + (barHeight - thumbHeight) * startIdx / (themeNames.size() - maxVisible);
renderer.fillRect(pageWidth - 5, startY, 2, barHeight, 0);
renderer.fillRect(pageWidth - 7, thumbY, 6, thumbHeight, 1);
}
const auto labels = mappedInput.mapLabels("Cancel", "Select", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
labels.btn4);
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}

View File

@ -1,12 +1,14 @@
#pragma once
#include "activities/Activity.h"
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <functional>
#include <string>
#include <vector>
#include "activities/Activity.h"
class ThemeSelectionActivity final : public Activity {
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
@ -20,8 +22,7 @@ class ThemeSelectionActivity final : public Activity {
void render() const;
public:
ThemeSelectionActivity(GfxRenderer &renderer, MappedInputManager &mappedInput,
const std::function<void()> &onGoBack)
ThemeSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function<void()>& onGoBack)
: Activity("ThemeSelection", renderer, mappedInput), onGoBack(onGoBack) {}
void onEnter() override;
void onExit() override;

View File

@ -51,92 +51,76 @@ EpdFont bookerly14RegularFont(&bookerly_14_regular);
EpdFont bookerly14BoldFont(&bookerly_14_bold);
EpdFont bookerly14ItalicFont(&bookerly_14_italic);
EpdFont bookerly14BoldItalicFont(&bookerly_14_bolditalic);
EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont,
&bookerly14ItalicFont,
EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont, &bookerly14ItalicFont,
&bookerly14BoldItalicFont);
#ifndef OMIT_FONTS
EpdFont bookerly12RegularFont(&bookerly_12_regular);
EpdFont bookerly12BoldFont(&bookerly_12_bold);
EpdFont bookerly12ItalicFont(&bookerly_12_italic);
EpdFont bookerly12BoldItalicFont(&bookerly_12_bolditalic);
EpdFontFamily bookerly12FontFamily(&bookerly12RegularFont, &bookerly12BoldFont,
&bookerly12ItalicFont,
EpdFontFamily bookerly12FontFamily(&bookerly12RegularFont, &bookerly12BoldFont, &bookerly12ItalicFont,
&bookerly12BoldItalicFont);
EpdFont bookerly16RegularFont(&bookerly_16_regular);
EpdFont bookerly16BoldFont(&bookerly_16_bold);
EpdFont bookerly16ItalicFont(&bookerly_16_italic);
EpdFont bookerly16BoldItalicFont(&bookerly_16_bolditalic);
EpdFontFamily bookerly16FontFamily(&bookerly16RegularFont, &bookerly16BoldFont,
&bookerly16ItalicFont,
EpdFontFamily bookerly16FontFamily(&bookerly16RegularFont, &bookerly16BoldFont, &bookerly16ItalicFont,
&bookerly16BoldItalicFont);
EpdFont bookerly18RegularFont(&bookerly_18_regular);
EpdFont bookerly18BoldFont(&bookerly_18_bold);
EpdFont bookerly18ItalicFont(&bookerly_18_italic);
EpdFont bookerly18BoldItalicFont(&bookerly_18_bolditalic);
EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont,
&bookerly18ItalicFont,
EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont, &bookerly18ItalicFont,
&bookerly18BoldItalicFont);
EpdFont notosans12RegularFont(&notosans_12_regular);
EpdFont notosans12BoldFont(&notosans_12_bold);
EpdFont notosans12ItalicFont(&notosans_12_italic);
EpdFont notosans12BoldItalicFont(&notosans_12_bolditalic);
EpdFontFamily notosans12FontFamily(&notosans12RegularFont, &notosans12BoldFont,
&notosans12ItalicFont,
EpdFontFamily notosans12FontFamily(&notosans12RegularFont, &notosans12BoldFont, &notosans12ItalicFont,
&notosans12BoldItalicFont);
EpdFont notosans14RegularFont(&notosans_14_regular);
EpdFont notosans14BoldFont(&notosans_14_bold);
EpdFont notosans14ItalicFont(&notosans_14_italic);
EpdFont notosans14BoldItalicFont(&notosans_14_bolditalic);
EpdFontFamily notosans14FontFamily(&notosans14RegularFont, &notosans14BoldFont,
&notosans14ItalicFont,
EpdFontFamily notosans14FontFamily(&notosans14RegularFont, &notosans14BoldFont, &notosans14ItalicFont,
&notosans14BoldItalicFont);
EpdFont notosans16RegularFont(&notosans_16_regular);
EpdFont notosans16BoldFont(&notosans_16_bold);
EpdFont notosans16ItalicFont(&notosans_16_italic);
EpdFont notosans16BoldItalicFont(&notosans_16_bolditalic);
EpdFontFamily notosans16FontFamily(&notosans16RegularFont, &notosans16BoldFont,
&notosans16ItalicFont,
EpdFontFamily notosans16FontFamily(&notosans16RegularFont, &notosans16BoldFont, &notosans16ItalicFont,
&notosans16BoldItalicFont);
EpdFont notosans18RegularFont(&notosans_18_regular);
EpdFont notosans18BoldFont(&notosans_18_bold);
EpdFont notosans18ItalicFont(&notosans_18_italic);
EpdFont notosans18BoldItalicFont(&notosans_18_bolditalic);
EpdFontFamily notosans18FontFamily(&notosans18RegularFont, &notosans18BoldFont,
&notosans18ItalicFont,
EpdFontFamily notosans18FontFamily(&notosans18RegularFont, &notosans18BoldFont, &notosans18ItalicFont,
&notosans18BoldItalicFont);
EpdFont opendyslexic8RegularFont(&opendyslexic_8_regular);
EpdFont opendyslexic8BoldFont(&opendyslexic_8_bold);
EpdFont opendyslexic8ItalicFont(&opendyslexic_8_italic);
EpdFont opendyslexic8BoldItalicFont(&opendyslexic_8_bolditalic);
EpdFontFamily opendyslexic8FontFamily(&opendyslexic8RegularFont,
&opendyslexic8BoldFont,
&opendyslexic8ItalicFont,
EpdFontFamily opendyslexic8FontFamily(&opendyslexic8RegularFont, &opendyslexic8BoldFont, &opendyslexic8ItalicFont,
&opendyslexic8BoldItalicFont);
EpdFont opendyslexic10RegularFont(&opendyslexic_10_regular);
EpdFont opendyslexic10BoldFont(&opendyslexic_10_bold);
EpdFont opendyslexic10ItalicFont(&opendyslexic_10_italic);
EpdFont opendyslexic10BoldItalicFont(&opendyslexic_10_bolditalic);
EpdFontFamily opendyslexic10FontFamily(&opendyslexic10RegularFont,
&opendyslexic10BoldFont,
&opendyslexic10ItalicFont,
EpdFontFamily opendyslexic10FontFamily(&opendyslexic10RegularFont, &opendyslexic10BoldFont, &opendyslexic10ItalicFont,
&opendyslexic10BoldItalicFont);
EpdFont opendyslexic12RegularFont(&opendyslexic_12_regular);
EpdFont opendyslexic12BoldFont(&opendyslexic_12_bold);
EpdFont opendyslexic12ItalicFont(&opendyslexic_12_italic);
EpdFont opendyslexic12BoldItalicFont(&opendyslexic_12_bolditalic);
EpdFontFamily opendyslexic12FontFamily(&opendyslexic12RegularFont,
&opendyslexic12BoldFont,
&opendyslexic12ItalicFont,
EpdFontFamily opendyslexic12FontFamily(&opendyslexic12RegularFont, &opendyslexic12BoldFont, &opendyslexic12ItalicFont,
&opendyslexic12BoldItalicFont);
EpdFont opendyslexic14RegularFont(&opendyslexic_14_regular);
EpdFont opendyslexic14BoldFont(&opendyslexic_14_bold);
EpdFont opendyslexic14ItalicFont(&opendyslexic_14_italic);
EpdFont opendyslexic14BoldItalicFont(&opendyslexic_14_bolditalic);
EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont,
&opendyslexic14BoldFont,
&opendyslexic14ItalicFont,
EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont, &opendyslexic14BoldFont, &opendyslexic14ItalicFont,
&opendyslexic14BoldItalicFont);
#endif // OMIT_FONTS
@ -180,14 +164,11 @@ void verifyWakeupLongPress() {
// now from millis()==0 (i.e. device start time).
const uint16_t calibration = start;
const uint16_t calibratedPressDuration =
(calibration < SETTINGS.getPowerButtonDuration())
? SETTINGS.getPowerButtonDuration() - calibration
: 1;
(calibration < SETTINGS.getPowerButtonDuration()) ? SETTINGS.getPowerButtonDuration() - calibration : 1;
inputManager.update();
// Verify the user has actually pressed
while (!inputManager.isPressed(InputManager::BTN_POWER) &&
millis() - start < 1000) {
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
delay(10); // only wait 10ms each iteration to not delay too much in case of
// short configured duration.
inputManager.update();
@ -198,8 +179,7 @@ void verifyWakeupLongPress() {
do {
delay(10);
inputManager.update();
} while (inputManager.isPressed(InputManager::BTN_POWER) &&
inputManager.getHeldTime() < calibratedPressDuration);
} while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < calibratedPressDuration);
abort = inputManager.getHeldTime() < calibratedPressDuration;
} else {
abort = true;
@ -208,8 +188,7 @@ void verifyWakeupLongPress() {
if (abort) {
// Button released too early. Returning to sleep.
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN,
ESP_GPIO_WAKEUP_GPIO_LOW);
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
esp_deep_sleep_start();
}
}
@ -228,11 +207,9 @@ void enterDeepSleep() {
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
einkDisplay.deepSleep();
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n",
millis(), t2 - t1);
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN,
ESP_GPIO_WAKEUP_GPIO_LOW);
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
// Ensure that the power button has been released to avoid immediately turning
// back on if you're holding it
waitForPowerRelease();
@ -241,55 +218,43 @@ void enterDeepSleep() {
}
void onGoHome();
void onGoToMyLibraryWithTab(const std::string &path,
MyLibraryActivity::Tab tab);
void onGoToReader(const std::string &initialEpubPath,
MyLibraryActivity::Tab fromTab) {
void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab);
void onGoToReader(const std::string& initialEpubPath, MyLibraryActivity::Tab fromTab) {
exitActivity();
enterNewActivity(new ReaderActivity(renderer, mappedInputManager,
initialEpubPath, fromTab, onGoHome,
onGoToMyLibraryWithTab));
}
void onContinueReading() {
onGoToReader(APP_STATE.openEpubPath, MyLibraryActivity::Tab::Recent);
enterNewActivity(
new ReaderActivity(renderer, mappedInputManager, initialEpubPath, fromTab, onGoHome, onGoToMyLibraryWithTab));
}
void onContinueReading() { onGoToReader(APP_STATE.openEpubPath, MyLibraryActivity::Tab::Recent); }
void onGoToFileTransfer() {
exitActivity();
enterNewActivity(
new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
}
void onGoToSettings() {
exitActivity();
enterNewActivity(
new SettingsActivity(renderer, mappedInputManager, onGoHome));
enterNewActivity(new SettingsActivity(renderer, mappedInputManager, onGoHome));
}
void onGoToMyLibrary() {
exitActivity();
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome,
onGoToReader));
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader));
}
void onGoToMyLibraryWithTab(const std::string &path,
MyLibraryActivity::Tab tab) {
void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab) {
exitActivity();
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome,
onGoToReader, tab, path));
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader, tab, path));
}
void onGoToBrowser() {
exitActivity();
enterNewActivity(
new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome));
enterNewActivity(new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome));
}
void onGoHome() {
exitActivity();
enterNewActivity(new HomeActivity(
renderer, mappedInputManager, onContinueReading, onGoToMyLibrary,
onGoToSettings, onGoToFileTransfer, onGoToBrowser));
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToMyLibrary, onGoToSettings,
onGoToFileTransfer, onGoToBrowser));
}
void setupDisplayAndFonts() {
@ -325,13 +290,10 @@ bool isWakeupAfterFlashing() {
const auto wakeupCause = esp_sleep_get_wakeup_cause();
const auto resetReason = esp_reset_reason();
return isUsbConnected() && (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED) &&
(resetReason == ESP_RST_UNKNOWN);
return isUsbConnected() && (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED) && (resetReason == ESP_RST_UNKNOWN);
}
bool isSoftwareRestart() {
return esp_reset_reason() == ESP_RST_SW;
}
bool isSoftwareRestart() { return esp_reset_reason() == ESP_RST_SW; }
void setup() {
t1 = millis();
@ -360,8 +322,7 @@ void setup() {
Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
setupDisplayAndFonts();
exitActivity();
enterNewActivity(new FullScreenMessageActivity(
renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
return;
}
@ -375,9 +336,7 @@ void setup() {
// First serial output only here to avoid timing inconsistencies for power
// button press duration verification
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION
"\n",
millis());
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
setupDisplayAndFonts();
@ -417,9 +376,8 @@ void loop() {
inputManager.update();
if (Serial && millis() - lastMemPrint >= 10000) {
Serial.printf(
"[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n",
millis(), ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getMinFreeHeap());
Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
ESP.getHeapSize(), ESP.getMinFreeHeap());
lastMemPrint = millis();
}
@ -433,9 +391,7 @@ void loop() {
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
if (millis() - lastActivityTime >= sleepTimeoutMs) {
Serial.printf(
"[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n",
millis(), sleepTimeoutMs);
Serial.printf("[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n", millis(), sleepTimeoutMs);
enterDeepSleep();
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
return;
@ -458,9 +414,8 @@ void loop() {
if (loopDuration > maxLoopDuration) {
maxLoopDuration = loopDuration;
if (maxLoopDuration > 50) {
Serial.printf(
"[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n",
millis(), maxLoopDuration, activityDuration);
Serial.printf("[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n", millis(), maxLoopDuration,
activityDuration);
}
}