From 37a90b39e243f9fe9a3ce2a02ce5d1db7c6191ea Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 27 Mar 2026 22:05:05 +0800 Subject: [PATCH] Extract shared editor tree view --- editor/src/UI/StyleTokens.h | 6 +- editor/src/UI/TreeView.h | 68 +++++++++++++ editor/src/UI/UI.h | 1 + editor/src/UI/Widgets.h | 37 ------- editor/src/panels/HierarchyPanel.cpp | 146 +++++++-------------------- editor/src/panels/ProjectPanel.cpp | 31 ++---- 6 files changed, 120 insertions(+), 169 deletions(-) create mode 100644 editor/src/UI/TreeView.h diff --git a/editor/src/UI/StyleTokens.h b/editor/src/UI/StyleTokens.h index 859c0219..8e67e78b 100644 --- a/editor/src/UI/StyleTokens.h +++ b/editor/src/UI/StyleTokens.h @@ -162,7 +162,7 @@ inline ImVec2 ProjectBrowserPanePadding() { return ImVec2(10.0f, 8.0f); } -inline ImVec2 ProjectTreeNodeFramePadding() { +inline ImVec2 NavigationTreeNodeFramePadding() { return ImVec2(4.0f, 3.0f); } @@ -226,10 +226,6 @@ inline ImVec2 PopupWindowPadding() { return ImVec2(12.0f, 10.0f); } -inline ImVec2 HierarchyNodeFramePadding() { - return ImVec2(4.0f, 3.0f); -} - inline ImVec2 AssetTileSize() { return ImVec2(104.0f, 82.0f); } diff --git a/editor/src/UI/TreeView.h b/editor/src/UI/TreeView.h new file mode 100644 index 00000000..06b89838 --- /dev/null +++ b/editor/src/UI/TreeView.h @@ -0,0 +1,68 @@ +#pragma once + +#include "StyleTokens.h" + +#include + +namespace XCEngine { +namespace Editor { +namespace UI { + +struct TreeNodeOptions { + bool selected = false; + bool leaf = false; + bool defaultOpen = false; + bool openOnArrow = true; + bool openOnDoubleClick = true; + bool spanAvailWidth = true; + bool framePadding = true; +}; + +struct TreeNodeResult { + bool open = false; + bool clicked = false; + bool doubleClicked = false; +}; + +inline TreeNodeResult DrawTreeNode(const void* id, const char* label, const TreeNodeOptions& options = {}) { + ImGuiTreeNodeFlags flags = 0; + if (options.openOnArrow) { + flags |= ImGuiTreeNodeFlags_OpenOnArrow; + } + if (options.openOnDoubleClick) { + flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick; + } + if (options.spanAvailWidth) { + flags |= ImGuiTreeNodeFlags_SpanAvailWidth; + } + if (options.framePadding) { + flags |= ImGuiTreeNodeFlags_FramePadding; + } + if (options.leaf) { + flags |= ImGuiTreeNodeFlags_Leaf; + } + if (options.selected) { + flags |= ImGuiTreeNodeFlags_Selected; + } + if (options.defaultOpen) { + flags |= ImGuiTreeNodeFlags_DefaultOpen; + } + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, NavigationTreeNodeFramePadding()); + const bool open = ImGui::TreeNodeEx(id, flags, "%s", label); + ImGui::PopStyleVar(); + + return TreeNodeResult{ + open, + ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen(), + ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0) + }; +} + +inline void EndTreeNode() { + ImGui::TreePop(); +} + +} // namespace UI +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/UI/UI.h b/editor/src/UI/UI.h index e4bb563d..696af9dc 100644 --- a/editor/src/UI/UI.h +++ b/editor/src/UI/UI.h @@ -15,6 +15,7 @@ #include "ScalarControls.h" #include "SceneStatusWidget.h" #include "StyleTokens.h" +#include "TreeView.h" #include "VectorControls.h" #include "Widgets.h" diff --git a/editor/src/UI/Widgets.h b/editor/src/UI/Widgets.h index 8b409fd4..d6df4d49 100644 --- a/editor/src/UI/Widgets.h +++ b/editor/src/UI/Widgets.h @@ -14,12 +14,6 @@ struct ComponentSectionResult { bool open = false; }; -struct HierarchyNodeResult { - bool open = false; - bool clicked = false; - bool doubleClicked = false; -}; - struct AssetTileResult { bool clicked = false; bool contextRequested = false; @@ -183,37 +177,6 @@ inline void DrawToolbarBreadcrumbs( ImGui::PopStyleColor(2); } -inline HierarchyNodeResult DrawHierarchyNode( - const void* id, - const char* label, - bool selected, - bool leaf) { - ImGuiTreeNodeFlags flags = - ImGuiTreeNodeFlags_OpenOnArrow | - ImGuiTreeNodeFlags_SpanAvailWidth | - ImGuiTreeNodeFlags_FramePadding; - if (leaf) { - flags |= ImGuiTreeNodeFlags_Leaf; - } - if (selected) { - flags |= ImGuiTreeNodeFlags_Selected; - } - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, HierarchyNodeFramePadding()); - const bool open = ImGui::TreeNodeEx(id, flags, "%s", label); - ImGui::PopStyleVar(); - - return HierarchyNodeResult{ - open, - ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen(), - ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0) - }; -} - -inline void EndHierarchyNode() { - ImGui::TreePop(); -} - template inline AssetTileResult DrawAssetTile( const char* label, diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index b5a4f6dc..2c4084f5 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -65,71 +65,41 @@ void HierarchyPanel::OnRenameRequested(const EntityRenameRequestedEvent& event) } void HierarchyPanel::Render() { - UI::PanelWindowScope panel(m_name.c_str()); - if (!panel.IsOpen()) { - return; + 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::DrawHierarchyBackgroundContextPopup(*m_context); + Actions::DrawHierarchyRootDropTarget(*m_context); } - - 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); + ImGui::PopStyleColor(2); } -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) { +void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject) { if (!gameObject) return; - - if (!filter.empty() && !PassesFilter(gameObject, filter)) { - return; - } - + ImGui::PushID(static_cast(gameObject->GetID())); if (m_renameState.IsEditing(gameObject->GetID())) { @@ -152,11 +122,14 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject CommitRename(); } } else { - const UI::HierarchyNodeResult node = UI::DrawHierarchyNode( + UI::TreeNodeOptions nodeOptions; + nodeOptions.selected = m_context->GetSelectionManager().IsSelected(gameObject->GetID()); + nodeOptions.leaf = gameObject->GetChildCount() == 0; + + const UI::TreeNodeResult node = UI::DrawTreeNode( (void*)gameObject->GetUUID(), gameObject->GetName().c_str(), - m_context->GetSelectionManager().IsSelected(gameObject->GetID()), - gameObject->GetChildCount() == 0); + nodeOptions); if (node.clicked) { Actions::HandleHierarchySelectionClick(*m_context, gameObject->GetID(), ImGui::GetIO().KeyCtrl); @@ -173,9 +146,9 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject if (node.open) { for (size_t i = 0; i < gameObject->GetChildCount(); i++) { - RenderEntity(gameObject->GetChild(i), filter); + RenderEntity(gameObject->GetChild(i)); } - UI::EndHierarchyNode(); + UI::EndTreeNode(); } } @@ -205,46 +178,5 @@ 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; - } -} - } } diff --git a/editor/src/panels/ProjectPanel.cpp b/editor/src/panels/ProjectPanel.cpp index c21882b5..972f6f95 100644 --- a/editor/src/panels/ProjectPanel.cpp +++ b/editor/src/panels/ProjectPanel.cpp @@ -118,37 +118,28 @@ void ProjectPanel::RenderFolderTreeNode( } } - ImGuiTreeNodeFlags flags = - ImGuiTreeNodeFlags_OpenOnArrow | - ImGuiTreeNodeFlags_OpenOnDoubleClick | - ImGuiTreeNodeFlags_SpanAvailWidth | - ImGuiTreeNodeFlags_FramePadding; - if (!hasChildFolders) { - flags |= ImGuiTreeNodeFlags_Leaf; - } - if (folder->fullPath == currentFolderPath) { - flags |= ImGuiTreeNodeFlags_Selected; - } - if (IsCurrentTreeBranch(currentFolderPath, folder->fullPath)) { - flags |= ImGuiTreeNodeFlags_DefaultOpen; - } + UI::TreeNodeOptions nodeOptions; + nodeOptions.selected = folder->fullPath == currentFolderPath; + nodeOptions.leaf = !hasChildFolders; + nodeOptions.defaultOpen = IsCurrentTreeBranch(currentFolderPath, folder->fullPath); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, UI::ProjectTreeNodeFramePadding()); - const bool open = ImGui::TreeNodeEx((void*)folder.get(), flags, "%s", folder->name.c_str()); - ImGui::PopStyleVar(); + const UI::TreeNodeResult node = UI::DrawTreeNode( + (void*)folder.get(), + folder->name.c_str(), + nodeOptions); - if (ImGui::IsItemClicked()) { + if (node.clicked) { manager.NavigateToFolder(folder); } - if (open) { + if (node.open) { for (const auto& child : folder->children) { if (!child || !child->isFolder) { continue; } RenderFolderTreeNode(manager, child, currentFolderPath); } - ImGui::TreePop(); + UI::EndTreeNode(); } }