7.1 KiB
HierarchyActionRouter
命名空间: XCEngine::Editor::Actions
类型: header-helper
源文件: editor/src/Actions/HierarchyActionRouter.h
描述: Hierarchy 面板的交互路由助手,负责把层级树中的点击、重命名、拖放、上下文菜单和创建动作转成统一的 editor command / event 流。
概述
HierarchyActionRouter 不是独立的运行时对象,而是一组 inline helper。
它的意义在于把 Hierarchy 这种“高交互密度、低业务独立性”的逻辑,从面板渲染代码中拆出来。
当前它负责的事情主要有:
- 实体选择与背景点击清空选择。
- 发布重命名请求,并提交最终名称。
- 处理实体拖拽重挂接。
- 绘制背景菜单、实体菜单和排序选项弹窗。
- 调用 EntityCommands 创建、复制、粘贴、删除、重复和重挂接实体。
这类拆法很符合商业编辑器的做法。Hierarchy 面板应该主要关心“树怎么画”,而不是“菜单项怎么执行”。
前置知识
当前 Hierarchy 相关调用链大致是:
HierarchyPanel 采样 ImGui 交互 -> HierarchyActionRouter 做输入解释与路由 -> EntityCommands 修改场景 / 撤销栈 -> SelectionManager 与 EventBus 推进后续 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 逻辑。
重命名分两步:
RequestEntityRename(context, gameObject)发布EntityRenameRequestedEvent。CommitEntityRename(context, entityId, newName)校验实体存在且名称非空,再调用 EntityCommands。
这种拆法的意义在于:
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 实现:
CreateCameraEntity会附加CameraComponentCreateLightEntity会附加LightComponentCreatePrimitiveEntity会附加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” 模式。
测试与验证
当前与该文件直接相关的行为,至少有以下测试锚点:
HierarchyRouteExecutesCopyPasteDuplicateDeleteAndRenameHierarchyRouterRenameHelpersPublishAndCommitHierarchyItemContextRequestSelectsEntityAndStoresPopupTargetReparentPreserveWorldTransformKeepsWorldPose
这些测试位于 tests/Editor/test_action_routing.cpp,覆盖了 rename 请求、上下文目标记录、复制粘贴和保持世界变换的重挂接语义。
当前限制
- 暂不支持多实体拖拽、范围选择或框选。
- additive 选择不是完整 Ctrl-toggle / Shift-range 模型。
- 拖拽 payload 使用原始
GameObject*,应视为单进程、单帧 UI 交互协议,而不是可持久化数据格式。 - popup 诊断日志当前直接写常规日志通道,调试时有帮助,但正式产品里可能需要更细粒度的 trace 控制。