Refactor new editor app context and workspace shell
This commit is contained in:
600
docs/plan/NewEditor对标旧Editor迁移重建计划_2026-04-12.md
Normal file
600
docs/plan/NewEditor对标旧Editor迁移重建计划_2026-04-12.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# NewEditor 对标旧 Editor 迁移重建计划
|
||||
|
||||
日期: `2026-04-12`
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
这份计划用于正式切换主线:
|
||||
|
||||
- 旧 `editor/` 继续作为参考实现与行为基线。
|
||||
- `new_editor/` 从“XCEditor 基础层试验宿主”升级为“新编辑器正式重建宿主”。
|
||||
- 后续工作重点不再是单独补 `XCEditor` 的孤立控件,而是参考旧 `editor/`,在 `new_editor/` 内逐步重建完整编辑器应用。
|
||||
|
||||
但有一个硬约束必须始终遵守:
|
||||
|
||||
- 如果在重建 `new_editor` 的过程中暴露出 `XCEditor`、宿主层或输入/布局/viewport 基础设施的结构问题,必须先回到底层修根因,再继续往上迁移。
|
||||
- 不允许在 `new_editor` 业务层堆临时兼容逻辑来掩盖底层问题。
|
||||
|
||||
这份计划替代此前“先单独收口 dock/UI 基础层,再暂停业务”的阶段性计划。当前主线已经进入 `new_editor` 正式重建阶段。
|
||||
|
||||
## 2. 不可违背的设计原则
|
||||
|
||||
### 2.1 根因优先
|
||||
|
||||
- 任何交互、布局、DPI、capture、拖拽、文本测量、渲染清晰度问题,优先在 `XCEditor` / `Host` / shared editor infrastructure 修。
|
||||
- 禁止在某个 panel 内写只为绕过去的局部 hack。
|
||||
|
||||
### 2.2 旧 Editor 是参考基线,不是直接复制目标
|
||||
|
||||
- 旧 `editor/` 的行为、工作流、信息架构、可用性和视觉结果是当前最可靠的参考。
|
||||
- 旧 `editor/` 里凡是 ImGui 特有的壳代码、临时 helper、历史包袱,不应机械照搬。
|
||||
- 迁移过程中允许顺手做架构收敛,但必须保持行为兼容,不允许一边“优化”一边偏离旧版交互。
|
||||
|
||||
### 2.3 `XCEditor` 只承载 Editor UI 基础层,不承载 Editor 业务
|
||||
|
||||
- `new_editor/include/XCEditor/**` + `new_editor/src/**` 负责 editor-only 的 UI primitive、shell、dock、field、tree/list、menu、viewport slot、workspace 等基础能力。
|
||||
- `new_editor` 的正式编辑器业务必须放在应用层,不得继续把业务逻辑塞回 `XCEditor` 库层。
|
||||
|
||||
### 2.4 Editor 样式固定写死在代码里
|
||||
|
||||
- `Runtime UI` 保留资源化/主题化能力,服务游戏开发者。
|
||||
- `Editor UI` 当前不走资源驱动主线,样式、palette、metrics、chrome 由代码固定维护。
|
||||
- 不再给 `Editor UI` 扩一套厚重的主题资源解释系统。
|
||||
|
||||
### 2.5 测试体系与产品宿主分离
|
||||
|
||||
- `tests/UI/Core|Runtime|Editor` 继续作为 UI 模块验证入口。
|
||||
- `new_editor/` 是产品宿主,不是测试场景堆放区。
|
||||
- 某项能力先在对应层的 `tests/UI` 里做 unit/integration 回归,再接入 `new_editor`。
|
||||
|
||||
## 3. 当前基线判断
|
||||
|
||||
## 3.1 已经完成并可复用的部分
|
||||
|
||||
当前 `new_editor` 已经具备一套可继续向上承接的 Editor UI 基础层:
|
||||
|
||||
- `XCEditor` 库层已经有:
|
||||
- `Fields`
|
||||
- `Tree/List/Scroll/TabStrip`
|
||||
- `MenuBar/MenuPopup/StatusBar`
|
||||
- `DockHost/Workspace/PanelHost`
|
||||
- `ViewportSlot/ViewportShell`
|
||||
- shortcut、command registry、workspace persistence
|
||||
- `Host` 层已经有:
|
||||
- Win32 宿主
|
||||
- Direct2D/DirectWrite 文本与图像绘制
|
||||
- DPI 感知
|
||||
- 自动截图
|
||||
- 自有 exe 截图输出
|
||||
- `new_editor` 当前已经验证过:
|
||||
- Unity 风格的基础 chrome
|
||||
- tab 拖拽重排与停靠
|
||||
- project 左树与右侧浏览器的基础外壳
|
||||
- hierarchy / project 图标与文本对齐
|
||||
- 多项 `tests/UI/Editor` unit / integration 回归
|
||||
|
||||
结论:
|
||||
|
||||
- `XCEditor` 不再是“完全不能承接产品”的状态。
|
||||
- 当前真正缺的不是几个零散 widget,而是 `new_editor` 应用层本身还没有长成旧 `editor/` 那样完整的编辑器架构。
|
||||
|
||||
## 3.2 当前仍然缺失的关键能力
|
||||
|
||||
距离“可以真正替代旧 `editor/` 开始长期迭代”的状态,当前还缺:
|
||||
|
||||
1. `new_editor` 自己的应用层骨架还不完整。
|
||||
2. 缺少类似旧 `editor/src/Core + Managers + Actions + Commands + Panels + Viewport + ComponentEditors + Scripting` 的正式分层。
|
||||
3. `Hierarchy` 仍是 demo/static 数据,不是场景真实数据。
|
||||
4. `Project` 虽然外壳已成型,但还不是旧 editor 的完整资产工作流。
|
||||
5. `Inspector` 及 `ComponentEditorRegistry` 体系尚未接回。
|
||||
6. `Console` 与日志桥接尚未接回。
|
||||
7. `Scene/Game viewport` 还没有接回旧 editor 的真实渲染/交互主链。
|
||||
8. project / scene / undo / selection / action route / runtime mode 这些编辑器全局状态在 `new_editor` 里还没有正式 owner。
|
||||
9. 脚本、运行模式、保存/打开、项目切换、Library/Asset 流程都还未接回。
|
||||
|
||||
## 4. 旧 Editor 与 NewEditor 的真实差距
|
||||
|
||||
## 4.1 旧 Editor 的实际分层
|
||||
|
||||
旧 `editor/` 当前已经形成的主干是:
|
||||
|
||||
`Application -> EditorLayer -> EditorWorkspace -> Panels/Viewport -> Actions -> Commands -> Managers/Core`
|
||||
|
||||
横向还包含:
|
||||
|
||||
- `UI`
|
||||
- `ComponentEditors`
|
||||
- `Layout`
|
||||
- `Scripting`
|
||||
- `Platform`
|
||||
|
||||
这意味着旧 editor 已经不是“只有一堆 panel”,而是一套完整的编辑器应用结构。
|
||||
|
||||
## 4.2 NewEditor 当前的真实分层
|
||||
|
||||
`new_editor/` 当前更接近:
|
||||
|
||||
`Application + Host + XCEditorLib + 少量 Product 面板`
|
||||
|
||||
其中:
|
||||
|
||||
- `include/XCEditor/**` + `src/**` 是 Editor UI 库层。
|
||||
- `Host/**` 是平台与绘制宿主。
|
||||
- `app/**` 目前还只是轻量应用壳与少量试制 panel。
|
||||
|
||||
缺口在于:
|
||||
|
||||
- 旧 editor 的“应用层中间层”几乎还没完整迁过来。
|
||||
- 当前 `new_editor` 的 panel 仍然偏 prototype,不是正式产品模块。
|
||||
|
||||
## 4.3 模块映射与结论
|
||||
|
||||
| 旧 editor 模块 | 当前状态 | new_editor 目标落点 |
|
||||
| --- | --- | --- |
|
||||
| `editor/src/UI/**` | 大部分语义已被 `XCEditor` 吸收 | 继续沉淀在 `include/XCEditor/**` + `src/**` |
|
||||
| `editor/src/Layout/**` | 旧版依赖 ImGui docking | 由 `XCEditor/Shell/**` 的 workspace/dock host 接替 |
|
||||
| `editor/src/Application.*` | 旧版完整 | `new_editor` 应保留为宿主壳,避免塞业务 |
|
||||
| `editor/src/Core/**` | 旧版完整 | `new_editor` 需要建立对应 app-level Core |
|
||||
| `editor/src/Managers/**` | 旧版完整 | `new_editor` 需要建立对应 app-level Managers |
|
||||
| `editor/src/Actions/**` | 旧版完整 | `new_editor` 需要正式迁入 |
|
||||
| `editor/src/Commands/**` | 旧版完整 | `new_editor` 需要正式迁入 |
|
||||
| `editor/src/panels/**` | 旧版完整 | `new_editor` 需要按新 shell 重写,但行为对齐旧版 |
|
||||
| `editor/src/ComponentEditors/**` | 旧版完整 | `new_editor` 需要迁入并适配 `XCEditor` property grid/field |
|
||||
| `editor/src/Viewport/**` | 旧版完整 | `new_editor` 需要重新桥接到 `XCEditor` viewport slot/shell |
|
||||
| `editor/src/Scripting/**` | 旧版完整 | `new_editor` 后期需要接回 |
|
||||
|
||||
结论:
|
||||
|
||||
- `XCEditor` 已经覆盖了大量旧 `editor/src/UI/**` 与 `Layout` 的职责。
|
||||
- 但 `new_editor` 还没有真正补上旧 editor 的应用层与工作流层。
|
||||
- 因此接下来的重点不是“继续造更多基础控件”,而是“用旧 editor 的结构和行为为参照,把新 editor 的应用层补齐”。
|
||||
|
||||
## 5. 迁移总策略
|
||||
|
||||
迁移策略固定为两条线并行推进:
|
||||
|
||||
### 5.1 主线:重建 NewEditor 应用层
|
||||
|
||||
- 以旧 `editor/` 为参考,在 `new_editor` 下逐步建立正式的:
|
||||
- `Core`
|
||||
- `Managers`
|
||||
- `Actions`
|
||||
- `Commands`
|
||||
- `Panels`
|
||||
- `Viewport`
|
||||
- `ComponentEditors`
|
||||
- `Scripting`
|
||||
|
||||
### 5.2 副线:按需回补 XCEditor / Host
|
||||
|
||||
- 当主线重建暴露底层能力缺口时,再回补:
|
||||
- `XCEditor`
|
||||
- `Host`
|
||||
- `tests/UI/Editor`
|
||||
|
||||
副线只为解决主线遇到的真实阻塞,不再无边界扩 editor-only widget。
|
||||
|
||||
## 6. 目标架构
|
||||
|
||||
## 6.1 `XCEditor` 的职责边界
|
||||
|
||||
保留在库层的内容:
|
||||
|
||||
- fixed-code editor theme / metrics / palette
|
||||
- menu / popup / dock / workspace / tab strip
|
||||
- tree / list / scroll / inline rename / property grid / fields
|
||||
- viewport slot / viewport shell
|
||||
- editor UI input model、session、interaction state machine
|
||||
|
||||
不放进库层的内容:
|
||||
|
||||
- project 资产语义
|
||||
- scene/entity/component 语义
|
||||
- console/log bridge
|
||||
- project/scene open/save 工作流
|
||||
- inspector 组件注册表
|
||||
- editor 菜单业务动作
|
||||
|
||||
## 6.2 `new_editor` 应用层目标结构
|
||||
|
||||
应用层建议逐步收敛到与旧 `editor/src` 接近的形态:
|
||||
|
||||
```text
|
||||
new_editor/
|
||||
Host/
|
||||
include/XCEditor/
|
||||
src/
|
||||
Core/
|
||||
Managers/
|
||||
Actions/
|
||||
Commands/
|
||||
Panels/
|
||||
Viewport/
|
||||
ComponentEditors/
|
||||
Scripting/
|
||||
Shell/
|
||||
Icons/
|
||||
Application/
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `include/XCEditor/**` + `src/**` 中的库层文件继续保留并演进。
|
||||
- `new_editor` 的产品应用代码不应长期散落在轻量 `app/*` 原型目录里。
|
||||
- 迁移早期可以暂时复用现有 `app/*`,但必须尽快收敛到正式应用层目录,避免“基础层代码”和“产品层代码”混杂。
|
||||
|
||||
## 7. 分阶段执行计划
|
||||
|
||||
## Phase 0:文档与目录主线切换
|
||||
|
||||
### 目标
|
||||
|
||||
把主线明确切到 `new_editor` 正式重建,清理过期计划入口。
|
||||
|
||||
### 任务
|
||||
|
||||
- 归档旧的阶段性 dock 收口计划。
|
||||
- 保留 `Editor架构说明.md` 作为旧 editor 参考文档,不归档。
|
||||
- 新建本迁移计划,作为当前唯一 editor 重建主线。
|
||||
- 在计划中正式写死:
|
||||
- `old editor = reference`
|
||||
- `new_editor = product host`
|
||||
- `XCEditor = editor ui library`
|
||||
- `tests/UI = module verification entry`
|
||||
|
||||
### 完成标准
|
||||
|
||||
- 后续不再以旧 dock 收口计划作为主线驱动文档。
|
||||
|
||||
## Phase 1:收敛 NewEditor 应用层骨架
|
||||
|
||||
### 目标
|
||||
|
||||
先把 `new_editor` 从“壳 + 若干 product panel”收敛成“真正的编辑器应用结构”。
|
||||
|
||||
### 任务
|
||||
|
||||
- 建立 `new_editor` 自己的应用层目录与命名边界。
|
||||
- 提炼 `Application` 的纯宿主职责:
|
||||
- window / DPI / renderer / input pump / screenshot / main loop
|
||||
- 建立 `EditorWorkspace` 式的应用装配层:
|
||||
- panel registry
|
||||
- workspace compose
|
||||
- panel attach/detach/update/render
|
||||
- 建立 `EditorContext` / `IEditorContext` 式的全局状态容器。
|
||||
- 定义新 editor 的状态所有权:
|
||||
- selection owner
|
||||
- scene owner
|
||||
- project owner
|
||||
- undo owner
|
||||
- active action route owner
|
||||
- runtime mode owner
|
||||
|
||||
### 根因要求
|
||||
|
||||
- 不允许继续让 `Application` 直接知道越来越多 project/hierarchy 细节。
|
||||
- 任何产品语义都必须逐步从 `Application` 下沉到 context / managers / panels。
|
||||
|
||||
### 完成标准
|
||||
|
||||
- `new_editor` 具备清晰的 app-level `Core/Workspace/Panels` 骨架。
|
||||
- 现有 `ProductHierarchyPanel` / `ProductProjectPanel` 不再是孤立 demo 对象,而是正式 panel 接口的一部分。
|
||||
|
||||
## Phase 2:迁入全局动作路由与命令层
|
||||
|
||||
### 目标
|
||||
|
||||
把旧 editor 的 “menu / shortcut / context menu / toolbar -> action/router -> command” 主链迁进来。
|
||||
|
||||
### 任务
|
||||
|
||||
- 对照旧 `editor/src/Actions/**` 梳理新 editor 的动作集:
|
||||
- `File`
|
||||
- `Edit`
|
||||
- `Assets`
|
||||
- `Run`
|
||||
- `Scripts`
|
||||
- `View`
|
||||
- `Help`
|
||||
- 建立新的 action route 机制:
|
||||
- global actions
|
||||
- focused panel actions
|
||||
- active route dispatch
|
||||
- 将真正修改数据的行为放入 `Commands/**`。
|
||||
- 让菜单、快捷键、右键菜单、toolbar 共享同一套动作定义。
|
||||
|
||||
### 根因要求
|
||||
|
||||
- 不允许 menu、shortcut、context menu 各自复制一套业务逻辑。
|
||||
- 不允许 panel 直接偷偷修改 scene/project 状态绕开 command。
|
||||
|
||||
### 完成标准
|
||||
|
||||
- 所有 editor 主动作都有稳定 owner。
|
||||
- UI 入口与业务执行不再混杂。
|
||||
|
||||
## Phase 3:接回 Hierarchy 正式数据模型
|
||||
|
||||
### 目标
|
||||
|
||||
把 `Hierarchy` 从 demo tree 改成真实 scene-backed 面板。
|
||||
|
||||
### 任务
|
||||
|
||||
- 用真实场景层级替换当前静态 `m_treeItems`。
|
||||
- 建立 hierarchy 展平与增量同步路径。
|
||||
- 接回 selection 同步。
|
||||
- 接回 rename。
|
||||
- 接回 create/delete/duplicate。
|
||||
- 接回拖拽 reparent:
|
||||
- drop on entity => 成为子节点
|
||||
- drop on empty area => 回到 root
|
||||
- 接回 context menu。
|
||||
- 接回旧 editor 的层级树图标/展开/焦点/多选策略。
|
||||
|
||||
### 优先参考文件
|
||||
|
||||
- `editor/src/panels/HierarchyPanel.*`
|
||||
- `editor/src/Actions/HierarchyActionRouter.h`
|
||||
- `editor/src/Commands/EntityCommands.h`
|
||||
- `editor/src/Managers/SceneManager.*`
|
||||
|
||||
### 根因要求
|
||||
|
||||
- 先建立可变 hierarchy source model,再做拖拽。
|
||||
- 如拖拽/capture 冲突,需要在 shared hosted-content capture 机制上统一修复,不允许再写 panel 特判。
|
||||
|
||||
### 完成标准
|
||||
|
||||
- `Hierarchy` 具备旧 editor 的核心编辑语义,而不是展示假数据。
|
||||
|
||||
## Phase 4:接回 Project 正式资产工作流
|
||||
|
||||
### 目标
|
||||
|
||||
把已经有外壳的 `Project` 面板接回真实 project/Assets 工作流。
|
||||
|
||||
### 任务
|
||||
|
||||
- 用正式 `ProjectManager`/Asset data source 替换当前轻量扫描逻辑。
|
||||
- 保持已完成的外观与交互风格。
|
||||
- 接回:
|
||||
- folder tree
|
||||
- breadcrumb navigation
|
||||
- asset grid selection
|
||||
- open / double click
|
||||
- rename
|
||||
- create folder
|
||||
- delete
|
||||
- drag move
|
||||
- context menu
|
||||
- refresh / watcher
|
||||
- 对齐旧 editor 的 `.meta` / `Library` / 资源管理语义。
|
||||
|
||||
### 优先参考文件
|
||||
|
||||
- `editor/src/panels/ProjectPanel.*`
|
||||
- `editor/src/Actions/ProjectActionRouter.h`
|
||||
- `editor/src/Commands/ProjectCommands.h`
|
||||
- `editor/src/Managers/ProjectManager.*`
|
||||
|
||||
### 根因要求
|
||||
|
||||
- Project 右侧 grid 的事件必须继续保持语义化输出,不要退回 raw hit test 乱写业务。
|
||||
- 资产操作必须收敛到 command / manager,不能把文件系统操作塞在 UI 绘制逻辑里。
|
||||
|
||||
### 完成标准
|
||||
|
||||
- `Project` 成为真正可用的资产浏览与操作入口。
|
||||
|
||||
## Phase 5:接回 Inspector 与 ComponentEditorRegistry
|
||||
|
||||
### 目标
|
||||
|
||||
把 `XCEditor` 已有的 field/property grid 真正用于 Inspector,而不是停留在基础控件演示。
|
||||
|
||||
### 任务
|
||||
|
||||
- 建立新的 `ComponentEditors/**` 注册表。
|
||||
- 接回 `InspectorPanel`。
|
||||
- 首批接回:
|
||||
- Transform
|
||||
- Camera
|
||||
- Light
|
||||
- MeshFilter
|
||||
- MeshRenderer
|
||||
- Script
|
||||
- 接回 object/asset reference field。
|
||||
- 处理 interactive undo 边界。
|
||||
- 对齐旧 editor 的 Add Component 入口与规则。
|
||||
|
||||
### 优先参考文件
|
||||
|
||||
- `editor/src/panels/InspectorPanel.*`
|
||||
- `editor/src/ComponentEditors/**`
|
||||
- `editor/src/Commands/ComponentCommands.h`
|
||||
|
||||
### 根因要求
|
||||
|
||||
- 通用字段行为沉淀在 `XCEditor/Fields/**`。
|
||||
- 组件语义停留在 app-level `ComponentEditors/**`。
|
||||
- 不允许把具体组件判断重新塞回 shared field 层。
|
||||
|
||||
### 完成标准
|
||||
|
||||
- `Inspector` 可以对真实选中对象执行稳定编辑。
|
||||
|
||||
## Phase 6:接回 Console 与编辑器日志桥接
|
||||
|
||||
### 目标
|
||||
|
||||
补齐旧 editor 的 console 工作流。
|
||||
|
||||
### 任务
|
||||
|
||||
- 迁入 console data model、过滤状态与 sink。
|
||||
- 接回日志分级、清空、过滤、点击定位等基础行为。
|
||||
- 与菜单/快捷键/上下文操作对齐。
|
||||
|
||||
### 优先参考文件
|
||||
|
||||
- `editor/src/panels/ConsolePanel.*`
|
||||
- `editor/src/UI/ConsoleFilterState.h`
|
||||
- `editor/src/Core/EditorConsoleSink.*`
|
||||
- `editor/src/Actions/ConsoleActionRouter.h`
|
||||
|
||||
### 完成标准
|
||||
|
||||
- `Console` 不再是空 panel,能承担真实调试反馈。
|
||||
|
||||
## Phase 7:接回 Scene / Game Viewport 正式主链
|
||||
|
||||
### 目标
|
||||
|
||||
这是替代旧 editor 的真正硬门槛。
|
||||
|
||||
### 任务
|
||||
|
||||
- 将旧 editor 的 viewport host/render path 重新桥接到 `XCEditorViewportSlot / XCEditorViewportShell`。
|
||||
- 接回:
|
||||
- scene viewport 离屏渲染
|
||||
- game viewport 渲染
|
||||
- viewport resize
|
||||
- navigation
|
||||
- gizmo
|
||||
- overlay
|
||||
- selection / picking
|
||||
- edit / play mode 切换
|
||||
- 处理 viewport 与 shell 输入焦点、capture、cursor 的统一。
|
||||
|
||||
### 优先参考文件
|
||||
|
||||
- `editor/src/panels/SceneViewPanel.*`
|
||||
- `editor/src/panels/GameViewPanel.*`
|
||||
- `editor/src/Viewport/**`
|
||||
- `editor/src/Application.*`
|
||||
- `editor/src/Platform/D3D12WindowRenderer*`
|
||||
|
||||
### 根因要求
|
||||
|
||||
- viewport 相关问题优先在 app viewport layer / host bridge 修。
|
||||
- 不允许在 panel 里私拼第二套渲染路径。
|
||||
|
||||
### 完成标准
|
||||
|
||||
- `Scene/Game` 具备旧 editor 的基础可用性。
|
||||
|
||||
## Phase 8:接回项目生命周期、运行模式与脚本工作流
|
||||
|
||||
### 目标
|
||||
|
||||
把编辑器从“几个面板能显示”推进到“有完整项目工作流”。
|
||||
|
||||
### 任务
|
||||
|
||||
- project 打开 / 切换 / 保存状态
|
||||
- scene 打开 / 保存 / Save As
|
||||
- runtime mode / play session controller
|
||||
- 脚本程序集重建与状态显示
|
||||
- Library/bootstrap/import 状态展示
|
||||
- window title 与 dirty state
|
||||
|
||||
### 优先参考文件
|
||||
|
||||
- `editor/src/Core/PlaySessionController.*`
|
||||
- `editor/src/Core/EditorWindowTitle.h`
|
||||
- `editor/src/Scripting/**`
|
||||
- `editor/src/Application.*`
|
||||
|
||||
### 完成标准
|
||||
|
||||
- `new_editor` 能跑通真实项目编辑主流程,而不是只展示 UI 壳。
|
||||
|
||||
## Phase 9:视觉一致性、回归与旧 Editor 退场条件
|
||||
|
||||
### 目标
|
||||
|
||||
在核心工作流都接回后,统一做收口。
|
||||
|
||||
### 任务
|
||||
|
||||
- 对关键界面做逐项比对:
|
||||
- menu bar
|
||||
- dock/tab
|
||||
- hierarchy
|
||||
- project
|
||||
- inspector
|
||||
- console
|
||||
- scene/game
|
||||
- 修正与旧 editor 的视觉与交互偏差。
|
||||
- 清理 `new_editor` 中所有 demo 数据、临时状态、过渡适配层。
|
||||
- 给每一块关键能力补齐:
|
||||
- `tests/UI/Editor`
|
||||
- product-level smoke validation
|
||||
|
||||
### 最终收口标准
|
||||
|
||||
满足以下条件后,才算真正达到“可替代旧 editor”的程度:
|
||||
|
||||
1. `new_editor` 已接回旧 editor 的核心面板与主工作流。
|
||||
2. `Scene/Game` viewport 可稳定工作。
|
||||
3. `Hierarchy/Project/Inspector/Console` 都是正式数据源,不再有 demo 假数据。
|
||||
4. menu / shortcut / context / toolbar 已共用动作层。
|
||||
5. undo / selection / runtime mode / active route 有清晰 owner。
|
||||
6. 没有为了赶进度堆出来的 panel-local hack。
|
||||
|
||||
## 8. 并行拆分建议
|
||||
|
||||
可以并行,但必须分主从关系:
|
||||
|
||||
### 主依赖顺序
|
||||
|
||||
1. `Phase 1` 应用层骨架
|
||||
2. `Phase 2` 动作与命令层
|
||||
3. `Phase 3/4/5/6` 面板业务回接
|
||||
4. `Phase 7` viewport 主链
|
||||
5. `Phase 8/9` 生命周期与总收口
|
||||
|
||||
### 可并行子线
|
||||
|
||||
1. `Core/Managers/Context` 收敛
|
||||
2. `Actions/Commands` 迁移
|
||||
3. `Hierarchy + SceneManager` 接回
|
||||
4. `Project + ProjectManager` 接回
|
||||
5. `Inspector + ComponentEditors` 接回
|
||||
6. `Console + tests/UI/Editor` 回归补齐
|
||||
|
||||
说明:
|
||||
|
||||
- `Viewport` 尽量单独作为高风险主线,不和别的大改混在一起。
|
||||
- 一旦并行子线发现 `XCEditor` 基础问题,优先暂停子线、回底层修复。
|
||||
|
||||
## 9. 立即执行顺序
|
||||
|
||||
当前建议的第一批执行顺序是:
|
||||
|
||||
1. 先收敛 `new_editor` 应用层骨架与目录边界。
|
||||
2. 立刻建立新的 `EditorContext / Managers / Workspace`。
|
||||
3. 然后先接 `Hierarchy` 和 `Project` 的真实数据源,因为这两块当前已有可见 UI 外壳。
|
||||
4. 再接 `Inspector` 与 `Console`。
|
||||
5. 最后接 `Scene/Game viewport` 与运行模式主链。
|
||||
|
||||
原因:
|
||||
|
||||
- `Hierarchy` / `Project` 已经有可见壳层,最适合先把产品语义接回来。
|
||||
- `Viewport` 风险最高,应在应用层骨架稳定后单独推进。
|
||||
|
||||
## 10. 当前需要归档的旧计划
|
||||
|
||||
本轮建议归档到 `docs/plan/used/` 的文档:
|
||||
|
||||
- `XCEditor_Dock统一与Tab拖拽停靠收口计划_2026-04-10.md`
|
||||
|
||||
保留在 `docs/plan/` 的参考文档:
|
||||
|
||||
- `Editor架构说明.md`
|
||||
|
||||
原因:
|
||||
|
||||
- 前者属于阶段性基础层收口计划,主线已被当前迁移重建计划替代。
|
||||
- 后者不是过期计划,而是旧 editor 的分层参考资料,后续迁移时仍需频繁对照。
|
||||
@@ -147,10 +147,12 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP)
|
||||
add_executable(XCUIEditorApp WIN32
|
||||
app/main.cpp
|
||||
app/Application.cpp
|
||||
app/Core/ProductEditorContext.cpp
|
||||
app/Icons/ProductBuiltInIcons.cpp
|
||||
app/Panels/ProductHierarchyPanel.cpp
|
||||
app/Panels/ProductProjectPanel.cpp
|
||||
app/Shell/ProductShellAsset.cpp
|
||||
app/Workspace/ProductEditorWorkspace.cpp
|
||||
)
|
||||
|
||||
target_include_directories(XCUIEditorApp PRIVATE
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "Application.h"
|
||||
|
||||
#include "Shell/ProductShellAsset.h"
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
@@ -33,53 +31,12 @@ using ::XCEngine::UI::UIInputEventType;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIPointerButton;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
using App::BuildProductShellAsset;
|
||||
using App::BuildProductShellInteractionDefinition;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost";
|
||||
constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor";
|
||||
constexpr UINT kDefaultDpi = 96u;
|
||||
constexpr float kBaseDpiScale = 96.0f;
|
||||
|
||||
UIEditorShellComposeModel BuildShellComposeModelFromFrame(
|
||||
const UIEditorShellInteractionFrame& frame) {
|
||||
UIEditorShellComposeModel model = {};
|
||||
model.menuBarItems = frame.request.menuBarItems;
|
||||
model.toolbarButtons = frame.model.toolbarButtons;
|
||||
model.statusSegments = frame.model.statusSegments;
|
||||
model.workspacePresentations = frame.model.workspacePresentations;
|
||||
return model;
|
||||
}
|
||||
|
||||
void AppendShellPopups(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorShellInteractionFrame& frame,
|
||||
const UIEditorShellInteractionPalette& palette,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
const std::size_t popupCount =
|
||||
(std::min)(frame.request.popupRequests.size(), frame.popupFrames.size());
|
||||
for (std::size_t index = 0; index < popupCount; ++index) {
|
||||
const UIEditorShellInteractionPopupRequest& popupRequest =
|
||||
frame.request.popupRequests[index];
|
||||
const UIEditorShellInteractionPopupFrame& popupFrame =
|
||||
frame.popupFrames[index];
|
||||
Widgets::AppendUIEditorMenuPopupBackground(
|
||||
drawList,
|
||||
popupRequest.layout,
|
||||
popupRequest.widgetItems,
|
||||
popupFrame.popupState,
|
||||
palette.popupPalette,
|
||||
metrics.popupMetrics);
|
||||
Widgets::AppendUIEditorMenuPopupForeground(
|
||||
drawList,
|
||||
popupRequest.layout,
|
||||
popupRequest.widgetItems,
|
||||
popupFrame.popupState,
|
||||
palette.popupPalette,
|
||||
metrics.popupMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
Application* GetApplicationFromWindow(HWND hwnd) {
|
||||
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
}
|
||||
@@ -387,27 +344,6 @@ std::string DescribeProjectPanelEvent(const App::ProductProjectPanel::Event& eve
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> FilterShellInputEventsForHostedContentCapture(
|
||||
const std::vector<UIInputEvent>& inputEvents) {
|
||||
std::vector<UIInputEvent> filteredEvents = {};
|
||||
filteredEvents.reserve(inputEvents.size());
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
switch (event.type) {
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerLeave:
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
case UIInputEventType::PointerWheel:
|
||||
break;
|
||||
default:
|
||||
filteredEvents.push_back(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int Application::Run(HINSTANCE hInstance, int nCmdShow) {
|
||||
@@ -441,28 +377,13 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
InitializeUIEditorRuntimeTrace(logRoot);
|
||||
SetUnhandledExceptionFilter(&Application::HandleUnhandledException);
|
||||
LogRuntimeTrace("app", "initialize begin");
|
||||
m_shellAsset = BuildProductShellAsset(repoRoot);
|
||||
m_shellValidation = ValidateEditorShellAsset(m_shellAsset);
|
||||
m_validationMessage = m_shellValidation.message;
|
||||
if (!m_shellValidation.IsValid()) {
|
||||
LogRuntimeTrace("app", "shell asset validation failed: " + m_validationMessage);
|
||||
if (!m_editorContext.Initialize(repoRoot, *this)) {
|
||||
LogRuntimeTrace(
|
||||
"app",
|
||||
"shell asset validation failed: " + m_editorContext.GetValidationMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_workspaceController = UIEditorWorkspaceController(
|
||||
m_shellAsset.panelRegistry,
|
||||
m_shellAsset.workspace,
|
||||
m_shellAsset.workspaceSession);
|
||||
m_shortcutManager = BuildEditorShellShortcutManager(m_shellAsset);
|
||||
m_shortcutManager.SetHostCommandHandler(this);
|
||||
m_shellServices = {};
|
||||
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
|
||||
m_shellServices.shortcutManager = &m_shortcutManager;
|
||||
m_shellServices.textMeasurer = &m_renderer;
|
||||
m_lastStatus = "Ready";
|
||||
m_lastMessage = "Old editor shell baseline loaded.";
|
||||
LogRuntimeTrace("app", "workspace initialized: " + DescribeWorkspaceState());
|
||||
|
||||
WNDCLASSEXW windowClass = {};
|
||||
windowClass.cbSize = sizeof(windowClass);
|
||||
windowClass.style = CS_HREDRAW | CS_VREDRAW;
|
||||
@@ -505,24 +426,23 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
LogRuntimeTrace("app", "renderer initialization failed");
|
||||
return false;
|
||||
}
|
||||
m_builtInIcons.Initialize(m_renderer, repoRoot);
|
||||
if (!m_builtInIcons.GetLastError().empty()) {
|
||||
LogRuntimeTrace("icons", m_builtInIcons.GetLastError());
|
||||
m_editorContext.AttachTextMeasurer(m_renderer);
|
||||
m_editorWorkspace.Initialize(repoRoot, m_renderer);
|
||||
if (!m_editorWorkspace.GetBuiltInIconError().empty()) {
|
||||
LogRuntimeTrace("icons", m_editorWorkspace.GetBuiltInIconError());
|
||||
}
|
||||
m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons);
|
||||
m_projectPanel.SetBuiltInIcons(&m_builtInIcons);
|
||||
m_hierarchyPanel.Initialize();
|
||||
m_projectPanel.SetTextMeasurer(&m_renderer);
|
||||
m_projectPanel.Initialize(repoRoot);
|
||||
LogRuntimeTrace(
|
||||
"app",
|
||||
"workspace initialized: " +
|
||||
m_editorContext.DescribeWorkspaceState(m_editorWorkspace.GetShellInteractionState()));
|
||||
|
||||
ShowWindow(m_hwnd, nCmdShow);
|
||||
UpdateWindow(m_hwnd);
|
||||
|
||||
m_autoScreenshot.Initialize(m_shellAsset.captureRootPath);
|
||||
m_autoScreenshot.Initialize(m_editorContext.GetShellAsset().captureRootPath);
|
||||
if (IsAutoCaptureOnStartupEnabled()) {
|
||||
m_autoScreenshot.RequestCapture("startup");
|
||||
m_lastStatus = "Capture";
|
||||
m_lastMessage = "Startup capture requested.";
|
||||
m_editorContext.SetStatus("Capture", "Startup capture requested.");
|
||||
}
|
||||
LogRuntimeTrace("app", "initialize completed");
|
||||
return true;
|
||||
@@ -535,7 +455,7 @@ void Application::Shutdown() {
|
||||
}
|
||||
|
||||
m_autoScreenshot.Shutdown();
|
||||
m_builtInIcons.Shutdown();
|
||||
m_editorWorkspace.Shutdown();
|
||||
m_renderer.Shutdown();
|
||||
|
||||
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
|
||||
@@ -572,80 +492,50 @@ void Application::RenderFrame() {
|
||||
UIRect(0.0f, 0.0f, width, height),
|
||||
UIColor(0.10f, 0.10f, 0.10f, 1.0f));
|
||||
|
||||
if (m_shellValidation.IsValid()) {
|
||||
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
|
||||
const auto& palette = ResolveUIEditorShellInteractionPalette();
|
||||
const UIEditorShellInteractionDefinition definition = BuildShellDefinition();
|
||||
if (m_editorContext.IsValid()) {
|
||||
std::vector<UIInputEvent> frameEvents = std::move(m_pendingInputEvents);
|
||||
m_pendingInputEvents.clear();
|
||||
const std::vector<UIInputEvent> hostedContentEvents = frameEvents;
|
||||
const std::vector<UIInputEvent> shellEvents =
|
||||
m_projectPanel.HasActivePointerCapture()
|
||||
? FilterShellInputEventsForHostedContentCapture(frameEvents)
|
||||
: frameEvents;
|
||||
if (!frameEvents.empty()) {
|
||||
LogRuntimeTrace(
|
||||
"input",
|
||||
DescribeInputEvents(frameEvents) + " | " + DescribeWorkspaceState());
|
||||
DescribeInputEvents(frameEvents) + " | " +
|
||||
m_editorContext.DescribeWorkspaceState(
|
||||
m_editorWorkspace.GetShellInteractionState()));
|
||||
}
|
||||
|
||||
m_shellFrame = UpdateUIEditorShellInteraction(
|
||||
m_shellInteractionState,
|
||||
m_workspaceController,
|
||||
m_editorWorkspace.Update(
|
||||
m_editorContext,
|
||||
UIRect(0.0f, 0.0f, width, height),
|
||||
definition,
|
||||
shellEvents,
|
||||
m_shellServices,
|
||||
metrics);
|
||||
if (!shellEvents.empty() ||
|
||||
m_shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
|
||||
m_shellFrame.result.workspaceResult.dockHostResult.commandExecuted) {
|
||||
frameEvents,
|
||||
BuildCaptureStatusText());
|
||||
const UIEditorShellInteractionFrame& shellFrame =
|
||||
m_editorWorkspace.GetShellFrame();
|
||||
if (!frameEvents.empty() ||
|
||||
shellFrame.result.workspaceResult.dockHostResult.layoutChanged ||
|
||||
shellFrame.result.workspaceResult.dockHostResult.commandExecuted) {
|
||||
std::ostringstream frameTrace = {};
|
||||
frameTrace << "result consumed="
|
||||
<< (m_shellFrame.result.consumed ? "true" : "false")
|
||||
<< (shellFrame.result.consumed ? "true" : "false")
|
||||
<< " layoutChanged="
|
||||
<< (m_shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false")
|
||||
<< (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false")
|
||||
<< " commandExecuted="
|
||||
<< (m_shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false")
|
||||
<< (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false")
|
||||
<< " active="
|
||||
<< m_workspaceController.GetWorkspace().activePanelId
|
||||
<< m_editorContext.GetWorkspaceController().GetWorkspace().activePanelId
|
||||
<< " message="
|
||||
<< m_shellFrame.result.workspaceResult.dockHostResult.layoutResult.message;
|
||||
<< shellFrame.result.workspaceResult.dockHostResult.layoutResult.message;
|
||||
LogRuntimeTrace(
|
||||
"frame",
|
||||
frameTrace.str());
|
||||
}
|
||||
ApplyHostCaptureRequests(m_shellFrame.result);
|
||||
UpdateLastStatus(m_shellFrame.result);
|
||||
m_hierarchyPanel.Update(
|
||||
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
|
||||
hostedContentEvents,
|
||||
!m_shellFrame.result.workspaceInputSuppressed,
|
||||
m_workspaceController.GetWorkspace().activePanelId == "hierarchy");
|
||||
m_projectPanel.Update(
|
||||
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
|
||||
hostedContentEvents,
|
||||
!m_shellFrame.result.workspaceInputSuppressed,
|
||||
m_workspaceController.GetWorkspace().activePanelId == "project");
|
||||
for (const App::ProductProjectPanel::Event& event : m_projectPanel.GetFrameEvents()) {
|
||||
ApplyHostCaptureRequests(shellFrame.result);
|
||||
for (const App::ProductProjectPanel::Event& event : m_editorWorkspace.GetProjectPanelEvents()) {
|
||||
LogRuntimeTrace("project", DescribeProjectPanelEvent(event));
|
||||
m_lastStatus = "Project";
|
||||
m_lastMessage = DescribeProjectPanelEvent(event);
|
||||
m_editorContext.SetStatus("Project", DescribeProjectPanelEvent(event));
|
||||
}
|
||||
ApplyHostedContentCaptureRequests();
|
||||
ApplyCurrentCursor();
|
||||
const UIEditorShellComposeModel shellComposeModel =
|
||||
BuildShellComposeModelFromFrame(m_shellFrame);
|
||||
AppendUIEditorShellCompose(
|
||||
drawList,
|
||||
m_shellFrame.shellFrame,
|
||||
shellComposeModel,
|
||||
m_shellInteractionState.composeState,
|
||||
palette.shellPalette,
|
||||
metrics.shellMetrics);
|
||||
m_hierarchyPanel.Append(drawList);
|
||||
m_projectPanel.Append(drawList);
|
||||
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
|
||||
m_editorWorkspace.Append(drawList);
|
||||
} else {
|
||||
drawList.AddText(
|
||||
UIPoint(28.0f, 28.0f),
|
||||
@@ -654,7 +544,9 @@ void Application::RenderFrame() {
|
||||
16.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(28.0f, 54.0f),
|
||||
m_validationMessage.empty() ? std::string("Unknown validation error.") : m_validationMessage,
|
||||
m_editorContext.GetValidationMessage().empty()
|
||||
? std::string("Unknown validation error.")
|
||||
: m_editorContext.GetValidationMessage(),
|
||||
UIColor(0.72f, 0.72f, 0.72f, 1.0f),
|
||||
12.0f);
|
||||
}
|
||||
@@ -695,7 +587,7 @@ bool Application::IsPointerInsideClientArea() const {
|
||||
}
|
||||
|
||||
LPCWSTR Application::ResolveCurrentCursorResource() const {
|
||||
switch (m_projectPanel.GetCursorKind()) {
|
||||
switch (m_editorWorkspace.GetHostedContentCursorKind()) {
|
||||
case App::ProductProjectPanel::CursorKind::ResizeEW:
|
||||
return IDC_SIZEWE;
|
||||
case App::ProductProjectPanel::CursorKind::Arrow:
|
||||
@@ -703,8 +595,7 @@ LPCWSTR Application::ResolveCurrentCursorResource() const {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (Widgets::ResolveUIEditorDockHostCursorKind(
|
||||
m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout)) {
|
||||
switch (m_editorWorkspace.GetDockCursorKind()) {
|
||||
case Widgets::UIEditorDockHostCursorKind::ResizeEW:
|
||||
return IDC_SIZEWE;
|
||||
case Widgets::UIEditorDockHostCursorKind::ResizeNS:
|
||||
@@ -735,38 +626,28 @@ UIPoint Application::ConvertClientPixelsToDips(LONG x, LONG y) const {
|
||||
PixelsToDips(static_cast<float>(y)));
|
||||
}
|
||||
|
||||
std::string Application::BuildCaptureStatusText() const {
|
||||
if (m_autoScreenshot.HasPendingCapture()) {
|
||||
return "Shot pending...";
|
||||
}
|
||||
|
||||
if (!m_autoScreenshot.GetLastCaptureError().empty()) {
|
||||
return TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u);
|
||||
}
|
||||
|
||||
if (!m_autoScreenshot.GetLastCaptureSummary().empty()) {
|
||||
return TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Application::LogRuntimeTrace(
|
||||
std::string_view channel,
|
||||
std::string_view message) const {
|
||||
AppendUIEditorRuntimeTrace(channel, message);
|
||||
}
|
||||
|
||||
std::string Application::DescribeWorkspaceState() const {
|
||||
std::ostringstream stream = {};
|
||||
stream << "active=" << m_workspaceController.GetWorkspace().activePanelId;
|
||||
const auto visiblePanels =
|
||||
CollectUIEditorWorkspaceVisiblePanels(
|
||||
m_workspaceController.GetWorkspace(),
|
||||
m_workspaceController.GetSession());
|
||||
stream << " visible=[";
|
||||
for (std::size_t index = 0; index < visiblePanels.size(); ++index) {
|
||||
if (index > 0u) {
|
||||
stream << ',';
|
||||
}
|
||||
stream << visiblePanels[index].panelId;
|
||||
}
|
||||
stream << ']';
|
||||
|
||||
const auto& dockState =
|
||||
m_shellInteractionState.workspaceInteractionState.dockHostInteractionState;
|
||||
stream << " dragNode=" << dockState.activeTabDragNodeId;
|
||||
stream << " dragPanel=" << dockState.activeTabDragPanelId;
|
||||
if (dockState.dockHostState.dropPreview.visible) {
|
||||
stream << " dropTarget=" << dockState.dockHostState.dropPreview.targetNodeId;
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string Application::DescribeInputEvents(
|
||||
const std::vector<UIInputEvent>& events) const {
|
||||
std::ostringstream stream = {};
|
||||
@@ -825,116 +706,19 @@ void Application::ApplyHostCaptureRequests(const UIEditorShellInteractionResult&
|
||||
}
|
||||
|
||||
void Application::ApplyHostedContentCaptureRequests() {
|
||||
if (m_projectPanel.WantsHostPointerCapture() && GetCapture() != m_hwnd) {
|
||||
if (m_editorWorkspace.WantsHostPointerCapture() && GetCapture() != m_hwnd) {
|
||||
SetCapture(m_hwnd);
|
||||
}
|
||||
|
||||
if (m_projectPanel.WantsHostPointerRelease() &&
|
||||
if (m_editorWorkspace.WantsHostPointerRelease() &&
|
||||
GetCapture() == m_hwnd &&
|
||||
!HasShellInteractiveCaptureState()) {
|
||||
!m_editorWorkspace.HasShellInteractiveCapture()) {
|
||||
ReleaseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::HasShellInteractiveCaptureState() const {
|
||||
if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.activeTabDragNodeId.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) {
|
||||
if (panelState.viewportShellState.inputBridgeState.captured) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::HasInteractiveCaptureState() const {
|
||||
return HasShellInteractiveCaptureState() || m_projectPanel.HasActivePointerCapture();
|
||||
}
|
||||
|
||||
UIEditorShellInteractionDefinition Application::BuildShellDefinition() const {
|
||||
std::string statusText = m_lastStatus;
|
||||
if (!m_lastMessage.empty()) {
|
||||
statusText += statusText.empty() ? m_lastMessage : ": " + m_lastMessage;
|
||||
}
|
||||
|
||||
std::string captureText = {};
|
||||
if (m_autoScreenshot.HasPendingCapture()) {
|
||||
captureText = "Shot pending...";
|
||||
} else if (!m_autoScreenshot.GetLastCaptureError().empty()) {
|
||||
captureText = TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u);
|
||||
} else if (!m_autoScreenshot.GetLastCaptureSummary().empty()) {
|
||||
captureText = TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u);
|
||||
}
|
||||
|
||||
return BuildProductShellInteractionDefinition(
|
||||
m_shellAsset,
|
||||
m_workspaceController,
|
||||
statusText,
|
||||
captureText);
|
||||
}
|
||||
|
||||
void Application::UpdateLastStatus(const UIEditorShellInteractionResult& result) {
|
||||
if (result.commandDispatched) {
|
||||
m_lastStatus = std::string(GetUIEditorCommandDispatchStatusName(result.commandDispatchResult.status));
|
||||
m_lastMessage = result.commandDispatchResult.message.empty()
|
||||
? result.commandDispatchResult.displayName
|
||||
: result.commandDispatchResult.message;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.workspaceResult.dockHostResult.layoutChanged) {
|
||||
m_lastStatus = "Layout";
|
||||
m_lastMessage = result.workspaceResult.dockHostResult.layoutResult.message;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.workspaceResult.dockHostResult.commandExecuted) {
|
||||
m_lastStatus = "Workspace";
|
||||
m_lastMessage = result.workspaceResult.dockHostResult.commandResult.message;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.viewportPanelId.empty()) {
|
||||
m_lastStatus = result.viewportPanelId;
|
||||
if (result.viewportInputFrame.captureStarted) {
|
||||
m_lastMessage = "Viewport capture started.";
|
||||
} else if (result.viewportInputFrame.captureEnded) {
|
||||
m_lastMessage = "Viewport capture ended.";
|
||||
} else if (result.viewportInputFrame.focusGained) {
|
||||
m_lastMessage = "Viewport focused.";
|
||||
} else if (result.viewportInputFrame.focusLost) {
|
||||
m_lastMessage = "Viewport focus lost.";
|
||||
} else if (result.viewportInputFrame.pointerPressedInside) {
|
||||
m_lastMessage = "Viewport pointer down.";
|
||||
} else if (result.viewportInputFrame.pointerReleasedInside) {
|
||||
m_lastMessage = "Viewport pointer up.";
|
||||
} else if (result.viewportInputFrame.pointerMoved) {
|
||||
m_lastMessage = "Viewport pointer move.";
|
||||
} else if (result.viewportInputFrame.wheelDelta != 0.0f) {
|
||||
m_lastMessage = "Viewport wheel.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.menuMutation.changed) {
|
||||
if (!result.itemId.empty() && !result.menuMutation.openedPopupId.empty()) {
|
||||
m_lastStatus = "Menu";
|
||||
m_lastMessage = result.itemId + " opened child popup.";
|
||||
} else if (!result.menuId.empty() && !result.menuMutation.openedPopupId.empty()) {
|
||||
m_lastStatus = "Menu";
|
||||
m_lastMessage = result.menuId + " opened.";
|
||||
} else {
|
||||
m_lastStatus = "Menu";
|
||||
m_lastMessage = "Popup chain dismissed.";
|
||||
}
|
||||
}
|
||||
return m_editorWorkspace.HasInteractiveCapture();
|
||||
}
|
||||
|
||||
void Application::QueuePointerEvent(
|
||||
@@ -1082,8 +866,7 @@ UIEditorHostCommandDispatchResult Application::DispatchHostCommand(
|
||||
if (commandId == "help.about") {
|
||||
result.commandExecuted = true;
|
||||
result.message = "About dialog will be wired after modal layer lands.";
|
||||
m_lastStatus = "About";
|
||||
m_lastMessage = result.message;
|
||||
m_editorContext.SetStatus("About", result.message);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,10 @@
|
||||
#include <Host/InputModifierTracker.h>
|
||||
#include <Host/NativeRenderer.h>
|
||||
|
||||
#include "Icons/ProductBuiltInIcons.h"
|
||||
#include "Panels/ProductHierarchyPanel.h"
|
||||
#include "Panels/ProductProjectPanel.h"
|
||||
#include "Core/ProductEditorContext.h"
|
||||
#include "Workspace/ProductEditorWorkspace.h"
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
@@ -53,14 +49,11 @@ private:
|
||||
float GetDpiScale() const;
|
||||
float PixelsToDips(float pixels) const;
|
||||
::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const;
|
||||
std::string BuildCaptureStatusText() const;
|
||||
void LogRuntimeTrace(std::string_view channel, std::string_view message) const;
|
||||
void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result);
|
||||
void ApplyHostedContentCaptureRequests();
|
||||
bool HasShellInteractiveCaptureState() const;
|
||||
bool HasInteractiveCaptureState() const;
|
||||
UIEditorShellInteractionDefinition BuildShellDefinition() const;
|
||||
void UpdateLastStatus(const UIEditorShellInteractionResult& result);
|
||||
std::string DescribeWorkspaceState() const;
|
||||
std::string DescribeInputEvents(
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& events) const;
|
||||
void QueuePointerEvent(
|
||||
@@ -82,21 +75,10 @@ private:
|
||||
::XCEngine::UI::Editor::Host::NativeRenderer m_renderer = {};
|
||||
::XCEngine::UI::Editor::Host::AutoScreenshotController m_autoScreenshot = {};
|
||||
::XCEngine::UI::Editor::Host::InputModifierTracker m_inputModifierTracker = {};
|
||||
EditorShellAsset m_shellAsset = {};
|
||||
EditorShellAssetValidationResult m_shellValidation = {};
|
||||
UIEditorWorkspaceController m_workspaceController = {};
|
||||
UIEditorShortcutManager m_shortcutManager = {};
|
||||
App::ProductBuiltInIcons m_builtInIcons = {};
|
||||
App::ProductHierarchyPanel m_hierarchyPanel = {};
|
||||
App::ProductProjectPanel m_projectPanel = {};
|
||||
UIEditorShellInteractionServices m_shellServices = {};
|
||||
UIEditorShellInteractionState m_shellInteractionState = {};
|
||||
UIEditorShellInteractionFrame m_shellFrame = {};
|
||||
App::ProductEditorContext m_editorContext = {};
|
||||
App::ProductEditorWorkspace m_editorWorkspace = {};
|
||||
std::vector<::XCEngine::UI::UIInputEvent> m_pendingInputEvents = {};
|
||||
bool m_trackingMouseLeave = false;
|
||||
std::string m_validationMessage = {};
|
||||
std::string m_lastStatus = {};
|
||||
std::string m_lastMessage = {};
|
||||
UINT m_windowDpi = 96u;
|
||||
float m_dpiScale = 1.0f;
|
||||
};
|
||||
|
||||
187
new_editor/app/Core/ProductEditorContext.cpp
Normal file
187
new_editor/app/Core/ProductEditorContext.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#include "ProductEditorContext.h"
|
||||
|
||||
#include "Shell/ProductShellAsset.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::Editor::BuildEditorShellShortcutManager;
|
||||
|
||||
std::string ComposeStatusText(
|
||||
std::string_view status,
|
||||
std::string_view message) {
|
||||
if (status.empty()) {
|
||||
return std::string(message);
|
||||
}
|
||||
|
||||
if (message.empty()) {
|
||||
return std::string(status);
|
||||
}
|
||||
|
||||
return std::string(status) + ": " + std::string(message);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ProductEditorContext::Initialize(
|
||||
const std::filesystem::path& repoRoot,
|
||||
UIEditorHostCommandHandler& hostCommandHandler) {
|
||||
m_shellAsset = BuildProductShellAsset(repoRoot);
|
||||
m_shellValidation = ValidateEditorShellAsset(m_shellAsset);
|
||||
if (!m_shellValidation.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_workspaceController = UIEditorWorkspaceController(
|
||||
m_shellAsset.panelRegistry,
|
||||
m_shellAsset.workspace,
|
||||
m_shellAsset.workspaceSession);
|
||||
m_shortcutManager = BuildEditorShellShortcutManager(m_shellAsset);
|
||||
m_shortcutManager.SetHostCommandHandler(&hostCommandHandler);
|
||||
m_shellServices = {};
|
||||
m_shellServices.commandDispatcher = &m_shortcutManager.GetCommandDispatcher();
|
||||
m_shellServices.shortcutManager = &m_shortcutManager;
|
||||
SetReadyStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProductEditorContext::AttachTextMeasurer(
|
||||
const UIEditorTextMeasurer& textMeasurer) {
|
||||
m_shellServices.textMeasurer = &textMeasurer;
|
||||
}
|
||||
|
||||
bool ProductEditorContext::IsValid() const {
|
||||
return m_shellValidation.IsValid();
|
||||
}
|
||||
|
||||
const std::string& ProductEditorContext::GetValidationMessage() const {
|
||||
return m_shellValidation.message;
|
||||
}
|
||||
|
||||
const EditorShellAsset& ProductEditorContext::GetShellAsset() const {
|
||||
return m_shellAsset;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceController& ProductEditorContext::GetWorkspaceController() {
|
||||
return m_workspaceController;
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceController& ProductEditorContext::GetWorkspaceController() const {
|
||||
return m_workspaceController;
|
||||
}
|
||||
|
||||
const UIEditorShellInteractionServices& ProductEditorContext::GetShellServices() const {
|
||||
return m_shellServices;
|
||||
}
|
||||
|
||||
UIEditorShellInteractionDefinition ProductEditorContext::BuildShellDefinition(
|
||||
std::string_view captureText) const {
|
||||
return BuildProductShellInteractionDefinition(
|
||||
m_shellAsset,
|
||||
m_workspaceController,
|
||||
ComposeStatusText(m_lastStatus, m_lastMessage),
|
||||
captureText);
|
||||
}
|
||||
|
||||
void ProductEditorContext::SetReadyStatus() {
|
||||
SetStatus("Ready", "Old editor shell baseline loaded.");
|
||||
}
|
||||
|
||||
void ProductEditorContext::SetStatus(
|
||||
std::string status,
|
||||
std::string message) {
|
||||
m_lastStatus = std::move(status);
|
||||
m_lastMessage = std::move(message);
|
||||
}
|
||||
|
||||
void ProductEditorContext::UpdateStatusFromShellResult(
|
||||
const UIEditorShellInteractionResult& result) {
|
||||
if (result.commandDispatched) {
|
||||
SetStatus(
|
||||
std::string(GetUIEditorCommandDispatchStatusName(result.commandDispatchResult.status)),
|
||||
result.commandDispatchResult.message.empty()
|
||||
? result.commandDispatchResult.displayName
|
||||
: result.commandDispatchResult.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.workspaceResult.dockHostResult.layoutChanged) {
|
||||
SetStatus("Layout", result.workspaceResult.dockHostResult.layoutResult.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.workspaceResult.dockHostResult.commandExecuted) {
|
||||
SetStatus("Workspace", result.workspaceResult.dockHostResult.commandResult.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.viewportPanelId.empty()) {
|
||||
std::string message = {};
|
||||
if (result.viewportInputFrame.captureStarted) {
|
||||
message = "Viewport capture started.";
|
||||
} else if (result.viewportInputFrame.captureEnded) {
|
||||
message = "Viewport capture ended.";
|
||||
} else if (result.viewportInputFrame.focusGained) {
|
||||
message = "Viewport focused.";
|
||||
} else if (result.viewportInputFrame.focusLost) {
|
||||
message = "Viewport focus lost.";
|
||||
} else if (result.viewportInputFrame.pointerPressedInside) {
|
||||
message = "Viewport pointer down.";
|
||||
} else if (result.viewportInputFrame.pointerReleasedInside) {
|
||||
message = "Viewport pointer up.";
|
||||
} else if (result.viewportInputFrame.pointerMoved) {
|
||||
message = "Viewport pointer move.";
|
||||
} else if (result.viewportInputFrame.wheelDelta != 0.0f) {
|
||||
message = "Viewport wheel.";
|
||||
}
|
||||
|
||||
if (!message.empty()) {
|
||||
SetStatus(result.viewportPanelId, std::move(message));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.menuMutation.changed) {
|
||||
if (!result.itemId.empty() && !result.menuMutation.openedPopupId.empty()) {
|
||||
SetStatus("Menu", result.itemId + " opened child popup.");
|
||||
} else if (!result.menuId.empty() && !result.menuMutation.openedPopupId.empty()) {
|
||||
SetStatus("Menu", result.menuId + " opened.");
|
||||
} else {
|
||||
SetStatus("Menu", "Popup chain dismissed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ProductEditorContext::DescribeWorkspaceState(
|
||||
const UIEditorShellInteractionState& interactionState) const {
|
||||
std::ostringstream stream = {};
|
||||
stream << "active=" << m_workspaceController.GetWorkspace().activePanelId;
|
||||
const auto visiblePanels =
|
||||
CollectUIEditorWorkspaceVisiblePanels(
|
||||
m_workspaceController.GetWorkspace(),
|
||||
m_workspaceController.GetSession());
|
||||
stream << " visible=[";
|
||||
for (std::size_t index = 0; index < visiblePanels.size(); ++index) {
|
||||
if (index > 0u) {
|
||||
stream << ',';
|
||||
}
|
||||
stream << visiblePanels[index].panelId;
|
||||
}
|
||||
stream << ']';
|
||||
|
||||
const auto& dockState =
|
||||
interactionState.workspaceInteractionState.dockHostInteractionState;
|
||||
stream << " dragNode=" << dockState.activeTabDragNodeId;
|
||||
stream << " dragPanel=" << dockState.activeTabDragPanelId;
|
||||
if (dockState.dockHostState.dropPreview.visible) {
|
||||
stream << " dropTarget=" << dockState.dockHostState.dropPreview.targetNodeId;
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
55
new_editor/app/Core/ProductEditorContext.h
Normal file
55
new_editor/app/Core/ProductEditorContext.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
class UIEditorHostCommandHandler;
|
||||
struct UIEditorTextMeasurer;
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
class ProductEditorContext {
|
||||
public:
|
||||
bool Initialize(
|
||||
const std::filesystem::path& repoRoot,
|
||||
UIEditorHostCommandHandler& hostCommandHandler);
|
||||
void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer);
|
||||
|
||||
bool IsValid() const;
|
||||
const std::string& GetValidationMessage() const;
|
||||
const EditorShellAsset& GetShellAsset() const;
|
||||
|
||||
UIEditorWorkspaceController& GetWorkspaceController();
|
||||
const UIEditorWorkspaceController& GetWorkspaceController() const;
|
||||
const UIEditorShellInteractionServices& GetShellServices() const;
|
||||
|
||||
UIEditorShellInteractionDefinition BuildShellDefinition(
|
||||
std::string_view captureText) const;
|
||||
|
||||
void SetReadyStatus();
|
||||
void SetStatus(std::string status, std::string message);
|
||||
void UpdateStatusFromShellResult(const UIEditorShellInteractionResult& result);
|
||||
std::string DescribeWorkspaceState(
|
||||
const UIEditorShellInteractionState& interactionState) const;
|
||||
|
||||
private:
|
||||
EditorShellAsset m_shellAsset = {};
|
||||
EditorShellAssetValidationResult m_shellValidation = {};
|
||||
UIEditorWorkspaceController m_workspaceController = {};
|
||||
UIEditorShortcutManager m_shortcutManager = {};
|
||||
UIEditorShellInteractionServices m_shellServices = {};
|
||||
std::string m_lastStatus = {};
|
||||
std::string m_lastMessage = {};
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
210
new_editor/app/Workspace/ProductEditorWorkspace.cpp
Normal file
210
new_editor/app/Workspace/ProductEditorWorkspace.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
#include "ProductEditorWorkspace.h"
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellCompose.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIColor;
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIInputEvent;
|
||||
using ::XCEngine::UI::UIInputEventType;
|
||||
|
||||
UIEditorShellComposeModel BuildShellComposeModelFromFrame(
|
||||
const UIEditorShellInteractionFrame& frame) {
|
||||
UIEditorShellComposeModel model = {};
|
||||
model.menuBarItems = frame.request.menuBarItems;
|
||||
model.toolbarButtons = frame.model.toolbarButtons;
|
||||
model.statusSegments = frame.model.statusSegments;
|
||||
model.workspacePresentations = frame.model.workspacePresentations;
|
||||
return model;
|
||||
}
|
||||
|
||||
void AppendShellPopups(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorShellInteractionFrame& frame,
|
||||
const UIEditorShellInteractionPalette& palette,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
const std::size_t popupCount =
|
||||
(std::min)(frame.request.popupRequests.size(), frame.popupFrames.size());
|
||||
for (std::size_t index = 0; index < popupCount; ++index) {
|
||||
const UIEditorShellInteractionPopupRequest& popupRequest =
|
||||
frame.request.popupRequests[index];
|
||||
const UIEditorShellInteractionPopupFrame& popupFrame =
|
||||
frame.popupFrames[index];
|
||||
Widgets::AppendUIEditorMenuPopupBackground(
|
||||
drawList,
|
||||
popupRequest.layout,
|
||||
popupRequest.widgetItems,
|
||||
popupFrame.popupState,
|
||||
palette.popupPalette,
|
||||
metrics.popupMetrics);
|
||||
Widgets::AppendUIEditorMenuPopupForeground(
|
||||
drawList,
|
||||
popupRequest.layout,
|
||||
popupRequest.widgetItems,
|
||||
popupFrame.popupState,
|
||||
palette.popupPalette,
|
||||
metrics.popupMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> FilterShellInputEventsForHostedContentCapture(
|
||||
const std::vector<UIInputEvent>& inputEvents) {
|
||||
std::vector<UIInputEvent> filteredEvents = {};
|
||||
filteredEvents.reserve(inputEvents.size());
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
switch (event.type) {
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerLeave:
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
case UIInputEventType::PointerWheel:
|
||||
break;
|
||||
default:
|
||||
filteredEvents.push_back(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ProductEditorWorkspace::Initialize(
|
||||
const std::filesystem::path& repoRoot,
|
||||
Host::NativeRenderer& renderer) {
|
||||
m_builtInIcons.Initialize(renderer, repoRoot);
|
||||
m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons);
|
||||
m_projectPanel.SetBuiltInIcons(&m_builtInIcons);
|
||||
m_projectPanel.SetTextMeasurer(&renderer);
|
||||
m_hierarchyPanel.Initialize();
|
||||
m_projectPanel.Initialize(repoRoot);
|
||||
}
|
||||
|
||||
void ProductEditorWorkspace::Shutdown() {
|
||||
m_shellFrame = {};
|
||||
m_shellInteractionState = {};
|
||||
m_builtInIcons.Shutdown();
|
||||
}
|
||||
|
||||
void ProductEditorWorkspace::Update(
|
||||
ProductEditorContext& context,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
||||
std::string_view captureText) {
|
||||
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
|
||||
const UIEditorShellInteractionDefinition definition =
|
||||
context.BuildShellDefinition(captureText);
|
||||
const std::vector<UIInputEvent> hostedContentEvents = inputEvents;
|
||||
const std::vector<UIInputEvent> shellEvents =
|
||||
HasHostedContentCapture()
|
||||
? FilterShellInputEventsForHostedContentCapture(inputEvents)
|
||||
: inputEvents;
|
||||
|
||||
m_shellFrame = UpdateUIEditorShellInteraction(
|
||||
m_shellInteractionState,
|
||||
context.GetWorkspaceController(),
|
||||
bounds,
|
||||
definition,
|
||||
shellEvents,
|
||||
context.GetShellServices(),
|
||||
metrics);
|
||||
context.UpdateStatusFromShellResult(m_shellFrame.result);
|
||||
|
||||
const std::string& activePanelId =
|
||||
context.GetWorkspaceController().GetWorkspace().activePanelId;
|
||||
m_hierarchyPanel.Update(
|
||||
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
|
||||
hostedContentEvents,
|
||||
!m_shellFrame.result.workspaceInputSuppressed,
|
||||
activePanelId == "hierarchy");
|
||||
m_projectPanel.Update(
|
||||
m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame,
|
||||
hostedContentEvents,
|
||||
!m_shellFrame.result.workspaceInputSuppressed,
|
||||
activePanelId == "project");
|
||||
}
|
||||
|
||||
void ProductEditorWorkspace::Append(UIDrawList& drawList) const {
|
||||
const auto& metrics = ResolveUIEditorShellInteractionMetrics();
|
||||
const auto& palette = ResolveUIEditorShellInteractionPalette();
|
||||
const UIEditorShellComposeModel shellComposeModel =
|
||||
BuildShellComposeModelFromFrame(m_shellFrame);
|
||||
AppendUIEditorShellCompose(
|
||||
drawList,
|
||||
m_shellFrame.shellFrame,
|
||||
shellComposeModel,
|
||||
m_shellInteractionState.composeState,
|
||||
palette.shellPalette,
|
||||
metrics.shellMetrics);
|
||||
m_hierarchyPanel.Append(drawList);
|
||||
m_projectPanel.Append(drawList);
|
||||
AppendShellPopups(drawList, m_shellFrame, palette, metrics);
|
||||
}
|
||||
|
||||
const UIEditorShellInteractionFrame& ProductEditorWorkspace::GetShellFrame() const {
|
||||
return m_shellFrame;
|
||||
}
|
||||
|
||||
const UIEditorShellInteractionState& ProductEditorWorkspace::GetShellInteractionState() const {
|
||||
return m_shellInteractionState;
|
||||
}
|
||||
|
||||
const std::vector<ProductProjectPanel::Event>& ProductEditorWorkspace::GetProjectPanelEvents() const {
|
||||
return m_projectPanel.GetFrameEvents();
|
||||
}
|
||||
|
||||
const std::string& ProductEditorWorkspace::GetBuiltInIconError() const {
|
||||
return m_builtInIcons.GetLastError();
|
||||
}
|
||||
|
||||
ProductProjectPanel::CursorKind ProductEditorWorkspace::GetHostedContentCursorKind() const {
|
||||
return m_projectPanel.GetCursorKind();
|
||||
}
|
||||
|
||||
Widgets::UIEditorDockHostCursorKind ProductEditorWorkspace::GetDockCursorKind() const {
|
||||
return Widgets::ResolveUIEditorDockHostCursorKind(
|
||||
m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout);
|
||||
}
|
||||
|
||||
bool ProductEditorWorkspace::WantsHostPointerCapture() const {
|
||||
return m_projectPanel.WantsHostPointerCapture();
|
||||
}
|
||||
|
||||
bool ProductEditorWorkspace::WantsHostPointerRelease() const {
|
||||
return m_projectPanel.WantsHostPointerRelease();
|
||||
}
|
||||
|
||||
bool ProductEditorWorkspace::HasHostedContentCapture() const {
|
||||
return m_projectPanel.HasActivePointerCapture();
|
||||
}
|
||||
|
||||
bool ProductEditorWorkspace::HasShellInteractiveCapture() const {
|
||||
if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.activeTabDragNodeId.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) {
|
||||
if (panelState.viewportShellState.inputBridgeState.captured) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProductEditorWorkspace::HasInteractiveCapture() const {
|
||||
return HasShellInteractiveCapture() || HasHostedContentCapture();
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
55
new_editor/app/Workspace/ProductEditorWorkspace.h
Normal file
55
new_editor/app/Workspace/ProductEditorWorkspace.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/ProductEditorContext.h"
|
||||
#include "Icons/ProductBuiltInIcons.h"
|
||||
#include "Panels/ProductHierarchyPanel.h"
|
||||
#include "Panels/ProductProjectPanel.h"
|
||||
|
||||
#include <Host/NativeRenderer.h>
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
class ProductEditorWorkspace {
|
||||
public:
|
||||
void Initialize(
|
||||
const std::filesystem::path& repoRoot,
|
||||
Host::NativeRenderer& renderer);
|
||||
void Shutdown();
|
||||
|
||||
void Update(
|
||||
ProductEditorContext& context,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
||||
std::string_view captureText);
|
||||
void Append(::XCEngine::UI::UIDrawList& drawList) const;
|
||||
|
||||
const UIEditorShellInteractionFrame& GetShellFrame() const;
|
||||
const UIEditorShellInteractionState& GetShellInteractionState() const;
|
||||
const std::vector<ProductProjectPanel::Event>& GetProjectPanelEvents() const;
|
||||
const std::string& GetBuiltInIconError() const;
|
||||
|
||||
ProductProjectPanel::CursorKind GetHostedContentCursorKind() const;
|
||||
Widgets::UIEditorDockHostCursorKind GetDockCursorKind() const;
|
||||
bool WantsHostPointerCapture() const;
|
||||
bool WantsHostPointerRelease() const;
|
||||
bool HasHostedContentCapture() const;
|
||||
bool HasShellInteractiveCapture() const;
|
||||
bool HasInteractiveCapture() const;
|
||||
|
||||
private:
|
||||
ProductBuiltInIcons m_builtInIcons = {};
|
||||
ProductHierarchyPanel m_hierarchyPanel = {};
|
||||
ProductProjectPanel m_projectPanel = {};
|
||||
UIEditorShellInteractionState m_shellInteractionState = {};
|
||||
UIEditorShellInteractionFrame m_shellFrame = {};
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
Reference in New Issue
Block a user