#include "ThemeManager.h" #include #include #include #include #include #include "DefaultTheme.h" #include "LayoutElements.h" #include "ListElement.h" namespace ThemeEngine { void ThemeManager::begin() {} 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; // Otherwise relative to theme root std::string rootPath = "/themes/" + currentThemeName + "/" + assetName; if (SdMan.exists(rootPath.c_str())) return rootPath; // Fallback to assets/ subfolder return "/themes/" + currentThemeName + "/assets/" + assetName; } 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); // Layout elements 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); return nullptr; } // Parse integer safely - returns 0 on error static int parseIntSafe(const std::string& val) { return static_cast(std::strtol(val.c_str(), nullptr, 10)); } void ThemeManager::applyProperties(UIElement* elem, const std::map& props) { const auto elemType = elem->getType(); for (const auto& kv : props) { const std::string& key = kv.first; const std::string& val = kv.second; // Common properties if (key == "X") elem->setX(Dimension::parse(val)); else if (key == "Y") elem->setY(Dimension::parse(val)); else if (key == "Width") elem->setWidth(Dimension::parse(val)); else if (key == "Height") elem->setHeight(Dimension::parse(val)); else if (key == "Visible") elem->setVisibleExpr(val); else if (key == "Cacheable") elem->setCacheable(val == "true" || val == "1"); // Rectangle properties else if (key == "Fill") { if (elemType == UIElement::ElementType::Rectangle) { auto rect = static_cast(elem); if (val.find('{') != std::string::npos) { rect->setFillExpr(val); } else { rect->setFill(val == "true" || val == "1"); } } } else if (key == "Color") { if (elemType == UIElement::ElementType::Rectangle) { static_cast(elem)->setColorExpr(val); } else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack || elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid || elemType == UIElement::ElementType::TabBar) { static_cast(elem)->setBackgroundColorExpr(val); } else if (elemType == UIElement::ElementType::Label) { static_cast(elem)->setColorExpr(val); } else if (elemType == UIElement::ElementType::Divider) { static_cast(elem)->setColorExpr(val); } else if (elemType == UIElement::ElementType::Icon) { static_cast(elem)->setColorExpr(val); } else if (elemType == UIElement::ElementType::BatteryIcon) { static_cast(elem)->setColor(val); } } // Container properties else if (key == "Border") { if (auto c = elem->asContainer()) { if (val.find('{') != std::string::npos) { c->setBorderExpr(val); } else { c->setBorder(val == "true" || val == "1" || val == "yes"); } } } else if (key == "Padding") { if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack || elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) { static_cast(elem)->setPadding(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::TabBar) { static_cast(elem)->setPadding(parseIntSafe(val)); } } else if (key == "BorderRadius") { if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack || elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) { static_cast(elem)->setBorderRadius(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::Bitmap) { static_cast(elem)->setBorderRadius(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::Rectangle) { static_cast(elem)->setBorderRadius(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::Badge) { static_cast(elem)->setBorderRadius(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setBorderRadius(parseIntSafe(val)); } } else if (key == "Spacing") { if (elemType == UIElement::ElementType::HStack) { static_cast(elem)->setSpacing(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::VStack) { static_cast(elem)->setSpacing(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::List) { static_cast(elem)->setSpacing(parseIntSafe(val)); } } else if (key == "CenterVertical") { if (elemType == UIElement::ElementType::HStack) { static_cast(elem)->setCenterVertical(val == "true" || val == "1"); } } else if (key == "CenterHorizontal") { if (elemType == UIElement::ElementType::VStack) { static_cast(elem)->setCenterHorizontal(val == "true" || val == "1"); } } // Label properties else if (key == "Text") { if (elemType == UIElement::ElementType::Label) { static_cast(elem)->setText(val); } else if (elemType == UIElement::ElementType::Badge) { static_cast(elem)->setText(val); } } else if (key == "Font") { if (elemType == UIElement::ElementType::Label) { if (fontMap.count(val)) { static_cast(elem)->setFont(fontMap[val]); } } else if (elemType == UIElement::ElementType::Badge) { if (fontMap.count(val)) { static_cast(elem)->setFont(fontMap[val]); } } } else if (key == "Centered") { if (elemType == UIElement::ElementType::Label) { static_cast(elem)->setCentered(val == "true" || val == "1"); } } 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; static_cast(elem)->setAlignment(align); } } else if (key == "MaxLines") { if (elemType == UIElement::ElementType::Label) { static_cast(elem)->setMaxLines(parseIntSafe(val)); } } else if (key == "Ellipsis") { if (elemType == UIElement::ElementType::Label) { static_cast(elem)->setEllipsis(val == "true" || val == "1"); } } // Bitmap/Icon properties else if (key == "Src") { if (elemType == UIElement::ElementType::Bitmap) { auto b = static_cast(elem); if (val.find('{') == std::string::npos && val.find('/') == std::string::npos) { b->setSrc(getAssetPath(val)); } else { b->setSrc(val); } } else if (elemType == UIElement::ElementType::Icon) { static_cast(elem)->setSrc(val); } } else if (key == "ScaleToFit") { if (elemType == UIElement::ElementType::Bitmap) { static_cast(elem)->setScaleToFit(val == "true" || val == "1"); } } else if (key == "PreserveAspect") { if (elemType == UIElement::ElementType::Bitmap) { static_cast(elem)->setPreserveAspect(val == "true" || val == "1"); } } else if (key == "IconSize") { if (elemType == UIElement::ElementType::Icon) { static_cast(elem)->setIconSize(parseIntSafe(val)); } } // List properties else if (key == "Source") { if (elemType == UIElement::ElementType::List) { static_cast(elem)->setSource(val); } } else if (key == "ItemTemplate") { if (elemType == UIElement::ElementType::List) { static_cast(elem)->setItemTemplateId(val); } } else if (key == "ItemHeight") { if (elemType == UIElement::ElementType::List) { static_cast(elem)->setItemHeight(parseIntSafe(val)); } } else if (key == "ItemWidth") { if (elemType == UIElement::ElementType::List) { static_cast(elem)->setItemWidth(parseIntSafe(val)); } } else if (key == "Direction") { if (elemType == UIElement::ElementType::List) { static_cast(elem)->setDirectionFromString(val); } } else if (key == "Columns") { if (elemType == UIElement::ElementType::List) { static_cast(elem)->setColumns(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::Grid) { static_cast(elem)->setColumns(parseIntSafe(val)); } } else if (key == "RowSpacing") { if (elemType == UIElement::ElementType::Grid) { static_cast(elem)->setRowSpacing(parseIntSafe(val)); } } else if (key == "ColSpacing") { if (elemType == UIElement::ElementType::Grid) { static_cast(elem)->setColSpacing(parseIntSafe(val)); } } // ProgressBar properties else if (key == "Value") { if (elemType == UIElement::ElementType::ProgressBar) { static_cast(elem)->setValue(val); } else if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setValue(val); } else if (elemType == UIElement::ElementType::BatteryIcon) { static_cast(elem)->setValue(val); } } else if (key == "Max") { if (elemType == UIElement::ElementType::ProgressBar) { static_cast(elem)->setMax(val); } } else if (key == "FgColor") { if (elemType == UIElement::ElementType::ProgressBar) { static_cast(elem)->setFgColor(val); } else if (elemType == UIElement::ElementType::Badge) { static_cast(elem)->setFgColor(val); } } else if (key == "BgColor") { if (elemType == UIElement::ElementType::ProgressBar) { static_cast(elem)->setBgColor(val); } else if (elemType == UIElement::ElementType::Badge) { static_cast(elem)->setBgColor(val); } else if (elemType == UIElement::ElementType::Container || elemType == UIElement::ElementType::HStack || elemType == UIElement::ElementType::VStack || elemType == UIElement::ElementType::Grid) { static_cast(elem)->setBackgroundColorExpr(val); } } else if (key == "ShowBorder") { if (elemType == UIElement::ElementType::ProgressBar) { static_cast(elem)->setShowBorder(val == "true" || val == "1"); } } // Divider properties else if (key == "Horizontal") { if (elemType == UIElement::ElementType::Divider) { static_cast(elem)->setHorizontal(val == "true" || val == "1"); } } else if (key == "Thickness") { if (elemType == UIElement::ElementType::Divider) { static_cast(elem)->setThickness(parseIntSafe(val)); } } // Toggle properties else if (key == "OnColor") { if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setOnColor(val); } } else if (key == "OffColor") { if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setOffColor(val); } } else if (key == "KnobColor") { if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setKnobColor(val); } } else if (key == "TrackWidth") { if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setTrackWidth(parseIntSafe(val)); } else if (elemType == UIElement::ElementType::ScrollIndicator) { static_cast(elem)->setTrackWidth(parseIntSafe(val)); } } else if (key == "TrackHeight") { if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setTrackHeight(parseIntSafe(val)); } } else if (key == "KnobSize") { if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setKnobSize(parseIntSafe(val)); } } else if (key == "KnobRadius") { if (elemType == UIElement::ElementType::Toggle) { static_cast(elem)->setKnobRadius(parseIntSafe(val)); } } // TabBar properties else if (key == "Selected") { if (elemType == UIElement::ElementType::TabBar) { static_cast(elem)->setSelected(val); } } else if (key == "TabSpacing") { if (elemType == UIElement::ElementType::TabBar) { static_cast(elem)->setTabSpacing(parseIntSafe(val)); } } else if (key == "IndicatorHeight") { if (elemType == UIElement::ElementType::TabBar) { static_cast(elem)->setIndicatorHeight(parseIntSafe(val)); } } else if (key == "ShowIndicator") { if (elemType == UIElement::ElementType::TabBar) { static_cast(elem)->setShowIndicator(val == "true" || val == "1"); } } // ScrollIndicator properties else if (key == "Position") { if (elemType == UIElement::ElementType::ScrollIndicator) { static_cast(elem)->setPosition(val); } } else if (key == "Total") { if (elemType == UIElement::ElementType::ScrollIndicator) { static_cast(elem)->setTotal(val); } } else if (key == "VisibleCount") { if (elemType == UIElement::ElementType::ScrollIndicator) { static_cast(elem)->setVisibleCount(val); } } // Badge properties else if (key == "PaddingH") { if (elemType == UIElement::ElementType::Badge) { static_cast(elem)->setPaddingH(parseIntSafe(val)); } } else if (key == "PaddingV") { if (elemType == UIElement::ElementType::Badge) { static_cast(elem)->setPaddingV(parseIntSafe(val)); } } } } const std::vector* ThemeManager::getCachedAsset(const std::string& path) { if (assetCache.count(path)) { return &assetCache.at(path); } if (!SdMan.exists(path.c_str())) return nullptr; FsFile file; if (SdMan.openFileForRead("ThemeCache", path, file)) { size_t size = file.size(); auto& buf = assetCache[path]; buf.resize(size); file.read(buf.data(), size); file.close(); return &buf; } return nullptr; } const ProcessedAsset* ThemeManager::getProcessedAsset(const std::string& path, GfxRenderer::Orientation orientation, int targetW, int targetH) { std::string cacheKey = path; if (targetW > 0 && targetH > 0) { cacheKey += ":" + std::to_string(targetW) + "x" + std::to_string(targetH); } if (processedCache.count(cacheKey)) { const auto& asset = processedCache.at(cacheKey); if (asset.orientation == orientation) { return &asset; } } return nullptr; } 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); } processedCache[cacheKey] = asset; } void ThemeManager::clearAssetCaches() { assetCache.clear(); processedCache.clear(); } void ThemeManager::unloadTheme() { for (auto& kv : elements) { delete kv.second; } elements.clear(); clearAssetCaches(); invalidateAllCaches(); } void ThemeManager::invalidateAllCaches() { for (auto& kv : screenCaches) { kv.second.invalidate(); } } void ThemeManager::invalidateScreenCache(const std::string& screenName) { if (screenCaches.count(screenName)) { screenCaches[screenName].invalidate(); } } uint32_t ThemeManager::computeContextHash(const ThemeContext& context, const std::string& screenName) { uint32_t hash = 2166136261u; for (char c : screenName) { hash ^= static_cast(c); hash *= 16777619u; } return hash; } void ThemeManager::loadTheme(const std::string& themeName) { unloadTheme(); currentThemeName = themeName; std::map> sections; if (themeName == "Default" || themeName.empty()) { std::string path = "/themes/Default/theme.ini"; if (SdMan.exists(path.c_str())) { FsFile file; if (SdMan.openFileForRead("Theme", path, file)) { sections = IniParser::parse(file); file.close(); } } else { sections = IniParser::parseString(getDefaultThemeIni()); } currentThemeName = "Default"; } else { std::string path = "/themes/" + themeName + "/theme.ini"; if (!SdMan.exists(path.c_str())) { sections = IniParser::parseString(getDefaultThemeIni()); currentThemeName = "Default"; } else { FsFile file; if (SdMan.openFileForRead("Theme", path, file)) { sections = IniParser::parse(file); file.close(); } else { sections = IniParser::parseString(getDefaultThemeIni()); currentThemeName = "Default"; } } } // Read theme configuration from [Global] section navBookCount = 1; if (sections.count("Global")) { const auto& global = sections.at("Global"); if (global.count("NavBookCount")) { navBookCount = parseIntSafe(global.at("NavBookCount")); if (navBookCount < 1) navBookCount = 1; if (navBookCount > 10) navBookCount = 10; } } // Pass 1: Create elements for (const auto& sec : sections) { const std::string& id = sec.first; const std::map& props = sec.second; if (id == "Global") continue; auto it = props.find("Type"); if (it == props.end()) continue; const std::string& type = it->second; if (type.empty()) continue; UIElement* elem = createElement(id, type); if (elem) { elements[id] = elem; } } // Pass 2: Apply properties and wire parent relationships std::vector lists; for (const auto& sec : sections) { const std::string& id = sec.first; if (id == "Global") continue; if (elements.find(id) == elements.end()) continue; UIElement* elem = elements[id]; applyProperties(elem, sec.second); if (elem->getType() == UIElement::ElementType::List) { lists.push_back(static_cast(elem)); } // Wire parent relationship (fallback if Children not specified) if (sec.second.count("Parent")) { const std::string& parentId = sec.second.at("Parent"); if (elements.count(parentId)) { UIElement* parent = elements[parentId]; if (auto c = parent->asContainer()) { const auto& children = c->getChildren(); if (std::find(children.begin(), children.end(), elem) == children.end()) { c->addChild(elem); } } } } // Children property - explicit ordering if (sec.second.count("Children")) { if (auto c = elem->asContainer()) { c->clearChildren(); std::string s = sec.second.at("Children"); size_t pos = 0; auto processChild = [&](const std::string& childName) { std::string childId = childName; size_t start = childId.find_first_not_of(" "); size_t end = childId.find_last_not_of(" "); if (start == std::string::npos) return; childId = childId.substr(start, end - start + 1); if (elements.count(childId)) { c->addChild(elements[childId]); } }; while ((pos = s.find(',')) != std::string::npos) { processChild(s.substr(0, pos)); s.erase(0, pos + 1); } processChild(s); } } } // Pass 3: Resolve list templates for (auto* l : lists) { l->resolveTemplate(elements); } } void ThemeManager::renderScreen(const std::string& screenName, const GfxRenderer& renderer, const ThemeContext& context) { if (elements.count(screenName) == 0) { return; } UIElement* root = elements[screenName]; 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) { renderScreen(screenName, renderer, context); } } // namespace ThemeEngine