# HierarchyPanel **命名空间**: `XCEngine::Editor` **类型**: `class` **源文件**: `editor/src/panels/HierarchyPanel.h` **描述**: 编辑器场景层级面板,负责绘制根实体树、处理选择与重命名事件,并把上下文菜单与拖放操作路由给 Action 层。 ## 概述 `HierarchyPanel` 是当前编辑器里最典型的“视图层面板”:它自己拥有 UI 状态,但不直接承担场景编辑命令实现。它的主要任务是把当前场景对象以树形方式呈现出来,并把用户交互转换成选择、重命名、右键菜单和拖放请求。 按当前实现,这个面板主要负责: - 订阅选择变化与外部重命名请求事件。 - 在工具栏中维护名称搜索输入。 - 读取 `ISceneManager` 提供的根实体列表。 - 基于 `UI::TreeView` 递归绘制场景对象树。 - 维护内联重命名状态。 - 将选择、右键菜单、拖放等行为委托给 `Actions::HierarchyActionRouter` 一侧的 helper。 这和商业引擎编辑器的分层很一致:Hierarchy 面板负责“看见和触发”,而不是直接“改数据和执行业务命令”。 ## 生命周期 - [Constructor](Constructor.md) 会把面板标题固定为 `"Hierarchy"`。 - [OnAttach](OnAttach.md) 会向 `EventBus` 订阅选择变化和重命名请求。 - [OnDetach](OnDetach.md) 会取消这些订阅并清零 handler id。 - [Render](Render.md) 会绘制工具栏、搜索框、实体树以及背景/实体上下文交互。 ## 当前实现行为 按 `editor/src/panels/HierarchyPanel.cpp` 的实现: - `OnAttach()` 会订阅: - `SelectionChangedEvent` - `EntityRenameRequestedEvent` - `OnDetach()` 会正确取消订阅并清零 handler id。 - `Render()` 会: - 应用 `HierarchyInspectorPanelBackgroundColor()`。 - 通过 `PanelWindowScope` / `PanelContentScope` 建立标准面板外壳。 - 调用 `Actions::ObserveFocusedActionRoute(...)` 将当前焦点路由标记为 `Hierarchy`。 - 在工具栏右侧绘制 `"Search"` 输入框,并用 `UI::SearchQuery` 构造过滤条件。 - 向 `ISceneManager` 读取根实体并逐个递归渲染。 - 在树绘制完成后处理背景点击、背景右键菜单、实体右键菜单和根级 drop target。 - 搜索命中为空时绘制 `"No matching entities"` empty state。 ## 节点绘制模型 当前的节点绘制已经不再是旧版本那种局部自定义树,而是完全建立在 `UI::TreeView` 之上: - 每个实体节点都会构造一个 `UI::TreeNodeDefinition`。 - `selected` 状态来自 `ISelectionManager::IsSelected(...)`。 - `leaf` 状态来自 `gameObject->GetChildCount() == 0`。 - `persistenceKey` 使用 `gameObject->GetUUID()` 转成字符串,保证展开状态在同一面板实例中稳定。 - `style` 使用 `UI::HierarchyTreeStyle()`。 - `prefix` 使用 `UI::NavigationTreePrefixWidth()` 和 `UI::DrawAssetIcon(..., AssetIconKind::GameObject)` 绘制对象图标。 - 搜索启用时不写入持久化 key,而是让有子节点的对象默认展开,方便显示命中的后代节点。 这意味着当前 Hierarchy 的视觉和交互行为已经被统一纳入 Editor UI 基础设施,而不是面板自己手工维护整套树渲染细节。 ## 搜索与可见性 当前 `HierarchyPanel` 已经支持名称搜索,但它属于轻量过滤而不是完整查询系统: - 工具栏搜索框写入 `m_searchBuffer`。 - `RenderEntity(...)` 会调用匿名 helper `HierarchyNodeMatchesSearch(...)`。 - 匹配规则不仅检查当前实体名,也会递归检查后代;只要子树中存在命中,该祖先节点就会保留显示。 - 搜索进行中时,节点展开状态不依赖常规 `m_treeState` 持久化键,而是让有子节点的对象默认展开。 ## 交互行为 当前节点交互通过 `TreeNodeCallbacks` 接入: - 单击左键:调用 `Actions::HandleHierarchySelectionClick(...)`,支持结合 `Ctrl` 执行多选。 - 右键:调用 `Actions::HandleHierarchyItemContextRequest(...)` 打开目标实体上下文菜单。 - 节点额外渲染阶段:调用 `Actions::BeginHierarchyEntityDrag(...)` 和 `Actions::AcceptHierarchyEntityDrop(...)` 接入拖放。 因此 `HierarchyPanel` 自己不直接执行“重排父子关系”之类操作,它只负责在正确的节点生命周期里提供 drop source / target 入口。 ## 重命名模型 `HierarchyPanel` 当前用 `UI::InlineTextEditState` 保存重命名状态。 当前行为是: - 外部若派发 `EntityRenameRequestedEvent`,面板会在 `OnRenameRequested(...)` 中找到实体并进入重命名。 - 编辑过程中: - `Enter` 提交。 - `Escape` 取消。 - 输入框失活时提交。 - `CommitRename()` 最终通过 `Actions::CommitEntityRename(...)` 把改名请求发往动作层。 这类实现很像商业编辑器中的“inline rename controller”:面板持有输入框状态,但真正的实体改名仍然走统一命令路径。 ## 生命周期与线程语义 - 该面板依赖有效的 `IEditorContext` 才能 attach、订阅事件并绘制。 - 事件订阅和 UI 绘制都应视为编辑器主线程行为。 - `m_treeState` 和 `m_renameState` 都属于面板实例私有状态,不会自动跨会话持久化。 ## 设计说明 当前实现的一个正确方向是:Hierarchy 面板只负责“把场景对象转成 Editor UI 语义”,而不是把所有业务都塞进面板里。 这样做的收益是: - 树视图风格、图标前缀、展开行为可以和 `ProjectPanel` 共享。 - 选择、重命名、上下文菜单、拖放都能被 `Actions` 层集中治理。 - 面板本身更容易继续重构,比如后续加入搜索、过滤、批量操作时,不必推翻底层树控件。 ## 当前限制 - 当前只有名称搜索,不支持按组件、Tag、Layer 或类型做更复杂过滤。 - 当前没有排序或大场景虚拟化能力。 - 展开状态只保存在 `UI::TreeViewState` 的内存态中,不会自动写入布局文件。 - 重命名输入框是替换式绘制,不是“树节点原位嵌入子控件”的更复杂实现。 - 目前只为每个节点提供统一的 `GameObject` 图标前缀,没有按组件类型做更细粒度图标区分。 ## 公开方法 | 方法 | 说明 | |------|------| | [Constructor](Constructor.md) | 创建名为 `"Hierarchy"` 的面板。 | | [OnAttach](OnAttach.md) | 订阅选择变化和重命名请求。 | | [OnDetach](OnDetach.md) | 注销事件订阅。 | | [Render](Render.md) | 绘制搜索栏、层级树和上下文交互。 | ## 相关文档 - [panels](../panels.md) - [TreeView](../../UI/TreeView/TreeView.md) - [BuiltInIcons](../../UI/BuiltInIcons/BuiltInIcons.md) - [HierarchyActionRouter](../../Actions/HierarchyActionRouter/HierarchyActionRouter.md) - [EditorEvents](../../Core/EditorEvents/EditorEvents.md) - [ISceneManager](../../Core/ISceneManager/ISceneManager.md) - [ISelectionManager](../../Core/ISelectionManager/ISelectionManager.md)