From 4fcaac81d6d5e6f367fc141e435c1c2f92a3a06a Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 26 Apr 2026 03:41:58 +0800 Subject: [PATCH] Refactor editor windowing and update renderer regression --- docs/plan/rendering_srp_urp_closeout_plan.md | 93 ------- ...模块长期目标与下一阶段执行计划_2026-04-26.md | 140 ---------- editor/CMakeLists.txt | 8 +- editor/app/Bootstrap/Application.cpp | 17 +- .../Platform/Win32/Windowing/EditorWindow.cpp | 91 ++++++- .../Platform/Win32/Windowing/EditorWindow.h | 70 ++--- .../Win32/Windowing/EditorWindowHostConfig.h | 19 ++ .../Windowing/EditorWindowHostRuntime.cpp | 160 ++++++++++-- .../Win32/Windowing/EditorWindowHostRuntime.h | 44 +++- .../EditorWindowMessageDispatcher.cpp | 42 +-- .../Windowing/EditorWindowPointerCapture.h | 41 +-- .../Win32/Windowing/EditorWindowState.h | 63 +---- .../EditorUtilityWindowRegistry.cpp | 6 + .../EditorUtilityWindowRegistry.h | 2 +- .../Content/EditorWindowContentFactory.cpp | 7 +- .../Content/EditorWindowContentFactory.h | 4 +- ...EditorWorkspaceWindowContentController.cpp | 60 ++++- .../EditorWorkspaceWindowContentController.h | 12 +- .../EditorUtilityWindowCoordinator.cpp | 59 ++--- .../EditorUtilityWindowCoordinator.h | 12 +- .../EditorWindowLifecycleCoordinator.cpp | 138 +++------- .../EditorWindowLifecycleCoordinator.h | 18 +- .../EditorWindowWorkspaceCoordinator.cpp | 241 ++++++++---------- .../EditorWindowWorkspaceCoordinator.h | 65 +++-- .../Windowing/EditorWindowManager.cpp | 57 +++-- .../Windowing/EditorWindowManager.h | 53 ++-- editor/app/Windowing/EditorWindowShared.h | 20 ++ .../Host/EditorWindowHostInterfaces.h | 107 ++++++++ .../Windowing/Host/EditorWindowHostTypes.h | 29 +++ .../Host/EditorWindowPointerCapture.h | 42 +++ editor/app/Windowing/Host/EditorWindowTypes.h | 67 +++++ .../Windowing/System/EditorWindowSystem.cpp | 24 ++ .../app/Windowing/System/EditorWindowSystem.h | 4 + managed/GameScripts/RenderPipelineApiProbe.cs | 6 +- .../Scripts/ProjectRenderPipelineProbe.cs | 8 +- scripts/Run-RendererPhaseRegression.ps1 | 184 +++++++++++++ tests/CMakeLists.txt | 10 +- tests/TEST_SPEC.md | 24 +- tests/UI/Editor/unit/CMakeLists.txt | 6 +- 39 files changed, 1181 insertions(+), 872 deletions(-) delete mode 100644 docs/plan/rendering_srp_urp_closeout_plan.md create mode 100644 editor/app/Platform/Win32/Windowing/EditorWindowHostConfig.h rename editor/app/{Platform/Win32/Windowing => Windowing/Coordinator}/EditorUtilityWindowCoordinator.cpp (70%) rename editor/app/{Platform/Win32/Windowing => Windowing/Coordinator}/EditorUtilityWindowCoordinator.h (72%) rename editor/app/{Platform/Win32/Windowing => Windowing/Coordinator}/EditorWindowLifecycleCoordinator.cpp (50%) rename editor/app/{Platform/Win32/Windowing => Windowing/Coordinator}/EditorWindowLifecycleCoordinator.h (57%) rename editor/app/{Platform/Win32/Windowing => Windowing/Coordinator}/EditorWindowWorkspaceCoordinator.cpp (81%) rename editor/app/{Platform/Win32/Windowing => Windowing/Coordinator}/EditorWindowWorkspaceCoordinator.h (69%) rename editor/app/{Platform/Win32 => }/Windowing/EditorWindowManager.cpp (69%) rename editor/app/{Platform/Win32 => }/Windowing/EditorWindowManager.h (62%) create mode 100644 editor/app/Windowing/Host/EditorWindowHostInterfaces.h create mode 100644 editor/app/Windowing/Host/EditorWindowHostTypes.h create mode 100644 editor/app/Windowing/Host/EditorWindowPointerCapture.h create mode 100644 editor/app/Windowing/Host/EditorWindowTypes.h create mode 100644 scripts/Run-RendererPhaseRegression.ps1 diff --git a/docs/plan/rendering_srp_urp_closeout_plan.md b/docs/plan/rendering_srp_urp_closeout_plan.md deleted file mode 100644 index 3341aa75..00000000 --- a/docs/plan/rendering_srp_urp_closeout_plan.md +++ /dev/null @@ -1,93 +0,0 @@ -# Rendering SRP / URP Closeout Plan - -更新日期: `2026-04-26` - -状态: `Completed, Phase 1 closeout done` - -## 1. 长期目标 - -目标不是继续堆更多 URP 外形,而是把默认渲染主线真正收口为: - -`GraphicsSettings.renderPipelineAsset -> UniversalRenderPipelineAsset -> ScriptableRendererData -> UniversalRenderer -> ScriptableRendererFeature/Pass -> ScriptableRenderPipelineHost -> native backend contract -> RenderGraph / RHI` - -收口后的职责边界: - -- managed URP 负责 camera policy、renderer 选择、stage planning、feature/pass 编排、final color 策略 -- native Rendering 负责 scene extraction、scene draw、fullscreen graph execution、RenderGraph/RHI -- `BuiltinForwardPipeline` 或其后继只保留 backend 语义,不继续承载产品层策略 - -## 2. 阶段路线 - -### Phase 1: Renderer-backed URP v1 收口 - -- 让默认 `UniversalRenderPipelineAsset -> UniversalRenderer` 成为唯一上层主线 -- 先收口 ownership 和 contract,不在本阶段做 backend 大拆分 - -### Phase 2: Native backend contract 抽取 - -- 把默认 native backend 从产品层语义里剥离出来 -- 让 host/bridge 面向稳定 backend contract,而不是面向 builtin forward 产品实现 - -### Phase 3: Feature ownership 迁移 - -- 把 feature 的启用、排序、插入点统一回收到 managed `ScriptableRendererFeature` - -### Phase 4: Asset / Editor / Runtime 产品化 - -- 稳定 renderer data、camera override、runtime invalidation、editor 消费路径 - -### Phase 5: 收尾与清理 - -- 删除临时兼容层,统一命名和文档 - -## 3. Phase 1 已完成进展 - -- 已补齐长期目标文档和 Rendering 模块目标描述 -- 已把 managed backend 解析从“隐式默认 fallback”改成“显式策略” -- 已新增 `ManagedPipelineRendererAssetPolicy` -- 已让 `ManagedScriptableRenderPipelineAsset` 先读显式 renderer asset,再按 policy 解析 default native backend -- 已让 `MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset()` 不再直接偷偷创建默认 native backend asset -- 已让 Mono runtime 显式返回 `DefaultNativeBackend` policy -- 已把 `UniversalRenderer` 的 `PostProcess` 变成显式 stage 分支 -- 已补 `UniversalPostProcessBlock.EnqueueRenderPasses()` 作为显式入口 -- 已把 `RuntimeStateHashUtility` 调整为跨程序集可见 -- 已把 `ScriptableRendererFeature.CreateInstance()` 调整为可供外部受控复用 -- 已更新一批 scripting 侧测试预期,使默认 URP/managed pipeline case 面向“显式 default backend policy” -- 已完成 editor 编译与 12 秒启动冒烟 - - `XCUIEditorApp` 编译通过 - - `build/editor/Debug/XCEngine.exe` 12 秒启动烟测通过 - -## 4. Phase 1 验收结果 - -- 已修掉 `managed/GameScripts/RenderPipelineApiProbe.cs` 中挡住本阶段验证的 managed API 问题 -- `scripting_tests` 已恢复为可用基线,并通过 9 条聚焦的 SRP/URP 回归用例 -- 默认 backend 来源、default renderer fallback、feature 注入、renderer invalidation 等关键主路径已被 focused regression tests 覆盖 -- editor 侧验证已补齐,`XCUIEditorApp` 编译与 `build/editor/Debug/XCEngine.exe` 12 秒启动烟测均通过 -- `rendering_unit_tests` 仍存在与本次收口无关的既有编译断点,但不再阻塞 Phase 1 收口验收 - -## 5. 当前 subplan - -本轮 subplan 已完成,已从执行列表清空。 - -### 5.1 已完成验收项 - -- 默认 backend 来源已变成显式可追踪的 policy -- `PostProcess` ownership 已稳定挂到 `UniversalRenderer` -- `scripting_tests` 已能稳定覆盖本阶段相关用例 -- 默认 URP 主路径已具备 focused regression tests -- Phase 1 未再引入新的隐式 fallback 语义 - -## 6. 下一阶段建议 - -下一阶段可以进入 Phase 2,但不要重新把产品层策略带回 native: - -1. 抽取更稳定的 native backend contract,继续收窄 host/bridge 对具体 builtin forward 实现的依赖 -2. 继续把 feature 的启用、排序、插入点留在 managed `ScriptableRendererFeature / UniversalRenderer` 主线 -3. 在当前 focused regression baseline 之上继续稳住 renderer invalidation、asset authoring 和 editor/runtime 消费路径 - -## 7. 非目标 - -- 本阶段不直接重命名 `BuiltinForwardPipeline` -- 不在本阶段完成 backend-only 最终类层次 -- 不引入 deferred、clustered lighting、完整 volume framework 等新能力 -- 不把 editor tooling 路径强行并进默认 URP 产品主线 diff --git a/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md index 16dfecd1..8b137891 100644 --- a/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md +++ b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md @@ -1,141 +1 @@ -# 渲染模块长期目标与下一阶段执行计划 -更新时间: `2026-04-26` - -状态: `Active` - -执行闸门: - -- 每完成一个阶段,先编译并启动 `editor`。 -- 对 `editor` 执行不少于 `12s` 的冒烟测试,确认进程可启动、主循环可持续运行且无立即崩溃。 -- 只有在该阶段编译和 `12s` 冒烟测试都通过后,才允许执行该阶段的 `commit` 和 `push`。 - -说明: 本文基于当前仓库中的实际代码重新审核后编写,用于替代已经失效的 `rendering_srp_urp_closeout_plan.md`。本计划只按现有实现说话,不沿用旧文档中的“已收口完成”结论。 - -## 1. 基于代码的审核结论 - -- 当前默认渲染主线是 - `SceneRenderer -> SceneRenderRequestPlanner -> RenderPipelineHost -> CameraFramePlanBuilder -> CameraRenderer -> ExecuteCameraFrameRenderGraphPlan`,不是“managed URP 直接驱动 native backend”的单线结构。 -- 当前默认顶层 pipeline 已经是 `ScriptableRenderPipelineHost`。在没有显式 preferred asset 时,`RenderPipelineFactory` 会优先尝试 configured managed pipeline asset,失败后回落到 host asset;host 的默认 backend 是 `BuiltinForwardPipelineAsset`。 -- managed SRP 不是空壳。当前已经具备 `ScriptableRenderPipelinePlanningContext`、`RenderSceneSetupContext`、`DirectionalShadowExecutionContext`、`ScriptableRenderContext`,能够参与 request/frame plan、scene setup、shadow policy、scene draw 和 fullscreen stage 录制。 -- 但 managed/backend contract 还没有真正完成。`MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset()` 当前不会返回 shared backend asset,`GetPipelineRendererAssetPolicy()` 在 managed asset 可用时固定返回 `DefaultNativeBackend`。也就是说,现阶段 managed renderer 仍统一依赖默认 native scene backend。 -- `RenderGraph` 的 managed 接口目前是 v1。它能创建 transient texture、记录 scene phase / injection point / native feature pass、按 desc 调 `DrawRenderers`,也能提交 raster pass;但 raster pass execution 仍只覆盖 `ColorScale` / `ShaderVector` / `FinalColor` 三类 fullscreen execution,且 flush 逻辑只对 fullscreen sequence stage 生效。 -- `UniversalRenderPipelineAsset` / `RendererBackedRenderPipelineAsset` 已支持 `rendererDataList`、`defaultRendererIndex`、`UniversalAdditionalCameraData.rendererIndex`,但 `UniversalPostProcessBlock` 仍为空实现,默认后处理主要依赖 feature 注入和 `FinalOutput` 内置 pass。 -- `BuiltinForwardPipeline` 仍同时承担 fallback pipeline、默认 `SceneDrawBackend`、默认 native feature host 三个角色,是当前渲染模块最大的耦合点。 -- editor tooling 仍是独立层。`CameraRenderer` 顶层安装 `BuiltinObjectIdPass`,而 scene viewport 的 grid / selection / helper pass 仍在 editor 专用路径,不应并入本阶段 SRP / URP 主线计划。 - -## 2. 长期目标 - -- 稳定总主线: - `SceneRenderer -> SceneRenderRequestPlanner -> CameraFramePlanBuilder -> CameraRenderer -> ScriptableRenderPipelineHost -> managed stage recorder / native scene draw backend -> RenderGraph / RHI` -- 稳定职责边界: - managed 负责 camera / request / frame plan、renderer 选择、feature / pass 排序、fullscreen stage 组合、scene setup、shadow policy;native 负责 scene extraction、culling、scene draw backend、资源生命周期、RenderGraph 编译执行、RHI。 -- 让 `ScriptableRenderPipelineHost` 成为唯一可信的 SRP 边界,而不是继续把产品层策略散落在 `BuiltinForwardPipeline` 和 editor / runtime 兼容层里。 -- 把 `BuiltinForwardPipeline` 收敛为清晰的 fallback / backend 角色,避免它继续同时承担“默认产品主线”和“可复用 native backend”两套语义。 -- 让 `rendererDataList`、`rendererIndex`、feature invalidation、bridge generation refresh、asset runtime versioning 变成稳定 contract,而不是靠隐式 fallback 维持行为。 -- 如果未来真的需要多个 native backend asset,必须通过明确定义的 backend asset contract 接入,而不是继续复用当前未完成的 placeholder API。 -- 明确 runtime 主渲染与 editor viewport / tooling 的共用层和专用层,避免后续计划再次混写。 - -## 3. 当前约束 - -- 下一阶段不做纯 managed scene draw backend,不承诺把 Gaussian Splat / Volumetric 改写为纯 C# feature。 -- 下一阶段不新增 deferred、clustered lighting、完整 volume framework 等新能力。 -- 下一阶段不改 editor viewport render loop,也不把 grid / selection / helper pass 并入 runtime 主线。 -- 下一阶段不把 managed `RenderGraph` 包装误写成“通用图形 / 计算图 API”;当前它只是 SRP v1 录制接口。 - -## 4. 下一阶段执行计划 - -### 4.1 先把 backend contract 讲真 - -状态: `Completed` (`2026-04-26`) - -目标: 把“当前唯一真实可用的是 `DefaultNativeBackend`”写进代码和测试,不再保留会误导人的伪完成语义。 - -执行: - -- 重新审核 `ManagedRenderPipelineAssetRuntime`、`MonoManagedRenderPipelineAssetRuntime`、`RenderPipelineFactory`、`ScriptableRenderPipelineHost` 的 backend 选择路径。 -- 对未实现的 explicit backend asset 分支做二选一收口:要么真正实现 shared backend asset 解析,要么明确删除 / 禁用该分支并补充注释与测试。 -- 把 `DefaultNativeBackend` 作为当前正式 contract 固化下来,避免再出现“文档已完成、代码仍默认回落”的错位。 - -完成标志: - -- 代码、测试、文档对 backend 选择语义一致。 -- 不再有“managed renderer 已支持 explicit backend asset”这一错误暗示。 - -### 4.2 从 `BuiltinForwardPipeline` 中抽出可复用的 native scene backend - -状态: `Completed` (`2026-04-26`) - -目标: 减轻 `BuiltinForwardPipeline` 的三重职责,让 host fallback 和 scene draw backend 可以独立演进。 - -执行: - -- 从 `BuiltinForwardSceneSetup`、`BuiltinForwardPipeline`、`NativeSceneRecorder` 梳理出稳定的 `SceneDrawBackend` 组装层。 -- 把 `ShadowCaster`、`DepthOnly`、main scene feature host 的 ownership 拆清楚,避免 host / backend / fallback wrapper 重复配置。 -- 统一 fallback path 和 managed recorder path 对默认 native feature 的消费方式,减少双份默认注册逻辑的漂移风险。 - -完成标志: - -- fallback pipeline 仍可独立工作。 -- managed stage recorder 与 fallback path 共用同一套默认 native backend 组装逻辑。 - -### 4.3 固化 managed SRP v1 能力边界 - -状态: `Completed` (`2026-04-26`) - -目标: 把现有 managed API 从“隐式能力集合”收敛成明确的 v1 contract,便于后续扩展而不是继续误判能力上限。 - -执行: - -- 整理 `ScriptableRenderContext`、`ScriptableRenderPipelinePlanningContext`、`RenderSceneSetupContext`、`DirectionalShadowExecutionContext` 的能力矩阵。 -- 对“fullscreen raster-only”“仅 fullscreen sequence stage flush managed raster pass”“scene draw 仍依赖 native backend”等限制补齐断言、测试和文档。 -- 补齐 `UniversalPostProcessBlock` 当前的真实语义说明:默认后处理并未在 block 内完成,仍主要依赖 feature / pass 注入与 `FinalOutput` 内置 pass。 - -完成标志: - -- managed 侧能做什么、不能做什么有明确边界。 -- 后续计划不再把现有 API 误写成完整 URP runtime 或通用 RenderGraph API。 - -### 4.4 稳住 renderer authoring / invalidation / bridge refresh - -状态: `Completed` (`2026-04-26`) - -目标: 把当前已经存在但还容易漂移的 authoring 路径变成可回归验证的稳定面。 - -执行: - -- 围绕 `rendererDataList`、`defaultRendererIndex`、`UniversalAdditionalCameraData.rendererIndex`、feature `SetDirty` / invalidation、bridge generation refresh 增加 focused tests。 -- 验证 default asset selection、explicit managed asset selection、bridge rebinding 这三条路径在 frame plan 和实际渲染行为上保持一致。 -- 明确 renderer 选择、feature 变更和 runtime resource version 之间的刷新链路,避免再次回退到隐式缓存。 - -完成标志: - -- renderer 切换、feature 改动、bridge 切换都有回归保护。 -- `CameraFramePlanBuilder` 和 `CameraRenderer` 对 managed path 的依赖关系稳定可追踪。 - -## 5. 下一阶段完成后的验收口径 - -- `ScriptableRenderPipelineHost` 的角色描述不再含混。 -- `DefaultNativeBackend` 与 explicit backend asset 的语义只保留一种真实状态。 -- `BuiltinForwardPipeline` 不再是继续扩张的“总包类”。 -- managed SRP v1 的能力边界和限制有文档、有测试、有 fail-fast。 -- renderer authoring / invalidation / bridge refresh 不再依赖人工记忆。 - -## 6. 审核依据 - -- `engine/src/Rendering/Execution/SceneRenderer.cpp` -- `engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp` -- `engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp` -- `engine/src/Rendering/Execution/CameraRenderer.cpp` -- `engine/src/Rendering/Internal/RenderPipelineFactory.cpp` -- `engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp` -- `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp` -- `engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp` -- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` -- `managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs` -- `managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs` -- `managed/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs` -- `managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs` -- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalPostProcessBlock.cs` diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 17aa3b36..63f162f9 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -260,6 +260,10 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) set(XCUI_EDITOR_APP_WINDOWING_SOURCES app/Composition/EditorWindowWorkspaceStore.cpp + app/Windowing/EditorWindowManager.cpp + app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.cpp + app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp + app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp app/Windowing/Content/EditorWindowContentFactory.cpp app/Windowing/Content/EditorUtilityWindowContentController.cpp app/Windowing/Content/EditorWorkspaceWindowContentController.cpp @@ -279,11 +283,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Platform/Win32/Runtime/EditorWindowRuntimeController.cpp app/Platform/Win32/System/Win32SystemInteractionHost.cpp app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp - app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp - app/Platform/Win32/Windowing/EditorWindowManager.cpp app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp - app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp - app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp ) set(XCUI_EDITOR_APP_CORE_SOURCES diff --git a/editor/app/Bootstrap/Application.cpp b/editor/app/Bootstrap/Application.cpp index 8547c1e8..b0b09bbf 100644 --- a/editor/app/Bootstrap/Application.cpp +++ b/editor/app/Bootstrap/Application.cpp @@ -2,9 +2,8 @@ #include "Bootstrap/EditorResources.h" #include "System/SystemInteractionService.h" #include "Composition/EditorContext.h" +#include "Windowing/EditorWindowManager.h" #include "Windowing/System/EditorWindowSystem.h" -#include "Platform/Win32/Windowing/EditorWindowManager.h" -#include "Platform/Win32/Windowing/EditorWindow.h" #include "Platform/Win32/System/Win32SystemInteractionHost.h" #include "Support/EnvironmentFlags.h" #include "Support/ExecutablePath.h" @@ -137,15 +136,11 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { return; } - if (App::EditorWindow* primaryWindow = m_windowManager->FindPrimaryWindow(); - primaryWindow != nullptr && - primaryWindow->GetHwnd() != nullptr) { - PostMessageW(primaryWindow->GetHwnd(), WM_CLOSE, 0, 0); - } + m_windowManager->RequestPrimaryWindowClose(); }); m_editorContext->SetReadyStatus(); - App::EditorWindowManager::CreateParams createParams = {}; + App::EditorWindowCreateParams createParams = {}; createParams.windowId = "main"; createParams.title = kWindowTitle; createParams.category = App::EditorWindowCategory::Workspace; @@ -304,11 +299,7 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) { "smoke", "auto-exit requested after duration/frame limit"); m_smokeTestCloseRequested = true; - if (App::EditorWindow* primaryWindow = m_windowManager->FindPrimaryWindow(); - primaryWindow != nullptr && - primaryWindow->GetHwnd() != nullptr) { - PostMessageW(primaryWindow->GetHwnd(), WM_CLOSE, 0, 0); - } else { + if (!m_windowManager->RequestPrimaryWindowClose()) { PostQuitMessage(0); } } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp index e6768941..2c1f9c94 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp @@ -70,6 +70,20 @@ namespace XCEngine::UI::Editor::App { using namespace EditorWindowSupport; using ::XCEngine::UI::UIPoint; +POINT EditorWindow::ToNativePoint(const EditorWindowScreenPoint& screenPoint) { + return POINT{ + static_cast(screenPoint.x), + static_cast(screenPoint.y), + }; +} + +EditorWindowScreenPoint EditorWindow::FromNativePoint(const POINT& screenPoint) { + EditorWindowScreenPoint point = {}; + point.x = screenPoint.x; + point.y = screenPoint.y; + return point; +} + EditorWindow::EditorWindow( std::string windowId, std::wstring title, @@ -135,6 +149,11 @@ bool EditorWindow::IsDestroyed() const { return m_session->IsDestroyed(); } +bool EditorWindow::HasLiveHostWindow() const { + const HWND hwnd = m_session->GetHwnd(); + return hwnd != nullptr && IsWindow(hwnd); +} + bool EditorWindow::IsRenderReady() const { return m_runtime->IsReady(); } @@ -204,6 +223,12 @@ void EditorWindow::SetTitle(std::wstring title) { m_session->SetTitle(std::move(title)); } +void EditorWindow::ApplyHostWindowTitle() { + if (HasLiveHostWindow()) { + SetWindowTextW(m_session->GetHwnd(), GetTitle().c_str()); + } +} + void EditorWindow::RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) { EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding(); assert(workspaceBinding != nullptr); @@ -343,8 +368,9 @@ UIPoint EditorWindow::ConvertClientPixelsToDips(LONG x, LONG y) const { PixelsToDips(static_cast(y))); } -UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) const { - POINT clientPoint = screenPoint; +UIPoint EditorWindow::ConvertScreenPixelsToClientDips( + const EditorWindowScreenPoint& screenPoint) const { + POINT clientPoint = ToNativePoint(screenPoint); if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr) { ScreenToClient(hwnd, &clientPoint); @@ -363,8 +389,8 @@ UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) bool EditorWindow::TryResolveDockTabDragHotspot( std::string_view nodeId, std::string_view panelId, - const POINT& screenPoint, - POINT& outHotspot) const { + const EditorWindowScreenPoint& screenPoint, + EditorWindowScreenPoint& outHotspot) const { const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding(); if (dockHostBinding == nullptr) { outHotspot = {}; @@ -383,13 +409,13 @@ bool EditorWindow::TryResolveDockTabDragHotspot( } const float dpiScale = GetDpiScale(); - outHotspot.x = static_cast(std::lround(hotspotDips.x * dpiScale)); - outHotspot.y = static_cast(std::lround(hotspotDips.y * dpiScale)); + outHotspot.x = static_cast(std::lround(hotspotDips.x * dpiScale)); + outHotspot.y = static_cast(std::lround(hotspotDips.y * dpiScale)); return true; } bool EditorWindow::TryResolveDockTabDropTarget( - const POINT& screenPoint, + const EditorWindowScreenPoint& screenPoint, UIEditorDockHostTabDropTarget& outTarget) const { const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding(); if (dockHostBinding == nullptr) { @@ -402,6 +428,57 @@ bool EditorWindow::TryResolveDockTabDropTarget( return outTarget.valid; } +bool EditorWindow::TryGetHostScreenRect(EditorWindowScreenRect& outRect) const { + outRect = {}; + + RECT nativeRect = {}; + if (!HasLiveHostWindow() || !GetWindowRect(m_session->GetHwnd(), &nativeRect)) { + return false; + } + + outRect.left = nativeRect.left; + outRect.top = nativeRect.top; + outRect.right = nativeRect.right; + outRect.bottom = nativeRect.bottom; + return true; +} + +void EditorWindow::SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) { + if (!HasLiveHostWindow()) { + return; + } + + SetWindowPos( + m_session->GetHwnd(), + nullptr, + screenPoint.x, + screenPoint.y, + 0, + 0, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); +} + +void EditorWindow::FocusHostWindow() { + if (!HasLiveHostWindow()) { + return; + } + + ShowWindow(m_session->GetHwnd(), SW_RESTORE); + SetForegroundWindow(m_session->GetHwnd()); +} + +void EditorWindow::PostCloseToHost() { + if (HasLiveHostWindow()) { + PostMessageW(m_session->GetHwnd(), WM_CLOSE, 0, 0); + } +} + +void EditorWindow::DestroyHostWindow() { + if (HasLiveHostWindow()) { + DestroyWindow(m_session->GetHwnd()); + } +} + bool EditorWindow::OnResize(UINT width, UINT height) { const bool matchedPresentedPrediction = m_chromeController->ConsumePresentedPredictedClientPixelSizeMatch(width, height); diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.h b/editor/app/Platform/Win32/Windowing/EditorWindow.h index ce5c9bbb..40647c69 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.h @@ -4,8 +4,8 @@ #define NOMINMAX #endif -#include "Platform/Win32/Windowing/EditorWindowPointerCapture.h" #include "Platform/Win32/Windowing/EditorWindowSession.h" +#include "Windowing/Host/EditorWindowHostInterfaces.h" #include @@ -64,7 +64,7 @@ class EditorWindowWorkspaceCoordinator; class EditorWindowSession; struct EditorWorkspaceWindowProjection; -class EditorWindow { +class EditorWindow final : public EditorHostWindow { public: EditorWindow( std::string windowId, @@ -80,25 +80,27 @@ public: EditorWindow(EditorWindow&&) = delete; EditorWindow& operator=(EditorWindow&&) = delete; - std::string_view GetWindowId() const; + std::string_view GetWindowId() const override; HWND GetHwnd() const; bool HasHwnd() const; EditorWindowCategory GetCategory() const; const EditorWindowChromePolicy& GetChromePolicy() const; - EditorWindowLifecycleState GetLifecycleState() const; - bool IsPrimary() const; - bool IsWorkspaceWindow() const; - bool IsUtilityWindow() const; - bool IsClosing() const; - bool IsDestroyed() const; - const std::wstring& GetTitle() const; + EditorWindowLifecycleState GetLifecycleState() const override; + bool IsPrimary() const override; + bool IsWorkspaceWindow() const override; + bool IsUtilityWindow() const override; + bool IsClosing() const override; + bool IsDestroyed() const override; + bool HasLiveHostWindow() const override; + const std::wstring& GetTitle() const override; std::string_view GetCachedTitleText() const; - const UIEditorWorkspaceController* TryGetWorkspaceController() const; - const EditorWorkspaceWindowProjection* TryGetWorkspaceProjection() const; + const UIEditorWorkspaceController* TryGetWorkspaceController() const override; + const EditorWorkspaceWindowProjection* TryGetWorkspaceProjection() const override; const UIEditorWorkspaceController& GetWorkspaceController() const; - EditorWindowDockHostBinding* TryGetDockHostBinding(); - const EditorWindowDockHostBinding* TryGetDockHostBinding() const; - ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips(const POINT& screenPoint) const; + EditorWindowDockHostBinding* TryGetDockHostBinding() override; + const EditorWindowDockHostBinding* TryGetDockHostBinding() const override; + ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips( + const EditorWindowScreenPoint& screenPoint) const override; private: friend class EditorWindowChromeController; @@ -108,33 +110,39 @@ private: friend class EditorWindowLifecycleCoordinator; friend class EditorWindowWorkspaceCoordinator; - bool IsRenderReady() const; + bool IsRenderReady() const override; bool TryResolveDockTabDragHotspot( std::string_view nodeId, std::string_view panelId, - const POINT& screenPoint, - POINT& outHotspot) const; + const EditorWindowScreenPoint& screenPoint, + EditorWindowScreenPoint& outHotspot) const override; bool TryResolveDockTabDropTarget( - const POINT& screenPoint, - UIEditorDockHostTabDropTarget& outTarget) const; - void InvalidateHostWindow() const; + const EditorWindowScreenPoint& screenPoint, + UIEditorDockHostTabDropTarget& outTarget) const override; + void InvalidateHostWindow() const override; void AttachHwnd(HWND hwnd); void MarkInitializing(); void MarkRunning(); - void MarkDestroyed(); - void MarkClosing(); - void SetPrimary(bool primary); - void SetTitle(std::wstring title); - void RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection); + void MarkDestroyed() override; + void MarkClosing() override; + void SetPrimary(bool primary) override; + void SetTitle(std::wstring title) override; + void ApplyHostWindowTitle() override; + void RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) override; bool Initialize( const std::filesystem::path& repoRoot, EditorContext& editorContext, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup); - void Shutdown(); - void ResetInteractionState(); + void Shutdown() override; + void ResetInteractionState() override; + bool TryGetHostScreenRect(EditorWindowScreenRect& outRect) const override; + void SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) override; + void FocusHostWindow() override; + void PostCloseToHost() override; + void DestroyHostWindow() override; EditorWindowFrameTransferRequests RenderFrame( EditorContext& editorContext, @@ -156,8 +164,8 @@ private: bool ApplyCurrentCursor() const; bool HasInteractiveCaptureState() const; bool OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const; - void AcquirePointerCapture(EditorWindowPointerCaptureOwner owner); - void ReleasePointerCapture(EditorWindowPointerCaptureOwner owner); + void AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) override; + void ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) override; void ForceReleasePointerCapture(); void TryStartImmediateShellPointerCapture(LPARAM lParam); @@ -179,6 +187,8 @@ private: ::XCEngine::UI::UIRect ResolveWorkspaceBounds( float clientWidthDips, float clientHeightDips) const; + static POINT ToNativePoint(const EditorWindowScreenPoint& screenPoint); + static EditorWindowScreenPoint FromNativePoint(const POINT& screenPoint); EditorWindowFrameTransferRequests RenderRuntimeFrame( EditorContext& editorContext, bool globalTabDragActive, diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostConfig.h b/editor/app/Platform/Win32/Windowing/EditorWindowHostConfig.h new file mode 100644 index 00000000..439dfb15 --- /dev/null +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostConfig.h @@ -0,0 +1,19 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +namespace XCEngine::UI::Editor::App { + +struct EditorWindowHostConfig { + HINSTANCE hInstance = nullptr; + const wchar_t* windowClassName = L""; + DWORD windowStyle = 0; + const wchar_t* primaryWindowTitle = L""; + void* windowUserData = nullptr; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp index 53bb121f..0d516cd3 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -3,13 +3,15 @@ #include "Bootstrap/EditorResources.h" #include "Composition/EditorContext.h" #include "Platform/Win32/Chrome/EditorWindowChromeController.h" +#include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" #include "Platform/Win32/Windowing/EditorWindow.h" #include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Runtime/EditorWindowFrameDriver.h" -#include "Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h" -#include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" -#include "Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" +#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" +#include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/System/EditorWindowSystem.h" #include @@ -61,17 +63,19 @@ EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, EditorContext& editorContext, + EditorWindowSystem& windowSystem, EditorWindowContentFactory& contentFactory) : m_hostConfig(hostConfig), m_repoRoot(std::move(repoRoot)), m_editorContext(editorContext), + m_windowSystem(windowSystem), m_contentFactory(contentFactory) {} EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( std::unique_ptr contentController, - const CreateParams& params) { + const EditorWindowCreateParams& params) { if (contentController == nullptr) { LogRuntimeTrace("window", "window creation failed: content controller is null"); return nullptr; @@ -113,14 +117,24 @@ EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( m_pendingCreateWindow = rawWindow; const DWORD windowStyle = params.nativeStylePolicy.useHostWindowStyle ? m_hostConfig.windowStyle - : params.nativeStylePolicy.windowStyle; + : static_cast(params.nativeStylePolicy.windowStyle); + const DWORD extendedWindowStyle = + params.nativeStylePolicy.extendedWindowStyle != 0u + ? static_cast(params.nativeStylePolicy.extendedWindowStyle) + : WS_EX_APPWINDOW; + const int initialX = params.initialX == kEditorWindowDefaultPosition + ? CW_USEDEFAULT + : params.initialX; + const int initialY = params.initialY == kEditorWindowDefaultPosition + ? CW_USEDEFAULT + : params.initialY; const HWND hwnd = CreateWindowExW( - params.nativeStylePolicy.extendedWindowStyle, + extendedWindowStyle, m_hostConfig.windowClassName, rawWindow->GetTitle().c_str(), windowStyle, - params.initialX, - params.initialY, + initialX, + initialY, params.initialWidth, params.initialHeight, nullptr, @@ -181,17 +195,18 @@ EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( EditorWindow* EditorWindowHostRuntime::CreateWorkspaceWindow( UIEditorWorkspaceController workspaceController, - const CreateParams& params) { + const EditorWindowCreateParams& params) { return CreateEditorWindow( m_contentFactory.CreateWorkspaceContentController( params.windowId, - std::move(workspaceController)), + std::move(workspaceController), + m_windowSystem), params); } EditorWindow* EditorWindowHostRuntime::CreateUtilityWindow( const EditorUtilityWindowDescriptor& descriptor, - const CreateParams& params) { + const EditorWindowCreateParams& params) { return CreateEditorWindow( m_contentFactory.CreateUtilityContentController(descriptor), params); @@ -215,6 +230,111 @@ bool EditorWindowHostRuntime::HasWindows() const { return !m_windows.empty(); } +std::vector EditorWindowHostRuntime::GetWindows() { + std::vector windows = {}; + windows.reserve(m_windows.size()); + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr) { + windows.push_back(window.get()); + } + } + return windows; +} + +std::vector EditorWindowHostRuntime::GetWindows() const { + std::vector windows = {}; + windows.reserve(m_windows.size()); + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr) { + windows.push_back(window.get()); + } + } + return windows; +} + +std::wstring_view EditorWindowHostRuntime::GetPrimaryWindowTitle() const { + return m_hostConfig.primaryWindowTitle != nullptr + ? std::wstring_view(m_hostConfig.primaryWindowTitle) + : std::wstring_view{}; +} + +bool EditorWindowHostRuntime::TryGetCursorScreenPoint( + EditorWindowScreenPoint& outPoint) const { + POINT nativePoint = {}; + if (!GetCursorPos(&nativePoint)) { + outPoint = {}; + return false; + } + + outPoint.x = nativePoint.x; + outPoint.y = nativePoint.y; + return true; +} + +EditorWindowScreenRect EditorWindowHostRuntime::ResolveFloatingPlacement( + const EditorWindowScreenPoint& screenPoint, + int preferredWidth, + int preferredHeight) const { + POINT nativePoint = {}; + nativePoint.x = screenPoint.x; + nativePoint.y = screenPoint.y; + const RECT nativeRect = BuildEditorFloatingWindowRect( + nativePoint, + preferredWidth, + preferredHeight); + EditorWindowScreenRect rect = {}; + rect.left = nativeRect.left; + rect.top = nativeRect.top; + rect.right = nativeRect.right; + rect.bottom = nativeRect.bottom; + return rect; +} + +EditorHostWindow* EditorWindowHostRuntime::FindWindowFromScreenPoint( + const EditorWindowScreenPoint& screenPoint) { + POINT nativePoint = {}; + nativePoint.x = screenPoint.x; + nativePoint.y = screenPoint.y; + const HWND hitWindow = WindowFromPoint(nativePoint); + if (hitWindow == nullptr) { + return nullptr; + } + + return FindWindow(GetAncestor(hitWindow, GA_ROOT)); +} + +const EditorHostWindow* EditorWindowHostRuntime::FindWindowFromScreenPoint( + const EditorWindowScreenPoint& screenPoint) const { + return const_cast(this)->FindWindowFromScreenPoint(screenPoint); +} + +void EditorWindowHostRuntime::ReapDestroyedWindows() { + for (auto it = m_windows.begin(); it != m_windows.end();) { + EditorWindow* const window = it->get(); + if (window == nullptr || !window->IsDestroyed()) { + ++it; + continue; + } + + if (m_pendingCreateWindow == window) { + m_pendingCreateWindow = nullptr; + } + + LogRuntimeTrace( + "window-close", + "ReapDestroyedWindows erase windowId='" + std::string(window->GetWindowId()) + + "' hostBefore=" + DescribeHostWindows(m_windows)); + it = m_windows.erase(it); + LogRuntimeTrace( + "window-close", + "ReapDestroyedWindows erase end hostAfter=" + DescribeHostWindows(m_windows)); + } +} + +std::string EditorWindowHostRuntime::DescribeWindows() const { + return DescribeHostWindows(m_windows); +} + void EditorWindowHostRuntime::RenderAllWindows( bool globalTabDragActive, EditorWindowWorkspaceCoordinator& workspaceCoordinator, @@ -229,7 +349,7 @@ void EditorWindowHostRuntime::RenderAllWindows( for (const std::unique_ptr& window : m_windows) { if (window == nullptr || - window->GetHwnd() == nullptr || + !window->HasLiveHostWindow() || window->GetLifecycleState() != EditorWindowLifecycleState::Running) { continue; } @@ -257,7 +377,7 @@ void EditorWindowHostRuntime::RenderAllWindows( for (WindowFrameTransferBatch& batch : transferBatches) { if (batch.sourceWindow == nullptr || - batch.sourceWindow->GetHwnd() == nullptr || + !batch.sourceWindow->HasLiveHostWindow() || batch.sourceWindow->GetLifecycleState() != EditorWindowLifecycleState::Running) { continue; } @@ -289,7 +409,7 @@ const EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) const { return const_cast(this)->FindWindow(hwnd); } -EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) { +EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view windowId) { if (windowId.empty()) { return nullptr; } @@ -303,8 +423,16 @@ EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) { return nullptr; } -const EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) const { - return const_cast(this)->FindWindow(windowId); +const EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view windowId) const { + return const_cast(this)->FindWindowByIdImpl(windowId); +} + +EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) { + return FindWindowByIdImpl(windowId); +} + +const EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) const { + return FindWindowByIdImpl(windowId); } EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() { diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h index 54ec99dc..dc800111 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h @@ -1,6 +1,8 @@ #pragma once -#include "Platform/Win32/Windowing/EditorWindowManager.h" +#include "Platform/Win32/Windowing/EditorWindow.h" +#include "Platform/Win32/Windowing/EditorWindowHostConfig.h" +#include "Windowing/Host/EditorWindowHostInterfaces.h" #include @@ -16,41 +18,55 @@ class EditorWindow; class EditorWindowContentController; class EditorWindowContentFactory; class EditorWindowLifecycleCoordinator; +class EditorWindowSystem; class EditorUtilityWindowCoordinator; class EditorWindowWorkspaceCoordinator; struct EditorUtilityWindowDescriptor; -class EditorWindowHostRuntime final { +class EditorWindowHostRuntime final : public EditorWindowHost { public: - using CreateParams = EditorWindowManager::CreateParams; - EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, EditorContext& editorContext, + EditorWindowSystem& windowSystem, EditorWindowContentFactory& contentFactory); ~EditorWindowHostRuntime(); EditorWindow* CreateEditorWindow( std::unique_ptr contentController, - const CreateParams& params); + const EditorWindowCreateParams& params); EditorWindow* CreateWorkspaceWindow( UIEditorWorkspaceController workspaceController, - const CreateParams& params); + const EditorWindowCreateParams& params) override; EditorWindow* CreateUtilityWindow( const EditorUtilityWindowDescriptor& descriptor, - const CreateParams& params); + const EditorWindowCreateParams& params) override; void BindLifecycleCoordinator(EditorWindowLifecycleCoordinator& lifecycleCoordinator); void HandlePendingNativeWindowCreated(HWND hwnd); EditorWindow* FindWindow(HWND hwnd); const EditorWindow* FindWindow(HWND hwnd) const; - EditorWindow* FindWindow(std::string_view windowId); - const EditorWindow* FindWindow(std::string_view windowId) const; + EditorHostWindow* FindWindowById(std::string_view windowId) override; + const EditorHostWindow* FindWindowById(std::string_view windowId) const override; EditorWindow* FindPrimaryWindow(); const EditorWindow* FindPrimaryWindow() const; bool HasWindows() const; + std::vector GetWindows() override; + std::vector GetWindows() const override; + std::wstring_view GetPrimaryWindowTitle() const override; + bool TryGetCursorScreenPoint(EditorWindowScreenPoint& outPoint) const override; + EditorWindowScreenRect ResolveFloatingPlacement( + const EditorWindowScreenPoint& screenPoint, + int preferredWidth, + int preferredHeight) const override; + EditorHostWindow* FindWindowFromScreenPoint( + const EditorWindowScreenPoint& screenPoint) override; + const EditorHostWindow* FindWindowFromScreenPoint( + const EditorWindowScreenPoint& screenPoint) const override; + void ReapDestroyedWindows() override; + std::string DescribeWindows() const override; void RenderAllWindows( bool globalTabDragActive, EditorWindowWorkspaceCoordinator& workspaceCoordinator, @@ -72,22 +88,24 @@ public: return m_repoRoot; } - std::vector>& GetWindows() { + std::vector>& GetWindowStorage() { return m_windows; } - const std::vector>& GetWindows() const { + const std::vector>& GetWindowStorage() const { return m_windows; } - void LogRuntimeTrace(std::string_view channel, std::string_view message) const; + void LogRuntimeTrace(std::string_view channel, std::string_view message) const override; private: - friend class EditorWindowLifecycleCoordinator; + EditorWindow* FindWindowByIdImpl(std::string_view windowId); + const EditorWindow* FindWindowByIdImpl(std::string_view windowId) const; EditorWindowHostConfig m_hostConfig = {}; std::filesystem::path m_repoRoot = {}; EditorContext& m_editorContext; + EditorWindowSystem& m_windowSystem; EditorWindowContentFactory& m_contentFactory; std::vector> m_windows = {}; EditorWindow* m_pendingCreateWindow = nullptr; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp index fcc8468c..9c749467 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp @@ -8,9 +8,9 @@ #include "Platform/Win32/Runtime/EditorWindowRuntimeController.h" #include "Platform/Win32/Windowing/EditorWindowPointerCapture.h" #include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" -#include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" -#include "Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h" -#include "Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" +#include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" +#include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" #include #include @@ -23,38 +23,6 @@ namespace { constexpr UINT kMessageNcUaDrawCaption = 0x00AEu; constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; -std::string DescribeHwnd(HWND hwnd) { - std::ostringstream stream = {}; - stream << "0x" << std::hex << std::uppercase - << reinterpret_cast(hwnd); - return stream.str(); -} - -std::string DescribeHostWindows(const EditorWindowHostRuntime& hostRuntime) { - std::ostringstream stream = {}; - const auto& windows = hostRuntime.GetWindows(); - stream << "count=" << windows.size() << " ["; - bool first = true; - for (const std::unique_ptr& window : windows) { - if (!first) { - stream << ", "; - } - first = false; - if (window == nullptr) { - stream << ""; - continue; - } - - stream << window->GetWindowId() - << "{hwnd=" << DescribeHwnd(window->GetHwnd()) - << ",primary=" << (window->IsPrimary() ? '1' : '0') - << ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState()) - << '}'; - } - stream << ']'; - return stream.str(); -} - } // namespace struct EditorWindowMessageDispatcher::DispatchContext { @@ -185,7 +153,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowPointerMessage( if (CanRouteEditorWindowGlobalTabDragPointerMessages( inputController.GetPointerCaptureOwner(), context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && - context.workspaceCoordinator.HandleGlobalTabDragPointerMove(context.hwnd)) { + context.workspaceCoordinator.HandleGlobalTabDragPointerMove(context.window)) { outResult = 0; return true; } @@ -274,7 +242,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowPointerMessage( if (CanRouteEditorWindowGlobalTabDragPointerMessages( inputController.GetPointerCaptureOwner(), context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && - context.workspaceCoordinator.HandleGlobalTabDragPointerButtonUp(context.hwnd)) { + context.workspaceCoordinator.HandleGlobalTabDragPointerButtonUp(context.window)) { outResult = 0; return true; } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowPointerCapture.h b/editor/app/Platform/Win32/Windowing/EditorWindowPointerCapture.h index eb12a4d2..9649e2d2 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowPointerCapture.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowPointerCapture.h @@ -1,42 +1,3 @@ #pragma once -#include - -namespace XCEngine::UI::Editor::App { - -enum class EditorWindowPointerCaptureOwner : std::uint8_t { - None = 0, - Shell, - HostedContent, - BorderlessResize, - BorderlessChrome, - GlobalTabDrag, -}; - -constexpr bool CanRouteEditorWindowGlobalTabDragPointerMessages( - EditorWindowPointerCaptureOwner owner, - bool ownsActiveGlobalTabDrag) { - return ownsActiveGlobalTabDrag && - owner == EditorWindowPointerCaptureOwner::GlobalTabDrag; -} - -constexpr bool CanRouteEditorWindowBorderlessResizePointerMessages( - EditorWindowPointerCaptureOwner owner) { - return owner == EditorWindowPointerCaptureOwner::BorderlessResize; -} - -constexpr bool CanRouteEditorWindowBorderlessChromePointerMessages( - EditorWindowPointerCaptureOwner owner) { - return owner == EditorWindowPointerCaptureOwner::BorderlessChrome; -} - -constexpr bool CanConsumeEditorWindowChromeHover( - EditorWindowPointerCaptureOwner owner, - bool shellInteractiveCaptureActive, - bool hostedContentCaptureActive) { - return owner == EditorWindowPointerCaptureOwner::None && - !shellInteractiveCaptureActive && - !hostedContentCaptureActive; -} - -} // namespace XCEngine::UI::Editor::App +#include "Windowing/Host/EditorWindowPointerCapture.h" diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowState.h b/editor/app/Platform/Win32/Windowing/EditorWindowState.h index f7c7d97b..05c7fa87 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowState.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowState.h @@ -4,73 +4,14 @@ #define NOMINMAX #endif +#include "Windowing/Host/EditorWindowTypes.h" + #include -#include #include -#include namespace XCEngine::UI::Editor::App { -enum class EditorWindowLifecycleState : std::uint8_t { - PendingNativeCreate = 0, - NativeAttached, - Initializing, - Running, - Closing, - Destroyed, -}; - -enum class EditorWindowCategory : std::uint8_t { - Workspace = 0, - Utility, -}; - -inline std::string_view GetEditorWindowCategoryName( - EditorWindowCategory category) { - switch (category) { - case EditorWindowCategory::Workspace: - return "Workspace"; - case EditorWindowCategory::Utility: - return "Utility"; - } - - return "Unknown"; -} - -struct EditorWindowChromePolicy { - bool allowDetachedTitleBarTabStrip = true; - bool showFrameStats = true; - bool showTopmostButton = false; - bool topmostByDefault = false; -}; - -struct EditorWindowNativeStylePolicy { - DWORD extendedWindowStyle = WS_EX_APPWINDOW; - DWORD windowStyle = 0; - bool useHostWindowStyle = true; -}; - -inline std::string_view GetEditorWindowLifecycleStateName( - EditorWindowLifecycleState state) { - switch (state) { - case EditorWindowLifecycleState::PendingNativeCreate: - return "PendingNativeCreate"; - case EditorWindowLifecycleState::NativeAttached: - return "NativeAttached"; - case EditorWindowLifecycleState::Initializing: - return "Initializing"; - case EditorWindowLifecycleState::Running: - return "Running"; - case EditorWindowLifecycleState::Closing: - return "Closing"; - case EditorWindowLifecycleState::Destroyed: - return "Destroyed"; - } - - return "Unknown"; -} - struct EditorWindowWindowState { HWND hwnd = nullptr; std::string windowId = {}; diff --git a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp index 0bc0ef7d..f42288c9 100644 --- a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp +++ b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp @@ -3,6 +3,12 @@ #include "Features/ColorPicker/ColorPickerPanel.h" #include "Features/Inspector/AddComponentPanel.h" +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + namespace XCEngine::UI::Editor::App { namespace { diff --git a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h index 7462f5f5..eda271e8 100644 --- a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h +++ b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h @@ -2,7 +2,7 @@ #include "UtilityWindows/EditorUtilityWindowKind.h" #include "UtilityWindows/EditorUtilityWindowPanel.h" -#include "Platform/Win32/Windowing/EditorWindowState.h" +#include "Windowing/Host/EditorWindowTypes.h" #include diff --git a/editor/app/Windowing/Content/EditorWindowContentFactory.cpp b/editor/app/Windowing/Content/EditorWindowContentFactory.cpp index 5ef277c3..2c6d3b9b 100644 --- a/editor/app/Windowing/Content/EditorWindowContentFactory.cpp +++ b/editor/app/Windowing/Content/EditorWindowContentFactory.cpp @@ -2,6 +2,7 @@ #include "Windowing/Content/EditorUtilityWindowContentController.h" #include "Windowing/Content/EditorWorkspaceWindowContentController.h" +#include "Windowing/System/EditorWindowSystem.h" #include @@ -13,10 +14,12 @@ class DefaultEditorWindowContentFactory final : public EditorWindowContentFactor public: std::unique_ptr CreateWorkspaceContentController( std::string_view windowId, - UIEditorWorkspaceController workspaceController) const override { + UIEditorWorkspaceController workspaceController, + EditorWindowSystem& windowSystem) const override { return CreateEditorWorkspaceWindowContentController( windowId, - std::move(workspaceController)); + std::move(workspaceController), + windowSystem); } std::unique_ptr CreateUtilityContentController( diff --git a/editor/app/Windowing/Content/EditorWindowContentFactory.h b/editor/app/Windowing/Content/EditorWindowContentFactory.h index 39bb0269..7eeb94f4 100644 --- a/editor/app/Windowing/Content/EditorWindowContentFactory.h +++ b/editor/app/Windowing/Content/EditorWindowContentFactory.h @@ -12,6 +12,7 @@ class UIEditorWorkspaceController; namespace XCEngine::UI::Editor::App { class EditorWindowContentController; +class EditorWindowSystem; struct EditorUtilityWindowDescriptor; class EditorWindowContentFactory { @@ -20,7 +21,8 @@ public: virtual std::unique_ptr CreateWorkspaceContentController( std::string_view windowId, - UIEditorWorkspaceController workspaceController) const = 0; + UIEditorWorkspaceController workspaceController, + EditorWindowSystem& windowSystem) const = 0; virtual std::unique_ptr CreateUtilityContentController( const EditorUtilityWindowDescriptor& descriptor) const = 0; }; diff --git a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp index cba85d52..9ed0f98e 100644 --- a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp @@ -1,5 +1,9 @@ #include "Windowing/Content/EditorWorkspaceWindowContentController.h" +#include "Windowing/System/EditorWindowPresentationPolicy.h" +#include "Windowing/System/EditorWindowSystem.h" + +#include #include #include @@ -49,10 +53,12 @@ EditorWorkspaceWindowProjection BuildWorkspaceProjectionFromController( EditorWorkspaceWindowContentController::EditorWorkspaceWindowContentController( std::string windowId, - UIEditorWorkspaceController workspaceController) + UIEditorWorkspaceController workspaceController, + EditorWindowSystem& windowSystem) : m_windowId(std::move(windowId)), - m_workspaceController(std::move(workspaceController)) { - m_projection = BuildWorkspaceProjectionFromController(m_workspaceController, m_windowId); + m_workspaceController(std::move(workspaceController)), + m_windowSystem(windowSystem) { + RefreshProjectionFromWorkspaceController(); } EditorWorkspaceWindowContentController::~EditorWorkspaceWindowContentController() = default; @@ -125,6 +131,27 @@ void EditorWorkspaceWindowContentController::RefreshWorkspaceProjection( m_projection = std::move(projection); } +void EditorWorkspaceWindowContentController::RefreshProjectionFromWorkspaceController(bool primary) { + const std::wstring preservedWindowTitle = m_projection.windowTitle; + m_projection = BuildWorkspaceProjectionFromController(m_workspaceController, m_windowId); + if (!primary) { + m_projection.windowTitle = ResolveEditorWindowPresentationTitle( + std::wstring_view{}, + m_workspaceController.GetPanelRegistry(), + m_projection.windowState, + false); + } else if (!preservedWindowTitle.empty()) { + m_projection.windowTitle = preservedWindowTitle; + } +} + +void EditorWorkspaceWindowContentController::RestoreWorkspaceControllerFromProjection() { + m_workspaceController = UIEditorWorkspaceController( + m_workspaceController.GetPanelRegistry(), + m_projection.windowState.workspace, + m_projection.windowState.session); +} + void EditorWorkspaceWindowContentController::Initialize( const EditorWindowContentInitializationContext& context) { m_shellRuntime.Initialize(context.repoRoot, context.textureHost, context.textMeasurer); @@ -165,14 +192,19 @@ EditorWindowFrameTransferRequests EditorWorkspaceWindowContentController::Update m_workspaceController.GetWorkspace(), m_workspaceController.GetSession()); if (!AreUIEditorWorkspaceLayoutSnapshotsEquivalent(beforeSnapshot, afterSnapshot)) { - transferRequests.workspace.workspaceMutation = EditorWindowWorkspaceMutationRequest{ - .windowState = - UIEditorWindowWorkspaceState{ - .windowId = m_windowId, - .workspace = m_workspaceController.GetWorkspace(), - .session = m_workspaceController.GetSession(), - }, - }; + std::string error = {}; + if (!m_windowSystem.CommitLiveWindowMutation( + m_windowId, + m_workspaceController, + error)) { + AppendUIEditorRuntimeTrace( + "window", + "workspace direct commit rejected for window '" + + m_windowId + "': " + error); + RestoreWorkspaceControllerFromProjection(); + } else { + RefreshProjectionFromWorkspaceController(context.primary); + } } return transferRequests; } @@ -263,10 +295,12 @@ std::string EditorWorkspaceWindowContentController::ResolveDetachedWindowTitleTe std::unique_ptr CreateEditorWorkspaceWindowContentController( std::string_view windowId, - UIEditorWorkspaceController workspaceController) { + UIEditorWorkspaceController workspaceController, + EditorWindowSystem& windowSystem) { return std::make_unique( std::string(windowId), - std::move(workspaceController)); + std::move(workspaceController), + windowSystem); } } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h index e6b18df1..3d34f1f9 100644 --- a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h @@ -10,6 +10,8 @@ namespace XCEngine::UI::Editor::App { +class EditorWindowSystem; + class EditorWorkspaceWindowContentController final : public EditorWindowContentController , public EditorWindowWorkspaceBinding @@ -19,7 +21,8 @@ class EditorWorkspaceWindowContentController final public: EditorWorkspaceWindowContentController( std::string windowId, - UIEditorWorkspaceController workspaceController); + UIEditorWorkspaceController workspaceController, + EditorWindowSystem& windowSystem); ~EditorWorkspaceWindowContentController() override; EditorWindowContentCapabilities GetCapabilities() const override; @@ -72,8 +75,12 @@ public: std::string_view fallbackWindowTitle) const override; private: + void RefreshProjectionFromWorkspaceController(bool primary = false); + void RestoreWorkspaceControllerFromProjection(); + std::string m_windowId = {}; UIEditorWorkspaceController m_workspaceController = {}; + EditorWindowSystem& m_windowSystem; EditorWorkspaceWindowProjection m_projection = {}; EditorShellRuntime m_shellRuntime = {}; EditorWindowFrameOrchestrator m_frameOrchestrator = {}; @@ -81,6 +88,7 @@ private: std::unique_ptr CreateEditorWorkspaceWindowContentController( std::string_view windowId, - UIEditorWorkspaceController workspaceController); + UIEditorWorkspaceController workspaceController, + EditorWindowSystem& windowSystem); } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp similarity index 70% rename from editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp rename to editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp index 870dec64..dc2766f0 100644 --- a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp @@ -1,10 +1,7 @@ -#include "Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h" +#include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" #include "UtilityWindows/EditorUtilityWindowRegistry.h" -#include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" -#include "Platform/Win32/Windowing/EditorWindow.h" -#include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" -#include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" +#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" #include @@ -12,29 +9,22 @@ namespace XCEngine::UI::Editor::App { namespace { -bool IsLiveWindow(const EditorWindow* window) { +bool IsLiveWindow(const EditorHostWindow* window) { return window != nullptr && - window->GetHwnd() != nullptr && + window->HasLiveHostWindow() && window->GetLifecycleState() == EditorWindowLifecycleState::Running; } -LONG ResolveOuterDimension(float value, LONG fallback) { +int ResolveOuterDimension(float value, int fallback) { return value > 0.0f - ? static_cast(std::lround(value)) + ? static_cast(std::lround(value)) : fallback; } -POINT ToNativePoint(const EditorWindowScreenPoint& point) { - return POINT{ - static_cast(point.x), - static_cast(point.y), - }; -} - } EditorUtilityWindowCoordinator::EditorUtilityWindowCoordinator( - EditorWindowHostRuntime& hostRuntime) + EditorWindowHost& hostRuntime) : m_hostRuntime(hostRuntime) {} EditorUtilityWindowCoordinator::~EditorUtilityWindowCoordinator() = default; @@ -45,7 +35,7 @@ void EditorUtilityWindowCoordinator::BindLifecycleCoordinator( } void EditorUtilityWindowCoordinator::HandleWindowFrameTransferRequests( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowFrameTransferRequests& transferRequests) { if (transferRequests.utility.openUtilityWindow.has_value() && transferRequests.utility.openUtilityWindow->IsValid()) { @@ -56,7 +46,7 @@ void EditorUtilityWindowCoordinator::HandleWindowFrameTransferRequests( } bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowOpenUtilityWindowRequest& request) { if (!IsLiveWindow(&sourceWindow)) { LogRuntimeTrace( @@ -73,13 +63,13 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( } if (descriptor->reusePolicy == EditorUtilityWindowReusePolicy::SingleInstance) { - if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); + if (EditorHostWindow* existingWindow = m_hostRuntime.FindWindowById(descriptor->windowId); existingWindow != nullptr && existingWindow->IsDestroyed() && m_lifecycleCoordinator != nullptr) { m_lifecycleCoordinator->ReapDestroyedWindows(); } - if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); + if (EditorHostWindow* existingWindow = m_hostRuntime.FindWindowById(descriptor->windowId); IsLiveWindow(existingWindow)) { if (!existingWindow->IsUtilityWindow()) { LogRuntimeTrace( @@ -87,14 +77,14 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( "open utility window request rejected: existing window id is not utility"); return false; } - FocusWindow(*existingWindow); + existingWindow->FocusHostWindow(); LogRuntimeTrace( "utility", "reused utility window '" + std::string(descriptor->windowId) + "'"); return true; } - if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); + if (EditorHostWindow* existingWindow = m_hostRuntime.FindWindowById(descriptor->windowId); existingWindow != nullptr) { LogRuntimeTrace( "utility", @@ -103,7 +93,7 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( } } - EditorWindowHostRuntime::CreateParams createParams = {}; + EditorWindowCreateParams createParams = {}; createParams.windowId = std::string(descriptor->windowId); createParams.title = descriptor->title; createParams.category = EditorWindowCategory::Utility; @@ -117,39 +107,30 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( descriptor->preferredOuterSize.height, createParams.initialHeight); if (request.useCursorPlacement) { - const RECT rect = BuildEditorFloatingWindowRect( - ToNativePoint(request.screenPoint), + const EditorWindowScreenRect rect = m_hostRuntime.ResolveFloatingPlacement( + request.screenPoint, createParams.initialWidth, createParams.initialHeight); createParams.initialX = rect.left; createParams.initialY = rect.top; - createParams.initialWidth = rect.right - rect.left; - createParams.initialHeight = rect.bottom - rect.top; + createParams.initialWidth = rect.Width(); + createParams.initialHeight = rect.Height(); } - EditorWindow* utilityWindow = + EditorHostWindow* utilityWindow = m_hostRuntime.CreateUtilityWindow(*descriptor, createParams); if (utilityWindow == nullptr) { LogRuntimeTrace("utility", "failed to create utility window"); return false; } - FocusWindow(*utilityWindow); + utilityWindow->FocusHostWindow(); LogRuntimeTrace( "utility", "opened utility window '" + std::string(descriptor->windowId) + "'"); return true; } -void EditorUtilityWindowCoordinator::FocusWindow(EditorWindow& window) const { - if (!IsLiveWindow(&window)) { - return; - } - - ShowWindow(window.GetHwnd(), SW_RESTORE); - SetForegroundWindow(window.GetHwnd()); -} - void EditorUtilityWindowCoordinator::LogRuntimeTrace( std::string_view channel, std::string_view message) const { diff --git a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h similarity index 72% rename from editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h rename to editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h index 87411a82..619c9f43 100644 --- a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h +++ b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h @@ -1,33 +1,31 @@ #pragma once #include "Windowing/Frame/EditorWindowTransferRequests.h" +#include "Windowing/Host/EditorWindowHostInterfaces.h" #include namespace XCEngine::UI::Editor::App { -class EditorWindow; -class EditorWindowHostRuntime; class EditorWindowLifecycleCoordinator; class EditorUtilityWindowCoordinator final { public: - explicit EditorUtilityWindowCoordinator(EditorWindowHostRuntime& hostRuntime); + explicit EditorUtilityWindowCoordinator(EditorWindowHost& hostRuntime); ~EditorUtilityWindowCoordinator(); void BindLifecycleCoordinator(EditorWindowLifecycleCoordinator& lifecycleCoordinator); void HandleWindowFrameTransferRequests( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowFrameTransferRequests& transferRequests); private: bool TryProcessOpenUtilityWindowRequest( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowOpenUtilityWindowRequest& request); - void FocusWindow(EditorWindow& window) const; void LogRuntimeTrace(std::string_view channel, std::string_view message) const; - EditorWindowHostRuntime& m_hostRuntime; + EditorWindowHost& m_hostRuntime; EditorWindowLifecycleCoordinator* m_lifecycleCoordinator = nullptr; }; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.cpp similarity index 50% rename from editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp rename to editor/app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.cpp index 6e4ac1b8..2710f7f7 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.cpp @@ -1,71 +1,29 @@ -#include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" +#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" -#include "Platform/Win32/Windowing/EditorWindow.h" -#include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" -#include "Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" -#include -#include -#include #include namespace XCEngine::UI::Editor::App { -namespace { - -std::string DescribeHwnd(HWND hwnd) { - std::ostringstream stream = {}; - stream << "0x" << std::hex << std::uppercase - << reinterpret_cast(hwnd); - return stream.str(); -} - -std::string DescribeHostWindows( - const std::vector>& windows) { - std::ostringstream stream = {}; - stream << "count=" << windows.size() << " ["; - bool first = true; - for (const std::unique_ptr& window : windows) { - if (!first) { - stream << ", "; - } - first = false; - if (window == nullptr) { - stream << ""; - continue; - } - - stream << window->GetWindowId() - << "{hwnd=" << DescribeHwnd(window->GetHwnd()) - << ",primary=" << (window->IsPrimary() ? '1' : '0') - << ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState()) - << '}'; - } - stream << ']'; - return stream.str(); -} - -} // namespace - EditorWindowLifecycleCoordinator::EditorWindowLifecycleCoordinator( - EditorWindowHostRuntime& hostRuntime, + EditorWindowHost& hostRuntime, EditorWindowWorkspaceCoordinator& workspaceCoordinator) : m_hostRuntime(hostRuntime), m_workspaceCoordinator(workspaceCoordinator) {} EditorWindowLifecycleCoordinator::~EditorWindowLifecycleCoordinator() = default; -void EditorWindowLifecycleCoordinator::PostCloseRequest(EditorWindow& window) { +void EditorWindowLifecycleCoordinator::PostCloseRequest(EditorHostWindow& window) { if (window.IsDestroyed()) { return; } - const HWND hwnd = window.GetHwnd(); if (!window.IsClosing()) { window.MarkClosing(); } - if (hwnd == nullptr || !IsWindow(hwnd)) { + if (!window.HasLiveHostWindow()) { ShutdownRuntimeIfNeeded(window); window.MarkDestroyed(); return; @@ -74,27 +32,24 @@ void EditorWindowLifecycleCoordinator::PostCloseRequest(EditorWindow& window) { LogRuntimeTrace( "window-close", "PostCloseRequest windowId='" + std::string(window.GetWindowId()) + - "' hwnd=" + DescribeHwnd(hwnd) + " primary=" + (window.IsPrimary() ? "1" : "0") + - " host=" + DescribeHostWindows(m_hostRuntime.GetWindows())); - PostMessageW(hwnd, WM_CLOSE, 0, 0); + " host=" + m_hostRuntime.DescribeWindows()); + window.PostCloseToHost(); } -void EditorWindowLifecycleCoordinator::ExecuteCloseRequest(EditorWindow& window) { +void EditorWindowLifecycleCoordinator::ExecuteCloseRequest(EditorHostWindow& window) { if (window.IsDestroyed()) { return; } - const HWND hwnd = window.GetHwnd(); LogRuntimeTrace( "window-close", "ExecuteCloseRequest begin windowId='" + std::string(window.GetWindowId()) + - "' hwnd=" + DescribeHwnd(hwnd) + " primary=" + (window.IsPrimary() ? "1" : "0") + " lifecycleBefore=" + std::string(GetEditorWindowLifecycleStateName(window.GetLifecycleState())) + " workspace=" + m_workspaceCoordinator.DescribeWindowSet() + - " host=" + DescribeHostWindows(m_hostRuntime.GetWindows())); + " host=" + m_hostRuntime.DescribeWindows()); if (!window.IsClosing()) { window.MarkClosing(); @@ -102,8 +57,8 @@ void EditorWindowLifecycleCoordinator::ExecuteCloseRequest(EditorWindow& window) ShutdownRuntimeIfNeeded(window); - if (hwnd != nullptr && IsWindow(hwnd)) { - DestroyWindow(hwnd); + if (window.HasLiveHostWindow()) { + window.DestroyHostWindow(); } else { window.MarkDestroyed(); } @@ -111,10 +66,10 @@ void EditorWindowLifecycleCoordinator::ExecuteCloseRequest(EditorWindow& window) LogRuntimeTrace( "window-close", "ExecuteCloseRequest end windowId='" + std::string(window.GetWindowId()) + - "' host=" + DescribeHostWindows(m_hostRuntime.GetWindows())); + "' host=" + m_hostRuntime.DescribeWindows()); } -void EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(EditorWindow& window) { +void EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(EditorHostWindow& window) { if (window.IsDestroyed()) { return; } @@ -124,11 +79,10 @@ void EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(EditorWindow& LogRuntimeTrace( "window-close", "HandleNativeWindowDestroyed begin windowId='" + std::string(window.GetWindowId()) + - "' hwnd=" + DescribeHwnd(window.GetHwnd()) + " destroyedPrimary=" + (destroyedPrimary ? "1" : "0") + " localPrimary=" + (window.IsPrimary() ? "1" : "0") + " workspaceBefore=" + m_workspaceCoordinator.DescribeWindowSet() + - " hostBefore=" + DescribeHostWindows(m_hostRuntime.GetWindows())); + " hostBefore=" + m_hostRuntime.DescribeWindows()); if (m_workspaceCoordinator.OwnsActiveGlobalTabDrag(window.GetWindowId())) { m_workspaceCoordinator.EndGlobalTabDragSession(); @@ -141,19 +95,20 @@ void EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(EditorWindow& } if (destroyedPrimary) { - std::vector closeTargets = {}; - closeTargets.reserve(m_hostRuntime.GetWindows().size()); - for (const std::unique_ptr& otherWindow : m_hostRuntime.GetWindows()) { + std::vector closeTargets = {}; + const std::vector windows = m_hostRuntime.GetWindows(); + closeTargets.reserve(windows.size()); + for (EditorHostWindow* otherWindow : windows) { if (otherWindow == nullptr || - otherWindow.get() == &window || - otherWindow->GetHwnd() == nullptr || + otherWindow == &window || + !otherWindow->HasLiveHostWindow() || otherWindow->IsClosing()) { continue; } - closeTargets.push_back(otherWindow.get()); + closeTargets.push_back(otherWindow); } - for (EditorWindow* closeTarget : closeTargets) { + for (EditorHostWindow* closeTarget : closeTargets) { if (closeTarget != nullptr) { PostCloseRequest(*closeTarget); } @@ -164,24 +119,22 @@ void EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(EditorWindow& "window-close", "HandleNativeWindowDestroyed end windowId='" + std::string(window.GetWindowId()) + "' workspaceAfter=" + m_workspaceCoordinator.DescribeWindowSet() + - " hostAfter=" + DescribeHostWindows(m_hostRuntime.GetWindows())); + " hostAfter=" + m_hostRuntime.DescribeWindows()); } -void EditorWindowLifecycleCoordinator::AbortUnregisteredWindow(EditorWindow& window) { - const HWND hwnd = window.GetHwnd(); +void EditorWindowLifecycleCoordinator::AbortUnregisteredWindow(EditorHostWindow& window) { LogRuntimeTrace( "window-close", "AbortUnregisteredWindow begin windowId='" + std::string(window.GetWindowId()) + - "' hwnd=" + DescribeHwnd(hwnd) + - " hostBefore=" + DescribeHostWindows(m_hostRuntime.GetWindows())); + " hostBefore=" + m_hostRuntime.DescribeWindows()); if (!window.IsClosing()) { window.MarkClosing(); } ShutdownRuntimeIfNeeded(window); - if (hwnd != nullptr && IsWindow(hwnd)) { - DestroyWindow(hwnd); + if (window.HasLiveHostWindow()) { + window.DestroyHostWindow(); } else { window.MarkDestroyed(); } @@ -190,19 +143,20 @@ void EditorWindowLifecycleCoordinator::AbortUnregisteredWindow(EditorWindow& win LogRuntimeTrace( "window-close", "AbortUnregisteredWindow end windowId='" + std::string(window.GetWindowId()) + - "' hostAfter=" + DescribeHostWindows(m_hostRuntime.GetWindows())); + "' hostAfter=" + m_hostRuntime.DescribeWindows()); } void EditorWindowLifecycleCoordinator::ShutdownAllWindows() { - std::vector closeTargets = {}; - closeTargets.reserve(m_hostRuntime.GetWindows().size()); - for (const std::unique_ptr& window : m_hostRuntime.GetWindows()) { + std::vector closeTargets = {}; + const std::vector windows = m_hostRuntime.GetWindows(); + closeTargets.reserve(windows.size()); + for (EditorHostWindow* window : windows) { if (window != nullptr) { - closeTargets.push_back(window.get()); + closeTargets.push_back(window); } } - for (EditorWindow* closeTarget : closeTargets) { + for (EditorHostWindow* closeTarget : closeTargets) { if (closeTarget != nullptr) { ExecuteCloseRequest(*closeTarget); } @@ -212,30 +166,10 @@ void EditorWindowLifecycleCoordinator::ShutdownAllWindows() { } void EditorWindowLifecycleCoordinator::ReapDestroyedWindows() { - auto& windows = m_hostRuntime.GetWindows(); - for (auto it = windows.begin(); it != windows.end();) { - EditorWindow* const window = it->get(); - if (window == nullptr || !window->IsDestroyed()) { - ++it; - continue; - } - - if (m_hostRuntime.m_pendingCreateWindow == window) { - m_hostRuntime.m_pendingCreateWindow = nullptr; - } - - LogRuntimeTrace( - "window-close", - "ReapDestroyedWindows erase windowId='" + std::string(window->GetWindowId()) + - "' hostBefore=" + DescribeHostWindows(windows)); - it = windows.erase(it); - LogRuntimeTrace( - "window-close", - "ReapDestroyedWindows erase end hostAfter=" + DescribeHostWindows(windows)); - } + m_hostRuntime.ReapDestroyedWindows(); } -void EditorWindowLifecycleCoordinator::ShutdownRuntimeIfNeeded(EditorWindow& window) { +void EditorWindowLifecycleCoordinator::ShutdownRuntimeIfNeeded(EditorHostWindow& window) { if (window.IsRenderReady()) { window.Shutdown(); } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h b/editor/app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.h similarity index 57% rename from editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h rename to editor/app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.h index 64c7c5ba..5a9e20c9 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h +++ b/editor/app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.h @@ -1,32 +1,32 @@ #pragma once +#include "Windowing/Host/EditorWindowHostInterfaces.h" + #include namespace XCEngine::UI::Editor::App { -class EditorWindow; -class EditorWindowHostRuntime; class EditorWindowWorkspaceCoordinator; class EditorWindowLifecycleCoordinator final { public: EditorWindowLifecycleCoordinator( - EditorWindowHostRuntime& hostRuntime, + EditorWindowHost& hostRuntime, EditorWindowWorkspaceCoordinator& workspaceCoordinator); ~EditorWindowLifecycleCoordinator(); - void PostCloseRequest(EditorWindow& window); - void ExecuteCloseRequest(EditorWindow& window); - void HandleNativeWindowDestroyed(EditorWindow& window); - void AbortUnregisteredWindow(EditorWindow& window); + void PostCloseRequest(EditorHostWindow& window); + void ExecuteCloseRequest(EditorHostWindow& window); + void HandleNativeWindowDestroyed(EditorHostWindow& window); + void AbortUnregisteredWindow(EditorHostWindow& window); void ShutdownAllWindows(); void ReapDestroyedWindows(); private: - void ShutdownRuntimeIfNeeded(EditorWindow& window); + void ShutdownRuntimeIfNeeded(EditorHostWindow& window); void LogRuntimeTrace(std::string_view channel, std::string_view message) const; - EditorWindowHostRuntime& m_hostRuntime; + EditorWindowHost& m_hostRuntime; EditorWindowWorkspaceCoordinator& m_workspaceCoordinator; }; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp similarity index 81% rename from editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp rename to editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp index 222c1805..1a431927 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp @@ -1,13 +1,9 @@ -#include "Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" -#include "Composition/EditorContext.h" -#include "Platform/Win32/Windowing/EditorWindow.h" -#include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" -#include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" -#include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" +#include "Windowing/Content/EditorWindowContentController.h" +#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" #include "Windowing/System/EditorWindowPresentationPolicy.h" #include "Windowing/System/EditorWindowSystem.h" -#include "Windowing/Content/EditorWindowContentController.h" #include #include @@ -17,6 +13,7 @@ #include #include +#include #include #include @@ -32,37 +29,30 @@ struct ExistingWindowSnapshot { using ::XCEngine::UI::UIPoint; -constexpr LONG kFallbackDragHotspotX = 40; -constexpr LONG kFallbackDragHotspotY = 12; +constexpr std::int32_t kFallbackDragHotspotX = 40; +constexpr std::int32_t kFallbackDragHotspotY = 12; -POINT BuildFallbackGlobalTabDragHotspot() { - POINT hotspot = {}; +EditorWindowScreenPoint BuildFallbackGlobalTabDragHotspot() { + EditorWindowScreenPoint hotspot = {}; hotspot.x = kFallbackDragHotspotX; hotspot.y = kFallbackDragHotspotY; return hotspot; } -POINT ToNativePoint(const EditorWindowScreenPoint& point) { - return POINT{ - static_cast(point.x), - static_cast(point.y), - }; -} - -std::wstring_view ResolvePrimaryWindowTitle(const EditorWindowHostRuntime& hostRuntime) { - return hostRuntime.GetHostConfig().primaryWindowTitle != nullptr - ? std::wstring_view(hostRuntime.GetHostConfig().primaryWindowTitle) - : std::wstring_view{}; -} - bool CanStartGlobalTabDragFromWindow( - const EditorWindow& sourceWindow, + const EditorHostWindow& sourceWindow, std::string_view sourceNodeId, std::string_view panelId) { + const UIEditorWorkspaceController* workspaceController = + sourceWindow.TryGetWorkspaceController(); + if (workspaceController == nullptr) { + return false; + } + UIEditorWorkspaceModel workspace = - sourceWindow.GetWorkspaceController().GetWorkspace(); + workspaceController->GetWorkspace(); UIEditorWorkspaceSession session = - sourceWindow.GetWorkspaceController().GetSession(); + workspaceController->GetSession(); UIEditorWorkspaceExtractedPanel extractedPanel = {}; return TryExtractUIEditorWorkspaceVisiblePanel( workspace, @@ -72,9 +62,9 @@ bool CanStartGlobalTabDragFromWindow( extractedPanel); } -bool IsLiveInteractiveWindow(const EditorWindow* window) { +bool IsLiveInteractiveWindow(const EditorHostWindow* window) { return window != nullptr && - window->GetHwnd() != nullptr && + window->HasLiveHostWindow() && window->GetLifecycleState() == EditorWindowLifecycleState::Running; } @@ -99,7 +89,7 @@ std::string DescribeWindowSetState(const UIEditorWindowWorkspaceSet& windowSet) } // namespace EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator( - EditorWindowHostRuntime& hostRuntime, + EditorWindowHost& hostRuntime, EditorWindowSystem& windowSystem) : m_hostRuntime(hostRuntime), m_windowSystem(windowSystem) {} @@ -111,12 +101,12 @@ void EditorWindowWorkspaceCoordinator::BindLifecycleCoordinator( m_lifecycleCoordinator = &lifecycleCoordinator; } -void EditorWindowWorkspaceCoordinator::RegisterExistingWindow(EditorWindow& window) { +void EditorWindowWorkspaceCoordinator::RegisterExistingWindow(EditorHostWindow& window) { RefreshWorkspaceProjectionFromAuthoritativeState(window); RefreshWindowTitle(window); } -void EditorWindowWorkspaceCoordinator::RefreshWindowPresentation(EditorWindow& window) { +void EditorWindowWorkspaceCoordinator::RefreshWindowPresentation(EditorHostWindow& window) { if (!window.IsWorkspaceWindow()) { return; } @@ -128,7 +118,7 @@ void EditorWindowWorkspaceCoordinator::HandleNativeWindowDestroyed(std::string_v EditorWindowSynchronizationPlan plan = m_windowSystem.BuildPlanForDestroyedWindow( windowId, CaptureHostSnapshots(), - ResolvePrimaryWindowTitle(m_hostRuntime), + m_hostRuntime.GetPrimaryWindowTitle(), error); if (!plan.valid) { LogRuntimeTrace( @@ -160,7 +150,7 @@ EditorWindowWorkspaceCoordinator::BuildWorkspaceMutationController() const { } UIEditorWindowWorkspaceState EditorWindowWorkspaceCoordinator::BuildWindowStateForWindow( - const EditorWindow& window) const { + const EditorHostWindow& window) const { if (const EditorWorkspaceWindowProjection* projection = window.TryGetWorkspaceProjection(); projection != nullptr) { return projection->windowState; @@ -181,7 +171,7 @@ EditorWorkspaceWindowProjection EditorWindowWorkspaceCoordinator::BuildWorkspace bool primary, std::wstring title) const { EditorWorkspaceWindowProjection projection = BuildEditorWorkspaceWindowProjection( - ResolvePrimaryWindowTitle(m_hostRuntime), + m_hostRuntime.GetPrimaryWindowTitle(), m_windowSystem.GetPanelRegistry(), windowState, primary); @@ -192,7 +182,7 @@ EditorWorkspaceWindowProjection EditorWindowWorkspaceCoordinator::BuildWorkspace } EditorWorkspaceWindowProjection EditorWindowWorkspaceCoordinator::BuildWorkspaceProjectionForWindow( - const EditorWindow& window) const { + const EditorHostWindow& window) const { if (const EditorWorkspaceWindowProjection* projection = window.TryGetWorkspaceProjection(); projection != nullptr) { return *projection; @@ -205,7 +195,7 @@ EditorWorkspaceWindowProjection EditorWindowWorkspaceCoordinator::BuildWorkspace } bool EditorWindowWorkspaceCoordinator::RefreshWorkspaceProjectionFromAuthoritativeState( - EditorWindow& window) const { + EditorHostWindow& window) const { if (!window.IsWorkspaceWindow()) { return false; } @@ -233,7 +223,7 @@ bool EditorWindowWorkspaceCoordinator::CommitLiveWindowMutation( EditorWindowSynchronizationPlan plan = m_windowSystem.BuildPlanForWorkspaceMutationRequest( request, CaptureHostSnapshots(), - ResolvePrimaryWindowTitle(m_hostRuntime), + m_hostRuntime.GetPrimaryWindowTitle(), error); if (!plan.valid) { LogRuntimeTrace( @@ -246,7 +236,7 @@ bool EditorWindowWorkspaceCoordinator::CommitLiveWindowMutation( return ApplySynchronizationPlan(plan); } -void EditorWindowWorkspaceCoordinator::RefreshWindowTitle(EditorWindow& window) const { +void EditorWindowWorkspaceCoordinator::RefreshWindowTitle(EditorHostWindow& window) const { const EditorWorkspaceWindowProjection* projection = window.TryGetWorkspaceProjection(); if (projection == nullptr || projection->windowTitle.empty()) { return; @@ -258,35 +248,33 @@ void EditorWindowWorkspaceCoordinator::RefreshWindowTitle(EditorWindow& window) } window.SetTitle(title); - if (window.GetHwnd() != nullptr) { - SetWindowTextW(window.GetHwnd(), window.GetTitle().c_str()); - } + window.ApplyHostWindowTitle(); } bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( const UIEditorWindowWorkspaceSet& windowSet, std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint, - LONG preferredWidth, - LONG preferredHeight) { + const EditorWindowScreenPoint& preferredScreenPoint, + int preferredWidth, + int preferredHeight) { EditorWindowSynchronizationPlacement preferredPlacement = {}; if (!preferredNewWindowId.empty()) { - const RECT detachedRect = BuildEditorFloatingWindowRect( + const EditorWindowScreenRect detachedRect = m_hostRuntime.ResolveFloatingPlacement( preferredScreenPoint, preferredWidth, preferredHeight); preferredPlacement.enabled = true; preferredPlacement.initialX = detachedRect.left; preferredPlacement.initialY = detachedRect.top; - preferredPlacement.initialWidth = detachedRect.right - detachedRect.left; - preferredPlacement.initialHeight = detachedRect.bottom - detachedRect.top; + preferredPlacement.initialWidth = detachedRect.Width(); + preferredPlacement.initialHeight = detachedRect.Height(); } std::string error = {}; EditorWindowSynchronizationPlan plan = m_windowSystem.BuildPlanForWindowSet( windowSet, CaptureHostSnapshots(), - ResolvePrimaryWindowTitle(m_hostRuntime), + m_hostRuntime.GetPrimaryWindowTitle(), preferredNewWindowId, preferredPlacement, error); @@ -300,9 +288,11 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( std::vector EditorWindowWorkspaceCoordinator::CaptureHostSnapshots() const { std::vector snapshots = {}; - snapshots.reserve(m_hostRuntime.GetWindows().size()); + const std::vector windows = + std::as_const(m_hostRuntime).GetWindows(); + snapshots.reserve(windows.size()); - for (const std::unique_ptr& window : m_hostRuntime.GetWindows()) { + for (const EditorHostWindow* window : windows) { if (window == nullptr) { continue; } @@ -314,7 +304,7 @@ std::vector EditorWindowWorkspaceCoordinator::CaptureH snapshot.primary = window->IsPrimary(); snapshot.running = window->GetLifecycleState() == EditorWindowLifecycleState::Running; snapshot.destroyed = window->IsDestroyed(); - snapshot.hasNativeWindow = window->GetHwnd() != nullptr; + snapshot.hasNativeWindow = window->HasLiveHostWindow(); snapshot.title = window->GetTitle(); if (window->TryGetWorkspaceProjection() != nullptr || window->TryGetWorkspaceController() != nullptr) { @@ -330,7 +320,7 @@ std::vector EditorWindowWorkspaceCoordinator::CaptureH bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( const EditorWindowSynchronizationPlan& plan) { const auto restoreWindowSnapshot = [this](const ExistingWindowSnapshot& snapshot) { - EditorWindow* const window = m_hostRuntime.FindWindow(snapshot.windowId); + EditorHostWindow* const window = m_hostRuntime.FindWindowById(snapshot.windowId); if (window == nullptr) { return; } @@ -342,7 +332,7 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( }; const auto destroyAndEraseWindowById = [this](std::string_view windowId) { - EditorWindow* const window = m_hostRuntime.FindWindow(windowId); + EditorHostWindow* const window = m_hostRuntime.FindWindowById(windowId); if (window == nullptr || m_lifecycleCoordinator == nullptr) { return false; } @@ -357,11 +347,11 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( for (const EditorWindowSynchronizationAction& action : plan.actions) { switch (action.kind) { case EditorWindowSynchronizationActionKind::UpdateWorkspaceWindow: { - EditorWindow* existingWindow = - m_hostRuntime.FindWindow(action.update.windowState.windowId); + EditorHostWindow* existingWindow = + m_hostRuntime.FindWindowById(action.update.windowState.windowId); if (existingWindow == nullptr && m_lifecycleCoordinator != nullptr) { m_lifecycleCoordinator->ReapDestroyedWindows(); - existingWindow = m_hostRuntime.FindWindow(action.update.windowState.windowId); + existingWindow = m_hostRuntime.FindWindowById(action.update.windowState.windowId); } if (existingWindow == nullptr) { return false; @@ -383,7 +373,7 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( break; } case EditorWindowSynchronizationActionKind::CreateWorkspaceWindow: { - EditorWindowHostRuntime::CreateParams createParams = {}; + EditorWindowCreateParams createParams = {}; createParams.windowId = action.create.windowState.windowId; createParams.category = EditorWindowCategory::Workspace; createParams.primary = action.create.primary; @@ -395,7 +385,7 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( createParams.initialHeight = action.create.placement.initialHeight; } - EditorWindow* const createdWindow = m_hostRuntime.CreateWorkspaceWindow( + EditorHostWindow* const createdWindow = m_hostRuntime.CreateWorkspaceWindow( BuildWorkspaceControllerForWindowState( m_windowSystem.GetPanelRegistry(), action.create.windowState), @@ -419,7 +409,7 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( } case EditorWindowSynchronizationActionKind::CloseWorkspaceWindow: if (m_lifecycleCoordinator != nullptr) { - if (EditorWindow* window = m_hostRuntime.FindWindow(action.close.windowId); + if (EditorHostWindow* window = m_hostRuntime.FindWindowById(action.close.windowId); window != nullptr) { m_lifecycleCoordinator->PostCloseRequest(*window); } @@ -442,9 +432,9 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( bool EditorWindowWorkspaceCoordinator::CommitWindowWorkspaceMutation( const UIEditorWindowWorkspaceController& windowWorkspaceController, std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint, - LONG preferredWidth, - LONG preferredHeight) { + const EditorWindowScreenPoint& preferredScreenPoint, + int preferredWidth, + int preferredHeight) { const UIEditorWindowWorkspaceSet nextWindowSet = windowWorkspaceController.GetWindowSet(); std::string error = {}; if (!m_windowSystem.ValidateWindowSet(nextWindowSet, error)) { @@ -478,8 +468,8 @@ void EditorWindowWorkspaceCoordinator::BeginGlobalTabDragSession( std::string_view panelWindowId, std::string_view sourceNodeId, std::string_view panelId, - const POINT& screenPoint, - const POINT& dragHotspot) { + const EditorWindowScreenPoint& screenPoint, + const EditorWindowScreenPoint& dragHotspot) { m_globalTabDragSession.active = true; m_globalTabDragSession.panelWindowId = std::string(panelWindowId); m_globalTabDragSession.sourceNodeId = std::string(sourceNodeId); @@ -489,11 +479,11 @@ void EditorWindowWorkspaceCoordinator::BeginGlobalTabDragSession( } bool EditorWindowWorkspaceCoordinator::TryResolveGlobalTabDragHotspot( - const EditorWindow& sourceWindow, + const EditorHostWindow& sourceWindow, std::string_view nodeId, std::string_view panelId, - const POINT& screenPoint, - POINT& outDragHotspot) const { + const EditorWindowScreenPoint& screenPoint, + EditorWindowScreenPoint& outDragHotspot) const { return sourceWindow.TryResolveDockTabDragHotspot( nodeId, panelId, @@ -506,32 +496,28 @@ void EditorWindowWorkspaceCoordinator::UpdateGlobalTabDragOwnerWindowPosition() return; } - EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); + EditorHostWindow* ownerWindow = m_hostRuntime.FindWindowById(m_globalTabDragSession.panelWindowId); if (!IsLiveInteractiveWindow(ownerWindow)) { return; } - RECT windowRect = {}; - if (!GetWindowRect(ownerWindow->GetHwnd(), &windowRect)) { + EditorWindowScreenRect windowRect = {}; + if (!ownerWindow->TryGetHostScreenRect(windowRect)) { return; } - const LONG targetLeft = + const std::int32_t targetLeft = m_globalTabDragSession.screenPoint.x - m_globalTabDragSession.dragHotspot.x; - const LONG targetTop = + const std::int32_t targetTop = m_globalTabDragSession.screenPoint.y - m_globalTabDragSession.dragHotspot.y; if (windowRect.left == targetLeft && windowRect.top == targetTop) { return; } - SetWindowPos( - ownerWindow->GetHwnd(), - nullptr, - targetLeft, - targetTop, - 0, - 0, - SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + ownerWindow->SetHostScreenPosition(EditorWindowScreenPoint{ + .x = targetLeft, + .y = targetTop, + }); } void EditorWindowWorkspaceCoordinator::ClearGlobalTabDragDropPreview() { @@ -539,7 +525,7 @@ void EditorWindowWorkspaceCoordinator::ClearGlobalTabDragDropPreview() { return; } - if (EditorWindow* previewWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.previewWindowId); + if (EditorHostWindow* previewWindow = m_hostRuntime.FindWindowById(m_globalTabDragSession.previewWindowId); IsLiveInteractiveWindow(previewWindow)) { if (EditorWindowDockHostBinding* dockHostBinding = previewWindow->TryGetDockHostBinding(); @@ -556,7 +542,7 @@ void EditorWindowWorkspaceCoordinator::UpdateGlobalTabDragDropPreview() { return; } - EditorWindow* targetWindow = FindTopmostWindowAtScreenPoint( + EditorHostWindow* targetWindow = FindTopmostWindowAtScreenPoint( m_globalTabDragSession.screenPoint, m_globalTabDragSession.panelWindowId); if (!IsLiveInteractiveWindow(targetWindow)) { @@ -600,7 +586,7 @@ void EditorWindowWorkspaceCoordinator::EndGlobalTabDragSession() { ClearGlobalTabDragDropPreview(); - if (EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); + if (EditorHostWindow* ownerWindow = m_hostRuntime.FindWindowById(m_globalTabDragSession.panelWindowId); ownerWindow != nullptr) { ownerWindow->ReleasePointerCapture(EditorWindowPointerCaptureOwner::GlobalTabDrag); if (IsLiveInteractiveWindow(ownerWindow)) { @@ -611,22 +597,22 @@ void EditorWindowWorkspaceCoordinator::EndGlobalTabDragSession() { m_globalTabDragSession = {}; } -bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerMove(HWND hwnd) { +bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerMove(EditorHostWindow& window) { if (!m_globalTabDragSession.active) { return false; } - const EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); + const EditorHostWindow* ownerWindow = m_hostRuntime.FindWindowById(m_globalTabDragSession.panelWindowId); if (!IsLiveInteractiveWindow(ownerWindow)) { EndGlobalTabDragSession(); return false; } - if (ownerWindow->GetHwnd() != hwnd) { + if (ownerWindow->GetWindowId() != window.GetWindowId()) { return false; } - POINT screenPoint = {}; - if (GetCursorPos(&screenPoint)) { + EditorWindowScreenPoint screenPoint = {}; + if (m_hostRuntime.TryGetCursorScreenPoint(screenPoint)) { m_globalTabDragSession.screenPoint = screenPoint; UpdateGlobalTabDragOwnerWindowPosition(); UpdateGlobalTabDragDropPreview(); @@ -634,29 +620,29 @@ bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerMove(HWND hwnd) return true; } -bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { +bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(EditorHostWindow& window) { if (!m_globalTabDragSession.active) { return false; } - const EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); + const EditorHostWindow* ownerWindow = m_hostRuntime.FindWindowById(m_globalTabDragSession.panelWindowId); if (!IsLiveInteractiveWindow(ownerWindow)) { EndGlobalTabDragSession(); return false; } - if (ownerWindow->GetHwnd() != hwnd) { + if (ownerWindow->GetWindowId() != window.GetWindowId()) { return false; } - POINT screenPoint = m_globalTabDragSession.screenPoint; - GetCursorPos(&screenPoint); + EditorWindowScreenPoint screenPoint = m_globalTabDragSession.screenPoint; + m_hostRuntime.TryGetCursorScreenPoint(screenPoint); const std::string panelWindowId = m_globalTabDragSession.panelWindowId; const std::string sourceNodeId = m_globalTabDragSession.sourceNodeId; const std::string panelId = m_globalTabDragSession.panelId; EndGlobalTabDragSession(); - EditorWindow* targetWindow = FindTopmostWindowAtScreenPoint(screenPoint, panelWindowId); + EditorHostWindow* targetWindow = FindTopmostWindowAtScreenPoint(screenPoint, panelWindowId); if (!IsLiveInteractiveWindow(targetWindow)) { return true; } @@ -697,9 +683,9 @@ bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND h return true; } - if (EditorWindow* updatedTargetWindow = m_hostRuntime.FindWindow(targetWindow->GetWindowId()); + if (EditorHostWindow* updatedTargetWindow = m_hostRuntime.FindWindowById(targetWindow->GetWindowId()); IsLiveInteractiveWindow(updatedTargetWindow)) { - SetForegroundWindow(updatedTargetWindow->GetHwnd()); + updatedTargetWindow->FocusHostWindow(); } LogRuntimeTrace( "drag", @@ -709,7 +695,7 @@ bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND h } bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowPanelTransferRequest& request) { if (!sourceWindow.IsWorkspaceWindow()) { LogRuntimeTrace( @@ -723,12 +709,12 @@ bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( return false; } - POINT dragHotspot = BuildFallbackGlobalTabDragHotspot(); + EditorWindowScreenPoint dragHotspot = BuildFallbackGlobalTabDragHotspot(); TryResolveGlobalTabDragHotspot( sourceWindow, request.nodeId, request.panelId, - ToNativePoint(request.screenPoint), + request.screenPoint, dragHotspot); const auto tryStartDetachedPanelGlobalDrag = @@ -738,12 +724,12 @@ bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( if (!CommitWindowWorkspaceMutation( windowWorkspaceController, result.targetWindowId, - ToNativePoint(request.screenPoint))) { + request.screenPoint)) { LogRuntimeTrace("drag", "failed to synchronize detached drag window state"); return false; } - EditorWindow* detachedWindow = m_hostRuntime.FindWindow(result.targetWindowId); + EditorHostWindow* detachedWindow = m_hostRuntime.FindWindowById(result.targetWindowId); if (!IsLiveInteractiveWindow(detachedWindow)) { LogRuntimeTrace("drag", "detached drag window was not created."); return false; @@ -760,12 +746,12 @@ bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( detachedWindow->GetWindowId(), detachedProjection->windowState.workspace.root.nodeId, request.panelId, - ToNativePoint(request.screenPoint), + request.screenPoint, dragHotspot); UpdateGlobalTabDragOwnerWindowPosition(); detachedWindow->AcquirePointerCapture( EditorWindowPointerCaptureOwner::GlobalTabDrag); - SetForegroundWindow(detachedWindow->GetHwnd()); + detachedWindow->FocusHostWindow(); LogRuntimeTrace( "drag", "started global tab drag by detaching panel '" + request.panelId + @@ -809,7 +795,7 @@ bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( sourceWindow.GetWindowId(), request.nodeId, request.panelId, - ToNativePoint(request.screenPoint), + request.screenPoint, dragHotspot); UpdateGlobalTabDragOwnerWindowPosition(); sourceWindow.AcquirePointerCapture(EditorWindowPointerCaptureOwner::GlobalTabDrag); @@ -822,7 +808,7 @@ bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( } bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowPanelTransferRequest& request) { if (!sourceWindow.IsWorkspaceWindow()) { LogRuntimeTrace( @@ -852,14 +838,14 @@ bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest( if (!CommitWindowWorkspaceMutation( windowWorkspaceController, result.targetWindowId, - ToNativePoint(request.screenPoint))) { + request.screenPoint)) { LogRuntimeTrace("detach", "failed to synchronize detached window state"); return false; } - if (EditorWindow* detachedWindow = m_hostRuntime.FindWindow(result.targetWindowId); + if (EditorHostWindow* detachedWindow = m_hostRuntime.FindWindowById(result.targetWindowId); IsLiveInteractiveWindow(detachedWindow)) { - SetForegroundWindow(detachedWindow->GetHwnd()); + detachedWindow->FocusHostWindow(); } LogRuntimeTrace( @@ -870,7 +856,7 @@ bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest( } void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowFrameTransferRequests& transferRequests) { if (transferRequests.workspace.workspaceMutation.has_value()) { if (!CommitLiveWindowMutation(*transferRequests.workspace.workspaceMutation)) { @@ -898,21 +884,19 @@ void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests( } } -EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, +EditorHostWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( + const EditorWindowScreenPoint& screenPoint, std::string_view excludedWindowId) { - if (const HWND hitWindow = WindowFromPoint(screenPoint); hitWindow != nullptr) { - const HWND rootWindow = GetAncestor(hitWindow, GA_ROOT); - if (EditorWindow* window = m_hostRuntime.FindWindow(rootWindow); - IsLiveInteractiveWindow(window) && - window->IsWorkspaceWindow() && - window->GetWindowId() != excludedWindowId) { - return window; - } + if (EditorHostWindow* window = m_hostRuntime.FindWindowFromScreenPoint(screenPoint); + IsLiveInteractiveWindow(window) && + window->IsWorkspaceWindow() && + window->GetWindowId() != excludedWindowId) { + return window; } - for (auto it = m_hostRuntime.GetWindows().rbegin(); it != m_hostRuntime.GetWindows().rend(); ++it) { - EditorWindow* const window = it->get(); + std::vector windows = m_hostRuntime.GetWindows(); + for (auto it = windows.rbegin(); it != windows.rend(); ++it) { + EditorHostWindow* const window = *it; if (window == nullptr || !IsLiveInteractiveWindow(window) || !window->IsWorkspaceWindow() || @@ -920,12 +904,9 @@ EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( continue; } - RECT windowRect = {}; - if (GetWindowRect(window->GetHwnd(), &windowRect) && - screenPoint.x >= windowRect.left && - screenPoint.x < windowRect.right && - screenPoint.y >= windowRect.top && - screenPoint.y < windowRect.bottom) { + EditorWindowScreenRect windowRect = {}; + if (window->TryGetHostScreenRect(windowRect) && + windowRect.Contains(screenPoint)) { return window; } } @@ -933,8 +914,8 @@ EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( return nullptr; } -const EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, +const EditorHostWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( + const EditorWindowScreenPoint& screenPoint, std::string_view excludedWindowId) const { return const_cast(this)->FindTopmostWindowAtScreenPoint( screenPoint, diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h similarity index 69% rename from editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h rename to editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h index c01a495d..27e57078 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h +++ b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h @@ -6,35 +6,32 @@ #include "Windowing/EditorWorkspaceWindowProjection.h" #include "Windowing/Frame/EditorWindowTransferRequests.h" +#include "Windowing/Host/EditorWindowHostInterfaces.h" #include "Windowing/System/EditorWindowSynchronizationPlan.h" #include #include #include -#include - #include #include #include namespace XCEngine::UI::Editor::App { -class EditorWindow; -class EditorWindowHostRuntime; class EditorWindowLifecycleCoordinator; class EditorWindowSystem; class EditorWindowWorkspaceCoordinator final { public: EditorWindowWorkspaceCoordinator( - EditorWindowHostRuntime& hostRuntime, + EditorWindowHost& hostRuntime, EditorWindowSystem& windowSystem); ~EditorWindowWorkspaceCoordinator(); void BindLifecycleCoordinator(EditorWindowLifecycleCoordinator& lifecycleCoordinator); - void RegisterExistingWindow(EditorWindow& window); - void RefreshWindowPresentation(EditorWindow& window); + void RegisterExistingWindow(EditorHostWindow& window); + void RefreshWindowPresentation(EditorHostWindow& window); void HandleNativeWindowDestroyed(std::string_view windowId); bool IsPrimaryWindowId(std::string_view windowId) const; std::string DescribeWindowSet() const; @@ -42,10 +39,10 @@ public: bool IsGlobalTabDragActive() const; bool OwnsActiveGlobalTabDrag(std::string_view windowId) const; void EndGlobalTabDragSession(); - bool HandleGlobalTabDragPointerMove(HWND hwnd); - bool HandleGlobalTabDragPointerButtonUp(HWND hwnd); + bool HandleGlobalTabDragPointerMove(EditorHostWindow& window); + bool HandleGlobalTabDragPointerButtonUp(EditorHostWindow& window); void HandleWindowFrameTransferRequests( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowFrameTransferRequests& transferRequests); private: @@ -55,65 +52,65 @@ private: std::string sourceNodeId = {}; std::string panelId = {}; std::string previewWindowId = {}; - POINT screenPoint = {}; - POINT dragHotspot = {}; + EditorWindowScreenPoint screenPoint = {}; + EditorWindowScreenPoint dragHotspot = {}; }; UIEditorWindowWorkspaceController BuildWorkspaceMutationController() const; bool SynchronizeWindowsFromWindowSet( const UIEditorWindowWorkspaceSet& windowSet, std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint, - LONG preferredWidth = 0, - LONG preferredHeight = 0); + const EditorWindowScreenPoint& preferredScreenPoint, + int preferredWidth = 0, + int preferredHeight = 0); bool ApplySynchronizationPlan(const EditorWindowSynchronizationPlan& plan); std::vector CaptureHostSnapshots() const; bool CommitWindowWorkspaceMutation( const UIEditorWindowWorkspaceController& windowWorkspaceController, std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint, - LONG preferredWidth = 0, - LONG preferredHeight = 0); - UIEditorWindowWorkspaceState BuildWindowStateForWindow(const EditorWindow& window) const; + const EditorWindowScreenPoint& preferredScreenPoint, + int preferredWidth = 0, + int preferredHeight = 0); + UIEditorWindowWorkspaceState BuildWindowStateForWindow(const EditorHostWindow& window) const; bool CommitLiveWindowMutation(const EditorWindowWorkspaceMutationRequest& request); EditorWorkspaceWindowProjection BuildWorkspaceProjectionForState( const UIEditorWindowWorkspaceState& windowState, bool primary, std::wstring title) const; EditorWorkspaceWindowProjection BuildWorkspaceProjectionForWindow( - const EditorWindow& window) const; - bool RefreshWorkspaceProjectionFromAuthoritativeState(EditorWindow& window) const; - void RefreshWindowTitle(EditorWindow& window) const; + const EditorHostWindow& window) const; + bool RefreshWorkspaceProjectionFromAuthoritativeState(EditorHostWindow& window) const; + void RefreshWindowTitle(EditorHostWindow& window) const; void BeginGlobalTabDragSession( std::string_view panelWindowId, std::string_view sourceNodeId, std::string_view panelId, - const POINT& screenPoint, - const POINT& dragHotspot); + const EditorWindowScreenPoint& screenPoint, + const EditorWindowScreenPoint& dragHotspot); bool TryResolveGlobalTabDragHotspot( - const EditorWindow& sourceWindow, + const EditorHostWindow& sourceWindow, std::string_view nodeId, std::string_view panelId, - const POINT& screenPoint, - POINT& outDragHotspot) const; + const EditorWindowScreenPoint& screenPoint, + EditorWindowScreenPoint& outDragHotspot) const; void ClearGlobalTabDragDropPreview(); void UpdateGlobalTabDragDropPreview(); void UpdateGlobalTabDragOwnerWindowPosition(); - EditorWindow* FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, + EditorHostWindow* FindTopmostWindowAtScreenPoint( + const EditorWindowScreenPoint& screenPoint, std::string_view excludedWindowId = {}); - const EditorWindow* FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, + const EditorHostWindow* FindTopmostWindowAtScreenPoint( + const EditorWindowScreenPoint& screenPoint, std::string_view excludedWindowId = {}) const; bool TryStartGlobalTabDrag( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowPanelTransferRequest& request); bool TryProcessDetachRequest( - EditorWindow& sourceWindow, + EditorHostWindow& sourceWindow, const EditorWindowPanelTransferRequest& request); void LogRuntimeTrace(std::string_view channel, std::string_view message) const; - EditorWindowHostRuntime& m_hostRuntime; + EditorWindowHost& m_hostRuntime; EditorWindowSystem& m_windowSystem; EditorWindowLifecycleCoordinator* m_lifecycleCoordinator = nullptr; GlobalTabDragSession m_globalTabDragSession = {}; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowManager.cpp b/editor/app/Windowing/EditorWindowManager.cpp similarity index 69% rename from editor/app/Platform/Win32/Windowing/EditorWindowManager.cpp rename to editor/app/Windowing/EditorWindowManager.cpp index 21e523ed..35b1da59 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowManager.cpp +++ b/editor/app/Windowing/EditorWindowManager.cpp @@ -1,13 +1,13 @@ -#include "Platform/Win32/Windowing/EditorWindowManager.h" +#include "Windowing/EditorWindowManager.h" #include "Platform/Win32/Windowing/EditorWindow.h" #include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" -#include "Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.h" -#include "Platform/Win32/Windowing/EditorUtilityWindowCoordinator.h" +#include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" +#include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" #include "Platform/Win32/Windowing/EditorWindowMessageDispatcher.h" -#include "Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" #include @@ -25,6 +25,7 @@ EditorWindowManager::EditorWindowManager( hostConfig, std::move(repoRoot), editorContext, + windowSystem, *m_contentFactory)) { m_workspaceCoordinator = std::make_unique(*m_hostRuntime, windowSystem); @@ -40,10 +41,10 @@ EditorWindowManager::EditorWindowManager( EditorWindowManager::~EditorWindowManager() = default; -EditorWindow* EditorWindowManager::CreateEditorWindow( +EditorHostWindow* EditorWindowManager::CreateEditorWindow( std::unique_ptr contentController, - const CreateParams& params) { - EditorWindow* const window = + const EditorWindowCreateParams& params) { + EditorHostWindow* const window = m_hostRuntime->CreateEditorWindow(std::move(contentController), params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); @@ -51,10 +52,10 @@ EditorWindow* EditorWindowManager::CreateEditorWindow( return window; } -EditorWindow* EditorWindowManager::CreateWorkspaceWindow( +EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow( UIEditorWorkspaceController workspaceController, - const CreateParams& params) { - EditorWindow* const window = + const EditorWindowCreateParams& params) { + EditorHostWindow* const window = m_hostRuntime->CreateWorkspaceWindow(std::move(workspaceController), params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); @@ -62,10 +63,10 @@ EditorWindow* EditorWindowManager::CreateWorkspaceWindow( return window; } -EditorWindow* EditorWindowManager::CreateUtilityWindow( +EditorHostWindow* EditorWindowManager::CreateUtilityWindow( const EditorUtilityWindowDescriptor& descriptor, - const CreateParams& params) { - EditorWindow* const window = m_hostRuntime->CreateUtilityWindow(descriptor, params); + const EditorWindowCreateParams& params) { + EditorHostWindow* const window = m_hostRuntime->CreateUtilityWindow(descriptor, params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); } @@ -81,6 +82,20 @@ void EditorWindowManager::Shutdown() { m_lifecycleCoordinator->ShutdownAllWindows(); } +bool EditorWindowManager::RequestPrimaryWindowClose() { + if (m_hostRuntime == nullptr || m_lifecycleCoordinator == nullptr) { + return false; + } + + EditorHostWindow* const primaryWindow = m_hostRuntime->FindPrimaryWindow(); + if (primaryWindow == nullptr) { + return false; + } + + m_lifecycleCoordinator->PostCloseRequest(*primaryWindow); + return true; +} + bool EditorWindowManager::TryDispatchWindowMessage( HWND hwnd, UINT message, @@ -109,27 +124,27 @@ bool EditorWindowManager::TryDispatchWindowMessage( outResult); } -EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) { +EditorHostWindow* EditorWindowManager::FindWindow(HWND hwnd) { return m_hostRuntime->FindWindow(hwnd); } -const EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) const { +const EditorHostWindow* EditorWindowManager::FindWindow(HWND hwnd) const { return m_hostRuntime->FindWindow(hwnd); } -EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) { - return m_hostRuntime->FindWindow(windowId); +EditorHostWindow* EditorWindowManager::FindWindow(std::string_view windowId) { + return m_hostRuntime->FindWindowById(windowId); } -const EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) const { - return m_hostRuntime->FindWindow(windowId); +const EditorHostWindow* EditorWindowManager::FindWindow(std::string_view windowId) const { + return m_hostRuntime->FindWindowById(windowId); } -EditorWindow* EditorWindowManager::FindPrimaryWindow() { +EditorHostWindow* EditorWindowManager::FindPrimaryWindow() { return m_hostRuntime->FindPrimaryWindow(); } -const EditorWindow* EditorWindowManager::FindPrimaryWindow() const { +const EditorHostWindow* EditorWindowManager::FindPrimaryWindow() const { return m_hostRuntime->FindPrimaryWindow(); } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowManager.h b/editor/app/Windowing/EditorWindowManager.h similarity index 62% rename from editor/app/Platform/Win32/Windowing/EditorWindowManager.h rename to editor/app/Windowing/EditorWindowManager.h index 7215e47b..856c14f4 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowManager.h +++ b/editor/app/Windowing/EditorWindowManager.h @@ -4,7 +4,9 @@ #define NOMINMAX #endif -#include "Platform/Win32/Windowing/EditorWindowState.h" +#include "Platform/Win32/Windowing/EditorWindowHostConfig.h" +#include "Windowing/Host/EditorWindowHostInterfaces.h" +#include "Windowing/Host/EditorWindowHostTypes.h" #include @@ -28,7 +30,6 @@ namespace XCEngine::UI::Editor::App { class EditorContext; class EditorWindowSystem; -class EditorWindow; class EditorWindowContentController; class EditorWindowContentFactory; class EditorWindowHostRuntime; @@ -39,31 +40,8 @@ struct EditorUtilityWindowDescriptor; struct EditorWindowPanelTransferRequest; struct EditorWindowFrameTransferRequests; -struct EditorWindowHostConfig { - HINSTANCE hInstance = nullptr; - const wchar_t* windowClassName = L""; - DWORD windowStyle = 0; - const wchar_t* primaryWindowTitle = L""; - void* windowUserData = nullptr; -}; - class EditorWindowManager final { public: - struct CreateParams { - std::string windowId = {}; - std::wstring title = {}; - int initialX = CW_USEDEFAULT; - int initialY = CW_USEDEFAULT; - int initialWidth = 1540; - int initialHeight = 940; - int showCommand = SW_SHOW; - EditorWindowCategory category = EditorWindowCategory::Workspace; - EditorWindowChromePolicy chromePolicy = {}; - EditorWindowNativeStylePolicy nativeStylePolicy = {}; - bool primary = false; - bool autoCaptureOnStartup = false; - }; - EditorWindowManager( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, @@ -76,17 +54,18 @@ public: EditorWindowManager(EditorWindowManager&&) = delete; EditorWindowManager& operator=(EditorWindowManager&&) = delete; - EditorWindow* CreateEditorWindow( + EditorHostWindow* CreateEditorWindow( std::unique_ptr contentController, - const CreateParams& params); - EditorWindow* CreateWorkspaceWindow( + const EditorWindowCreateParams& params); + EditorHostWindow* CreateWorkspaceWindow( UIEditorWorkspaceController workspaceController, - const CreateParams& params); - EditorWindow* CreateUtilityWindow( + const EditorWindowCreateParams& params); + EditorHostWindow* CreateUtilityWindow( const EditorUtilityWindowDescriptor& descriptor, - const CreateParams& params); + const EditorWindowCreateParams& params); void HandlePendingNativeWindowCreated(HWND hwnd); void Shutdown(); + bool RequestPrimaryWindowClose(); bool TryDispatchWindowMessage( HWND hwnd, UINT message, @@ -94,12 +73,12 @@ public: LPARAM lParam, LRESULT& outResult); - EditorWindow* FindWindow(HWND hwnd); - const EditorWindow* FindWindow(HWND hwnd) const; - EditorWindow* FindWindow(std::string_view windowId); - const EditorWindow* FindWindow(std::string_view windowId) const; - EditorWindow* FindPrimaryWindow(); - const EditorWindow* FindPrimaryWindow() const; + EditorHostWindow* FindWindow(HWND hwnd); + const EditorHostWindow* FindWindow(HWND hwnd) const; + EditorHostWindow* FindWindow(std::string_view windowId); + const EditorHostWindow* FindWindow(std::string_view windowId) const; + EditorHostWindow* FindPrimaryWindow(); + const EditorHostWindow* FindPrimaryWindow() const; bool HasWindows() const; void DestroyClosedWindows(); diff --git a/editor/app/Windowing/EditorWindowShared.h b/editor/app/Windowing/EditorWindowShared.h index c2dc640c..627f1a4e 100644 --- a/editor/app/Windowing/EditorWindowShared.h +++ b/editor/app/Windowing/EditorWindowShared.h @@ -13,6 +13,26 @@ struct EditorWindowScreenPoint { std::int32_t y = 0; }; +struct EditorWindowScreenRect { + std::int32_t left = 0; + std::int32_t top = 0; + std::int32_t right = 0; + std::int32_t bottom = 0; + + [[nodiscard]] std::int32_t Width() const { + return right - left; + } + + [[nodiscard]] std::int32_t Height() const { + return bottom - top; + } + + [[nodiscard]] bool Contains(const EditorWindowScreenPoint& point) const { + return point.x >= left && point.x < right && + point.y >= top && point.y < bottom; + } +}; + inline constexpr float kBorderlessTitleBarHeightDips = 28.0f; inline const ::XCEngine::UI::UIColor kShellSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); diff --git a/editor/app/Windowing/Host/EditorWindowHostInterfaces.h b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h new file mode 100644 index 00000000..2e601b42 --- /dev/null +++ b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h @@ -0,0 +1,107 @@ +#pragma once + +#include "Windowing/EditorWindowShared.h" +#include "Windowing/EditorWorkspaceWindowProjection.h" +#include "Windowing/Host/EditorWindowHostTypes.h" +#include "Windowing/Host/EditorWindowPointerCapture.h" +#include "Windowing/Host/EditorWindowTypes.h" + +#include + +#include +#include +#include + +namespace XCEngine::UI { + +struct UIPoint; + +} // namespace XCEngine::UI + +namespace XCEngine::UI::Editor { + +struct UIEditorDockHostTabDropTarget; + +} // namespace XCEngine::UI::Editor + +namespace XCEngine::UI::Editor::App { + +class EditorWindowDockHostBinding; +struct EditorUtilityWindowDescriptor; + +class EditorHostWindow { +public: + virtual ~EditorHostWindow() = default; + + virtual std::string_view GetWindowId() const = 0; + virtual EditorWindowLifecycleState GetLifecycleState() const = 0; + virtual bool IsPrimary() const = 0; + virtual bool IsWorkspaceWindow() const = 0; + virtual bool IsUtilityWindow() const = 0; + virtual bool IsClosing() const = 0; + virtual bool IsDestroyed() const = 0; + virtual bool HasLiveHostWindow() const = 0; + virtual const std::wstring& GetTitle() const = 0; + virtual const UIEditorWorkspaceController* TryGetWorkspaceController() const = 0; + virtual const EditorWorkspaceWindowProjection* TryGetWorkspaceProjection() const = 0; + virtual EditorWindowDockHostBinding* TryGetDockHostBinding() = 0; + virtual const EditorWindowDockHostBinding* TryGetDockHostBinding() const = 0; + virtual ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips( + const EditorWindowScreenPoint& screenPoint) const = 0; + virtual bool TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const EditorWindowScreenPoint& screenPoint, + EditorWindowScreenPoint& outHotspot) const = 0; + virtual bool TryResolveDockTabDropTarget( + const EditorWindowScreenPoint& screenPoint, + UIEditorDockHostTabDropTarget& outTarget) const = 0; + virtual void InvalidateHostWindow() const = 0; + virtual void SetPrimary(bool primary) = 0; + virtual void SetTitle(std::wstring title) = 0; + virtual void ApplyHostWindowTitle() = 0; + virtual void RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) = 0; + virtual void ResetInteractionState() = 0; + virtual void AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) = 0; + virtual void ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) = 0; + virtual void MarkClosing() = 0; + virtual void MarkDestroyed() = 0; + virtual bool IsRenderReady() const = 0; + virtual void Shutdown() = 0; + virtual bool TryGetHostScreenRect(EditorWindowScreenRect& outRect) const = 0; + virtual void SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) = 0; + virtual void FocusHostWindow() = 0; + virtual void PostCloseToHost() = 0; + virtual void DestroyHostWindow() = 0; +}; + +class EditorWindowHost { +public: + virtual ~EditorWindowHost() = default; + + virtual EditorHostWindow* CreateWorkspaceWindow( + UIEditorWorkspaceController workspaceController, + const EditorWindowCreateParams& params) = 0; + virtual EditorHostWindow* CreateUtilityWindow( + const EditorUtilityWindowDescriptor& descriptor, + const EditorWindowCreateParams& params) = 0; + virtual EditorHostWindow* FindWindowById(std::string_view windowId) = 0; + virtual const EditorHostWindow* FindWindowById(std::string_view windowId) const = 0; + virtual std::vector GetWindows() = 0; + virtual std::vector GetWindows() const = 0; + virtual std::wstring_view GetPrimaryWindowTitle() const = 0; + virtual bool TryGetCursorScreenPoint(EditorWindowScreenPoint& outPoint) const = 0; + virtual EditorWindowScreenRect ResolveFloatingPlacement( + const EditorWindowScreenPoint& screenPoint, + int preferredWidth, + int preferredHeight) const = 0; + virtual EditorHostWindow* FindWindowFromScreenPoint( + const EditorWindowScreenPoint& screenPoint) = 0; + virtual const EditorHostWindow* FindWindowFromScreenPoint( + const EditorWindowScreenPoint& screenPoint) const = 0; + virtual void ReapDestroyedWindows() = 0; + virtual std::string DescribeWindows() const = 0; + virtual void LogRuntimeTrace(std::string_view channel, std::string_view message) const = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Host/EditorWindowHostTypes.h b/editor/app/Windowing/Host/EditorWindowHostTypes.h new file mode 100644 index 00000000..4fa65b54 --- /dev/null +++ b/editor/app/Windowing/Host/EditorWindowHostTypes.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Windowing/Host/EditorWindowTypes.h" + +#include +#include + +namespace XCEngine::UI::Editor::App { + +inline constexpr int kEditorWindowDefaultPosition = + (std::numeric_limits::min)(); +inline constexpr int kEditorWindowDefaultShowCommand = 5; + +struct EditorWindowCreateParams { + std::string windowId = {}; + std::wstring title = {}; + int initialX = kEditorWindowDefaultPosition; + int initialY = kEditorWindowDefaultPosition; + int initialWidth = 1540; + int initialHeight = 940; + int showCommand = kEditorWindowDefaultShowCommand; + EditorWindowCategory category = EditorWindowCategory::Workspace; + EditorWindowChromePolicy chromePolicy = {}; + EditorWindowNativeStylePolicy nativeStylePolicy = {}; + bool primary = false; + bool autoCaptureOnStartup = false; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Host/EditorWindowPointerCapture.h b/editor/app/Windowing/Host/EditorWindowPointerCapture.h new file mode 100644 index 00000000..eb12a4d2 --- /dev/null +++ b/editor/app/Windowing/Host/EditorWindowPointerCapture.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::App { + +enum class EditorWindowPointerCaptureOwner : std::uint8_t { + None = 0, + Shell, + HostedContent, + BorderlessResize, + BorderlessChrome, + GlobalTabDrag, +}; + +constexpr bool CanRouteEditorWindowGlobalTabDragPointerMessages( + EditorWindowPointerCaptureOwner owner, + bool ownsActiveGlobalTabDrag) { + return ownsActiveGlobalTabDrag && + owner == EditorWindowPointerCaptureOwner::GlobalTabDrag; +} + +constexpr bool CanRouteEditorWindowBorderlessResizePointerMessages( + EditorWindowPointerCaptureOwner owner) { + return owner == EditorWindowPointerCaptureOwner::BorderlessResize; +} + +constexpr bool CanRouteEditorWindowBorderlessChromePointerMessages( + EditorWindowPointerCaptureOwner owner) { + return owner == EditorWindowPointerCaptureOwner::BorderlessChrome; +} + +constexpr bool CanConsumeEditorWindowChromeHover( + EditorWindowPointerCaptureOwner owner, + bool shellInteractiveCaptureActive, + bool hostedContentCaptureActive) { + return owner == EditorWindowPointerCaptureOwner::None && + !shellInteractiveCaptureActive && + !hostedContentCaptureActive; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Host/EditorWindowTypes.h b/editor/app/Windowing/Host/EditorWindowTypes.h new file mode 100644 index 00000000..2fa396b3 --- /dev/null +++ b/editor/app/Windowing/Host/EditorWindowTypes.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +namespace XCEngine::UI::Editor::App { + +enum class EditorWindowLifecycleState : std::uint8_t { + PendingNativeCreate = 0, + NativeAttached, + Initializing, + Running, + Closing, + Destroyed, +}; + +enum class EditorWindowCategory : std::uint8_t { + Workspace = 0, + Utility, +}; + +inline std::string_view GetEditorWindowCategoryName( + EditorWindowCategory category) { + switch (category) { + case EditorWindowCategory::Workspace: + return "Workspace"; + case EditorWindowCategory::Utility: + return "Utility"; + } + + return "Unknown"; +} + +struct EditorWindowChromePolicy { + bool allowDetachedTitleBarTabStrip = true; + bool showFrameStats = true; + bool showTopmostButton = false; + bool topmostByDefault = false; +}; + +struct EditorWindowNativeStylePolicy { + std::uint32_t extendedWindowStyle = 0u; + std::uint32_t windowStyle = 0u; + bool useHostWindowStyle = true; +}; + +inline std::string_view GetEditorWindowLifecycleStateName( + EditorWindowLifecycleState state) { + switch (state) { + case EditorWindowLifecycleState::PendingNativeCreate: + return "PendingNativeCreate"; + case EditorWindowLifecycleState::NativeAttached: + return "NativeAttached"; + case EditorWindowLifecycleState::Initializing: + return "Initializing"; + case EditorWindowLifecycleState::Running: + return "Running"; + case EditorWindowLifecycleState::Closing: + return "Closing"; + case EditorWindowLifecycleState::Destroyed: + return "Destroyed"; + } + + return "Unknown"; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/System/EditorWindowSystem.cpp b/editor/app/Windowing/System/EditorWindowSystem.cpp index 17479d0d..0c4b1a9e 100644 --- a/editor/app/Windowing/System/EditorWindowSystem.cpp +++ b/editor/app/Windowing/System/EditorWindowSystem.cpp @@ -54,6 +54,30 @@ UIEditorWindowWorkspaceController EditorWindowSystem::BuildWorkspaceMutationCont return UIEditorWindowWorkspaceController(GetPanelRegistry(), GetWindowSet()); } +bool EditorWindowSystem::CommitLiveWindowMutation( + std::string_view windowId, + const UIEditorWorkspaceController& workspaceController, + std::string& outError) { + if (windowId.empty()) { + outError = "live window mutation missing window id"; + return false; + } + + UIEditorWindowWorkspaceSet nextWindowSet = GetWindowSet(); + UIEditorWindowWorkspaceState* existingState = + FindMutableUIEditorWindowWorkspaceState(nextWindowSet, windowId); + if (existingState == nullptr) { + outError = + "live window mutation references unknown window '" + + std::string(windowId) + "'"; + return false; + } + + existingState->workspace = workspaceController.GetWorkspace(); + existingState->session = workspaceController.GetSession(); + return m_workspaceStore.TrySetWindowSet(std::move(nextWindowSet), outError); +} + EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForWindowSet( const UIEditorWindowWorkspaceSet& targetWindowSet, const std::vector& hostWindows, diff --git a/editor/app/Windowing/System/EditorWindowSystem.h b/editor/app/Windowing/System/EditorWindowSystem.h index c1ec9ac2..e568dfe8 100644 --- a/editor/app/Windowing/System/EditorWindowSystem.h +++ b/editor/app/Windowing/System/EditorWindowSystem.h @@ -29,6 +29,10 @@ public: const UIEditorWindowWorkspaceSet& GetWindowSet() const; UIEditorWindowWorkspaceController BuildWorkspaceMutationController() const; + bool CommitLiveWindowMutation( + std::string_view windowId, + const UIEditorWorkspaceController& workspaceController, + std::string& outError); EditorWindowSynchronizationPlan BuildPlanForWindowSet( const UIEditorWindowWorkspaceSet& targetWindowSet, const std::vector& hostWindows, diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 5d3b1eb4..41404918 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -1360,6 +1360,11 @@ namespace Gameplay internal sealed class ManagedRendererInvalidationProbeFeature : ScriptableRendererFeature { + public override void Create() + { + ManagedRendererInvalidationProbeState.CreateFeatureCallCount++; + } + protected override void ReleaseRuntimeResources() { ManagedRendererInvalidationProbeState.DisposeFeatureCallCount++; @@ -1381,7 +1386,6 @@ namespace Gameplay public ManagedRendererInvalidationProbeRendererData() : base(false) { - ManagedRendererInvalidationProbeState.CreateFeatureCallCount++; rendererFeatures = ProbeScriptableObjectFactory .CreateRendererFeatureList( diff --git a/project/Assets/Scripts/ProjectRenderPipelineProbe.cs b/project/Assets/Scripts/ProjectRenderPipelineProbe.cs index f7a70eff..d1564719 100644 --- a/project/Assets/Scripts/ProjectRenderPipelineProbe.cs +++ b/project/Assets/Scripts/ProjectRenderPipelineProbe.cs @@ -269,6 +269,12 @@ namespace ProjectScripts public sealed class ProjectRendererInvalidationProbeFeature : ScriptableRendererFeature { + public override void Create() + { + ProjectRendererInvalidationProbeState + .CreateFeatureCallCount++; + } + protected override void ReleaseRuntimeResources() { ProjectRendererInvalidationProbeState @@ -305,8 +311,6 @@ namespace ProjectScripts public ProjectRendererInvalidationProbeRendererData() : base(false) { - ProjectRendererInvalidationProbeState - .CreateFeatureCallCount++; rendererFeatures = new ScriptableRendererFeature[] { new ProjectRendererInvalidationProbeFeature() diff --git a/scripts/Run-RendererPhaseRegression.ps1 b/scripts/Run-RendererPhaseRegression.ps1 new file mode 100644 index 00000000..b99b2f63 --- /dev/null +++ b/scripts/Run-RendererPhaseRegression.ps1 @@ -0,0 +1,184 @@ +[CmdletBinding()] +param( + [string]$RepoRoot = (Split-Path -Parent $PSScriptRoot), + [string]$BuildDir = "", + [string]$Config = "Debug", + [switch]$SkipBuild, + [switch]$IncludeSmoke, + [switch]$SkipCtestEnumerate +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Resolve-NormalizedPath { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if (Test-Path -LiteralPath $Path) { + return (Resolve-Path -LiteralPath $Path).Path + } + + return [System.IO.Path]::GetFullPath($Path) +} + +function Join-NormalizedPath { + param( + [Parameter(Mandatory = $true)] + [string[]]$Parts + ) + + return [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($Parts)) +} + +function Assert-PathExists { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [string]$Label + ) + + if (-not (Test-Path -LiteralPath $Path)) { + throw "$Label not found: $Path" + } +} + +function Invoke-NativeStep { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [Parameter(Mandatory = $true)] + [string]$FilePath, + [string[]]$Arguments = @() + ) + + Write-Host "==> $Name" -ForegroundColor Cyan + & $FilePath @Arguments + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + throw "$Name failed with exit code $exitCode." + } +} + +function Invoke-CtestEnumeration { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDir, + [Parameter(Mandatory = $true)] + [string]$Config + ) + + Write-Host "==> Enumerate CTest registry" -ForegroundColor Cyan + $output = & ctest --test-dir $BuildDir -N -C $Config 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + $output | ForEach-Object { Write-Host $_ } + throw "Enumerate CTest registry failed with exit code $exitCode." + } + + $lines = $output | ForEach-Object { $_.ToString() } + $registeredCount = ($lines | Where-Object { $_ -match '^\s*Test\s+#\d+:' }).Count + $notBuiltCount = ($lines | Where-Object { $_ -match '_NOT_BUILT' }).Count + $totalLine = $lines | Where-Object { $_ -match '^Total Tests:\s+\d+' } | Select-Object -Last 1 + if ($null -ne $totalLine) { + Write-Host ("{0}; NOT_BUILT placeholders: {1}" -f $totalLine.Trim(), $notBuiltCount) ` + -ForegroundColor DarkGray + } else { + Write-Host ("CTest enumeration completed; registered tests: {0}; NOT_BUILT placeholders: {1}" ` + -f $registeredCount, $notBuiltCount) -ForegroundColor DarkGray + } +} + +$RepoRoot = Resolve-NormalizedPath -Path $RepoRoot +if ([string]::IsNullOrWhiteSpace($BuildDir)) { + $BuildDir = Join-NormalizedPath -Parts @($RepoRoot, "build") +} else { + $BuildDir = Resolve-NormalizedPath -Path $BuildDir +} + +$ctestRoot = Join-NormalizedPath -Parts @($BuildDir, "CTestTestfile.cmake") +Assert-PathExists -Path $ctestRoot -Label "CTest root" + +$scriptingExe = Join-NormalizedPath -Parts @( + $BuildDir, "tests", "Scripting", $Config, "scripting_tests.exe") +$windowingExe = Join-NormalizedPath -Parts @( + $BuildDir, "tests", "UI", "Editor", "unit", $Config, "editor_windowing_phase1_tests.exe") + +$srpValidationTests = @( + "MonoScriptRuntimeTest.ManagedRenderPipelineBridgeRuntimeExposesDefaultNativeBackendPolicy", + "MonoScriptRuntimeTest.ScriptCoreUniversalRenderPipelineAssetExposesDefaultNativeBackendPolicy", + "MonoScriptRuntimeTest.ManagedRenderPipelineBridgeUsesDefaultRendererSelectionForNativeBackendPolicy", + "MonoScriptRuntimeTest.ManagedRenderPipelineBridgeUsesCameraRendererOverrideAcrossPlanningAndExecution", + "MonoScriptRuntimeTest.ManagedRenderPipelineBridgeRebuildsRendererAfterRendererDataInvalidation", + "MonoScriptRuntimeTest.ManagedRenderPipelineBridgeReleasesPersistentRendererFeatureAcrossRendererInvalidationAndAssetRuntimeRelease", + "MonoScriptRuntimeTest.ManagedRenderPipelineBridgeRebuildsPipelineAfterAssetInvalidation", + "MonoScriptRuntimeTest.ManagedRenderPipelineAssetPlansFullscreenStagesFromPipelineStageSupport", + "MonoScriptRuntimeTest.ManagedRendererFeatureConfiguresCameraFramePlanThroughPlanningContext", + "ProjectScriptAssemblyTest.ProjectManagedBridgeRebuildsRendererAfterProjectRendererDataInvalidation", + "ProjectScriptAssemblyTest.ProjectManagedBridgeReleasesProjectRendererCachesAcrossInvalidationAndAssetRuntimeRelease", + "ProjectScriptAssemblyTest.ProjectManagedBridgeRebuildsPipelineAfterProjectAssetInvalidation" +) +$srpValidationFilter = [string]::Join(":", $srpValidationTests) + +Push-Location $RepoRoot +try { + if (-not $SkipBuild) { + $buildTargets = @( + "scripting_tests", + "editor_windowing_phase1_tests" + ) + if ($IncludeSmoke) { + $buildTargets += "editor_ui_smoke_targets" + } + + $buildArguments = @( + "--build", + $BuildDir, + "--config", + $Config, + "--target" + ) + $buildTargets + + Invoke-NativeStep ` + -Name "Build renderer phase regression targets ($Config)" ` + -FilePath "cmake" ` + -Arguments $buildArguments + } + + Assert-PathExists -Path $scriptingExe -Label "scripting_tests.exe" + Assert-PathExists -Path $windowingExe -Label "editor_windowing_phase1_tests.exe" + + if (-not $SkipCtestEnumerate) { + Write-Host "ctest enumeration may still print *_NOT_BUILT placeholders for targets that were not built." ` + -ForegroundColor DarkGray + Invoke-CtestEnumeration -BuildDir $BuildDir -Config $Config + } + + Invoke-NativeStep ` + -Name "Run managed SRP validation batch (12 tests)" ` + -FilePath $scriptingExe ` + -Arguments @("--gtest_filter=$srpValidationFilter") + + Invoke-NativeStep ` + -Name "Run editor windowing phase1 tests" ` + -FilePath $windowingExe + + if ($IncludeSmoke) { + Invoke-NativeStep ` + -Name "Run XCUI editor smoke" ` + -FilePath "ctest" ` + -Arguments @( + "--test-dir", $BuildDir, + "-C", $Config, + "-R", "^xcui_editor_app_smoke$", + "--output-on-failure" + ) + } + + Write-Host "Renderer phase regression passed." -ForegroundColor Green +} finally { + Pop-Location +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c6bb2a4a..632cd840 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,16 +65,10 @@ if(WIN32) find_program(XCENGINE_POWERSHELL_EXECUTABLE NAMES powershell pwsh REQUIRED) set(XCENGINE_RENDERING_PHASE_REGRESSION_DEPENDENCIES - rendering_all_tests - XCEditor + scripting_tests + editor_windowing_phase1_tests ) - if(TARGET editor_tests) - list(APPEND XCENGINE_RENDERING_PHASE_REGRESSION_DEPENDENCIES - editor_tests - ) - endif() - add_custom_target(rendering_phase_regression_build DEPENDS ${XCENGINE_RENDERING_PHASE_REGRESSION_DEPENDENCIES} diff --git a/tests/TEST_SPEC.md b/tests/TEST_SPEC.md index 3c90bc63..f6cc145c 100644 --- a/tests/TEST_SPEC.md +++ b/tests/TEST_SPEC.md @@ -1,6 +1,6 @@ # XCEngine 测试规范 -最后更新:`2026-04-04` +最后更新:`2026-04-26` ## 1. 目标 @@ -178,16 +178,28 @@ ctest --test-dir build -C Debug --output-on-failure - `rendering_integration_material_state_scene` - `rendering_integration_offscreen_scene` -`rendering_phase_regression` 当前是 Windows 下的 PowerShell 回归入口,依赖: +`rendering_phase_regression` 当前是 Windows 下的最小渲染/SRP 回归入口,依赖: -- `rendering_all_tests` -- `editor_tests` -- `XCEditor` +- `scripting_tests` +- `editor_windowing_phase1_tests` 并执行: - `scripts/Run-RendererPhaseRegression.ps1` +默认覆盖: + +- `ctest --test-dir build -N -C ` 的测试注册枚举健康检查 +- 一组固定的 managed SRP focused scripting 回归 +- `editor_windowing_phase1_tests` + +补充说明: + +- 该入口当前刻意不依赖 `editor_ui_tests`。 +- `xcui_editor_app_smoke` 当前保留为可选补充验证,不再作为默认构建门槛。 +- 如果要在现有产物上补跑 smoke,使用 `powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Run-RendererPhaseRegression.ps1 -RepoRoot . -BuildDir .\build -Config Debug -SkipBuild -IncludeSmoke`。 +- `ctest -N` 现在应能正常完成枚举;输出里的 `*_NOT_BUILT` 占位项表示目标尚未构建,不再表示 discover/include 生成损坏。 + ### 4.5 RHI | 类别 | target | @@ -316,7 +328,7 @@ build\tests\RHI\integration\backpack\Debug\rhi_integration_backpack.exe --gtest_ 按改动类型选择最小有效验证: - 改 `engine/RHI`:先跑 `rhi_abstraction_tests` 或 `rhi_backend_tests` -- 改 `engine/Rendering`:先跑 `rendering_unit_tests` 和最相关的 `rendering_integration_*` +- 改 `engine/Rendering`:先跑 `rendering_unit_tests` 和最相关的 `rendering_integration_*`;若改动涉及当前 managed SRP / renderer invalidation / editor viewport 主链回归,补跑 `rendering_phase_regression` - 改 `editor/Viewport`:先跑 `editor_tests`,必要时再跑 `rendering_phase_regression` - 改脚本运行时 / managed / 项目脚本程序集:先跑 `scripting_tests`;如果还要验证 editor / runtime 实际使用的项目程序集目录,再补构建 `xcengine_project_managed_assemblies` - 改资源导入 / `.meta` / artifact:优先跑对应 `Resources/*` tests diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index f8019c34..84470230 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -95,7 +95,7 @@ endif() include(GoogleTest) gtest_discover_tests(editor_ui_tests - DISCOVERY_MODE PRE_TEST + DISCOVERY_MODE POST_BUILD ) add_executable(editor_windowing_phase1_tests @@ -139,7 +139,7 @@ if(MSVC) endif() gtest_discover_tests(editor_windowing_phase1_tests - DISCOVERY_MODE PRE_TEST + DISCOVERY_MODE POST_BUILD ) if(TARGET XCUIEditorAppLib) @@ -214,6 +214,6 @@ if(TARGET XCUIEditorAppLib) endif() gtest_discover_tests(editor_app_feature_tests - DISCOVERY_MODE PRE_TEST + DISCOVERY_MODE POST_BUILD ) endif()