mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 22:57:50 +03:00
fix: reset clang correctly
This commit is contained in:
parent
1fab1f9ec5
commit
dccd200b86
@ -1,8 +1,10 @@
|
||||
#include "Bitmap.h"
|
||||
#include "BitmapHelpers.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "BitmapHelpers.h"
|
||||
|
||||
// ============================================================================
|
||||
// IMAGE PROCESSING OPTIONS
|
||||
// ============================================================================
|
||||
@ -36,8 +38,7 @@ size_t Bitmap::readBytes(void *buf, size_t count) const {
|
||||
return file->read(buf, count);
|
||||
} else if (memoryBuffer) {
|
||||
size_t available = memorySize - bufferPos;
|
||||
if (count > available)
|
||||
count = available;
|
||||
if (count > available) count = available;
|
||||
memcpy(buf, memoryBuffer + bufferPos, count);
|
||||
bufferPos += count;
|
||||
return count;
|
||||
@ -74,8 +75,7 @@ bool Bitmap::seekCur(int32_t offset) const {
|
||||
uint16_t Bitmap::readLE16() {
|
||||
const int c0 = readByte();
|
||||
const int c1 = readByte();
|
||||
return static_cast<uint16_t>(c0 & 0xFF) |
|
||||
(static_cast<uint16_t>(c1 & 0xFF) << 8);
|
||||
return static_cast<uint16_t>(c0 & 0xFF) | (static_cast<uint16_t>(c1 & 0xFF) << 8);
|
||||
}
|
||||
|
||||
uint32_t Bitmap::readLE32() {
|
||||
@ -83,10 +83,8 @@ uint32_t Bitmap::readLE32() {
|
||||
const int c1 = readByte();
|
||||
const int c2 = readByte();
|
||||
const int c3 = readByte();
|
||||
return static_cast<uint32_t>(c0 & 0xFF) |
|
||||
(static_cast<uint32_t>(c1 & 0xFF) << 8) |
|
||||
(static_cast<uint32_t>(c2 & 0xFF) << 16) |
|
||||
(static_cast<uint32_t>(c3 & 0xFF) << 24);
|
||||
return static_cast<uint32_t>(c0 & 0xFF) | (static_cast<uint32_t>(c1 & 0xFF) << 8) |
|
||||
(static_cast<uint32_t>(c2 & 0xFF) << 16) | (static_cast<uint32_t>(c3 & 0xFF) << 24);
|
||||
}
|
||||
|
||||
const char* Bitmap::errorToString(BmpReaderError err) {
|
||||
@ -126,21 +124,17 @@ const char *Bitmap::errorToString(BmpReaderError err) {
|
||||
}
|
||||
|
||||
BmpReaderError Bitmap::parseHeaders() {
|
||||
if (!file && !memoryBuffer)
|
||||
return BmpReaderError::FileInvalid;
|
||||
if (!seekSet(0))
|
||||
return BmpReaderError::SeekStartFailed;
|
||||
if (!file && !memoryBuffer) return BmpReaderError::FileInvalid;
|
||||
if (!seekSet(0)) return BmpReaderError::SeekStartFailed;
|
||||
|
||||
const uint16_t bfType = readLE16();
|
||||
if (bfType != 0x4D42)
|
||||
return BmpReaderError::NotBMP;
|
||||
if (bfType != 0x4D42) return BmpReaderError::NotBMP;
|
||||
|
||||
seekCur(8);
|
||||
bfOffBits = readLE32();
|
||||
|
||||
const uint32_t biSize = readLE32();
|
||||
if (biSize < 40)
|
||||
return BmpReaderError::DIBTooSmall;
|
||||
if (biSize < 40) return BmpReaderError::DIBTooSmall;
|
||||
|
||||
width = static_cast<int32_t>(readLE32());
|
||||
const auto rawHeight = static_cast<int32_t>(readLE32());
|
||||
@ -150,24 +144,18 @@ BmpReaderError Bitmap::parseHeaders() {
|
||||
const uint16_t planes = readLE16();
|
||||
bpp = readLE16();
|
||||
const uint32_t comp = readLE32();
|
||||
const bool validBpp =
|
||||
bpp == 1 || bpp == 2 || bpp == 8 || bpp == 24 || bpp == 32;
|
||||
const bool validBpp = bpp == 1 || bpp == 2 || bpp == 8 || bpp == 24 || bpp == 32;
|
||||
|
||||
if (planes != 1)
|
||||
return BmpReaderError::BadPlanes;
|
||||
if (!validBpp)
|
||||
return BmpReaderError::UnsupportedBpp;
|
||||
if (!(comp == 0 || (bpp == 32 && comp == 3)))
|
||||
return BmpReaderError::UnsupportedCompression;
|
||||
if (planes != 1) return BmpReaderError::BadPlanes;
|
||||
if (!validBpp) return BmpReaderError::UnsupportedBpp;
|
||||
if (!(comp == 0 || (bpp == 32 && comp == 3))) return BmpReaderError::UnsupportedCompression;
|
||||
|
||||
seekCur(12);
|
||||
const uint32_t colorsUsed = readLE32();
|
||||
if (colorsUsed > 256u)
|
||||
return BmpReaderError::PaletteTooLarge;
|
||||
if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge;
|
||||
seekCur(4);
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
return BmpReaderError::BadDimensions;
|
||||
if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions;
|
||||
|
||||
constexpr int MAX_IMAGE_WIDTH = 2048;
|
||||
constexpr int MAX_IMAGE_HEIGHT = 3072;
|
||||
@ -177,8 +165,7 @@ BmpReaderError Bitmap::parseHeaders() {
|
||||
|
||||
rowBytes = (width * bpp + 31) / 32 * 4;
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
paletteLum[i] = static_cast<uint8_t>(i);
|
||||
for (int i = 0; i < 256; i++) paletteLum[i] = static_cast<uint8_t>(i);
|
||||
if (colorsUsed > 0) {
|
||||
for (uint32_t i = 0; i < colorsUsed; i++) {
|
||||
uint8_t rgb[4];
|
||||
@ -187,8 +174,7 @@ BmpReaderError Bitmap::parseHeaders() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!seekSet(bfOffBits))
|
||||
return BmpReaderError::SeekPixelDataFailed;
|
||||
if (!seekSet(bfOffBits)) return BmpReaderError::SeekPixelDataFailed;
|
||||
|
||||
if (bpp > 2 && dithering) {
|
||||
if (USE_ATKINSON) {
|
||||
@ -202,8 +188,7 @@ BmpReaderError Bitmap::parseHeaders() {
|
||||
}
|
||||
|
||||
BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const {
|
||||
if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes)
|
||||
return BmpReaderError::ShortReadRow;
|
||||
if (readBytes(rowBuffer, rowBytes) != (size_t)rowBytes) return BmpReaderError::ShortReadRow;
|
||||
|
||||
prevRowY += 1;
|
||||
uint8_t* outPtr = data;
|
||||
@ -255,14 +240,12 @@ BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const {
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
for (int x = 0; x < width; x++)
|
||||
packPixel(paletteLum[rowBuffer[x]]);
|
||||
for (int x = 0; x < width; x++) packPixel(paletteLum[rowBuffer[x]]);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint8_t lum =
|
||||
paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03];
|
||||
uint8_t lum = paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03];
|
||||
packPixel(lum);
|
||||
}
|
||||
break;
|
||||
@ -283,17 +266,13 @@ BmpReaderError Bitmap::readNextRow(uint8_t *data, uint8_t *rowBuffer) const {
|
||||
else if (fsDitherer)
|
||||
fsDitherer->nextRow();
|
||||
|
||||
if (bitShift != 6)
|
||||
*outPtr = currentOutByte;
|
||||
if (bitShift != 6) *outPtr = currentOutByte;
|
||||
return BmpReaderError::Ok;
|
||||
}
|
||||
|
||||
BmpReaderError Bitmap::rewindToData() const {
|
||||
if (!seekSet(bfOffBits))
|
||||
return BmpReaderError::SeekPixelDataFailed;
|
||||
if (fsDitherer)
|
||||
fsDitherer->reset();
|
||||
if (atkinsonDitherer)
|
||||
atkinsonDitherer->reset();
|
||||
if (!seekSet(bfOffBits)) return BmpReaderError::SeekPixelDataFailed;
|
||||
if (fsDitherer) fsDitherer->reset();
|
||||
if (atkinsonDitherer) atkinsonDitherer->reset();
|
||||
return BmpReaderError::Ok;
|
||||
}
|
||||
|
||||
@ -32,11 +32,9 @@ class Bitmap {
|
||||
public:
|
||||
static const char* errorToString(BmpReaderError err);
|
||||
|
||||
explicit Bitmap(FsFile &file, bool dithering = false)
|
||||
: file(&file), dithering(dithering) {}
|
||||
explicit Bitmap(FsFile& file, bool dithering = false) : file(&file), dithering(dithering) {}
|
||||
explicit Bitmap(const uint8_t* buffer, size_t size, bool dithering = false)
|
||||
: file(nullptr), memoryBuffer(buffer), memorySize(size),
|
||||
dithering(dithering) {}
|
||||
: file(nullptr), memoryBuffer(buffer), memorySize(size), dithering(dithering) {}
|
||||
|
||||
~Bitmap();
|
||||
BmpReaderError parseHeaders();
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <Bitmap.h>
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ThemeContext.h"
|
||||
#include "ThemeTypes.h"
|
||||
#include "UIElement.h"
|
||||
#include <Bitmap.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
@ -21,12 +23,9 @@ protected:
|
||||
int borderRadius = 0; // Corner radius (for future rounded rect support)
|
||||
|
||||
public:
|
||||
Container(const std::string &id) : UIElement(id) {
|
||||
bgColorExpr = Expression::parse("0xFF");
|
||||
}
|
||||
Container(const std::string& id) : UIElement(id) { bgColorExpr = Expression::parse("0xFF"); }
|
||||
virtual ~Container() {
|
||||
for (auto child : children)
|
||||
delete child;
|
||||
for (auto child : children) delete child;
|
||||
}
|
||||
|
||||
Container* asContainer() override { return this; }
|
||||
@ -69,8 +68,7 @@ public:
|
||||
|
||||
int getBorderRadius() const { return borderRadius; }
|
||||
|
||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
||||
int parentW, int parentH) override {
|
||||
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||
// Children are laid out with padding offset
|
||||
int childX = absX + padding;
|
||||
@ -90,8 +88,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
if (hasBg) {
|
||||
std::string colStr = context.evaluatestring(bgColorExpr);
|
||||
@ -148,9 +145,7 @@ class Rectangle : public UIElement {
|
||||
Expression colorExpr;
|
||||
|
||||
public:
|
||||
Rectangle(const std::string &id) : UIElement(id) {
|
||||
colorExpr = Expression::parse("0x00");
|
||||
}
|
||||
Rectangle(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
|
||||
ElementType getType() const override { return ElementType::Rectangle; }
|
||||
|
||||
void setFill(bool f) {
|
||||
@ -169,8 +164,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
std::string colStr = context.evaluatestring(colorExpr);
|
||||
uint8_t color = Color::parse(colStr).value;
|
||||
@ -205,9 +199,7 @@ private:
|
||||
bool ellipsis = true; // Truncate with ... if too long
|
||||
|
||||
public:
|
||||
Label(const std::string &id) : UIElement(id) {
|
||||
colorExpr = Expression::parse("0x00");
|
||||
}
|
||||
Label(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
|
||||
ElementType getType() const override { return ElementType::Label; }
|
||||
|
||||
void setText(const std::string& expr) {
|
||||
@ -240,8 +232,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
std::string finalText = context.evaluatestring(textExpr);
|
||||
if (finalText.empty()) {
|
||||
@ -353,22 +344,18 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
std::string valStr = context.evaluatestring(valueExpr);
|
||||
std::string maxStr = context.evaluatestring(maxExpr);
|
||||
|
||||
int value = valStr.empty() ? 0 : std::stoi(valStr);
|
||||
int maxVal = maxStr.empty() ? 100 : std::stoi(maxStr);
|
||||
if (maxVal <= 0)
|
||||
maxVal = 100;
|
||||
if (maxVal <= 0) maxVal = 100;
|
||||
|
||||
float ratio = static_cast<float>(value) / static_cast<float>(maxVal);
|
||||
if (ratio < 0)
|
||||
ratio = 0;
|
||||
if (ratio > 1)
|
||||
ratio = 1;
|
||||
if (ratio < 0) ratio = 0;
|
||||
if (ratio > 1) ratio = 1;
|
||||
|
||||
// Draw background
|
||||
std::string bgStr = context.evaluatestring(bgColorExpr);
|
||||
@ -399,9 +386,7 @@ class Divider : public UIElement {
|
||||
int thickness = 1;
|
||||
|
||||
public:
|
||||
Divider(const std::string &id) : UIElement(id) {
|
||||
colorExpr = Expression::parse("0x00");
|
||||
}
|
||||
Divider(const std::string& id) : UIElement(id) { colorExpr = Expression::parse("0x00"); }
|
||||
|
||||
ElementType getType() const override { return ElementType::Divider; }
|
||||
|
||||
@ -419,8 +404,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
std::string colStr = context.evaluatestring(colorExpr);
|
||||
uint8_t color = Color::parse(colStr).value;
|
||||
@ -464,8 +448,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
std::string valStr = context.evaluatestring(valueExpr);
|
||||
int percentage = valStr.empty() ? 0 : std::stoi(valStr);
|
||||
@ -480,22 +463,17 @@ public:
|
||||
int x = absX;
|
||||
int y = absY;
|
||||
|
||||
if (absW > batteryWidth)
|
||||
x += (absW - batteryWidth) / 2;
|
||||
if (absH > batteryHeight)
|
||||
y += (absH - batteryHeight) / 2;
|
||||
if (absW > batteryWidth) x += (absW - batteryWidth) / 2;
|
||||
if (absH > batteryHeight) y += (absH - batteryHeight) / 2;
|
||||
|
||||
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y, black);
|
||||
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3,
|
||||
y + batteryHeight - 1, black);
|
||||
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1, black);
|
||||
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2, black);
|
||||
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2,
|
||||
y + batteryHeight - 2, black);
|
||||
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2, black);
|
||||
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + 3, black);
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4, black);
|
||||
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0,
|
||||
y + batteryHeight - 5, black);
|
||||
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5, black);
|
||||
|
||||
if (percentage > 0) {
|
||||
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
|
||||
|
||||
@ -26,12 +26,10 @@ struct IniSection {
|
||||
class IniParser {
|
||||
public:
|
||||
// Parse a stream (File, Serial, etc.)
|
||||
static std::map<std::string, std::map<std::string, std::string>>
|
||||
parse(Stream &stream);
|
||||
static std::map<std::string, std::map<std::string, std::string>> parse(Stream& stream);
|
||||
|
||||
// Parse a string buffer (useful for testing)
|
||||
static std::map<std::string, std::map<std::string, std::string>>
|
||||
parseString(const std::string &content);
|
||||
static std::map<std::string, std::map<std::string, std::string>> parseString(const std::string& content);
|
||||
|
||||
private:
|
||||
static void trim(std::string& s);
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "BasicElements.h"
|
||||
#include "ThemeContext.h"
|
||||
#include "ThemeTypes.h"
|
||||
#include "UIElement.h"
|
||||
#include <vector>
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
@ -32,8 +33,7 @@ public:
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
||||
int parentW, int parentH) override {
|
||||
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||
|
||||
int currentX = absX + padding;
|
||||
@ -85,8 +85,7 @@ public:
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
||||
int parentW, int parentH) override {
|
||||
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||
|
||||
int currentY = absY + padding;
|
||||
@ -141,12 +140,10 @@ public:
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
||||
int parentW, int parentH) override {
|
||||
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||
|
||||
if (children.empty())
|
||||
return;
|
||||
if (children.empty()) return;
|
||||
|
||||
int availableW = absW - 2 * padding - (columns - 1) * colSpacing;
|
||||
int cellW = availableW / columns;
|
||||
@ -162,8 +159,7 @@ public:
|
||||
// Pass cell dimensions to avoid clamping issues
|
||||
child->layout(context, cellX, currentY, cellW, availableH);
|
||||
int childH = child->getAbsH();
|
||||
if (childH > maxRowHeight)
|
||||
maxRowHeight = childH;
|
||||
if (childH > maxRowHeight) maxRowHeight = childH;
|
||||
|
||||
col++;
|
||||
if (col >= columns) {
|
||||
@ -222,8 +218,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
std::string text = context.evaluatestring(textExpr);
|
||||
if (text.empty()) {
|
||||
@ -238,10 +233,8 @@ public:
|
||||
int badgeH = textH + 2 * paddingV;
|
||||
|
||||
// Use absX, absY as position, but we may auto-size
|
||||
if (absW == 0)
|
||||
absW = badgeW;
|
||||
if (absH == 0)
|
||||
absH = badgeH;
|
||||
if (absW == 0) absW = badgeW;
|
||||
if (absH == 0) absH = badgeH;
|
||||
|
||||
// Draw background
|
||||
std::string bgStr = context.evaluatestring(bgColorExpr);
|
||||
@ -306,14 +299,12 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
bool isOn = context.evaluateBool(valueExpr.rawExpr);
|
||||
|
||||
// Get colors
|
||||
std::string colorStr =
|
||||
isOn ? context.evaluatestring(onColorExpr) : context.evaluatestring(offColorExpr);
|
||||
std::string colorStr = isOn ? context.evaluatestring(onColorExpr) : context.evaluatestring(offColorExpr);
|
||||
uint8_t trackColor = Color::parse(colorStr).value;
|
||||
|
||||
// Draw track
|
||||
@ -324,8 +315,7 @@ public:
|
||||
|
||||
// Draw knob
|
||||
int knobMargin = (trackHeight - knobSize) / 2;
|
||||
int knobX = isOn ? (trackX + trackWidth - knobSize - knobMargin)
|
||||
: (trackX + knobMargin);
|
||||
int knobX = isOn ? (trackX + trackWidth - knobSize - knobMargin) : (trackX + knobMargin);
|
||||
int knobY = trackY + knobMargin;
|
||||
|
||||
// Knob is opposite color of track
|
||||
@ -369,12 +359,10 @@ public:
|
||||
markDirty();
|
||||
}
|
||||
|
||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
||||
int parentW, int parentH) override {
|
||||
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||
|
||||
if (children.empty())
|
||||
return;
|
||||
if (children.empty()) return;
|
||||
|
||||
// Distribute tabs evenly
|
||||
int numTabs = children.size();
|
||||
@ -390,8 +378,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
// Draw background if set
|
||||
if (hasBg) {
|
||||
@ -501,8 +488,7 @@ public:
|
||||
}
|
||||
|
||||
void draw(const GfxRenderer& renderer, const ThemeContext& context) override {
|
||||
if (!isVisible(context))
|
||||
return;
|
||||
if (!isVisible(context)) return;
|
||||
|
||||
// Get values
|
||||
std::string posStr = context.evaluatestring(positionExpr);
|
||||
@ -526,8 +512,7 @@ public:
|
||||
// Calculate thumb size and position
|
||||
float ratio = static_cast<float>(visible) / static_cast<float>(total);
|
||||
int thumbH = static_cast<int>(absH * ratio);
|
||||
if (thumbH < 20)
|
||||
thumbH = 20; // Minimum thumb size
|
||||
if (thumbH < 20) thumbH = 20; // Minimum thumb size
|
||||
|
||||
int maxScroll = total - visible;
|
||||
float scrollRatio = maxScroll > 0 ? position / maxScroll : 0;
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "BasicElements.h"
|
||||
#include "UIElement.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
@ -64,18 +65,14 @@ public:
|
||||
}
|
||||
|
||||
int getItemHeight() const {
|
||||
if (itemHeight > 0)
|
||||
return itemHeight;
|
||||
if (itemTemplate)
|
||||
return itemTemplate->getAbsH() > 0 ? itemTemplate->getAbsH() : 45;
|
||||
if (itemHeight > 0) return itemHeight;
|
||||
if (itemTemplate) return itemTemplate->getAbsH() > 0 ? itemTemplate->getAbsH() : 45;
|
||||
return 45;
|
||||
}
|
||||
|
||||
int getItemWidth() const {
|
||||
if (itemWidth > 0)
|
||||
return itemWidth;
|
||||
if (itemTemplate)
|
||||
return itemTemplate->getAbsW() > 0 ? itemTemplate->getAbsW() : 100;
|
||||
if (itemWidth > 0) return itemWidth;
|
||||
if (itemTemplate) return itemTemplate->getAbsW() > 0 ? itemTemplate->getAbsW() : 100;
|
||||
return 100;
|
||||
}
|
||||
|
||||
@ -98,8 +95,7 @@ public:
|
||||
|
||||
void setColumns(int c) {
|
||||
columns = c > 0 ? c : 1;
|
||||
if (columns > 1)
|
||||
layoutMode = LayoutMode::Grid;
|
||||
if (columns > 1) layoutMode = LayoutMode::Grid;
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@ -129,8 +125,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void layout(const ThemeContext &context, int parentX, int parentY,
|
||||
int parentW, int parentH) override {
|
||||
void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) override {
|
||||
// Layout self first (bounds)
|
||||
UIElement::layout(context, parentX, parentY, parentW, parentH);
|
||||
|
||||
|
||||
@ -25,8 +25,7 @@ struct Expression {
|
||||
Expression expr;
|
||||
expr.rawExpr = str;
|
||||
|
||||
if (str.empty())
|
||||
return expr;
|
||||
if (str.empty()) return expr;
|
||||
|
||||
size_t start = 0;
|
||||
while (start < str.length()) {
|
||||
@ -39,8 +38,7 @@ struct Expression {
|
||||
|
||||
if (open > start) {
|
||||
// Literal before variable
|
||||
expr.tokens.push_back(
|
||||
{ExpressionToken::LITERAL, str.substr(start, open - start)});
|
||||
expr.tokens.push_back({ExpressionToken::LITERAL, str.substr(start, open - start)});
|
||||
}
|
||||
|
||||
size_t close = str.find('}', open);
|
||||
@ -51,8 +49,7 @@ struct Expression {
|
||||
}
|
||||
|
||||
// Variable
|
||||
expr.tokens.push_back(
|
||||
{ExpressionToken::VARIABLE, str.substr(open + 1, close - open - 1)});
|
||||
expr.tokens.push_back({ExpressionToken::VARIABLE, str.substr(open + 1, close - open - 1)});
|
||||
start = close + 1;
|
||||
}
|
||||
return expr;
|
||||
@ -70,20 +67,17 @@ private:
|
||||
// Helper to trim whitespace
|
||||
static std::string trim(const std::string& s) {
|
||||
size_t start = s.find_first_not_of(" \t\n\r");
|
||||
if (start == std::string::npos)
|
||||
return "";
|
||||
if (start == std::string::npos) return "";
|
||||
size_t end = s.find_last_not_of(" \t\n\r");
|
||||
return s.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
// Helper to check if string is a number
|
||||
static bool isNumber(const std::string& s) {
|
||||
if (s.empty())
|
||||
return false;
|
||||
if (s.empty()) return false;
|
||||
size_t start = (s[0] == '-') ? 1 : 0;
|
||||
for (size_t i = start; i < s.length(); i++) {
|
||||
if (!isdigit(s[i]))
|
||||
return false;
|
||||
if (!isdigit(s[i])) return false;
|
||||
}
|
||||
return start < s.length();
|
||||
}
|
||||
@ -91,45 +85,34 @@ private:
|
||||
public:
|
||||
ThemeContext(const ThemeContext* parent = nullptr) : parent(parent) {}
|
||||
|
||||
void setString(const std::string &key, const std::string &value) {
|
||||
strings[key] = value;
|
||||
}
|
||||
void setString(const std::string& key, const std::string& value) { strings[key] = value; }
|
||||
void setInt(const std::string& key, int value) { ints[key] = value; }
|
||||
void setBool(const std::string& key, bool value) { bools[key] = value; }
|
||||
|
||||
std::string getString(const std::string &key,
|
||||
const std::string &defaultValue = "") const {
|
||||
std::string getString(const std::string& key, const std::string& defaultValue = "") const {
|
||||
auto it = strings.find(key);
|
||||
if (it != strings.end())
|
||||
return it->second;
|
||||
if (parent)
|
||||
return parent->getString(key, defaultValue);
|
||||
if (it != strings.end()) return it->second;
|
||||
if (parent) return parent->getString(key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
int getInt(const std::string& key, int defaultValue = 0) const {
|
||||
auto it = ints.find(key);
|
||||
if (it != ints.end())
|
||||
return it->second;
|
||||
if (parent)
|
||||
return parent->getInt(key, defaultValue);
|
||||
if (it != ints.end()) return it->second;
|
||||
if (parent) return parent->getInt(key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool getBool(const std::string& key, bool defaultValue = false) const {
|
||||
auto it = bools.find(key);
|
||||
if (it != bools.end())
|
||||
return it->second;
|
||||
if (parent)
|
||||
return parent->getBool(key, defaultValue);
|
||||
if (it != bools.end()) return it->second;
|
||||
if (parent) return parent->getBool(key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool hasKey(const std::string& key) const {
|
||||
if (strings.count(key) || ints.count(key) || bools.count(key))
|
||||
return true;
|
||||
if (parent)
|
||||
return parent->hasKey(key);
|
||||
if (strings.count(key) || ints.count(key) || bools.count(key)) return true;
|
||||
if (parent) return parent->hasKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -137,22 +120,18 @@ public:
|
||||
std::string getAnyAsString(const std::string& key) const {
|
||||
// Check strings first
|
||||
auto sit = strings.find(key);
|
||||
if (sit != strings.end())
|
||||
return sit->second;
|
||||
if (sit != strings.end()) return sit->second;
|
||||
|
||||
// Check ints
|
||||
auto iit = ints.find(key);
|
||||
if (iit != ints.end())
|
||||
return std::to_string(iit->second);
|
||||
if (iit != ints.end()) return std::to_string(iit->second);
|
||||
|
||||
// Check bools
|
||||
auto bit = bools.find(key);
|
||||
if (bit != bools.end())
|
||||
return bit->second ? "true" : "false";
|
||||
if (bit != bools.end()) return bit->second ? "true" : "false";
|
||||
|
||||
// Check parent
|
||||
if (parent)
|
||||
return parent->getAnyAsString(key);
|
||||
if (parent) return parent->getAnyAsString(key);
|
||||
|
||||
return "";
|
||||
}
|
||||
@ -161,14 +140,11 @@ public:
|
||||
// Supports: !, &&, ||, ==, !=, <, >, <=, >=, parentheses
|
||||
bool evaluateBool(const std::string& expression) const {
|
||||
std::string expr = trim(expression);
|
||||
if (expr.empty())
|
||||
return false;
|
||||
if (expr.empty()) return false;
|
||||
|
||||
// Handle literal true/false
|
||||
if (expr == "true" || expr == "1")
|
||||
return true;
|
||||
if (expr == "false" || expr == "0")
|
||||
return false;
|
||||
if (expr == "true" || expr == "1") return true;
|
||||
if (expr == "false" || expr == "0") return false;
|
||||
|
||||
// Handle {var} wrapper
|
||||
if (expr.size() > 2 && expr.front() == '{' && expr.back() == '}') {
|
||||
@ -185,10 +161,8 @@ public:
|
||||
int depth = 1;
|
||||
size_t closePos = 1;
|
||||
while (closePos < expr.length() && depth > 0) {
|
||||
if (expr[closePos] == '(')
|
||||
depth++;
|
||||
if (expr[closePos] == ')')
|
||||
depth--;
|
||||
if (expr[closePos] == '(') depth++;
|
||||
if (expr[closePos] == ')') depth--;
|
||||
closePos++;
|
||||
}
|
||||
if (closePos <= expr.length()) {
|
||||
@ -212,14 +186,11 @@ public:
|
||||
size_t orPos = expr.find("||");
|
||||
|
||||
// Process || first (lower precedence than &&)
|
||||
if (orPos != std::string::npos &&
|
||||
(andPos == std::string::npos || orPos < andPos)) {
|
||||
return evaluateBool(expr.substr(0, orPos)) ||
|
||||
evaluateBool(expr.substr(orPos + 2));
|
||||
if (orPos != std::string::npos && (andPos == std::string::npos || orPos < andPos)) {
|
||||
return evaluateBool(expr.substr(0, orPos)) || evaluateBool(expr.substr(orPos + 2));
|
||||
}
|
||||
if (andPos != std::string::npos) {
|
||||
return evaluateBool(expr.substr(0, andPos)) &&
|
||||
evaluateBool(expr.substr(andPos + 2));
|
||||
return evaluateBool(expr.substr(0, andPos)) && evaluateBool(expr.substr(andPos + 2));
|
||||
}
|
||||
|
||||
// Handle comparisons
|
||||
@ -298,8 +269,7 @@ public:
|
||||
}
|
||||
|
||||
// If it's a number, return as-is
|
||||
if (isNumber(v))
|
||||
return v;
|
||||
if (isNumber(v)) return v;
|
||||
|
||||
// Check for hex color literals (0x00, 0xFF, etc.)
|
||||
if (v.size() > 2 && v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) {
|
||||
@ -327,8 +297,7 @@ public:
|
||||
|
||||
// Evaluate a string expression with variable substitution
|
||||
std::string evaluatestring(const Expression& expr) const {
|
||||
if (expr.empty())
|
||||
return "";
|
||||
if (expr.empty()) return "";
|
||||
|
||||
std::string result;
|
||||
for (const auto& token : expr.tokens) {
|
||||
@ -339,10 +308,8 @@ public:
|
||||
std::string varName = token.value;
|
||||
|
||||
// If the variable contains comparison operators, evaluate as condition
|
||||
if (varName.find("==") != std::string::npos ||
|
||||
varName.find("!=") != std::string::npos ||
|
||||
varName.find("&&") != std::string::npos ||
|
||||
varName.find("||") != std::string::npos) {
|
||||
if (varName.find("==") != std::string::npos || varName.find("!=") != std::string::npos ||
|
||||
varName.find("&&") != std::string::npos || varName.find("||") != std::string::npos) {
|
||||
result += evaluateBool(varName) ? "true" : "false";
|
||||
continue;
|
||||
}
|
||||
@ -372,8 +339,7 @@ public:
|
||||
|
||||
// Legacy method for backward compatibility
|
||||
std::string evaluateString(const std::string& expression) const {
|
||||
if (expression.empty())
|
||||
return "";
|
||||
if (expression.empty()) return "";
|
||||
Expression expr = Expression::parse(expression);
|
||||
return evaluatestring(expr);
|
||||
}
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "BasicElements.h"
|
||||
#include "IniParser.h"
|
||||
#include "ThemeContext.h"
|
||||
#include <GfxRenderer.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
@ -50,8 +52,7 @@ private:
|
||||
|
||||
// Factory and property methods
|
||||
UIElement* createElement(const std::string& id, const std::string& type);
|
||||
void applyProperties(UIElement *elem,
|
||||
const std::map<std::string, std::string> &props);
|
||||
void applyProperties(UIElement* elem, const std::map<std::string, std::string>& props);
|
||||
|
||||
public:
|
||||
static ThemeManager& get() {
|
||||
@ -76,13 +77,10 @@ public:
|
||||
int getNavBookCount() const { return navBookCount; }
|
||||
|
||||
// Render a screen
|
||||
void renderScreen(const std::string &screenName, const GfxRenderer &renderer,
|
||||
const ThemeContext &context);
|
||||
void renderScreen(const std::string& screenName, const GfxRenderer& renderer, const ThemeContext& context);
|
||||
|
||||
// Render with dirty tracking (only redraws changed regions)
|
||||
void renderScreenOptimized(const std::string &screenName,
|
||||
const GfxRenderer &renderer,
|
||||
const ThemeContext &context,
|
||||
void renderScreenOptimized(const std::string& screenName, const GfxRenderer& renderer, const ThemeContext& context,
|
||||
const ThemeContext* prevContext = nullptr);
|
||||
|
||||
// Invalidate all caches (call when theme changes or screen switches)
|
||||
@ -100,12 +98,9 @@ public:
|
||||
|
||||
// Asset caching
|
||||
const std::vector<uint8_t>* getCachedAsset(const std::string& path);
|
||||
const ProcessedAsset *getProcessedAsset(const std::string &path,
|
||||
GfxRenderer::Orientation orientation,
|
||||
int targetW = 0, int targetH = 0);
|
||||
void cacheProcessedAsset(const std::string &path,
|
||||
const ProcessedAsset &asset,
|
||||
const ProcessedAsset* getProcessedAsset(const std::string& path, GfxRenderer::Orientation orientation,
|
||||
int targetW = 0, int targetH = 0);
|
||||
void cacheProcessedAsset(const std::string& path, const ProcessedAsset& asset, int targetW = 0, int targetH = 0);
|
||||
|
||||
// Clear asset caches (for memory management)
|
||||
void clearAssetCaches();
|
||||
@ -121,8 +116,7 @@ private:
|
||||
std::map<std::string, ProcessedAsset> processedCache;
|
||||
|
||||
// Compute a simple hash of context data for cache invalidation
|
||||
uint32_t computeContextHash(const ThemeContext &context,
|
||||
const std::string &screenName);
|
||||
uint32_t computeContextHash(const ThemeContext& context, const std::string& screenName);
|
||||
};
|
||||
|
||||
} // namespace ThemeEngine
|
||||
|
||||
@ -15,12 +15,10 @@ struct Dimension {
|
||||
Dimension() : value(0), unit(DimensionUnit::PIXELS) {}
|
||||
|
||||
static Dimension parse(const std::string& str) {
|
||||
if (str.empty())
|
||||
return Dimension(0, DimensionUnit::PIXELS);
|
||||
if (str.empty()) return Dimension(0, DimensionUnit::PIXELS);
|
||||
|
||||
if (str.back() == '%') {
|
||||
return Dimension(std::stoi(str.substr(0, str.length() - 1)),
|
||||
DimensionUnit::PERCENT);
|
||||
return Dimension(std::stoi(str.substr(0, str.length() - 1)), DimensionUnit::PERCENT);
|
||||
}
|
||||
return Dimension(std::stoi(str), DimensionUnit::PIXELS);
|
||||
}
|
||||
@ -40,14 +38,10 @@ struct Color {
|
||||
Color() : value(0) {}
|
||||
|
||||
static Color parse(const std::string& str) {
|
||||
if (str.empty())
|
||||
return Color(0);
|
||||
if (str == "black")
|
||||
return Color(0x00);
|
||||
if (str == "white")
|
||||
return Color(0xFF);
|
||||
if (str == "gray" || str == "grey")
|
||||
return Color(0x80);
|
||||
if (str.empty()) return Color(0);
|
||||
if (str == "black") return Color(0x00);
|
||||
if (str == "white") return Color(0xFF);
|
||||
if (str == "gray" || str == "grey") return Color(0x80);
|
||||
if (str.size() > 2 && str.substr(0, 2) == "0x") {
|
||||
return Color((uint8_t)std::strtol(str.c_str(), nullptr, 16));
|
||||
}
|
||||
@ -65,15 +59,12 @@ struct Rect {
|
||||
bool isEmpty() const { return w <= 0 || h <= 0; }
|
||||
|
||||
bool intersects(const Rect& other) const {
|
||||
return !(x + w <= other.x || other.x + other.w <= x || y + h <= other.y ||
|
||||
other.y + other.h <= y);
|
||||
return !(x + w <= other.x || other.x + other.w <= x || y + h <= other.y || other.y + other.h <= y);
|
||||
}
|
||||
|
||||
Rect unite(const Rect& other) const {
|
||||
if (isEmpty())
|
||||
return other;
|
||||
if (other.isEmpty())
|
||||
return *this;
|
||||
if (isEmpty()) return other;
|
||||
if (other.isEmpty()) return *this;
|
||||
int nx = std::min(x, other.x);
|
||||
int ny = std::min(y, other.y);
|
||||
int nx2 = std::max(x + w, other.x + other.w);
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ThemeContext.h"
|
||||
#include "ThemeTypes.h"
|
||||
#include <GfxRenderer.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
@ -38,15 +40,12 @@ protected:
|
||||
bool dirty = true; // Needs redraw
|
||||
|
||||
bool isVisible(const ThemeContext& context) const {
|
||||
if (visibleExpr.empty())
|
||||
return true;
|
||||
if (visibleExpr.empty()) return true;
|
||||
return context.evaluateBool(visibleExpr.rawExpr);
|
||||
}
|
||||
|
||||
public:
|
||||
UIElement(const std::string &id) : id(id) {
|
||||
visibleExpr = Expression::parse("true");
|
||||
}
|
||||
UIElement(const std::string& id) : id(id) { visibleExpr = Expression::parse("true"); }
|
||||
|
||||
virtual ~UIElement() {
|
||||
if (cachedRender) {
|
||||
@ -74,8 +73,8 @@ public:
|
||||
void setVisibleExpr(const std::string& expr) {
|
||||
visibleExpr = Expression::parse(expr);
|
||||
// Check if expression contains variables
|
||||
visibleExprIsStatic = (expr == "true" || expr == "false" || expr == "1" ||
|
||||
expr == "0" || expr.find('{') == std::string::npos);
|
||||
visibleExprIsStatic =
|
||||
(expr == "true" || expr == "false" || expr == "1" || expr == "0" || expr.find('{') == std::string::npos);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@ -97,31 +96,24 @@ public:
|
||||
}
|
||||
|
||||
// Calculate absolute position based on parent
|
||||
virtual void layout(const ThemeContext &context, int parentX, int parentY,
|
||||
int parentW, int parentH) {
|
||||
virtual void layout(const ThemeContext& context, int parentX, int parentY, int parentW, int parentH) {
|
||||
int newX = parentX + x.resolve(parentW);
|
||||
int newY = parentY + y.resolve(parentH);
|
||||
int newW = width.resolve(parentW);
|
||||
int newH = height.resolve(parentH);
|
||||
|
||||
// Clamp to parent bounds
|
||||
if (newX >= parentX + parentW)
|
||||
newX = parentX + parentW - 1;
|
||||
if (newY >= parentY + parentH)
|
||||
newY = parentY + parentH - 1;
|
||||
if (newX >= parentX + parentW) newX = parentX + parentW - 1;
|
||||
if (newY >= parentY + parentH) newY = parentY + parentH - 1;
|
||||
|
||||
int maxX = parentX + parentW;
|
||||
int maxY = parentY + parentH;
|
||||
|
||||
if (newX + newW > maxX)
|
||||
newW = maxX - newX;
|
||||
if (newY + newH > maxY)
|
||||
newH = maxY - newY;
|
||||
if (newX + newW > maxX) newW = maxX - newX;
|
||||
if (newY + newH > maxY) newH = maxY - newY;
|
||||
|
||||
if (newW < 0)
|
||||
newW = 0;
|
||||
if (newH < 0)
|
||||
newH = 0;
|
||||
if (newW < 0) newW = 0;
|
||||
if (newH < 0) newH = 0;
|
||||
|
||||
// Check if position changed
|
||||
if (newX != absX || newY != absY || newW != absW || newH != absH) {
|
||||
@ -166,8 +158,7 @@ public:
|
||||
Rect getBounds() const { return Rect(absX, absY, absW, absH); }
|
||||
|
||||
// Main draw method - handles caching automatically
|
||||
virtual void draw(const GfxRenderer &renderer,
|
||||
const ThemeContext &context) = 0;
|
||||
virtual void draw(const GfxRenderer& renderer, const ThemeContext& context) = 0;
|
||||
|
||||
protected:
|
||||
// Cache the rendered output
|
||||
@ -191,11 +182,8 @@ protected:
|
||||
|
||||
// Restore from cache
|
||||
bool restoreFromCache(const GfxRenderer& renderer) const {
|
||||
if (!cacheValid || !cachedRender)
|
||||
return false;
|
||||
if (absX != cachedX || absY != cachedY || absW != cachedW ||
|
||||
absH != cachedH)
|
||||
return false;
|
||||
if (!cacheValid || !cachedRender) return false;
|
||||
if (absX != cachedX || absY != cachedY || absW != cachedW || absH != cachedH) return false;
|
||||
|
||||
renderer.restoreRegion(cachedRender, absX, absY, absW, absH);
|
||||
return true;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include "BasicElements.h"
|
||||
|
||||
#include "Bitmap.h"
|
||||
#include "ListElement.h"
|
||||
#include "ThemeManager.h"
|
||||
@ -7,8 +8,7 @@
|
||||
namespace ThemeEngine {
|
||||
|
||||
// --- BitmapElement ---
|
||||
void BitmapElement::draw(const GfxRenderer &renderer,
|
||||
const ThemeContext &context) {
|
||||
void BitmapElement::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
||||
if (!isVisible(context)) {
|
||||
markClean();
|
||||
return;
|
||||
@ -21,8 +21,7 @@ void BitmapElement::draw(const GfxRenderer &renderer,
|
||||
}
|
||||
|
||||
// Check if we have a cached 1-bit render of this bitmap at this size
|
||||
const ProcessedAsset *processed =
|
||||
ThemeManager::get().getProcessedAsset(path, renderer.getOrientation(), absW, absH);
|
||||
const ProcessedAsset* processed = ThemeManager::get().getProcessedAsset(path, renderer.getOrientation(), absW, absH);
|
||||
if (processed && processed->w == absW && processed->h == absH) {
|
||||
// Draw cached 1-bit data directly
|
||||
const int rowBytes = (absW + 7) / 8;
|
||||
@ -148,8 +147,7 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
||||
currentX += itemW + spacing;
|
||||
continue;
|
||||
}
|
||||
if (currentX > absX + absW)
|
||||
break;
|
||||
if (currentX > absX + absW) break;
|
||||
} else {
|
||||
// Grid mode
|
||||
if (currentY + itemH < absY) {
|
||||
@ -162,8 +160,7 @@ void List::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
||||
currentX = absX + col * (itemW + spacing);
|
||||
continue;
|
||||
}
|
||||
if (currentY > absY + absH)
|
||||
break;
|
||||
if (currentY > absY + absH) break;
|
||||
}
|
||||
|
||||
// Layout and draw
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#include "IniParser.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
void IniParser::trim(std::string& s) {
|
||||
if (s.empty())
|
||||
return;
|
||||
if (s.empty()) return;
|
||||
|
||||
// Trim left
|
||||
size_t first = s.find_first_not_of(" \t\n\r");
|
||||
@ -19,8 +19,7 @@ void IniParser::trim(std::string &s) {
|
||||
s = s.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
std::map<std::string, std::map<std::string, std::string>>
|
||||
IniParser::parse(Stream &stream) {
|
||||
std::map<std::string, std::map<std::string, std::string>> IniParser::parse(Stream& stream) {
|
||||
std::map<std::string, std::map<std::string, std::string>> sections;
|
||||
// stream check not strictly possible like file, can rely on available()
|
||||
|
||||
@ -62,8 +61,7 @@ IniParser::parse(Stream &stream) {
|
||||
return sections;
|
||||
}
|
||||
|
||||
std::map<std::string, std::map<std::string, std::string>>
|
||||
IniParser::parseString(const std::string &content) {
|
||||
std::map<std::string, std::map<std::string, std::string>> IniParser::parseString(const std::string& content) {
|
||||
std::map<std::string, std::map<std::string, std::string>> sections;
|
||||
std::stringstream ss(content);
|
||||
std::string line;
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
#include "LayoutElements.h"
|
||||
#include "ThemeManager.h"
|
||||
|
||||
#include <Bitmap.h>
|
||||
|
||||
#include "ThemeManager.h"
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
// Built-in icon drawing
|
||||
@ -29,8 +31,7 @@ void Icon::draw(const GfxRenderer &renderer, const ThemeContext &context) {
|
||||
int cy = absY + h / 2;
|
||||
|
||||
// Check if it's a path to a BMP file
|
||||
if (iconName.find('/') != std::string::npos ||
|
||||
iconName.find('.') != std::string::npos) {
|
||||
if (iconName.find('/') != std::string::npos || iconName.find('.') != std::string::npos) {
|
||||
// Try to load as bitmap
|
||||
std::string path = iconName;
|
||||
if (path[0] != '/') {
|
||||
|
||||
@ -1,77 +1,58 @@
|
||||
#include "ThemeManager.h"
|
||||
#include "DefaultTheme.h"
|
||||
#include "LayoutElements.h"
|
||||
#include "ListElement.h"
|
||||
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "DefaultTheme.h"
|
||||
#include "LayoutElements.h"
|
||||
#include "ListElement.h"
|
||||
|
||||
namespace ThemeEngine {
|
||||
|
||||
void ThemeManager::begin() {
|
||||
// Default fonts or setup
|
||||
}
|
||||
|
||||
void ThemeManager::registerFont(const std::string &name, int id) {
|
||||
fontMap[name] = id;
|
||||
}
|
||||
void ThemeManager::registerFont(const std::string& name, int id) { fontMap[name] = id; }
|
||||
|
||||
std::string ThemeManager::getAssetPath(const std::string& assetName) {
|
||||
// Check if absolute path
|
||||
if (!assetName.empty() && assetName[0] == '/')
|
||||
return assetName;
|
||||
if (!assetName.empty() && assetName[0] == '/') return assetName;
|
||||
|
||||
// Otherwise relative to theme assets
|
||||
return "/themes/" + currentThemeName + "/assets/" + assetName;
|
||||
}
|
||||
|
||||
UIElement *ThemeManager::createElement(const std::string &id,
|
||||
const std::string &type) {
|
||||
UIElement* ThemeManager::createElement(const std::string& id, const std::string& type) {
|
||||
// Basic elements
|
||||
if (type == "Container")
|
||||
return new Container(id);
|
||||
if (type == "Rectangle")
|
||||
return new Rectangle(id);
|
||||
if (type == "Label")
|
||||
return new Label(id);
|
||||
if (type == "Bitmap")
|
||||
return new BitmapElement(id);
|
||||
if (type == "List")
|
||||
return new List(id);
|
||||
if (type == "ProgressBar")
|
||||
return new ProgressBar(id);
|
||||
if (type == "Divider")
|
||||
return new Divider(id);
|
||||
if (type == "Container") return new Container(id);
|
||||
if (type == "Rectangle") return new Rectangle(id);
|
||||
if (type == "Label") return new Label(id);
|
||||
if (type == "Bitmap") return new BitmapElement(id);
|
||||
if (type == "List") return new List(id);
|
||||
if (type == "ProgressBar") return new ProgressBar(id);
|
||||
if (type == "Divider") return new Divider(id);
|
||||
|
||||
// Layout elements
|
||||
if (type == "HStack")
|
||||
return new HStack(id);
|
||||
if (type == "VStack")
|
||||
return new VStack(id);
|
||||
if (type == "Grid")
|
||||
return new Grid(id);
|
||||
if (type == "HStack") return new HStack(id);
|
||||
if (type == "VStack") return new VStack(id);
|
||||
if (type == "Grid") return new Grid(id);
|
||||
|
||||
// Advanced elements
|
||||
if (type == "Badge")
|
||||
return new Badge(id);
|
||||
if (type == "Toggle")
|
||||
return new Toggle(id);
|
||||
if (type == "TabBar")
|
||||
return new TabBar(id);
|
||||
if (type == "Icon")
|
||||
return new Icon(id);
|
||||
if (type == "ScrollIndicator")
|
||||
return new ScrollIndicator(id);
|
||||
if (type == "BatteryIcon")
|
||||
return new BatteryIcon(id);
|
||||
if (type == "Badge") return new Badge(id);
|
||||
if (type == "Toggle") return new Toggle(id);
|
||||
if (type == "TabBar") return new TabBar(id);
|
||||
if (type == "Icon") return new Icon(id);
|
||||
if (type == "ScrollIndicator") return new ScrollIndicator(id);
|
||||
if (type == "BatteryIcon") return new BatteryIcon(id);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ThemeManager::applyProperties(
|
||||
UIElement *elem, const std::map<std::string, std::string> &props) {
|
||||
|
||||
void ThemeManager::applyProperties(UIElement* elem, const std::map<std::string, std::string>& props) {
|
||||
const auto elemType = elem->getType();
|
||||
|
||||
for (const auto& kv : props) {
|
||||
@ -105,10 +86,8 @@ void ThemeManager::applyProperties(
|
||||
} else if (key == "Color") {
|
||||
if (elemType == UIElement::ElementType::Rectangle) {
|
||||
static_cast<Rectangle*>(elem)->setColorExpr(val);
|
||||
} else if (elemType == UIElement::ElementType::Container ||
|
||||
elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack ||
|
||||
elemType == UIElement::ElementType::Grid ||
|
||||
} else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid ||
|
||||
elemType == UIElement::ElementType::TabBar) {
|
||||
static_cast<Container*>(elem)->setBackgroundColorExpr(val);
|
||||
} else if (elemType == UIElement::ElementType::Label) {
|
||||
@ -132,19 +111,15 @@ void ThemeManager::applyProperties(
|
||||
}
|
||||
}
|
||||
} else if (key == "Padding") {
|
||||
if (elemType == UIElement::ElementType::Container ||
|
||||
elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack ||
|
||||
elemType == UIElement::ElementType::Grid) {
|
||||
if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
|
||||
static_cast<Container*>(elem)->setPadding(std::stoi(val));
|
||||
} else if (elemType == UIElement::ElementType::TabBar) {
|
||||
static_cast<TabBar*>(elem)->setPadding(std::stoi(val));
|
||||
}
|
||||
} else if (key == "BorderRadius") {
|
||||
if (elemType == UIElement::ElementType::Container ||
|
||||
elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack ||
|
||||
elemType == UIElement::ElementType::Grid) {
|
||||
if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
|
||||
static_cast<Container*>(elem)->setBorderRadius(std::stoi(val));
|
||||
}
|
||||
} else if (key == "Spacing") {
|
||||
@ -189,10 +164,8 @@ void ThemeManager::applyProperties(
|
||||
} else if (key == "Align") {
|
||||
if (elemType == UIElement::ElementType::Label) {
|
||||
Label::Alignment align = Label::Alignment::Left;
|
||||
if (val == "Center" || val == "center")
|
||||
align = Label::Alignment::Center;
|
||||
if (val == "Right" || val == "right")
|
||||
align = Label::Alignment::Right;
|
||||
if (val == "Center" || val == "center") align = Label::Alignment::Center;
|
||||
if (val == "Right" || val == "right") align = Label::Alignment::Right;
|
||||
static_cast<Label*>(elem)->setAlignment(align);
|
||||
}
|
||||
} else if (key == "MaxLines") {
|
||||
@ -209,8 +182,7 @@ void ThemeManager::applyProperties(
|
||||
else if (key == "Src") {
|
||||
if (elemType == UIElement::ElementType::Bitmap) {
|
||||
auto b = static_cast<BitmapElement*>(elem);
|
||||
if (val.find('{') == std::string::npos &&
|
||||
val.find('/') == std::string::npos) {
|
||||
if (val.find('{') == std::string::npos && val.find('/') == std::string::npos) {
|
||||
b->setSrc(getAssetPath(val));
|
||||
} else {
|
||||
b->setSrc(val);
|
||||
@ -220,13 +192,11 @@ void ThemeManager::applyProperties(
|
||||
}
|
||||
} else if (key == "ScaleToFit") {
|
||||
if (elemType == UIElement::ElementType::Bitmap) {
|
||||
static_cast<BitmapElement *>(elem)->setScaleToFit(val == "true" ||
|
||||
val == "1");
|
||||
static_cast<BitmapElement*>(elem)->setScaleToFit(val == "true" || val == "1");
|
||||
}
|
||||
} else if (key == "PreserveAspect") {
|
||||
if (elemType == UIElement::ElementType::Bitmap) {
|
||||
static_cast<BitmapElement *>(elem)->setPreserveAspect(val == "true" ||
|
||||
val == "1");
|
||||
static_cast<BitmapElement*>(elem)->setPreserveAspect(val == "true" || val == "1");
|
||||
}
|
||||
} else if (key == "IconSize") {
|
||||
if (elemType == UIElement::ElementType::Icon) {
|
||||
@ -295,16 +265,13 @@ void ThemeManager::applyProperties(
|
||||
static_cast<ProgressBar*>(elem)->setBgColor(val);
|
||||
} else if (elemType == UIElement::ElementType::Badge) {
|
||||
static_cast<Badge*>(elem)->setBgColor(val);
|
||||
} else if (elemType == UIElement::ElementType::Container ||
|
||||
elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack ||
|
||||
elemType == UIElement::ElementType::Grid) {
|
||||
} else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack ||
|
||||
elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) {
|
||||
static_cast<Container*>(elem)->setBackgroundColorExpr(val);
|
||||
}
|
||||
} else if (key == "ShowBorder") {
|
||||
if (elemType == UIElement::ElementType::ProgressBar) {
|
||||
static_cast<ProgressBar *>(elem)->setShowBorder(val == "true" ||
|
||||
val == "1");
|
||||
static_cast<ProgressBar*>(elem)->setShowBorder(val == "true" || val == "1");
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,14 +358,12 @@ void ThemeManager::applyProperties(
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> *
|
||||
ThemeManager::getCachedAsset(const std::string &path) {
|
||||
const std::vector<uint8_t>* ThemeManager::getCachedAsset(const std::string& path) {
|
||||
if (assetCache.count(path)) {
|
||||
return &assetCache.at(path);
|
||||
}
|
||||
|
||||
if (!SdMan.exists(path.c_str()))
|
||||
return nullptr;
|
||||
if (!SdMan.exists(path.c_str())) return nullptr;
|
||||
|
||||
FsFile file;
|
||||
if (SdMan.openFileForRead("ThemeCache", path, file)) {
|
||||
@ -412,9 +377,7 @@ ThemeManager::getCachedAsset(const std::string &path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ProcessedAsset *
|
||||
ThemeManager::getProcessedAsset(const std::string &path,
|
||||
GfxRenderer::Orientation orientation,
|
||||
const ProcessedAsset* ThemeManager::getProcessedAsset(const std::string& path, GfxRenderer::Orientation orientation,
|
||||
int targetW, int targetH) {
|
||||
// Include dimensions in cache key for scaled images
|
||||
std::string cacheKey = path;
|
||||
@ -431,9 +394,7 @@ ThemeManager::getProcessedAsset(const std::string &path,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ThemeManager::cacheProcessedAsset(const std::string &path,
|
||||
const ProcessedAsset &asset,
|
||||
int targetW, int targetH) {
|
||||
void ThemeManager::cacheProcessedAsset(const std::string& path, const ProcessedAsset& asset, int targetW, int targetH) {
|
||||
std::string cacheKey = path;
|
||||
if (targetW > 0 && targetH > 0) {
|
||||
cacheKey += ":" + std::to_string(targetW) + "x" + std::to_string(targetH);
|
||||
@ -467,8 +428,7 @@ void ThemeManager::invalidateScreenCache(const std::string &screenName) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ThemeManager::computeContextHash(const ThemeContext &context,
|
||||
const std::string &screenName) {
|
||||
uint32_t ThemeManager::computeContextHash(const ThemeContext& context, const std::string& screenName) {
|
||||
uint32_t hash = 2166136261u;
|
||||
for (char c : screenName) {
|
||||
hash ^= static_cast<uint32_t>(c);
|
||||
@ -511,8 +471,7 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
||||
Serial.printf("[ThemeManager] Parsing theme file...\n");
|
||||
sections = IniParser::parse(file);
|
||||
file.close();
|
||||
Serial.printf("[ThemeManager] Parsed %d sections from %s\n",
|
||||
(int)sections.size(), themeName.c_str());
|
||||
Serial.printf("[ThemeManager] Parsed %d sections from %s\n", (int)sections.size(), themeName.c_str());
|
||||
} else {
|
||||
Serial.printf("[ThemeManager] Failed to open %s, using Default\n", path.c_str());
|
||||
sections = IniParser::parseString(getDefaultThemeIni());
|
||||
@ -537,15 +496,12 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
||||
std::string id = sec.first;
|
||||
const std::map<std::string, std::string>& props = sec.second;
|
||||
|
||||
if (id == "Global")
|
||||
continue;
|
||||
if (id == "Global") continue;
|
||||
|
||||
auto it = props.find("Type");
|
||||
if (it == props.end())
|
||||
continue;
|
||||
if (it == props.end()) continue;
|
||||
std::string type = it->second;
|
||||
if (type.empty())
|
||||
continue;
|
||||
if (type.empty()) continue;
|
||||
|
||||
UIElement* elem = createElement(id, type);
|
||||
if (elem) {
|
||||
@ -557,10 +513,8 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
||||
std::vector<List*> lists;
|
||||
for (const auto& sec : sections) {
|
||||
std::string id = sec.first;
|
||||
if (id == "Global")
|
||||
continue;
|
||||
if (elements.find(id) == elements.end())
|
||||
continue;
|
||||
if (id == "Global") continue;
|
||||
if (elements.find(id) == elements.end()) continue;
|
||||
|
||||
UIElement* elem = elements[id];
|
||||
applyProperties(elem, sec.second);
|
||||
@ -577,8 +531,7 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
||||
c->addChild(elem);
|
||||
}
|
||||
} else {
|
||||
Serial.printf("[ThemeManager] WARN: Parent %s not found for %s\n",
|
||||
parentId.c_str(), id.c_str());
|
||||
Serial.printf("[ThemeManager] WARN: Parent %s not found for %s\n", parentId.c_str(), id.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -588,12 +541,10 @@ void ThemeManager::loadTheme(const std::string &themeName) {
|
||||
l->resolveTemplate(elements);
|
||||
}
|
||||
|
||||
Serial.printf("[ThemeManager] Theme loaded with %d elements\n",
|
||||
(int)elements.size());
|
||||
Serial.printf("[ThemeManager] Theme loaded with %d elements\n", (int)elements.size());
|
||||
}
|
||||
|
||||
void ThemeManager::renderScreen(const std::string &screenName,
|
||||
const GfxRenderer &renderer,
|
||||
void ThemeManager::renderScreen(const std::string& screenName, const GfxRenderer& renderer,
|
||||
const ThemeContext& context) {
|
||||
if (elements.count(screenName) == 0) {
|
||||
Serial.printf("[ThemeManager] Screen '%s' not found\n", screenName.c_str());
|
||||
@ -602,16 +553,13 @@ void ThemeManager::renderScreen(const std::string &screenName,
|
||||
|
||||
UIElement* root = elements[screenName];
|
||||
|
||||
root->layout(context, 0, 0, renderer.getScreenWidth(),
|
||||
renderer.getScreenHeight());
|
||||
root->layout(context, 0, 0, renderer.getScreenWidth(), renderer.getScreenHeight());
|
||||
|
||||
root->draw(renderer, context);
|
||||
}
|
||||
|
||||
void ThemeManager::renderScreenOptimized(const std::string &screenName,
|
||||
const GfxRenderer &renderer,
|
||||
const ThemeContext &context,
|
||||
const ThemeContext *prevContext) {
|
||||
void ThemeManager::renderScreenOptimized(const std::string& screenName, const GfxRenderer& renderer,
|
||||
const ThemeContext& context, const ThemeContext* prevContext) {
|
||||
renderScreen(screenName, renderer, context);
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <Epub.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <ThemeManager.h>
|
||||
#include <Xtc.h>
|
||||
|
||||
#include <cstring>
|
||||
@ -17,7 +18,6 @@
|
||||
#include "ScreenComponents.h"
|
||||
#include "fontIds.h"
|
||||
#include "util/StringUtils.h"
|
||||
#include <ThemeManager.h>
|
||||
|
||||
void HomeActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<HomeActivity*>(param);
|
||||
@ -26,8 +26,7 @@ void HomeActivity::taskTrampoline(void *param) {
|
||||
|
||||
int HomeActivity::getMenuItemCount() const {
|
||||
int count = 3; // Browse Files, File Transfer, Settings
|
||||
if (hasOpdsUrl)
|
||||
count++; // + Calibre Library
|
||||
if (hasOpdsUrl) count++; // + Calibre Library
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -43,8 +42,7 @@ void HomeActivity::onEnter() {
|
||||
selectorIndex = 0; // Start at first item (first book if any, else first menu)
|
||||
|
||||
// Check if we have a book to continue reading
|
||||
hasContinueReading = !APP_STATE.openEpubPath.empty() &&
|
||||
SdMan.exists(APP_STATE.openEpubPath.c_str());
|
||||
hasContinueReading = !APP_STATE.openEpubPath.empty() && SdMan.exists(APP_STATE.openEpubPath.c_str());
|
||||
|
||||
// Check if OPDS browser URL is configured
|
||||
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
|
||||
@ -190,8 +188,8 @@ void HomeActivity::loadRecentBooksData() {
|
||||
}
|
||||
}
|
||||
|
||||
Serial.printf("[HOME] Book %d: title='%s', cover='%s', progress=%d%%\n",
|
||||
i, info.title.c_str(), info.coverPath.c_str(), info.progressPercent);
|
||||
Serial.printf("[HOME] Book %d: title='%s', cover='%s', progress=%d%%\n", i, info.title.c_str(),
|
||||
info.coverPath.c_str(), info.progressPercent);
|
||||
cachedRecentBooks.push_back(info);
|
||||
}
|
||||
|
||||
@ -258,11 +256,9 @@ void HomeActivity::freeCoverBuffer() {
|
||||
}
|
||||
|
||||
void HomeActivity::loop() {
|
||||
const bool prevPressed =
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left);
|
||||
const bool nextPressed =
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right);
|
||||
const bool confirmPressed = mappedInput.wasReleased(MappedInputManager::Button::Confirm);
|
||||
|
||||
@ -324,8 +320,7 @@ void HomeActivity::displayTaskLoop() {
|
||||
void HomeActivity::render() {
|
||||
// Battery check logic (only update every 60 seconds)
|
||||
const uint32_t now = millis();
|
||||
const bool needBatteryUpdate =
|
||||
(now - lastBatteryCheck > 60000) || (lastBatteryCheck == 0);
|
||||
const bool needBatteryUpdate = (now - lastBatteryCheck > 60000) || (lastBatteryCheck == 0);
|
||||
if (needBatteryUpdate) {
|
||||
cachedBatteryLevel = battery.readPercentage();
|
||||
lastBatteryCheck = now;
|
||||
@ -339,8 +334,7 @@ void HomeActivity::render() {
|
||||
// --- Bind Global Data ---
|
||||
context.setString("BatteryPercent", std::to_string(cachedBatteryLevel));
|
||||
context.setBool("ShowBatteryPercent",
|
||||
SETTINGS.hideBatteryPercentage !=
|
||||
CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS);
|
||||
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS);
|
||||
|
||||
// --- Navigation counts (must match loop()) ---
|
||||
const int recentCount = static_cast<int>(cachedRecentBooks.size());
|
||||
@ -370,8 +364,7 @@ void HomeActivity::render() {
|
||||
context.setString("BookTitle", lastBookTitle);
|
||||
context.setString("BookAuthor", lastBookAuthor);
|
||||
context.setString("BookCoverPath", coverBmpPath);
|
||||
context.setBool("HasCover",
|
||||
hasContinueReading && hasCoverImage && !coverBmpPath.empty());
|
||||
context.setBool("HasCover", hasContinueReading && hasCoverImage && !coverBmpPath.empty());
|
||||
context.setBool("ShowInfoBox", true);
|
||||
|
||||
// --- Main Menu Data ---
|
||||
|
||||
@ -53,14 +53,14 @@ class HomeActivity final : public Activity {
|
||||
|
||||
public:
|
||||
explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()> &onContinueReading,
|
||||
const std::function<void()> &onMyLibraryOpen,
|
||||
const std::function<void()> &onSettingsOpen,
|
||||
const std::function<void()> &onFileTransferOpen,
|
||||
const std::function<void()>& onContinueReading, const std::function<void()>& onMyLibraryOpen,
|
||||
const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen,
|
||||
const std::function<void()>& onOpdsBrowserOpen)
|
||||
: Activity("Home", renderer, mappedInput),
|
||||
onContinueReading(onContinueReading), onMyLibraryOpen(onMyLibraryOpen),
|
||||
onSettingsOpen(onSettingsOpen), onFileTransferOpen(onFileTransferOpen),
|
||||
onContinueReading(onContinueReading),
|
||||
onMyLibraryOpen(onMyLibraryOpen),
|
||||
onSettingsOpen(onSettingsOpen),
|
||||
onFileTransferOpen(onFileTransferOpen),
|
||||
onOpdsBrowserOpen(onOpdsBrowserOpen) {}
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
|
||||
@ -30,8 +30,7 @@ void CategorySettingsActivity::onEnter() {
|
||||
selectedSettingIndex = 0;
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&CategorySettingsActivity::taskTrampoline,
|
||||
"CategorySettingsActivityTask", 4096, this, 1,
|
||||
xTaskCreate(&CategorySettingsActivity::taskTrampoline, "CategorySettingsActivityTask", 4096, this, 1,
|
||||
&displayTaskHandle);
|
||||
}
|
||||
|
||||
@ -71,15 +70,11 @@ void CategorySettingsActivity::loop() {
|
||||
// Handle navigation
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedSettingIndex = (selectedSettingIndex > 0)
|
||||
? (selectedSettingIndex - 1)
|
||||
: (settingsCount - 1);
|
||||
selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1);
|
||||
updateRequired = true;
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedSettingIndex = (selectedSettingIndex < settingsCount - 1)
|
||||
? (selectedSettingIndex + 1)
|
||||
: 0;
|
||||
selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0;
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
@ -97,10 +92,8 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
||||
SETTINGS.*(setting.valuePtr) = !currentValue;
|
||||
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
||||
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||
SETTINGS.*(setting.valuePtr) =
|
||||
(currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
||||
} else if (setting.type == SettingType::VALUE &&
|
||||
setting.valuePtr != nullptr) {
|
||||
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
||||
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
||||
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||
if (currentValue + setting.valueRange.step > setting.valueRange.max) {
|
||||
SETTINGS.*(setting.valuePtr) = setting.valueRange.min;
|
||||
@ -111,8 +104,7 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
||||
if (strcmp(setting.name, "KOReader Sync") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(
|
||||
new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
||||
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
@ -120,8 +112,7 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
||||
} else if (strcmp(setting.name, "OPDS Browser") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(
|
||||
new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
||||
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
@ -145,8 +136,7 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
||||
} else if (strcmp(setting.name, "Theme") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(
|
||||
new ThemeSelectionActivity(renderer, mappedInput, [this] {
|
||||
enterNewActivity(new ThemeSelectionActivity(renderer, mappedInput, [this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
@ -177,8 +167,7 @@ void CategorySettingsActivity::render() const {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true,
|
||||
EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw selection highlight
|
||||
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
|
||||
@ -189,39 +178,30 @@ void CategorySettingsActivity::render() const {
|
||||
const bool isSelected = (i == selectedSettingIndex);
|
||||
|
||||
// Draw setting name
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name,
|
||||
!isSelected);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected);
|
||||
|
||||
// Draw value based on setting type
|
||||
std::string valueText;
|
||||
if (settingsList[i].type == SettingType::TOGGLE &&
|
||||
settingsList[i].valuePtr != nullptr) {
|
||||
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = value ? "ON" : "OFF";
|
||||
} else if (settingsList[i].type == SettingType::ENUM &&
|
||||
settingsList[i].valuePtr != nullptr) {
|
||||
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = settingsList[i].enumValues[value];
|
||||
} else if (settingsList[i].type == SettingType::VALUE &&
|
||||
settingsList[i].valuePtr != nullptr) {
|
||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||
}
|
||||
if (!valueText.empty()) {
|
||||
const auto width =
|
||||
renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY,
|
||||
valueText.c_str(), !isSelected);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
renderer.drawText(
|
||||
SMALL_FONT_ID,
|
||||
pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
||||
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
||||
pageHeight - 60, CROSSPOINT_VERSION);
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
|
||||
labels.btn4);
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
#include "ThemeSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <esp_system.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "fontIds.h"
|
||||
#include <GfxRenderer.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <cstring>
|
||||
#include <esp_system.h>
|
||||
|
||||
void ThemeSelectionActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<ThemeSelectionActivity*>(param);
|
||||
@ -49,8 +52,7 @@ void ThemeSelectionActivity::onEnter() {
|
||||
}
|
||||
|
||||
updateRequired = true;
|
||||
xTaskCreate(&ThemeSelectionActivity::taskTrampoline, "ThemeSelTask", 4096,
|
||||
this, 1, &displayTaskHandle);
|
||||
xTaskCreate(&ThemeSelectionActivity::taskTrampoline, "ThemeSelTask", 4096, this, 1, &displayTaskHandle);
|
||||
}
|
||||
|
||||
void ThemeSelectionActivity::onExit() {
|
||||
@ -72,17 +74,14 @@ void ThemeSelectionActivity::loop() {
|
||||
|
||||
// Only reboot if theme actually changed
|
||||
if (selected != std::string(SETTINGS.themeName)) {
|
||||
strncpy(SETTINGS.themeName, selected.c_str(),
|
||||
sizeof(SETTINGS.themeName) - 1);
|
||||
strncpy(SETTINGS.themeName, selected.c_str(), sizeof(SETTINGS.themeName) - 1);
|
||||
SETTINGS.themeName[sizeof(SETTINGS.themeName) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
|
||||
// Show reboot message
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() / 2 - 20,
|
||||
"Applying theme...", true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, renderer.getScreenHeight() / 2 + 10,
|
||||
"Device will restart", true);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() / 2 - 20, "Applying theme...", true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, renderer.getScreenHeight() / 2 + 10, "Device will restart", true);
|
||||
renderer.displayBuffer();
|
||||
|
||||
// Small delay to ensure display updates
|
||||
@ -103,13 +102,11 @@ void ThemeSelectionActivity::loop() {
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedIndex =
|
||||
(selectedIndex > 0) ? (selectedIndex - 1) : (themeNames.size() - 1);
|
||||
selectedIndex = (selectedIndex > 0) ? (selectedIndex - 1) : (themeNames.size() - 1);
|
||||
updateRequired = true;
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedIndex =
|
||||
(selectedIndex < themeNames.size() - 1) ? (selectedIndex + 1) : 0;
|
||||
selectedIndex = (selectedIndex < themeNames.size() - 1) ? (selectedIndex + 1) : 0;
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
@ -133,8 +130,7 @@ void ThemeSelectionActivity::render() const {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Theme", true,
|
||||
EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Theme", true, EpdFontFamily::BOLD);
|
||||
|
||||
// Layout constants
|
||||
const int entryHeight = 30;
|
||||
@ -155,8 +151,7 @@ void ThemeSelectionActivity::render() const {
|
||||
// Draw Highlight
|
||||
int visibleIndex = selectedIndex - startIdx;
|
||||
if (visibleIndex >= 0 && visibleIndex < maxVisible) {
|
||||
renderer.fillRect(0, startY + visibleIndex * entryHeight - 2, pageWidth - 1,
|
||||
entryHeight);
|
||||
renderer.fillRect(0, startY + visibleIndex * entryHeight - 2, pageWidth - 1, entryHeight);
|
||||
}
|
||||
|
||||
// Draw List
|
||||
@ -176,15 +171,13 @@ void ThemeSelectionActivity::render() const {
|
||||
if (themeNames.size() > maxVisible) {
|
||||
int barHeight = pageHeight - startY - 40;
|
||||
int thumbHeight = barHeight * maxVisible / themeNames.size();
|
||||
int thumbY = startY + (barHeight - thumbHeight) * startIdx /
|
||||
(themeNames.size() - maxVisible);
|
||||
int thumbY = startY + (barHeight - thumbHeight) * startIdx / (themeNames.size() - maxVisible);
|
||||
renderer.fillRect(pageWidth - 5, startY, 2, barHeight, 0);
|
||||
renderer.fillRect(pageWidth - 7, thumbY, 6, thumbHeight, 1);
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Cancel", "Select", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
|
||||
labels.btn4);
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
#pragma once
|
||||
#include "activities/Activity.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "activities/Activity.h"
|
||||
|
||||
class ThemeSelectionActivity final : public Activity {
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
@ -20,8 +22,7 @@ class ThemeSelectionActivity final : public Activity {
|
||||
void render() const;
|
||||
|
||||
public:
|
||||
ThemeSelectionActivity(GfxRenderer &renderer, MappedInputManager &mappedInput,
|
||||
const std::function<void()> &onGoBack)
|
||||
ThemeSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function<void()>& onGoBack)
|
||||
: Activity("ThemeSelection", renderer, mappedInput), onGoBack(onGoBack) {}
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user