#include "HierarchyModel.h" #include "Scene/EditorSceneBridge.h" #include #include #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); } } HierarchyNode BuildSceneNodeRecursive( const ::XCEngine::Components::GameObject& gameObject) { HierarchyNode node = {}; node.nodeId = MakeEditorGameObjectItemId(gameObject.GetID()); node.label = gameObject.GetName().empty() ? std::string("GameObject") : gameObject.GetName(); node.children.reserve(gameObject.GetChildCount()); for (std::size_t childIndex = 0u; childIndex < gameObject.GetChildCount(); ++childIndex) { const auto* child = gameObject.GetChild(childIndex); if (child == nullptr) { continue; } node.children.push_back(BuildSceneNodeRecursive(*child)); } return node; } } // namespace HierarchyModel HierarchyModel::BuildFromScene( const ::XCEngine::Components::Scene* scene) { HierarchyModel model = {}; if (scene == nullptr) { return model; } const auto roots = scene->GetRootGameObjects(); model.m_roots.reserve(roots.size()); for (const auto* root : roots) { if (root == nullptr) { continue; } model.m_roots.push_back(BuildSceneNodeRecursive(*root)); } return model; } bool HierarchyModel::Empty() const { return m_roots.empty(); } bool HierarchyModel::HasSameTree(const HierarchyModel& other) const { return m_roots == other.m_roots; } 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