#include "UITree.h" #include "UILayout.h" #include "../Widgets/UIPanel.h" #include "../Widgets/UILabel.h" #include "../Widgets/UIButton.h" #include "../Widgets/UIImage.h" #include "../Widgets/UISlider.h" #include "../Widgets/UICheckbox.h" #include "../Widgets/UIProgressBar.h" #include "../Widgets/UITextInput.h" #include "../Widgets/UIScrollPanel.h" #include #include #include namespace grove { UITree::UITree() { registerDefaultWidgets(); } void UITree::registerWidget(const std::string& type, WidgetFactory factory) { m_factories[type] = std::move(factory); } void UITree::registerDefaultWidgets() { // Register panel factory registerWidget("panel", [](const IDataNode& node) -> std::unique_ptr { auto panel = std::make_unique(); // Parse style (const_cast safe for read-only operations) auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { std::string bgColorStr = style->getString("bgColor", "0x333333FF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { panel->bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } panel->borderRadius = static_cast(style->getDouble("borderRadius", 0.0)); } return panel; }); // Register label factory registerWidget("label", [](const IDataNode& node) -> std::unique_ptr { auto label = std::make_unique(); label->text = node.getString("text", ""); // Parse style (const_cast safe for read-only operations) auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { std::string colorStr = style->getString("color", "0xFFFFFFFF"); if (colorStr.size() >= 2 && (colorStr.substr(0, 2) == "0x" || colorStr.substr(0, 2) == "0X")) { label->color = static_cast(std::stoul(colorStr, nullptr, 16)); } label->fontSize = static_cast(style->getDouble("fontSize", 16.0)); } return label; }); // Register button factory registerWidget("button", [](const IDataNode& node) -> std::unique_ptr { auto button = std::make_unique(); button->text = node.getString("text", ""); button->onClick = node.getString("onClick", ""); button->enabled = node.getBool("enabled", true); // Parse style (const_cast safe for read-only operations) auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { // Normal style if (auto* normalStyle = style->getChildReadOnly("normal")) { std::string bgColorStr = normalStyle->getString("bgColor", "0x444444FF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { button->normalStyle.bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } std::string textColorStr = normalStyle->getString("textColor", "0xFFFFFFFF"); if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) { button->normalStyle.textColor = static_cast(std::stoul(textColorStr, nullptr, 16)); } } // Hover style if (auto* hoverStyle = style->getChildReadOnly("hover")) { std::string bgColorStr = hoverStyle->getString("bgColor", "0x666666FF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { button->hoverStyle.bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } std::string textColorStr = hoverStyle->getString("textColor", "0xFFFFFFFF"); if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) { button->hoverStyle.textColor = static_cast(std::stoul(textColorStr, nullptr, 16)); } } // Pressed style if (auto* pressedStyle = style->getChildReadOnly("pressed")) { std::string bgColorStr = pressedStyle->getString("bgColor", "0x333333FF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { button->pressedStyle.bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } std::string textColorStr = pressedStyle->getString("textColor", "0xFFFFFFFF"); if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) { button->pressedStyle.textColor = static_cast(std::stoul(textColorStr, nullptr, 16)); } } // Disabled style if (auto* disabledStyle = style->getChildReadOnly("disabled")) { std::string bgColorStr = disabledStyle->getString("bgColor", "0x222222FF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { button->disabledStyle.bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } std::string textColorStr = disabledStyle->getString("textColor", "0x666666FF"); if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) { button->disabledStyle.textColor = static_cast(std::stoul(textColorStr, nullptr, 16)); } } // Font size from style root button->fontSize = static_cast(style->getDouble("fontSize", 16.0)); } return button; }); // Register image factory registerWidget("image", [](const IDataNode& node) -> std::unique_ptr { auto image = std::make_unique(); image->textureId = node.getInt("textureId", 0); image->texturePath = node.getString("texturePath", ""); auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { std::string tintStr = style->getString("tintColor", "0xFFFFFFFF"); if (tintStr.size() >= 2 && (tintStr.substr(0, 2) == "0x" || tintStr.substr(0, 2) == "0X")) { image->tintColor = static_cast(std::stoul(tintStr, nullptr, 16)); } } return image; }); // Register slider factory registerWidget("slider", [](const IDataNode& node) -> std::unique_ptr { auto slider = std::make_unique(); slider->minValue = static_cast(node.getDouble("min", 0.0)); slider->maxValue = static_cast(node.getDouble("max", 100.0)); slider->value = static_cast(node.getDouble("value", 50.0)); slider->step = static_cast(node.getDouble("step", 0.0)); slider->horizontal = node.getBool("horizontal", true); slider->onChange = node.getString("onChange", ""); auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { std::string trackColorStr = style->getString("trackColor", "0x34495eFF"); if (trackColorStr.size() >= 2 && (trackColorStr.substr(0, 2) == "0x" || trackColorStr.substr(0, 2) == "0X")) { slider->trackColor = static_cast(std::stoul(trackColorStr, nullptr, 16)); } std::string fillColorStr = style->getString("fillColor", "0x3498dbFF"); if (fillColorStr.size() >= 2 && (fillColorStr.substr(0, 2) == "0x" || fillColorStr.substr(0, 2) == "0X")) { slider->fillColor = static_cast(std::stoul(fillColorStr, nullptr, 16)); } std::string handleColorStr = style->getString("handleColor", "0xecf0f1FF"); if (handleColorStr.size() >= 2 && (handleColorStr.substr(0, 2) == "0x" || handleColorStr.substr(0, 2) == "0X")) { slider->handleColor = static_cast(std::stoul(handleColorStr, nullptr, 16)); } slider->handleSize = static_cast(style->getDouble("handleSize", 16.0)); } return slider; }); // Register checkbox factory registerWidget("checkbox", [](const IDataNode& node) -> std::unique_ptr { auto checkbox = std::make_unique(); checkbox->checked = node.getBool("checked", false); checkbox->text = node.getString("text", ""); checkbox->onChange = node.getString("onChange", ""); auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { std::string boxColorStr = style->getString("boxColor", "0x34495eFF"); if (boxColorStr.size() >= 2 && (boxColorStr.substr(0, 2) == "0x" || boxColorStr.substr(0, 2) == "0X")) { checkbox->boxColor = static_cast(std::stoul(boxColorStr, nullptr, 16)); } std::string checkColorStr = style->getString("checkColor", "0x2ecc71FF"); if (checkColorStr.size() >= 2 && (checkColorStr.substr(0, 2) == "0x" || checkColorStr.substr(0, 2) == "0X")) { checkbox->checkColor = static_cast(std::stoul(checkColorStr, nullptr, 16)); } std::string textColorStr = style->getString("textColor", "0xecf0f1FF"); if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) { checkbox->textColor = static_cast(std::stoul(textColorStr, nullptr, 16)); } checkbox->boxSize = static_cast(style->getDouble("boxSize", 24.0)); checkbox->fontSize = static_cast(style->getDouble("fontSize", 16.0)); checkbox->spacing = static_cast(style->getDouble("spacing", 8.0)); } return checkbox; }); // Register progressbar factory registerWidget("progressbar", [](const IDataNode& node) -> std::unique_ptr { auto progressBar = std::make_unique(); progressBar->setProgress(static_cast(node.getDouble("progress", 0.5))); progressBar->horizontal = node.getBool("horizontal", true); progressBar->showText = node.getBool("showText", false); auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { std::string bgColorStr = style->getString("bgColor", "0x34495eFF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { progressBar->bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } std::string fillColorStr = style->getString("fillColor", "0x2ecc71FF"); if (fillColorStr.size() >= 2 && (fillColorStr.substr(0, 2) == "0x" || fillColorStr.substr(0, 2) == "0X")) { progressBar->fillColor = static_cast(std::stoul(fillColorStr, nullptr, 16)); } std::string textColorStr = style->getString("textColor", "0xFFFFFFFF"); if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) { progressBar->textColor = static_cast(std::stoul(textColorStr, nullptr, 16)); } progressBar->fontSize = static_cast(style->getDouble("fontSize", 14.0)); } return progressBar; }); // Register textinput factory registerWidget("textinput", [](const IDataNode& node) -> std::unique_ptr { auto textInput = std::make_unique(); textInput->text = node.getString("text", ""); textInput->placeholder = node.getString("placeholder", "Enter text..."); textInput->maxLength = node.getInt("maxLength", 256); textInput->passwordMode = node.getBool("passwordMode", false); textInput->onSubmit = node.getString("onSubmit", ""); // Parse filter type std::string filterStr = node.getString("filter", "none"); if (filterStr == "alphanumeric") { textInput->filter = TextInputFilter::Alphanumeric; } else if (filterStr == "numeric") { textInput->filter = TextInputFilter::Numeric; } else if (filterStr == "float") { textInput->filter = TextInputFilter::Float; } else if (filterStr == "nospaces") { textInput->filter = TextInputFilter::NoSpaces; } else { textInput->filter = TextInputFilter::None; } auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { // Normal style std::string bgColorStr = style->getString("bgColor", "0x222222FF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { textInput->normalStyle.bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } std::string textColorStr = style->getString("textColor", "0xFFFFFFFF"); if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) { textInput->normalStyle.textColor = static_cast(std::stoul(textColorStr, nullptr, 16)); } std::string borderColorStr = style->getString("borderColor", "0x666666FF"); if (borderColorStr.size() >= 2 && (borderColorStr.substr(0, 2) == "0x" || borderColorStr.substr(0, 2) == "0X")) { textInput->normalStyle.borderColor = static_cast(std::stoul(borderColorStr, nullptr, 16)); } std::string focusBorderColorStr = style->getString("focusBorderColor", "0x4488FFFF"); if (focusBorderColorStr.size() >= 2 && (focusBorderColorStr.substr(0, 2) == "0x" || focusBorderColorStr.substr(0, 2) == "0X")) { textInput->normalStyle.focusBorderColor = static_cast(std::stoul(focusBorderColorStr, nullptr, 16)); } // Copy normal style to focused and disabled textInput->focusedStyle = textInput->normalStyle; textInput->disabledStyle = textInput->normalStyle; textInput->disabledStyle.bgColor = 0x111111FF; textInput->disabledStyle.textColor = 0x666666FF; textInput->fontSize = static_cast(style->getDouble("fontSize", 16.0)); } return textInput; }); // Register scrollpanel factory registerWidget("scrollpanel", [](const IDataNode& node) -> std::unique_ptr { auto scrollPanel = std::make_unique(); scrollPanel->scrollVertical = node.getBool("scrollVertical", true); scrollPanel->scrollHorizontal = node.getBool("scrollHorizontal", false); scrollPanel->showScrollbar = node.getBool("showScrollbar", true); scrollPanel->dragToScroll = node.getBool("dragToScroll", true); // Parse style auto& mutableNode = const_cast(node); if (auto* style = mutableNode.getChildReadOnly("style")) { std::string bgColorStr = style->getString("bgColor", "0x2a2a2aFF"); if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) { scrollPanel->bgColor = static_cast(std::stoul(bgColorStr, nullptr, 16)); } std::string borderColorStr = style->getString("borderColor", "0x444444FF"); if (borderColorStr.size() >= 2 && (borderColorStr.substr(0, 2) == "0x" || borderColorStr.substr(0, 2) == "0X")) { scrollPanel->borderColor = static_cast(std::stoul(borderColorStr, nullptr, 16)); } std::string scrollbarColorStr = style->getString("scrollbarColor", "0x666666FF"); if (scrollbarColorStr.size() >= 2 && (scrollbarColorStr.substr(0, 2) == "0x" || scrollbarColorStr.substr(0, 2) == "0X")) { scrollPanel->scrollbarColor = static_cast(std::stoul(scrollbarColorStr, nullptr, 16)); } scrollPanel->borderWidth = static_cast(style->getDouble("borderWidth", 1.0)); scrollPanel->scrollbarWidth = static_cast(style->getDouble("scrollbarWidth", 8.0)); } return scrollPanel; }); } std::unique_ptr UITree::loadFromJson(const IDataNode& layoutData) { m_root = parseWidget(layoutData); if (m_root) { m_root->computeAbsolutePosition(); } return std::move(m_root); } UIWidget* UITree::findById(const std::string& id) { if (!m_root) return nullptr; return m_root->findById(id); } std::unique_ptr UITree::parseWidget(const IDataNode& node) { std::string type = node.getString("type", ""); if (type.empty()) { spdlog::warn("UITree: Widget missing 'type' property"); return nullptr; } auto it = m_factories.find(type); if (it == m_factories.end()) { spdlog::warn("UITree: Unknown widget type '{}'", type); return nullptr; } // Create widget via factory auto widget = it->second(node); if (!widget) { spdlog::warn("UITree: Factory failed for type '{}'", type); return nullptr; } // Parse common properties parseCommonProperties(widget.get(), node); // Parse children recursively (const_cast safe for read-only operations) auto& mutableNode = const_cast(node); if (auto* children = mutableNode.getChildReadOnly("children")) { auto childNames = children->getChildNames(); for (const auto& childName : childNames) { if (auto* childNode = children->getChildReadOnly(childName)) { if (auto child = parseWidget(*childNode)) { widget->addChild(std::move(child)); } } } } // Also check for array-style children (indexed by number) // JsonDataNode stores array elements as children with numeric keys int childIndex = 0; while (true) { std::string childKey = std::to_string(childIndex); // Check if there's a child with this numeric key inside "children" if (auto* childrenNode = mutableNode.getChildReadOnly("children")) { if (auto* childNode = childrenNode->getChildReadOnly(childKey)) { if (auto child = parseWidget(*childNode)) { widget->addChild(std::move(child)); } childIndex++; continue; } } break; } return widget; } void UITree::parseCommonProperties(UIWidget* widget, const IDataNode& node) { widget->id = node.getString("id", ""); widget->tooltip = node.getString("tooltip", ""); widget->x = static_cast(node.getDouble("x", 0.0)); widget->y = static_cast(node.getDouble("y", 0.0)); widget->width = static_cast(node.getDouble("width", 0.0)); widget->height = static_cast(node.getDouble("height", 0.0)); widget->visible = node.getBool("visible", true); // Parse layout properties (Phase 2) auto& mutableNode = const_cast(node); if (auto* layout = mutableNode.getChildReadOnly("layout")) { parseLayoutProperties(widget, *layout); } // Parse flex property (can be at root level) if (node.hasChild("flex")) { widget->layoutProps.flex = static_cast(node.getDouble("flex", 0.0)); } } void UITree::parseLayoutProperties(UIWidget* widget, const IDataNode& layoutNode) { // Layout mode std::string modeStr = layoutNode.getString("type", "absolute"); static const std::unordered_map modeMap = { {"vertical", LayoutMode::Vertical}, {"horizontal", LayoutMode::Horizontal}, {"stack", LayoutMode::Stack}, {"absolute", LayoutMode::Absolute} }; auto modeIt = modeMap.find(modeStr); if (modeIt != modeMap.end()) { widget->layoutProps.mode = modeIt->second; } // Padding widget->layoutProps.padding = static_cast(layoutNode.getDouble("padding", 0.0)); widget->layoutProps.paddingTop = static_cast(layoutNode.getDouble("paddingTop", 0.0)); widget->layoutProps.paddingRight = static_cast(layoutNode.getDouble("paddingRight", 0.0)); widget->layoutProps.paddingBottom = static_cast(layoutNode.getDouble("paddingBottom", 0.0)); widget->layoutProps.paddingLeft = static_cast(layoutNode.getDouble("paddingLeft", 0.0)); // Margin widget->layoutProps.margin = static_cast(layoutNode.getDouble("margin", 0.0)); widget->layoutProps.marginTop = static_cast(layoutNode.getDouble("marginTop", 0.0)); widget->layoutProps.marginRight = static_cast(layoutNode.getDouble("marginRight", 0.0)); widget->layoutProps.marginBottom = static_cast(layoutNode.getDouble("marginBottom", 0.0)); widget->layoutProps.marginLeft = static_cast(layoutNode.getDouble("marginLeft", 0.0)); // Spacing widget->layoutProps.spacing = static_cast(layoutNode.getDouble("spacing", 0.0)); // Alignment std::string alignStr = layoutNode.getString("align", "start"); static const std::unordered_map alignMap = { {"start", Alignment::Start}, {"center", Alignment::Center}, {"end", Alignment::End}, {"stretch", Alignment::Stretch} }; auto alignIt = alignMap.find(alignStr); if (alignIt != alignMap.end()) { widget->layoutProps.align = alignIt->second; } // Justification std::string justifyStr = layoutNode.getString("justify", "start"); static const std::unordered_map justifyMap = { {"start", Justification::Start}, {"center", Justification::Center}, {"end", Justification::End}, {"spaceBetween", Justification::SpaceBetween}, {"spaceAround", Justification::SpaceAround} }; auto justifyIt = justifyMap.find(justifyStr); if (justifyIt != justifyMap.end()) { widget->layoutProps.justify = justifyIt->second; } // Size constraints widget->layoutProps.minWidth = static_cast(layoutNode.getDouble("minWidth", 0.0)); widget->layoutProps.minHeight = static_cast(layoutNode.getDouble("minHeight", 0.0)); widget->layoutProps.maxWidth = static_cast(layoutNode.getDouble("maxWidth", -1.0)); widget->layoutProps.maxHeight = static_cast(layoutNode.getDouble("maxHeight", -1.0)); // Flex widget->layoutProps.flex = static_cast(layoutNode.getDouble("flex", 0.0)); } } // namespace grove