#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 { void DrawHierarchyTreePrefix(const XCEngine::Editor::UI::TreeNodePrefixContext& context) { if (!context.drawList) { return; } const ImVec4 color = XCEngine::Editor::UI::NavigationTreePrefixColor(context.selected, context.hovered); const float size = 9.0f; const float centerX = context.min.x + (context.max.x - context.min.x) * 0.5f; const float centerY = context.min.y + (context.max.y - context.min.y) * 0.5f; const ImVec2 min(centerX - size * 0.5f, centerY - size * 0.5f); const ImVec2 max(centerX + size * 0.5f, centerY + size * 0.5f); context.drawList->AddRect(min, max, ImGui::GetColorU32(color), 1.5f); context.drawList->AddLine(ImVec2(min.x, centerY), ImVec2(max.x, centerY), ImGui::GetColorU32(color), 1.0f); } } // namespace 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() { ImGui::PushStyleColor(ImGuiCol_WindowBg, UI::HierarchyInspectorPanelBackgroundColor()); ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::HierarchyInspectorPanelBackgroundColor()); { UI::PanelWindowScope panel(m_name.c_str()); if (!panel.IsOpen()) { ImGui::PopStyleColor(2); return; } Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy); UI::PanelContentScope content("EntityList"); if (!content.IsOpen()) { ImGui::PopStyleColor(2); return; } auto& sceneManager = m_context->GetSceneManager(); auto rootEntities = sceneManager.GetRootEntities(); for (auto* gameObject : rootEntities) { RenderEntity(gameObject); } Actions::HandleHierarchyBackgroundPrimaryClick(*m_context, m_renameState); Actions::RequestHierarchyBackgroundContextPopup(m_backgroundContextMenu); Actions::DrawHierarchyEntityContextPopup(*m_context, m_itemContextMenu); Actions::DrawHierarchyBackgroundContextPopup(*m_context, m_backgroundContextMenu); Actions::DrawHierarchyRootDropTarget(*m_context); } ImGui::PopStyleColor(2); } void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject) { if (!gameObject) 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 { UI::TreeNodeDefinition nodeDefinition; nodeDefinition.options.selected = m_context->GetSelectionManager().IsSelected(gameObject->GetID()); nodeDefinition.options.leaf = gameObject->GetChildCount() == 0; const std::string persistenceKey = std::to_string(gameObject->GetUUID()); nodeDefinition.persistenceKey = persistenceKey; nodeDefinition.prefix.width = UI::NavigationTreePrefixWidth(); nodeDefinition.prefix.draw = DrawHierarchyTreePrefix; nodeDefinition.callbacks.onInteraction = [this, gameObject](const UI::TreeNodeResult& node) { if (node.clicked) { Actions::HandleHierarchySelectionClick(*m_context, gameObject->GetID(), ImGui::GetIO().KeyCtrl); } if (node.secondaryClicked) { Actions::HandleHierarchyItemContextRequest(*m_context, gameObject, m_itemContextMenu); } if (node.doubleClicked) { BeginRename(gameObject); } }; nodeDefinition.callbacks.onRenderExtras = [this, gameObject]() { Actions::BeginHierarchyEntityDrag(gameObject); Actions::AcceptHierarchyEntityDrop(*m_context, gameObject); }; const UI::TreeNodeResult node = UI::DrawTreeNode( &m_treeState, (void*)gameObject->GetUUID(), gameObject->GetName().c_str(), nodeDefinition); if (node.open) { for (size_t i = 0; i < gameObject->GetChildCount(); i++) { RenderEntity(gameObject->GetChild(i)); } UI::EndTreeNode(); } } 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(); } } }