#include "Actions/HierarchyActionRouter.h" #include "Actions/ActionRouting.h" #include "Commands/EntityCommands.h" #include "HierarchyPanel.h" #include "Core/IEditorContext.h" #include "Core/ISceneManager.h" #include "Core/ISelectionManager.h" #include "Core/EditorEvents.h" #include "Core/EventBus.h" #include "UI/UI.h" #include namespace XCEngine { namespace Editor { HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") { } void HierarchyPanel::OnAttach() { if (!m_context || m_selectionHandlerId || m_renameRequestHandlerId) { return; } m_selectionHandlerId = m_context->GetEventBus().Subscribe( [this](const SelectionChangedEvent& event) { OnSelectionChanged(event); } ); m_renameRequestHandlerId = m_context->GetEventBus().Subscribe( [this](const EntityRenameRequestedEvent& event) { OnRenameRequested(event); } ); } void HierarchyPanel::OnDetach() { if (!m_context) { return; } if (m_selectionHandlerId) { m_context->GetEventBus().Unsubscribe(m_selectionHandlerId); m_selectionHandlerId = 0; } if (m_renameRequestHandlerId) { m_context->GetEventBus().Unsubscribe(m_renameRequestHandlerId); m_renameRequestHandlerId = 0; } } void HierarchyPanel::OnSelectionChanged(const SelectionChangedEvent& event) { if (m_renameState.IsActive() && event.primarySelection != m_renameState.Item()) { CancelRename(); } } void HierarchyPanel::OnRenameRequested(const EntityRenameRequestedEvent& event) { if (!m_context || event.entityId == 0) { return; } if (auto* gameObject = m_context->GetSceneManager().GetEntity(event.entityId)) { BeginRename(gameObject); } } void HierarchyPanel::Render() { UI::PanelWindowScope panel(m_name.c_str()); if (!panel.IsOpen()) { return; } Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy); RenderSearchBar(); std::string filter = m_searchBuffer; UI::PanelContentScope content("EntityList"); if (!content.IsOpen()) { return; } auto& sceneManager = m_context->GetSceneManager(); auto rootEntities = sceneManager.GetRootEntities(); SortEntities(const_cast&>(rootEntities)); for (auto* gameObject : rootEntities) { RenderEntity(gameObject, filter); } Actions::HandleHierarchyBackgroundPrimaryClick(*m_context, m_renameState); Actions::DrawHierarchyBackgroundContextPopup(*m_context); Actions::DrawHierarchyRootDropTarget(*m_context); } void HierarchyPanel::RenderSearchBar() { UI::PanelToolbarScope toolbar("HierarchyToolbar", UI::StandardPanelToolbarHeight()); if (!toolbar.IsOpen()) { return; } const float buttonWidth = UI::HierarchyOverflowButtonWidth(); UI::ToolbarSearchField( "##Search", "Search hierarchy", m_searchBuffer, sizeof(m_searchBuffer), buttonWidth + UI::ToolbarSearchTrailingSpacing()); ImGui::SameLine(); if (UI::ToolbarButton("...", false, ImVec2(buttonWidth, 0.0f))) { Actions::RequestHierarchyOptionsPopup(m_optionsPopup); } Actions::DrawHierarchySortOptionsPopup( m_optionsPopup, m_sortMode, SortMode::Name, SortMode::ComponentCount, SortMode::TransformFirst, [this](SortMode mode) { m_sortMode = mode; }); } 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())); if (m_renameState.IsEditing(gameObject->GetID())) { if (m_renameState.ConsumeFocusRequest()) { ImGui::SetKeyboardFocusHere(); } ImGui::SetNextItemWidth(-1); if (ImGui::InputText( "##Rename", m_renameState.Buffer(), m_renameState.BufferSize(), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { CommitRename(); } if (ImGui::IsItemActive() && ImGui::IsKeyPressed(ImGuiKey_Escape)) { CancelRename(); } else if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) { CommitRename(); } } else { const UI::HierarchyNodeResult node = UI::DrawHierarchyNode( (void*)gameObject->GetUUID(), gameObject->GetName().c_str(), m_context->GetSelectionManager().IsSelected(gameObject->GetID()), gameObject->GetChildCount() == 0); if (node.clicked) { Actions::HandleHierarchySelectionClick(*m_context, gameObject->GetID(), ImGui::GetIO().KeyCtrl); } if (node.doubleClicked) { BeginRename(gameObject); } Actions::BeginHierarchyEntityDrag(gameObject); Actions::AcceptHierarchyEntityDrop(*m_context, gameObject); Actions::DrawHierarchyEntityContextPopup(*m_context, gameObject); if (node.open) { for (size_t i = 0; i < gameObject->GetChildCount(); i++) { RenderEntity(gameObject->GetChild(i), filter); } UI::EndHierarchyNode(); } } ImGui::PopID(); } void HierarchyPanel::BeginRename(::XCEngine::Components::GameObject* gameObject) { if (!gameObject) { CancelRename(); return; } m_renameState.Begin(gameObject->GetID(), gameObject->GetName().c_str()); } void HierarchyPanel::CommitRename() { if (!m_renameState.IsActive()) { return; } const uint64_t entityId = m_renameState.Item(); Actions::CommitEntityRename(*m_context, entityId, m_renameState.Buffer()); CancelRename(); } void HierarchyPanel::CancelRename() { m_renameState.Cancel(); } 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; } } } }