12 KiB
XCEditor Dock统一与Tab拖拽停靠收口计划
日期:2026-04-10
1. 文档定位
这份计划只解决 XCEditor 当前 dock 基础层的两个核心问题:
Hierarchy / Inspector与Scene / Game / Console / Project顶部 chrome 不是同一套。- tab 不能像旧
editor/的 ImGui docking 那样拖拽重排与调整停靠位置。
这两个问题本质上是同一个根问题:当前 DockHost 的叶子语义和渲染路径没有统一,导致视觉、命中、交互、布局变更都被拆成两套,后续再堆业务面板只会继续在错误基础上加复杂度。
本计划属于 Editor 基础层收口,不属于业务面板重建计划。
在这份计划完成前,不继续推进 new_editor 的 Project / Hierarchy / Inspector / Console 业务重建。
2. 当前问题与根因
2.1 当前现象
Hierarchy / Inspector的 header 仍然走 standalone panel 外壳。Scene / Game / Console / Project走 tab stack 外壳。- 用户看到的直接结果是:tab 高度、内边距、标题区结构、可交互区域都不一致。
- 用户尝试拖 tab 时,没有任何重排、合并、分裂停靠反馈。
2.2 当前真实根因
当前实现里同时存在两种叶子形态:
PanelTabStack
这使得 DockHost 的整条链路都被拆成两套:
- 布局输出分叉
- 绘制分叉
- hit test 分叉
- 交互分叉
- 未来的 docking mutation 也无从统一落点
当前代码里的直接证据:
new_editor/app/Shell/ProductShellAsset.cppnew_editor/include/XCEditor/Shell/UIEditorDockHost.hnew_editor/src/Shell/UIEditorDockHost.cppnew_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.hnew_editor/src/Shell/UIEditorDockHostInteraction.cppnew_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.hnew_editor/src/Collections/UIEditorTabStripInteraction.cppnew_editor/include/XCEditor/Shell/UIEditorWorkspaceController.hnew_editor/src/Shell/UIEditorWorkspaceController.cppnew_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp
当前控制器层只具备:
OpenPanelClosePanelShowPanelHidePanelActivatePanelResetWorkspaceSetSplitRatio
还没有真正支撑 docking 的命令与树变更能力:
ReorderTabMoveTabToStackDockTabRelativeExtractTabMergeTabStack
3. 改造目标
本轮收口后的目标状态如下:
- 运行态叶子节点只保留一种语义:
TabStack。 - 单面板也被视为“只有一个 tab 的 tab stack”。
DockHost只保留一套统一的DockHeader / TabWell / ContentBody视觉结构。Hierarchy / Inspector与Scene / Game / Console / Project必须走同一套 header/tab primitive。- tab 至少支持:
- 同 stack 重排
- 跨 stack 合并
- 向目标 leaf 的
left/right/top/bottom/center停靠
- 拖拽过程中必须有明确 preview,不允许“拖了但没有反馈”。
- workspace 布局持久化必须覆盖新结构,并兼容旧布局升级。
- 在
tests/UI/Editor中补齐单元测试与集成测试后,才允许继续推进业务面板。
4. 范围边界
4.1 本轮必须做
XCEditor的 dock 叶子语义统一- dock chrome 统一
- tab 拖拽状态机
- docking drop target 与 preview overlay
- workspace tree surgery
- layout persistence 升级
tests/UI/Editor的回归补齐
4.2 本轮明确不做
- 多原生窗口 detached docking
- 浮动窗口系统
- 业务面板内部复杂逻辑重建
Runtime UI相关能力- 为
Editor再做一套资源化主题体系
Editor 当前继续采用固定代码样式,不走 UI 资源那套。
5. 目标结构
5.1 统一后的 workspace 叶子模型
运行态只保留:
SplitNodeTabStackNode
其中 TabStackNode 内部包含:
tabsselectedTabIdactivePanelId
不再保留“裸 Panel 叶子节点”这种视觉语义。
5.2 统一后的 dock 布局输出
DockHost 最终应面向统一 leaf 输出:
tabWellRectcontentBodyRectselectedPanelIdtabItemsdropPreview
不再分成:
panelLayoutstabStackLayouts
两套平行分支。
5.3 统一后的交互分层
交互链路收口为:
UIEditorTabStripInteraction- 负责 tab press / armed / drag / release / cancel
UIEditorDockHostInteraction- 负责把 tab 拖拽映射为 docking preview 与 docking command
UIEditorWorkspaceController- 负责真正修改 workspace tree
UIEditorWorkspaceLayoutPersistence- 负责新旧布局读写与升级
6. 分阶段执行计划
Phase A:统一叶子语义
目标
把 workspace 运行态的叶子语义统一到 TabStackNode,彻底消除 standalone panel 叶子。
任务
- 调整
UIEditorWorkspaceModel,明确叶子节点只允许TabStackNode。 - 调整
ProductShellAsset与默认 workspace 构建逻辑,让单面板默认也生成单-tab stack。 - 调整
UIEditorWorkspaceSession,保证 active/selected 状态都围绕 tab stack 工作。 - 调整
UIEditorWorkspaceLayoutPersistence,读旧格式时自动升级为统一 leaf 结构。 - 增加 degenerate tree cleanup:
- 空 stack 删除
- 单子 split 折叠
- 非法 selectedTab 修正
完成标准
- 运行态 workspace 中不再出现 standalone panel 叶子。
- 旧布局能被自动升级并正常显示。
Phase B:统一 dock chrome 与布局输出
目标
让所有 panel 顶部都走同一套 dock header / tab well primitive。
任务
- 重构
UIEditorDockHost的 layout 输出结构,改为统一 leaf 布局。 - 删除 standalone panel 与 tab stack 的渲染分叉。
UIEditorPanelFrame缩回“边框与 body 外壳”职责,不再自带独立 header 风格。UIEditorTabStrip成为唯一 tab/header 渲染入口。UIEditorPanelContentHost只根据统一 leaf layout 定位 panel body。- 统一 hit target 语义,保证 header/tab/body 命中都来自同一套结构。
完成标准
Hierarchy / Inspector / Scene / Game / Console / Project顶部结构完全统一。- tab 高度、padding、命中区域、激活态样式只由一套 metric/palette 控制。
Phase C:同 stack tab 拖拽重排
目标
先把最小闭环做稳:同一个 stack 内 tab 可拖拽重排。
任务
- 在
UIEditorTabStripInteraction增加拖拽状态机:pressedarmeddraggingcancelledcommitted
- 增加 drag threshold、pointer capture、Esc cancel、release commit。
- 在同 stack 内计算插入位置与 preview insertion marker。
- 在
UIEditorWorkspaceController增加ReorderTab(...)。 - 保证 selected/active/focus 状态在重排后仍然正确。
完成标准
- 同一 tab strip 内可拖拽重排。
- 有明确插入线 preview。
- 无闪烁、无丢焦、无错误激活。
Phase D:跨 stack 合并与 split docking
目标
把 tab 从“只能在本组重排”扩展到“可跨组移动并形成新停靠布局”。
任务
- 在
DockHostInteraction中引入 drop target 解析:centerleftrighttopbottomroot empty area
- 增加 preview overlay:
- center merge 预览
- edge split 高亮预览
- 同 stack reorder 线性预览
- 在
WorkspaceController中增加:MoveTabToStack(...)DockTabRelative(...)ExtractTab(...)MergeTabStack(...)
- 增加 tree surgery:
- 从源 stack 抽 tab
- 插入目标 stack
- 围绕目标 leaf 生成 split
- 空 stack 清理
- 单子 split 折叠
完成标准
- tab 可从一个 stack 拖到另一个 stack。
- 可通过
left/right/top/bottom/center形成新布局。 - 交互有实时 preview。
Phase E:持久化、回归与测试体系补齐
目标
把新 docking 结构正式纳入 tests/UI/Editor 的标准回归。
任务
- 更新 layout serialization,稳定写回统一 leaf 结构。
- 旧布局读取时自动升级。
- 补齐
tests/UI/Editor/unit:- 旧布局升级
- 同 stack reorder
- 跨 stack merge
- split docking
- cleanup collapse
- active/selected 同步
- persistence round-trip
- 补齐
tests/UI/Editor/integration:shell/dock_header_unifiedshell/dock_tab_reorder_same_stackshell/dock_tab_move_between_stacksshell/dock_tab_split_targetsstate/dock_layout_persistence
- 每个集成测试 exe 顶部写清楚当前验证目标,不再只写模糊状态文本。
完成标准
tests/UI/Editor对新 docking 具备完整回归入口。- 后续业务面板接入时,不需要再倒回头修基础 docking 架构。
7. 代码落点
本轮预计主要改动路径:
new_editor/include/XCEditor/Shell/UIEditorWorkspaceModel.hnew_editor/src/Shell/UIEditorWorkspaceModel.cppnew_editor/include/XCEditor/Shell/UIEditorWorkspaceController.hnew_editor/src/Shell/UIEditorWorkspaceController.cppnew_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.hnew_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cppnew_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.hnew_editor/src/Shell/UIEditorWorkspaceSession.cppnew_editor/include/XCEditor/Shell/UIEditorDockHost.hnew_editor/src/Shell/UIEditorDockHost.cppnew_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.hnew_editor/src/Shell/UIEditorDockHostInteraction.cppnew_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.hnew_editor/src/Collections/UIEditorTabStripInteraction.cppnew_editor/include/XCEditor/Shell/UIEditorPanelFrame.hnew_editor/src/Shell/UIEditorPanelFrame.cppnew_editor/include/XCEditor/Shell/UIEditorPanelContentHost.hnew_editor/src/Shell/UIEditorPanelContentHost.cppnew_editor/app/Shell/ProductShellAsset.cpptests/UI/Editor/unit/*dock*tests/UI/Editor/unit/*workspace*tests/UI/Editor/integration/shell/*dock*tests/UI/Editor/integration/state/layout_persistence/*
8. 并行拆分建议
这轮改造可以拆成 4 条可并行子线,但必须按主从顺序集成:
WorkspaceModel / Controller / PersistenceDockHost layout / render / hit-test 统一TabStripInteraction / DockHostInteraction拖拽状态机tests/UI/Editor单元与集成回归补齐
并行原则:
- 先以
Phase A的统一 leaf 语义为总前提。 Phase B与Phase C可以在统一模型落定后并行推进。Phase E可以在Phase C / D功能接口稳定后并行补齐。
9. 风险与控制
9.1 最大风险
- 旧布局兼容被打断
- 统一叶子后现有 panel body 定位错位
- 拖拽状态机与 focus/capture 冲突
- split tree surgery 产生退化树或悬空 active panel
9.2 控制方式
- 先做模型统一,再做交互,不反过来堆补丁
- 每个 mutation 先补
unit再补integration - 任何旧布局兼容问题都在 persistence 层处理,不在渲染层写兼容分叉
- 任何单面板特殊视觉都回收为单-tab stack,不再恢复 standalone panel 路径
10. 收口判定
只有同时满足以下条件,才算这轮 dock 基础层真正收口:
- 所有 dock 叶子统一为
TabStackNode。 Hierarchy / Inspector与Scene / Game / Console / Project头部视觉完全统一。- tab 可同组重排、跨组合并、左右上下停靠。
- 拖拽过程有稳定 preview,释放后结果正确。
- 布局可持久化,旧布局可升级。
tests/UI/Editor/unit + integration已覆盖关键回归。
在这 6 条完成前,不进入下一轮业务面板重建。