#include "HierarchyModel.h" #include #include #include #include #include namespace XCEngine::UI::Editor::App { namespace { const HierarchyNode* FindNodeRecursive( const std::vector& nodes, std::string_view nodeId) { for (const HierarchyNode& node : nodes) { if (node.nodeId == nodeId) { return &node; } if (const HierarchyNode* child = FindNodeRecursive(node.children, nodeId); child != nullptr) { return child; } } return nullptr; } HierarchyNode* FindNodeRecursive( std::vector& nodes, std::string_view nodeId) { for (HierarchyNode& node : nodes) { if (node.nodeId == nodeId) { return &node; } if (HierarchyNode* child = FindNodeRecursive(node.children, nodeId); child != nullptr) { return child; } } return nullptr; } bool FindNodeParentRecursive( const std::vector& nodes, std::string_view nodeId, const HierarchyNode*& parent) { for (const HierarchyNode& node : nodes) { for (const HierarchyNode& 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, HierarchyNode& 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; } HierarchyNode CloneNodeRecursive( const HierarchyNode& source, const std::function& allocateNodeId) { HierarchyNode duplicate = {}; duplicate.nodeId = allocateNodeId(); duplicate.label = source.label; duplicate.children.reserve(source.children.size()); for (const HierarchyNode& child : source.children) { duplicate.children.push_back(CloneNodeRecursive(child, allocateNodeId)); } return duplicate; } bool DuplicateNodeRecursive( std::vector& nodes, std::string_view nodeId, const std::function& allocateNodeId, std::string& duplicatedNodeId) { for (std::size_t index = 0u; index < nodes.size(); ++index) { if (nodes[index].nodeId == nodeId) { HierarchyNode duplicate = CloneNodeRecursive(nodes[index], allocateNodeId); duplicatedNodeId = duplicate.nodeId; nodes.insert( nodes.begin() + static_cast(index + 1u), std::move(duplicate)); return true; } if (DuplicateNodeRecursive( nodes[index].children, nodeId, allocateNodeId, duplicatedNodeId)) { return true; } } return false; } void BuildTreeItemsRecursive( const std::vector& nodes, std::uint32_t depth, const ::XCEngine::UI::UITextureHandle& icon, std::vector& items) { for (const HierarchyNode& 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 HierarchyModel HierarchyModel::BuildDefault() { HierarchyModel model = {}; model.m_roots = { HierarchyNode{ "main_camera", "Main Camera", {} }, HierarchyNode{ "directional_light", "Directional Light", {} }, HierarchyNode{ "player", "Player", { HierarchyNode{ "camera_pivot", "Camera Pivot", {} }, HierarchyNode{ "player_mesh", "Mesh", {} } } }, HierarchyNode{ "environment", "Environment", { HierarchyNode{ "ground", "Ground", {} }, HierarchyNode{ "props", "Props", { HierarchyNode{ "crate_01", "Crate_01", {} }, HierarchyNode{ "barrel_01", "Barrel_01", {} } } } } } }; model.m_nextGeneratedNodeId = 1u; return model; } bool HierarchyModel::Empty() const { return m_roots.empty(); } bool HierarchyModel::ContainsNode(std::string_view nodeId) const { return FindNode(nodeId) != nullptr; } const HierarchyNode* HierarchyModel::FindNode(std::string_view nodeId) const { return FindNodeRecursive(m_roots, nodeId); } HierarchyNode* HierarchyModel::FindNode(std::string_view nodeId) { return FindNodeRecursive(m_roots, nodeId); } std::optional HierarchyModel::GetParentId(std::string_view nodeId) const { const HierarchyNode* parent = nullptr; if (!FindNodeParentRecursive(m_roots, nodeId, parent) || parent == nullptr) { return std::nullopt; } return parent->nodeId; } bool HierarchyModel::RenameNode(std::string_view nodeId, std::string label) { HierarchyNode* node = FindNode(nodeId); if (node == nullptr || label.empty() || node->label == label) { return false; } node->label = std::move(label); return true; } std::string HierarchyModel::CreateChild( std::string_view parentId, std::string_view label) { HierarchyNode* parent = FindNode(parentId); if (parent == nullptr) { return {}; } HierarchyNode 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; } std::string HierarchyModel::DuplicateNode(std::string_view nodeId) { if (nodeId.empty() || !ContainsNode(nodeId)) { return {}; } std::string duplicatedNodeId = {}; const auto allocateNodeId = [this]() { return AllocateNodeId(); }; if (!DuplicateNodeRecursive(m_roots, nodeId, allocateNodeId, duplicatedNodeId)) { return {}; } return duplicatedNodeId; } bool HierarchyModel::DeleteNode(std::string_view nodeId) { if (nodeId.empty()) { return false; } HierarchyNode removed = {}; return ExtractNodeRecursive(m_roots, nodeId, removed); } bool HierarchyModel::CanReparent( std::string_view sourceNodeId, std::string_view targetParentId) const { if (sourceNodeId.empty()) { return false; } const HierarchyNode* source = FindNode(sourceNodeId); if (source == nullptr) { return false; } if (targetParentId.empty()) { return true; } const HierarchyNode* targetParent = FindNode(targetParentId); return CanAdopt(sourceNodeId, targetParent); } bool HierarchyModel::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; } HierarchyNode movedNode = {}; if (!ExtractNodeRecursive(m_roots, sourceNodeId, movedNode)) { return false; } HierarchyNode* targetParent = FindNode(targetParentId); if (targetParent == nullptr) { return false; } targetParent->children.push_back(std::move(movedNode)); return true; } bool HierarchyModel::MoveToRoot(std::string_view sourceNodeId) { if (sourceNodeId.empty() || !ContainsNode(sourceNodeId)) { return false; } if (!GetParentId(sourceNodeId).has_value()) { return false; } HierarchyNode movedNode = {}; if (!ExtractNodeRecursive(m_roots, sourceNodeId, movedNode)) { return false; } m_roots.push_back(std::move(movedNode)); return true; } std::vector HierarchyModel::BuildTreeItems( const ::XCEngine::UI::UITextureHandle& icon) const { std::vector items = {}; BuildTreeItemsRecursive(m_roots, 0u, icon, items); return items; } std::string HierarchyModel::AllocateNodeId() { std::ostringstream stream = {}; stream << "generated_node_" << m_nextGeneratedNodeId++; return stream.str(); } bool HierarchyModel::CanAdopt( std::string_view sourceNodeId, const HierarchyNode* targetParent) const { if (targetParent == nullptr || sourceNodeId == targetParent->nodeId) { return false; } const HierarchyNode* source = FindNode(sourceNodeId); if (source == nullptr) { return false; } return !ContainsDescendant(*source, targetParent->nodeId); } bool HierarchyModel::ContainsDescendant( const HierarchyNode& node, std::string_view candidateId) const { for (const HierarchyNode& child : node.children) { if (child.nodeId == candidateId || ContainsDescendant(child, candidateId)) { return true; } } return false; } } // namespace XCEngine::UI::Editor::App