From 41b912933d11be7a165cab28e4581426f5a46fc7 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 25 Apr 2026 17:51:37 +0800 Subject: [PATCH] Refine editor window architecture --- ...w_Architecture_Refactor_Plan_2026-04-25.md | 176 ++++++++++++ editor/AGENT.md | 267 ++++++++++++++++++ editor/CMakeLists.txt | 3 +- editor/app/Bootstrap/Application.cpp | 1 + .../Chrome/EditorWindowChromeController.cpp | 6 +- .../EditorUtilityWindowContentController.cpp | 12 + .../EditorUtilityWindowContentController.h | 1 + .../Content/EditorWindowContentController.h | 13 + ...EditorWorkspaceWindowContentController.cpp | 12 + .../EditorWorkspaceWindowContentController.h | 1 + .../Runtime/EditorWindowFrameOrchestrator.cpp | 10 +- .../EditorUtilityWindowCoordinator.cpp | 57 ++-- .../Platform/Win32/Windowing/EditorWindow.cpp | 20 ++ .../Platform/Win32/Windowing/EditorWindow.h | 6 + .../Windowing/EditorWindowHostRuntime.cpp | 25 +- .../EditorWindowLifecycleCoordinator.cpp | 4 +- .../Win32/Windowing/EditorWindowManager.h | 5 + .../Win32/Windowing/EditorWindowSession.cpp | 38 ++- .../Win32/Windowing/EditorWindowSession.h | 11 +- .../Win32/Windowing/EditorWindowState.h | 30 ++ .../Windowing/EditorWindowTransferRequests.h | 24 +- .../EditorWindowWorkspaceCoordinator.cpp | 48 +++- .../EditorUtilityWindowRegistry.cpp | 15 + .../EditorUtilityWindowRegistry.h | 9 + 24 files changed, 741 insertions(+), 53 deletions(-) create mode 100644 docs/plan/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md create mode 100644 editor/AGENT.md diff --git a/docs/plan/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md b/docs/plan/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md new file mode 100644 index 00000000..b168d20b --- /dev/null +++ b/docs/plan/Editor_Window_Architecture_Refactor_Plan_2026-04-25.md @@ -0,0 +1,176 @@ +# Editor Window Architecture Refactor Plan - 2026-04-25 + +## 背景 + +当前 `editor/` 的窗口宿主已经有两类窗口: + +- Workspace window:主窗口和由 dock tab/panel 拖出的独立窗口,内容是 `EditorWorkspaceWindowContentController`。 +- Utility window:颜色选择器、Add Component 等天然浮窗,内容是 `EditorUtilityWindowContentController`。 + +两者共享 `EditorWindow -> EditorWindowRuntimeController -> D3D12WindowRenderLoop` 这套 native/D3D12/chrome/input/lifecycle 宿主。当前主要问题是窗口类型依赖 `TryGetWorkspaceBinding()`、`TryGetWorkspaceController()` 等 capability 是否存在来隐式推断,导致 workspace coordinator、utility coordinator、lifecycle、chrome policy 的语义不够清晰。 + +## 目标 + +把窗口类型变成一等概念,让代码明确区分: + +```text +Native EditorWindow host + -> Workspace content window + -> Utility content window +``` + +并让 workspace docking、utility registry、frame request、chrome policy 分别只处理自己的窗口域。 + +## 非目标 + +- 不重写 docking/workspace model。 +- 不重写 D3D12 UI renderer。 +- 不一次性重排 `editor/app/Platform/Win32` 的目录结构。 +- 不改变颜色选择器和 Add Component 的现有用户行为,第一轮保持单例复用。 + +## 收口状态 + +第一轮重构已收口: + +- `EditorWindowCategory` 已接入窗口创建、session 和 runtime 查询。 +- Workspace / utility frame request 已拆分为独立子结构。 +- Workspace coordinator 显式过滤 workspace window。 +- Utility coordinator 通过 descriptor 创建 utility window,并保留单例复用策略。 +- Utility descriptor 已携带 chrome/native style policy。 +- Window content 已有显式 capabilities,用于创建时校验 category 和 content 是否匹配。 +- `XCUIEditorApp` 输出名已改为 `XCEngine.exe`。 +- 用户已完成 GUI 行为验证。 +- `cmake --build build --config Debug --target XCUIEditorApp` 已通过。 +- `build/editor/Debug/XCEngine.exe` 12 秒启动冒烟测试已通过,进程可正常响应关闭请求。 + +## 执行步骤 + +### 1. 显式窗口分类 + +新增 `EditorWindowCategory`: + +```cpp +enum class EditorWindowCategory { + Workspace, + Utility, +}; +``` + +改动点: + +- `EditorWindowState` +- `EditorWindowSession` +- `EditorWindow` +- `EditorWindowManager::CreateParams` +- `EditorWindowHostRuntime::CreateEditorWindow` +- workspace window 创建处传 `Workspace` +- utility window 创建处传 `Utility` + +完成后,调用方可以使用: + +```cpp +window.GetCategory(); +window.IsWorkspaceWindow(); +window.IsUtilityWindow(); +``` + +### 2. Workspace coordinator 显式过滤 workspace window + +改动点: + +- `BuildLiveWindowWorkspaceSet` +- `SynchronizeWindowsFromWindowSet` +- `FindTopmostWindowAtScreenPoint` +- global tab drag / detach 入口 + +规则: + +- `EditorWindowWorkspaceCoordinator` 只处理 `Workspace` 窗口。 +- utility window 不参与 workspace set。 +- cross-window dock hit test 不把 utility window 当作 drop target。 + +### 3. Utility coordinator 显式创建 utility window + +扩展 `EditorUtilityWindowDescriptor`,逐步承载: + +- window category +- reuse policy +- preferred/minimum outer size +- 后续可加入 style/chrome policy + +第一轮保持: + +- `ColorPicker` 单例 windowId:`utility.color-picker` +- `AddComponent` 单例 windowId:`utility.add-component` +- 重复打开时 focus 现有窗口 + +### 4. 拆分 frame transfer request + +把当前混合结构: + +```cpp +EditorWindowFrameTransferRequests { + beginGlobalTabDrag; + detachPanel; + openUtilityWindow; +} +``` + +拆成: + +```cpp +EditorWorkspaceWindowFrameRequests { + beginGlobalTabDrag; + detachPanel; +} + +EditorUtilityWindowFrameRequests { + openUtilityWindow; +} + +EditorWindowFrameTransferRequests { + workspace; + utility; +} +``` + +规则: + +- `EditorWindowWorkspaceCoordinator` 只消费 `transferRequests.workspace`。 +- `EditorUtilityWindowCoordinator` 只消费 `transferRequests.utility`。 +- queued immediate frame 合并时分别合并两个子结构。 + +### 5. 最小 chrome/window policy + +新增轻量 policy,不急着改完整视觉: + +```cpp +struct EditorWindowChromePolicy { + bool allowDetachedTitleBarTabStrip = false; + bool showFrameStats = true; +}; +``` + +初始规则: + +- Workspace window:允许 detached titlebar tab strip。 +- Utility window:不允许 detached titlebar tab strip。 + +后续再扩展 maximize/minimize/toolwindow/taskbar style。 + +## 验证 + +已执行: + +- `cmake --build build --config Debug --target XCUIEditorApp` +- 启动 `build/editor/Debug/XCEngine.exe`,运行 12 秒后关闭 + +行为检查: + +- 主窗口仍能启动。 +- 从 dock tab 拖出 workspace window。 +- detached workspace window 可拖回其他 workspace window。 +- 打开 Color Picker 时创建 utility window。 +- 重复打开 Color Picker 时 focus 已有窗口。 +- utility window 不作为 dock drop target。 +- 关闭 primary workspace window 仍关闭所有窗口。 diff --git a/editor/AGENT.md b/editor/AGENT.md new file mode 100644 index 00000000..57162beb --- /dev/null +++ b/editor/AGENT.md @@ -0,0 +1,267 @@ +# XCUI Editor Agent Guide + +这份文档面向在 `editor/` 下工作的 coding agent / 开发者。它描述当前 editor 的真实工程边界、窗口架构和修改约束,不是产品说明。 + +如果本文和当前代码、`editor/CMakeLists.txt`、实际目录结构冲突,以当前 checkout 为准,并在本次工作里同步修正文档。 + +## 1. 当前定位 + +`editor/` 是当前 editor 应用主线。 + +当前工程事实: + +- 构建目标是 `XCUIEditorApp`。 +- 输出名是 `XCEngine`,构建产物为 `XCEngine.exe`。 +- `editor/src` 是可复用的 XCEditor UI framework。 +- `editor/include/XCEditor` 是 `editor/src` 的公开接口。 +- `editor/app` 是具体 XCEngine editor 应用层。 +- `editor/resources` 存放 editor 自有图标和 scene viewport shader。 + +虽然 `editor/CMakeLists.txt` 仍把这些代码编进一个可执行目标,但语义上已经分成 framework、app core、Win32 host 和 D3D12 host 四层。 + +## 2. 分层边界 + +推荐按下面四层理解和修改: + +1. `editor/src` / `editor/include/XCEditor` + XCEditor UI framework。这里应保持通用,不依赖具体项目、场景、Win32 宿主或 D3D12 实现。 + +2. `editor/app/Composition`、`Features`、`Project`、`Scene`、`State`、`Commands` + Editor app core。这里装配 panel、项目运行时、场景运行时、选择状态、命令路由和 shell frame。 + +3. `editor/app/Platform/Win32` + Win32 host。这里负责原生窗口、消息分发、窗口生命周期、输入收集、窗口内容装载和 title bar / chrome。 + +4. `editor/app/Rendering` + Editor rendering host。这里负责 D3D12 UI 渲染、窗口 swapchain、viewport 离屏资源、editor 图标纹理和 scene viewport pass。 + +依赖方向尽量保持: + +```text +app/platform + app/rendering -> app/core -> XCEditor framework -> engine UI primitives +``` + +不要让 `editor/src` 反向依赖 `editor/app`。 + +## 3. 顶层启动流程 + +入口: + +```text +app/main.cpp + -> RunXCUIEditorApp + -> Application::Run +``` + +核心对象关系: + +```text +Application + -> EditorContext + -> EditorWindowManager + -> EditorWindow + -> EditorWindowRuntimeController + -> D3D12WindowRenderer + -> D3D12UiRenderer + -> D3D12WindowRenderLoop + -> EditorWindowContentController + -> EditorWorkspaceWindowContentController + -> EditorUtilityWindowContentController +``` + +`EditorContext` 是全局共享应用状态。每个 workspace window 持有自己的 `UIEditorWorkspaceController` 和 `EditorShellRuntime`。utility window 不持有 workspace controller。 + +## 4. 显式窗口架构 + +当前窗口系统已经显式区分两类窗口: + +- `EditorWindowCategory::Workspace` + 主窗口和由 dock tab / panel 拖出的独立窗口。 +- `EditorWindowCategory::Utility` + 颜色选择器、Add Component 这类天然工具窗。 + +两类窗口共享同一个 native host: + +```text +EditorWindow + -> EditorWindowRuntimeController + -> D3D12WindowRenderLoop + -> EditorWindowContentController +``` + +差异放在 content controller 和 policy 上,而不是靠“有没有 workspace binding”隐式推断。 + +### 4.1 Workspace Window + +workspace window 的内容控制器是 `EditorWorkspaceWindowContentController`,内部持有: + +- `UIEditorWorkspaceController` +- `EditorShellRuntime` +- `EditorWindowFrameOrchestrator` + +这类窗口是完整 shell/workspace 容器。主窗口和 detached workspace window 都属于这一类。 + +### 4.2 Utility Window + +utility window 的内容控制器是 `EditorUtilityWindowContentController`,内部持有: + +- `EditorUtilityWindowPanel` + +当前 utility 窗口通过 registry/factory 创建,已实现显式 descriptor: + +- `EditorUtilityWindowDescriptor` +- `EditorUtilityWindowReusePolicy` +- `EditorWindowChromePolicy` +- `EditorWindowNativeStylePolicy` + +当前 `ColorPicker` 和 `AddComponent` 都是单例复用窗口。 + +## 5. 窗口能力与策略 + +窗口内容能力由 `EditorWindowContentCapabilities` 描述。创建窗口时,host 会校验: + +- `Workspace` 分类必须对应具备 workspace capability 的 content controller。 +- `Utility` 分类必须对应具备 utility capability 的 content controller。 + +这一步是硬校验,不再依赖后续 coordinator 通过空 binding 兜底。 + +窗口外观/宿主策略当前最少包括: + +- `EditorWindowChromePolicy` + 当前用于控制 detached title bar tab strip 和 frame stats。 +- `EditorWindowNativeStylePolicy` + 当前用于控制扩展窗口样式和是否复用 host 默认 style。 + +当前默认规则: + +- workspace window 允许 detached title bar tab strip。 +- utility window 不允许 detached title bar tab strip。 +- utility window 不显示 frame stats。 +- utility window 使用 `WS_EX_TOOLWINDOW`。 + +## 6. Workspace / Utility 协调器边界 + +### 6.1 Workspace Coordinator + +`EditorWindowWorkspaceCoordinator` 只处理 `Workspace` 窗口: + +- 维护 window workspace set +- 处理 detach panel +- 处理 global tab drag +- 处理 cross-window dock/drop +- 更新 detached workspace title + +utility window 不参与: + +- workspace set +- dock target hit test +- cross-window drop target + +### 6.2 Utility Coordinator + +`EditorUtilityWindowCoordinator` 只处理 utility window 请求: + +- 根据 `EditorUtilityWindowDescriptor` 创建工具窗 +- 按 reuse policy 决定复用还是新建 +- 对现有 utility window 做 focus / restore + +不要把 utility window 塞进 workspace mutation 流程。 + +## 7. Frame Request 结构 + +`EditorWindowFrameTransferRequests` 已拆成两个子域: + +```text +workspace: + beginGlobalTabDrag + detachPanel + +utility: + openUtilityWindow +``` + +规则: + +- `EditorWindowWorkspaceCoordinator` 只消费 `transferRequests.workspace` +- `EditorUtilityWindowCoordinator` 只消费 `transferRequests.utility` +- queued immediate frame 也按两个子域分别合并 + +不要再把 workspace 和 utility 的窗口请求混进同一个平铺字段集合。 + +## 8. 每帧流程 + +当前每帧主路径: + +```text +Application::Run + -> EditorWindowManager::RenderAllWindows + -> EditorWindowRuntimeController::BeginFrame + -> EditorWindowRuntimeController::UpdateAndAppend + -> EditorWindowFrameOrchestrator::UpdateAndAppend + -> EditorShellRuntime::Update + -> EditorShellSessionCoordinator::PrepareShellDefinition + -> EditorShellInteractionEngine::Update + -> UpdateUIEditorShellInteraction + -> ViewportHostService::RequestViewport + -> EditorShellSessionCoordinator::FinalizeFrame + -> EditorShellHostedPanelCoordinator::Update + -> EditorShellRuntime::Append + -> EditorWindowRuntimeController::RenderRequestedViewports + -> EditorWindowRuntimeController::Present +``` + +重要约束: + +- viewport 先在 shell layout 阶段通过 `RequestViewport()` 申明尺寸和纹理需求。 +- 再在 `RenderRequestedViewports()` 阶段真正渲染。 +- 不要打乱这两个阶段的顺序。 + +## 9. 修改规范 + +- 先判断改动属于 framework、app core、Win32 host 还是 rendering host。 +- 小改动优先贴近现有模式,不顺手做跨层重构。 +- 新增通用 widget 放 `editor/src` / `editor/include/XCEditor`。 +- 新增具体 editor 功能放 `editor/app/Features/`,再由 composition 装配。 +- 新增窗口行为放 `editor/app/Platform/Win32`,通过 content/controller/policy/binding 与 app core 通信。 +- 新增 viewport 渲染资源或 pass 放 `editor/app/Rendering/Viewport` 或 `editor/resources/shaders/scene-viewport`。 +- draw append 只负责绘制,不修改 editor runtime 或 scene/project state。 +- D3D12 host、Win32 host、panel 之间避免双向强耦合。 + +## 10. 当前架构债务 + +这轮重构完成后,仍有以下债务: + +- `editor/CMakeLists.txt` 还没有把 framework、app core、Win32 host、D3D12 host 拆成独立 target。 +- `EditorContext` 仍然过重,混合了全局状态、service locator、command bridge、session sync 和状态输出。 +- `EditorShellRuntime` 仍然过重,既持有 panel,又持有 viewport host、shell frame、draw composer 和 interaction coordinator。 +- panel 仍缺少统一 feature 接口,`HierarchyPanel`、`ProjectPanel`、`InspectorPanel` 只是模式相似。 +- Win32 host 和 utility host 还没有拆到独立目录层级,例如 `Windowing/Core`、`Windowing/Workspace`、`Windowing/Utility`。 + +这些债务后续要分步处理,不要一次性重写整个窗口系统。 + +## 11. 验证基线 + +当前这轮窗口架构重构的验证状态: + +- 用户已完成 GUI 行为验证。 +- `cmake --build build --config Debug --target XCUIEditorApp` 已通过。 +- `build/editor/Debug/XCEngine.exe` 的 12 秒启动冒烟测试已通过,进程可正常响应关闭请求。 + +后续涉及窗口架构的改动,至少应维持这条验证基线。 + +## 12. 推荐阅读入口 + +开始 editor 相关任务时,优先阅读: + +- `editor/CMakeLists.txt` +- `editor/app/Bootstrap/Application.*` +- `editor/app/Composition/EditorContext.*` +- `editor/app/Composition/EditorShellRuntime.*` +- `editor/app/Platform/Win32/Content/EditorWindowContentController.h` +- `editor/app/Platform/Win32/Windowing/EditorWindow.*` +- `editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.*` +- `editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.*` +- `editor/app/UtilityWindows/EditorUtilityWindowRegistry.*` +- `editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h` +- `editor/include/XCEditor/Shell/UIEditorShellInteraction.h` + diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 4cca2f57..d47d74e3 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -306,7 +306,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ) set_target_properties(XCUIEditorApp PROPERTIES - OUTPUT_NAME "XCUIEditor" + OUTPUT_NAME "XCEngine" ) if(WIN32 AND EXISTS "${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll") @@ -323,6 +323,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) $/XCUIEditorAppLib.lib $/XCUIEditorHost.lib $/XCUIEditorLib.lib + $/XCUIEditor.exe ) if(WIN32 AND XCENGINE_ENABLE_PHYSX) diff --git a/editor/app/Bootstrap/Application.cpp b/editor/app/Bootstrap/Application.cpp index 6ebfdbbe..9db9cf45 100644 --- a/editor/app/Bootstrap/Application.cpp +++ b/editor/app/Bootstrap/Application.cpp @@ -144,6 +144,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { App::EditorWindowManager::CreateParams createParams = {}; createParams.windowId = "main"; createParams.title = kWindowTitle; + createParams.category = App::EditorWindowCategory::Workspace; createParams.showCommand = nCmdShow; createParams.primary = true; createParams.autoCaptureOnStartup = diff --git a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp index 7f41ac94..b7f0fef7 100644 --- a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp +++ b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp @@ -699,6 +699,7 @@ bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip( const EditorWindow& window) const { const EditorWindowTitleBarBinding* titleBarBinding = window.m_runtime->TryGetTitleBarBinding(); return !window.IsPrimary() && + window.GetChromePolicy().allowDetachedTitleBarTabStrip && titleBarBinding != nullptr && titleBarBinding->ShouldUseDetachedTitleBarTabStrip(); } @@ -783,7 +784,10 @@ void EditorWindowChromeController::AppendChrome( const std::string titleText = cachedTitleText.empty() ? std::string("XCEngine Editor") : std::string(cachedTitleText); - const std::string frameRateText = window.m_runtime->BuildFrameRateText(); + const std::string frameRateText = + window.GetChromePolicy().showFrameStats + ? window.m_runtime->BuildFrameRateText() + : std::string(); drawList.AddText( UIPoint( iconX + diff --git a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp b/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp index da51ebca..f7dd5759 100644 --- a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp +++ b/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.cpp @@ -18,6 +18,18 @@ EditorUtilityWindowContentController::EditorUtilityWindowContentController( EditorUtilityWindowContentController::~EditorUtilityWindowContentController() = default; +EditorWindowContentCapabilities +EditorUtilityWindowContentController::GetCapabilities() const { + return EditorWindowContentCapabilities{ + .workspace = false, + .dockHost = false, + .inputFeedback = false, + .titleBar = false, + .viewportRendering = false, + .utilityPanel = true, + }; +} + void EditorUtilityWindowContentController::Shutdown() { if (m_panel != nullptr) { m_panel->ResetInteractionState(); diff --git a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.h b/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.h index 2b31125d..bb3dd3e0 100644 --- a/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.h +++ b/editor/app/Platform/Win32/Content/EditorUtilityWindowContentController.h @@ -21,6 +21,7 @@ public: const ::XCEngine::UI::UISize& minimumOuterSize); ~EditorUtilityWindowContentController() override; + EditorWindowContentCapabilities GetCapabilities() const override; void Shutdown() override; void ResetInteractionState() override; EditorWindowFrameTransferRequests UpdateAndAppend( diff --git a/editor/app/Platform/Win32/Content/EditorWindowContentController.h b/editor/app/Platform/Win32/Content/EditorWindowContentController.h index 5a883f9b..ca6e196c 100644 --- a/editor/app/Platform/Win32/Content/EditorWindowContentController.h +++ b/editor/app/Platform/Win32/Content/EditorWindowContentController.h @@ -58,6 +58,15 @@ enum class EditorWindowContentCursorKind : std::uint8_t { ResizeNS, }; +struct EditorWindowContentCapabilities { + bool workspace = false; + bool dockHost = false; + bool inputFeedback = false; + bool titleBar = false; + bool viewportRendering = false; + bool utilityPanel = false; +}; + class EditorWindowWorkspaceBinding { public: virtual ~EditorWindowWorkspaceBinding() = default; @@ -125,6 +134,10 @@ class EditorWindowContentController { public: virtual ~EditorWindowContentController() = default; + virtual EditorWindowContentCapabilities GetCapabilities() const { + return {}; + } + virtual EditorWindowWorkspaceBinding* TryGetWorkspaceBinding() { return nullptr; } diff --git a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp b/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp index e901f869..a85566c0 100644 --- a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp +++ b/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.cpp @@ -37,6 +37,18 @@ EditorWorkspaceWindowContentController::EditorWorkspaceWindowContentController( EditorWorkspaceWindowContentController::~EditorWorkspaceWindowContentController() = default; +EditorWindowContentCapabilities +EditorWorkspaceWindowContentController::GetCapabilities() const { + return EditorWindowContentCapabilities{ + .workspace = true, + .dockHost = true, + .inputFeedback = true, + .titleBar = true, + .viewportRendering = true, + .utilityPanel = false, + }; +} + EditorWindowWorkspaceBinding* EditorWorkspaceWindowContentController::TryGetWorkspaceBinding() { return this; } diff --git a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h b/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h index 59465157..a74add20 100644 --- a/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h +++ b/editor/app/Platform/Win32/Content/EditorWorkspaceWindowContentController.h @@ -20,6 +20,7 @@ public: explicit EditorWorkspaceWindowContentController(UIEditorWorkspaceController workspaceController); ~EditorWorkspaceWindowContentController() override; + EditorWindowContentCapabilities GetCapabilities() const override; EditorWindowWorkspaceBinding* TryGetWorkspaceBinding() override; const EditorWindowWorkspaceBinding* TryGetWorkspaceBinding() const override; EditorWindowDockHostBinding* TryGetDockHostBinding() override; diff --git a/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp b/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp index 63c10bee..e739873c 100644 --- a/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp +++ b/editor/app/Platform/Win32/Runtime/EditorWindowFrameOrchestrator.cpp @@ -83,12 +83,12 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend if (const std::optional requestedKind = editorContext.ConsumeOpenUtilityWindowRequest(); requestedKind.has_value()) { - transferRequests.openUtilityWindow = EditorWindowOpenUtilityWindowRequest{ + transferRequests.utility.openUtilityWindow = EditorWindowOpenUtilityWindowRequest{ .kind = *requestedKind, .useCursorPlacement = GetCursorPos(&screenPoint) != FALSE, }; - if (transferRequests.openUtilityWindow->useCursorPlacement) { - transferRequests.openUtilityWindow->screenPoint = screenPoint; + if (transferRequests.utility.openUtilityWindow->useCursorPlacement) { + transferRequests.utility.openUtilityWindow->screenPoint = screenPoint; } } @@ -193,7 +193,7 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::BuildShellTrans !dockHostInteractionState.activeTabDragNodeId.empty() && !dockHostInteractionState.activeTabDragPanelId.empty() && hasScreenPoint) { - transferRequests.beginGlobalTabDrag = EditorWindowPanelTransferRequest{ + transferRequests.workspace.beginGlobalTabDrag = EditorWindowPanelTransferRequest{ dockHostInteractionState.activeTabDragNodeId, dockHostInteractionState.activeTabDragPanelId, screenPoint, @@ -201,7 +201,7 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::BuildShellTrans } if (shellFrame.result.workspaceResult.dockHostResult.detachRequested && hasScreenPoint) { - transferRequests.detachPanel = EditorWindowPanelTransferRequest{ + transferRequests.workspace.detachPanel = EditorWindowPanelTransferRequest{ shellFrame.result.workspaceResult.dockHostResult.detachedNodeId, shellFrame.result.workspaceResult.dockHostResult.detachedPanelId, screenPoint, diff --git a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp b/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp index 5c9beac1..ef9a7ebc 100644 --- a/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.cpp @@ -42,9 +42,11 @@ void EditorUtilityWindowCoordinator::BindLifecycleCoordinator( void EditorUtilityWindowCoordinator::HandleWindowFrameTransferRequests( EditorWindow& sourceWindow, const EditorWindowFrameTransferRequests& transferRequests) { - if (transferRequests.openUtilityWindow.has_value() && - transferRequests.openUtilityWindow->IsValid()) { - TryProcessOpenUtilityWindowRequest(sourceWindow, *transferRequests.openUtilityWindow); + if (transferRequests.utility.openUtilityWindow.has_value() && + transferRequests.utility.openUtilityWindow->IsValid()) { + TryProcessOpenUtilityWindowRequest( + sourceWindow, + *transferRequests.utility.openUtilityWindow); } } @@ -65,27 +67,35 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( return false; } - if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); - existingWindow != nullptr && existingWindow->IsDestroyed() && - m_lifecycleCoordinator != nullptr) { - m_lifecycleCoordinator->ReapDestroyedWindows(); - } + if (descriptor->reusePolicy == EditorUtilityWindowReusePolicy::SingleInstance) { + if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); + existingWindow != nullptr && existingWindow->IsDestroyed() && + m_lifecycleCoordinator != nullptr) { + m_lifecycleCoordinator->ReapDestroyedWindows(); + } - if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); - IsLiveWindow(existingWindow)) { - FocusWindow(*existingWindow); - LogRuntimeTrace( - "utility", - "reused utility window '" + std::string(descriptor->windowId) + "'"); - return true; - } + if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); + IsLiveWindow(existingWindow)) { + if (!existingWindow->IsUtilityWindow()) { + LogRuntimeTrace( + "utility", + "open utility window request rejected: existing window id is not utility"); + return false; + } + FocusWindow(*existingWindow); + LogRuntimeTrace( + "utility", + "reused utility window '" + std::string(descriptor->windowId) + "'"); + return true; + } - if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); - existingWindow != nullptr) { - LogRuntimeTrace( - "utility", - "open utility window request rejected: existing utility window is not reusable"); - return false; + if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(descriptor->windowId); + existingWindow != nullptr) { + LogRuntimeTrace( + "utility", + "open utility window request rejected: existing utility window is not reusable"); + return false; + } } std::unique_ptr contentController = @@ -100,6 +110,9 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( EditorWindowHostRuntime::CreateParams createParams = {}; createParams.windowId = std::string(descriptor->windowId); createParams.title = descriptor->title; + createParams.category = EditorWindowCategory::Utility; + createParams.chromePolicy = descriptor->chromePolicy; + createParams.nativeStylePolicy = descriptor->nativeStylePolicy; createParams.primary = false; createParams.initialWidth = ResolveOuterDimension( descriptor->preferredOuterSize.width, diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp index 1f67497d..da966419 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp @@ -77,11 +77,15 @@ using ::XCEngine::UI::UIPoint; EditorWindow::EditorWindow( std::string windowId, std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, bool primary, std::unique_ptr contentController) : m_session(std::make_unique( std::move(windowId), std::move(title), + category, + chromePolicy, primary)) , m_chromeController(std::make_unique()) , m_frameOrchestrator(std::make_unique()) @@ -103,6 +107,14 @@ bool EditorWindow::HasHwnd() const { return m_session->HasHwnd(); } +EditorWindowCategory EditorWindow::GetCategory() const { + return m_session->GetCategory(); +} + +const EditorWindowChromePolicy& EditorWindow::GetChromePolicy() const { + return m_session->GetChromePolicy(); +} + EditorWindowLifecycleState EditorWindow::GetLifecycleState() const { return m_session->GetLifecycleState(); } @@ -111,6 +123,14 @@ bool EditorWindow::IsPrimary() const { return m_session->IsPrimary(); } +bool EditorWindow::IsWorkspaceWindow() const { + return m_session->IsWorkspaceWindow(); +} + +bool EditorWindow::IsUtilityWindow() const { + return m_session->IsUtilityWindow(); +} + bool EditorWindow::IsClosing() const { return m_session->IsClosing(); } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.h b/editor/app/Platform/Win32/Windowing/EditorWindow.h index 84cbf521..4d7f4afc 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.h @@ -67,6 +67,8 @@ public: EditorWindow( std::string windowId, std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, bool primary, std::unique_ptr contentController); ~EditorWindow(); @@ -79,8 +81,12 @@ public: std::string_view GetWindowId() const; 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; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp index f2e3b382..b246c051 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -67,9 +67,27 @@ EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( std::unique_ptr contentController, const CreateParams& params) { + if (contentController == nullptr) { + LogRuntimeTrace("window", "window creation failed: content controller is null"); + return nullptr; + } + + const EditorWindowContentCapabilities capabilities = + contentController->GetCapabilities(); + if (params.category == EditorWindowCategory::Workspace && !capabilities.workspace) { + LogRuntimeTrace("window", "workspace window creation rejected: content is not workspace"); + return nullptr; + } + if (params.category == EditorWindowCategory::Utility && !capabilities.utilityPanel) { + LogRuntimeTrace("window", "utility window creation rejected: content is not utility"); + return nullptr; + } + auto windowPtr = std::make_unique( params.windowId, params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title, + params.category, + params.chromePolicy, params.primary, std::move(contentController)); EditorWindow* const rawWindow = windowPtr.get(); @@ -88,11 +106,14 @@ EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( }; m_pendingCreateWindow = rawWindow; + const DWORD windowStyle = params.nativeStylePolicy.useHostWindowStyle + ? m_hostConfig.windowStyle + : params.nativeStylePolicy.windowStyle; const HWND hwnd = CreateWindowExW( - WS_EX_APPWINDOW, + params.nativeStylePolicy.extendedWindowStyle, m_hostConfig.windowClassName, rawWindow->GetTitle().c_str(), - m_hostConfig.windowStyle, + windowStyle, params.initialX, params.initialY, params.initialWidth, diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp index 55fa32f5..aaef013c 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowLifecycleCoordinator.cpp @@ -135,7 +135,9 @@ void EditorWindowLifecycleCoordinator::HandleNativeWindowDestroyed(EditorWindow& } ShutdownRuntimeIfNeeded(window); - m_workspaceCoordinator.RemoveWindowProjection(window.GetWindowId(), destroyedPrimary); + if (window.IsWorkspaceWindow()) { + m_workspaceCoordinator.RemoveWindowProjection(window.GetWindowId(), destroyedPrimary); + } window.MarkDestroyed(); if (destroyedPrimary) { diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowManager.h b/editor/app/Platform/Win32/Windowing/EditorWindowManager.h index eca85c52..ffc966bd 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowManager.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowManager.h @@ -4,6 +4,8 @@ #define NOMINMAX #endif +#include "Platform/Win32/Windowing/EditorWindowState.h" + #include #include @@ -52,6 +54,9 @@ public: int initialWidth = 1540; int initialHeight = 940; int showCommand = SW_SHOW; + EditorWindowCategory category = EditorWindowCategory::Workspace; + EditorWindowChromePolicy chromePolicy = {}; + EditorWindowNativeStylePolicy nativeStylePolicy = {}; bool primary = false; bool autoCaptureOnStartup = false; }; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp index 2e5045ad..58d8f8a0 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp @@ -11,9 +11,13 @@ using namespace EditorWindowSupport; EditorWindowSession::EditorWindowSession( std::string windowId, std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, bool primary) { m_state.window.windowId = std::move(windowId); m_state.window.title = std::move(title); + m_state.window.category = category; + m_state.window.chromePolicy = chromePolicy; m_state.window.primary = primary; UpdateCachedTitleText(); } @@ -30,6 +34,14 @@ bool EditorWindowSession::HasHwnd() const { return m_state.window.hwnd != nullptr; } +EditorWindowCategory EditorWindowSession::GetCategory() const { + return m_state.window.category; +} + +const EditorWindowChromePolicy& EditorWindowSession::GetChromePolicy() const { + return m_state.window.chromePolicy; +} + EditorWindowLifecycleState EditorWindowSession::GetLifecycleState() const { return m_state.window.lifecycle; } @@ -38,6 +50,14 @@ bool EditorWindowSession::IsPrimary() const { return m_state.window.primary; } +bool EditorWindowSession::IsWorkspaceWindow() const { + return m_state.window.category == EditorWindowCategory::Workspace; +} + +bool EditorWindowSession::IsUtilityWindow() const { + return m_state.window.category == EditorWindowCategory::Utility; +} + bool EditorWindowSession::IsClosing() const { return m_state.window.lifecycle == EditorWindowLifecycleState::Closing; } @@ -92,17 +112,17 @@ void EditorWindowSession::SetTitle(std::wstring title) { void EditorWindowSession::QueueCompletedImmediateFrame( EditorWindowFrameTransferRequests transferRequests) { m_hasQueuedCompletedImmediateFrame = true; - if (transferRequests.beginGlobalTabDrag.has_value()) { - m_queuedImmediateFrameTransferRequests.beginGlobalTabDrag = - std::move(transferRequests.beginGlobalTabDrag); + if (transferRequests.workspace.beginGlobalTabDrag.has_value()) { + m_queuedImmediateFrameTransferRequests.workspace.beginGlobalTabDrag = + std::move(transferRequests.workspace.beginGlobalTabDrag); } - if (transferRequests.detachPanel.has_value()) { - m_queuedImmediateFrameTransferRequests.detachPanel = - std::move(transferRequests.detachPanel); + if (transferRequests.workspace.detachPanel.has_value()) { + m_queuedImmediateFrameTransferRequests.workspace.detachPanel = + std::move(transferRequests.workspace.detachPanel); } - if (transferRequests.openUtilityWindow.has_value()) { - m_queuedImmediateFrameTransferRequests.openUtilityWindow = - std::move(transferRequests.openUtilityWindow); + if (transferRequests.utility.openUtilityWindow.has_value()) { + m_queuedImmediateFrameTransferRequests.utility.openUtilityWindow = + std::move(transferRequests.utility.openUtilityWindow); } } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowSession.h b/editor/app/Platform/Win32/Windowing/EditorWindowSession.h index c2027c0b..95a5065a 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowSession.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowSession.h @@ -10,13 +10,22 @@ namespace XCEngine::UI::Editor::App { class EditorWindowSession final { public: - EditorWindowSession(std::string windowId, std::wstring title, bool primary); + EditorWindowSession( + std::string windowId, + std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, + bool primary); std::string_view GetWindowId() const; 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; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowState.h b/editor/app/Platform/Win32/Windowing/EditorWindowState.h index 5b17b15c..bd9abbd7 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowState.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowState.h @@ -21,6 +21,34 @@ enum class EditorWindowLifecycleState : std::uint8_t { 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; +}; + +struct EditorWindowNativeStylePolicy { + DWORD extendedWindowStyle = WS_EX_APPWINDOW; + DWORD windowStyle = 0; + bool useHostWindowStyle = true; +}; + inline std::string_view GetEditorWindowLifecycleStateName( EditorWindowLifecycleState state) { switch (state) { @@ -46,6 +74,8 @@ struct EditorWindowWindowState { std::string windowId = {}; std::wstring title = {}; std::string titleText = {}; + EditorWindowCategory category = EditorWindowCategory::Workspace; + EditorWindowChromePolicy chromePolicy = {}; bool primary = false; EditorWindowLifecycleState lifecycle = EditorWindowLifecycleState::PendingNativeCreate; }; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h b/editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h index 363cad23..29f55d4b 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowTransferRequests.h @@ -33,15 +33,31 @@ struct EditorWindowOpenUtilityWindowRequest { } }; -struct EditorWindowFrameTransferRequests { +struct EditorWorkspaceWindowFrameTransferRequests { std::optional beginGlobalTabDrag = {}; std::optional detachPanel = {}; - std::optional openUtilityWindow = {}; bool HasPendingRequests() const { return beginGlobalTabDrag.has_value() || - detachPanel.has_value() || - openUtilityWindow.has_value(); + detachPanel.has_value(); + } +}; + +struct EditorUtilityWindowFrameTransferRequests { + std::optional openUtilityWindow = {}; + + bool HasPendingRequests() const { + return openUtilityWindow.has_value(); + } +}; + +struct EditorWindowFrameTransferRequests { + EditorWorkspaceWindowFrameTransferRequests workspace = {}; + EditorUtilityWindowFrameTransferRequests utility = {}; + + bool HasPendingRequests() const { + return workspace.HasPendingRequests() || + utility.HasPendingRequests(); } }; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp index e924b001..6cba00ed 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.cpp @@ -92,6 +92,7 @@ UIEditorWindowWorkspaceSet BuildLiveWindowWorkspaceSet( for (const std::unique_ptr& window : hostRuntime.GetWindows()) { if (window == nullptr || window->GetHwnd() == nullptr || + !window->IsWorkspaceWindow() || window->GetWindowId() == excludedWindowId || (!includeClosingWindows && window->GetLifecycleState() != EditorWindowLifecycleState::Running)) { @@ -144,12 +145,15 @@ void EditorWindowWorkspaceCoordinator::RegisterExistingWindow(EditorWindow& wind } void EditorWindowWorkspaceCoordinator::RefreshWindowPresentation(EditorWindow& window) const { + if (!window.IsWorkspaceWindow()) { + return; + } RefreshWindowTitle(window); } bool EditorWindowWorkspaceCoordinator::IsPrimaryWindowId(std::string_view windowId) const { if (const EditorWindow* window = m_hostRuntime.FindWindow(windowId); window != nullptr) { - return window->IsPrimary(); + return window->IsWorkspaceWindow() && window->IsPrimary(); } return false; @@ -273,6 +277,14 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( } if (existingWindow != nullptr) { + if (!existingWindow->IsWorkspaceWindow()) { + LogRuntimeTrace( + "window", + "workspace synchronization rejected: window '" + entry.windowId + + "' is not a workspace window"); + return false; + } + if (existingWindow->GetLifecycleState() != EditorWindowLifecycleState::Running) { LogRuntimeTrace( "window", @@ -310,6 +322,7 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( EditorWindowHostRuntime::CreateParams createParams = {}; createParams.windowId = entry.windowId; + createParams.category = EditorWindowCategory::Workspace; createParams.primary = isPrimaryWindow; createParams.title = createParams.primary @@ -348,6 +361,7 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( for (const std::unique_ptr& window : m_hostRuntime.GetWindows()) { if (window == nullptr || window->GetHwnd() == nullptr || + !window->IsWorkspaceWindow() || window->IsPrimary() || window->GetLifecycleState() != EditorWindowLifecycleState::Running) { continue; @@ -640,6 +654,13 @@ bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND h bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( EditorWindow& sourceWindow, const EditorWindowPanelTransferRequest& request) { + if (!sourceWindow.IsWorkspaceWindow()) { + LogRuntimeTrace( + "drag", + "failed to start global tab drag: source window is not a workspace window"); + return false; + } + if (!IsLiveInteractiveWindow(&sourceWindow)) { LogRuntimeTrace("drag", "failed to start global tab drag: source window is closing"); return false; @@ -739,6 +760,13 @@ bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest( EditorWindow& sourceWindow, const EditorWindowPanelTransferRequest& request) { + if (!sourceWindow.IsWorkspaceWindow()) { + LogRuntimeTrace( + "detach", + "detach request rejected: source window is not a workspace window"); + return false; + } + if (!IsLiveInteractiveWindow(&sourceWindow)) { LogRuntimeTrace("detach", "detach request rejected: source window is closing"); return false; @@ -781,15 +809,19 @@ void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests( EditorWindow& sourceWindow, const EditorWindowFrameTransferRequests& transferRequests) { if (!m_globalTabDragSession.active && - transferRequests.beginGlobalTabDrag.has_value() && - transferRequests.beginGlobalTabDrag->IsValid()) { - TryStartGlobalTabDrag(sourceWindow, *transferRequests.beginGlobalTabDrag); + transferRequests.workspace.beginGlobalTabDrag.has_value() && + transferRequests.workspace.beginGlobalTabDrag->IsValid()) { + TryStartGlobalTabDrag( + sourceWindow, + *transferRequests.workspace.beginGlobalTabDrag); } if (!m_globalTabDragSession.active && - transferRequests.detachPanel.has_value() && - transferRequests.detachPanel->IsValid()) { - TryProcessDetachRequest(sourceWindow, *transferRequests.detachPanel); + transferRequests.workspace.detachPanel.has_value() && + transferRequests.workspace.detachPanel->IsValid()) { + TryProcessDetachRequest( + sourceWindow, + *transferRequests.workspace.detachPanel); } } @@ -800,6 +832,7 @@ EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( const HWND rootWindow = GetAncestor(hitWindow, GA_ROOT); if (EditorWindow* window = m_hostRuntime.FindWindow(rootWindow); IsLiveInteractiveWindow(window) && + window->IsWorkspaceWindow() && window->GetWindowId() != excludedWindowId) { return window; } @@ -809,6 +842,7 @@ EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( EditorWindow* const window = it->get(); if (window == nullptr || !IsLiveInteractiveWindow(window) || + !window->IsWorkspaceWindow() || window->GetWindowId() == excludedWindowId) { continue; } diff --git a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp index b317167e..f0496d6c 100644 --- a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp +++ b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.cpp @@ -9,10 +9,23 @@ namespace { using ::XCEngine::UI::UISize; +constexpr EditorWindowChromePolicy kUtilityWindowChromePolicy = { + .allowDetachedTitleBarTabStrip = false, + .showFrameStats = false, +}; + +constexpr EditorWindowNativeStylePolicy kUtilityWindowNativeStylePolicy = { + .extendedWindowStyle = WS_EX_TOOLWINDOW, + .windowStyle = 0, + .useHostWindowStyle = true, +}; + constexpr EditorUtilityWindowDescriptor kColorPickerUtilityWindowDescriptor = { .kind = EditorUtilityWindowKind::ColorPicker, .windowId = "utility.color-picker", .title = L"Color Picker", + .chromePolicy = kUtilityWindowChromePolicy, + .nativeStylePolicy = kUtilityWindowNativeStylePolicy, .preferredOuterSize = UISize(352.0f, 500.0f), .minimumOuterSize = UISize(320.0f, 460.0f), }; @@ -21,6 +34,8 @@ constexpr EditorUtilityWindowDescriptor kAddComponentUtilityWindowDescriptor = { .kind = EditorUtilityWindowKind::AddComponent, .windowId = "utility.add-component", .title = L"Add Component", + .chromePolicy = kUtilityWindowChromePolicy, + .nativeStylePolicy = kUtilityWindowNativeStylePolicy, .preferredOuterSize = UISize(352.0f, 500.0f), .minimumOuterSize = UISize(320.0f, 460.0f), }; diff --git a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h index 50a8c776..7462f5f5 100644 --- a/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h +++ b/editor/app/UtilityWindows/EditorUtilityWindowRegistry.h @@ -2,6 +2,7 @@ #include "UtilityWindows/EditorUtilityWindowKind.h" #include "UtilityWindows/EditorUtilityWindowPanel.h" +#include "Platform/Win32/Windowing/EditorWindowState.h" #include @@ -10,10 +11,18 @@ namespace XCEngine::UI::Editor::App { +enum class EditorUtilityWindowReusePolicy { + SingleInstance = 0, +}; + struct EditorUtilityWindowDescriptor { EditorUtilityWindowKind kind = EditorUtilityWindowKind::None; std::string_view windowId = {}; const wchar_t* title = L""; + EditorUtilityWindowReusePolicy reusePolicy = + EditorUtilityWindowReusePolicy::SingleInstance; + EditorWindowChromePolicy chromePolicy = {}; + EditorWindowNativeStylePolicy nativeStylePolicy = {}; ::XCEngine::UI::UISize preferredOuterSize = {}; ::XCEngine::UI::UISize minimumOuterSize = {}; };