# HierarchyActionRouter **命名空间**: `XCEngine::Editor::Actions` **类型**: `header-helper` **源文件**: `editor/src/Actions/HierarchyActionRouter.h` **描述**: Hierarchy 面板的交互路由助手,负责把层级树中的点击、重命名、拖放、上下文菜单和创建动作转成统一的 editor command / event 流。 ## 概述 `HierarchyActionRouter` 不是独立的运行时对象,而是一组 `inline` helper。 它的意义在于把 Hierarchy 这种“高交互密度、低业务独立性”的逻辑,从面板渲染代码中拆出来。 当前它负责的事情主要有: - 实体选择与背景点击清空选择。 - 发布重命名请求,并提交最终名称。 - 处理实体拖拽重挂接。 - 绘制背景菜单、实体菜单和排序选项弹窗。 - 调用 [EntityCommands](../../Commands/EntityCommands/EntityCommands.md) 创建、复制、粘贴、删除、重复和重挂接实体。 这类拆法很符合商业编辑器的做法。Hierarchy 面板应该主要关心“树怎么画”,而不是“菜单项怎么执行”。 ## 前置知识 当前 Hierarchy 相关调用链大致是: `HierarchyPanel` 采样 ImGui 交互 -> `HierarchyActionRouter` 做输入解释与路由 -> [EntityCommands](../../Commands/EntityCommands/EntityCommands.md) 修改场景 / 撤销栈 -> [SelectionManager](../../Core/ISelectionManager/ISelectionManager.md) 与 [EventBus](../../Core/EventBus/EventBus.md) 推进后续 UI 同步。 这条链路里: - 面板负责显示。 - router 负责交互语义。 - command 层负责真正修改场景。 这就是典型的“展示层 / 交互层 / 命令层”分离。 ## 主要职责 | 职责 | 代表入口 | 说明 | |------|------|------| | 选择路由 | `HandleHierarchySelectionClick` | 单击实体时更新当前选择。 | | 重命名请求 | `RequestEntityRename` / `CommitEntityRename` | 通过事件请求进入重命名,再调用命令层提交名称。 | | 背景交互 | `HandleHierarchyBackgroundPrimaryClick` / `DrawHierarchyBackgroundInteraction` | 清空选择、接收拖放到根节点。 | | 右键菜单 | `RequestHierarchyBackgroundContextPopup` / `DrawHierarchyContextActions` | 组装创建、删除、复制、粘贴等菜单项。 | | 创建实体 | `DrawHierarchyCreateActions` | 调用命令层创建空对象、相机、灯光和内置 primitive。 | | 拖拽重挂接 | `BeginHierarchyEntityDrag` / `AcceptHierarchyEntityDrop` | 在实体之间或根节点上完成重挂接。 | | 排序菜单 | `DrawHierarchySortOptionsPopup` | 绘制排序选项 popup。 | ## 当前实现行为 ### 选择与重命名 - `HandleHierarchySelectionClick` 当前支持两种模式: - 普通点击: `SetSelectedEntity(entityId)` - additive 点击: 仅在未选中时 `AddToSelection(entityId)` - 当前 additive 语义更接近“增量加入”,不是完整的 Ctrl-toggle 逻辑。 重命名分两步: 1. `RequestEntityRename(context, gameObject)` 发布 `EntityRenameRequestedEvent`。 2. `CommitEntityRename(context, entityId, newName)` 校验实体存在且名称非空,再调用 [EntityCommands](../../Commands/EntityCommands/EntityCommands.md)。 这种拆法的意义在于: Hierarchy 行内编辑、菜单栏 Rename、快捷键 Rename 都可以走同一条“先请求、后提交”的链路。 ### 背景交互与 action route `HandleHierarchyBackgroundPrimaryClick` 和 `DrawHierarchyBackgroundInteraction` 会在以下条件下清空选择: - 鼠标在窗口内。 - 当前没有悬停在具体 item 上。 - 当前不处于 rename 编辑状态。 `DrawHierarchyBackgroundInteraction` 还会创建一个不可见交互面,并允许把实体直接拖回根节点。这一点很像 Unity 的 Hierarchy 根级拖放体验。 ### 拖拽载荷与重挂接 当前拖拽载荷类型固定为: - `"ENTITY_PTR"` `BeginHierarchyEntityDrag` 会把 `GameObject*` 直接写入 ImGui drag payload。 `AcceptHierarchyEntityDrop` / `AcceptHierarchyEntityDropToRoot` 在 drop 时读取这个指针,并调用: - `Commands::CanReparentEntity(...)` - `Commands::ReparentEntityPreserveWorldTransform(...)` 所以当前 router 的重挂接语义不是“只改 parent 指针”,而是“尽量保持世界空间位置、旋转和缩放不跳变”。 ### 上下文菜单与创建动作 `DrawHierarchyContextActions(...)` 当前统一拼装了: - Detach - Rename - Delete - Copy - Paste - Duplicate - Create 子菜单 其中 `DrawHierarchyCreateActions(...)` 已经不只是创建“命名好的空物体”。 按当前 [EntityCommands](../../Commands/EntityCommands/EntityCommands.md) 实现: - `CreateCameraEntity` 会附加 `CameraComponent` - `CreateLightEntity` 会附加 `LightComponent` - `CreatePrimitiveEntity` 会附加 `MeshFilterComponent` 与 `MeshRendererComponent` - primitive 还会绑定内置 mesh 路径和默认 primitive material 这点和旧文档结论不同,当前实现已经更接近商业引擎里“Create 3D Object” 的真实行为。 ### Popup 诊断日志 文件中还有一组 `TraceHierarchyPopup(...)` 调试日志 helper。 它们会在背景右键菜单、实体右键菜单和 `Create` 子菜单的打开 / 关闭过程中写日志,便于定位 ImGui popup 状态错乱问题。 ## 设计说明 Hierarchy 交互往往是编辑器里最容易失控的一块,因为它同时牵涉: - 选择 - 多级树展开 - 内联重命名 - 拖放 - 上下文菜单 - 创建 / 删除 / 复制 / 粘贴 如果这些逻辑全部堆在 `HierarchyPanel.cpp`,后续一加多选、过滤、排序或者 prefab 支持,文件会很快失去可维护性。 当前拆成 router 的好处很实际: - `HierarchyPanel` 仍然能保持“绘树”为主。 - 菜单、拖放、重命名可以复用同一套命令层。 - 快捷键、菜单栏、右键菜单都能共用行为定义。 这就是商业编辑器常见的“thin panel, rich routing, centralized commands” 模式。 ## 测试与验证 当前与该文件直接相关的行为,至少有以下测试锚点: - `HierarchyRouteExecutesCopyPasteDuplicateDeleteAndRename` - `HierarchyRouterRenameHelpersPublishAndCommit` - `HierarchyItemContextRequestSelectsEntityAndStoresPopupTarget` - `ReparentPreserveWorldTransformKeepsWorldPose` 这些测试位于 `tests/Editor/test_action_routing.cpp`,覆盖了 rename 请求、上下文目标记录、复制粘贴和保持世界变换的重挂接语义。 ## 当前限制 - 暂不支持多实体拖拽、范围选择或框选。 - additive 选择不是完整 Ctrl-toggle / Shift-range 模型。 - 拖拽 payload 使用原始 `GameObject*`,应视为单进程、单帧 UI 交互协议,而不是可持久化数据格式。 - popup 诊断日志当前直接写常规日志通道,调试时有帮助,但正式产品里可能需要更细粒度的 trace 控制。 ## 相关文档 - [HierarchyPanel](../../panels/HierarchyPanel/HierarchyPanel.md) - [EntityCommands](../../Commands/EntityCommands/EntityCommands.md) - [EditorEvents](../../Core/EditorEvents/EditorEvents.md) - [EventBus](../../Core/EventBus/EventBus.md) - [EditorActionRoute](../../Core/EditorActionRoute/EditorActionRoute.md) - [Widgets](../../UI/Widgets/Widgets.md)