mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-08 08:37:38 +03:00
Compare commits
No commits in common. "1d357629d75960a7f39fee954614363f42ec7ee1" and "c6cfaa38afbd46f05b62e436323243c441ba0d70" have entirely different histories.
1d357629d7
...
c6cfaa38af
@ -11,22 +11,6 @@
|
|||||||
|
|
||||||
namespace ThemeEngine {
|
namespace ThemeEngine {
|
||||||
|
|
||||||
// Safe integer parsing (no exceptions)
|
|
||||||
inline int parseIntSafe(const std::string& s, int defaultVal = 0) {
|
|
||||||
if (s.empty()) return defaultVal;
|
|
||||||
char* end;
|
|
||||||
long val = strtol(s.c_str(), &end, 10);
|
|
||||||
return (end != s.c_str()) ? static_cast<int>(val) : defaultVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safe float parsing (no exceptions)
|
|
||||||
inline float parseFloatSafe(const std::string& s, float defaultVal = 0.0f) {
|
|
||||||
if (s.empty()) return defaultVal;
|
|
||||||
char* end;
|
|
||||||
float val = strtof(s.c_str(), &end);
|
|
||||||
return (end != s.c_str()) ? val : defaultVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Container ---
|
// --- Container ---
|
||||||
class Container : public UIElement {
|
class Container : public UIElement {
|
||||||
protected:
|
protected:
|
||||||
@ -279,8 +263,8 @@ class ProgressBar : public UIElement {
|
|||||||
std::string valStr = context.evaluatestring(valueExpr);
|
std::string valStr = context.evaluatestring(valueExpr);
|
||||||
std::string maxStr = context.evaluatestring(maxExpr);
|
std::string maxStr = context.evaluatestring(maxExpr);
|
||||||
|
|
||||||
int value = parseIntSafe(valStr, 0);
|
int value = valStr.empty() ? 0 : std::stoi(valStr);
|
||||||
int maxVal = parseIntSafe(maxStr, 100);
|
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);
|
float ratio = static_cast<float>(value) / static_cast<float>(maxVal);
|
||||||
@ -383,7 +367,7 @@ class BatteryIcon : public UIElement {
|
|||||||
if (!isVisible(context)) return;
|
if (!isVisible(context)) return;
|
||||||
|
|
||||||
std::string valStr = context.evaluatestring(valueExpr);
|
std::string valStr = context.evaluatestring(valueExpr);
|
||||||
int percentage = parseIntSafe(valStr, 0);
|
int percentage = valStr.empty() ? 0 : std::stoi(valStr);
|
||||||
|
|
||||||
std::string colStr = context.evaluatestring(colorExpr);
|
std::string colStr = context.evaluatestring(colorExpr);
|
||||||
uint8_t color = Color::parse(colStr).value;
|
uint8_t color = Color::parse(colStr).value;
|
||||||
|
|||||||
@ -12,13 +12,9 @@ namespace ThemeEngine {
|
|||||||
// --- HStack: Horizontal Stack Layout ---
|
// --- HStack: Horizontal Stack Layout ---
|
||||||
// Children are arranged horizontally with optional spacing
|
// Children are arranged horizontally with optional spacing
|
||||||
class HStack : public Container {
|
class HStack : public Container {
|
||||||
public:
|
|
||||||
enum class VAlign { Top, Center, Bottom };
|
|
||||||
|
|
||||||
private:
|
|
||||||
int spacing = 0; // Gap between children
|
int spacing = 0; // Gap between children
|
||||||
int padding = 0; // Internal padding
|
int padding = 0; // Internal padding
|
||||||
VAlign vAlign = VAlign::Top;
|
bool centerVertical = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HStack(const std::string& id) : Container(id) {}
|
HStack(const std::string& id) : Container(id) {}
|
||||||
@ -33,18 +29,8 @@ class HStack : public Container {
|
|||||||
padding = p;
|
padding = p;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setVAlign(VAlign a) {
|
void setCenterVertical(bool c) {
|
||||||
vAlign = a;
|
centerVertical = c;
|
||||||
markDirty();
|
|
||||||
}
|
|
||||||
void setVAlignFromString(const std::string& s) {
|
|
||||||
if (s == "center" || s == "Center") {
|
|
||||||
vAlign = VAlign::Center;
|
|
||||||
} else if (s == "bottom" || s == "Bottom") {
|
|
||||||
vAlign = VAlign::Bottom;
|
|
||||||
} else {
|
|
||||||
vAlign = VAlign::Top;
|
|
||||||
}
|
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,34 +48,13 @@ class HStack : public Container {
|
|||||||
int childW = child->getAbsW();
|
int childW = child->getAbsW();
|
||||||
int childH = child->getAbsH();
|
int childH = child->getAbsH();
|
||||||
|
|
||||||
// Extract child's own Y offset (from first layout pass)
|
// Re-layout with proper position
|
||||||
int childYOffset = child->getAbsY() - (absY + padding);
|
|
||||||
|
|
||||||
// Calculate base position based on vertical alignment
|
|
||||||
int childY = absY + padding;
|
int childY = absY + padding;
|
||||||
if (childH < availableH) {
|
if (centerVertical && childH < availableH) {
|
||||||
switch (vAlign) {
|
|
||||||
case VAlign::Center:
|
|
||||||
childY = absY + padding + (availableH - childH) / 2;
|
childY = absY + padding + (availableH - childH) / 2;
|
||||||
break;
|
|
||||||
case VAlign::Bottom:
|
|
||||||
childY = absY + padding + (availableH - childH);
|
|
||||||
break;
|
|
||||||
case VAlign::Top:
|
|
||||||
default:
|
|
||||||
childY = absY + padding;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add child's own Y offset to the calculated position
|
|
||||||
childY += childYOffset;
|
|
||||||
|
|
||||||
// Only do second layout pass if position changed from first pass
|
|
||||||
int firstPassY = child->getAbsY();
|
|
||||||
if (childY != firstPassY) {
|
|
||||||
child->layout(context, currentX, childY, childW, childH);
|
child->layout(context, currentX, childY, childW, childH);
|
||||||
}
|
|
||||||
currentX += childW + spacing;
|
currentX += childW + spacing;
|
||||||
availableW -= (childW + spacing);
|
availableW -= (childW + spacing);
|
||||||
if (availableW < 0) availableW = 0;
|
if (availableW < 0) availableW = 0;
|
||||||
@ -100,13 +65,9 @@ class HStack : public Container {
|
|||||||
// --- VStack: Vertical Stack Layout ---
|
// --- VStack: Vertical Stack Layout ---
|
||||||
// Children are arranged vertically with optional spacing
|
// Children are arranged vertically with optional spacing
|
||||||
class VStack : public Container {
|
class VStack : public Container {
|
||||||
public:
|
|
||||||
enum class HAlign { Left, Center, Right };
|
|
||||||
|
|
||||||
private:
|
|
||||||
int spacing = 0;
|
int spacing = 0;
|
||||||
int padding = 0;
|
int padding = 0;
|
||||||
HAlign hAlign = HAlign::Left;
|
bool centerHorizontal = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VStack(const std::string& id) : Container(id) {}
|
VStack(const std::string& id) : Container(id) {}
|
||||||
@ -121,18 +82,8 @@ class VStack : public Container {
|
|||||||
padding = p;
|
padding = p;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
void setHAlign(HAlign a) {
|
void setCenterHorizontal(bool c) {
|
||||||
hAlign = a;
|
centerHorizontal = c;
|
||||||
markDirty();
|
|
||||||
}
|
|
||||||
void setHAlignFromString(const std::string& s) {
|
|
||||||
if (s == "center" || s == "Center") {
|
|
||||||
hAlign = HAlign::Center;
|
|
||||||
} else if (s == "right" || s == "Right") {
|
|
||||||
hAlign = HAlign::Right;
|
|
||||||
} else {
|
|
||||||
hAlign = HAlign::Left;
|
|
||||||
}
|
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,34 +100,12 @@ class VStack : public Container {
|
|||||||
int childW = child->getAbsW();
|
int childW = child->getAbsW();
|
||||||
int childH = child->getAbsH();
|
int childH = child->getAbsH();
|
||||||
|
|
||||||
// Extract child's own X offset (from first layout pass)
|
|
||||||
int childXOffset = child->getAbsX() - (absX + padding);
|
|
||||||
|
|
||||||
// Calculate base position based on horizontal alignment
|
|
||||||
int childX = absX + padding;
|
int childX = absX + padding;
|
||||||
if (childW < availableW) {
|
if (centerHorizontal && childW < availableW) {
|
||||||
switch (hAlign) {
|
|
||||||
case HAlign::Center:
|
|
||||||
childX = absX + padding + (availableW - childW) / 2;
|
childX = absX + padding + (availableW - childW) / 2;
|
||||||
break;
|
|
||||||
case HAlign::Right:
|
|
||||||
childX = absX + padding + (availableW - childW);
|
|
||||||
break;
|
|
||||||
case HAlign::Left:
|
|
||||||
default:
|
|
||||||
childX = absX + padding;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add child's own X offset to the calculated position
|
|
||||||
childX += childXOffset;
|
|
||||||
|
|
||||||
// Only do second layout pass if position changed from first pass
|
|
||||||
int firstPassX = child->getAbsX();
|
|
||||||
if (childX != firstPassX) {
|
|
||||||
child->layout(context, childX, currentY, childW, childH);
|
child->layout(context, childX, currentY, childW, childH);
|
||||||
}
|
|
||||||
currentY += childH + spacing;
|
currentY += childH + spacing;
|
||||||
availableH -= (childH + spacing);
|
availableH -= (childH + spacing);
|
||||||
if (availableH < 0) availableH = 0;
|
if (availableH < 0) availableH = 0;
|
||||||
@ -219,10 +148,8 @@ class Grid : public Container {
|
|||||||
|
|
||||||
if (children.empty()) return;
|
if (children.empty()) return;
|
||||||
|
|
||||||
// Guard against division by zero
|
int availableW = absW - 2 * padding - (columns - 1) * colSpacing;
|
||||||
int cols = columns > 0 ? columns : 1;
|
int cellW = availableW / columns;
|
||||||
int availableW = absW - 2 * padding - (cols - 1) * colSpacing;
|
|
||||||
int cellW = availableW / cols;
|
|
||||||
int availableH = absH - 2 * padding;
|
int availableH = absH - 2 * padding;
|
||||||
|
|
||||||
int row = 0, col = 0;
|
int row = 0, col = 0;
|
||||||
@ -238,7 +165,7 @@ class Grid : public Container {
|
|||||||
if (childH > maxRowHeight) maxRowHeight = childH;
|
if (childH > maxRowHeight) maxRowHeight = childH;
|
||||||
|
|
||||||
col++;
|
col++;
|
||||||
if (col >= cols) {
|
if (col >= columns) {
|
||||||
col = 0;
|
col = 0;
|
||||||
row++;
|
row++;
|
||||||
currentY += maxRowHeight + rowSpacing;
|
currentY += maxRowHeight + rowSpacing;
|
||||||
@ -597,7 +524,15 @@ class TabBar : public Container {
|
|||||||
// Draw selection indicator
|
// Draw selection indicator
|
||||||
if (showIndicator && !children.empty()) {
|
if (showIndicator && !children.empty()) {
|
||||||
std::string selStr = context.evaluatestring(selectedExpr);
|
std::string selStr = context.evaluatestring(selectedExpr);
|
||||||
int selectedIdx = parseIntSafe(selStr, 0);
|
int selectedIdx = 0;
|
||||||
|
if (!selStr.empty()) {
|
||||||
|
// Try to parse as number
|
||||||
|
try {
|
||||||
|
selectedIdx = std::stoi(selStr);
|
||||||
|
} catch (...) {
|
||||||
|
selectedIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedIdx >= 0 && selectedIdx < static_cast<int>(children.size())) {
|
if (selectedIdx >= 0 && selectedIdx < static_cast<int>(children.size())) {
|
||||||
UIElement* tab = children[selectedIdx];
|
UIElement* tab = children[selectedIdx];
|
||||||
@ -688,9 +623,9 @@ class ScrollIndicator : public UIElement {
|
|||||||
std::string totalStr = context.evaluatestring(totalExpr);
|
std::string totalStr = context.evaluatestring(totalExpr);
|
||||||
std::string visStr = context.evaluatestring(visibleExpr);
|
std::string visStr = context.evaluatestring(visibleExpr);
|
||||||
|
|
||||||
float position = parseFloatSafe(posStr, 0.0f);
|
float position = posStr.empty() ? 0 : std::stof(posStr);
|
||||||
int total = parseIntSafe(totalStr, 1);
|
int total = totalStr.empty() ? 1 : std::stoi(totalStr);
|
||||||
int visible = parseIntSafe(visStr, 1);
|
int visible = visStr.empty() ? 1 : std::stoi(visStr);
|
||||||
|
|
||||||
if (total <= visible) {
|
if (total <= visible) {
|
||||||
// No need to show scrollbar
|
// No need to show scrollbar
|
||||||
|
|||||||
@ -26,7 +26,6 @@ struct ScreenCache {
|
|||||||
uint32_t contextHash = 0; // Hash of context data to detect changes
|
uint32_t contextHash = 0; // Hash of context data to detect changes
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
|
|
||||||
ScreenCache() = default;
|
|
||||||
~ScreenCache() {
|
~ScreenCache() {
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
free(buffer);
|
free(buffer);
|
||||||
@ -34,37 +33,6 @@ struct ScreenCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent double-free from copy
|
|
||||||
ScreenCache(const ScreenCache&) = delete;
|
|
||||||
ScreenCache& operator=(const ScreenCache&) = delete;
|
|
||||||
|
|
||||||
// Allow move
|
|
||||||
ScreenCache(ScreenCache&& other) noexcept
|
|
||||||
: buffer(other.buffer),
|
|
||||||
bufferSize(other.bufferSize),
|
|
||||||
screenName(std::move(other.screenName)),
|
|
||||||
contextHash(other.contextHash),
|
|
||||||
valid(other.valid) {
|
|
||||||
other.buffer = nullptr;
|
|
||||||
other.bufferSize = 0;
|
|
||||||
other.valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreenCache& operator=(ScreenCache&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
if (buffer) free(buffer);
|
|
||||||
buffer = other.buffer;
|
|
||||||
bufferSize = other.bufferSize;
|
|
||||||
screenName = std::move(other.screenName);
|
|
||||||
contextHash = other.contextHash;
|
|
||||||
valid = other.valid;
|
|
||||||
other.buffer = nullptr;
|
|
||||||
other.bufferSize = 0;
|
|
||||||
other.valid = false;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void invalidate() { valid = false; }
|
void invalidate() { valid = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -126,7 +126,6 @@ void Label::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
|||||||
int lineHeight = renderer.getLineHeight(fontId);
|
int lineHeight = renderer.getLineHeight(fontId);
|
||||||
|
|
||||||
std::vector<std::string> lines;
|
std::vector<std::string> lines;
|
||||||
lines.reserve(maxLines); // Pre-allocate to avoid reallocations
|
|
||||||
if (absW > 0 && textWidth > absW && maxLines > 1) {
|
if (absW > 0 && textWidth > absW && maxLines > 1) {
|
||||||
// Logic to wrap text
|
// Logic to wrap text
|
||||||
std::string remaining = finalStr;
|
std::string remaining = finalStr;
|
||||||
@ -375,13 +374,6 @@ void List::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
|||||||
int itemW = getItemWidth();
|
int itemW = getItemWidth();
|
||||||
int itemH = getItemHeight();
|
int itemH = getItemHeight();
|
||||||
|
|
||||||
// Pre-allocate string buffers to avoid repeated allocations
|
|
||||||
std::string prefix;
|
|
||||||
prefix.reserve(source.length() + 16);
|
|
||||||
std::string key;
|
|
||||||
key.reserve(source.length() + 32);
|
|
||||||
char numBuf[12];
|
|
||||||
|
|
||||||
// Handle different layout modes
|
// Handle different layout modes
|
||||||
if (direction == Direction::Horizontal || layoutMode == LayoutMode::Grid) {
|
if (direction == Direction::Horizontal || layoutMode == LayoutMode::Grid) {
|
||||||
// Horizontal or Grid layout
|
// Horizontal or Grid layout
|
||||||
@ -390,23 +382,16 @@ void List::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
|||||||
int currentX = absX;
|
int currentX = absX;
|
||||||
int currentY = absY;
|
int currentY = absY;
|
||||||
|
|
||||||
// For grid, calculate item width based on columns only if not explicitly set
|
// For grid, calculate item width based on columns
|
||||||
if (layoutMode == LayoutMode::Grid && columns > 1 && itemWidth == 0) {
|
if (layoutMode == LayoutMode::Grid && columns > 1) {
|
||||||
int totalSpacing = (columns - 1) * spacing;
|
int totalSpacing = (columns - 1) * spacing;
|
||||||
itemW = (absW - totalSpacing) / columns;
|
itemW = (absW - totalSpacing) / columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
// Build prefix efficiently: "source.i."
|
|
||||||
prefix.clear();
|
|
||||||
prefix += source;
|
|
||||||
prefix += '.';
|
|
||||||
snprintf(numBuf, sizeof(numBuf), "%d", i);
|
|
||||||
prefix += numBuf;
|
|
||||||
prefix += '.';
|
|
||||||
|
|
||||||
// Create item context with scoped variables
|
// Create item context with scoped variables
|
||||||
ThemeContext itemContext(&context);
|
ThemeContext itemContext(&context);
|
||||||
|
std::string prefix = source + "." + std::to_string(i) + ".";
|
||||||
|
|
||||||
// Standard list item variables - include all properties for full flexibility
|
// Standard list item variables - include all properties for full flexibility
|
||||||
std::string nameVal = context.getString(prefix + "Name");
|
std::string nameVal = context.getString(prefix + "Name");
|
||||||
@ -485,16 +470,9 @@ void List::draw(const GfxRenderer& renderer, const ThemeContext& context) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build prefix efficiently: "source.i."
|
|
||||||
prefix.clear();
|
|
||||||
prefix += source;
|
|
||||||
prefix += '.';
|
|
||||||
snprintf(numBuf, sizeof(numBuf), "%d", i);
|
|
||||||
prefix += numBuf;
|
|
||||||
prefix += '.';
|
|
||||||
|
|
||||||
// Create item context with scoped variables
|
// Create item context with scoped variables
|
||||||
ThemeContext itemContext(&context);
|
ThemeContext itemContext(&context);
|
||||||
|
std::string prefix = source + "." + std::to_string(i) + ".";
|
||||||
|
|
||||||
// Standard list item variables - include all properties for full flexibility
|
// Standard list item variables - include all properties for full flexibility
|
||||||
std::string nameVal = context.getString(prefix + "Name");
|
std::string nameVal = context.getString(prefix + "Name");
|
||||||
|
|||||||
@ -55,6 +55,9 @@ UIElement* ThemeManager::createElement(const std::string& id, const std::string&
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse integer safely - returns 0 on error
|
||||||
|
static int parseIntSafe(const std::string& val) { return static_cast<int>(std::strtol(val.c_str(), nullptr, 10)); }
|
||||||
|
|
||||||
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();
|
const auto elemType = elem->getType();
|
||||||
|
|
||||||
@ -141,13 +144,13 @@ void ThemeManager::applyProperties(UIElement* elem, const std::map<std::string,
|
|||||||
} else if (elemType == UIElement::ElementType::List) {
|
} else if (elemType == UIElement::ElementType::List) {
|
||||||
static_cast<List*>(elem)->setSpacing(parseIntSafe(val));
|
static_cast<List*>(elem)->setSpacing(parseIntSafe(val));
|
||||||
}
|
}
|
||||||
} else if (key == "VAlign") {
|
} else if (key == "CenterVertical") {
|
||||||
if (elemType == UIElement::ElementType::HStack) {
|
if (elemType == UIElement::ElementType::HStack) {
|
||||||
static_cast<HStack*>(elem)->setVAlignFromString(val);
|
static_cast<HStack*>(elem)->setCenterVertical(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
} else if (key == "HAlign") {
|
} else if (key == "CenterHorizontal") {
|
||||||
if (elemType == UIElement::ElementType::VStack) {
|
if (elemType == UIElement::ElementType::VStack) {
|
||||||
static_cast<VStack*>(elem)->setHAlignFromString(val);
|
static_cast<VStack*>(elem)->setCenterHorizontal(val == "true" || val == "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user