20 KiB
NewEditor Editor Layer / App Boundary Closure Plan
Date: 2026-04-22
1. Objective
这份计划的目标不是泛泛讨论“分层好不好看”,而是把 new_editor 当前最明显的边界泄漏收口掉:
engine中的 UI 基础层 / 运行时层继续只负责通用 UI 能力new_editor/include+new_editor/src中的XCUIEditorLib明确成为“编辑器 UI 模块层”new_editor/app只保留编辑器应用层应当持有的业务、平台、渲染宿主和应用装配- 把当前明显散落在
app、但本质属于“通用 editor UI 机制”的内容,抽回XCUIEditorLib
本轮计划只聚焦已经确认影响较大的三类问题:
- hosted panel 运行时机制散落在
app - detached/tool-window 策略散落在多个
app模块 - panel lifecycle / focus / input gate 已经在 editor 层有雏形,但
app没有接上,反而重复实现
2. Confirmed Findings
2.1 Dependency direction is mostly correct
当前 XCUIEditorLib 没有实际反向 include app,说明“目录依赖方向”本身没有彻底坏掉。
这点必须保留:
engine不依赖new_editorXCUIEditorLib不依赖new_editor/appapp依赖XCUIEditorLib和 host/platform/rendering ports
所以本次工作不是纠正反向依赖,而是纠正“职责泄漏到 app”。
2.2 Hosted panel runtime is still implemented in app
以下职责目前散落在 app:
- 具体 panel 实例持有与总调度:
new_editor/app/Composition/EditorShellRuntime.h
- 面板更新分发、输入过滤、焦点派发:
new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp
- 面板绘制拼装:
new_editor/app/Composition/EditorShellDrawComposer.cpp
- 面板事件回流到 session/status:
new_editor/app/Composition/WorkspaceEventSync.cpp
问题不在于这些代码位于 app,而在于它们实现的内容已经不是“应用业务”,而是“通用 editor-hosted-panel 运行时机制”。
2.3 Detached window policy is duplicated in multiple app modules
当前至少有三处各自推导 detached/tool-window 规则:
- shell 交互期的 metrics 调整:
new_editor/app/Composition/EditorShellInteractionEngine.cpp
- 窗口 chrome 的 tool-window 判定和最小尺寸:
new_editor/app/Platform/Win32/EditorWindowChromeController.cpp
- 打开 detached panel 时的 preferred size 解析:
new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp
但这些规则依赖的源数据其实都在:
UIEditorPanelRegistryUIEditorWorkspaceControllerUIEditorWindowWorkspaceController
也就是说,策略消费被拆散了,策略数据却本来就在 editor 层。
2.4 Panel lifecycle abstraction exists in editor layer but is not used
XCUIEditorLib 已经有:
new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.hnew_editor/src/Panels/UIEditorPanelHostLifecycle.cpp
但 app 里仍然自己维护:
PanelInputContextallowInteractionfocusGainedfocusLost- mounted panel 扫描
对应文件包括:
new_editor/app/Composition/EditorShellHostedPanelCoordinator.cppnew_editor/app/Features/PanelInputContext.hnew_editor/app/Features/Hierarchy/HierarchyPanel.cppnew_editor/app/Features/Project/ProjectPanel.cppnew_editor/app/Features/Inspector/InspectorPanel.cppnew_editor/app/Features/ColorPicker/ColorPickerPanel.cppnew_editor/app/Features/Console/ConsolePanel.cpp
这会导致同一套 host lifecycle 语义出现两份实现,并最终行为漂移。
2.5 Resulting symptom: EditorShellRuntime is carrying too much generic mechanism
EditorShellRuntime 现在同时承担:
- shell interaction orchestration
- hosted panel runtime orchestration
- viewport host service orchestration
- app feature update routing
- draw packet assembly
- workspace event tracing and status backflow
其中前半部分有相当一块本应属于 editor 模块层,而不是 app 层。
3. Boundary Rules
3.1 What must stay in engine UI base/runtime layers
engine 中的 UI 层继续只负责:
UIInputEvent、UIRect、UIDrawData等基础类型- 通用 UI 输入、焦点、样式、文字、widget model
- 与编辑器无关的 runtime UI 能力
engine 不负责:
- editor panel host lifecycle
- docked editor shell 策略
- detached editor window 策略
- editor hosted panel 调度
3.2 What must belong to XCUIEditorLib
XCUIEditorLib 应当负责所有“与具体业务面板无关、但与 editor shell / workspace / panel host 强相关”的通用机制,包括:
- panel registry 元数据及校验
- workspace / dock host / viewport shell 的通用交互
- hosted panel mount/layout/lifecycle 的统一推导
- hosted panel 输入过滤与 input-owner 对接
- detached/tool-window 策略与尺寸解析
- 供应用层消费的 panel dispatch frame / request / result
这里的核心判断标准是:
只要某段逻辑不关心“这是 Hierarchy 还是 Project”,而只关心“这是一个 externally hosted panel”,它就应该在 XCUIEditorLib。
3.3 What must stay in app
new_editor/app 只保留以下内容:
- 具体业务 panel 本身:
HierarchyPanelProjectPanelInspectorPanelConsolePanelColorPickerPanelSceneViewportFeature
- 业务 runtime:
EditorSceneRuntimeEditorProjectRuntimeEditorSession
- 应用装配:
- 菜单/命令/toolbar 的应用级定义
- panel registry 的应用级实例构建
- 平台宿主:
- Win32 window lifecycle
- D3D12 host renderer
- capture/system interaction
3.4 What must not be moved into XCUIEditorLib
本次收口不是把所有东西都塞进 editor 库。
以下内容明确不下沉:
- Hierarchy / Project / Inspector 的业务数据模型
- Scene runtime / project runtime / selection service
- Win32 chrome 和窗口消息循环
- D3D12 host 层资源与 swapchain
- app 级菜单、命令、panel 组合方案
3.5 Placement rule for new modules
当前 new_editor/src/App 实际没有形成可用公共层,include/XCEditor/App 也不存在。
因此新增 editor-layer 模块不应新建一个伪 XCEditor/App。
新增通用能力应优先落到:
XCEditor/PanelsXCEditor/ShellXCEditor/Workspace
只在确实与上述三个域都不匹配时,才考虑新增域。
4. Target End State
4.1 Target runtime flow
目标运行流应当变成:
app构造应用级EditorShellAsset、业务 panel、业务 runtimeXCUIEditorLib负责:- shell/workspace 交互
- content host mount frame
- panel host lifecycle frame
- hosted panel dispatch frame
- detached window policy resolution
app只做两件事:- 把 dispatch frame 按
panelId路由给具体 panel - 把具体 panel 的业务事件回流到 app session/runtime
- 把 dispatch frame 按
这样 app 保留“内容”,XCUIEditorLib 收回“机制”。
4.2 Target module responsibilities
Editor layer should own
建议新增或重组出以下 editor-layer 模块:
UIEditorHostedPanelDispatch- 统一生成每个 hosted panel 的 update request / input slice / lifecycle view
UIEditorDetachedWindowPolicy- 统一解析 tool-window、preferred size、minimum size、detached title behavior
UIEditorPanelHostLifecycle- 从“已有但未接入”变为“活跃主路径能力”
App layer should own
app 最终只保留一个轻量路由层,例如:
EditorHostedPanelRouter- 根据
panelId找到具体 panel 对象 - 调用
Update(...) - 调用
Append(...) - 收集业务事件
- 根据
如果不需要单独新建这个类,也至少要把现有 EditorShellHostedPanelCoordinator 收缩到这个角色。
4.3 Target data model direction
本次收口后,app 不应再自己定义一套 panel host 语义。
目标是:
- 删除或废弃
PanelInputContext - 用 editor-layer 的统一 dispatch entry / lifecycle frame 取代
- hosted panel 的 mounted / visible / active / focused / focus gained / focus lost 只存在一套来源
5. Refactor Strategy
Phase A. Freeze boundary and add structural guardrails
目标:
在开始搬迁逻辑前,先把边界约束写清楚,避免中途再次把通用 editor 机制塞回 app。
要做的事:
- 在文档中明确本计划的边界规则
- 审查
new_editor/CMakeLists.txt中XCUIEditorLib与app的 include 使用现状 - 增加最小结构验证规则:
new_editor/src/new_editor/include不反向 includeappapp不再新增 editor-generic duplicated helper
- 为后续 grep 验证准备检查项
验收标准:
- 本计划作为边界基线落地
- 后续每一阶段都能用 grep 验证“重复逻辑是否还活着”
Phase B. Make panel host lifecycle a live path
目标:
先把已有的 UIEditorPanelHostLifecycle 从死代码变成活代码。
要做的事:
- 在 editor-layer 主路径中接入
UpdateUIEditorPanelHostLifecycle(...) - 明确
focusedPanelId的统一来源 - 让 lifecycle frame 成为 hosted panel dispatch 的基础输入
- 禁止
app再自行推导:focusGainedfocusLostactivevisibleattached
重点文件范围:
new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.hnew_editor/src/Panels/UIEditorPanelHostLifecycle.cppnew_editor/include/XCEditor/Shell/UIEditorShellInteraction.hnew_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.hnew_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp
验收标准:
UpdateUIEditorPanelHostLifecycle(...)进入实际 frame pathapp不再自己计算 focus enter/leave 语义
Phase C. Introduce hosted panel dispatch into XCUIEditorLib
目标:
把 hosted panel 的通用运行时机制从 app 抽回 editor 层,但不把具体业务 panel 也硬搬进库里。
建议新增能力:
UIEditorHostedPanelDispatchEntryUIEditorHostedPanelDispatchFrameUIEditorHostedPanelDispatchRequest
每个 entry 至少应包含:
panelId- mounted/bounds 信息
- lifecycle state 或 lifecycle frame 视图
- 该 panel 可见时应收到的过滤后输入
- 交互是否允许
- 当前 input owner / capture owner 相关视图
要做的事:
- 把
EditorShellHostedPanelCoordinator.cpp中的通用逻辑下沉到XCUIEditorLib - 统一处理 hosted-content 与 shell 对 pointer stream 的让渡规则
- 统一处理 panel 的 mounted/bounds 查找
- 统一处理 focus/lifecycle/input gate 计算
- app 只保留:
panelId -> concrete panel路由- 业务对象依赖注入
- 业务事件回流
重点文件范围:
- 新增:
new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.hnew_editor/src/Panels/UIEditorHostedPanelDispatch.cpp
- 调整:
new_editor/app/Composition/EditorShellHostedPanelCoordinator.hnew_editor/app/Composition/EditorShellHostedPanelCoordinator.cppnew_editor/app/Features/PanelInputContext.hnew_editor/app/Features/Hierarchy/HierarchyPanel.*new_editor/app/Features/Project/ProjectPanel.*new_editor/app/Features/Inspector/InspectorPanel.*new_editor/app/Features/ColorPicker/ColorPickerPanel.*new_editor/app/Features/Console/ConsolePanel.*
验收标准:
PanelInputContext被删除或降级为纯 app-specific 数据,不再承载通用 host 语义FindMountedHierarchyPanel、FindMountedProjectPanel等重复 mounted 扫描逻辑被消除或大幅收缩EditorShellHostedPanelCoordinator只剩业务路由和依赖注入
Phase D. Centralize detached window / tool-window policy
目标:
把 detached/tool-window 相关策略从多个 app 模块收成一套 editor-layer 能力。
建议新增能力:
UIEditorDetachedWindowPolicyUIEditorDetachedWindowPolicyResultResolveUIEditorDetachedWindowPolicy(...)
这套策略至少统一回答:
- 当前 workspace 是否表现为 tool-window
- 当前 detached window 的 preferred/minimum 尺寸是多少
- 是否应使用 detached title-bar tab strip
- 单面板 root tab stack 的判定逻辑
要做的事:
- 把
ResolveSingleVisibleToolWindowPanelDescriptor(...)和同类逻辑下沉 - 把
ResolveMinimumOuterSize(...)、ResolveDetachedPanelPreferredSize(...)统一消费同一套 API - 让 shell interaction metrics、Win32 chrome、detached-panel open request 共用同一套策略结果
重点文件范围:
- 新增:
new_editor/include/XCEditor/Shell/UIEditorDetachedWindowPolicy.hnew_editor/src/Shell/UIEditorDetachedWindowPolicy.cpp
- 调整:
new_editor/app/Composition/EditorShellInteractionEngine.cppnew_editor/app/Platform/Win32/EditorWindowChromeController.cppnew_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp
验收标准:
toolWindow、preferred size、minimum size 的判定只有一套主实现- 上述三个 app 文件只消费统一 helper,不再保留平行推导函数
Phase E. Reduce EditorShellRuntime to app orchestration
目标:
在完成通用机制下沉之后,让 EditorShellRuntime 从“半个 editor framework”收缩为“应用层编排器”。
要做的事:
- 移除
EditorShellRuntime中与 hosted panel lifecycle / dispatch / policy 直接相关的通用逻辑 - 保留:
- app feature 依赖注入
- scene/project runtime 同步
- status / selection / command route 回流
- viewport host 服务调用
- 视情况把
EditorShellDrawComposer收缩为纯 draw packet 组织器 - 视情况把
WorkspaceEventSync收缩为纯 app 业务事件同步器
重点文件范围:
new_editor/app/Composition/EditorShellRuntime.hnew_editor/app/Composition/EditorShellRuntime.cppnew_editor/app/Composition/EditorShellDrawComposer.hnew_editor/app/Composition/EditorShellDrawComposer.cppnew_editor/app/Composition/WorkspaceEventSync.hnew_editor/app/Composition/WorkspaceEventSync.cpp
验收标准:
EditorShellRuntime不再承载 editor-generic host policy- 它只负责应用装配和业务流程编排
Phase F. Verification and regression hardening
目标:
确保这次收口不是“结构更好看了,但行为退化了”。
要做的事:
- 编译
XCUIEditorLib、XCUIEditorAppCore、XCUIEditorApp - 验证 primary window、detached window、tool-window 三种路径
- 验证以下交互没有回归:
- hierarchy 选择/拖拽/rename
- project tree/grid 交互
- inspector 输入
- color picker detached open
- scene viewport capture
- 增加 grep 结构检查:
PanelInputContext不再作为通用 host 语义入口ResolveSingleVisibleToolWindowPanelDescriptor等重复 helper 已移除UpdateUIEditorPanelHostLifecycle已有活跃调用
6. File-Level Scope
6.1 Existing editor-layer files to extend
new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.hnew_editor/src/Panels/UIEditorPanelHostLifecycle.cppnew_editor/include/XCEditor/Panels/UIEditorPanelContentHost.hnew_editor/src/Panels/UIEditorPanelContentHost.cppnew_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.hnew_editor/src/Workspace/UIEditorWorkspaceInteraction.cppnew_editor/include/XCEditor/Shell/UIEditorShellInteraction.h
6.2 New editor-layer files expected
new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.hnew_editor/src/Panels/UIEditorHostedPanelDispatch.cppnew_editor/include/XCEditor/Shell/UIEditorDetachedWindowPolicy.hnew_editor/src/Shell/UIEditorDetachedWindowPolicy.cpp
如果实际实现中发现这些文件更适合放到 Workspace/,可以调整,但不能回退到 app。
6.3 App files expected to shrink
new_editor/app/Composition/EditorShellHostedPanelCoordinator.hnew_editor/app/Composition/EditorShellHostedPanelCoordinator.cppnew_editor/app/Composition/EditorShellInteractionEngine.cppnew_editor/app/Composition/EditorShellRuntime.hnew_editor/app/Composition/EditorShellRuntime.cppnew_editor/app/Platform/Win32/EditorWindowChromeController.cppnew_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cppnew_editor/app/Features/PanelInputContext.h
6.4 App business files expected to adapt, not be generalized
new_editor/app/Features/Hierarchy/HierarchyPanel.*new_editor/app/Features/Project/ProjectPanel.*new_editor/app/Features/Inspector/InspectorPanel.*new_editor/app/Features/ColorPicker/ColorPickerPanel.*new_editor/app/Features/Console/ConsolePanel.*new_editor/app/Features/Scene/SceneViewportFeature.*
这些文件应改为消费 editor-layer 的统一 dispatch/lifecycle 数据,而不是继续维护自己的 host 规则。
7. Validation
7.1 Structural validation
完成后必须满足:
XCUIEditorLib仍然不 includeappUpdateUIEditorPanelHostLifecycle(...)已进入活跃路径app中不再保留三套 detached/tool-window 策略推导app中不再保留多套 mounted/focus/allowInteraction 推导
7.2 Behavior validation
至少验证以下行为:
- 主窗口正常更新/绘制
- detached panel 正常打开
- tool-window 最小尺寸和标题栏行为正确
- hierarchy / project / inspector / color picker 交互不回归
- scene viewport capture / pointer ownership 不回归
- 跨窗口面板移动后生命周期和交互状态仍然正确
7.3 Regression grep checklist
收口完成后,以下 grep 结果应显著收缩或归零:
FindMountedHierarchyPanelFindMountedProjectPanelFindMountedInspectorPanelFindMountedColorPickerPanelPanelInputContextResolveSingleVisibleToolWindowPanelDescriptorResolveSingleVisibleRootPanelDescriptorResolveMinimumOuterSizeResolveDetachedPanelPreferredSize
8. Execution Order
建议按以下顺序执行,降低回归面:
- 先接通
UIEditorPanelHostLifecycle活路径 - 再引入
UIEditorHostedPanelDispatch - 然后让各个 panel 改为消费统一 dispatch entry
- 再统一 detached/tool-window policy
- 最后收缩
EditorShellRuntime、EditorShellHostedPanelCoordinator和EditorShellDrawComposer
不要反过来先大拆 EditorShellRuntime,否则会在旧重复逻辑还没收掉时放大回归风险。
9. Out Of Scope
本计划明确不包含:
- 把业务 panel 本身迁入
XCUIEditorLib - 重做 Win32 window manager
- 重做 D3D12 host renderer
- 重做 viewport render pipeline
- 全面重命名
new_editor目录结构
10. Completion Criteria
只有满足以下条件,这次分层收口才算真正完成:
app中不再保存 editor-generic hosted-panel 机制实现- detached/tool-window 策略收成 editor-layer 单一入口
UIEditorPanelHostLifecycle从闲置工具变为真实主路径能力- 具体业务 panel 继续留在
app,但只消费统一的 editor-layer dispatch/lifecycle 数据 EditorShellRuntime收缩为应用编排器,而不是第二套 editor frameworkXCUIEditorLib与app的单向依赖方向保持不变
11. Final Statement
这次收口的目标不是“把更多代码搬到 new_editor/src 就算成功”,而是:
- 让
XCUIEditorLib真的承担 editor 模块层职责 - 让
app退回应用层职责 - 让同一套 panel host / detached-window 语义只保留一份实现
做到这三点之后,new_editor 的四层结构才算从“目录上分层”走到“职责上分层”。