diff --git a/docs/plan/NewEditor_EditorLayerAppBoundaryClosurePlan_2026-04-22.md b/docs/plan/NewEditor_EditorLayerAppBoundaryClosurePlan_2026-04-22.md new file mode 100644 index 00000000..a06fe0dd --- /dev/null +++ b/docs/plan/NewEditor_EditorLayerAppBoundaryClosurePlan_2026-04-22.md @@ -0,0 +1,561 @@ +# NewEditor Editor Layer / App Boundary Closure Plan + +Date: 2026-04-22 + +## 1. Objective + +这份计划的目标不是泛泛讨论“分层好不好看”,而是把 `new_editor` 当前最明显的边界泄漏收口掉: + +1. `engine` 中的 UI 基础层 / 运行时层继续只负责通用 UI 能力 +2. `new_editor/include` + `new_editor/src` 中的 `XCUIEditorLib` 明确成为“编辑器 UI 模块层” +3. `new_editor/app` 只保留编辑器应用层应当持有的业务、平台、渲染宿主和应用装配 +4. 把当前明显散落在 `app`、但本质属于“通用 editor UI 机制”的内容,抽回 `XCUIEditorLib` + +本轮计划只聚焦已经确认影响较大的三类问题: + +1. hosted panel 运行时机制散落在 `app` +2. detached/tool-window 策略散落在多个 `app` 模块 +3. panel lifecycle / focus / input gate 已经在 editor 层有雏形,但 `app` 没有接上,反而重复实现 + +## 2. Confirmed Findings + +### 2.1 Dependency direction is mostly correct + +当前 `XCUIEditorLib` 没有实际反向 include `app`,说明“目录依赖方向”本身没有彻底坏掉。 + +这点必须保留: + +1. `engine` 不依赖 `new_editor` +2. `XCUIEditorLib` 不依赖 `new_editor/app` +3. `app` 依赖 `XCUIEditorLib` 和 host/platform/rendering ports + +所以本次工作不是纠正反向依赖,而是纠正“职责泄漏到 `app`”。 + +### 2.2 Hosted panel runtime is still implemented in app + +以下职责目前散落在 `app`: + +1. 具体 panel 实例持有与总调度: + - `new_editor/app/Composition/EditorShellRuntime.h` +2. 面板更新分发、输入过滤、焦点派发: + - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` +3. 面板绘制拼装: + - `new_editor/app/Composition/EditorShellDrawComposer.cpp` +4. 面板事件回流到 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 规则: + +1. shell 交互期的 metrics 调整: + - `new_editor/app/Composition/EditorShellInteractionEngine.cpp` +2. 窗口 chrome 的 tool-window 判定和最小尺寸: + - `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` +3. 打开 detached panel 时的 preferred size 解析: + - `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` + +但这些规则依赖的源数据其实都在: + +1. `UIEditorPanelRegistry` +2. `UIEditorWorkspaceController` +3. `UIEditorWindowWorkspaceController` + +也就是说,策略消费被拆散了,策略数据却本来就在 editor 层。 + +### 2.4 Panel lifecycle abstraction exists in editor layer but is not used + +`XCUIEditorLib` 已经有: + +1. `new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h` +2. `new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp` + +但 `app` 里仍然自己维护: + +1. `PanelInputContext` +2. `allowInteraction` +3. `focusGained` +4. `focusLost` +5. mounted panel 扫描 + +对应文件包括: + +1. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` +2. `new_editor/app/Features/PanelInputContext.h` +3. `new_editor/app/Features/Hierarchy/HierarchyPanel.cpp` +4. `new_editor/app/Features/Project/ProjectPanel.cpp` +5. `new_editor/app/Features/Inspector/InspectorPanel.cpp` +6. `new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp` +7. `new_editor/app/Features/Console/ConsolePanel.cpp` + +这会导致同一套 host lifecycle 语义出现两份实现,并最终行为漂移。 + +### 2.5 Resulting symptom: EditorShellRuntime is carrying too much generic mechanism + +`EditorShellRuntime` 现在同时承担: + +1. shell interaction orchestration +2. hosted panel runtime orchestration +3. viewport host service orchestration +4. app feature update routing +5. draw packet assembly +6. workspace event tracing and status backflow + +其中前半部分有相当一块本应属于 editor 模块层,而不是 app 层。 + +## 3. Boundary Rules + +## 3.1 What must stay in engine UI base/runtime layers + +`engine` 中的 UI 层继续只负责: + +1. `UIInputEvent`、`UIRect`、`UIDrawData` 等基础类型 +2. 通用 UI 输入、焦点、样式、文字、widget model +3. 与编辑器无关的 runtime UI 能力 + +`engine` 不负责: + +1. editor panel host lifecycle +2. docked editor shell 策略 +3. detached editor window 策略 +4. editor hosted panel 调度 + +## 3.2 What must belong to XCUIEditorLib + +`XCUIEditorLib` 应当负责所有“与具体业务面板无关、但与 editor shell / workspace / panel host 强相关”的通用机制,包括: + +1. panel registry 元数据及校验 +2. workspace / dock host / viewport shell 的通用交互 +3. hosted panel mount/layout/lifecycle 的统一推导 +4. hosted panel 输入过滤与 input-owner 对接 +5. detached/tool-window 策略与尺寸解析 +6. 供应用层消费的 panel dispatch frame / request / result + +这里的核心判断标准是: + +只要某段逻辑不关心“这是 Hierarchy 还是 Project”,而只关心“这是一个 externally hosted panel”,它就应该在 `XCUIEditorLib`。 + +## 3.3 What must stay in app + +`new_editor/app` 只保留以下内容: + +1. 具体业务 panel 本身: + - `HierarchyPanel` + - `ProjectPanel` + - `InspectorPanel` + - `ConsolePanel` + - `ColorPickerPanel` + - `SceneViewportFeature` +2. 业务 runtime: + - `EditorSceneRuntime` + - `EditorProjectRuntime` + - `EditorSession` +3. 应用装配: + - 菜单/命令/toolbar 的应用级定义 + - panel registry 的应用级实例构建 +4. 平台宿主: + - Win32 window lifecycle + - D3D12 host renderer + - capture/system interaction + +## 3.4 What must not be moved into XCUIEditorLib + +本次收口不是把所有东西都塞进 editor 库。 + +以下内容明确不下沉: + +1. Hierarchy / Project / Inspector 的业务数据模型 +2. Scene runtime / project runtime / selection service +3. Win32 chrome 和窗口消息循环 +4. D3D12 host 层资源与 swapchain +5. app 级菜单、命令、panel 组合方案 + +## 3.5 Placement rule for new modules + +当前 `new_editor/src/App` 实际没有形成可用公共层,`include/XCEditor/App` 也不存在。 + +因此新增 editor-layer 模块不应新建一个伪 `XCEditor/App`。 + +新增通用能力应优先落到: + +1. `XCEditor/Panels` +2. `XCEditor/Shell` +3. `XCEditor/Workspace` + +只在确实与上述三个域都不匹配时,才考虑新增域。 + +## 4. Target End State + +## 4.1 Target runtime flow + +目标运行流应当变成: + +1. `app` 构造应用级 `EditorShellAsset`、业务 panel、业务 runtime +2. `XCUIEditorLib` 负责: + - shell/workspace 交互 + - content host mount frame + - panel host lifecycle frame + - hosted panel dispatch frame + - detached window policy resolution +3. `app` 只做两件事: + - 把 dispatch frame 按 `panelId` 路由给具体 panel + - 把具体 panel 的业务事件回流到 app session/runtime + +这样 `app` 保留“内容”,`XCUIEditorLib` 收回“机制”。 + +## 4.2 Target module responsibilities + +### Editor layer should own + +建议新增或重组出以下 editor-layer 模块: + +1. `UIEditorHostedPanelDispatch` + - 统一生成每个 hosted panel 的 update request / input slice / lifecycle view +2. `UIEditorDetachedWindowPolicy` + - 统一解析 tool-window、preferred size、minimum size、detached title behavior +3. `UIEditorPanelHostLifecycle` + - 从“已有但未接入”变为“活跃主路径能力” + +### App layer should own + +`app` 最终只保留一个轻量路由层,例如: + +1. `EditorHostedPanelRouter` + - 根据 `panelId` 找到具体 panel 对象 + - 调用 `Update(...)` + - 调用 `Append(...)` + - 收集业务事件 + +如果不需要单独新建这个类,也至少要把现有 `EditorShellHostedPanelCoordinator` 收缩到这个角色。 + +## 4.3 Target data model direction + +本次收口后,`app` 不应再自己定义一套 panel host 语义。 + +目标是: + +1. 删除或废弃 `PanelInputContext` +2. 用 editor-layer 的统一 dispatch entry / lifecycle frame 取代 +3. hosted panel 的 mounted / visible / active / focused / focus gained / focus lost 只存在一套来源 + +## 5. Refactor Strategy + +### Phase A. Freeze boundary and add structural guardrails + +目标: + +在开始搬迁逻辑前,先把边界约束写清楚,避免中途再次把通用 editor 机制塞回 `app`。 + +要做的事: + +1. 在文档中明确本计划的边界规则 +2. 审查 `new_editor/CMakeLists.txt` 中 `XCUIEditorLib` 与 `app` 的 include 使用现状 +3. 增加最小结构验证规则: + - `new_editor/src` / `new_editor/include` 不反向 include `app` + - `app` 不再新增 editor-generic duplicated helper +4. 为后续 grep 验证准备检查项 + +验收标准: + +1. 本计划作为边界基线落地 +2. 后续每一阶段都能用 grep 验证“重复逻辑是否还活着” + +### Phase B. Make panel host lifecycle a live path + +目标: + +先把已有的 `UIEditorPanelHostLifecycle` 从死代码变成活代码。 + +要做的事: + +1. 在 editor-layer 主路径中接入 `UpdateUIEditorPanelHostLifecycle(...)` +2. 明确 `focusedPanelId` 的统一来源 +3. 让 lifecycle frame 成为 hosted panel dispatch 的基础输入 +4. 禁止 `app` 再自行推导: + - `focusGained` + - `focusLost` + - `active` + - `visible` + - `attached` + +重点文件范围: + +1. `new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h` +2. `new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp` +3. `new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h` +4. `new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h` +5. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` + +验收标准: + +1. `UpdateUIEditorPanelHostLifecycle(...)` 进入实际 frame path +2. `app` 不再自己计算 focus enter/leave 语义 + +### Phase C. Introduce hosted panel dispatch into XCUIEditorLib + +目标: + +把 hosted panel 的通用运行时机制从 `app` 抽回 editor 层,但不把具体业务 panel 也硬搬进库里。 + +建议新增能力: + +1. `UIEditorHostedPanelDispatchEntry` +2. `UIEditorHostedPanelDispatchFrame` +3. `UIEditorHostedPanelDispatchRequest` + +每个 entry 至少应包含: + +1. `panelId` +2. mounted/bounds 信息 +3. lifecycle state 或 lifecycle frame 视图 +4. 该 panel 可见时应收到的过滤后输入 +5. 交互是否允许 +6. 当前 input owner / capture owner 相关视图 + +要做的事: + +1. 把 `EditorShellHostedPanelCoordinator.cpp` 中的通用逻辑下沉到 `XCUIEditorLib` +2. 统一处理 hosted-content 与 shell 对 pointer stream 的让渡规则 +3. 统一处理 panel 的 mounted/bounds 查找 +4. 统一处理 focus/lifecycle/input gate 计算 +5. app 只保留: + - `panelId -> concrete panel` 路由 + - 业务对象依赖注入 + - 业务事件回流 + +重点文件范围: + +1. 新增: + - `new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.h` + - `new_editor/src/Panels/UIEditorHostedPanelDispatch.cpp` +2. 调整: + - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.h` + - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` + - `new_editor/app/Features/PanelInputContext.h` + - `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.*` + +验收标准: + +1. `PanelInputContext` 被删除或降级为纯 app-specific 数据,不再承载通用 host 语义 +2. `FindMountedHierarchyPanel`、`FindMountedProjectPanel` 等重复 mounted 扫描逻辑被消除或大幅收缩 +3. `EditorShellHostedPanelCoordinator` 只剩业务路由和依赖注入 + +### Phase D. Centralize detached window / tool-window policy + +目标: + +把 detached/tool-window 相关策略从多个 `app` 模块收成一套 editor-layer 能力。 + +建议新增能力: + +1. `UIEditorDetachedWindowPolicy` +2. `UIEditorDetachedWindowPolicyResult` +3. `ResolveUIEditorDetachedWindowPolicy(...)` + +这套策略至少统一回答: + +1. 当前 workspace 是否表现为 tool-window +2. 当前 detached window 的 preferred/minimum 尺寸是多少 +3. 是否应使用 detached title-bar tab strip +4. 单面板 root tab stack 的判定逻辑 + +要做的事: + +1. 把 `ResolveSingleVisibleToolWindowPanelDescriptor(...)` 和同类逻辑下沉 +2. 把 `ResolveMinimumOuterSize(...)`、`ResolveDetachedPanelPreferredSize(...)` 统一消费同一套 API +3. 让 shell interaction metrics、Win32 chrome、detached-panel open request 共用同一套策略结果 + +重点文件范围: + +1. 新增: + - `new_editor/include/XCEditor/Shell/UIEditorDetachedWindowPolicy.h` + - `new_editor/src/Shell/UIEditorDetachedWindowPolicy.cpp` +2. 调整: + - `new_editor/app/Composition/EditorShellInteractionEngine.cpp` + - `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` + - `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` + +验收标准: + +1. `toolWindow`、preferred size、minimum size 的判定只有一套主实现 +2. 上述三个 app 文件只消费统一 helper,不再保留平行推导函数 + +### Phase E. Reduce EditorShellRuntime to app orchestration + +目标: + +在完成通用机制下沉之后,让 `EditorShellRuntime` 从“半个 editor framework”收缩为“应用层编排器”。 + +要做的事: + +1. 移除 `EditorShellRuntime` 中与 hosted panel lifecycle / dispatch / policy 直接相关的通用逻辑 +2. 保留: + - app feature 依赖注入 + - scene/project runtime 同步 + - status / selection / command route 回流 + - viewport host 服务调用 +3. 视情况把 `EditorShellDrawComposer` 收缩为纯 draw packet 组织器 +4. 视情况把 `WorkspaceEventSync` 收缩为纯 app 业务事件同步器 + +重点文件范围: + +1. `new_editor/app/Composition/EditorShellRuntime.h` +2. `new_editor/app/Composition/EditorShellRuntime.cpp` +3. `new_editor/app/Composition/EditorShellDrawComposer.h` +4. `new_editor/app/Composition/EditorShellDrawComposer.cpp` +5. `new_editor/app/Composition/WorkspaceEventSync.h` +6. `new_editor/app/Composition/WorkspaceEventSync.cpp` + +验收标准: + +1. `EditorShellRuntime` 不再承载 editor-generic host policy +2. 它只负责应用装配和业务流程编排 + +### Phase F. Verification and regression hardening + +目标: + +确保这次收口不是“结构更好看了,但行为退化了”。 + +要做的事: + +1. 编译 `XCUIEditorLib`、`XCUIEditorAppCore`、`XCUIEditorApp` +2. 验证 primary window、detached window、tool-window 三种路径 +3. 验证以下交互没有回归: + - hierarchy 选择/拖拽/rename + - project tree/grid 交互 + - inspector 输入 + - color picker detached open + - scene viewport capture +4. 增加 grep 结构检查: + - `PanelInputContext` 不再作为通用 host 语义入口 + - `ResolveSingleVisibleToolWindowPanelDescriptor` 等重复 helper 已移除 + - `UpdateUIEditorPanelHostLifecycle` 已有活跃调用 + +## 6. File-Level Scope + +### 6.1 Existing editor-layer files to extend + +1. `new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h` +2. `new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp` +3. `new_editor/include/XCEditor/Panels/UIEditorPanelContentHost.h` +4. `new_editor/src/Panels/UIEditorPanelContentHost.cpp` +5. `new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h` +6. `new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp` +7. `new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h` + +### 6.2 New editor-layer files expected + +1. `new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.h` +2. `new_editor/src/Panels/UIEditorHostedPanelDispatch.cpp` +3. `new_editor/include/XCEditor/Shell/UIEditorDetachedWindowPolicy.h` +4. `new_editor/src/Shell/UIEditorDetachedWindowPolicy.cpp` + +如果实际实现中发现这些文件更适合放到 `Workspace/`,可以调整,但不能回退到 `app`。 + +### 6.3 App files expected to shrink + +1. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.h` +2. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` +3. `new_editor/app/Composition/EditorShellInteractionEngine.cpp` +4. `new_editor/app/Composition/EditorShellRuntime.h` +5. `new_editor/app/Composition/EditorShellRuntime.cpp` +6. `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` +7. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` +8. `new_editor/app/Features/PanelInputContext.h` + +### 6.4 App business files expected to adapt, not be generalized + +1. `new_editor/app/Features/Hierarchy/HierarchyPanel.*` +2. `new_editor/app/Features/Project/ProjectPanel.*` +3. `new_editor/app/Features/Inspector/InspectorPanel.*` +4. `new_editor/app/Features/ColorPicker/ColorPickerPanel.*` +5. `new_editor/app/Features/Console/ConsolePanel.*` +6. `new_editor/app/Features/Scene/SceneViewportFeature.*` + +这些文件应改为消费 editor-layer 的统一 dispatch/lifecycle 数据,而不是继续维护自己的 host 规则。 + +## 7. Validation + +## 7.1 Structural validation + +完成后必须满足: + +1. `XCUIEditorLib` 仍然不 include `app` +2. `UpdateUIEditorPanelHostLifecycle(...)` 已进入活跃路径 +3. `app` 中不再保留三套 detached/tool-window 策略推导 +4. `app` 中不再保留多套 mounted/focus/allowInteraction 推导 + +## 7.2 Behavior validation + +至少验证以下行为: + +1. 主窗口正常更新/绘制 +2. detached panel 正常打开 +3. tool-window 最小尺寸和标题栏行为正确 +4. hierarchy / project / inspector / color picker 交互不回归 +5. scene viewport capture / pointer ownership 不回归 +6. 跨窗口面板移动后生命周期和交互状态仍然正确 + +## 7.3 Regression grep checklist + +收口完成后,以下 grep 结果应显著收缩或归零: + +1. `FindMountedHierarchyPanel` +2. `FindMountedProjectPanel` +3. `FindMountedInspectorPanel` +4. `FindMountedColorPickerPanel` +5. `PanelInputContext` +6. `ResolveSingleVisibleToolWindowPanelDescriptor` +7. `ResolveSingleVisibleRootPanelDescriptor` +8. `ResolveMinimumOuterSize` +9. `ResolveDetachedPanelPreferredSize` + +## 8. Execution Order + +建议按以下顺序执行,降低回归面: + +1. 先接通 `UIEditorPanelHostLifecycle` 活路径 +2. 再引入 `UIEditorHostedPanelDispatch` +3. 然后让各个 panel 改为消费统一 dispatch entry +4. 再统一 detached/tool-window policy +5. 最后收缩 `EditorShellRuntime`、`EditorShellHostedPanelCoordinator` 和 `EditorShellDrawComposer` + +不要反过来先大拆 `EditorShellRuntime`,否则会在旧重复逻辑还没收掉时放大回归风险。 + +## 9. Out Of Scope + +本计划明确不包含: + +1. 把业务 panel 本身迁入 `XCUIEditorLib` +2. 重做 Win32 window manager +3. 重做 D3D12 host renderer +4. 重做 viewport render pipeline +5. 全面重命名 `new_editor` 目录结构 + +## 10. Completion Criteria + +只有满足以下条件,这次分层收口才算真正完成: + +1. `app` 中不再保存 editor-generic hosted-panel 机制实现 +2. detached/tool-window 策略收成 editor-layer 单一入口 +3. `UIEditorPanelHostLifecycle` 从闲置工具变为真实主路径能力 +4. 具体业务 panel 继续留在 `app`,但只消费统一的 editor-layer dispatch/lifecycle 数据 +5. `EditorShellRuntime` 收缩为应用编排器,而不是第二套 editor framework +6. `XCUIEditorLib` 与 `app` 的单向依赖方向保持不变 + +## 11. Final Statement + +这次收口的目标不是“把更多代码搬到 `new_editor/src` 就算成功”,而是: + +1. 让 `XCUIEditorLib` 真的承担 editor 模块层职责 +2. 让 `app` 退回应用层职责 +3. 让同一套 panel host / detached-window 语义只保留一份实现 + +做到这三点之后,`new_editor` 的四层结构才算从“目录上分层”走到“职责上分层”。 diff --git a/new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp b/new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp index c69beaa2..4cc0bbd4 100644 --- a/new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp +++ b/new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp @@ -6,12 +6,11 @@ #include "Features/Console/ConsolePanel.h" #include "Features/Hierarchy/HierarchyPanel.h" #include "Features/Inspector/InspectorPanel.h" -#include "Features/PanelInputContext.h" #include "Features/Project/ProjectPanel.h" #include "Features/Scene/SceneViewportFeature.h" +#include #include -#include namespace XCEngine::UI::Editor::App { @@ -19,21 +18,17 @@ namespace { using ::XCEngine::UI::UIInputEvent; -PanelInputContext BuildHostedPanelInputContext( - const UIEditorWorkspaceInteractionFrame& workspaceFrame, - bool allowInteraction, +const UIEditorHostedPanelDispatchEntry& ResolveHostedPanelDispatchEntry( + const UIEditorHostedPanelDispatchFrame& dispatchFrame, std::string_view panelId) { - PanelInputContext inputContext = {}; - inputContext.allowInteraction = allowInteraction; - inputContext.hasInputFocus = - IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.inputOwner, panelId); - inputContext.focusGained = - !IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.previousInputOwner, panelId) && - inputContext.hasInputFocus; - inputContext.focusLost = - IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.previousInputOwner, panelId) && - !inputContext.hasInputFocus; - return inputContext; + static const UIEditorHostedPanelDispatchEntry kEmptyEntry = {}; + if (const UIEditorHostedPanelDispatchEntry* entry = + FindUIEditorHostedPanelDispatchEntry(dispatchFrame, panelId); + entry != nullptr) { + return *entry; + } + + return kEmptyEntry; } bool ShouldHostedContentYieldPointerStream( @@ -135,23 +130,18 @@ void EditorShellHostedPanelCoordinator::Update( context.inputEvents, shellOwnsHostedContentPointerStream); - const bool allowHostedInteraction = !context.shellFrame.result.workspaceInputSuppressed; - const PanelInputContext hierarchyInputContext = BuildHostedPanelInputContext( - context.shellFrame.workspaceInteractionFrame, - allowHostedInteraction, - kHierarchyPanelId); - const PanelInputContext projectInputContext = BuildHostedPanelInputContext( - context.shellFrame.workspaceInteractionFrame, - allowHostedInteraction, - kProjectPanelId); - const PanelInputContext inspectorInputContext = BuildHostedPanelInputContext( - context.shellFrame.workspaceInteractionFrame, - allowHostedInteraction, - kInspectorPanelId); - const PanelInputContext colorPickerInputContext = BuildHostedPanelInputContext( - context.shellFrame.workspaceInteractionFrame, - allowHostedInteraction, - kColorPickerPanelId); + const UIEditorHostedPanelDispatchFrame& hostedPanelDispatchFrame = + context.shellFrame.hostedPanelDispatchFrame; + const UIEditorHostedPanelDispatchEntry& hierarchyDispatchEntry = + ResolveHostedPanelDispatchEntry(hostedPanelDispatchFrame, kHierarchyPanelId); + const UIEditorHostedPanelDispatchEntry& projectDispatchEntry = + ResolveHostedPanelDispatchEntry(hostedPanelDispatchFrame, kProjectPanelId); + const UIEditorHostedPanelDispatchEntry& inspectorDispatchEntry = + ResolveHostedPanelDispatchEntry(hostedPanelDispatchFrame, kInspectorPanelId); + const UIEditorHostedPanelDispatchEntry& colorPickerDispatchEntry = + ResolveHostedPanelDispatchEntry(hostedPanelDispatchFrame, kColorPickerPanelId); + const UIEditorHostedPanelDispatchEntry& consoleDispatchEntry = + ResolveHostedPanelDispatchEntry(hostedPanelDispatchFrame, kConsolePanelId); context.sceneViewportFeature.Update( context.context.GetSceneRuntime(), @@ -162,27 +152,23 @@ void EditorShellHostedPanelCoordinator::Update( context.projectPanel.SetSystemInteractionHost(context.context.GetSystemInteractionHost()); context.inspectorPanel.SetCommandFocusService(&context.context.GetCommandFocusService()); context.hierarchyPanel.Update( - context.shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, - hostedContentEvents, - hierarchyInputContext); + hierarchyDispatchEntry, + hostedContentEvents); context.projectPanel.Update( - context.shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, - hostedContentEvents, - projectInputContext); + projectDispatchEntry, + hostedContentEvents); context.colorPickerPanel.Update( context.context, - context.shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, - hostedContentEvents, - colorPickerInputContext); + colorPickerDispatchEntry, + hostedContentEvents); context.inspectorPanel.Update( context.context, - context.shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, - hostedContentEvents, - inspectorInputContext); + inspectorDispatchEntry, + hostedContentEvents); context.context.SyncSessionFromCommandFocusService(); context.consolePanel.Update( context.context.GetSession(), - context.shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); + consoleDispatchEntry); } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Composition/EditorShellInteractionEngine.cpp b/new_editor/app/Composition/EditorShellInteractionEngine.cpp index 82c61795..e74805bd 100644 --- a/new_editor/app/Composition/EditorShellInteractionEngine.cpp +++ b/new_editor/app/Composition/EditorShellInteractionEngine.cpp @@ -3,6 +3,7 @@ #include "Composition/EditorPanelIds.h" #include +#include #include @@ -79,67 +80,19 @@ void ApplyViewportFramesToShellFrame( applyToViewportFrames(shellFrame.workspaceInteractionFrame.composeFrame.viewportFrames); } -const UIEditorPanelDescriptor* ResolveSingleVisibleToolWindowPanelDescriptor( - const UIEditorWorkspaceController& workspaceController) { - const UIEditorWorkspaceNode& root = workspaceController.GetWorkspace().root; - if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { - return nullptr; - } - - const UIEditorWorkspaceSession& session = workspaceController.GetSession(); - const UIEditorPanelRegistry& panelRegistry = workspaceController.GetPanelRegistry(); - const UIEditorPanelDescriptor* visibleDescriptor = nullptr; - std::size_t visibleCount = 0u; - for (const UIEditorWorkspaceNode& child : root.children) { - if (child.kind != UIEditorWorkspaceNodeKind::Panel) { - continue; - } - - const UIEditorPanelSessionState* panelState = - FindUIEditorPanelSessionState(session, child.panel.panelId); - if (panelState == nullptr || !panelState->open || !panelState->visible) { - continue; - } - - const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor(panelRegistry, child.panel.panelId); - if (descriptor == nullptr) { - return nullptr; - } - - ++visibleCount; - visibleDescriptor = descriptor; - if (visibleCount > 1u) { - return nullptr; - } - } - - return visibleDescriptor != nullptr && visibleDescriptor->toolWindow - ? visibleDescriptor - : nullptr; -} - UIEditorShellInteractionMetrics ResolveInteractionMetrics( const UIEditorWorkspaceController& workspaceController, bool useDetachedTitleBarTabStrip, float detachedTitleBarTabHeight, float detachedWindowChromeHeight) { UIEditorShellInteractionMetrics metrics = ResolveUIEditorShellInteractionMetrics(); - if (const UIEditorPanelDescriptor* toolWindowDescriptor = - ResolveSingleVisibleToolWindowPanelDescriptor(workspaceController); - toolWindowDescriptor != nullptr) { + if (IsUIEditorDetachedWorkspaceToolWindow(workspaceController)) { metrics.shellMetrics.dockHostMetrics.tabStripMetrics.layoutMetrics.headerHeight = 0.0f; - const float minimumContentWidth = - toolWindowDescriptor->minimumDetachedWindowSize.width > 0.0f - ? toolWindowDescriptor->minimumDetachedWindowSize.width - : metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize.width; - const float minimumContentHeight = - toolWindowDescriptor->minimumDetachedWindowSize.height > detachedWindowChromeHeight - ? toolWindowDescriptor->minimumDetachedWindowSize.height - - detachedWindowChromeHeight - : metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize.height; metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize = - ::XCEngine::UI::UISize(minimumContentWidth, minimumContentHeight); + ResolveUIEditorDetachedToolWindowMinimumContentSize( + workspaceController, + metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize, + detachedWindowChromeHeight); metrics.shellMetrics.dockHostMetrics.minimumTabContentBodySize = metrics.shellMetrics.dockHostMetrics.minimumStandalonePanelBodySize; } diff --git a/new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp b/new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp index 0898c6ac..c767d23d 100644 --- a/new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp +++ b/new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp @@ -1,7 +1,6 @@ #include "ColorPickerPanel.h" #include "Composition/EditorContext.h" -#include "Composition/EditorPanelIds.h" #include "State/EditorColorPickerToolState.h" #include @@ -70,17 +69,6 @@ UIRect BuildColorPickerEditorRect( } // namespace -const UIEditorPanelContentHostPanelState* ColorPickerPanel::FindMountedColorPickerPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const { - for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { - if (panelState.panelId == kColorPickerPanelId && panelState.mounted) { - return &panelState; - } - } - - return nullptr; -} - void ColorPickerPanel::ResetPanelState() { m_visible = false; m_hasActiveTarget = false; @@ -101,18 +89,15 @@ void ColorPickerPanel::ResetInteractionState() { void ColorPickerPanel::Update( EditorContext& context, - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - const PanelInputContext& inputContext) { - const UIEditorPanelContentHostPanelState* panelState = - FindMountedColorPickerPanel(contentHostFrame); - if (panelState == nullptr) { + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector& inputEvents) { + if (!dispatchEntry.mounted) { ResetPanelState(); return; } m_visible = true; - m_bounds = panelState->bounds; + m_bounds = dispatchEntry.bounds; m_metrics = BuildColorPickerPanelMetrics(m_bounds); m_palette = BuildColorPickerPanelPalette(); @@ -151,18 +136,18 @@ void ColorPickerPanel::Update( m_bounds, inputEvents, UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = inputContext.allowInteraction, + .allowPointerInBounds = dispatchEntry.allowInteraction, .allowPointerWhileCaptured = false, - .allowKeyboardInput = inputContext.hasInputFocus, + .allowKeyboardInput = dispatchEntry.focused, .allowFocusEvents = - inputContext.hasInputFocus || - inputContext.focusGained || - inputContext.focusLost, + dispatchEntry.focused || + dispatchEntry.focusGained || + dispatchEntry.focusLost, .includePointerLeave = - inputContext.allowInteraction || inputContext.hasInputFocus + dispatchEntry.allowInteraction || dispatchEntry.focused }, - inputContext.focusGained, - inputContext.focusLost); + dispatchEntry.focusGained, + dispatchEntry.focusLost); const UIRect editorRect = BuildColorPickerEditorRect(m_bounds, m_metrics); m_frame = UpdateUIEditorColorFieldInteraction( diff --git a/new_editor/app/Features/ColorPicker/ColorPickerPanel.h b/new_editor/app/Features/ColorPicker/ColorPickerPanel.h index 1f5229e4..acc42868 100644 --- a/new_editor/app/Features/ColorPicker/ColorPickerPanel.h +++ b/new_editor/app/Features/ColorPicker/ColorPickerPanel.h @@ -1,9 +1,7 @@ #pragma once -#include "Features/PanelInputContext.h" - #include -#include +#include #include @@ -19,14 +17,11 @@ public: void ResetInteractionState(); void Update( EditorContext& context, - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const PanelInputContext& inputContext); + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); void Append(::XCEngine::UI::UIDrawList& drawList) const; private: - const UIEditorPanelContentHostPanelState* FindMountedColorPickerPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const; void ResetPanelState(); bool m_visible = false; diff --git a/new_editor/app/Features/Console/ConsolePanel.cpp b/new_editor/app/Features/Console/ConsolePanel.cpp index f4f6d788..f4a1cb13 100644 --- a/new_editor/app/Features/Console/ConsolePanel.cpp +++ b/new_editor/app/Features/Console/ConsolePanel.cpp @@ -1,6 +1,5 @@ #include "ConsolePanel.h" -#include "Composition/EditorPanelIds.h" #include #include @@ -31,23 +30,10 @@ float ResolveTextTop(float rectY, float rectHeight, float fontSize) { } // namespace -const UIEditorPanelContentHostPanelState* ConsolePanel::FindMountedConsolePanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const { - for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { - if (panelState.panelId == kConsolePanelId && panelState.mounted) { - return &panelState; - } - } - - return nullptr; -} - void ConsolePanel::Update( const EditorSession& session, - const UIEditorPanelContentHostFrame& contentHostFrame) { - const UIEditorPanelContentHostPanelState* panelState = - FindMountedConsolePanel(contentHostFrame); - if (panelState == nullptr) { + const UIEditorHostedPanelDispatchEntry& dispatchEntry) { + if (!dispatchEntry.mounted) { m_visible = false; m_bounds = {}; m_entries = nullptr; @@ -55,7 +41,7 @@ void ConsolePanel::Update( } m_visible = true; - m_bounds = panelState->bounds; + m_bounds = dispatchEntry.bounds; m_entries = &session.consoleEntries; } diff --git a/new_editor/app/Features/Console/ConsolePanel.h b/new_editor/app/Features/Console/ConsolePanel.h index f8216370..ce540e89 100644 --- a/new_editor/app/Features/Console/ConsolePanel.h +++ b/new_editor/app/Features/Console/ConsolePanel.h @@ -2,7 +2,7 @@ #include "State/EditorSession.h" -#include +#include #include @@ -12,13 +12,9 @@ class ConsolePanel { public: void Update( const EditorSession& session, - const UIEditorPanelContentHostFrame& contentHostFrame); + const UIEditorHostedPanelDispatchEntry& dispatchEntry); void Append(::XCEngine::UI::UIDrawList& drawList) const; -private: - const UIEditorPanelContentHostPanelState* FindMountedConsolePanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const; - bool m_visible = false; ::XCEngine::UI::UIRect m_bounds = {}; const std::vector* m_entries = nullptr; diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index c765a1e5..0371113e 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -1,6 +1,5 @@ #include "HierarchyPanel.h" #include "Rendering/Assets/BuiltInIcons.h" -#include "Composition/EditorPanelIds.h" #include #include "Scene/EditorSceneRuntime.h" #include "State/EditorCommandFocusService.h" @@ -98,17 +97,6 @@ void HierarchyPanel::ResetInteractionState() { ResetTransientState(); } -const UIEditorPanelContentHostPanelState* HierarchyPanel::FindMountedHierarchyPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const { - for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { - if (panelState.panelId == kHierarchyPanelId && panelState.mounted) { - return &panelState; - } - } - - return nullptr; -} - void HierarchyPanel::ResetTransientState() { m_frameEvents.clear(); TreeDrag::ResetTransientRequests(m_dragState); @@ -497,17 +485,17 @@ UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand( void HierarchyPanel::ProcessDragAndFrameEvents( const std::vector& inputEvents, const UIRect& bounds, - const PanelInputContext& inputContext) { + const UIEditorHostedPanelDispatchEntry& dispatchEntry) { const std::vector filteredEvents = BuildUIEditorTreePanelInputEvents( bounds, inputEvents, UIEditorTreePanelInputFilterOptions{ - .allowInteraction = inputContext.allowInteraction, - .hasInputFocus = inputContext.hasInputFocus, + .allowInteraction = dispatchEntry.allowInteraction, + .hasInputFocus = dispatchEntry.focused, .captureActive = HasActivePointerCapture() }, - inputContext.focusGained, - inputContext.focusLost); + dispatchEntry.focusGained, + dispatchEntry.focusLost); if (m_treeFrame.result.selectionChanged) { SyncSceneRuntimeSelectionFromTree(); @@ -579,14 +567,11 @@ void HierarchyPanel::ProcessDragAndFrameEvents( } void HierarchyPanel::Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - const PanelInputContext& inputContext) { + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector& inputEvents) { ResetTransientState(); - const UIEditorPanelContentHostPanelState* panelState = - FindMountedHierarchyPanel(contentHostFrame); - if (panelState == nullptr) { + if (!dispatchEntry.mounted) { m_visible = false; m_treeFrame = {}; m_dragState = {}; @@ -604,23 +589,26 @@ void HierarchyPanel::Update( m_visible = true; const std::vector filteredEvents = BuildUIEditorTreePanelInputEvents( - panelState->bounds, + dispatchEntry.bounds, inputEvents, UIEditorTreePanelInputFilterOptions{ - .allowInteraction = inputContext.allowInteraction, - .hasInputFocus = inputContext.hasInputFocus, + .allowInteraction = dispatchEntry.allowInteraction, + .hasInputFocus = dispatchEntry.focused, .captureActive = HasActivePointerCapture() }, - inputContext.focusGained, - inputContext.focusLost); + dispatchEntry.focusGained, + dispatchEntry.focusLost); SyncTreeFocusState(filteredEvents); - ClaimCommandFocus(filteredEvents, panelState->bounds, inputContext.allowInteraction); + ClaimCommandFocus( + filteredEvents, + dispatchEntry.bounds, + dispatchEntry.allowInteraction); const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics(); const Widgets::UIEditorTreeViewLayout layout = Widgets::BuildUIEditorTreeViewLayout( - panelState->bounds, + dispatchEntry.bounds, m_treeItems, m_expansion, treeMetrics, @@ -646,7 +634,7 @@ void HierarchyPanel::Update( m_treeInteractionState, m_treeSelection, m_expansion, - panelState->bounds, + dispatchEntry.bounds, m_treeItems, interactionEvents, treeMetrics); @@ -660,8 +648,8 @@ void HierarchyPanel::Update( ProcessDragAndFrameEvents( inputEvents, - panelState->bounds, - inputContext); + dispatchEntry.bounds, + dispatchEntry); } void HierarchyPanel::Append(UIDrawList& drawList) const { diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.h b/new_editor/app/Features/Hierarchy/HierarchyPanel.h index 1ec4c3b3..4ac86c15 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.h @@ -3,11 +3,10 @@ #include "HierarchyModel.h" #include "Commands/EditorEditCommandRoute.h" -#include "Features/PanelInputContext.h" #include #include #include -#include +#include #include #include @@ -46,9 +45,8 @@ public: void SetBuiltInIcons(const BuiltInIcons* icons); void ResetInteractionState(); void Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const PanelInputContext& inputContext); + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); void Append(::XCEngine::UI::UIDrawList& drawList) const; bool WantsHostPointerCapture() const; bool WantsHostPointerRelease() const; @@ -60,15 +58,13 @@ public: std::string_view commandId) override; private: - const UIEditorPanelContentHostPanelState* FindMountedHierarchyPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const; void ResetTransientState(); void SyncModelFromScene(); void RebuildItems(); void ProcessDragAndFrameEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const ::XCEngine::UI::UIRect& bounds, - const PanelInputContext& inputContext); + const UIEditorHostedPanelDispatchEntry& dispatchEntry); const HierarchyNode* GetSelectedNode() const; void SyncTreeSelectionFromSceneRuntime(); void SyncSceneRuntimeSelectionFromTree(); diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index a5950200..4c281c1e 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -1,7 +1,6 @@ #include "InspectorPanel.h" #include "Composition/EditorContext.h" -#include "Composition/EditorPanelIds.h" #include "State/EditorColorPickerToolState.h" #include #include @@ -89,17 +88,6 @@ UIEditorHostCommandDispatchResult BuildDispatchResult( } // namespace -const UIEditorPanelContentHostPanelState* InspectorPanel::FindMountedInspectorPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const { - for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { - if (panelState.panelId == kInspectorPanelId && panelState.mounted) { - return &panelState; - } - } - - return nullptr; -} - void InspectorPanel::SetCommandFocusService( EditorCommandFocusService* commandFocusService) { m_commandFocusService = commandFocusService; @@ -493,18 +481,15 @@ bool InspectorPanel::ApplyChangedField(std::string_view fieldId) { void InspectorPanel::Update( EditorContext& context, - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - const PanelInputContext& inputContext) { - const UIEditorPanelContentHostPanelState* panelState = - FindMountedInspectorPanel(contentHostFrame); - if (panelState == nullptr) { + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector& inputEvents) { + if (!dispatchEntry.mounted) { ResetPanelState(); return; } m_visible = true; - m_bounds = panelState->bounds; + m_bounds = dispatchEntry.bounds; m_sceneRuntime = &context.GetSceneRuntime(); m_subject = BuildInspectorSubject(context.GetSession(), context.GetSceneRuntime()); @@ -528,19 +513,19 @@ void InspectorPanel::Update( m_bounds, inputEvents, UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = inputContext.allowInteraction, + .allowPointerInBounds = dispatchEntry.allowInteraction, .allowPointerWhileCaptured = false, - .allowKeyboardInput = inputContext.hasInputFocus, + .allowKeyboardInput = dispatchEntry.focused, .allowFocusEvents = - inputContext.hasInputFocus || - inputContext.focusGained || - inputContext.focusLost, + dispatchEntry.focused || + dispatchEntry.focusGained || + dispatchEntry.focusLost, .includePointerLeave = - inputContext.allowInteraction || inputContext.hasInputFocus + dispatchEntry.allowInteraction || dispatchEntry.focused }, - inputContext.focusGained, - inputContext.focusLost); - ClaimCommandFocus(filteredEvents, inputContext.allowInteraction); + dispatchEntry.focusGained, + dispatchEntry.focusLost); + ClaimCommandFocus(filteredEvents, dispatchEntry.allowInteraction); m_gridFrame = UpdateUIEditorPropertyGridInteraction( m_interactionState, m_fieldSelection, diff --git a/new_editor/app/Features/Inspector/InspectorPanel.h b/new_editor/app/Features/Inspector/InspectorPanel.h index 644c71aa..9cda1008 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.h +++ b/new_editor/app/Features/Inspector/InspectorPanel.h @@ -4,9 +4,8 @@ #include "Features/Inspector/InspectorSubject.h" #include "Commands/EditorEditCommandRoute.h" -#include "Features/PanelInputContext.h" #include -#include +#include #include #include @@ -29,9 +28,8 @@ public: void SetCommandFocusService(EditorCommandFocusService* commandFocusService); void Update( EditorContext& context, - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const PanelInputContext& inputContext); + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); void Append(::XCEngine::UI::UIDrawList& drawList) const; UIEditorHostCommandEvaluationResult EvaluateEditCommand( @@ -40,8 +38,6 @@ public: std::string_view commandId) override; private: - const UIEditorPanelContentHostPanelState* FindMountedInspectorPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const; void ResetPanelState(); void ResetInteractionState(); void SyncExpansionState(bool subjectChanged); diff --git a/new_editor/app/Features/PanelInputContext.h b/new_editor/app/Features/PanelInputContext.h deleted file mode 100644 index f080b2c9..00000000 --- a/new_editor/app/Features/PanelInputContext.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -namespace XCEngine::UI::Editor::App { - -struct PanelInputContext { - bool allowInteraction = false; - bool hasInputFocus = false; - bool focusGained = false; - bool focusLost = false; -}; - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index 40124b0d..be42100a 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -1,6 +1,5 @@ #include "ProjectPanel.h" #include "Rendering/Assets/BuiltInIcons.h" -#include "Composition/EditorPanelIds.h" #include #include #include @@ -668,17 +667,6 @@ std::optional ProjectPanel::ResolveEditCommandT forceCurrentFolder); } -const UIEditorPanelContentHostPanelState* ProjectPanel::FindMountedProjectPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const { - for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { - if (panelState.panelId == kProjectPanelId && panelState.mounted) { - return &panelState; - } - } - - return nullptr; -} - void ProjectPanel::SyncCurrentFolderSelection() { if (!HasProjectRuntime()) { m_windowTreeItems.clear(); @@ -1088,25 +1076,25 @@ void ProjectPanel::EmitSelectionClearedEvent(EventSource source) { std::vector ProjectPanel::BuildTreeInteractionInputEvents( const std::vector& inputEvents, const UIRect& bounds, - const PanelInputContext& inputContext) const { + const UIEditorHostedPanelDispatchEntry& dispatchEntry) const { const std::vector rawEvents = BuildUIEditorPanelInputEvents( bounds, inputEvents, UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = inputContext.allowInteraction, + .allowPointerInBounds = dispatchEntry.allowInteraction, .allowPointerWhileCaptured = HasActivePointerCapture(), - .allowKeyboardInput = inputContext.hasInputFocus, + .allowKeyboardInput = dispatchEntry.focused, .allowFocusEvents = - inputContext.hasInputFocus || + dispatchEntry.focused || HasActivePointerCapture() || - inputContext.focusGained || - inputContext.focusLost, + dispatchEntry.focusGained || + dispatchEntry.focusLost, .includePointerLeave = - inputContext.allowInteraction || HasActivePointerCapture() + dispatchEntry.allowInteraction || HasActivePointerCapture() }, - inputContext.focusGained, - inputContext.focusLost); + dispatchEntry.focusGained, + dispatchEntry.focusLost); const Widgets::UIEditorTreeViewLayout layout = m_treeFrame.layout.bounds.width > 0.0f @@ -1490,18 +1478,15 @@ void ProjectPanel::ClaimCommandFocus( } void ProjectPanel::Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - const PanelInputContext& inputContext) { + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector& inputEvents) { m_requestPointerCapture = false; m_requestPointerRelease = false; m_frameEvents.clear(); GridDrag::ResetTransientRequests(m_assetDragState); TreeDrag::ResetTransientRequests(m_treeDragState); - const UIEditorPanelContentHostPanelState* panelState = - FindMountedProjectPanel(contentHostFrame); - if (panelState == nullptr) { + if (!dispatchEntry.mounted) { if (m_splitterDragging || m_assetDragState.dragging || m_treeDragState.dragging || @@ -1536,26 +1521,29 @@ void ProjectPanel::Update( SyncAssetSelectionFromRuntime(); const std::vector filteredEvents = BuildUIEditorPanelInputEvents( - panelState->bounds, + dispatchEntry.bounds, inputEvents, UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = inputContext.allowInteraction, + .allowPointerInBounds = dispatchEntry.allowInteraction, .allowPointerWhileCaptured = HasActivePointerCapture(), - .allowKeyboardInput = inputContext.hasInputFocus, + .allowKeyboardInput = dispatchEntry.focused, .allowFocusEvents = - inputContext.hasInputFocus || + dispatchEntry.focused || HasActivePointerCapture() || - inputContext.focusGained || - inputContext.focusLost, + dispatchEntry.focusGained || + dispatchEntry.focusLost, .includePointerLeave = - inputContext.allowInteraction || HasActivePointerCapture() + dispatchEntry.allowInteraction || HasActivePointerCapture() }, - inputContext.focusGained, - inputContext.focusLost); - ClaimCommandFocus(filteredEvents, panelState->bounds, inputContext.allowInteraction); + dispatchEntry.focusGained, + dispatchEntry.focusLost); + ClaimCommandFocus( + filteredEvents, + dispatchEntry.bounds, + dispatchEntry.allowInteraction); - m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); - m_layout = BuildLayout(panelState->bounds); + m_navigationWidth = ClampNavigationWidth(m_navigationWidth, dispatchEntry.bounds.width); + m_layout = BuildLayout(dispatchEntry.bounds); if (m_contextMenu.open) { RebuildContextMenu(); } @@ -1583,8 +1571,8 @@ void ProjectPanel::Update( const std::vector treeEvents = BuildTreeInteractionInputEvents( inputEvents, - panelState->bounds, - inputContext); + dispatchEntry.bounds, + dispatchEntry); m_treeFrame = UpdateUIEditorTreeViewInteraction( m_treeInteractionState, m_folderSelection, @@ -1599,7 +1587,7 @@ void ProjectPanel::Update( m_treeFrame.result.selectedItemId != GetBrowserModel().GetCurrentFolderId()) { CloseContextMenu(); NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); - m_layout = BuildLayout(panelState->bounds); + m_layout = BuildLayout(dispatchEntry.bounds); } if (m_treeFrame.result.renameRequested && !m_treeFrame.result.renameItemId.empty()) { @@ -1685,7 +1673,7 @@ void ProjectPanel::Update( EmitSelectionClearedEvent(EventSource::Tree); } SyncCurrentFolderSelection(); - m_layout = BuildLayout(panelState->bounds); + m_layout = BuildLayout(dispatchEntry.bounds); m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( m_layout.treeRect, GetWindowTreeItems(), @@ -1808,7 +1796,7 @@ void ProjectPanel::Update( } } - m_layout = BuildLayout(panelState->bounds); + m_layout = BuildLayout(dispatchEntry.bounds); m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( m_layout.treeRect, GetWindowTreeItems(), @@ -1859,8 +1847,10 @@ void ProjectPanel::Update( case UIInputEventType::PointerMove: { if (m_splitterDragging) { m_navigationWidth = - ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width); - m_layout = BuildLayout(panelState->bounds); + ClampNavigationWidth( + event.position.x - dispatchEntry.bounds.x, + dispatchEntry.bounds.width); + m_layout = BuildLayout(dispatchEntry.bounds); } m_splitterHovered = @@ -1981,7 +1971,7 @@ void ProjectPanel::Update( m_layout.breadcrumbItems[releasedBreadcrumbIndex]; if (item.clickable) { NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); - m_layout = BuildLayout(panelState->bounds); + m_layout = BuildLayout(dispatchEntry.bounds); } } m_pressedBreadcrumbIndex = kInvalidLayoutIndex; diff --git a/new_editor/app/Features/Project/ProjectPanel.h b/new_editor/app/Features/Project/ProjectPanel.h index 52d8d2c5..658a0fda 100644 --- a/new_editor/app/Features/Project/ProjectPanel.h +++ b/new_editor/app/Features/Project/ProjectPanel.h @@ -2,7 +2,6 @@ #include "Project/EditorProjectRuntime.h" #include "ProjectBrowserModel.h" -#include "Features/PanelInputContext.h" #include "Commands/EditorEditCommandRoute.h" #include @@ -11,7 +10,7 @@ #include #include #include -#include +#include #include #include @@ -89,9 +88,8 @@ public: void SetTextMeasurer(const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer); void ResetInteractionState(); void Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const PanelInputContext& inputContext); + const UIEditorHostedPanelDispatchEntry& dispatchEntry, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); void Append(::XCEngine::UI::UIDrawList& drawList) const; CursorKind GetCursorKind() const; @@ -180,8 +178,6 @@ private: std::optional ResolveEditCommandTarget( std::string_view explicitItemId = {}, bool forceCurrentFolder = false) const; - const UIEditorPanelContentHostPanelState* FindMountedProjectPanel( - const UIEditorPanelContentHostFrame& contentHostFrame) const; Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds) const; std::size_t HitTestBreadcrumbItem(const ::XCEngine::UI::UIPoint& point) const; std::size_t HitTestAssetTile(const ::XCEngine::UI::UIPoint& point) const; @@ -220,7 +216,7 @@ private: std::vector<::XCEngine::UI::UIInputEvent> BuildTreeInteractionInputEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const ::XCEngine::UI::UIRect& bounds, - const PanelInputContext& inputContext) const; + const UIEditorHostedPanelDispatchEntry& dispatchEntry) const; void ClaimCommandFocus( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const ::XCEngine::UI::UIRect& bounds, diff --git a/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp b/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp index c634bcc4..bde13386 100644 --- a/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp @@ -6,7 +6,7 @@ #include "Platform/Win32/EditorWindowSupport.h" #include -#include +#include #include #include @@ -32,86 +32,6 @@ constexpr float kTitleBarLogoInsetLeft = 8.0f; constexpr float kTitleBarLogoTextGap = 8.0f; constexpr float kTitleBarFrameStatsInsetRight = 12.0f; -bool IsRootPanelVisible( - const UIEditorWorkspaceController& controller, - std::string_view panelId) { - const UIEditorPanelSessionState* panelState = - FindUIEditorPanelSessionState(controller.GetSession(), panelId); - return panelState != nullptr && panelState->open && panelState->visible; -} - -std::string ResolveDetachedTitleTabText(const EditorWindow& window) { - const auto& workspaceController = window.GetWorkspaceController(); - const std::string_view activePanelId = workspaceController.GetWorkspace().activePanelId; - if (!activePanelId.empty()) { - if (const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor( - workspaceController.GetPanelRegistry(), - activePanelId); - descriptor != nullptr && - !descriptor->defaultTitle.empty()) { - return descriptor->defaultTitle; - } - } - - return std::string("Panel"); -} - -const UIEditorPanelDescriptor* ResolveSingleVisibleRootPanelDescriptor( - const EditorWindow& window) { - const UIEditorWorkspaceController& workspaceController = window.GetWorkspaceController(); - const UIEditorWorkspaceNode& root = workspaceController.GetWorkspace().root; - if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { - return nullptr; - } - - const UIEditorPanelRegistry& panelRegistry = workspaceController.GetPanelRegistry(); - const UIEditorPanelDescriptor* visibleDescriptor = nullptr; - std::size_t visibleCount = 0u; - for (const UIEditorWorkspaceNode& child : root.children) { - if (child.kind != UIEditorWorkspaceNodeKind::Panel || - !IsRootPanelVisible(workspaceController, child.panel.panelId)) { - continue; - } - - const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor(panelRegistry, child.panel.panelId); - if (descriptor == nullptr) { - return nullptr; - } - - ++visibleCount; - visibleDescriptor = descriptor; - if (visibleCount > 1u) { - return nullptr; - } - } - - return visibleCount == 1u ? visibleDescriptor : nullptr; -} - -bool IsToolWindow(const EditorWindow& window) { - if (window.IsPrimary()) { - return false; - } - - const UIEditorPanelDescriptor* descriptor = - ResolveSingleVisibleRootPanelDescriptor(window); - return descriptor != nullptr && descriptor->toolWindow; -} - -::XCEngine::UI::UISize ResolveMinimumOuterSize(const EditorWindow& window) { - if (const UIEditorPanelDescriptor* descriptor = - ResolveSingleVisibleRootPanelDescriptor(window); - descriptor != nullptr && - descriptor->minimumDetachedWindowSize.width > 0.0f && - descriptor->minimumDetachedWindowSize.height > 0.0f) { - return descriptor->minimumDetachedWindowSize; - } - - return ::XCEngine::UI::UISize(640.0f, 360.0f); -} - float ResolveDetachedTabWidth( std::string_view text, const UIEditorTextMeasurer* textMeasurer) { @@ -126,28 +46,6 @@ float ResolveDetachedTabWidth( return MeasureUITabStripHeaderWidth(measuredLabelWidth, metrics.layoutMetrics); } -bool HasSingleVisibleRootTab(const UIEditorWorkspaceController& controller) { - const UIEditorWorkspaceNode& root = controller.GetWorkspace().root; - if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { - return false; - } - - std::size_t visiblePanelCount = 0u; - for (const UIEditorWorkspaceNode& child : root.children) { - if (child.kind != UIEditorWorkspaceNodeKind::Panel || - !IsRootPanelVisible(controller, child.panel.panelId)) { - continue; - } - - ++visiblePanelCount; - if (visiblePanelCount > 1u) { - return false; - } - } - - return visiblePanelCount == 1u; -} - UIRect BuildDetachedTitleLogoRect(const Host::BorderlessWindowChromeLayout& layout) { const float availableLeft = layout.titleBarRect.x; const float availableRight = layout.minimizeButtonRect.x; @@ -332,7 +230,8 @@ bool EditorWindowChromeController::HandleSystemCommand( bool EditorWindowChromeController::HandleGetMinMaxInfo( const EditorWindow& window, LPARAM lParam) const { - const ::XCEngine::UI::UISize minimumOuterSize = ResolveMinimumOuterSize(window); + const ::XCEngine::UI::UISize minimumOuterSize = + ResolveUIEditorDetachedWorkspaceMinimumOuterSize(window.GetWorkspaceController()); return Host::HandleBorderlessWindowGetMinMaxInfo( window.m_state->window.hwnd, lParam, @@ -412,7 +311,8 @@ bool EditorWindowChromeController::HandleResizePointerMove( return false; } - const ::XCEngine::UI::UISize minimumOuterSize = ResolveMinimumOuterSize(window); + const ::XCEngine::UI::UISize minimumOuterSize = + ResolveUIEditorDetachedWorkspaceMinimumOuterSize(window.GetWorkspaceController()); RECT targetRect = Host::ComputeBorderlessWindowResizeRect( GetBorderlessResizeInitialWindowRect(), GetBorderlessResizeInitialScreenPoint(), @@ -718,7 +618,9 @@ Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLa float leadingOccupiedRight = 0.0f; if (ShouldUseDetachedTitleBarTabStrip(window)) { leadingOccupiedRight = ResolveDetachedTabWidth( - ResolveDetachedTitleTabText(window), + ResolveUIEditorDetachedWorkspaceTitle( + window.GetWorkspaceController(), + "Panel"), &window.m_runtime->GetTextMeasurer()); } @@ -730,8 +632,8 @@ Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLa bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip( const EditorWindow& window) const { return !window.m_state->window.primary && - !IsToolWindow(window) && - HasSingleVisibleRootTab(window.m_runtime->GetWorkspaceController()); + !IsUIEditorDetachedWorkspaceToolWindow(window.m_runtime->GetWorkspaceController()) && + HasUIEditorSingleVisibleRootTab(window.m_runtime->GetWorkspaceController()); } void EditorWindowChromeController::AppendChrome( @@ -787,8 +689,10 @@ void EditorWindowChromeController::AppendChrome( 0.0f, (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), - IsToolWindow(window) - ? ResolveDetachedTitleTabText(window) + IsUIEditorDetachedWorkspaceToolWindow(window.GetWorkspaceController()) + ? ResolveUIEditorDetachedWorkspaceTitle( + window.GetWorkspaceController(), + "Panel") : (window.m_state->window.titleText.empty() ? std::string("XCEngine Editor") : window.m_state->window.titleText), diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp index 80697542..7dd1d91e 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp @@ -6,6 +6,7 @@ #include "Platform/Win32/EditorWindowRuntimeController.h" #include "Platform/Win32/EditorWindowSupport.h" +#include #include #include @@ -43,22 +44,6 @@ std::string DescribeInputEventType(const UIInputEvent& event) { } } -::XCEngine::UI::UISize ResolveDetachedPanelPreferredSize( - const EditorContext& editorContext, - std::string_view panelId, - float fallbackWidth, - float fallbackHeight) { - const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor(editorContext.GetShellAsset().panelRegistry, panelId); - if (descriptor == nullptr || - descriptor->preferredDetachedWindowSize.width <= 0.0f || - descriptor->preferredDetachedWindowSize.height <= 0.0f) { - return ::XCEngine::UI::UISize(fallbackWidth, fallbackHeight); - } - - return descriptor->preferredDetachedWindowSize; -} - } // namespace EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend( @@ -104,11 +89,10 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend editorContext.GetColorPickerToolState()) && GetCursorPos(&screenPoint) != FALSE) { const ::XCEngine::UI::UISize preferredSize = - ResolveDetachedPanelPreferredSize( - editorContext, + ResolveUIEditorDetachedPanelPreferredWindowSize( + editorContext.GetShellAsset().panelRegistry, kColorPickerPanelId, - 360.0f, - 520.0f); + ::XCEngine::UI::UISize(360.0f, 520.0f)); transferRequests.openDetachedPanel = EditorWindowOpenDetachedPanelRequest{ std::string(kColorPickerPanelId), screenPoint, diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp index 5c2b1e4e..9e7b32db 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp @@ -6,6 +6,7 @@ #include "Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h" #include +#include #include #include #include @@ -173,15 +174,11 @@ UIEditorWorkspaceController EditorWindowWorkspaceCoordinator::BuildWorkspaceCont std::wstring EditorWindowWorkspaceCoordinator::BuildWindowTitle( const UIEditorWorkspaceController& workspaceController) const { - const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; - if (const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor( - workspaceController.GetPanelRegistry(), - activePanelId); - descriptor != nullptr && - !descriptor->defaultTitle.empty()) { - const std::string titleText = descriptor->defaultTitle + " - XCEngine Editor"; - return std::wstring(titleText.begin(), titleText.end()); + const std::string titleText = + ResolveUIEditorDetachedWorkspaceTitle(workspaceController); + if (!titleText.empty()) { + const std::string decoratedTitle = titleText + " - XCEngine Editor"; + return std::wstring(decoratedTitle.begin(), decoratedTitle.end()); } return std::wstring(L"XCEngine Editor"); diff --git a/new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.h b/new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.h new file mode 100644 index 00000000..0ba16079 --- /dev/null +++ b/new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorHostedPanelDispatchEntry { + std::string panelId = {}; + UIEditorPanelPresentationKind presentationKind = + UIEditorPanelPresentationKind::Placeholder; + bool mounted = false; + ::XCEngine::UI::UIRect bounds = {}; + bool allowInteraction = false; + bool attached = false; + bool visible = false; + bool active = false; + bool focused = false; + bool focusGained = false; + bool focusLost = false; +}; + +struct UIEditorHostedPanelDispatchFrame { + std::vector entries = {}; +}; + +const UIEditorHostedPanelDispatchEntry* FindUIEditorHostedPanelDispatchEntry( + const UIEditorHostedPanelDispatchFrame& frame, + std::string_view panelId); + +UIEditorHostedPanelDispatchFrame BuildUIEditorHostedPanelDispatchFrame( + const UIEditorPanelContentHostFrame& contentHostFrame, + const UIEditorPanelHostLifecycleFrame& lifecycleFrame, + bool allowInteraction); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h b/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h index e529a6e9..487b8f88 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,7 @@ struct UIEditorShellInteractionFrame { UIEditorShellInteractionRequest request = {}; UIEditorShellComposeFrame shellFrame = {}; UIEditorWorkspaceInteractionFrame workspaceInteractionFrame = {}; + UIEditorHostedPanelDispatchFrame hostedPanelDispatchFrame = {}; std::vector popupFrames = {}; UIEditorShellInteractionResult result = {}; std::string openRootMenuId = {}; diff --git a/new_editor/include/XCEditor/Workspace/UIEditorDetachedWindowPolicy.h b/new_editor/include/XCEditor/Workspace/UIEditorDetachedWindowPolicy.h new file mode 100644 index 00000000..7bafd0b2 --- /dev/null +++ b/new_editor/include/XCEditor/Workspace/UIEditorDetachedWindowPolicy.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +const UIEditorPanelDescriptor* ResolveUIEditorSingleVisibleRootPanelDescriptor( + const UIEditorWorkspaceController& controller); + +bool HasUIEditorSingleVisibleRootTab( + const UIEditorWorkspaceController& controller); + +bool IsUIEditorDetachedWorkspaceToolWindow( + const UIEditorWorkspaceController& controller); + +std::string ResolveUIEditorDetachedWorkspaceTitle( + const UIEditorWorkspaceController& controller, + std::string_view fallbackTitle = {}); + +::XCEngine::UI::UISize ResolveUIEditorDetachedWorkspaceMinimumOuterSize( + const UIEditorWorkspaceController& controller, + const ::XCEngine::UI::UISize& fallbackSize = ::XCEngine::UI::UISize(640.0f, 360.0f)); + +::XCEngine::UI::UISize ResolveUIEditorDetachedToolWindowMinimumContentSize( + const UIEditorWorkspaceController& controller, + const ::XCEngine::UI::UISize& fallbackContentSize, + float detachedWindowChromeHeight = 0.0f); + +::XCEngine::UI::UISize ResolveUIEditorDetachedPanelPreferredWindowSize( + const UIEditorPanelRegistry& panelRegistry, + std::string_view panelId, + const ::XCEngine::UI::UISize& fallbackSize = {}); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h index 7c969327..979e857f 100644 --- a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -18,6 +19,7 @@ struct UIEditorWorkspaceInteractionModel { struct UIEditorWorkspaceInteractionState { UIEditorDockHostInteractionState dockHostInteractionState = {}; + UIEditorPanelHostLifecycleState panelHostLifecycleState = {}; UIEditorWorkspaceComposeState composeState = {}; UIEditorWorkspaceInputOwner inputOwner = {}; }; @@ -34,6 +36,7 @@ struct UIEditorWorkspaceInteractionResult { struct UIEditorWorkspaceInteractionFrame { UIEditorDockHostInteractionFrame dockHostFrame = {}; + UIEditorPanelHostLifecycleFrame panelHostLifecycleFrame = {}; UIEditorWorkspaceComposeFrame composeFrame = {}; UIEditorWorkspaceInteractionResult result = {}; UIEditorWorkspaceInputOwner previousInputOwner = {}; diff --git a/new_editor/src/Panels/UIEditorHostedPanelDispatch.cpp b/new_editor/src/Panels/UIEditorHostedPanelDispatch.cpp new file mode 100644 index 00000000..0fdfb989 --- /dev/null +++ b/new_editor/src/Panels/UIEditorHostedPanelDispatch.cpp @@ -0,0 +1,74 @@ +#include + +#include + +namespace XCEngine::UI::Editor { + +namespace { + +bool HasLifecycleEvent( + const UIEditorPanelHostLifecycleFrame& lifecycleFrame, + std::string_view panelId, + UIEditorPanelHostLifecycleEventKind kind) { + for (const UIEditorPanelHostLifecycleEvent& event : lifecycleFrame.events) { + if (event.panelId == panelId && event.kind == kind) { + return true; + } + } + + return false; +} + +} // namespace + +const UIEditorHostedPanelDispatchEntry* FindUIEditorHostedPanelDispatchEntry( + const UIEditorHostedPanelDispatchFrame& frame, + std::string_view panelId) { + for (const UIEditorHostedPanelDispatchEntry& entry : frame.entries) { + if (entry.panelId == panelId) { + return &entry; + } + } + + return nullptr; +} + +UIEditorHostedPanelDispatchFrame BuildUIEditorHostedPanelDispatchFrame( + const UIEditorPanelContentHostFrame& contentHostFrame, + const UIEditorPanelHostLifecycleFrame& lifecycleFrame, + bool allowInteraction) { + UIEditorHostedPanelDispatchFrame frame = {}; + frame.entries.reserve(contentHostFrame.panelStates.size()); + + for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { + UIEditorHostedPanelDispatchEntry entry = {}; + entry.panelId = panelState.panelId; + entry.presentationKind = panelState.kind; + entry.mounted = panelState.mounted; + entry.bounds = panelState.bounds; + entry.allowInteraction = allowInteraction; + + if (const UIEditorPanelHostState* hostState = + FindUIEditorPanelHostState(lifecycleFrame, panelState.panelId); + hostState != nullptr) { + entry.attached = hostState->attached; + entry.visible = hostState->visible; + entry.active = hostState->active; + entry.focused = hostState->focused; + } + + entry.focusGained = HasLifecycleEvent( + lifecycleFrame, + panelState.panelId, + UIEditorPanelHostLifecycleEventKind::FocusGained); + entry.focusLost = HasLifecycleEvent( + lifecycleFrame, + panelState.panelId, + UIEditorPanelHostLifecycleEventKind::FocusLost); + frame.entries.push_back(std::move(entry)); + } + + return frame; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Shell/UIEditorShellInteraction.cpp b/new_editor/src/Shell/UIEditorShellInteraction.cpp index 5b568f44..26a8c6fb 100644 --- a/new_editor/src/Shell/UIEditorShellInteraction.cpp +++ b/new_editor/src/Shell/UIEditorShellInteraction.cpp @@ -768,6 +768,10 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( frame.request = request; frame.shellFrame.layout = request.shellRequest.layout; frame.workspaceInteractionFrame = std::move(workspaceInteractionFrame); + frame.hostedPanelDispatchFrame = BuildUIEditorHostedPanelDispatchFrame( + frame.workspaceInteractionFrame.composeFrame.contentHostFrame, + frame.workspaceInteractionFrame.panelHostLifecycleFrame, + !menuModalDuringFrame); frame.popupFrames = Internal::BuildPopupFrames( frame.request, state, diff --git a/new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp b/new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp new file mode 100644 index 00000000..e9788eaa --- /dev/null +++ b/new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp @@ -0,0 +1,129 @@ +#include + +namespace XCEngine::UI::Editor { + +namespace { + +bool IsRootPanelVisible( + const UIEditorWorkspaceController& controller, + std::string_view panelId) { + const UIEditorPanelSessionState* panelState = + FindUIEditorPanelSessionState(controller.GetSession(), panelId); + return panelState != nullptr && panelState->open && panelState->visible; +} + +} // namespace + +const UIEditorPanelDescriptor* ResolveUIEditorSingleVisibleRootPanelDescriptor( + const UIEditorWorkspaceController& controller) { + const UIEditorWorkspaceNode& root = controller.GetWorkspace().root; + if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { + return nullptr; + } + + const UIEditorPanelRegistry& panelRegistry = controller.GetPanelRegistry(); + const UIEditorPanelDescriptor* visibleDescriptor = nullptr; + std::size_t visibleCount = 0u; + for (const UIEditorWorkspaceNode& child : root.children) { + if (child.kind != UIEditorWorkspaceNodeKind::Panel || + !IsRootPanelVisible(controller, child.panel.panelId)) { + continue; + } + + const UIEditorPanelDescriptor* descriptor = + FindUIEditorPanelDescriptor(panelRegistry, child.panel.panelId); + if (descriptor == nullptr) { + return nullptr; + } + + ++visibleCount; + visibleDescriptor = descriptor; + if (visibleCount > 1u) { + return nullptr; + } + } + + return visibleCount == 1u ? visibleDescriptor : nullptr; +} + +bool HasUIEditorSingleVisibleRootTab( + const UIEditorWorkspaceController& controller) { + return ResolveUIEditorSingleVisibleRootPanelDescriptor(controller) != nullptr; +} + +bool IsUIEditorDetachedWorkspaceToolWindow( + const UIEditorWorkspaceController& controller) { + if (const UIEditorPanelDescriptor* descriptor = + ResolveUIEditorSingleVisibleRootPanelDescriptor(controller); + descriptor != nullptr) { + return descriptor->toolWindow; + } + + return false; +} + +std::string ResolveUIEditorDetachedWorkspaceTitle( + const UIEditorWorkspaceController& controller, + std::string_view fallbackTitle) { + const std::string& activePanelId = controller.GetWorkspace().activePanelId; + if (const UIEditorPanelDescriptor* descriptor = + FindUIEditorPanelDescriptor(controller.GetPanelRegistry(), activePanelId); + descriptor != nullptr && + !descriptor->defaultTitle.empty()) { + return descriptor->defaultTitle; + } + + return std::string(fallbackTitle); +} + +::XCEngine::UI::UISize ResolveUIEditorDetachedWorkspaceMinimumOuterSize( + const UIEditorWorkspaceController& controller, + const ::XCEngine::UI::UISize& fallbackSize) { + if (const UIEditorPanelDescriptor* descriptor = + ResolveUIEditorSingleVisibleRootPanelDescriptor(controller); + descriptor != nullptr && + descriptor->minimumDetachedWindowSize.width > 0.0f && + descriptor->minimumDetachedWindowSize.height > 0.0f) { + return descriptor->minimumDetachedWindowSize; + } + + return fallbackSize; +} + +::XCEngine::UI::UISize ResolveUIEditorDetachedToolWindowMinimumContentSize( + const UIEditorWorkspaceController& controller, + const ::XCEngine::UI::UISize& fallbackContentSize, + float detachedWindowChromeHeight) { + if (const UIEditorPanelDescriptor* descriptor = + ResolveUIEditorSingleVisibleRootPanelDescriptor(controller); + descriptor != nullptr && descriptor->toolWindow) { + const float minimumContentWidth = + descriptor->minimumDetachedWindowSize.width > 0.0f + ? descriptor->minimumDetachedWindowSize.width + : fallbackContentSize.width; + const float minimumContentHeight = + descriptor->minimumDetachedWindowSize.height > detachedWindowChromeHeight + ? descriptor->minimumDetachedWindowSize.height - detachedWindowChromeHeight + : fallbackContentSize.height; + return ::XCEngine::UI::UISize(minimumContentWidth, minimumContentHeight); + } + + return fallbackContentSize; +} + +::XCEngine::UI::UISize ResolveUIEditorDetachedPanelPreferredWindowSize( + const UIEditorPanelRegistry& panelRegistry, + std::string_view panelId, + const ::XCEngine::UI::UISize& fallbackSize) { + const UIEditorPanelDescriptor* descriptor = + FindUIEditorPanelDescriptor(panelRegistry, panelId); + if (descriptor == nullptr || + descriptor->preferredDetachedWindowSize.width <= 0.0f || + descriptor->preferredDetachedWindowSize.height <= 0.0f) { + return fallbackSize; + } + + return descriptor->preferredDetachedWindowSize; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp b/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp index 1b999aef..c635b315 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp @@ -55,6 +55,19 @@ UIEditorWorkspaceInputOwner ResolveNextInputOwner( contentHostFrame); } +std::string ResolveFocusedPanelId(const UIEditorWorkspaceInputOwner& owner) { + switch (owner.kind) { + case UIEditorWorkspaceInputOwnerKind::HostedPanel: + case UIEditorWorkspaceInputOwnerKind::Viewport: + return owner.panelId; + + case UIEditorWorkspaceInputOwnerKind::DockHost: + case UIEditorWorkspaceInputOwnerKind::None: + default: + return {}; + } +} + } // namespace UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( @@ -126,6 +139,14 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( model.workspacePresentations, inputEvents, &frame.inputOwner); + frame.panelHostLifecycleFrame = UpdateUIEditorPanelHostLifecycle( + state.panelHostLifecycleState, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + UIEditorPanelHostLifecycleRequest{ + .focusedPanelId = ResolveFocusedPanelId(frame.inputOwner), + }); frame.result.dockHostResult = frame.dockHostFrame.result; frame.result.consumed = frame.dockHostFrame.result.consumed;