#include "HierarchyPanel.h" #include "Core/IEditorContext.h" #include #include #include #include namespace XCEngine { namespace Editor { HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") { } HierarchyPanel::~HierarchyPanel() { } void HierarchyPanel::OnAttach() { auto* sceneManager = static_cast(m_context->GetSceneManager()); sceneManager->CreateDemoScene(); } void HierarchyPanel::Render() { ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None); RenderSearchBar(); ImGui::Separator(); HandleKeyboardShortcuts(); std::string filter = m_searchBuffer; ImGui::BeginChild("EntityList"); auto& sceneManager = *static_cast(m_context->GetSceneManager()); auto rootEntities = sceneManager.GetRootEntities(); SortEntities(const_cast&>(rootEntities)); for (auto* gameObject : rootEntities) { RenderEntity(gameObject, filter); } if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(0) && !ImGui::IsAnyItemHovered()) { if (!m_renaming) { m_context->GetSelectionManager().ClearSelection(); } } if (ImGui::BeginPopupContextWindow("HierarchyContextMenu", ImGuiPopupFlags_MouseButtonRight)) { RenderCreateMenu(nullptr); ImGui::EndPopup(); } ImGui::InvisibleButton("##DragTarget", ImVec2(-1, -1)); if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) { ::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data; if (sourceGameObject && sourceGameObject->GetParent() != nullptr) { auto& sceneManager = *static_cast(m_context->GetSceneManager()); sceneManager.MoveEntity(sourceGameObject->GetID(), 0); } } ImGui::EndDragDropTarget(); } ImGui::EndChild(); ImGui::End(); } void HierarchyPanel::RenderSearchBar() { ImGui::SetNextItemWidth(120); const char* sortLabels[] = { "Name", "Components", "Transform First" }; int currentSort = static_cast(m_sortMode); if (ImGui::Combo("##Sort", ¤tSort, sortLabels, 3)) { m_sortMode = static_cast(currentSort); } ImGui::SameLine(); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer)); } void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter) { if (!gameObject) return; if (!filter.empty() && !PassesFilter(gameObject, filter)) { return; } ImGui::PushID(static_cast(gameObject->GetID())); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth; if (gameObject->GetChildCount() == 0) { flags |= ImGuiTreeNodeFlags_Leaf; } if (m_context->GetSelectionManager().IsSelected(gameObject->GetID())) { flags |= ImGuiTreeNodeFlags_Selected; } if (m_renaming && m_renamingEntity == gameObject) { if (m_renameJustStarted) { ImGui::SetKeyboardFocusHere(); m_renameJustStarted = false; } ImGui::SetNextItemWidth(-1); if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { if (strlen(m_renameBuffer) > 0) { static_cast(m_context->GetSceneManager())->RenameEntity(gameObject->GetID(), m_renameBuffer); } m_renaming = false; m_renamingEntity = nullptr; } if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) { if (strlen(m_renameBuffer) > 0) { static_cast(m_context->GetSceneManager())->RenameEntity(gameObject->GetID(), m_renameBuffer); } m_renaming = false; m_renamingEntity = nullptr; } } else { bool isOpen = ImGui::TreeNodeEx((void*)gameObject->GetUUID(), flags, "%s", gameObject->GetName().c_str()); if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) { ImGuiIO& io = ImGui::GetIO(); if (io.KeyCtrl) { if (!m_context->GetSelectionManager().IsSelected(gameObject->GetID())) { m_context->GetSelectionManager().AddToSelection(gameObject->GetID()); } } else { m_context->GetSelectionManager().SetSelectedEntity(gameObject->GetID()); } } if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { m_renaming = true; m_renamingEntity = gameObject; strcpy_s(m_renameBuffer, gameObject->GetName().c_str()); m_renameJustStarted = true; } HandleDragDrop(gameObject); if (ImGui::BeginPopupContextItem("EntityContextMenu")) { RenderContextMenu(gameObject); ImGui::EndPopup(); } if (isOpen) { for (size_t i = 0; i < gameObject->GetChildCount(); i++) { RenderEntity(gameObject->GetChild(i), filter); } ImGui::TreePop(); } } ImGui::PopID(); } void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameObject) { auto& sceneManager = *static_cast(m_context->GetSceneManager()); auto& selectionManager = m_context->GetSelectionManager(); if (ImGui::BeginMenu("Create")) { RenderCreateMenu(gameObject); ImGui::EndMenu(); } if (gameObject != nullptr && ImGui::MenuItem("Create Child")) { auto* child = sceneManager.CreateEntity("GameObject", gameObject); selectionManager.SetSelectedEntity(child->GetID()); } ImGui::Separator(); if (gameObject != nullptr && gameObject->GetParent() != nullptr) { if (ImGui::MenuItem("Detach from Parent")) { gameObject->DetachFromParent(); } } if (ImGui::MenuItem("Rename", "F2")) { if (gameObject) { m_renaming = true; m_renamingEntity = gameObject; strcpy_s(m_renameBuffer, gameObject->GetName().c_str()); m_renameJustStarted = true; } } if (ImGui::MenuItem("Delete", "Delete")) { sceneManager.DeleteEntity(gameObject->GetID()); } ImGui::Separator(); if (ImGui::MenuItem("Copy", "Ctrl+C")) { sceneManager.CopyEntity(gameObject->GetID()); } if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) { sceneManager.PasteEntity(gameObject->GetID()); } if (ImGui::MenuItem("Duplicate", "Ctrl+D")) { uint64_t newId = sceneManager.DuplicateEntity(gameObject->GetID()); if (newId != 0) { } } } void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent) { auto& sceneManager = *static_cast(m_context->GetSceneManager()); auto& selectionManager = m_context->GetSelectionManager(); if (ImGui::MenuItem("Empty Object")) { auto* newEntity = sceneManager.CreateEntity("GameObject", parent); selectionManager.SetSelectedEntity(newEntity->GetID()); } ImGui::Separator(); if (ImGui::MenuItem("Camera")) { auto* newEntity = sceneManager.CreateEntity("Camera", parent); newEntity->AddComponent<::XCEngine::Components::TransformComponent>(); selectionManager.SetSelectedEntity(newEntity->GetID()); } if (ImGui::MenuItem("Light")) { auto* newEntity = sceneManager.CreateEntity("Light", parent); selectionManager.SetSelectedEntity(newEntity->GetID()); } ImGui::Separator(); if (ImGui::MenuItem("Cube")) { auto* newEntity = sceneManager.CreateEntity("Cube", parent); newEntity->AddComponent<::XCEngine::Components::TransformComponent>(); selectionManager.SetSelectedEntity(newEntity->GetID()); } if (ImGui::MenuItem("Sphere")) { auto* newEntity = sceneManager.CreateEntity("Sphere", parent); newEntity->AddComponent<::XCEngine::Components::TransformComponent>(); selectionManager.SetSelectedEntity(newEntity->GetID()); } if (ImGui::MenuItem("Plane")) { auto* newEntity = sceneManager.CreateEntity("Plane", parent); newEntity->AddComponent<::XCEngine::Components::TransformComponent>(); selectionManager.SetSelectedEntity(newEntity->GetID()); } } void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObject) { auto& sceneManager = *static_cast(m_context->GetSceneManager()); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { ImGui::SetDragDropPayload("ENTITY_PTR", &gameObject, sizeof(::XCEngine::Components::GameObject*)); ImGui::Text("%s", gameObject->GetName().c_str()); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) { ::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data; if (sourceGameObject != gameObject && sourceGameObject != nullptr) { bool isValidMove = true; ::XCEngine::Components::GameObject* checkParent = gameObject; while (checkParent != nullptr) { if (checkParent == sourceGameObject) { isValidMove = false; break; } checkParent = checkParent->GetParent(); } if (isValidMove) { auto* srcTransform = sourceGameObject->GetTransform(); Math::Vector3 worldPos = srcTransform->GetPosition(); Math::Quaternion worldRot = srcTransform->GetRotation(); Math::Vector3 worldScale = srcTransform->GetScale(); sceneManager.MoveEntity(sourceGameObject->GetID(), gameObject->GetID()); srcTransform->SetPosition(worldPos); srcTransform->SetRotation(worldRot); srcTransform->SetScale(worldScale); } } } ImGui::EndDragDropTarget(); } } void HierarchyPanel::HandleKeyboardShortcuts() { auto& sceneManager = *static_cast(m_context->GetSceneManager()); auto& selectionManager = m_context->GetSelectionManager(); ::XCEngine::Components::GameObject* selectedGameObject = sceneManager.GetEntity(selectionManager.GetSelectedEntity()); if (ImGui::IsWindowFocused()) { if (ImGui::IsKeyPressed(ImGuiKey_Delete)) { if (selectedGameObject != nullptr) { sceneManager.DeleteEntity(selectedGameObject->GetID()); } } if (ImGui::IsKeyPressed(ImGuiKey_F2)) { if (selectedGameObject != nullptr) { m_renaming = true; m_renamingEntity = selectedGameObject; strcpy_s(m_renameBuffer, selectedGameObject->GetName().c_str()); m_renameJustStarted = true; } } ImGuiIO& io = ImGui::GetIO(); if (io.KeyCtrl) { if (ImGui::IsKeyPressed(ImGuiKey_C)) { if (selectedGameObject != nullptr) { sceneManager.CopyEntity(selectedGameObject->GetID()); } } if (ImGui::IsKeyPressed(ImGuiKey_V)) { if (sceneManager.HasClipboardData()) { sceneManager.PasteEntity(selectedGameObject ? selectedGameObject->GetID() : 0); } } if (ImGui::IsKeyPressed(ImGuiKey_D)) { if (selectedGameObject != nullptr) { sceneManager.DuplicateEntity(selectedGameObject->GetID()); } } } } } bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) { if (!gameObject) return false; if (gameObject->GetName().find(filter) != std::string::npos) { return true; } for (size_t i = 0; i < gameObject->GetChildCount(); i++) { if (PassesFilter(gameObject->GetChild(i), filter)) { return true; } } return false; } void HierarchyPanel::SortEntities(std::vector<::XCEngine::Components::GameObject*>& entities) { switch (m_sortMode) { case SortMode::Name: std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) { return a->GetName() < b->GetName(); }); break; case SortMode::ComponentCount: std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) { return a->GetComponents<::XCEngine::Components::Component>().size() > b->GetComponents<::XCEngine::Components::Component>().size(); }); break; case SortMode::TransformFirst: std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) { bool aHasTransform = a->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr; bool bHasTransform = b->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr; if (aHasTransform != bHasTransform) { return aHasTransform; } return a->GetName() < b->GetName(); }); break; } } } }