Refactor editor windowing and update renderer regression

This commit is contained in:
2026-04-26 03:41:58 +08:00
parent 68993c46bb
commit 4fcaac81d6
39 changed files with 1181 additions and 872 deletions

View File

@@ -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 产品主线

View File

@@ -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 assethost 的默认 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 policynative 负责 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`

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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<LONG>(screenPoint.x),
static_cast<LONG>(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<float>(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<LONG>(std::lround(hotspotDips.x * dpiScale));
outHotspot.y = static_cast<LONG>(std::lround(hotspotDips.y * dpiScale));
outHotspot.x = static_cast<std::int32_t>(std::lround(hotspotDips.x * dpiScale));
outHotspot.y = static_cast<std::int32_t>(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);

View File

@@ -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 <windows.h>
@@ -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,

View File

@@ -0,0 +1,19 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
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

View File

@@ -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 <XCEditor/Workspace/UIEditorWorkspaceController.h>
@@ -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<EditorWindowContentController> 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<DWORD>(params.nativeStylePolicy.windowStyle);
const DWORD extendedWindowStyle =
params.nativeStylePolicy.extendedWindowStyle != 0u
? static_cast<DWORD>(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<EditorHostWindow*> EditorWindowHostRuntime::GetWindows() {
std::vector<EditorHostWindow*> windows = {};
windows.reserve(m_windows.size());
for (const std::unique_ptr<EditorWindow>& window : m_windows) {
if (window != nullptr) {
windows.push_back(window.get());
}
}
return windows;
}
std::vector<const EditorHostWindow*> EditorWindowHostRuntime::GetWindows() const {
std::vector<const EditorHostWindow*> windows = {};
windows.reserve(m_windows.size());
for (const std::unique_ptr<EditorWindow>& 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<EditorWindowHostRuntime*>(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<EditorWindow>& 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<EditorWindowHostRuntime*>(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<EditorWindowHostRuntime*>(this)->FindWindow(windowId);
const EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view windowId) const {
return const_cast<EditorWindowHostRuntime*>(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() {

View File

@@ -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 <XCEditor/Workspace/UIEditorWorkspaceController.h>
@@ -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<EditorWindowContentController> 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<EditorHostWindow*> GetWindows() override;
std::vector<const EditorHostWindow*> 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<std::unique_ptr<EditorWindow>>& GetWindows() {
std::vector<std::unique_ptr<EditorWindow>>& GetWindowStorage() {
return m_windows;
}
const std::vector<std::unique_ptr<EditorWindow>>& GetWindows() const {
const std::vector<std::unique_ptr<EditorWindow>>& 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<std::unique_ptr<EditorWindow>> m_windows = {};
EditorWindow* m_pendingCreateWindow = nullptr;

View File

@@ -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 <cstdint>
#include <sstream>
@@ -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<std::uintptr_t>(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<EditorWindow>& window : windows) {
if (!first) {
stream << ", ";
}
first = false;
if (window == nullptr) {
stream << "<null>";
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;
}

View File

@@ -1,42 +1,3 @@
#pragma once
#include <cstdint>
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"

View File

@@ -4,73 +4,14 @@
#define NOMINMAX
#endif
#include "Windowing/Host/EditorWindowTypes.h"
#include <windows.h>
#include <cstdint>
#include <string>
#include <string_view>
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 = {};

View File

@@ -3,6 +3,12 @@
#include "Features/ColorPicker/ColorPickerPanel.h"
#include "Features/Inspector/AddComponentPanel.h"
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
namespace XCEngine::UI::Editor::App {
namespace {

View File

@@ -2,7 +2,7 @@
#include "UtilityWindows/EditorUtilityWindowKind.h"
#include "UtilityWindows/EditorUtilityWindowPanel.h"
#include "Platform/Win32/Windowing/EditorWindowState.h"
#include "Windowing/Host/EditorWindowTypes.h"
#include <XCEngine/UI/Types.h>

View File

@@ -2,6 +2,7 @@
#include "Windowing/Content/EditorUtilityWindowContentController.h"
#include "Windowing/Content/EditorWorkspaceWindowContentController.h"
#include "Windowing/System/EditorWindowSystem.h"
#include <utility>
@@ -13,10 +14,12 @@ class DefaultEditorWindowContentFactory final : public EditorWindowContentFactor
public:
std::unique_ptr<EditorWindowContentController> 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<EditorWindowContentController> CreateUtilityContentController(

View File

@@ -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<EditorWindowContentController> CreateWorkspaceContentController(
std::string_view windowId,
UIEditorWorkspaceController workspaceController) const = 0;
UIEditorWorkspaceController workspaceController,
EditorWindowSystem& windowSystem) const = 0;
virtual std::unique_ptr<EditorWindowContentController> CreateUtilityContentController(
const EditorUtilityWindowDescriptor& descriptor) const = 0;
};

View File

@@ -1,5 +1,9 @@
#include "Windowing/Content/EditorWorkspaceWindowContentController.h"
#include "Windowing/System/EditorWindowPresentationPolicy.h"
#include "Windowing/System/EditorWindowSystem.h"
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <XCEditor/Workspace/UIEditorDetachedWindowPolicy.h>
#include <XCEditor/Workspace/UIEditorWorkspaceLayoutPersistence.h>
@@ -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<EditorWindowContentController> CreateEditorWorkspaceWindowContentController(
std::string_view windowId,
UIEditorWorkspaceController workspaceController) {
UIEditorWorkspaceController workspaceController,
EditorWindowSystem& windowSystem) {
return std::make_unique<EditorWorkspaceWindowContentController>(
std::string(windowId),
std::move(workspaceController));
std::move(workspaceController),
windowSystem);
}
} // namespace XCEngine::UI::Editor::App

View File

@@ -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<EditorWindowContentController> CreateEditorWorkspaceWindowContentController(
std::string_view windowId,
UIEditorWorkspaceController workspaceController);
UIEditorWorkspaceController workspaceController,
EditorWindowSystem& windowSystem);
} // namespace XCEngine::UI::Editor::App

View File

@@ -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 <cmath>
@@ -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<LONG>(std::lround(value))
? static_cast<int>(std::lround(value))
: fallback;
}
POINT ToNativePoint(const EditorWindowScreenPoint& point) {
return POINT{
static_cast<LONG>(point.x),
static_cast<LONG>(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 {

View File

@@ -1,33 +1,31 @@
#pragma once
#include "Windowing/Frame/EditorWindowTransferRequests.h"
#include "Windowing/Host/EditorWindowHostInterfaces.h"
#include <string_view>
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;
};

View File

@@ -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 <algorithm>
#include <cstdint>
#include <sstream>
#include <vector>
namespace XCEngine::UI::Editor::App {
namespace {
std::string DescribeHwnd(HWND hwnd) {
std::ostringstream stream = {};
stream << "0x" << std::hex << std::uppercase
<< reinterpret_cast<std::uintptr_t>(hwnd);
return stream.str();
}
std::string DescribeHostWindows(
const std::vector<std::unique_ptr<EditorWindow>>& windows) {
std::ostringstream stream = {};
stream << "count=" << windows.size() << " [";
bool first = true;
for (const std::unique_ptr<EditorWindow>& window : windows) {
if (!first) {
stream << ", ";
}
first = false;
if (window == nullptr) {
stream << "<null>";
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<EditorWindow*> closeTargets = {};
closeTargets.reserve(m_hostRuntime.GetWindows().size());
for (const std::unique_ptr<EditorWindow>& otherWindow : m_hostRuntime.GetWindows()) {
std::vector<EditorHostWindow*> closeTargets = {};
const std::vector<EditorHostWindow*> 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<EditorWindow*> closeTargets = {};
closeTargets.reserve(m_hostRuntime.GetWindows().size());
for (const std::unique_ptr<EditorWindow>& window : m_hostRuntime.GetWindows()) {
std::vector<EditorHostWindow*> closeTargets = {};
const std::vector<EditorHostWindow*> 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();
}

View File

@@ -1,32 +1,32 @@
#pragma once
#include "Windowing/Host/EditorWindowHostInterfaces.h"
#include <string_view>
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;
};

View File

@@ -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 <XCEditor/Docking/UIEditorDockHostTransfer.h>
#include <XCEditor/Workspace/UIEditorWindowWorkspaceController.h>
@@ -17,6 +13,7 @@
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <sstream>
#include <utility>
@@ -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<LONG>(point.x),
static_cast<LONG>(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<EditorWindowHostSnapshot> EditorWindowWorkspaceCoordinator::CaptureHostSnapshots() const {
std::vector<EditorWindowHostSnapshot> snapshots = {};
snapshots.reserve(m_hostRuntime.GetWindows().size());
const std::vector<const EditorHostWindow*> windows =
std::as_const(m_hostRuntime).GetWindows();
snapshots.reserve(windows.size());
for (const std::unique_ptr<EditorWindow>& window : m_hostRuntime.GetWindows()) {
for (const EditorHostWindow* window : windows) {
if (window == nullptr) {
continue;
}
@@ -314,7 +304,7 @@ std::vector<EditorWindowHostSnapshot> 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<EditorWindowHostSnapshot> 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<EditorHostWindow*> 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<EditorWindowWorkspaceCoordinator*>(this)->FindTopmostWindowAtScreenPoint(
screenPoint,

View File

@@ -6,35 +6,32 @@
#include "Windowing/EditorWorkspaceWindowProjection.h"
#include "Windowing/Frame/EditorWindowTransferRequests.h"
#include "Windowing/Host/EditorWindowHostInterfaces.h"
#include "Windowing/System/EditorWindowSynchronizationPlan.h"
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
#include <XCEditor/Workspace/UIEditorWindowWorkspaceController.h>
#include <XCEditor/Workspace/UIEditorWindowWorkspaceModel.h>
#include <windows.h>
#include <string>
#include <string_view>
#include <vector>
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<EditorWindowHostSnapshot> 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 = {};

View File

@@ -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 <XCEditor/Workspace/UIEditorWorkspaceController.h>
@@ -25,6 +25,7 @@ EditorWindowManager::EditorWindowManager(
hostConfig,
std::move(repoRoot),
editorContext,
windowSystem,
*m_contentFactory)) {
m_workspaceCoordinator =
std::make_unique<EditorWindowWorkspaceCoordinator>(*m_hostRuntime, windowSystem);
@@ -40,10 +41,10 @@ EditorWindowManager::EditorWindowManager(
EditorWindowManager::~EditorWindowManager() = default;
EditorWindow* EditorWindowManager::CreateEditorWindow(
EditorHostWindow* EditorWindowManager::CreateEditorWindow(
std::unique_ptr<EditorWindowContentController> 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();
}

View File

@@ -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 <windows.h>
@@ -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<EditorWindowContentController> 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();

View File

@@ -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);

View File

@@ -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 <XCEditor/Workspace/UIEditorWorkspaceController.h>
#include <string>
#include <string_view>
#include <vector>
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<EditorHostWindow*> GetWindows() = 0;
virtual std::vector<const EditorHostWindow*> 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

View File

@@ -0,0 +1,29 @@
#pragma once
#include "Windowing/Host/EditorWindowTypes.h"
#include <limits>
#include <string>
namespace XCEngine::UI::Editor::App {
inline constexpr int kEditorWindowDefaultPosition =
(std::numeric_limits<int>::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

View File

@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
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

View File

@@ -0,0 +1,67 @@
#pragma once
#include <cstdint>
#include <string_view>
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

View File

@@ -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<EditorWindowHostSnapshot>& hostWindows,

View File

@@ -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<EditorWindowHostSnapshot>& hostWindows,

View File

@@ -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(

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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}

View File

@@ -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 <Config>` 的测试注册枚举健康检查
- 一组固定的 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

View File

@@ -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()