#include "Hierarchy/ProductHierarchyModel.h" #include #include #include #include #include namespace XCEngine::UI::Editor::App { namespace { const ProductHierarchyNode* FindNodeRecursive( const std::vector& nodes, std::string_view nodeId) { for (const ProductHierarchyNode& node : nodes) { if (node.nodeId == nodeId) { return &node; } if (const ProductHierarchyNode* child = FindNodeRecursive(node.children, nodeId); child != nullptr) { return child; } } return nullptr; } ProductHierarchyNode* FindNodeRecursive( std::vector& nodes, std::string_view nodeId) { for (ProductHierarchyNode& node : nodes) { if (node.nodeId == nodeId) { return &node; } if (ProductHierarchyNode* child = FindNodeRecursive(node.children, nodeId); child != nullptr) { return child; } } return nullptr; } bool FindNodeParentRecursive( const std::vector& nodes, std::string_view nodeId, const ProductHierarchyNode*& parent) { for (const ProductHierarchyNode& node : nodes) { for (const ProductHierarchyNode& child : node.children) { if (child.nodeId == nodeId) { parent = &node; return true; } } if (FindNodeParentRecursive(node.children, nodeId, parent)) { return true; } } return false; } bool ExtractNodeRecursive( std::vector& nodes, std::string_view nodeId, ProductHierarchyNode& extractedNode) { for (std::size_t index = 0u; index < nodes.size(); ++index) { if (nodes[index].nodeId == nodeId) { extractedNode = std::move(nodes[index]); nodes.erase(nodes.begin() + static_cast(index)); return true; } if (ExtractNodeRecursive(nodes[index].children, nodeId, extractedNode)) { return true; } } return false; } void BuildTreeItemsRecursive( const std::vector& nodes, std::uint32_t depth, const ::XCEngine::UI::UITextureHandle& icon, std::vector& items) { for (const ProductHierarchyNode& node : nodes) { Widgets::UIEditorTreeViewItem item = {}; item.itemId = node.nodeId; item.label = node.label; item.depth = depth; item.forceLeaf = node.children.empty(); item.leadingIcon = icon; items.push_back(std::move(item)); BuildTreeItemsRecursive(node.children, depth + 1u, icon, items); } } } // namespace ProductHierarchyModel ProductHierarchyModel::BuildDefault() { ProductHierarchyModel model = {}; model.m_roots = { ProductHierarchyNode{ "main_camera", "Main Camera", {} }, ProductHierarchyNode{ "directional_light", "Directional Light", {} }, ProductHierarchyNode{ "player", "Player", { ProductHierarchyNode{ "camera_pivot", "Camera Pivot", {} }, ProductHierarchyNode{ "player_mesh", "Mesh", {} } } }, ProductHierarchyNode{ "environment", "Environment", { ProductHierarchyNode{ "ground", "Ground", {} }, ProductHierarchyNode{ "props", "Props", { ProductHierarchyNode{ "crate_01", "Crate_01", {} }, ProductHierarchyNode{ "barrel_01", "Barrel_01", {} } } } } } }; model.m_nextGeneratedNodeId = 1u; return model; } bool ProductHierarchyModel::Empty() const { return m_roots.empty(); } bool ProductHierarchyModel::ContainsNode(std::string_view nodeId) const { return FindNode(nodeId) != nullptr; } const ProductHierarchyNode* ProductHierarchyModel::FindNode(std::string_view nodeId) const { return FindNodeRecursive(m_roots, nodeId); } ProductHierarchyNode* ProductHierarchyModel::FindNode(std::string_view nodeId) { return FindNodeRecursive(m_roots, nodeId); } std::optional ProductHierarchyModel::GetParentId(std::string_view nodeId) const { const ProductHierarchyNode* parent = nullptr; if (!FindNodeParentRecursive(m_roots, nodeId, parent) || parent == nullptr) { return std::nullopt; } return parent->nodeId; } bool ProductHierarchyModel::RenameNode(std::string_view nodeId, std::string label) { ProductHierarchyNode* node = FindNode(nodeId); if (node == nullptr || label.empty() || node->label == label) { return false; } node->label = std::move(label); return true; } std::string ProductHierarchyModel::CreateChild( std::string_view parentId, std::string_view label) { ProductHierarchyNode* parent = FindNode(parentId); if (parent == nullptr) { return {}; } ProductHierarchyNode node = {}; node.nodeId = AllocateNodeId(); node.label = label.empty() ? std::string("GameObject") : std::string(label); parent->children.push_back(std::move(node)); return parent->children.back().nodeId; } bool ProductHierarchyModel::DeleteNode(std::string_view nodeId) { if (nodeId.empty()) { return false; } ProductHierarchyNode removed = {}; return ExtractNodeRecursive(m_roots, nodeId, removed); } bool ProductHierarchyModel::CanReparent( std::string_view sourceNodeId, std::string_view targetParentId) const { if (sourceNodeId.empty()) { return false; } const ProductHierarchyNode* source = FindNode(sourceNodeId); if (source == nullptr) { return false; } if (targetParentId.empty()) { return true; } const ProductHierarchyNode* targetParent = FindNode(targetParentId); return CanAdopt(sourceNodeId, targetParent); } bool ProductHierarchyModel::Reparent( std::string_view sourceNodeId, std::string_view targetParentId) { if (!CanReparent(sourceNodeId, targetParentId) || targetParentId.empty()) { return false; } const std::optional currentParentId = GetParentId(sourceNodeId); if (currentParentId.has_value() && currentParentId.value() == targetParentId) { return false; } ProductHierarchyNode movedNode = {}; if (!ExtractNodeRecursive(m_roots, sourceNodeId, movedNode)) { return false; } ProductHierarchyNode* targetParent = FindNode(targetParentId); if (targetParent == nullptr) { return false; } targetParent->children.push_back(std::move(movedNode)); return true; } bool ProductHierarchyModel::MoveToRoot(std::string_view sourceNodeId) { if (sourceNodeId.empty() || !ContainsNode(sourceNodeId)) { return false; } if (!GetParentId(sourceNodeId).has_value()) { return false; } ProductHierarchyNode movedNode = {}; if (!ExtractNodeRecursive(m_roots, sourceNodeId, movedNode)) { return false; } m_roots.push_back(std::move(movedNode)); return true; } std::vector ProductHierarchyModel::BuildTreeItems( const ::XCEngine::UI::UITextureHandle& icon) const { std::vector items = {}; BuildTreeItemsRecursive(m_roots, 0u, icon, items); return items; } std::string ProductHierarchyModel::AllocateNodeId() { std::ostringstream stream = {}; stream << "generated_node_" << m_nextGeneratedNodeId++; return stream.str(); } bool ProductHierarchyModel::CanAdopt( std::string_view sourceNodeId, const ProductHierarchyNode* targetParent) const { if (targetParent == nullptr || sourceNodeId == targetParent->nodeId) { return false; } const ProductHierarchyNode* source = FindNode(sourceNodeId); if (source == nullptr) { return false; } return !ContainsDescendant(*source, targetParent->nodeId); } bool ProductHierarchyModel::ContainsDescendant( const ProductHierarchyNode& node, std::string_view candidateId) const { for (const ProductHierarchyNode& child : node.children) { if (child.nodeId == candidateId || ContainsDescendant(child, candidateId)) { return true; } } return false; } } // namespace XCEngine::UI::Editor::App