diff --git a/editor/AGENTS.md b/editor/AGENTS.md index c0d4a319..23f4c1ab 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -1,319 +1,244 @@ # XCUI Editor Agent Guide + +This file documents the current editor architecture for agents working under +`editor/`. It describes the code that exists in this checkout, not a desired +future shape. + +If this file conflicts with the current code, `editor/CMakeLists.txt`, or the +real directory tree, trust the code and update this file in the same change. + ## Build Shape -The editor is a two-layer system. +The important production targets are: -- `XCUIEditorLib` is the reusable framework library. -- `XCUIEditorApp` is the concrete editor application executable. -- The build shape of `editor/` is `XCUIEditorLib <- XCUIEditorApp`. -- `editor/src/**` and `editor/include/XCEditor/**` belong to the framework layer. -- `editor/app/**` belongs to the app layer and is compiled directly into `XCUIEditorApp`. -- App-internal windowing authority, planner, projection, and synchronization code now live in `editor/app/Windowing/System` and `editor/app/Windowing/Presentation`. -- `app/Windowing` now owns app-side content controllers, coordinators, and frame transfer seams; `app/Platform/Win32` remains the concrete host. -- Win32 hosting, utility windows, and editor-only rendering stay in `editor/app/**`. -- `editor/include/XCEditor/**` exposes framework-level APIs only. +- `XCUIEditorLib`: reusable XCEditor framework code from `editor/src` and + `editor/include/XCEditor`. +- `XCUIEditorAppWindowing`: app-internal static library for the current + window authority, planner, store, presentation policy, host contracts, + content controllers, coordinators, frame transfer flow, and window manager. +- `XCUIEditorApp`: concrete editor executable. Its output name is `XCEngine`. -Architecture shorthand for this repo: +Do not invent target boundaries that are not present in `editor/CMakeLists.txt`. +Names such as `XCUIEditorAppCore`, `XCUIEditorAppLib`, and `XCUIEditorHost` +are not current production library boundaries in this checkout. -```text -XCUIEditorLib - <- XCUIEditorApp -``` +There is no public `editor/include/XCEditor/Windowing` layer yet. Windowing is +currently an app-internal architecture under `editor/app/Windowing` plus the +concrete Win32 host under `editor/app/Platform/Win32`. -When describing the current architecture, treat `XCEditor/Windowing` as framework code and `app/Windowing` / `app/Platform/Win32` as the app-side orchestration and host layers. +## Layering -这份文档面向在 `editor/` 下工作的 coding agent / 开发者。它描述当前 editor 的真实工程边界、窗口架构和修改约束,不是产品说明。 +Use these ownership boundaries when changing code: -如果本文和当前代码、`editor/CMakeLists.txt`、实际目录结构冲突,以当前 checkout 为准,并在本次工作里同步修正文档。 +- `editor/include/XCEditor` and `editor/src`: reusable editor framework. + Keep this layer independent from app state, Win32, D3D12 host code, and + `App::*` types. +- `editor/app/Composition`, `Commands`, `Features`, `Project`, `Scene`, + `State`, `System`, `UtilityWindows`: editor product semantics. +- `editor/app/Windowing/System`: authoritative window set, validation, + synchronization planning, and commit. +- `editor/app/Windowing/Presentation`: projection policy derived from + authoritative state. +- `editor/app/Windowing/Content`, `Coordinator`, `Frame`, `Host`: platform + neutral app window orchestration. +- `editor/app/Platform/Win32`: native window, message dispatch, input, + lifecycle, chrome, and concrete host runtime. +- `editor/app/Rendering`: editor rendering host and D3D12 integration. -## 1. 当前定位 - -`editor/` 是当前 editor 应用主线。 - -当前工程事实: - -- 可复用 UI framework 的真实构建目标是 `XCUIEditorLib`。 -- editor 应用的真实可执行目标是 `XCUIEditorApp`。 -- `XCUIEditorApp` 的输出名是 `XCEngine`,产物为 `XCEngine.exe`。 -- `editor/src` + `editor/include/XCEditor` 是 XCEditor framework。 -- `editor/app` 是具体 XCEngine editor 应用层。 -- `editor/resources` 存放 editor 自有图标和 scene viewport shader。 - -当前 `editor/CMakeLists.txt` 并没有把 app core、windowing seam、Win32 host、rendering host 拆成独立生产 target。除了 `XCUIEditorLib` 之外,其余 app / windowing / platform / rendering 代码仍直接编进 `XCUIEditorApp`。 - -`XCUIEditorAppLib`、`XCUIEditorAppCore`、`XCUIEditorHost` 不是当前 `editor/CMakeLists.txt` 里真实定义的生产 target;它们只在部分测试脚本或清理逻辑里作为条件名称出现。不要把这些名字当成当前已经存在的模块边界。 - -## 2. 当前分层 - -推荐按下面五层理解和修改,但要记住这主要是目录和语义边界,不是已经被 CMake 强制落实的编译边界: - -1. `editor/src` / `editor/include/XCEditor` - XCEditor framework。这里应保持通用,不依赖具体 editor app、Win32 宿主或 D3D12 实现。 - -2. `editor/app/Composition`、`Commands`、`Features`、`Project`、`Scene`、`State`、`Support`、`System`、`UtilityWindows` - editor 应用语义层。这里放 panel 组合、项目/场景运行时、选择状态、命令桥接、utility window 描述和 app 侧服务。 - -3. `editor/app/Windowing` - 平台无关的窗口主线。这里放 content controller contract、frame transfer request、workspace synchronization planner / presentation policy / authoritative window set。 - -4. `editor/app/Platform/Win32` - Win32 host。这里负责 native window、消息分发、生命周期、输入翻译、窗口 chrome、host runtime 和 coordinator。 - -5. `editor/app/Rendering` - editor 渲染宿主。这里负责 D3D12 UI 渲染、窗口 render loop、swapchain、viewport 资源和 scene viewport pass。 - -依赖方向尽量保持为: +The semantic dependency direction should remain: ```text XCEditor framework - <- app semantics - <- app/windowing - <- Platform/Win32 - <- app/Rendering + <- editor app semantics + <- app windowing + <- Win32 host / rendering host ``` -现实约束: +## Startup Flow -- 当前只有 `XCUIEditorLib` 是真实独立 target。 -- 其余层级只是目录和 ownership 约定。 -- 修改时仍要主动控制 include 方向,避免把“语义上应该分层”写回成硬耦合。 - -## 3. 顶层启动流程 - -入口: +The application starts through: ```text app/main.cpp -> RunXCUIEditorApp -> Application::Run + -> Application::Initialize ``` -当前启动主线: - -```text -Application - -> EditorContext - -> Win32SystemInteractionHost - -> EditorWindowSystem - -> EditorWindowManager - -> EditorWindowHostRuntime - -> EditorWindowWorkspaceCoordinator - -> EditorUtilityWindowCoordinator - -> EditorWindowLifecycleCoordinator -``` - -primary workspace window 的 bootstrap 顺序是: +The primary workspace window is initialized through: ```text EditorContext::BuildWorkspaceController() -> EditorWindowSystem::BootstrapPrimaryWindow(...) -> EditorWindowManager::CreateWorkspaceWindow(...) + -> EditorWindowContentFactory::CreateWorkspaceContentController(...) + -> EditorWindowHostRuntime::CreateHostWindow(...) ``` -不要把 primary window authority 初始化放回 Win32 host。当前 authoritative bootstrap 已经在 `EditorWindowSystem`。 +Keep authoritative window bootstrap in `EditorWindowSystem`; do not move it +back into the Win32 host. -## 4. 当前窗口系统事实 +## Window Authority Model -### 4.1 Authority / Planner / Host Execution +`EditorWindowSystem` owns the authoritative `UIEditorWindowWorkspaceSet` through +`EditorWindowWorkspaceStore`. -当前多窗口 workspace 主线里,几个核心对象的职责是: - -- `EditorWindowSystem` - - 持有 authoritative `UIEditorWindowWorkspaceSet` - - 通过 `EditorWindowWorkspaceStore` 做 validation / commit - - 构建 synchronization plan - - 处理显式 `workspaceMutation` request - - 处理 destroyed window reconciliation - -- `EditorWindowWorkspaceCoordinator` - - 采集 host snapshot - - 调用 `EditorWindowSystem` 生成 plan - - 执行 host-side create / update / close - - 处理 global tab drag、detach、cross-window dock/drop - - 处理 window presentation refresh - - 在 native destroy 时回到 `EditorWindowSystem` 做 reconcile - -重要事实: - -- `EditorWindowWorkspaceCoordinator` 已不再直接写 authority store。 -- 但它仍然很厚,仍同时承担 host 执行、presentation refresh、drag/detach 流程和部分回滚时序。 -- 当前不要把它描述成“纯粹的薄 host adapter”。 - -### 4.2 Live Workspace Projection 还没有完全去状态化 - -`EditorWorkspaceWindowContentController` 当前内部仍持有: - -- `UIEditorWorkspaceController` -- `EditorShellRuntime` -- `EditorWindowFrameOrchestrator` - -这意味着当前 workspace window 仍然有一个 live mutable controller。它已经不是 authority,但也还不是完全只读 projection。 - -当前真实行为是: - -- `EditorWorkspaceWindowContentController::UpdateAndAppend(...)` 会在 layout snapshot 发生变化时发出显式 `workspaceMutation` request。 -- `EditorWorkspaceWindowContentController` 现在持有显式 `EditorWorkspaceWindowProjection`,host 常规同步 / presentation 路径优先消费 projection refresh,而不是整体替换 live controller。 -- `EditorWindow::TryGetWorkspaceController()` 仍保留给局部交互 / 过渡路径使用,但它不再是常规 host 同步主接口。 -- 每个 workspace window 依然持有自己的一份 live `UIEditorWorkspaceController`。 - -因此: - -- 不要把当前架构写成“projection-only content controller 已完成”。 - -## 5. 窗口类别与内容控制器 - -当前窗口系统显式区分两类窗口: - -- `EditorWindowCategory::Workspace` -- `EditorWindowCategory::Utility` - -两类窗口共享同一个 native host: +Normal per-frame workspace edits now use direct writes into that authoritative +state: ```text -EditorWindow - -> EditorWindowRuntimeController - -> D3D12WindowRenderLoop - -> EditorWindowContentController +EditorWorkspaceWindowContentController::UpdateAndAppend(...) + -> EditorWindowSystem::TryBuildLiveWindowWorkspaceController(windowId, ...) + -> UIEditorWorkspaceController::BindToState(...) + -> workspace operations mutate the bound authoritative workspace/session ``` -### 5.1 Workspace Window - -workspace window 的内容控制器是 `EditorWorkspaceWindowContentController`。 - -它是完整 shell/workspace 容器,用于: - -- 主窗口 -- detached workspace window - -### 5.2 Utility Window - -utility window 的内容控制器是 `EditorUtilityWindowContentController`,内部承载 `EditorUtilityWindowPanel`。 - -当前 registry / factory 主线是显式 descriptor 驱动: - -- `EditorUtilityWindowDescriptor` -- `EditorUtilityWindowReusePolicy` -- `EditorWindowChromePolicy` -- `EditorWindowNativeStylePolicy` - -当前已注册的 utility window: - -- `ColorPicker` -- `AddComponent` - -两者当前都使用 `SingleInstance` 复用策略。 - -### 5.3 Capability 校验 - -窗口内容能力由 `EditorWindowContentCapabilities` 描述,`EditorWindowHostRuntime::CreateEditorWindow(...)` 在创建时做硬校验: - -- `Workspace` 分类要求 `capabilities.workspace == true` -- `Utility` 分类要求 `capabilities.utilityPanel == true` - -不要依赖“后续 coordinator 看到空 binding 再兜底”这种旧思路。 - -## 6. Frame Request 与每帧主线 - -`EditorWindowFrameTransferRequests` 当前按两个子域组织: +The old normal path is obsolete: ```text -workspace: - workspaceMutation - beginGlobalTabDrag - detachPanel - -utility: - openUtilityWindow +snapshot diff + -> workspaceMutation frame request + -> coordinator + -> synchronization plan + -> commit ``` -当前事实: +Do not reintroduce `workspaceMutation` as the steady-state route for ordinary +layout, tab, visibility, or active-panel edits inside an existing workspace +window. -- `workspaceMutation` 已经是 frame 内 workspace 变化的显式回传入口。 -- `openUtilityWindow` 来自 `EditorContext::RequestOpenUtilityWindow(...)`,由 `EditorWindowFrameOrchestrator` 消费 request state 后写入 frame transfer request。 -- queued immediate frame 也按这两个子域分别合并。 - -steady-state frame 主路径是: +Cross-window create, update, close, and destroyed-window reconciliation still +use the planner/coordinator flow: ```text -Application::Run - -> EditorWindowManager::RenderAllWindows - -> EditorWindowHostRuntime::RenderAllWindows - -> EditorWindowFrameDriver::DriveFrame - -> EditorWindowRuntimeController::UpdateAndAppend - -> EditorWindowContentController::UpdateAndAppend - -> EditorWindowWorkspaceCoordinator::RefreshWindowPresentation - -> EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests - -> EditorUtilityWindowCoordinator::HandleWindowFrameTransferRequests +target UIEditorWindowWorkspaceSet + -> EditorWindowSystem::BuildPlanForWindowSet(...) + -> EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan(...) + -> EditorWindowSystem::CommitSynchronizationPlan(...) ``` -另外,`WM_PAINT`、`WM_SIZE`、`WM_DPICHANGED`、`WM_EXITSIZEMOVE` 等消息也会触发 immediate frame,并在 message dispatcher 中 flush queued transfer requests。 +`EditorWindowWorkspaceCoordinator::RefreshWindowPresentation(...)` refreshes +each workspace window projection from authoritative state. Presentation should +follow authority; it should not become a second source of truth. -## 7. 修改规范 +## Frame Transfer Requests -- 先判断改动属于 framework、app semantics、windowing、Win32 host 还是 rendering host。 -- 小改动优先贴近现有模式,不顺手做跨层重构。 -- 新增通用 widget 放 `editor/src` / `editor/include/XCEditor`。 -- 新增具体 editor 功能放 `editor/app/Features/`,再由 composition 装配。 -- 新增 utility window 先补 `EditorUtilityWindowKind`、`EditorUtilityWindowDescriptor`、`CreateEditorUtilityWindowPanel(...)`,不要直接在 Win32 coordinator 里硬编码窗口类型。 -- 新增跨窗口语义时,优先增加显式 frame request 或 app/windowing plan builder,不要回退到 Win32 host 直接读写 authority。 -- 修改窗口同步逻辑时,区分“当前代码事实”和“长期目标”。当前还存在 live mutable projection,不要按尚未落地的终态直接删接口。 -- 不要让 `editor/include/XCEditor/**` 暴露 `App::*` 类型。 -- draw append 只负责绘制,不顺手修改 scene / project / authority 状态。 +`EditorWindowFrameTransferRequests` is only for host-side or cross-window side +effects that cannot be represented as direct in-window workspace edits. -## 8. 当前架构债务 +Current request fields are: -当前仍然存在的主要债务: +- `workspace.beginGlobalTabDrag` +- `workspace.detachPanel` +- `utility.openUtilityWindow` -- 除 `XCUIEditorLib` 外,app core、windowing、Win32 host、rendering host 还没有真实拆成独立 target。 -- `EditorContext` 仍然很重,混合了 shell asset、项目/场景运行时、selection、command focus、utility window request 和状态输出。 -- `EditorShellRuntime` 仍然很重,既负责 shell compose / interaction,也负责 hosted panel、viewport request 和 draw append。 -- `EditorWorkspaceWindowContentController` 仍持有 live mutable `UIEditorWorkspaceController`,只是 host 常规同步 / presentation 已经切到 projection refresh;projection 和 authority 的边界仍未完全硬化。 -- `EditorWindowWorkspaceCoordinator` 仍然过厚,还没有收缩成纯粹的 host executor + native event bridge。 -- `EditorWindow::TryGetWorkspaceController()` 仍存在于交互 / 过渡路径,说明 live projection 去状态化还没完成;但常规同步路径已经不再依赖 `ReplaceWorkspaceController()`。 +There is no `workspace.workspaceMutation` field in the current frame transfer +contract. Treat any doc, comment, or test name that says otherwise as stale. -这些都是当前事实,不要在新的规范文档或代码注释里把它们写成“已经解决”。 +## Window Categories -## 9. 验证入口 +Workspace and utility windows share the native host path but use distinct +content controllers. -当前 editor 相关验证入口统一在 `tests/UI/Editor`: +- Workspace windows use `EditorWorkspaceWindowContentController`. +- Utility windows use `EditorUtilityWindowContentController`. +- App windowing creates content through `EditorWindowContentFactory`. +- The concrete host validates `EditorWindowContentCapabilities` when a native + host window is created. -- `editor_ui_tests` - framework 和基础 UI 单元测试。 -- `editor_windowing_phase1_tests` - 当前窗口 authority / planner / reconcile 规则的 focused unit tests。 -- `xcui_editor_app_smoke` - editor 启动烟测。 -- `tests/UI/Editor/manual_validation` - 复杂交互人工验证场景。 +Utility windows are descriptor driven through `EditorUtilityWindowDescriptor`, +`EditorUtilityWindowRegistry`, and `CreateEditorUtilityWindowPanel(...)`. +Register new utility windows there rather than hard-coding them in Win32 host +logic. -条件事实: +## Modification Rules -- `editor_app_feature_tests` 只会在 `XCUIEditorAppLib` 等可选 target 真实存在时创建。 -- 不要把它当成当前 checkout 一定存在的测试目标。 +- First decide whether the change belongs to XCEditor framework, app semantics, + app windowing, Win32 host, or rendering host. +- Keep public framework headers free of `App::*`, Win32, and D3D12 host types. +- Use `editor/app/Windowing` for window semantics that are not inherently Win32. +- Use `editor/app/Platform/Win32` only for native host behavior and message + integration. +- Do not let `editor/app/Windowing` include `Platform/Win32` or D3D12 concrete + host headers. +- Do not let Win32 host code create workspace or utility content directly. + It should receive content through the app-windowing host contract. +- Use direct authoritative workspace binding for ordinary in-window workspace + mutations. +- Use synchronization plans for operations that create, close, replace, or + reconcile workspace windows. +- Use frame transfer requests only for host-side or cross-window effects. +- Do not describe `XCEditor/Windowing` as an existing framework/public layer. -涉及窗口系统的改动,优先保持: +## Current Architecture Debt -- `XCUIEditorApp` 可构建 -- `editor_windowing_phase1_tests` 不回退 -- `xcui_editor_app_smoke` 不回退 +The highest-value windowing boundary has been hardened: `XCUIEditorAppWindowing` +now owns the app-internal window authority, presentation, content controller +factory, coordinators, frame transfer flow, host contracts, and manager. The +Win32 host is the concrete native adapter and creates native host windows from +content supplied by app windowing. -## 10. 推荐阅读入口 +The main remaining debt is promotion discipline. `XCUIEditorAppWindowing` is +still app-internal and may depend on app semantics such as `EditorContext`, +`EditorShellRuntime`, utility window descriptors, and product-specific content. +Do not promote this surface to `XCEditor` until the authority model, host +interfaces, frame transfer contract, and presentation projection are stable and +generic enough to expose. -开始 editor 相关任务时,优先阅读: +Do not paper over this debt by documenting a public windowing framework that +does not exist. + +## Validation + +Useful local targets for editor/windowing changes: + +- `cmake --build build --config Debug --target XCUIEditorApp` +- `cmake --build build --config Debug --target editor_windowing_phase1_tests` +- `cmake --build build --config Debug --target editor_ui_tests` + +Useful test executables when they are present in the configured build tree: + +- `build/tests/UI/Editor/unit/Debug/editor_windowing_phase1_tests.exe` +- `build/tests/UI/Editor/unit/Debug/editor_ui_tests.exe` + +`xcui_editor_app_smoke` is a useful supplemental smoke test when the app target +and smoke test are configured, but it is not the default windowing unit-test +entry point. In the current Debug build, the direct 12-second smoke command is: + +```powershell +build\tests\UI\Editor\smoke\Debug\editor_ui_smoke_runner.exe build\editor\Debug\XCEngine.exe +``` + +The runner sets `XCUIEDITOR_SMOKE_TEST_DURATION_SECONDS=12`, waits for the +editor to launch, lets the app auto-exit, and treats a clean editor exit as +success. + +## Recommended Reading + +Start with these files for editor/windowing work: - `editor/CMakeLists.txt` - `editor/app/Bootstrap/Application.*` - `editor/app/Composition/EditorContext.*` - `editor/app/Composition/EditorShellRuntime.*` +- `editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h` +- `editor/src/Workspace/UIEditorWorkspaceController.cpp` +- `editor/app/Windowing/System/EditorWindowSystem.*` +- `editor/app/Windowing/System/EditorWindowWorkspaceStore.*` +- `editor/app/Windowing/System/EditorWindowSynchronizationPlanner.*` +- `editor/app/Windowing/Presentation/EditorWindowPresentationPolicy.*` +- `editor/app/Windowing/Host/EditorWindowHostInterfaces.h` - `editor/app/Windowing/Content/EditorWindowContentController.h` +- `editor/app/Windowing/Content/EditorWindowContentFactory.*` - `editor/app/Windowing/Content/EditorWorkspaceWindowContentController.*` - `editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.*` -- `editor/app/Windowing/System/EditorWindowSystem.h` -- `editor/app/Windowing/System/EditorWindowSystem.cpp` +- `editor/app/Windowing/Frame/EditorWindowTransferRequests.h` +- `editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.*` +- `editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.*` +- `editor/app/Windowing/EditorWindowManager.*` - `editor/app/Platform/Win32/Windowing/EditorWindow.*` - `editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.*` -- `editor/app/Platform/Win32/Windowing/EditorWindowWorkspaceCoordinator.*` -- `editor/app/Platform/Win32/Windowing/EditorUtilityWindowCoordinator.*` -- `editor/app/UtilityWindows/EditorUtilityWindowRegistry.*` +- `editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.*` +- `tests/UI/Editor/unit/test_editor_window_synchronization_planner.cpp` - `tests/UI/Editor/unit/CMakeLists.txt` - `tests/UI/Editor/smoke/CMakeLists.txt` diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index cbea546b..0c39ee80 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -170,6 +170,15 @@ target_link_libraries(XCUIEditorLib PUBLIC xcui_editor_apply_common_target_settings(XCUIEditorLib PUBLIC) set(XCUI_EDITOR_APP_WINDOWING_SOURCES + app/Windowing/EditorWindowManager.cpp + app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.cpp + app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp + app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp + app/Windowing/Content/EditorWindowContentController.cpp + app/Windowing/Content/EditorWindowContentFactory.cpp + app/Windowing/Content/EditorUtilityWindowContentController.cpp + app/Windowing/Content/EditorWorkspaceWindowContentController.cpp + app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp app/Windowing/Presentation/EditorWindowPresentationPolicy.cpp app/Windowing/System/EditorWindowSynchronizationPlanner.cpp app/Windowing/System/EditorWindowSystem.cpp @@ -279,17 +288,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Scene/EditorSceneBridge.cpp ) - set(XCUI_EDITOR_APP_WINDOWING_SOURCES - 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 - app/Windowing/Frame/EditorWindowFrameOrchestrator.cpp - ) - set(XCUI_EDITOR_APP_PLATFORM_SOURCES app/Platform/Win32/Windowing/EditorWindow.cpp app/Platform/Win32/Windowing/EditorFloatingWindowPlacement.cpp @@ -317,7 +315,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ${XCUI_EDITOR_HOST_PLATFORM_SOURCES} ${XCUI_EDITOR_HOST_RENDERING_SOURCES} ${XCUI_EDITOR_APP_CORE_SOURCES} - ${XCUI_EDITOR_APP_WINDOWING_SOURCES} ${XCUI_EDITOR_APP_PLATFORM_SOURCES} ) diff --git a/editor/app/Bootstrap/Application.cpp b/editor/app/Bootstrap/Application.cpp index 7e9b038d..fdac0a83 100644 --- a/editor/app/Bootstrap/Application.cpp +++ b/editor/app/Bootstrap/Application.cpp @@ -2,7 +2,6 @@ #include "Bootstrap/EditorResources.h" #include "System/SystemInteractionService.h" #include "Composition/EditorContext.h" -#include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/EditorWindowManager.h" #include "Platform/Win32/System/Win32SystemInteractionHost.h" #include "Platform/Win32/Windowing/EditorWindow.h" @@ -141,13 +140,10 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { hostConfig.windowStyle = kBorderlessWindowStyle; hostConfig.primaryWindowTitle = kWindowTitle; hostConfig.windowUserData = this; - m_windowContentFactory = - App::CreateDefaultEditorWindowContentFactory(*m_windowSystem); m_windowHostRuntime = std::make_unique( hostConfig, m_repoRoot, - *m_editorContext, - *m_windowContentFactory); + m_editorContext->GetShellAsset().captureRootPath); m_windowManager = std::make_unique( *m_editorContext, *m_windowSystem, @@ -228,7 +224,6 @@ void Application::Shutdown() { m_windowManager.reset(); } m_windowHostRuntime.reset(); - m_windowContentFactory.reset(); m_windowSystem.reset(); if (m_editorContext != nullptr) { diff --git a/editor/app/Bootstrap/Application.h b/editor/app/Bootstrap/Application.h index 6443c379..2be7b2e2 100644 --- a/editor/app/Bootstrap/Application.h +++ b/editor/app/Bootstrap/Application.h @@ -15,7 +15,6 @@ class EditorWindowSystem; namespace App { class EditorContext; -class EditorWindowContentFactory; class EditorWindowManager; class EditorWindowHostRuntime; } @@ -55,7 +54,6 @@ private: std::chrono::milliseconds m_smokeTestDuration = std::chrono::milliseconds::zero(); std::unique_ptr m_editorContext = {}; std::unique_ptr m_windowSystem = {}; - std::unique_ptr m_windowContentFactory = {}; std::unique_ptr m_windowHostRuntime = {}; std::unique_ptr m_windowManager = {}; std::unique_ptr m_systemInteractionHost = {}; diff --git a/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.cpp b/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.cpp index 3125182c..fba2e142 100644 --- a/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.cpp +++ b/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.cpp @@ -1,7 +1,6 @@ #include "Platform/Win32/Runtime/EditorWindowRuntimeController.h" #include "Bootstrap/EditorResources.h" -#include "Composition/EditorContext.h" #include "Platform/Win32/Windowing/EditorWindowSupport.h" #include "Support/EmbeddedPngLoader.h" @@ -157,8 +156,8 @@ bool EditorWindowRuntimeController::Initialize( LogRuntimeTrace("app", attachResult.warning); } - editorContext.AttachTextMeasurer(m_textSystem); assert(m_contentController != nullptr); + m_contentController->PrepareEditorContext(editorContext, m_textSystem); m_contentController->Initialize(EditorWindowContentInitializationContext{ .repoRoot = repoRoot, .editorContext = editorContext, @@ -193,7 +192,7 @@ bool EditorWindowRuntimeController::Initialize( m_screenshotController.Initialize(captureRoot); if (autoCaptureOnStartup) { m_screenshotController.RequestCapture("startup"); - editorContext.SetStatus("Capture", "Startup capture requested."); + m_contentController->NotifyStartupCaptureRequested(editorContext); } return true; @@ -254,6 +253,26 @@ bool EditorWindowRuntimeController::ApplyResize(UINT width, UINT height) { return resizeResult.hasViewportSurfacePresentation; } +void EditorWindowRuntimeController::PrepareEditorContext(EditorContext& editorContext) { + if (m_contentController != nullptr) { + m_contentController->PrepareEditorContext(editorContext, m_textSystem); + } +} + +bool EditorWindowRuntimeController::IsEditorContextValid( + const EditorContext& editorContext) const { + return m_contentController != nullptr && + m_contentController->IsEditorContextValid(editorContext); +} + +void EditorWindowRuntimeController::AppendInvalidFrame( + const EditorContext& editorContext, + ::XCEngine::UI::UIDrawList& drawList) const { + if (m_contentController != nullptr) { + m_contentController->AppendInvalidFrame(editorContext, drawList); + } +} + Host::D3D12WindowRenderLoopFrameContext EditorWindowRuntimeController::BeginFrame() { UpdateFrameTiming(); return m_windowRenderLoop.BeginFrame(); diff --git a/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h b/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h index fc684206..2ef0afa1 100644 --- a/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h +++ b/editor/app/Platform/Win32/Runtime/EditorWindowRuntimeController.h @@ -22,6 +22,12 @@ #include #include +namespace XCEngine::UI { + +class UIDrawList; + +} // namespace XCEngine::UI + namespace XCEngine::UI::Editor::App { class EditorContext; @@ -65,6 +71,11 @@ public: void ResetInteractionState(); bool ApplyResize(UINT width, UINT height); + void PrepareEditorContext(EditorContext& editorContext); + bool IsEditorContextValid(const EditorContext& editorContext) const; + void AppendInvalidFrame( + const EditorContext& editorContext, + ::XCEngine::UI::UIDrawList& drawList) const; Host::D3D12WindowRenderLoopFrameContext BeginFrame(); Host::D3D12WindowRenderLoopPresentResult Present( const ::XCEngine::UI::UIDrawData& drawData); diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp index 7666259d..d22e79e5 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp @@ -5,10 +5,8 @@ #include "Platform/Win32/Runtime/EditorWindowFrameDriver.h" #include "Platform/Win32/Windowing/EditorWindowSession.h" #include "Platform/Win32/Windowing/EditorWindowSupport.h" -#include "Windowing/Frame/EditorWindowFrameOrchestrator.h" #include "Platform/Win32/Runtime/EditorWindowInputController.h" #include "Platform/Win32/Runtime/EditorWindowRuntimeController.h" -#include "Composition/EditorContext.h" #include #include #include @@ -98,7 +96,6 @@ EditorWindow::EditorWindow( chromePolicy, primary)) , m_chromeController(std::make_unique()) - , m_frameOrchestrator(std::make_unique()) , m_inputController(std::make_unique()) , m_runtime(std::make_unique( std::move(contentController))) {} @@ -641,12 +638,12 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( kShellSurfaceColor); EditorWindowFrameTransferRequests transferRequests = {}; - if (editorContext.IsValid()) { + if (m_runtime->IsEditorContextValid(editorContext)) { transferRequests = RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawData); } else { UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid"); - m_frameOrchestrator->AppendInvalidFrame(editorContext, invalidDrawList); + m_runtime->AppendInvalidFrame(editorContext, invalidDrawList); } UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome"); @@ -721,7 +718,7 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( std::vector frameEvents = m_inputController->TakePendingEvents(); const bool useDetachedTitleBarTabStrip = m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this); - editorContext.AttachTextMeasurer(m_runtime->GetTextMeasurer()); + m_runtime->PrepareEditorContext(editorContext); const Host::D3D12WindowRenderLoopFrameContext frameContext = m_runtime->BeginFrame(); if (!frameContext.warning.empty()) { LogRuntimeTrace("viewport", frameContext.warning); diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.h b/editor/app/Platform/Win32/Windowing/EditorWindow.h index 55242ad9..2b04e6f1 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.h @@ -54,7 +54,6 @@ class EditorWindowContentController; class EditorWindowChromeController; class EditorWindowDockHostBinding; class EditorWindowFrameDriver; -class EditorWindowFrameOrchestrator; class EditorWindowHostRuntime; class EditorWindowInputController; class EditorWindowLifecycleCoordinator; @@ -201,7 +200,6 @@ private: std::unique_ptr m_session = {}; std::unique_ptr m_chromeController = {}; - std::unique_ptr m_frameOrchestrator = {}; std::unique_ptr m_inputController = {}; std::unique_ptr m_runtime = {}; }; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp index 96eb9124..35453b18 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -1,11 +1,9 @@ #include "Platform/Win32/Windowing/EditorWindowHostRuntime.h" #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 "Windowing/Host/EditorWindowHostCoordinator.h" #include "Platform/Win32/Runtime/EditorWindowFrameDriver.h" @@ -57,16 +55,14 @@ std::string DescribeHostWindows( EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, - EditorContext& editorContext, - EditorWindowContentFactory& contentFactory) + std::filesystem::path captureRoot) : m_hostConfig(hostConfig), m_repoRoot(std::move(repoRoot)), - m_editorContext(editorContext), - m_contentFactory(contentFactory) {} + m_captureRoot(std::move(captureRoot)) {} EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; -EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( +EditorWindow* EditorWindowHostRuntime::CreateHostWindow( std::unique_ptr contentController, const EditorWindowCreateParams& params) { if (contentController == nullptr) { @@ -177,10 +173,14 @@ EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(smallIcon)); } + if (m_hostCoordinator == nullptr) { + return failWindowInitialization("managed window initialization failed: coordinator missing"); + } + if (!rawWindow->Initialize( m_repoRoot, - m_editorContext, - m_editorContext.GetShellAsset().captureRootPath, + m_hostCoordinator->GetEditorContext(), + m_captureRoot, params.autoCaptureOnStartup)) { return failWindowInitialization("managed window initialization failed"); } @@ -190,35 +190,6 @@ EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( return rawWindow; } -EditorWindow* EditorWindowHostRuntime::CreateWorkspaceWindow( - const UIEditorWindowWorkspaceState& windowState, - const EditorWindowCreateParams& params) { - if (windowState.windowId.empty()) { - LogRuntimeTrace("window", "workspace window creation rejected: state missing window id"); - return nullptr; - } - if (!params.windowId.empty() && windowState.windowId != params.windowId) { - LogRuntimeTrace( - "window", - "workspace window creation rejected: state window id '" + - windowState.windowId + "' does not match create params '" + - params.windowId + "'"); - return nullptr; - } - - return CreateEditorWindow( - m_contentFactory.CreateWorkspaceContentController(windowState), - params); -} - -EditorWindow* EditorWindowHostRuntime::CreateUtilityWindow( - const EditorUtilityWindowDescriptor& descriptor, - const EditorWindowCreateParams& params) { - return CreateEditorWindow( - m_contentFactory.CreateUtilityContentController(descriptor), - params); -} - void EditorWindowHostRuntime::BindHostCoordinator( EditorWindowHostCoordinator& hostCoordinator) { m_hostCoordinator = &hostCoordinator; @@ -370,7 +341,7 @@ void EditorWindowHostRuntime::RenderAllWindows() { EditorWindowFrameTransferRequests transferRequests = EditorWindowFrameDriver::DriveFrame( *window, - m_editorContext, + m_hostCoordinator->GetEditorContext(), m_hostCoordinator->IsGlobalTabDragActive()); m_hostCoordinator->RefreshWindowPresentation(*window); if (!transferRequests.HasPendingRequests()) { diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h index 03875d48..fc259d0d 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h @@ -12,29 +12,19 @@ namespace XCEngine::UI::Editor::App { -class EditorContext; class EditorWindow; class EditorWindowContentController; -class EditorWindowContentFactory; -struct EditorUtilityWindowDescriptor; class EditorWindowHostRuntime final : public EditorWindowHostRuntimeServices { public: EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, - EditorContext& editorContext, - EditorWindowContentFactory& contentFactory); + std::filesystem::path captureRoot); ~EditorWindowHostRuntime(); - EditorWindow* CreateEditorWindow( + EditorWindow* CreateHostWindow( std::unique_ptr contentController, - const EditorWindowCreateParams& params); - EditorWindow* CreateWorkspaceWindow( - const UIEditorWindowWorkspaceState& windowState, - const EditorWindowCreateParams& params) override; - EditorWindow* CreateUtilityWindow( - const EditorUtilityWindowDescriptor& descriptor, const EditorWindowCreateParams& params) override; void BindHostCoordinator(EditorWindowHostCoordinator& hostCoordinator) override; void HandlePendingNativeWindowCreated(HWND hwnd); @@ -87,8 +77,7 @@ private: EditorWindowHostConfig m_hostConfig = {}; std::filesystem::path m_repoRoot = {}; - EditorContext& m_editorContext; - EditorWindowContentFactory& m_contentFactory; + std::filesystem::path m_captureRoot = {}; std::vector> m_windows = {}; EditorWindow* m_pendingCreateWindow = nullptr; EditorWindowHostCoordinator* m_hostCoordinator = nullptr; diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp index 812e04f4..58d8f8a0 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowSession.cpp @@ -112,10 +112,6 @@ void EditorWindowSession::SetTitle(std::wstring title) { void EditorWindowSession::QueueCompletedImmediateFrame( EditorWindowFrameTransferRequests transferRequests) { m_hasQueuedCompletedImmediateFrame = true; - if (transferRequests.workspace.workspaceMutation.has_value()) { - m_queuedImmediateFrameTransferRequests.workspace.workspaceMutation = - std::move(transferRequests.workspace.workspaceMutation); - } if (transferRequests.workspace.beginGlobalTabDrag.has_value()) { m_queuedImmediateFrameTransferRequests.workspace.beginGlobalTabDrag = std::move(transferRequests.workspace.beginGlobalTabDrag); diff --git a/editor/app/Windowing/Content/EditorWindowContentController.cpp b/editor/app/Windowing/Content/EditorWindowContentController.cpp new file mode 100644 index 00000000..1314c247 --- /dev/null +++ b/editor/app/Windowing/Content/EditorWindowContentController.cpp @@ -0,0 +1,41 @@ +#include "Windowing/Content/EditorWindowContentController.h" + +#include "Composition/EditorContext.h" +#include "Windowing/EditorWindowShared.h" + +#include + +namespace XCEngine::UI::Editor::App { + +void EditorWindowContentController::PrepareEditorContext( + EditorContext& context, + UIEditorTextMeasurer& textMeasurer) { + context.AttachTextMeasurer(textMeasurer); +} + +bool EditorWindowContentController::IsEditorContextValid(const EditorContext& context) const { + return context.IsValid(); +} + +void EditorWindowContentController::AppendInvalidFrame( + const EditorContext& context, + ::XCEngine::UI::UIDrawList& drawList) const { + drawList.AddText( + ::XCEngine::UI::UIPoint(28.0f, 28.0f), + "Editor shell asset invalid.", + kShellTextColor, + 16.0f); + drawList.AddText( + ::XCEngine::UI::UIPoint(28.0f, 54.0f), + context.GetValidationMessage().empty() + ? std::string("Unknown validation error.") + : context.GetValidationMessage(), + kShellMutedTextColor, + 12.0f); +} + +void EditorWindowContentController::NotifyStartupCaptureRequested(EditorContext& context) { + context.SetStatus("Capture", "Startup capture requested."); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Content/EditorWindowContentController.h b/editor/app/Windowing/Content/EditorWindowContentController.h index 7b0ea12c..b4e358fd 100644 --- a/editor/app/Windowing/Content/EditorWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWindowContentController.h @@ -22,6 +22,7 @@ class RenderContext; namespace XCEngine::UI { class UIDrawData; +class UIDrawList; struct UIInputEvent; struct UIPoint; @@ -158,6 +159,12 @@ public: } virtual void Initialize(const EditorWindowContentInitializationContext&) {} + virtual void PrepareEditorContext(EditorContext& context, UIEditorTextMeasurer& textMeasurer); + virtual bool IsEditorContextValid(const EditorContext& context) const; + virtual void AppendInvalidFrame( + const EditorContext& context, + ::XCEngine::UI::UIDrawList& drawList) const; + virtual void NotifyStartupCaptureRequested(EditorContext& context); virtual void Shutdown() {} virtual void ResetInteractionState() {} virtual void SetViewportSurfacePresentationEnabled(bool) {} diff --git a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp index ba458c9e..0e882663 100644 --- a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp @@ -4,31 +4,11 @@ #include "Windowing/Presentation/EditorWindowPresentationPolicy.h" #include "Windowing/System/EditorWindowSystem.h" #include -#include namespace XCEngine::UI::Editor::App { namespace { -UIEditorWorkspaceController BuildWorkspaceControllerForWindowState( - const UIEditorPanelRegistry& panelRegistry, - const UIEditorWindowWorkspaceState& windowState) { - return UIEditorWorkspaceController( - panelRegistry, - windowState.workspace, - windowState.session); -} - -UIEditorWindowWorkspaceState BuildWindowStateFromController( - std::string_view windowId, - const UIEditorWorkspaceController& workspaceController) { - UIEditorWindowWorkspaceState windowState = {}; - windowState.windowId = std::string(windowId); - windowState.workspace = workspaceController.GetWorkspace(); - windowState.session = workspaceController.GetSession(); - return windowState; -} - EditorWindowContentCursorKind ToContentCursorKind(ProjectPanel::CursorKind cursorKind) { switch (cursorKind) { case ProjectPanel::CursorKind::ResizeEW: @@ -118,17 +98,10 @@ void EditorWorkspaceWindowContentController::RefreshWorkspaceProjection( } bool EditorWorkspaceWindowContentController::TryBuildAuthoritativeWorkspaceController( - UIEditorWorkspaceController& outController) const { - const UIEditorWindowWorkspaceState* windowState = - m_windowSystem.FindWindowState(m_windowId); - if (windowState == nullptr) { - return false; - } - - outController = BuildWorkspaceControllerForWindowState( - m_windowSystem.GetPanelRegistry(), - *windowState); - return true; + UIEditorWorkspaceController& outController) { + return m_windowSystem.TryBuildLiveWindowWorkspaceController( + m_windowId, + outController); } void EditorWorkspaceWindowContentController::Initialize( @@ -161,10 +134,7 @@ EditorWindowFrameTransferRequests EditorWorkspaceWindowContentController::Update return {}; } - const auto beforeSnapshot = BuildUIEditorWorkspaceLayoutSnapshot( - workspaceController.GetWorkspace(), - workspaceController.GetSession()); - EditorWindowFrameTransferRequests transferRequests = m_frameOrchestrator.UpdateAndAppend( + return m_frameOrchestrator.UpdateAndAppend( context.editorContext, workspaceController, m_shellRuntime, @@ -176,15 +146,6 @@ EditorWindowFrameTransferRequests EditorWorkspaceWindowContentController::Update context.globalTabDragActive, context.useDetachedTitleBarTabStrip, drawData); - const auto afterSnapshot = BuildUIEditorWorkspaceLayoutSnapshot( - workspaceController.GetWorkspace(), - workspaceController.GetSession()); - if (!AreUIEditorWorkspaceLayoutSnapshotsEquivalent(beforeSnapshot, afterSnapshot)) { - transferRequests.workspace.workspaceMutation = EditorWindowWorkspaceMutationRequest{ - .windowState = BuildWindowStateFromController(m_windowId, workspaceController), - }; - } - return transferRequests; } void EditorWorkspaceWindowContentController::RenderRequestedViewports( diff --git a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h index 79be8a79..b89a1b5f 100644 --- a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.h @@ -76,7 +76,7 @@ public: std::string_view fallbackWindowTitle) const override; private: - bool TryBuildAuthoritativeWorkspaceController(UIEditorWorkspaceController& outController) const; + bool TryBuildAuthoritativeWorkspaceController(UIEditorWorkspaceController& outController); std::string m_windowId = {}; EditorWindowSystem& m_windowSystem; diff --git a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp index dc2766f0..d194f15e 100644 --- a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp @@ -1,6 +1,8 @@ #include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" #include "UtilityWindows/EditorUtilityWindowRegistry.h" +#include "Windowing/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" #include @@ -24,8 +26,10 @@ int ResolveOuterDimension(float value, int fallback) { } EditorUtilityWindowCoordinator::EditorUtilityWindowCoordinator( - EditorWindowHost& hostRuntime) - : m_hostRuntime(hostRuntime) {} + EditorWindowHost& hostRuntime, + EditorWindowContentFactory& contentFactory) + : m_hostRuntime(hostRuntime), + m_contentFactory(contentFactory) {} EditorUtilityWindowCoordinator::~EditorUtilityWindowCoordinator() = default; @@ -117,8 +121,9 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( createParams.initialHeight = rect.Height(); } - EditorHostWindow* utilityWindow = - m_hostRuntime.CreateUtilityWindow(*descriptor, createParams); + EditorHostWindow* utilityWindow = m_hostRuntime.CreateHostWindow( + m_contentFactory.CreateUtilityContentController(*descriptor), + createParams); if (utilityWindow == nullptr) { LogRuntimeTrace("utility", "failed to create utility window"); return false; diff --git a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h index 619c9f43..5de58a27 100644 --- a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h +++ b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h @@ -7,11 +7,14 @@ namespace XCEngine::UI::Editor::App { +class EditorWindowContentFactory; class EditorWindowLifecycleCoordinator; class EditorUtilityWindowCoordinator final { public: - explicit EditorUtilityWindowCoordinator(EditorWindowHost& hostRuntime); + EditorUtilityWindowCoordinator( + EditorWindowHost& hostRuntime, + EditorWindowContentFactory& contentFactory); ~EditorUtilityWindowCoordinator(); void BindLifecycleCoordinator(EditorWindowLifecycleCoordinator& lifecycleCoordinator); @@ -26,6 +29,7 @@ private: void LogRuntimeTrace(std::string_view channel, std::string_view message) const; EditorWindowHost& m_hostRuntime; + EditorWindowContentFactory& m_contentFactory; EditorWindowLifecycleCoordinator* m_lifecycleCoordinator = nullptr; }; diff --git a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp index 6afac72c..ac818eab 100644 --- a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp @@ -1,5 +1,6 @@ #include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Content/EditorWindowContentController.h" #include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" @@ -88,9 +89,11 @@ std::string DescribeWindowSetState(const UIEditorWindowWorkspaceSet& windowSet) EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator( EditorWindowHost& hostRuntime, - EditorWindowSystem& windowSystem) + EditorWindowSystem& windowSystem, + EditorWindowContentFactory& contentFactory) : m_hostRuntime(hostRuntime), - m_windowSystem(windowSystem) {} + m_windowSystem(windowSystem), + m_contentFactory(contentFactory) {} EditorWindowWorkspaceCoordinator::~EditorWindowWorkspaceCoordinator() = default; @@ -108,6 +111,8 @@ void EditorWindowWorkspaceCoordinator::RefreshWindowPresentation(EditorHostWindo if (!window.IsWorkspaceWindow()) { return; } + + RefreshWorkspaceProjectionFromAuthoritativeState(window); RefreshWindowTitle(window); } @@ -207,25 +212,6 @@ bool EditorWindowWorkspaceCoordinator::RefreshWorkspaceProjectionFromAuthoritati return true; } -bool EditorWindowWorkspaceCoordinator::CommitLiveWindowMutation( - const EditorWindowWorkspaceMutationRequest& request) { - std::string error = {}; - EditorWindowSynchronizationPlan plan = m_windowSystem.BuildPlanForWorkspaceMutationRequest( - request, - CaptureHostSnapshots(), - m_hostRuntime.GetPrimaryWindowTitle(), - error); - if (!plan.valid) { - LogRuntimeTrace( - "window", - "workspace live mutation rejected for window '" + - request.windowState.windowId + "': " + error); - return false; - } - - return ApplySynchronizationPlan(plan); -} - void EditorWindowWorkspaceCoordinator::RefreshWindowTitle(EditorHostWindow& window) const { const EditorWorkspaceWindowProjection* projection = window.TryGetWorkspaceProjection(); if (projection == nullptr || projection->windowTitle.empty()) { @@ -378,9 +364,9 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( createParams.initialHeight = action.create.placement.initialHeight; } - EditorHostWindow* const createdWindow = m_hostRuntime.CreateWorkspaceWindow( - action.create.windowState, - createParams); + EditorHostWindow* const createdWindow = m_hostRuntime.CreateHostWindow( + m_contentFactory.CreateWorkspaceContentController(action.create.windowState), + createParams); if (createdWindow == nullptr) { for (const ExistingWindowSnapshot& snapshot : existingWindowSnapshots) { restoreWindowSnapshot(snapshot); @@ -853,15 +839,6 @@ bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest( void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests( EditorHostWindow& sourceWindow, const EditorWindowFrameTransferRequests& transferRequests) { - if (transferRequests.workspace.workspaceMutation.has_value()) { - if (!CommitLiveWindowMutation(*transferRequests.workspace.workspaceMutation)) { - LogRuntimeTrace( - "window", - "failed to commit live workspace mutation for window '" + - std::string(sourceWindow.GetWindowId()) + "'"); - } - } - if (!m_globalTabDragSession.active && transferRequests.workspace.beginGlobalTabDrag.has_value() && transferRequests.workspace.beginGlobalTabDrag->IsValid()) { diff --git a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h index 0cfaf43b..ae743797 100644 --- a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h +++ b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h @@ -22,13 +22,15 @@ class EditorWindowSystem; namespace XCEngine::UI::Editor::App { +class EditorWindowContentFactory; class EditorWindowLifecycleCoordinator; class EditorWindowWorkspaceCoordinator final { public: EditorWindowWorkspaceCoordinator( EditorWindowHost& hostRuntime, - EditorWindowSystem& windowSystem); + EditorWindowSystem& windowSystem, + EditorWindowContentFactory& contentFactory); ~EditorWindowWorkspaceCoordinator(); void BindLifecycleCoordinator(EditorWindowLifecycleCoordinator& lifecycleCoordinator); @@ -74,7 +76,6 @@ private: int preferredWidth = 0, int preferredHeight = 0); UIEditorWindowWorkspaceState BuildWindowStateForWindow(const EditorHostWindow& window) const; - bool CommitLiveWindowMutation(const EditorWindowWorkspaceMutationRequest& request); EditorWorkspaceWindowProjection BuildWorkspaceProjectionForState( const UIEditorWindowWorkspaceState& windowState, bool primary, @@ -114,6 +115,7 @@ private: EditorWindowHost& m_hostRuntime; EditorWindowSystem& m_windowSystem; + EditorWindowContentFactory& m_contentFactory; EditorWindowLifecycleCoordinator* m_lifecycleCoordinator = nullptr; GlobalTabDragSession m_globalTabDragSession = {}; }; diff --git a/editor/app/Windowing/EditorWindowManager.cpp b/editor/app/Windowing/EditorWindowManager.cpp index aca4ad52..3a8d9df9 100644 --- a/editor/app/Windowing/EditorWindowManager.cpp +++ b/editor/app/Windowing/EditorWindowManager.cpp @@ -1,5 +1,7 @@ #include "Windowing/EditorWindowManager.h" +#include "Windowing/Content/EditorWindowContentController.h" +#include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" #include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" #include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" @@ -16,10 +18,16 @@ EditorWindowManager::EditorWindowManager( EditorWindowHostRuntimeServices& hostRuntime) : m_editorContext(editorContext) , m_hostRuntime(hostRuntime) { + m_contentFactory = CreateDefaultEditorWindowContentFactory(windowSystem); m_workspaceCoordinator = - std::make_unique(m_hostRuntime, windowSystem); + std::make_unique( + m_hostRuntime, + windowSystem, + *m_contentFactory); m_utilityCoordinator = - std::make_unique(m_hostRuntime); + std::make_unique( + m_hostRuntime, + *m_contentFactory); m_lifecycleCoordinator = std::make_unique( m_hostRuntime, *m_workspaceCoordinator); @@ -33,8 +41,19 @@ EditorWindowManager::~EditorWindowManager() = default; EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow( const UIEditorWindowWorkspaceState& windowState, const EditorWindowCreateParams& params) { - EditorHostWindow* const window = - m_hostRuntime.CreateWorkspaceWindow(windowState, params); + if (m_contentFactory == nullptr) { + return nullptr; + } + if (windowState.windowId.empty()) { + return nullptr; + } + if (!params.windowId.empty() && windowState.windowId != params.windowId) { + return nullptr; + } + + EditorHostWindow* const window = m_hostRuntime.CreateHostWindow( + m_contentFactory->CreateWorkspaceContentController(windowState), + params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); } @@ -44,7 +63,13 @@ EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow( EditorHostWindow* EditorWindowManager::CreateUtilityWindow( const EditorUtilityWindowDescriptor& descriptor, const EditorWindowCreateParams& params) { - EditorHostWindow* const window = m_hostRuntime.CreateUtilityWindow(descriptor, params); + if (m_contentFactory == nullptr) { + return nullptr; + } + + EditorHostWindow* const window = m_hostRuntime.CreateHostWindow( + m_contentFactory->CreateUtilityContentController(descriptor), + params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); } diff --git a/editor/app/Windowing/EditorWindowManager.h b/editor/app/Windowing/EditorWindowManager.h index 9bc3e245..42a1ce12 100644 --- a/editor/app/Windowing/EditorWindowManager.h +++ b/editor/app/Windowing/EditorWindowManager.h @@ -20,6 +20,7 @@ struct UIEditorWindowWorkspaceState; namespace XCEngine::UI::Editor::App { class EditorContext; +class EditorWindowContentFactory; class EditorWindowHostRuntimeServices; class EditorWindowLifecycleCoordinator; class EditorUtilityWindowCoordinator; @@ -72,6 +73,7 @@ private: EditorContext& m_editorContext; EditorWindowHostRuntimeServices& m_hostRuntime; + std::unique_ptr m_contentFactory = {}; std::unique_ptr m_lifecycleCoordinator = {}; std::unique_ptr m_utilityCoordinator = {}; std::unique_ptr m_workspaceCoordinator = {}; diff --git a/editor/app/Windowing/Frame/EditorWindowTransferRequests.h b/editor/app/Windowing/Frame/EditorWindowTransferRequests.h index f68b2742..8afa0cce 100644 --- a/editor/app/Windowing/Frame/EditorWindowTransferRequests.h +++ b/editor/app/Windowing/Frame/EditorWindowTransferRequests.h @@ -7,9 +7,6 @@ #include "Windowing/EditorWindowShared.h" #include "UtilityWindows/EditorUtilityWindowKind.h" -#include "Windowing/System/EditorWindowSynchronizationPlan.h" -#include - #include #include @@ -36,13 +33,11 @@ struct EditorWindowOpenUtilityWindowRequest { }; struct EditorWorkspaceWindowFrameTransferRequests { - std::optional workspaceMutation = {}; std::optional beginGlobalTabDrag = {}; std::optional detachPanel = {}; bool HasPendingRequests() const { - return workspaceMutation.has_value() || - beginGlobalTabDrag.has_value() || + return beginGlobalTabDrag.has_value() || detachPanel.has_value(); } }; diff --git a/editor/app/Windowing/Host/EditorWindowHostInterfaces.h b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h index 26037d67..1812c1a3 100644 --- a/editor/app/Windowing/Host/EditorWindowHostInterfaces.h +++ b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h @@ -6,8 +6,8 @@ #include "Windowing/Host/EditorWindowTypes.h" #include "Windowing/Presentation/EditorWorkspaceWindowProjection.h" -#include +#include #include #include #include @@ -27,8 +27,8 @@ struct UIEditorDockHostTabDropTarget; namespace XCEngine::UI::Editor::App { class EditorWindowHostCoordinator; +class EditorWindowContentController; class EditorWindowDockHostBinding; -struct EditorUtilityWindowDescriptor; class EditorHostWindow { public: @@ -79,11 +79,8 @@ class EditorWindowHost { public: virtual ~EditorWindowHost() = default; - virtual EditorHostWindow* CreateWorkspaceWindow( - const UIEditorWindowWorkspaceState& windowState, - const EditorWindowCreateParams& params) = 0; - virtual EditorHostWindow* CreateUtilityWindow( - const EditorUtilityWindowDescriptor& descriptor, + virtual EditorHostWindow* CreateHostWindow( + std::unique_ptr contentController, const EditorWindowCreateParams& params) = 0; virtual EditorHostWindow* FindWindowById(std::string_view windowId) = 0; virtual const EditorHostWindow* FindWindowById(std::string_view windowId) const = 0; diff --git a/editor/app/Windowing/System/EditorWindowSynchronizationPlan.h b/editor/app/Windowing/System/EditorWindowSynchronizationPlan.h index d35f7745..633cfc89 100644 --- a/editor/app/Windowing/System/EditorWindowSynchronizationPlan.h +++ b/editor/app/Windowing/System/EditorWindowSynchronizationPlan.h @@ -9,14 +9,6 @@ namespace XCEngine::UI::Editor { -struct EditorWindowWorkspaceMutationRequest { - UIEditorWindowWorkspaceState windowState = {}; - - bool IsValid() const { - return !windowState.windowId.empty(); - } -}; - struct EditorWindowHostSnapshot { std::string windowId = {}; bool workspaceWindow = false; diff --git a/editor/app/Windowing/System/EditorWindowSystem.cpp b/editor/app/Windowing/System/EditorWindowSystem.cpp index a1b724fd..fd47fc47 100644 --- a/editor/app/Windowing/System/EditorWindowSystem.cpp +++ b/editor/app/Windowing/System/EditorWindowSystem.cpp @@ -56,6 +56,23 @@ const UIEditorWindowWorkspaceState* EditorWindowSystem::FindWindowState( return FindUIEditorWindowWorkspaceState(GetWindowSet(), windowId); } +bool EditorWindowSystem::TryBuildLiveWindowWorkspaceController( + std::string_view windowId, + UIEditorWorkspaceController& outController) { + UIEditorWindowWorkspaceState* const windowState = + m_workspaceStore->FindMutableWindowState(windowId); + if (windowState == nullptr) { + outController = {}; + return false; + } + + outController = UIEditorWorkspaceController::BindToState( + GetPanelRegistry(), + windowState->workspace, + windowState->session); + return true; +} + UIEditorWindowWorkspaceController EditorWindowSystem::BuildWorkspaceMutationController() const { return UIEditorWindowWorkspaceController(GetPanelRegistry(), GetWindowSet()); } @@ -78,37 +95,6 @@ EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForWindowSet( outError); } -EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForWorkspaceMutationRequest( - const EditorWindowWorkspaceMutationRequest& request, - const std::vector& hostWindows, - std::wstring_view primaryWindowTitle, - std::string& outError) const { - if (!request.IsValid()) { - outError = "live window mutation request missing window id"; - return {}; - } - - UIEditorWindowWorkspaceSet nextWindowSet = GetWindowSet(); - UIEditorWindowWorkspaceState* existingState = - FindMutableUIEditorWindowWorkspaceState(nextWindowSet, request.windowState.windowId); - if (existingState == nullptr) { - outError = - "live window mutation references unknown window '" + - request.windowState.windowId + "'"; - return {}; - } - - existingState->workspace = request.windowState.workspace; - existingState->session = request.windowState.session; - return BuildPlanForWindowSet( - nextWindowSet, - hostWindows, - primaryWindowTitle, - {}, - {}, - outError); -} - EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForDestroyedWindow( std::string_view windowId, const std::vector& hostWindows, diff --git a/editor/app/Windowing/System/EditorWindowSystem.h b/editor/app/Windowing/System/EditorWindowSystem.h index 141bd7a1..9fa42d03 100644 --- a/editor/app/Windowing/System/EditorWindowSystem.h +++ b/editor/app/Windowing/System/EditorWindowSystem.h @@ -1,6 +1,7 @@ #pragma once #include "Windowing/System/EditorWindowSynchronizationPlan.h" +#include #include #include @@ -35,6 +36,9 @@ public: const UIEditorWindowWorkspaceSet& GetWindowSet() const; const UIEditorWindowWorkspaceState* FindWindowState(std::string_view windowId) const; + bool TryBuildLiveWindowWorkspaceController( + std::string_view windowId, + UIEditorWorkspaceController& outController); UIEditorWindowWorkspaceController BuildWorkspaceMutationController() const; EditorWindowSynchronizationPlan BuildPlanForWindowSet( const UIEditorWindowWorkspaceSet& targetWindowSet, @@ -43,11 +47,6 @@ public: std::string_view preferredNewWindowId, const EditorWindowSynchronizationPlacement& preferredPlacement, std::string& outError) const; - EditorWindowSynchronizationPlan BuildPlanForWorkspaceMutationRequest( - const EditorWindowWorkspaceMutationRequest& request, - const std::vector& hostWindows, - std::wstring_view primaryWindowTitle, - std::string& outError) const; EditorWindowSynchronizationPlan BuildPlanForDestroyedWindow( std::string_view windowId, const std::vector& hostWindows, diff --git a/editor/app/Windowing/System/EditorWindowWorkspaceStore.cpp b/editor/app/Windowing/System/EditorWindowWorkspaceStore.cpp index eee44147..bd49f534 100644 --- a/editor/app/Windowing/System/EditorWindowWorkspaceStore.cpp +++ b/editor/app/Windowing/System/EditorWindowWorkspaceStore.cpp @@ -38,4 +38,9 @@ bool EditorWindowWorkspaceStore::IsPrimaryWindowId(std::string_view windowId) co return !windowId.empty() && m_windowSet.primaryWindowId == windowId; } +UIEditorWindowWorkspaceState* EditorWindowWorkspaceStore::FindMutableWindowState( + std::string_view windowId) { + return FindMutableUIEditorWindowWorkspaceState(m_windowSet, windowId); +} + } // namespace XCEngine::UI::Editor diff --git a/editor/app/Windowing/System/EditorWindowWorkspaceStore.h b/editor/app/Windowing/System/EditorWindowWorkspaceStore.h index 09c380b6..02d19c87 100644 --- a/editor/app/Windowing/System/EditorWindowWorkspaceStore.h +++ b/editor/app/Windowing/System/EditorWindowWorkspaceStore.h @@ -28,6 +28,7 @@ public: const UIEditorWindowWorkspaceSet& GetWindowSet() const { return m_windowSet; } + UIEditorWindowWorkspaceState* FindMutableWindowState(std::string_view windowId); private: UIEditorPanelRegistry m_panelRegistry = {}; diff --git a/editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h b/editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h index b7bdb7be..1db3b2e7 100644 --- a/editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h +++ b/editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h @@ -83,6 +83,15 @@ public: UIEditorPanelRegistry panelRegistry, UIEditorWorkspaceModel workspace, UIEditorWorkspaceSession session); + UIEditorWorkspaceController(const UIEditorWorkspaceController& other); + UIEditorWorkspaceController& operator=(const UIEditorWorkspaceController& other); + UIEditorWorkspaceController(UIEditorWorkspaceController&&) noexcept = default; + UIEditorWorkspaceController& operator=(UIEditorWorkspaceController&&) noexcept = default; + + static UIEditorWorkspaceController BindToState( + UIEditorPanelRegistry panelRegistry, + UIEditorWorkspaceModel& workspace, + UIEditorWorkspaceSession& session); const UIEditorPanelRegistry& GetPanelRegistry() const { return m_panelRegistry; @@ -119,6 +128,8 @@ public: UIEditorWorkspaceCommandResult Dispatch(const UIEditorWorkspaceCommand& command); private: + void SyncBoundState(); + UIEditorWorkspaceCommandResult BuildResult( const UIEditorWorkspaceCommand& command, UIEditorWorkspaceCommandStatus status, @@ -143,6 +154,8 @@ private: UIEditorWorkspaceSession m_baselineSession = {}; UIEditorWorkspaceModel m_workspace = {}; UIEditorWorkspaceSession m_session = {}; + UIEditorWorkspaceModel* m_boundWorkspace = nullptr; + UIEditorWorkspaceSession* m_boundSession = nullptr; }; UIEditorWorkspaceController BuildDefaultUIEditorWorkspaceController( diff --git a/editor/src/Workspace/UIEditorWorkspaceController.cpp b/editor/src/Workspace/UIEditorWorkspaceController.cpp index 76734b1c..7605058c 100644 --- a/editor/src/Workspace/UIEditorWorkspaceController.cpp +++ b/editor/src/Workspace/UIEditorWorkspaceController.cpp @@ -139,6 +139,53 @@ UIEditorWorkspaceController::UIEditorWorkspaceController( , m_session(std::move(session)) { } +UIEditorWorkspaceController::UIEditorWorkspaceController( + const UIEditorWorkspaceController& other) + : m_panelRegistry(other.m_panelRegistry) + , m_baselineWorkspace(other.m_baselineWorkspace) + , m_baselineSession(other.m_baselineSession) + , m_workspace(other.m_workspace) + , m_session(other.m_session) { +} + +UIEditorWorkspaceController& UIEditorWorkspaceController::operator=( + const UIEditorWorkspaceController& other) { + if (this == &other) { + return *this; + } + + m_panelRegistry = other.m_panelRegistry; + m_baselineWorkspace = other.m_baselineWorkspace; + m_baselineSession = other.m_baselineSession; + m_workspace = other.m_workspace; + m_session = other.m_session; + m_boundWorkspace = nullptr; + m_boundSession = nullptr; + return *this; +} + +UIEditorWorkspaceController UIEditorWorkspaceController::BindToState( + UIEditorPanelRegistry panelRegistry, + UIEditorWorkspaceModel& workspace, + UIEditorWorkspaceSession& session) { + UIEditorWorkspaceController controller( + std::move(panelRegistry), + workspace, + session); + controller.m_boundWorkspace = &workspace; + controller.m_boundSession = &session; + return controller; +} + +void UIEditorWorkspaceController::SyncBoundState() { + if (m_boundWorkspace != nullptr) { + *m_boundWorkspace = m_workspace; + } + if (m_boundSession != nullptr) { + *m_boundSession = m_session; + } +} + UIEditorWorkspaceControllerValidationResult UIEditorWorkspaceController::ValidateState() const { const UIEditorPanelRegistryValidationResult registryValidation = ValidateUIEditorPanelRegistry(m_panelRegistry); @@ -223,6 +270,8 @@ UIEditorWorkspaceCommandResult UIEditorWorkspaceController::FinalizeMutation( "Command produced invalid workspace state: " + validation.message); } + SyncBoundState(); + return BuildResult( command, UIEditorWorkspaceCommandStatus::Changed, @@ -533,6 +582,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayou "Restored layout produced invalid controller state: " + validation.message); } + SyncBoundState(); + return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Changed, "Layout restored."); @@ -595,6 +646,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::SetSplitRati "Split ratio update produced invalid controller state: " + postValidation.message); } + SyncBoundState(); + return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Changed, "Split ratio updated."); @@ -701,6 +754,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToSta "MoveTabToStack produced invalid controller state: " + postValidation.message); } + SyncBoundState(); + return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Changed, "Tab moved to target stack."); @@ -796,6 +851,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::DockTabRelat "DockTabRelative produced invalid controller state: " + postValidation.message); } + SyncBoundState(); + return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Changed, "Tab docked relative to target stack."); diff --git a/tests/UI/Editor/unit/test_editor_window_synchronization_planner.cpp b/tests/UI/Editor/unit/test_editor_window_synchronization_planner.cpp index e5509f0b..7ed0ba7e 100644 --- a/tests/UI/Editor/unit/test_editor_window_synchronization_planner.cpp +++ b/tests/UI/Editor/unit/test_editor_window_synchronization_planner.cpp @@ -20,12 +20,12 @@ using XCEngine::UI::Editor::EditorWindowHostSnapshot; using XCEngine::UI::Editor::EditorWindowSynchronizationActionKind; using XCEngine::UI::Editor::EditorWindowSynchronizationPlannerInput; using XCEngine::UI::Editor::EditorWindowSystem; -using XCEngine::UI::Editor::EditorWindowWorkspaceMutationRequest; using XCEngine::UI::Editor::FindUIEditorWindowWorkspaceState; using XCEngine::UI::Editor::UIEditorWorkspaceCommand; using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind; using XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus; using XCEngine::UI::Editor::UIEditorPanelRegistry; +using XCEngine::UI::Editor::UIEditorWorkspaceController; using XCEngine::UI::Editor::UIEditorWindowWorkspaceController; using XCEngine::UI::Editor::UIEditorWindowWorkspaceOperationStatus; using XCEngine::UI::Editor::UIEditorWorkspaceModel; @@ -363,7 +363,7 @@ TEST(EditorWindowSynchronizationPlannerTest, RejectsInvalidTargetWindowSet) { EXPECT_FALSE(error.empty()); } -TEST(EditorWindowSynchronizationPlannerTest, LiveWindowMutationBuildsCommitPlanWithoutPlatformWriteback) { +TEST(EditorWindowSynchronizationPlannerTest, LiveWindowControllerWritesThroughToAuthoritativeState) { EditorWindowSystem system = BuildSystem(); const auto workspaceController = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); @@ -373,40 +373,35 @@ TEST(EditorWindowSynchronizationPlannerTest, LiveWindowMutationBuildsCommitPlanW BuildWindowState("main", workspaceController), error)) << error; - auto liveController = workspaceController; + UIEditorWorkspaceController liveController = {}; + ASSERT_TRUE(system.TryBuildLiveWindowWorkspaceController("main", liveController)); const auto commandResult = liveController.Dispatch( UIEditorWorkspaceCommand{ .kind = UIEditorWorkspaceCommandKind::ActivatePanel, .panelId = "doc-b", }); ASSERT_EQ(commandResult.status, UIEditorWorkspaceCommandStatus::Changed); + EXPECT_EQ(system.GetWindowSet().windows.front().workspace.activePanelId, "doc-b"); - EditorWindowWorkspaceMutationRequest request = {}; - request.windowState = BuildWindowState("main", liveController); - - auto plan = system.BuildPlanForWorkspaceMutationRequest( - request, - { - BuildWorkspaceSnapshot( - "main", - true, - liveController, - L"Main Scene - XCEngine Editor"), + auto plan = system.BuildSynchronizationPlan( + EditorWindowSynchronizationPlannerInput{ + .targetWindowSet = &system.GetWindowSet(), + .primaryWindowTitle = L"Main Scene - XCEngine Editor", + .hostWindows = { + BuildWorkspaceSnapshot( + "main", + true, + liveController, + L"Main Scene - XCEngine Editor"), + }, }, - L"Main Scene - XCEngine Editor", error); ASSERT_TRUE(plan.valid) << error; EXPECT_TRUE(plan.actions.empty()); - const auto* mutatedState = FindUIEditorWindowWorkspaceState(plan.targetWindowSet, "main"); - ASSERT_NE(mutatedState, nullptr); - EXPECT_EQ(mutatedState->workspace.activePanelId, "doc-b"); - - ASSERT_TRUE(system.CommitSynchronizationPlan(plan, error)) << error; - EXPECT_EQ(system.GetWindowSet().windows.front().workspace.activePanelId, "doc-b"); } -TEST(EditorWindowSynchronizationPlannerTest, WorkspaceMutationRequestBuildsCommitPlanFromExplicitPayload) { +TEST(EditorWindowSynchronizationPlannerTest, LiveWindowControllerCopyDoesNotWriteThroughToAuthoritativeState) { EditorWindowSystem system = BuildSystem(); const auto workspaceController = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); @@ -416,44 +411,21 @@ TEST(EditorWindowSynchronizationPlannerTest, WorkspaceMutationRequestBuildsCommi BuildWindowState("main", workspaceController), error)) << error; - auto liveController = workspaceController; - const auto commandResult = liveController.Dispatch( + UIEditorWorkspaceController liveController = {}; + ASSERT_TRUE(system.TryBuildLiveWindowWorkspaceController("main", liveController)); + UIEditorWorkspaceController previewController = liveController; + + const auto commandResult = previewController.Dispatch( UIEditorWorkspaceCommand{ .kind = UIEditorWorkspaceCommandKind::ActivatePanel, .panelId = "doc-b", }); ASSERT_EQ(commandResult.status, UIEditorWorkspaceCommandStatus::Changed); - - EditorWindowWorkspaceMutationRequest request = {}; - request.windowState.windowId = "main"; - request.windowState.workspace = liveController.GetWorkspace(); - request.windowState.session = liveController.GetSession(); - - auto plan = system.BuildPlanForWorkspaceMutationRequest( - request, - { - BuildWorkspaceSnapshot( - "main", - true, - workspaceController, - L"Main Scene - XCEngine Editor"), - }, - L"Main Scene - XCEngine Editor", - error); - - ASSERT_TRUE(plan.valid) << error; - ASSERT_EQ(plan.actions.size(), 1u); - EXPECT_EQ(plan.actions[0].kind, EditorWindowSynchronizationActionKind::UpdateWorkspaceWindow); - EXPECT_EQ(plan.actions[0].update.windowState.windowId, "main"); - const auto* mutatedState = FindUIEditorWindowWorkspaceState(plan.targetWindowSet, "main"); - ASSERT_NE(mutatedState, nullptr); - EXPECT_EQ(mutatedState->workspace.activePanelId, "doc-b"); - - ASSERT_TRUE(system.CommitSynchronizationPlan(plan, error)) << error; - EXPECT_EQ(system.GetWindowSet().windows.front().workspace.activePanelId, "doc-b"); + EXPECT_EQ(previewController.GetWorkspace().activePanelId, "doc-b"); + EXPECT_EQ(system.GetWindowSet().windows.front().workspace.activePanelId, "doc-a"); } -TEST(EditorWindowSynchronizationPlannerTest, WorkspaceMutationRequestRejectsUnknownWindowId) { +TEST(EditorWindowSynchronizationPlannerTest, TryBuildLiveWindowWorkspaceControllerRejectsUnknownWindowId) { EditorWindowSystem system = BuildSystem(); const auto workspaceController = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); @@ -463,25 +435,8 @@ TEST(EditorWindowSynchronizationPlannerTest, WorkspaceMutationRequestRejectsUnkn BuildWindowState("main", workspaceController), error)) << error; - EditorWindowWorkspaceMutationRequest request = {}; - request.windowState.windowId = "missing-window"; - request.windowState.workspace = workspaceController.GetWorkspace(); - request.windowState.session = workspaceController.GetSession(); - - auto plan = system.BuildPlanForWorkspaceMutationRequest( - request, - { - BuildWorkspaceSnapshot( - "main", - true, - workspaceController, - L"Main Scene - XCEngine Editor"), - }, - L"Main Scene - XCEngine Editor", - error); - - EXPECT_FALSE(plan.valid); - EXPECT_NE(error.find("missing-window"), std::string::npos); + UIEditorWorkspaceController liveController = {}; + EXPECT_FALSE(system.TryBuildLiveWindowWorkspaceController("missing-window", liveController)); } TEST(EditorWindowSynchronizationPlannerTest, DestroyedPrimaryWindowProducesCloseActionsForRemainingDetachedWindows) {