Unify panel search behavior and polish console UI

This commit is contained in:
2026-04-01 16:40:54 +08:00
parent e03f17146a
commit 4e8ad9a706
11 changed files with 998 additions and 172 deletions

View File

@@ -8,10 +8,37 @@
#include "Core/EditorEvents.h"
#include "Core/EventBus.h"
#include "UI/UI.h"
#include <string>
#include <imgui.h>
namespace {
constexpr float kHierarchyToolbarHeight = 26.0f;
constexpr float kHierarchyToolbarPaddingY = 3.0f;
constexpr float kHierarchySearchWidth = 220.0f;
bool HierarchyNodeMatchesSearch(
XCEngine::Components::GameObject* gameObject,
const XCEngine::Editor::UI::SearchQuery& searchQuery) {
if (!gameObject) {
return false;
}
if (searchQuery.Matches(gameObject->GetName().c_str())) {
return true;
}
for (size_t i = 0; i < gameObject->GetChildCount(); ++i) {
if (HierarchyNodeMatchesSearch(gameObject->GetChild(i), searchQuery)) {
return true;
}
}
return false;
}
void DrawHierarchyTreePrefix(const XCEngine::Editor::UI::TreeNodePrefixContext& context) {
if (!context.drawList) {
return;
@@ -157,6 +184,33 @@ void HierarchyPanel::Render() {
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy);
{
UI::PanelToolbarScope toolbar(
"HierarchyToolbar",
kHierarchyToolbarHeight,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
true,
ImVec2(UI::ToolbarPadding().x, kHierarchyToolbarPaddingY),
UI::ToolbarItemSpacing(),
UI::HierarchyInspectorPanelBackgroundColor());
if (toolbar.IsOpen()) {
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f));
if (ImGui::BeginTable("##HierarchyToolbarLayout", 2, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("##Spacer", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##Search", ImGuiTableColumnFlags_WidthFixed, kHierarchySearchWidth);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
UI::ToolbarSearchField("##HierarchySearch", "Search", m_searchBuffer, sizeof(m_searchBuffer));
ImGui::EndTable();
}
ImGui::PopStyleVar();
}
}
UI::PanelContentScope content("EntityList", UI::HierarchyPanelContentPadding());
if (!content.IsOpen()) {
ImGui::PopStyleColor(2);
@@ -165,10 +219,18 @@ void HierarchyPanel::Render() {
auto& sceneManager = m_context->GetSceneManager();
auto rootEntities = sceneManager.GetRootEntities();
const UI::SearchQuery searchQuery(m_searchBuffer);
size_t visibleEntityCount = 0;
UI::ResetTreeLayout();
for (auto* gameObject : rootEntities) {
RenderEntity(gameObject);
if (RenderEntity(gameObject, searchQuery)) {
++visibleEntityCount;
}
}
if (!searchQuery.Empty() && visibleEntityCount == 0) {
UI::DrawEmptyState("No matching entities", "Try a different search term");
}
Actions::DrawHierarchyBackgroundInteraction(*m_context, m_renameState);
@@ -179,7 +241,7 @@ void HierarchyPanel::Render() {
Actions::TraceHierarchyPopup("Hierarchy background popup opened via background surface");
s_backgroundContextOpen = true;
}
Actions::DrawHierarchyCreateActions(*m_context, nullptr);
Actions::DrawHierarchyContextActions(*m_context, nullptr, true);
UI::EndContextMenu();
} else if (s_backgroundContextOpen) {
Actions::TraceHierarchyPopup("Hierarchy background popup closed");
@@ -189,18 +251,32 @@ void HierarchyPanel::Render() {
ImGui::PopStyleColor(2);
}
void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject) {
if (!gameObject) return;
bool HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject, const UI::SearchQuery& searchQuery) {
if (!gameObject) {
return false;
}
const bool visibleInSearch = HierarchyNodeMatchesSearch(gameObject, searchQuery);
if (!visibleInSearch) {
return false;
}
const bool searching = !searchQuery.Empty();
ImGui::PushID(static_cast<int>(gameObject->GetID()));
UI::TreeNodeDefinition nodeDefinition =
BuildHierarchyNodeDefinition(*m_context, gameObject, m_itemContextMenu);
const std::string persistenceKey = std::to_string(gameObject->GetUUID());
nodeDefinition.persistenceKey = persistenceKey;
const std::string persistenceKey = searching ? std::string() : std::to_string(gameObject->GetUUID());
if (searching) {
nodeDefinition.options.defaultOpen = gameObject->GetChildCount() > 0;
nodeDefinition.persistenceKey = {};
} else {
nodeDefinition.persistenceKey = persistenceKey;
}
const bool editing = m_renameState.IsEditing(gameObject->GetID());
const UI::TreeNodeResult node = UI::DrawTreeNode(
&m_treeState,
searching ? nullptr : &m_treeState,
(void*)gameObject->GetUUID(),
editing ? "" : gameObject->GetName().c_str(),
nodeDefinition);
@@ -220,12 +296,13 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
if (node.open) {
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
RenderEntity(gameObject->GetChild(i));
RenderEntity(gameObject->GetChild(i), searchQuery);
}
UI::EndTreeNode();
}
ImGui::PopID();
return true;
}
void HierarchyPanel::BeginRename(::XCEngine::Components::GameObject* gameObject) {