diff --git a/lib/GfxRenderer/Bitmap.cpp b/lib/GfxRenderer/Bitmap.cpp index 0b8a3e65..603954d2 100644 --- a/lib/GfxRenderer/Bitmap.cpp +++ b/lib/GfxRenderer/Bitmap.cpp @@ -1,8 +1,10 @@ #include "Bitmap.h" -#include "BitmapHelpers.h" + #include #include +#include "BitmapHelpers.h" + // ============================================================================ // IMAGE PROCESSING OPTIONS // ============================================================================ @@ -31,13 +33,12 @@ int Bitmap::readByte() const { return -1; } -size_t Bitmap::readBytes(void *buf, size_t count) const { +size_t Bitmap::readBytes(void* buf, size_t count) const { if (file && *file) { 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(c0 & 0xFF) | - (static_cast(c1 & 0xFF) << 8); + return static_cast(c0 & 0xFF) | (static_cast(c1 & 0xFF) << 8); } uint32_t Bitmap::readLE32() { @@ -83,64 +83,58 @@ uint32_t Bitmap::readLE32() { const int c1 = readByte(); const int c2 = readByte(); const int c3 = readByte(); - return static_cast(c0 & 0xFF) | - (static_cast(c1 & 0xFF) << 8) | - (static_cast(c2 & 0xFF) << 16) | - (static_cast(c3 & 0xFF) << 24); + return static_cast(c0 & 0xFF) | (static_cast(c1 & 0xFF) << 8) | + (static_cast(c2 & 0xFF) << 16) | (static_cast(c3 & 0xFF) << 24); } -const char *Bitmap::errorToString(BmpReaderError err) { +const char* Bitmap::errorToString(BmpReaderError err) { switch (err) { - case BmpReaderError::Ok: - return "Ok"; - case BmpReaderError::FileInvalid: - return "FileInvalid"; - case BmpReaderError::SeekStartFailed: - return "SeekStartFailed"; - case BmpReaderError::NotBMP: - return "NotBMP"; - case BmpReaderError::DIBTooSmall: - return "DIBTooSmall"; - case BmpReaderError::BadPlanes: - return "BadPlanes"; - case BmpReaderError::UnsupportedBpp: - return "UnsupportedBpp"; - case BmpReaderError::UnsupportedCompression: - return "UnsupportedCompression"; - case BmpReaderError::BadDimensions: - return "BadDimensions"; - case BmpReaderError::ImageTooLarge: - return "ImageTooLarge"; - case BmpReaderError::PaletteTooLarge: - return "PaletteTooLarge"; - case BmpReaderError::SeekPixelDataFailed: - return "SeekPixelDataFailed"; - case BmpReaderError::BufferTooSmall: - return "BufferTooSmall"; - case BmpReaderError::OomRowBuffer: - return "OomRowBuffer"; - case BmpReaderError::ShortReadRow: - return "ShortReadRow"; + case BmpReaderError::Ok: + return "Ok"; + case BmpReaderError::FileInvalid: + return "FileInvalid"; + case BmpReaderError::SeekStartFailed: + return "SeekStartFailed"; + case BmpReaderError::NotBMP: + return "NotBMP"; + case BmpReaderError::DIBTooSmall: + return "DIBTooSmall"; + case BmpReaderError::BadPlanes: + return "BadPlanes"; + case BmpReaderError::UnsupportedBpp: + return "UnsupportedBpp"; + case BmpReaderError::UnsupportedCompression: + return "UnsupportedCompression"; + case BmpReaderError::BadDimensions: + return "BadDimensions"; + case BmpReaderError::ImageTooLarge: + return "ImageTooLarge"; + case BmpReaderError::PaletteTooLarge: + return "PaletteTooLarge"; + case BmpReaderError::SeekPixelDataFailed: + return "SeekPixelDataFailed"; + case BmpReaderError::BufferTooSmall: + return "BufferTooSmall"; + case BmpReaderError::OomRowBuffer: + return "OomRowBuffer"; + case BmpReaderError::ShortReadRow: + return "ShortReadRow"; } return "Unknown"; } 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(readLE32()); const auto rawHeight = static_cast(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(i); + for (int i = 0; i < 256; i++) paletteLum[i] = static_cast(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) { @@ -201,12 +187,11 @@ BmpReaderError Bitmap::parseHeaders() { return BmpReaderError::Ok; } -BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const { - if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes) - return BmpReaderError::ShortReadRow; +BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const { + if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes) return BmpReaderError::ShortReadRow; prevRowY += 1; - uint8_t *outPtr = data; + uint8_t* outPtr = data; uint8_t currentOutByte = 0; int bitShift = 6; int currentX = 0; @@ -236,46 +221,44 @@ BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const { }; switch (bpp) { - case 32: { - const uint8_t *p = rowBuffer; - for (int x = 0; x < width; x++) { - uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8; - packPixel(lum); - p += 4; + case 32: { + const uint8_t* p = rowBuffer; + for (int x = 0; x < width; x++) { + uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8; + packPixel(lum); + p += 4; + } + break; } - break; - } - case 24: { - const uint8_t *p = rowBuffer; - for (int x = 0; x < width; x++) { - uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8; - packPixel(lum); - p += 3; + case 24: { + const uint8_t* p = rowBuffer; + for (int x = 0; x < width; x++) { + uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8; + packPixel(lum); + p += 3; + } + break; } - break; - } - case 8: { - for (int x = 0; x < width; x++) - packPixel(paletteLum[rowBuffer[x]]); - break; - } - case 2: { - for (int x = 0; x < width; x++) { - uint8_t lum = - paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03]; - packPixel(lum); + case 8: { + for (int x = 0; x < width; x++) packPixel(paletteLum[rowBuffer[x]]); + break; } - break; - } - case 1: { - for (int x = 0; x < width; x++) { - const uint8_t palIndex = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 1 : 0; - packPixel(paletteLum[palIndex]); + case 2: { + for (int x = 0; x < width; x++) { + uint8_t lum = paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03]; + packPixel(lum); + } + break; } - break; - } - default: - return BmpReaderError::UnsupportedBpp; + case 1: { + for (int x = 0; x < width; x++) { + const uint8_t palIndex = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 1 : 0; + packPixel(paletteLum[palIndex]); + } + break; + } + default: + return BmpReaderError::UnsupportedBpp; } if (atkinsonDitherer) @@ -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; } diff --git a/lib/GfxRenderer/Bitmap.h b/lib/GfxRenderer/Bitmap.h index 595c8d30..41ee0509 100644 --- a/lib/GfxRenderer/Bitmap.h +++ b/lib/GfxRenderer/Bitmap.h @@ -29,18 +29,16 @@ enum class BmpReaderError : uint8_t { }; class Bitmap { -public: - static const char *errorToString(BmpReaderError err); + public: + static const char* errorToString(BmpReaderError err); - 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) {} + 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) {} ~Bitmap(); BmpReaderError parseHeaders(); - BmpReaderError readNextRow(uint8_t *data, uint8_t *rowBuffer) const; + BmpReaderError readNextRow(uint8_t* data, uint8_t* rowBuffer) const; BmpReaderError rewindToData() const; // Getters @@ -52,19 +50,19 @@ public: bool is1Bit() const { return bpp == 1; } uint16_t getBpp() const { return bpp; } -private: + private: // Internal IO helpers int readByte() const; - size_t readBytes(void *buf, size_t count) const; + size_t readBytes(void* buf, size_t count) const; bool seekSet(uint32_t pos) const; - bool seekCur(int32_t offset) const; // Only needed for skip? + bool seekCur(int32_t offset) const; // Only needed for skip? uint16_t readLE16(); uint32_t readLE32(); // Source (one is valid) - FsFile *file = nullptr; - const uint8_t *memoryBuffer = nullptr; + FsFile* file = nullptr; + const uint8_t* memoryBuffer = nullptr; size_t memorySize = 0; mutable size_t bufferPos = 0; @@ -78,10 +76,10 @@ private: uint8_t paletteLum[256] = {}; // Floyd-Steinberg dithering state (mutable for const methods) - mutable int16_t *errorCurRow = nullptr; - mutable int16_t *errorNextRow = nullptr; - mutable int prevRowY = -1; // Track row progression for error propagation + mutable int16_t* errorCurRow = nullptr; + mutable int16_t* errorNextRow = nullptr; + mutable int prevRowY = -1; // Track row progression for error propagation - mutable AtkinsonDitherer *atkinsonDitherer = nullptr; - mutable FloydSteinbergDitherer *fsDitherer = nullptr; + mutable AtkinsonDitherer* atkinsonDitherer = nullptr; + mutable FloydSteinbergDitherer* fsDitherer = nullptr; }; diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index b5998b0f..4a0e6676 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1,47 +1,45 @@ #include "GfxRenderer.h" #include + #include -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) - // Rotation: 90 degrees clockwise - *rotatedX = y; - *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; - break; - } - case LandscapeClockwise: { - // Logical landscape (800x480) rotated 180 degrees (swap top/bottom and - // left/right) - *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - x; - *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - y; - break; - } - case PortraitInverted: { - // Logical portrait (480x800) → panel (800x480) - // Rotation: 90 degrees counter-clockwise - *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - y; - *rotatedY = x; - break; - } - case LandscapeCounterClockwise: { - // Logical landscape (800x480) aligned with panel orientation - *rotatedX = x; - *rotatedY = y; - break; - } + case Portrait: { + // Logical portrait (480x800) → panel (800x480) + // Rotation: 90 degrees clockwise + *rotatedX = y; + *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; + break; + } + case LandscapeClockwise: { + // Logical landscape (800x480) rotated 180 degrees (swap top/bottom and + // left/right) + *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - x; + *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - y; + break; + } + case PortraitInverted: { + // Logical portrait (480x800) → panel (800x480) + // Rotation: 90 degrees counter-clockwise + *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - y; + *rotatedY = x; + break; + } + case LandscapeCounterClockwise: { + // Logical landscape (800x480) aligned with panel orientation + *rotatedX = x; + *rotatedY = y; + break; + } } } void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { - uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); + uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); // Early return if no framebuffer is set if (!frameBuffer) { @@ -56,25 +54,23 @@ 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 uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first + const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); + const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first if (state) { - frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit + frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit } else { - frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit + frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit } } bool GfxRenderer::readPixel(const int x, const int y) const { - uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); + uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); if (!frameBuffer) { return false; } @@ -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; @@ -139,13 +131,12 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, } uint32_t cp; - while ((cp = utf8NextCodepoint(reinterpret_cast(&text)))) { + while ((cp = utf8NextCodepoint(reinterpret_cast(&text)))) { renderChar(font, cp, &xpos, &yPos, black, style); } } -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); @@ -155,9 +146,9 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, while (true) { drawPixel(x1, y1, state); - + if (x1 == x2 && y1 == y2) break; - + int e2 = 2 * err; if (e2 > -dy) { err -= dy; @@ -170,17 +161,15 @@ 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 { - uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); +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,13 +196,12 @@ 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 + frameBuffer[byteIndex] &= ~mask; // Black } else { - frameBuffer[byteIndex] |= mask; // White + frameBuffer[byteIndex] |= mask; // White } } } @@ -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,47 +272,51 @@ 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 - + if (grayLevel == 0x00) { fillRect(x, y, width, height, true); // Solid black return; } if (grayLevel >= 0xF0) { - fillRect(x, y, width, height, false); // Solid white + fillRect(x, y, width, height, false); // Solid white return; } - + // Use ordered dithering (Bayer matrix 2x2) // Gray levels: 0x00=black, 0x55=25%, 0xAA=50%, 0xD5=75%, 0xFF=white const int screenWidth = getScreenWidth(); const int screenHeight = getScreenHeight(); - + const int x1 = std::max(0, x); const int y1 = std::max(0, y); 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; - + // Determine threshold based on gray level // Lower gray = more black pixels, higher gray = more white pixels - int threshold = (grayLevel * 4) / 255; // 0-4 range - + int threshold = (grayLevel * 4) / 255; // 0-4 range + // 2x2 Bayer matrix thresholds: 0, 2, 3, 1 for (int sy = y1; sy <= y2; sy++) { for (int sx = x1; sx <= x2; sx++) { 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; drawPixel(sx, sy, isBlack); @@ -334,41 +324,45 @@ 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; } - + int r = std::min(radius, std::min(width / 2, height / 2)); - + // Draw 4 corner arcs using midpoint circle algorithm int cx, cy; int px = 0, py = r; int d = 1 - r; - + 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; + + // Top-right corner + 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); - + if (d < 0) { d += 2 * px + 3; } else { @@ -377,34 +371,34 @@ void GfxRenderer::drawRoundedRect(const int x, const int y, const int width, } px++; } - + // Draw straight edges - drawLine(x + r, y, x + width - 1 - r, y, state); // Top - drawLine(x + r, y + height - 1, x + width - 1 - r, y + height - 1, state); // Bottom - drawLine(x, y + r, x, y + height - 1 - r, state); // Left - drawLine(x + width - 1, y + r, x + width - 1, y + height - 1 - r, state); // Right + drawLine(x + r, y, x + width - 1 - r, y, state); // Top + drawLine(x + r, y + height - 1, x + width - 1 - r, y + height - 1, state); // Bottom + drawLine(x, y + r, x, y + height - 1 - r, state); // Left + 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; } - + int r = std::min(radius, std::min(width / 2, height / 2)); - + // Fill the center rectangle fillRect(x + r, y, width - 2 * r, height, state); - + // Fill left and right rectangles (excluding corners) fillRect(x, y + r, r, height - 2 * r, state); fillRect(x + width - r, y + r, r, height - 2 * r, state); - + // Fill corners using circle algorithm int px = 0, py = r; int d = 1 - r; - + while (px <= py) { // Fill horizontal lines for each corner // Top-left and top-right @@ -412,13 +406,13 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, drawLine(x + width - 1 - r, y + r - px, x + width - 1 - r + py, y + r - px, state); drawLine(x + r - px, y + r - py, x + r, y + r - py, state); drawLine(x + width - 1 - r, y + r - py, x + width - 1 - r + px, y + r - py, state); - + // Bottom-left and bottom-right drawLine(x + r - py, y + height - 1 - r + px, x + r, y + height - 1 - r + px, state); drawLine(x + width - 1 - r, y + height - 1 - r + px, x + width - 1 - r + py, y + height - 1 - r + px, state); drawLine(x + r - px, y + height - 1 - r + py, x + r, y + height - 1 - r + py, state); drawLine(x + width - 1 - r, y + height - 1 - r + py, x + width - 1 - r + px, y + height - 1 - r + py, state); - + if (d < 0) { d += 2 * px + 3; } else { @@ -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); @@ -440,18 +433,18 @@ void GfxRenderer::fillRoundedRectDithered(const int x, const int y, const int wi fillRoundedRect(x, y, width, height, radius, false); return; } - + int r = std::min(radius, std::min(width / 2, height / 2)); if (r <= 0) { fillRectDithered(x, y, width, height, grayLevel); return; } - + const int screenWidth = getScreenWidth(); const int screenHeight = getScreenHeight(); - + int threshold = (grayLevel * 4) / 255; - + // Check if a point is inside the rounded rectangle auto isInside = [&](int px, int py) -> bool { // Check corners @@ -482,32 +475,35 @@ void GfxRenderer::fillRoundedRectDithered(const int x, const int y, const int wi // Inside the non-corner region return px >= x && px < x + width && py >= y && py < y + height; }; - + const int x1 = std::max(0, x); const int y1 = std::max(0, y); const int x2 = std::min(screenWidth - 1, x + width - 1); const int y2 = std::min(screenHeight - 1, y + height - 1); - + for (int sy = y1; sy <= y2; sy++) { for (int sx = x1; sx <= x2; sx++) { if (!isInside(sx, sy)) continue; - + 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); } } } -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(maxWidth) / - static_cast((1.0f - cropX) * bitmap.getWidth()); + scale = static_cast(maxWidth) / static_cast((1.0f - cropX) * bitmap.getWidth()); isScaled = true; } if (maxHeight > 0 && (1.0f - cropY) * bitmap.getHeight() > maxHeight) { - scale = std::min( - scale, static_cast(maxHeight) / - static_cast((1.0f - cropY) * bitmap.getHeight())); + scale = std::min(scale, static_cast(maxHeight) / static_cast((1.0f - cropY) * bitmap.getHeight())); isScaled = true; } @@ -546,12 +538,11 @@ void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y, // IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels // wide const int outputRowSize = (bitmap.getWidth() + 3) / 4; - auto *outputRow = static_cast(malloc(outputRowSize)); - auto *rowBytes = static_cast(malloc(bitmap.getRowBytes())); + auto* outputRow = static_cast(malloc(outputRowSize)); + auto* rowBytes = static_cast(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,19 +551,17 @@ 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); } - screenY += y; // the offset should not be scaled + screenY += y; // the offset should not be scaled if (screenY >= getScreenHeight()) { break; } 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; @@ -592,7 +581,7 @@ void GfxRenderer::drawBitmap(const Bitmap &bitmap, const int x, const int y, if (isScaled) { screenX = std::floor(screenX * scale); } - screenX += x; // the offset should not be scaled + screenX += x; // the offset should not be scaled if (screenX >= getScreenWidth()) { break; } @@ -616,9 +605,8 @@ 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 { - uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); +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,17 +622,15 @@ 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; - const uint8_t *srcRow = data + row * srcRowBytes; + 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; // Extract 2-bit value (4 pixels per byte) const uint8_t val = (srcRow[col / 4] >> (6 - ((col % 4) * 2))) & 0x3; @@ -653,10 +639,9 @@ 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 + 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; + 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; + 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; + 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,31 +730,27 @@ 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(maxWidth) / static_cast(bitmap.getWidth()); + scale = static_cast(maxWidth) / static_cast(bitmap.getWidth()); isScaled = true; } if (maxHeight > 0 && bitmap.getHeight() > maxHeight) { - scale = std::min(scale, static_cast(maxHeight) / - static_cast(bitmap.getHeight())); + scale = std::min(scale, static_cast(maxHeight) / static_cast(bitmap.getHeight())); isScaled = true; } // For 1-bit BMP, output is still 2-bit packed (for consistency with // readNextRow) const int outputRowSize = (bitmap.getWidth() + 3) / 4; - auto *outputRow = static_cast(malloc(outputRowSize)); - auto *rowBytes = static_cast(malloc(bitmap.getRowBytes())); + auto* outputRow = static_cast(malloc(outputRowSize)); + auto* rowBytes = static_cast(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,29 +759,24 @@ 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(std::floor(bmpYOffset * scale)) - : bmpYOffset); + const int bmpYOffset = bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY; + int screenY = y + (isScaled ? static_cast(std::floor(bmpYOffset * scale)) : bmpYOffset); if (screenY >= getScreenHeight()) { - continue; // Continue reading to keep row counter in sync + continue; // Continue reading to keep row counter in sync } if (screenY < 0) { continue; } for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) { - int screenX = - x + (isScaled ? static_cast(std::floor(bmpX * scale)) : bmpX); + int screenX = x + (isScaled ? static_cast(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(malloc(numPoints * sizeof(int))); + auto* nodeX = static_cast(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 *frameBuffer = einkDisplay.getFrameBuffer(); +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,40 +886,36 @@ 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; } // Pack as 1-bit: ceil(width/8) bytes per row const size_t rowBytes = (width + 7) / 8; - const size_t bufferSize = rowBytes * height + 4 * sizeof(int); // +header - uint8_t *buffer = static_cast(malloc(bufferSize)); + const size_t bufferSize = rowBytes * height + 4 * sizeof(int); // +header + uint8_t* buffer = static_cast(malloc(bufferSize)); if (!buffer) { - if (outSize) - *outSize = 0; + if (outSize) *outSize = 0; return nullptr; } // Store dimensions in header - int *header = reinterpret_cast(buffer); + int* header = reinterpret_cast(buffer); header[0] = x; header[1] = y; header[2] = width; header[3] = height; - uint8_t *data = buffer + 4 * sizeof(int); + uint8_t* data = buffer + 4 * sizeof(int); // Extract pixels - this is orientation-dependent for (int row = 0; row < height; row++) { const int screenY = y + row; - uint8_t *destRow = data + row * rowBytes; - memset(destRow, 0xFF, rowBytes); // Start with white + uint8_t* destRow = data + row * rowBytes; + memset(destRow, 0xFF, rowBytes); // Start with white for (int col = 0; col < width; col++) { const int screenX = x + col; @@ -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,29 +936,26 @@ 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 { - uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); +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; } const size_t rowBytes = (width + 7) / 8; - const uint8_t *data = buffer + 4 * sizeof(int); // Skip header + const uint8_t* data = buffer + 4 * sizeof(int); // Skip header // Optimized path for Portrait mode 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 uint8_t* srcRow = data + row * rowBytes; const int physX = screenY; const uint8_t physXByte = physX / 8; const uint8_t physXBit = 7 - (physX % 8); @@ -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; @@ -1039,7 +982,7 @@ void GfxRenderer::restoreRegion(const uint8_t *buffer, int x, int y, int width, // Generic fallback using drawPixel for (int row = 0; row < height; row++) { const int screenY = y + row; - const uint8_t *srcRow = data + row * rowBytes; + const uint8_t* srcRow = data + row * rowBytes; for (int col = 0; col < width; col++) { const int screenX = x + col; @@ -1049,12 +992,10 @@ 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(); + uint8_t* buffer = einkDisplay.getFrameBuffer(); if (!buffer) { Serial.printf("[%lu] [GFX] !! No framebuffer in invertScreen\n", millis()); return; @@ -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); @@ -1085,28 +1024,28 @@ std::string GfxRenderer::truncatedText(const int fontId, const char *text, // exposes a logical orientation int GfxRenderer::getScreenWidth() const { switch (orientation) { - case Portrait: - case PortraitInverted: - // 480px wide in portrait logical coordinates - return EInkDisplay::DISPLAY_HEIGHT; - case LandscapeClockwise: - case LandscapeCounterClockwise: - // 800px wide in landscape logical coordinates - return EInkDisplay::DISPLAY_WIDTH; + case Portrait: + case PortraitInverted: + // 480px wide in portrait logical coordinates + return EInkDisplay::DISPLAY_HEIGHT; + case LandscapeClockwise: + case LandscapeCounterClockwise: + // 800px wide in landscape logical coordinates + return EInkDisplay::DISPLAY_WIDTH; } return EInkDisplay::DISPLAY_HEIGHT; } int GfxRenderer::getScreenHeight() const { switch (orientation) { - case Portrait: - case PortraitInverted: - // 800px tall in portrait logical coordinates - return EInkDisplay::DISPLAY_WIDTH; - case LandscapeClockwise: - case LandscapeCounterClockwise: - // 480px tall in landscape logical coordinates - return EInkDisplay::DISPLAY_HEIGHT; + case Portrait: + case PortraitInverted: + // 800px tall in portrait logical coordinates + return EInkDisplay::DISPLAY_WIDTH; + case LandscapeClockwise: + case LandscapeCounterClockwise: + // 480px tall in landscape logical coordinates + return EInkDisplay::DISPLAY_HEIGHT; } return EInkDisplay::DISPLAY_WIDTH; } @@ -1138,19 +1077,18 @@ 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, - const char *btn4) { +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); const int pageHeight = getScreenHeight(); constexpr int buttonWidth = 106; constexpr int buttonHeight = 40; - constexpr int buttonY = 40; // Distance from bottom - constexpr int textYOffset = 7; // Distance from top of button to text baseline + constexpr int buttonY = 40; // Distance from bottom + constexpr int textYOffset = 7; // Distance from top of button to text baseline constexpr int buttonPositions[] = {25, 130, 245, 350}; - const char *labels[] = {btn1, btn2, btn3, btn4}; + const char* labels[] = {btn1, btn2, btn3, btn4}; for (int i = 0; i < 4; i++) { // Only draw if the label is non-empty @@ -1167,44 +1105,41 @@ 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) - constexpr int buttonX = 5; // Distance from right edge + constexpr int buttonWidth = 40; // Width on screen (height when rotated) + constexpr int buttonHeight = 80; // Height on screen (width when rotated) + constexpr int buttonX = 5; // Distance from right edge // Position for the button group - buttons share a border so they're adjacent - constexpr int topButtonY = 345; // Top button position + constexpr int topButtonY = 345; // Top button position - const char *labels[] = {topBtn, bottomBtn}; + const char* labels[] = {topBtn, bottomBtn}; // Draw the shared border for both buttons as one unit const int x = screenWidth - buttonX - buttonWidth; // Draw top button outline (3 sides, bottom open) if (topBtn != nullptr && topBtn[0] != '\0') { - drawLine(x, topButtonY, x + buttonWidth - 1, topButtonY); // Top - drawLine(x, topButtonY, x, topButtonY + buttonHeight - 1); // Left + drawLine(x, topButtonY, x + buttonWidth - 1, topButtonY); // Top + drawLine(x, topButtonY, x, topButtonY + buttonHeight - 1); // Left drawLine(x + buttonWidth - 1, topButtonY, x + buttonWidth - 1, - topButtonY + buttonHeight - 1); // Right + topButtonY + buttonHeight - 1); // Right } // 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 + topButtonY + buttonHeight); // Shared border } // Draw bottom button outline (3 sides, top is shared) 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, - topButtonY + 2 * buttonHeight - 1); // Right + topButtonY + 2 * buttonHeight - 1); // Left + 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 + topButtonY + 2 * buttonHeight - 1); // Bottom } // Draw text for each button @@ -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') { @@ -1257,11 +1190,11 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, // Original (glyphX, glyphY) -> Rotated (glyphY, -glyphX) // Text reads from bottom to top - int yPos = y; // Current Y position (decreases as we draw characters) + int yPos = y; // Current Y position (decreases as we draw characters) uint32_t cp; - while ((cp = utf8NextCodepoint(reinterpret_cast(&text)))) { - const EpdGlyph *glyph = font.getGlyph(cp, style); + while ((cp = utf8NextCodepoint(reinterpret_cast(&text)))) { + const EpdGlyph* glyph = font.getGlyph(cp, style); if (!glyph) { glyph = font.getGlyph(REPLACEMENT_GLYPH, style); } @@ -1276,7 +1209,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int left = glyph->left; const int top = glyph->top; - const uint8_t *bitmap = &font.getData(style)->bitmap[offset]; + const uint8_t* bitmap = &font.getData(style)->bitmap[offset]; if (bitmap != nullptr) { for (int glyphY = 0; glyphY < height; glyphY++) { @@ -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,26 +1251,20 @@ 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(); } void GfxRenderer::freeBwBufferChunks() { - for (auto &bwBufferChunk : bwBufferChunks) { + for (auto& bwBufferChunk : bwBufferChunks) { if (bwBufferChunk) { free(bwBufferChunk); bwBufferChunk = nullptr; @@ -1355,7 +1280,7 @@ void GfxRenderer::freeBwBufferChunks() { * allocation failed. */ bool GfxRenderer::storeBwBuffer() { - const uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); + const uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); if (!frameBuffer) { Serial.printf("[%lu] [GFX] !! No framebuffer in storeBwBuffer\n", millis()); return false; @@ -1365,20 +1290,20 @@ 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 " - "is likely a bug, freeing chunk\n", - millis(), i); + Serial.printf( + "[%lu] [GFX] !! BW buffer chunk %zu already stored - this " + "is likely a bug, freeing chunk\n", + millis(), i); free(bwBufferChunks[i]); bwBufferChunks[i] = nullptr; } const size_t offset = i * BW_BUFFER_CHUNK_SIZE; - bwBufferChunks[i] = static_cast(malloc(BW_BUFFER_CHUNK_SIZE)); + bwBufferChunks[i] = static_cast(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; } @@ -1400,7 +1325,7 @@ bool GfxRenderer::storeBwBuffer() { void GfxRenderer::restoreBwBuffer() { // Check if any all chunks are allocated bool missingChunks = false; - for (const auto &bwBufferChunk : bwBufferChunks) { + for (const auto& bwBufferChunk : bwBufferChunks) { if (!bwBufferChunk) { missingChunks = true; break; @@ -1412,10 +1337,9 @@ void GfxRenderer::restoreBwBuffer() { return; } - uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); + 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; } @@ -1445,16 +1367,15 @@ void GfxRenderer::restoreBwBuffer() { * Use this when BW buffer was re-rendered instead of stored/restored. */ void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const { - uint8_t *frameBuffer = einkDisplay.getFrameBuffer(); + uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); if (frameBuffer) { einkDisplay.cleanupGrayscaleBuffers(frameBuffer); } } -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); +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); } @@ -1471,7 +1392,7 @@ void GfxRenderer::renderChar(const EpdFontFamily &fontFamily, const uint32_t cp, const uint8_t height = glyph->height; const int left = glyph->left; - const uint8_t *bitmap = nullptr; + const uint8_t* bitmap = nullptr; bitmap = &fontFamily.getData(style)->bitmap[offset]; if (bitmap != nullptr) { @@ -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,32 +1438,31 @@ 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; - *outRight = VIEWABLE_MARGIN_RIGHT; - *outBottom = VIEWABLE_MARGIN_BOTTOM; - *outLeft = VIEWABLE_MARGIN_LEFT; - break; - case LandscapeClockwise: - *outTop = VIEWABLE_MARGIN_LEFT; - *outRight = VIEWABLE_MARGIN_TOP; - *outBottom = VIEWABLE_MARGIN_RIGHT; - *outLeft = VIEWABLE_MARGIN_BOTTOM; - break; - case PortraitInverted: - *outTop = VIEWABLE_MARGIN_BOTTOM; - *outRight = VIEWABLE_MARGIN_LEFT; - *outBottom = VIEWABLE_MARGIN_TOP; - *outLeft = VIEWABLE_MARGIN_RIGHT; - break; - case LandscapeCounterClockwise: - *outTop = VIEWABLE_MARGIN_RIGHT; - *outRight = VIEWABLE_MARGIN_BOTTOM; - *outBottom = VIEWABLE_MARGIN_LEFT; - *outLeft = VIEWABLE_MARGIN_TOP; - break; + case Portrait: + *outTop = VIEWABLE_MARGIN_TOP; + *outRight = VIEWABLE_MARGIN_RIGHT; + *outBottom = VIEWABLE_MARGIN_BOTTOM; + *outLeft = VIEWABLE_MARGIN_LEFT; + break; + case LandscapeClockwise: + *outTop = VIEWABLE_MARGIN_LEFT; + *outRight = VIEWABLE_MARGIN_TOP; + *outBottom = VIEWABLE_MARGIN_RIGHT; + *outLeft = VIEWABLE_MARGIN_BOTTOM; + break; + case PortraitInverted: + *outTop = VIEWABLE_MARGIN_BOTTOM; + *outRight = VIEWABLE_MARGIN_LEFT; + *outBottom = VIEWABLE_MARGIN_TOP; + *outLeft = VIEWABLE_MARGIN_RIGHT; + break; + case LandscapeCounterClockwise: + *outTop = VIEWABLE_MARGIN_RIGHT; + *outRight = VIEWABLE_MARGIN_BOTTOM; + *outBottom = VIEWABLE_MARGIN_LEFT; + *outLeft = VIEWABLE_MARGIN_TOP; + break; } } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index f1b5f4ce..adc72dcc 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -8,42 +8,37 @@ #include "Bitmap.h" class GfxRenderer { -public: + public: enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB }; // Logical screen orientation from the perspective of callers enum Orientation { - Portrait, // 480x800 logical coordinates (current default) - LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap - // top/bottom) - PortraitInverted, // 480x800 logical coordinates, inverted - LandscapeCounterClockwise // 800x480 logical coordinates, native panel - // orientation + Portrait, // 480x800 logical coordinates (current default) + LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap + // top/bottom) + PortraitInverted, // 480x800 logical coordinates, inverted + LandscapeCounterClockwise // 800x480 logical coordinates, native panel + // orientation }; -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, + 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, "BW buffer chunking does not line up with display buffer size"); - EInkDisplay &einkDisplay; + EInkDisplay& einkDisplay; RenderMode renderMode; Orientation orientation; - uint8_t *bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; + uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; std::map 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; + void rotateCoordinates(int x, int y, int* rotatedX, int* rotatedY) const; -public: - explicit GfxRenderer(EInkDisplay &einkDisplay) - : einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {} + public: + 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; @@ -71,7 +65,7 @@ public: // Drawing void drawPixel(int x, int y, bool state = true) const; - bool readPixel(int x, int y) const; // Returns true if pixel is black + bool readPixel(int x, int y) const; // Returns true if pixel is black void drawLine(int x1, int y1, int x2, int y2, bool state = true) const; void drawRect(int x, int y, int width, int height, bool state = true) const; void fillRect(int x, int y, int width, int height, bool state = true) const; @@ -79,67 +73,55 @@ 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, - EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; - void drawText(int fontId, int x, 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, - EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; + 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: + 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, - EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; + 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; -public: + public: // Grayscale functions void setRenderMode(const RenderMode mode) { this->renderMode = mode; } void copyGrayscaleLsbBuffers() const; void copyGrayscaleMsbBuffers() const; void displayGrayBuffer() const; - bool storeBwBuffer(); // Returns true if buffer was stored successfully - void restoreBwBuffer(); // Restore and free the stored buffer + bool storeBwBuffer(); // Returns true if buffer was stored successfully + void restoreBwBuffer(); // Restore and free the stored buffer void cleanupGrayscaleWithFrameBuffer() const; // Low level functions - uint8_t *getFrameBuffer() const; + 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; }; diff --git a/lib/ThemeEngine/include/BasicElements.h b/lib/ThemeEngine/include/BasicElements.h index e0032d0b..1c75c98e 100644 --- a/lib/ThemeEngine/include/BasicElements.h +++ b/lib/ThemeEngine/include/BasicElements.h @@ -1,43 +1,42 @@ #pragma once +#include +#include + +#include + #include "ThemeContext.h" #include "ThemeTypes.h" #include "UIElement.h" -#include -#include -#include namespace ThemeEngine { // --- Container --- class Container : public UIElement { -protected: - std::vector children; + protected: + std::vector children; Expression bgColorExpr; bool hasBg = false; bool border = false; - Expression borderExpr; // Dynamic border based on expression - int padding = 0; // Inner padding for children - int borderRadius = 0; // Corner radius (for future rounded rect support) + Expression borderExpr; // Dynamic border based on expression + int padding = 0; // Inner padding for children + int borderRadius = 0; // Corner radius (for future rounded rect support) -public: - Container(const std::string &id) : UIElement(id) { - bgColorExpr = Expression::parse("0xFF"); - } + public: + 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; } + Container* asContainer() override { return this; } ElementType getType() const override { return ElementType::Container; } - void addChild(UIElement *child) { children.push_back(child); } + void addChild(UIElement* child) { children.push_back(child); } - const std::vector &getChildren() const { return children; } + const std::vector& getChildren() const { return children; } - void setBackgroundColorExpr(const std::string &expr) { + void setBackgroundColorExpr(const std::string& expr) { bgColorExpr = Expression::parse(expr); hasBg = true; markDirty(); @@ -48,29 +47,28 @@ public: markDirty(); } - void setBorderExpr(const std::string &expr) { + void setBorderExpr(const std::string& expr) { borderExpr = Expression::parse(expr); markDirty(); } bool hasBorderExpr() const { return !borderExpr.empty(); } - + void setPadding(int p) { padding = p; markDirty(); } - + int getPadding() const { return padding; } - + void setBorderRadius(int r) { borderRadius = r; markDirty(); } - + 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; @@ -89,9 +87,8 @@ public: } } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + if (!isVisible(context)) return; if (hasBg) { std::string colStr = context.evaluatestring(bgColorExpr); @@ -144,13 +141,11 @@ public: // --- Rectangle --- class Rectangle : public UIElement { bool fill = false; - Expression fillExpr; // Dynamic fill based on expression + Expression fillExpr; // Dynamic fill based on expression Expression colorExpr; -public: - Rectangle(const std::string &id) : UIElement(id) { - colorExpr = Expression::parse("0x00"); - } + public: + Rectangle(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); } ElementType getType() const override { return ElementType::Rectangle; } void setFill(bool f) { @@ -158,19 +153,18 @@ public: markDirty(); } - void setFillExpr(const std::string &expr) { + void setFillExpr(const std::string& expr) { fillExpr = Expression::parse(expr); markDirty(); } - void setColorExpr(const std::string &c) { + void setColorExpr(const std::string& c) { colorExpr = Expression::parse(c); markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + if (!isVisible(context)) return; std::string colStr = context.evaluatestring(colorExpr); uint8_t color = Color::parse(colStr).value; @@ -193,24 +187,22 @@ public: // --- Label --- class Label : public UIElement { -public: + public: enum class Alignment { Left, Center, Right }; -private: + private: Expression textExpr; int fontId = 0; Alignment alignment = Alignment::Left; Expression colorExpr; - int maxLines = 1; // For multi-line support - bool ellipsis = true; // Truncate with ... if too long + int maxLines = 1; // For multi-line support + bool ellipsis = true; // Truncate with ... if too long -public: - Label(const std::string &id) : UIElement(id) { - colorExpr = Expression::parse("0x00"); - } + public: + Label(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); } ElementType getType() const override { return ElementType::Label; } - void setText(const std::string &expr) { + void setText(const std::string& expr) { textExpr = Expression::parse(expr); markDirty(); } @@ -226,7 +218,7 @@ public: alignment = c ? Alignment::Center : Alignment::Left; markDirty(); } - void setColorExpr(const std::string &c) { + void setColorExpr(const std::string& c) { colorExpr = Expression::parse(c); markDirty(); } @@ -239,9 +231,8 @@ public: markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + if (!isVisible(context)) return; std::string finalText = context.evaluatestring(textExpr); if (finalText.empty()) { @@ -288,13 +279,13 @@ class BitmapElement : public UIElement { bool scaleToFit = true; bool preserveAspect = true; -public: - BitmapElement(const std::string &id) : UIElement(id) { - cacheable = true; // Bitmaps benefit from caching + public: + BitmapElement(const std::string& id) : UIElement(id) { + cacheable = true; // Bitmaps benefit from caching } ElementType getType() const override { return ElementType::Bitmap; } - void setSrc(const std::string &src) { + void setSrc(const std::string& src) { srcExpr = Expression::parse(src); invalidateCache(); } @@ -309,41 +300,41 @@ public: invalidateCache(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override; }; // --- ProgressBar --- class ProgressBar : public UIElement { - Expression valueExpr; // Current value (0-100 or 0-max) - Expression maxExpr; // Max value (default 100) - Expression fgColorExpr; // Foreground color - Expression bgColorExpr; // Background color + Expression valueExpr; // Current value (0-100 or 0-max) + Expression maxExpr; // Max value (default 100) + Expression fgColorExpr; // Foreground color + Expression bgColorExpr; // Background color bool showBorder = true; int borderWidth = 1; -public: - ProgressBar(const std::string &id) : UIElement(id) { + public: + ProgressBar(const std::string& id) : UIElement(id) { valueExpr = Expression::parse("0"); maxExpr = Expression::parse("100"); - fgColorExpr = Expression::parse("0x00"); // Black fill - bgColorExpr = Expression::parse("0xFF"); // White background + fgColorExpr = Expression::parse("0x00"); // Black fill + bgColorExpr = Expression::parse("0xFF"); // White background } ElementType getType() const override { return ElementType::ProgressBar; } - void setValue(const std::string &expr) { + void setValue(const std::string& expr) { valueExpr = Expression::parse(expr); markDirty(); } - void setMax(const std::string &expr) { + void setMax(const std::string& expr) { maxExpr = Expression::parse(expr); markDirty(); } - void setFgColor(const std::string &expr) { + void setFgColor(const std::string& expr) { fgColorExpr = Expression::parse(expr); markDirty(); } - void setBgColor(const std::string &expr) { + void setBgColor(const std::string& expr) { bgColorExpr = Expression::parse(expr); markDirty(); } @@ -352,23 +343,19 @@ public: markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + 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(value) / static_cast(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); @@ -398,14 +385,12 @@ class Divider : public UIElement { bool horizontal = true; int thickness = 1; -public: - Divider(const std::string &id) : UIElement(id) { - colorExpr = Expression::parse("0x00"); - } + public: + Divider(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); } ElementType getType() const override { return ElementType::Divider; } - void setColorExpr(const std::string &expr) { + void setColorExpr(const std::string& expr) { colorExpr = Expression::parse(expr); markDirty(); } @@ -418,9 +403,8 @@ public: markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + if (!isVisible(context)) return; std::string colStr = context.evaluatestring(colorExpr); uint8_t color = Color::parse(colStr).value; @@ -445,27 +429,26 @@ class BatteryIcon : public UIElement { Expression valueExpr; Expression colorExpr; -public: - BatteryIcon(const std::string &id) : UIElement(id) { + public: + BatteryIcon(const std::string& id) : UIElement(id) { valueExpr = Expression::parse("0"); - colorExpr = Expression::parse("0x00"); // Black by default + colorExpr = Expression::parse("0x00"); // Black by default } ElementType getType() const override { return ElementType::BatteryIcon; } - void setValue(const std::string &expr) { + void setValue(const std::string& expr) { valueExpr = Expression::parse(expr); markDirty(); } - void setColor(const std::string &expr) { + void setColor(const std::string& expr) { colorExpr = Expression::parse(expr); markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + 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; @@ -509,4 +487,4 @@ public: } }; -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/DefaultTheme.h b/lib/ThemeEngine/include/DefaultTheme.h index 2c5e263b..f1822582 100644 --- a/lib/ThemeEngine/include/DefaultTheme.h +++ b/lib/ThemeEngine/include/DefaultTheme.h @@ -6,8 +6,8 @@ namespace ThemeEngine { // Use static function for C++14 ODR compatibility -static const char *getDefaultThemeIni() { - static const char *theme = R"INI( +static const char* getDefaultThemeIni() { + static const char* theme = R"INI( ; ============================================ ; DEFAULT THEME - Original CrossPoint Reader ; ============================================ @@ -275,4 +275,4 @@ Align = center return theme; } -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/IniParser.h b/lib/ThemeEngine/include/IniParser.h index 5e27f6ed..3f7321b4 100644 --- a/lib/ThemeEngine/include/IniParser.h +++ b/lib/ThemeEngine/include/IniParser.h @@ -24,17 +24,15 @@ struct IniSection { }; class IniParser { -public: + public: // Parse a stream (File, Serial, etc.) - static std::map> - parse(Stream &stream); + static std::map> parse(Stream& stream); // Parse a string buffer (useful for testing) - static std::map> - parseString(const std::string &content); + static std::map> parseString(const std::string& content); -private: - static void trim(std::string &s); + private: + static void trim(std::string& s); }; -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/LayoutElements.h b/lib/ThemeEngine/include/LayoutElements.h index 337c3a00..050ef334 100644 --- a/lib/ThemeEngine/include/LayoutElements.h +++ b/lib/ThemeEngine/include/LayoutElements.h @@ -1,22 +1,23 @@ #pragma once +#include + #include "BasicElements.h" #include "ThemeContext.h" #include "ThemeTypes.h" #include "UIElement.h" -#include namespace ThemeEngine { // --- HStack: Horizontal Stack Layout --- // Children are arranged horizontally with optional spacing class HStack : public Container { - int spacing = 0; // Gap between children - int padding = 0; // Internal padding + int spacing = 0; // Gap between children + int padding = 0; // Internal padding bool centerVertical = false; -public: - HStack(const std::string &id) : Container(id) {} + public: + HStack(const std::string& id) : Container(id) {} ElementType getType() const override { return ElementType::HStack; } void setSpacing(int s) { @@ -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; @@ -68,8 +68,8 @@ class VStack : public Container { int padding = 0; bool centerHorizontal = false; -public: - VStack(const std::string &id) : Container(id) {} + public: + VStack(const std::string& id) : Container(id) {} ElementType getType() const override { return ElementType::VStack; } void setSpacing(int s) { @@ -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; @@ -120,8 +119,8 @@ class Grid : public Container { int colSpacing = 10; int padding = 0; -public: - Grid(const std::string &id) : Container(id) {} + public: + Grid(const std::string& id) : Container(id) {} ElementType getType() const override { return ElementType::Grid; } void setColumns(int c) { @@ -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) { @@ -184,27 +180,27 @@ class Badge : public UIElement { Expression bgColorExpr; Expression fgColorExpr; int fontId = 0; - int paddingH = 8; // Horizontal padding - int paddingV = 4; // Vertical padding + int paddingH = 8; // Horizontal padding + int paddingV = 4; // Vertical padding int cornerRadius = 0; -public: - Badge(const std::string &id) : UIElement(id) { - bgColorExpr = Expression::parse("0x00"); // Black background - fgColorExpr = Expression::parse("0xFF"); // White text + public: + Badge(const std::string& id) : UIElement(id) { + bgColorExpr = Expression::parse("0x00"); // Black background + fgColorExpr = Expression::parse("0xFF"); // White text } ElementType getType() const override { return ElementType::Badge; } - void setText(const std::string &expr) { + void setText(const std::string& expr) { textExpr = Expression::parse(expr); markDirty(); } - void setBgColor(const std::string &expr) { + void setBgColor(const std::string& expr) { bgColorExpr = Expression::parse(expr); markDirty(); } - void setFgColor(const std::string &expr) { + void setFgColor(const std::string& expr) { fgColorExpr = Expression::parse(expr); markDirty(); } @@ -221,9 +217,8 @@ public: markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + 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); @@ -264,31 +257,31 @@ public: // --- Toggle: On/Off Switch --- class Toggle : public UIElement { - Expression valueExpr; // Boolean expression + Expression valueExpr; // Boolean expression Expression onColorExpr; Expression offColorExpr; int trackWidth = 44; int trackHeight = 24; int knobSize = 20; -public: - Toggle(const std::string &id) : UIElement(id) { + public: + Toggle(const std::string& id) : UIElement(id) { valueExpr = Expression::parse("false"); - onColorExpr = Expression::parse("0x00"); // Black when on - offColorExpr = Expression::parse("0xFF"); // White when off + onColorExpr = Expression::parse("0x00"); // Black when on + offColorExpr = Expression::parse("0xFF"); // White when off } ElementType getType() const override { return ElementType::Toggle; } - void setValue(const std::string &expr) { + void setValue(const std::string& expr) { valueExpr = Expression::parse(expr); markDirty(); } - void setOnColor(const std::string &expr) { + void setOnColor(const std::string& expr) { onColorExpr = Expression::parse(expr); markDirty(); } - void setOffColor(const std::string &expr) { + void setOffColor(const std::string& expr) { offColorExpr = Expression::parse(expr); markDirty(); } @@ -305,15 +298,13 @@ public: markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + 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 @@ -338,17 +328,17 @@ public: // --- TabBar: Horizontal tab selection --- class TabBar : public Container { - Expression selectedExpr; // Currently selected tab index or name + Expression selectedExpr; // Currently selected tab index or name int tabSpacing = 0; int padding = 0; int indicatorHeight = 3; bool showIndicator = true; -public: - TabBar(const std::string &id) : Container(id) {} + public: + TabBar(const std::string& id) : Container(id) {} ElementType getType() const override { return ElementType::TabBar; } - void setSelected(const std::string &expr) { + void setSelected(const std::string& expr) { selectedExpr = Expression::parse(expr); markDirty(); } @@ -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(); @@ -389,9 +377,8 @@ public: } } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + if (!isVisible(context)) return; // Draw background if set if (hasBg) { @@ -419,7 +406,7 @@ public: } if (selectedIdx >= 0 && selectedIdx < static_cast(children.size())) { - UIElement *tab = children[selectedIdx]; + UIElement* tab = children[selectedIdx]; int indX = tab->getAbsX(); int indY = absY + absH - indicatorHeight; int indW = tab->getAbsW(); @@ -437,25 +424,25 @@ public: // --- Icon: Small symbolic image --- // Can be a built-in icon name or a path to a BMP class Icon : public UIElement { - Expression srcExpr; // Icon name or path + Expression srcExpr; // Icon name or path Expression colorExpr; int iconSize = 24; // Built-in icon names and their simple representations // In a real implementation, these would be actual bitmap data -public: - Icon(const std::string &id) : UIElement(id) { - colorExpr = Expression::parse("0x00"); // Black by default + public: + Icon(const std::string& id) : UIElement(id) { + colorExpr = Expression::parse("0x00"); // Black by default } ElementType getType() const override { return ElementType::Icon; } - void setSrc(const std::string &expr) { + void setSrc(const std::string& expr) { srcExpr = Expression::parse(expr); markDirty(); } - void setColorExpr(const std::string &expr) { + void setColorExpr(const std::string& expr) { colorExpr = Expression::parse(expr); markDirty(); } @@ -464,18 +451,18 @@ public: markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override; }; // --- ScrollIndicator: Visual scroll position --- class ScrollIndicator : public UIElement { - Expression positionExpr; // 0.0 to 1.0 - Expression totalExpr; // Total items - Expression visibleExpr; // Visible items + Expression positionExpr; // 0.0 to 1.0 + Expression totalExpr; // Total items + Expression visibleExpr; // Visible items int trackWidth = 4; -public: - ScrollIndicator(const std::string &id) : UIElement(id) { + public: + ScrollIndicator(const std::string& id) : UIElement(id) { positionExpr = Expression::parse("0"); totalExpr = Expression::parse("1"); visibleExpr = Expression::parse("1"); @@ -483,15 +470,15 @@ public: ElementType getType() const override { return ElementType::ScrollIndicator; } - void setPosition(const std::string &expr) { + void setPosition(const std::string& expr) { positionExpr = Expression::parse(expr); markDirty(); } - void setTotal(const std::string &expr) { + void setTotal(const std::string& expr) { totalExpr = Expression::parse(expr); markDirty(); } - void setVisibleCount(const std::string &expr) { + void setVisibleCount(const std::string& expr) { visibleExpr = Expression::parse(expr); markDirty(); } @@ -500,9 +487,8 @@ public: markDirty(); } - void draw(const GfxRenderer &renderer, const ThemeContext &context) override { - if (!isVisible(context)) - return; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override { + 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(visible) / static_cast(total); int thumbH = static_cast(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; @@ -540,4 +525,4 @@ public: } }; -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/ListElement.h b/lib/ThemeEngine/include/ListElement.h index 2e9ea4cd..b17c1275 100644 --- a/lib/ThemeEngine/include/ListElement.h +++ b/lib/ThemeEngine/include/ListElement.h @@ -1,57 +1,58 @@ #pragma once +#include +#include + #include "BasicElements.h" #include "UIElement.h" -#include -#include namespace ThemeEngine { // --- List --- // Supports vertical, horizontal, and grid layouts class List : public Container { -public: + public: enum class Direction { Vertical, Horizontal }; enum class LayoutMode { List, Grid }; -private: - std::string source; // Data source name (e.g., "MainMenu", "FileList") - std::string itemTemplateId; // ID of the template element - int itemWidth = 0; // Explicit item width (0 = auto) - int itemHeight = 0; // Explicit item height (0 = auto from template) - int scrollOffset = 0; // Scroll position for long lists - int visibleItems = -1; // Max visible items (-1 = auto) - int spacing = 0; // Gap between items - int columns = 1; // Number of columns (for grid mode) + private: + std::string source; // Data source name (e.g., "MainMenu", "FileList") + std::string itemTemplateId; // ID of the template element + int itemWidth = 0; // Explicit item width (0 = auto) + int itemHeight = 0; // Explicit item height (0 = auto from template) + int scrollOffset = 0; // Scroll position for long lists + int visibleItems = -1; // Max visible items (-1 = auto) + int spacing = 0; // Gap between items + int columns = 1; // Number of columns (for grid mode) Direction direction = Direction::Vertical; LayoutMode layoutMode = LayoutMode::List; // Template element reference (resolved after loading) - UIElement *itemTemplate = nullptr; + UIElement* itemTemplate = nullptr; -public: - List(const std::string &id) : Container(id) {} + public: + List(const std::string& id) : Container(id) {} ElementType getType() const override { return ElementType::List; } - void setSource(const std::string &s) { + void setSource(const std::string& s) { source = s; markDirty(); } - const std::string &getSource() const { return source; } + const std::string& getSource() const { return source; } - void setItemTemplateId(const std::string &id) { + void setItemTemplateId(const std::string& id) { itemTemplateId = id; markDirty(); } - void setItemTemplate(UIElement *elem) { + void setItemTemplate(UIElement* elem) { itemTemplate = elem; markDirty(); } - UIElement *getItemTemplate() const { return itemTemplate; } + UIElement* getItemTemplate() const { return itemTemplate; } void setItemWidth(int w) { itemWidth = w; @@ -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(); } @@ -108,7 +104,7 @@ public: markDirty(); } - void setDirectionFromString(const std::string &dir) { + void setDirectionFromString(const std::string& dir) { if (dir == "Horizontal" || dir == "horizontal" || dir == "row") { direction = Direction::Horizontal; } else { @@ -123,14 +119,13 @@ public: } // Resolve template reference from element map - void resolveTemplate(const std::map &elements) { + void resolveTemplate(const std::map& elements) { if (elements.count(itemTemplateId)) { itemTemplate = elements.at(itemTemplateId); } } - 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); @@ -142,7 +137,7 @@ public: } // Draw is implemented in BasicElements.cpp - void draw(const GfxRenderer &renderer, const ThemeContext &context) override; + void draw(const GfxRenderer& renderer, const ThemeContext& context) override; }; -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/ThemeContext.h b/lib/ThemeEngine/include/ThemeContext.h index 42c40361..1f03e654 100644 --- a/lib/ThemeEngine/include/ThemeContext.h +++ b/lib/ThemeEngine/include/ThemeContext.h @@ -11,22 +11,21 @@ namespace ThemeEngine { struct ExpressionToken { enum Type { LITERAL, VARIABLE }; Type type; - std::string value; // Literal text or variable name + std::string value; // Literal text or variable name }; // Pre-parsed expression for efficient repeated evaluation struct Expression { std::vector tokens; - std::string rawExpr; // Original expression string for complex evaluation + std::string rawExpr; // Original expression string for complex evaluation bool empty() const { return tokens.empty() && rawExpr.empty(); } - static Expression parse(const std::string &str) { + static Expression parse(const std::string& str) { 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; @@ -60,115 +57,94 @@ struct Expression { }; class ThemeContext { -private: + private: std::map strings; std::map ints; std::map bools; - const ThemeContext *parent = nullptr; + const ThemeContext* parent = nullptr; // Helper to trim whitespace - static std::string trim(const std::string &s) { + static std::string trim(const std::string& s) { size_t start = s.find_first_not_of(" \t\n\r"); - 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; + static bool isNumber(const std::string& s) { + 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(); } -public: - ThemeContext(const ThemeContext *parent = nullptr) : parent(parent) {} + public: + ThemeContext(const ThemeContext* parent = nullptr) : parent(parent) {} - 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; } + 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 { + 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 { + 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); + 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); return false; } // Get any value as string - std::string getAnyAsString(const std::string &key) const { + 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 ""; } // Evaluate a complex boolean expression // Supports: !, &&, ||, ==, !=, <, >, <=, >=, parentheses - bool evaluateBool(const std::string &expression) const { + 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 @@ -270,7 +241,7 @@ public: } // Compare two values (handles variables, numbers, strings) - int compareValues(const std::string &left, const std::string &right) const { + int compareValues(const std::string& left, const std::string& right) const { std::string leftVal = resolveValue(left); std::string rightVal = resolveValue(right); @@ -286,7 +257,7 @@ public: } // Resolve a value (variable name -> value, or literal) - std::string resolveValue(const std::string &val) const { + std::string resolveValue(const std::string& val) const { std::string v = trim(val); // Remove quotes for string literals @@ -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')) { @@ -326,12 +296,11 @@ public: } // Evaluate a string expression with variable substitution - std::string evaluatestring(const Expression &expr) const { - if (expr.empty()) - return ""; + std::string evaluatestring(const Expression& expr) const { + if (expr.empty()) return ""; std::string result; - for (const auto &token : expr.tokens) { + for (const auto& token : expr.tokens) { if (token.type == ExpressionToken::LITERAL) { result += token.value; } else { @@ -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; } @@ -355,7 +322,7 @@ public: std::string condition = trim(varName.substr(0, qPos)); std::string trueVal = trim(varName.substr(qPos + 1, cPos - qPos - 1)); std::string falseVal = trim(varName.substr(cPos + 1)); - + bool condResult = evaluateBool(condition); result += resolveValue(condResult ? trueVal : falseVal); continue; @@ -371,12 +338,11 @@ public: } // Legacy method for backward compatibility - std::string evaluateString(const std::string &expression) const { - if (expression.empty()) - return ""; + std::string evaluateString(const std::string& expression) const { + if (expression.empty()) return ""; Expression expr = Expression::parse(expression); return evaluatestring(expr); } }; -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/ThemeManager.h b/lib/ThemeEngine/include/ThemeManager.h index 65c99f9b..1bee3566 100644 --- a/lib/ThemeEngine/include/ThemeManager.h +++ b/lib/ThemeEngine/include/ThemeManager.h @@ -1,12 +1,14 @@ #pragma once +#include + +#include +#include +#include + #include "BasicElements.h" #include "IniParser.h" #include "ThemeContext.h" -#include -#include -#include -#include namespace ThemeEngine { @@ -18,10 +20,10 @@ struct ProcessedAsset { // Screen render cache - stores full screen state for quick restore struct ScreenCache { - uint8_t *buffer = nullptr; + uint8_t* buffer = nullptr; size_t bufferSize = 0; std::string screenName; - uint32_t contextHash = 0; // Hash of context data to detect changes + uint32_t contextHash = 0; // Hash of context data to detect changes bool valid = false; ~ScreenCache() { @@ -35,8 +37,8 @@ struct ScreenCache { }; class ThemeManager { -private: - std::map elements; // All elements by ID + private: + std::map elements; // All elements by ID std::string currentThemeName; int navBookCount = 1; // Number of navigable book slots (from theme [Global] section) std::map fontMap; @@ -49,12 +51,11 @@ private: std::map elementDependsOnData; // Factory and property methods - UIElement *createElement(const std::string &id, const std::string &type); - void applyProperties(UIElement *elem, - const std::map &props); + UIElement* createElement(const std::string& id, const std::string& type); + void applyProperties(UIElement* elem, const std::map& props); -public: - static ThemeManager &get() { + public: + static ThemeManager& get() { static ThemeManager instance; return instance; } @@ -63,66 +64,59 @@ public: void begin(); // Register a font ID mapping (e.g. "UI_12" -> 0) - void registerFont(const std::string &name, int id); + void registerFont(const std::string& name, int id); // Theme loading - void loadTheme(const std::string &themeName); + void loadTheme(const std::string& themeName); void unloadTheme(); // Get current theme name - const std::string &getCurrentTheme() const { return currentThemeName; } + const std::string& getCurrentTheme() const { return currentThemeName; } // Get number of navigable book slots (from theme config, default 1) 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, - const ThemeContext *prevContext = nullptr); + 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) void invalidateAllCaches(); // Invalidate specific screen cache - void invalidateScreenCache(const std::string &screenName); + void invalidateScreenCache(const std::string& screenName); // Enable/disable caching void setCachingEnabled(bool enabled) { useCaching = enabled; } bool isCachingEnabled() const { return useCaching; } // Asset path resolution - std::string getAssetPath(const std::string &assetName); + std::string getAssetPath(const std::string& assetName); // Asset caching - const std::vector *getCachedAsset(const std::string &path); - const ProcessedAsset *getProcessedAsset(const std::string &path, - GfxRenderer::Orientation orientation, + const std::vector* 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, - 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(); // Get element by ID (useful for direct manipulation) - UIElement *getElement(const std::string &id) { + UIElement* getElement(const std::string& id) { auto it = elements.find(id); return it != elements.end() ? it->second : nullptr; } -private: + private: std::map> assetCache; std::map 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 +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/ThemeTypes.h b/lib/ThemeEngine/include/ThemeTypes.h index 554c582f..d9d733f6 100644 --- a/lib/ThemeEngine/include/ThemeTypes.h +++ b/lib/ThemeEngine/include/ThemeTypes.h @@ -14,13 +14,11 @@ struct Dimension { Dimension(int v, DimensionUnit u) : value(v), unit(u) {} Dimension() : value(0), unit(DimensionUnit::PIXELS) {} - static Dimension parse(const std::string &str) { - if (str.empty()) - return Dimension(0, DimensionUnit::PIXELS); + static Dimension parse(const std::string& str) { + 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); } @@ -34,20 +32,16 @@ struct Dimension { }; struct Color { - uint8_t value; // For E-Ink: 0 (Black) to 255 (White), or simplified palette + uint8_t value; // For E-Ink: 0 (Black) to 255 (White), or simplified palette Color(uint8_t v) : value(v) {} Color() : 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); + 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.size() > 2 && str.substr(0, 2) == "0x") { return Color((uint8_t)std::strtol(str.c_str(), nullptr, 16)); } @@ -64,16 +58,13 @@ struct Rect { bool isEmpty() const { return w <= 0 || h <= 0; } - bool intersects(const Rect &other) const { - return !(x + w <= other.x || other.x + other.w <= x || y + h <= other.y || - other.y + other.h <= y); + 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); } - Rect unite(const Rect &other) const { - if (isEmpty()) - return other; - if (other.isEmpty()) - return *this; + Rect unite(const Rect& other) const { + 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); @@ -82,4 +73,4 @@ struct Rect { } }; -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/include/UIElement.h b/lib/ThemeEngine/include/UIElement.h index 306c4548..ac53f7ac 100644 --- a/lib/ThemeEngine/include/UIElement.h +++ b/lib/ThemeEngine/include/UIElement.h @@ -1,52 +1,51 @@ #pragma once -#include "ThemeContext.h" -#include "ThemeTypes.h" #include + #include #include +#include "ThemeContext.h" +#include "ThemeTypes.h" + namespace ThemeEngine { -class Container; // Forward declaration +class Container; // Forward declaration class UIElement { -public: + public: int getAbsX() const { return absX; } int getAbsY() const { return absY; } int getAbsW() const { return absW; } int getAbsH() const { return absH; } - const std::string &getId() const { return id; } + const std::string& getId() const { return id; } -protected: + protected: std::string id; Dimension x, y, width, height; Expression visibleExpr; - bool visibleExprIsStatic = true; // True if visibility doesn't depend on data + bool visibleExprIsStatic = true; // True if visibility doesn't depend on data // Recomputed every layout pass int absX = 0, absY = 0, absW = 0, absH = 0; // Caching support - bool cacheable = false; // Set true for expensive elements like bitmaps - bool cacheValid = false; // Is the cached render still valid? - uint8_t *cachedRender = nullptr; + bool cacheable = false; // Set true for expensive elements like bitmaps + bool cacheValid = false; // Is the cached render still valid? + uint8_t* cachedRender = nullptr; size_t cachedRenderSize = 0; int cachedX = 0, cachedY = 0, cachedW = 0, cachedH = 0; // Dirty tracking - bool dirty = true; // Needs redraw + bool dirty = true; // Needs redraw - bool isVisible(const ThemeContext &context) const { - if (visibleExpr.empty()) - return true; + bool isVisible(const ThemeContext& context) const { + if (visibleExpr.empty()) return true; return context.evaluateBool(visibleExpr.rawExpr); } -public: - UIElement(const std::string &id) : id(id) { - visibleExpr = Expression::parse("true"); - } + public: + UIElement(const std::string& id) : id(id) { visibleExpr = Expression::parse("true"); } virtual ~UIElement() { if (cachedRender) { @@ -71,11 +70,11 @@ public: height = val; markDirty(); } - void setVisibleExpr(const std::string &expr) { + 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) { @@ -133,7 +125,7 @@ public: } } - virtual Container *asContainer() { return nullptr; } + virtual Container* asContainer() { return nullptr; } enum class ElementType { Base, @@ -166,12 +158,11 @@ 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: + protected: // Cache the rendered output - bool cacheRender(const GfxRenderer &renderer) { + bool cacheRender(const GfxRenderer& renderer) { if (cachedRender) { free(cachedRender); cachedRender = nullptr; @@ -190,16 +181,13 @@ 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; + bool restoreFromCache(const GfxRenderer& renderer) const { + 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; } }; -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/src/BasicElements.cpp b/lib/ThemeEngine/src/BasicElements.cpp index 1f180076..8fed357a 100644 --- a/lib/ThemeEngine/src/BasicElements.cpp +++ b/lib/ThemeEngine/src/BasicElements.cpp @@ -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,13 +21,12 @@ 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; for (int y = 0; y < absH; y++) { - const uint8_t *srcRow = processed->data.data() + y * rowBytes; + const uint8_t* srcRow = processed->data.data() + y * rowBytes; for (int x = 0; x < absW; x++) { bool isBlack = !(srcRow[x / 8] & (1 << (7 - (x % 8)))); if (isBlack) { @@ -40,7 +39,7 @@ void BitmapElement::draw(const GfxRenderer &renderer, } // Load raw asset from cache (file data cached in memory) - const std::vector *data = ThemeManager::get().getCachedAsset(path); + const std::vector* data = ThemeManager::get().getCachedAsset(path); if (!data || data->empty()) { markClean(); return; @@ -54,19 +53,19 @@ void BitmapElement::draw(const GfxRenderer &renderer, // Draw the bitmap (handles scaling internally) renderer.drawBitmap(bmp, absX, absY, absW, absH); - + // Cache the result as 1-bit packed data for next time ProcessedAsset asset; asset.w = absW; asset.h = absH; asset.orientation = renderer.getOrientation(); - + const int rowBytes = (absW + 7) / 8; - asset.data.resize(rowBytes * absH, 0xFF); // Initialize to white - + asset.data.resize(rowBytes * absH, 0xFF); // Initialize to white + // Capture pixels using renderer's coordinate system for (int y = 0; y < absH; y++) { - uint8_t *dstRow = asset.data.data() + y * rowBytes; + uint8_t* dstRow = asset.data.data() + y * rowBytes; for (int x = 0; x < absW; x++) { // Read pixel from framebuffer (this handles orientation) bool isBlack = renderer.readPixel(absX + x, absY + y); @@ -75,14 +74,14 @@ void BitmapElement::draw(const GfxRenderer &renderer, } } } - + ThemeManager::get().cacheProcessedAsset(path, asset, absW, absH); markClean(); } // --- List --- -void List::draw(const GfxRenderer &renderer, const ThemeContext &context) { +void List::draw(const GfxRenderer& renderer, const ThemeContext& context) { if (!isVisible(context)) { markClean(); return; @@ -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 @@ -225,4 +222,4 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) { markClean(); } -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/src/IniParser.cpp b/lib/ThemeEngine/src/IniParser.cpp index 3d44cf54..022aa805 100644 --- a/lib/ThemeEngine/src/IniParser.cpp +++ b/lib/ThemeEngine/src/IniParser.cpp @@ -1,11 +1,11 @@ #include "IniParser.h" + #include namespace ThemeEngine { -void IniParser::trim(std::string &s) { - if (s.empty()) - return; +void IniParser::trim(std::string& s) { + if (s.empty()) return; // Trim left size_t first = s.find_first_not_of(" \t\n\r"); @@ -19,14 +19,13 @@ void IniParser::trim(std::string &s) { s = s.substr(first, (last - first + 1)); } -std::map> -IniParser::parse(Stream &stream) { +std::map> IniParser::parse(Stream& stream) { std::map> sections; // stream check not strictly possible like file, can rely on available() std::string currentSection = ""; - String line; // Use Arduino String for easy file reading, then convert to - // std::string + String line; // Use Arduino String for easy file reading, then convert to + // std::string while (stream.available()) { line = stream.readStringUntil('\n'); @@ -34,7 +33,7 @@ IniParser::parse(Stream &stream) { trim(sLine); if (sLine.empty() || sLine[0] == ';' || sLine[0] == '#') { - continue; // Skip comments and empty lines + continue; // Skip comments and empty lines } if (sLine.front() == '[' && sLine.back() == ']') { @@ -62,8 +61,7 @@ IniParser::parse(Stream &stream) { return sections; } -std::map> -IniParser::parseString(const std::string &content) { +std::map> IniParser::parseString(const std::string& content) { std::map> sections; std::stringstream ss(content); std::string line; @@ -101,4 +99,4 @@ IniParser::parseString(const std::string &content) { return sections; } -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/src/LayoutElements.cpp b/lib/ThemeEngine/src/LayoutElements.cpp index 5fd04d5a..5b08af92 100644 --- a/lib/ThemeEngine/src/LayoutElements.cpp +++ b/lib/ThemeEngine/src/LayoutElements.cpp @@ -1,12 +1,14 @@ #include "LayoutElements.h" -#include "ThemeManager.h" + #include +#include "ThemeManager.h" + namespace ThemeEngine { // Built-in icon drawing // These are simple geometric representations of common icons -void Icon::draw(const GfxRenderer &renderer, const ThemeContext &context) { +void Icon::draw(const GfxRenderer& renderer, const ThemeContext& context) { if (!isVisible(context)) { markClean(); return; @@ -29,15 +31,14 @@ 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] != '/') { path = ThemeManager::get().getAssetPath(iconName); } - const std::vector *data = ThemeManager::get().getCachedAsset(path); + const std::vector* data = ThemeManager::get().getCachedAsset(path); if (data && !data->empty()) { Bitmap bmp(data->data(), data->size()); if (bmp.parseHeaders() == BmpReaderError::Ok) { @@ -170,4 +171,4 @@ void Icon::draw(const GfxRenderer &renderer, const ThemeContext &context) { markClean(); } -} // namespace ThemeEngine +} // namespace ThemeEngine diff --git a/lib/ThemeEngine/src/ThemeManager.cpp b/lib/ThemeEngine/src/ThemeManager.cpp index 24330167..6e706bc4 100644 --- a/lib/ThemeEngine/src/ThemeManager.cpp +++ b/lib/ThemeEngine/src/ThemeManager.cpp @@ -1,82 +1,63 @@ #include "ThemeManager.h" -#include "DefaultTheme.h" -#include "LayoutElements.h" -#include "ListElement.h" + #include + #include #include #include +#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) { +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 &props) { - +void ThemeManager::applyProperties(UIElement* elem, const std::map& props) { const auto elemType = elem->getType(); - - for (const auto &kv : props) { - const std::string &key = kv.first; - const std::string &val = kv.second; + + for (const auto& kv : props) { + const std::string& key = kv.first; + const std::string& val = kv.second; // ========== Common properties ========== if (key == "X") @@ -95,7 +76,7 @@ void ThemeManager::applyProperties( // ========== Rectangle properties ========== else if (key == "Fill") { if (elemType == UIElement::ElementType::Rectangle) { - auto rect = static_cast(elem); + auto rect = static_cast(elem); if (val.find('{') != std::string::npos) { rect->setFillExpr(val); } else { @@ -104,21 +85,19 @@ void ThemeManager::applyProperties( } } else if (key == "Color") { if (elemType == UIElement::ElementType::Rectangle) { - static_cast(elem)->setColorExpr(val); - } else if (elemType == UIElement::ElementType::Container || - elemType == UIElement::ElementType::HStack || - elemType == UIElement::ElementType::VStack || - elemType == UIElement::ElementType::Grid || + static_cast(elem)->setColorExpr(val); + } else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack || + elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid || elemType == UIElement::ElementType::TabBar) { - static_cast(elem)->setBackgroundColorExpr(val); + static_cast(elem)->setBackgroundColorExpr(val); } else if (elemType == UIElement::ElementType::Label) { - static_cast