From e251b77d0d39a4b3ccb280eba102778195b440ac Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 22 Apr 2026 20:32:56 +0800 Subject: [PATCH] refactor(new_editor): extract window content boundary --- ...ilityWindowSemanticSplitPlan_2026-04-22.md | 494 ++++++++++++++++++ new_editor/CMakeLists.txt | 1 + new_editor/app/Bootstrap/Application.cpp | 4 +- .../app/Platform/Win32/EditorWindow.cpp | 115 ++-- new_editor/app/Platform/Win32/EditorWindow.h | 19 +- .../Win32/EditorWindowChromeController.cpp | 29 +- .../Win32/EditorWindowContentController.h | 126 +++++ .../Win32/EditorWindowFrameOrchestrator.cpp | 44 +- .../Win32/EditorWindowFrameOrchestrator.h | 9 +- .../app/Platform/Win32/EditorWindowManager.h | 3 +- .../Win32/EditorWindowRuntimeController.cpp | 186 +++++-- .../Win32/EditorWindowRuntimeController.h | 49 +- ...EditorWorkspaceWindowContentController.cpp | 182 +++++++ .../EditorWorkspaceWindowContentController.h | 69 +++ .../WindowManager/EditorWindowHostRuntime.cpp | 7 +- .../WindowManager/EditorWindowHostRuntime.h | 5 +- .../WindowManager/EditorWindowManager.cpp | 5 +- .../EditorWindowMessageDispatcher.cpp | 33 +- .../EditorWindowWorkspaceCoordinator.cpp | 77 ++- 19 files changed, 1223 insertions(+), 234 deletions(-) create mode 100644 docs/plan/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md create mode 100644 new_editor/app/Platform/Win32/EditorWindowContentController.h create mode 100644 new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.h diff --git a/docs/plan/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md b/docs/plan/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md new file mode 100644 index 00000000..19edb976 --- /dev/null +++ b/docs/plan/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md @@ -0,0 +1,494 @@ +# NewEditor Workspace Utility Window Semantic Split Plan + +Date: 2026-04-22 +Status: In Progress + +## 0. Execution Progress + +### Completed on 2026-04-22 + +Phase A shell/content split foundation is now in place. + +Completed changes: + +1. `EditorWindow` no longer constructs itself from `UIEditorWorkspaceController` +2. `EditorWindowRuntimeController` no longer directly owns `m_workspaceController` +3. `EditorWindowHostRuntime::CreateEditorWindow(...)` no longer accepts raw workspace controller input +4. a new content abstraction now exists: + - `EditorWindowContentController` + - `EditorWorkspaceWindowContentController` +5. current workspace-backed behavior has been moved behind the new content layer without changing workspace semantics +6. detached workspace chrome policy now reads window-content capabilities instead of shell-owned workspace state +7. `XCUIEditorApp` Debug build passes after this phase + +What is intentionally not done yet: + +1. utility window domain does not exist yet +2. color picker still opens through detached panel workflow +3. `toolWindow` still exists in workspace panel descriptors as legacy leakage to remove in later phases + +This means the root shell/content ownership boundary has been extracted first, while feature semantics remain unchanged for safety. + +## 1. Objective + +This plan solves the problem from the architectural root, not by adding another special case. + +The goal is to completely separate: + +1. native window shell +2. workspace-backed dockable windows +3. utility windows that are inherently standalone + +The immediate trigger is the color picker window, but the real issue is broader: + +1. `EditorWindow` is still specialized around `UIEditorWorkspaceController` +2. utility windows are currently modeled as panels with a `toolWindow` flag +3. the color picker is opened through `openDetachedPanel` +4. utility window policy is inferred indirectly from workspace root content + +This architecture is semantically wrong. + +If a window is inherently standalone and can never merge back into the main workspace, it must not live inside the workspace panel model. + +## 2. Architectural Judgment + +The current implementation shares too much. + +Sharing the native host is correct: + +1. HWND lifetime +2. D3D render loop +3. DPI handling +4. input capture +5. title bar / chrome +6. screenshot and frame pacing + +Sharing the workspace panel abstraction is not correct for the color picker. + +The color picker is currently treated as: + +1. a panel descriptor in `UIEditorPanelRegistry` +2. a hosted panel inside shell composition +3. a detached panel opened via `OpenPanelInNewWindow(...)` +4. a single-root detached workspace that is merely styled as a tool window + +That means the color picker still belongs to the dock / detach / re-dock / transfer universe. + +This is the wrong owner domain. + +## 3. Confirmed Root Cause + +The root cause is not one bad function. + +The root cause is that the codebase currently conflates two different semantics: + +1. workspace window + - owns a `UIEditorWorkspaceController` + - participates in layout, docking, tab stacks, cross-window transfer + - can detach and re-merge +2. utility window + - owns standalone tool content + - does not belong to workspace layout + - does not expose `nodeId` / `panelId` docking semantics + - cannot merge into the main window + +Today those two semantics are both forced through `EditorWindow` + `UIEditorWorkspaceController`. + +That is why the color picker can only exist as a fake panel. + +## 4. Current Coupling Points + +These are the concrete places where the semantic mistake is encoded today: + +1. `new_editor/app/Platform/Win32/EditorWindow.h` + - `EditorWindow` constructor directly requires `UIEditorWorkspaceController` +2. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h` + - runtime directly stores `m_workspaceController` +3. `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h` + - utility-open intent is modeled as `EditorWindowOpenDetachedPanelRequest` +4. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` + - color picker open is converted into `openDetachedPanel` +5. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp` + - color picker window creation is routed through workspace mutation +6. `new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp` + - `OpenPanelInNewWindow(...)` is used to create the color picker window +7. `new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h` + - `toolWindow` is attached to panel descriptors +8. `new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp` + - utility semantics are inferred from detached workspace root content +9. `new_editor/app/Composition/EditorShellAssetBuilder.cpp` + - color picker is registered as a panel +10. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` + - color picker lives inside hosted panel update / dispatch infrastructure +11. `new_editor/app/State/EditorColorPickerToolState.h` + - color picker open intent is expressed as `requestOpenDetachedPanel` + +This is not one bug. This is an ownership boundary failure. + +## 5. Refactor Red Lines + +The refactor must not degrade into any of the following: + +1. keeping the color picker in `UIEditorPanelRegistry` and merely adding more `if (panelId == "color-picker")` branches +2. keeping `toolWindow` as the long-term mechanism for non-workspace windows +3. keeping `openDetachedPanel` as the generic request type for utility windows +4. allowing utility windows to remain inside `UIEditorWindowWorkspaceSet` +5. preserving `nodeId` / `panelId` transfer semantics for utility windows +6. building a second special-case bypass inside `EditorWindowWorkspaceCoordinator` +7. renaming the current path without changing ownership + +If any of those remain in the end state, the architecture is still wrong. + +## 6. Target End State + +The correct end state has three layers. + +### 6.1 Native Shell Layer + +A shared native shell owns only platform and rendering responsibilities: + +1. HWND creation and destruction +2. D3D renderer / swap chain / present loop +3. window chrome and title +4. DPI, sizing, screenshot, pointer capture plumbing +5. input event collection and dispatch to content + +This layer must not know whether the content is workspace-backed or utility-backed. + +Suggested shape: + +1. `EditorNativeWindowShell` +2. `EditorNativeWindowRuntime` +3. `IEditorWindowContentController` + +The names can change, but the boundary cannot. + +### 6.2 Workspace Window Layer + +Workspace windows are the only windows allowed to own: + +1. `UIEditorWorkspaceController` +2. `EditorShellRuntime` +3. dock host interaction state +4. detached panel transfer requests +5. cross-window tab drag and dock / re-dock semantics + +A workspace window can: + +1. represent the main window +2. represent a detached panel window +3. merge back into another workspace window + +Suggested shape: + +1. `EditorWorkspaceWindowContent` +2. `EditorWorkspaceWindowCoordinator` +3. `EditorWorkspaceWindowTransferRequests` + +### 6.3 Utility Window Layer + +Utility windows are standalone tool surfaces. + +They must not own: + +1. `UIEditorWorkspaceController` +2. dock layout +3. tab stack state +4. `nodeId` +5. `panelId` workspace transfer semantics + +They may own: + +1. dedicated tool state +2. dedicated rendering and input logic +3. explicit request / response channels to editor features +4. separate focus / reuse / close rules + +Suggested shape: + +1. `EditorUtilityWindowContent` +2. `EditorUtilityWindowCoordinator` +3. `EditorOpenUtilityWindowRequest` +4. `EditorUtilityWindowKind` +5. `EditorColorPickerUtilityWindowContent` + +## 7. Required Ownership Changes + +The refactor must move ownership, not just code location. + +### 7.1 Window Identity + +Today: + +1. detached color picker window identity is derived from `panelId` +2. detached utility behavior is inferred from the root workspace panel descriptor + +After refactor: + +1. workspace windows are identified inside workspace domain +2. utility windows are identified inside utility domain +3. utility window identity is no longer a panel identity + +### 7.2 Open Intent + +Today: + +1. color picker open intent means "open this panel in a detached window" + +After refactor: + +1. color picker open intent means "open or focus utility window of kind color picker" +2. the request carries explicit tool payload: + - initial color + - alpha mode + - inspector target binding + - focus / reuse policy + +### 7.3 Persistence + +Today: + +1. utility-like windows still pass through workspace layout concepts + +After refactor: + +1. workspace window state remains in workspace layout persistence +2. utility window state uses separate persistence or no persistence +3. utility windows never enter `UIEditorWindowWorkspaceSet` + +### 7.4 Visual Policy + +Today: + +1. tool-window policy is inferred from detached workspace content + +After refactor: + +1. workspace detached-window policy is only about workspace windows +2. utility-window policy belongs to utility window descriptors or utility content + +## 8. Migration Strategy + +The migration must happen in strict phases. + +### Phase A. Extract a Content-Neutral Native Window Shell + +Goal: + +1. stop making the native window host depend directly on `UIEditorWorkspaceController` + +Required changes: + +1. split the current `EditorWindow` responsibilities into: + - shell responsibilities + - content responsibilities +2. extract a content interface for: + - update + - draw + - input dispatch + - external preview / title contributions if needed +3. make `EditorWindowHostRuntime::CreateEditorWindow(...)` create a shell plus content, not a workspace-bound window object + +Success criteria: + +1. a shell instance can host workspace content +2. a shell instance can host non-workspace content +3. no shell constructor directly requires `UIEditorWorkspaceController` + +Red line: + +1. do not keep a hidden `m_workspaceController` in shell-level runtime + +### Phase B. Move Existing Workspace Windows onto the New Content Layer + +Goal: + +1. preserve all existing main-window and detached-panel behavior while isolating it into workspace-specific content + +Required changes: + +1. create `EditorWorkspaceWindowContent` +2. move current workspace-only responsibilities into it: + - `UIEditorWorkspaceController` + - `EditorShellRuntime` + - workspace frame orchestration + - dock transfer request generation +3. keep `EditorWindowWorkspaceCoordinator` but narrow its responsibility to workspace windows only + +Success criteria: + +1. main window still works +2. detached panel windows still work +3. cross-window tab drag and re-dock still work +4. utility windows are still not introduced yet, but the shell is already ready for them + +### Phase C. Introduce a Real Utility Window Domain + +Goal: + +1. create a first-class utility window pipeline independent from workspace mutation + +Required changes: + +1. add utility window request types +2. add `EditorUtilityWindowCoordinator` +3. add utility window registry / descriptor model +4. define utility window lifecycle: + - open + - reuse + - focus + - close + - app-shutdown behavior +5. define utility window payload passing + +Success criteria: + +1. utility windows can be created without touching `UIEditorWindowWorkspaceController` +2. utility windows can be reused without entering workspace state +3. utility windows do not generate dock transfer requests + +### Phase D. Migrate Color Picker Out of the Panel System + +Goal: + +1. make the color picker the first true utility window + +Required changes: + +1. remove color picker from `UIEditorPanelRegistry` +2. remove color picker from shell hosted-panel composition +3. replace `requestOpenDetachedPanel` with utility-window open intent +4. implement `EditorColorPickerUtilityWindowContent` +5. wire inspector -> color picker communication through explicit tool contracts instead of workspace panel visibility + +Success criteria: + +1. opening the color picker never calls `OpenPanelInNewWindow(...)` +2. color picker never appears in workspace visible panels +3. color picker never participates in detach / re-dock / cross-window panel transfer +4. color picker still updates inspector-bound color correctly + +### Phase E. Delete Transitional Workspace Utility Leakage + +Goal: + +1. remove all leftover semantic leakage after color picker migration + +Required deletions: + +1. remove `toolWindow` from `UIEditorPanelDescriptor` +2. remove color-picker-specific detached-panel request plumbing +3. remove utility inference from `UIEditorDetachedWindowPolicy` +4. remove any remaining workspace special cases that exist only for the old color picker path + +Success criteria: + +1. workspace code no longer knows the color picker exists +2. utility code no longer depends on panel registry membership +3. no root-content-based inference is needed to determine window kind + +## 9. Files Expected to Change + +This refactor is architecture-level and will necessarily touch a broad slice. + +### Shell / Host Layer + +1. `new_editor/app/Platform/Win32/EditorWindow.h` +2. `new_editor/app/Platform/Win32/EditorWindow.cpp` +3. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.h` +4. `new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp` +5. `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h` +6. `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp` +7. `new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp` +8. `new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp` + +### Workspace Layer + +1. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h` +2. `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp` +3. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` +4. `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h` +5. `new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp` +6. `new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp` + +### Utility Layer + +1. new utility window coordinator files +2. new utility window descriptor / request files +3. new color picker utility content files +4. possibly a host-level coordinator that routes shell instances to workspace or utility content + +### Color Picker / Feature Layer + +1. `new_editor/app/State/EditorColorPickerToolState.h` +2. `new_editor/app/State/EditorColorPickerToolState.cpp` +3. `new_editor/app/Features/ColorPicker/ColorPickerPanel.h` +4. `new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp` +5. `new_editor/app/Composition/EditorShellAssetBuilder.cpp` +6. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` +7. `new_editor/app/Composition/EditorShellRuntime.h` +8. `new_editor/app/Composition/EditorShellRuntime.cpp` +9. `new_editor/app/Composition/EditorShellDrawComposer.cpp` + +## 10. Verification Matrix + +This refactor is not done unless all of the following are checked. + +### Workspace Regression Checks + +1. main window render / input / title behavior is intact +2. panel detach still creates detached workspace windows +3. detached workspace windows can re-dock +4. cross-window tab drag-drop still works +5. workspace close path still closes detached workspace windows correctly +6. workspace layout persistence still excludes utility windows + +### Utility Window Checks + +1. opening color picker creates or focuses a utility window, not a detached panel window +2. color picker cannot be docked into the main window +3. color picker cannot be merged by tab drag +4. color picker does not appear in workspace visible-panel lists +5. color picker close does not mutate workspace layout +6. color picker reuse policy is correct: + - single-instance reuse or explicit multi-instance rule, but not accidental workspace reuse + +### Cross-Boundary Checks + +1. inspector -> color picker color synchronization still works +2. closing the main app tears down utility windows correctly +3. focus changes between workspace and utility windows do not break input capture +4. utility windows do not accidentally receive workspace drop preview / transfer state + +### Build Checks + +1. `XCUIEditorApp` Debug build passes +2. `XCUIEditorApp` Release build passes if used by the team + +## 11. Completion Criteria + +This refactor is complete only when all of these are true: + +1. no utility window is represented as a workspace panel +2. `EditorWindow` shell no longer requires `UIEditorWorkspaceController` +3. workspace and utility windows share only native shell infrastructure +4. `toolWindow` is not the long-term carrier of utility-window semantics +5. the color picker opens through utility window coordination, not workspace mutation +6. workspace code can be reasoned about without referencing utility windows +7. utility windows can be reasoned about without referencing dock / transfer semantics + +## 12. Final Statement + +The correct fix is not: + +1. a new bypass for the color picker +2. another flag on panel descriptors +3. another branch inside workspace mutation + +The correct fix is to restore the missing architectural boundary: + +1. native shell is shared +2. workspace semantics are isolated +3. utility semantics are isolated + +Only after this split is in place will the color picker stop being a fake detached panel and become a real standalone editor window type. diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index eb8dc103..e55d469d 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -284,6 +284,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Platform/Win32/EditorWindowFrameOrchestrator.cpp app/Platform/Win32/EditorWindowInputController.cpp app/Platform/Win32/EditorWindowRuntimeController.cpp + app/Platform/Win32/EditorWorkspaceWindowContentController.cpp app/Platform/Win32/Win32SystemInteractionHost.cpp app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp app/Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.cpp diff --git a/new_editor/app/Bootstrap/Application.cpp b/new_editor/app/Bootstrap/Application.cpp index 1b34f1b5..345712f4 100644 --- a/new_editor/app/Bootstrap/Application.cpp +++ b/new_editor/app/Bootstrap/Application.cpp @@ -4,6 +4,7 @@ #include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindowManager.h" #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWorkspaceWindowContentController.h" #include "Platform/Win32/Win32SystemInteractionHost.h" #include "Support/EnvironmentFlags.h" #include "Support/ExecutablePath.h" @@ -148,7 +149,8 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { createParams.autoCaptureOnStartup = App::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP"); if (m_windowManager->CreateEditorWindow( - m_editorContext->BuildWorkspaceController(), + App::CreateEditorWorkspaceWindowContentController( + m_editorContext->BuildWorkspaceController()), createParams) == nullptr) { AppendUIEditorRuntimeTrace("app", "primary window creation failed"); return false; diff --git a/new_editor/app/Platform/Win32/EditorWindow.cpp b/new_editor/app/Platform/Win32/EditorWindow.cpp index 89098be6..97f34600 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.cpp +++ b/new_editor/app/Platform/Win32/EditorWindow.cpp @@ -1,6 +1,7 @@ #include "Platform/Win32/EditorWindow.h" #include "Bootstrap/EditorResources.h" #include "Platform/Win32/EditorWindowChromeController.h" +#include "Platform/Win32/EditorWindowContentController.h" #include "Platform/Win32/EditorWindowSupport.h" #include "Platform/Win32/EditorWindowFrameOrchestrator.h" #include "Platform/Win32/EditorWindowInputController.h" @@ -9,6 +10,7 @@ #include "Composition/EditorContext.h" #include #include +#include #include #include #include @@ -74,13 +76,13 @@ EditorWindow::EditorWindow( std::string windowId, std::wstring title, bool primary, - UIEditorWorkspaceController workspaceController) + std::unique_ptr contentController) : m_state(std::make_unique()) , m_chromeController(std::make_unique()) , m_frameOrchestrator(std::make_unique()) , m_inputController(std::make_unique()) , m_runtime(std::make_unique( - std::move(workspaceController))) { + std::move(contentController))) { m_state->window.windowId = std::move(windowId); m_state->window.title = std::move(title); m_state->window.primary = primary; @@ -126,6 +128,10 @@ const std::wstring& EditorWindow::GetTitle() const { return m_state->window.title; } +const UIEditorWorkspaceController* EditorWindow::TryGetWorkspaceController() const { + return m_runtime->TryGetWorkspaceController(); +} + const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const { return m_runtime->GetWorkspaceController(); } @@ -134,20 +140,12 @@ UIEditorWorkspaceController& EditorWindow::GetMutableWorkspaceController() { return m_runtime->GetMutableWorkspaceController(); } -const EditorShellRuntime& EditorWindow::GetShellRuntime() const { - return m_runtime->GetShellRuntime(); +bool EditorWindow::HasHostedContentCapture() const { + return m_runtime->HasHostedContentCapture(); } -EditorShellRuntime& EditorWindow::GetShellRuntime() { - return m_runtime->GetShellRuntime(); -} - -const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const { - return m_runtime->GetShellFrame(); -} - -const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const { - return m_runtime->GetShellInteractionState(); +bool EditorWindow::HasShellInteractiveCapture() const { + return m_runtime->HasShellInteractiveCapture(); } void EditorWindow::SetExternalDockHostDropPreview( @@ -221,16 +219,12 @@ bool EditorWindow::Initialize( << " scale=" << GetDpiScale(); LogRuntimeTrace("window", dpiTrace.str()); - const bool initialized = m_runtime->Initialize( + return m_runtime->Initialize( m_state->window.hwnd, repoRoot, editorContext, captureRoot, autoCaptureOnStartup); - if (initialized) { - RequestFrame(EditorWindowFrameRequestReason::Initial); - } - return initialized; } void EditorWindow::Shutdown() { @@ -345,7 +339,7 @@ bool EditorWindow::TryResolveDockTabDragHotspot( POINT& outHotspot) const { const UIPoint clientPointDips = ConvertScreenPixelsToClientDips(screenPoint); UIPoint hotspotDips = {}; - if (!m_runtime->GetShellRuntime().TryResolveDockTabDragHotspot( + if (!m_runtime->TryResolveDockTabDragHotspot( nodeId, panelId, clientPointDips, @@ -363,7 +357,7 @@ bool EditorWindow::TryResolveDockTabDragHotspot( bool EditorWindow::TryResolveDockTabDropTarget( const POINT& screenPoint, UIEditorDockHostTabDropTarget& outTarget) const { - outTarget = m_runtime->GetShellRuntime().ResolveDockTabDropTarget( + outTarget = m_runtime->ResolveDockTabDropTarget( ConvertScreenPixelsToClientDips(screenPoint)); return outTarget.valid; } @@ -388,7 +382,6 @@ void EditorWindow::OnResize(UINT width, UINT height) { if (!matchesPredictedClientSize) { ApplyWindowResize(width, height); } - RequestFrame(EditorWindowFrameRequestReason::Resize); } void EditorWindow::OnEnterSizeMove() { @@ -403,7 +396,6 @@ void EditorWindow::OnExitSizeMove() { if (QueryCurrentClientPixelSize(width, height)) { ApplyWindowResize(width, height); } - RequestFrame(EditorWindowFrameRequestReason::ExitSizeMove); } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { @@ -432,7 +424,6 @@ void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { trace << "dpi changed to " << m_chromeController->GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", trace.str()); - RequestFrame(EditorWindowFrameRequestReason::DpiChanged); } bool EditorWindow::IsVerboseRuntimeTraceEnabled() { @@ -592,9 +583,8 @@ std::uint8_t ButtonMaskFromModifiers(const UIInputModifiers& modifiers) { } std::uint8_t ResolveExpectedShellCaptureButtons( - const EditorShellRuntime& shellRuntime) { + const UIEditorShellInteractionState& shellState) { std::uint8_t expectedButtons = 0u; - const auto& shellState = shellRuntime.GetShellInteractionState(); const auto& dockHostState = shellState.workspaceInteractionState.dockHostInteractionState; if (dockHostState.splitterDragState.active || @@ -657,15 +647,19 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( return transferRequests; } -void EditorWindow::OnPaintMessage() { +EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage( + EditorContext& editorContext, + bool globalTabDragActive) { if (!m_runtime->IsReady() || m_state->window.hwnd == nullptr) { - return; + return {}; } PAINTSTRUCT paintStruct = {}; BeginPaint(m_state->window.hwnd, &paintStruct); + const EditorWindowFrameTransferRequests transferRequests = + RenderFrame(editorContext, globalTabDragActive); EndPaint(m_state->window.hwnd, &paintStruct); - RequestFrame(EditorWindowFrameRequestReason::PaintMessage); + return transferRequests; } UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const { @@ -693,17 +687,27 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( SyncShellCapturedPointerButtonsFromSystemState(); std::vector frameEvents = m_inputController->TakePendingEvents(); const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(); + editorContext.AttachTextMeasurer(m_runtime->GetTextMeasurer()); + const Host::D3D12WindowRenderLoopFrameContext frameContext = m_runtime->BeginFrame(); + if (!frameContext.warning.empty()) { + LogRuntimeTrace("viewport", frameContext.warning); + } + const EditorWindowFrameTransferRequests transferRequests = - m_frameOrchestrator->UpdateAndAppend( - editorContext, - *m_runtime, - workspaceBounds, - frameEvents, - m_runtime->BuildCaptureStatusText(), - m_state->window.primary, - globalTabDragActive, - useDetachedTitleBarTabStrip, + m_runtime->UpdateAndAppend( + EditorWindowContentFrameContext{ + .editorContext = editorContext, + .bounds = workspaceBounds, + .inputEvents = frameEvents, + .captureStatusText = m_runtime->BuildCaptureStatusText(), + .primary = m_state->window.primary, + .globalTabDragActive = globalTabDragActive, + .useDetachedTitleBarTabStrip = useDetachedTitleBarTabStrip, + }, drawData); + if (frameContext.canRenderViewports) { + m_runtime->RenderRequestedViewports(frameContext.renderContext); + } ApplyShellRuntimePointerCapture(); ApplyCurrentCursor(); @@ -714,7 +718,7 @@ void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { m_inputController->SyncInputModifiersFromSystemState(); const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons( - m_runtime->GetShellRuntime()); + m_runtime->GetShellInteractionState()); if (expectedButtons == 0u || m_inputController->HasPendingPointerStateReconciliationEvent()) { return; @@ -729,12 +733,12 @@ void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { } void EditorWindow::ApplyShellRuntimePointerCapture() { - if (m_runtime->GetShellRuntime().HasShellInteractiveCapture()) { + if (m_runtime->HasShellInteractiveCapture()) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell); return; } - if (m_runtime->GetShellRuntime().HasHostedContentCapture()) { + if (m_runtime->HasHostedContentCapture()) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::HostedContent); return; } @@ -794,7 +798,7 @@ bool EditorWindow::ApplyCurrentCursor() const { bool EditorWindow::HasInteractiveCaptureState() const { - return m_runtime->GetShellRuntime().HasInteractiveCapture() || + return m_runtime->HasInteractiveCapture() || m_chromeController->IsBorderlessWindowDragRestoreArmed() || m_chromeController->IsBorderlessResizeActive() || m_inputController->HasPointerCaptureOwner(); @@ -927,17 +931,6 @@ void EditorWindow::ResetInputModifiers() { void EditorWindow::RequestManualScreenshot() { m_runtime->RequestManualScreenshot("manual_f12"); - RequestFrame(EditorWindowFrameRequestReason::ManualScreenshot); -} - -void EditorWindow::RequestFrame(EditorWindowFrameRequestReason reason) { - m_pendingFrameRequestReasons |= ToFrameRequestMask(reason); -} - -std::uint32_t EditorWindow::ConsumePendingFrameRequestReasons() { - const std::uint32_t pendingFrameRequestReasons = m_pendingFrameRequestReasons; - m_pendingFrameRequestReasons = 0u; - return pendingFrameRequestReasons; } bool EditorWindow::IsPointerInsideClientArea() const { @@ -969,20 +962,22 @@ LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge); } - switch (m_runtime->GetShellRuntime().GetHostedContentCursorKind()) { - case ProjectPanel::CursorKind::ResizeEW: + switch (m_runtime->GetHostedContentCursorKind()) { + case EditorWindowContentCursorKind::ResizeEW: return IDC_SIZEWE; - case ProjectPanel::CursorKind::Arrow: + case EditorWindowContentCursorKind::ResizeNS: + return IDC_SIZENS; + case EditorWindowContentCursorKind::Arrow: default: break; } - switch (m_runtime->GetShellRuntime().GetDockCursorKind()) { - case Widgets::UIEditorDockHostCursorKind::ResizeEW: + switch (m_runtime->GetDockCursorKind()) { + case EditorWindowContentCursorKind::ResizeEW: return IDC_SIZEWE; - case Widgets::UIEditorDockHostCursorKind::ResizeNS: + case EditorWindowContentCursorKind::ResizeNS: return IDC_SIZENS; - case Widgets::UIEditorDockHostCursorKind::Arrow: + case EditorWindowContentCursorKind::Arrow: default: return IDC_ARROW; } diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h index cea1f14b..e3dc6215 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.h +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -5,7 +5,6 @@ #endif #include "Platform/Win32/EditorWindowPointerCapture.h" -#include "Platform/Win32/EditorWindowFrameRequest.h" #include "Platform/Win32/EditorWindowTransferRequests.h" #include @@ -60,7 +59,7 @@ struct BorderlessWindowChromeLayout; namespace XCEngine::UI::Editor::App { class EditorContext; -class EditorShellRuntime; +class EditorWindowContentController; class EditorWindowChromeController; class EditorWindowFrameDriver; class EditorWindowFrameOrchestrator; @@ -78,7 +77,7 @@ public: std::string windowId, std::wstring title, bool primary, - UIEditorWorkspaceController workspaceController); + std::unique_ptr contentController); ~EditorWindow(); EditorWindow(const EditorWindow&) = delete; @@ -92,8 +91,8 @@ public: bool IsPrimary() const; bool IsClosing() const; const std::wstring& GetTitle() const; + const UIEditorWorkspaceController* TryGetWorkspaceController() const; const UIEditorWorkspaceController& GetWorkspaceController() const; - const UIEditorShellInteractionFrame& GetShellFrame() const; ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips(const POINT& screenPoint) const; private: @@ -107,9 +106,8 @@ private: bool IsRenderReady() const; bool IsTrackingMouseLeave() const; bool HasHoveredBorderlessResizeEdge() const; - const EditorShellRuntime& GetShellRuntime() const; - EditorShellRuntime& GetShellRuntime(); - const UIEditorShellInteractionState& GetShellInteractionState() const; + bool HasHostedContentCapture() const; + bool HasShellInteractiveCapture() const; bool TryResolveDockTabDragHotspot( std::string_view nodeId, std::string_view panelId, @@ -140,13 +138,13 @@ private: bool autoCaptureOnStartup); void Shutdown(); void ResetInteractionState(); - void RequestFrame(EditorWindowFrameRequestReason reason); - std::uint32_t ConsumePendingFrameRequestReasons(); EditorWindowFrameTransferRequests RenderFrame( EditorContext& editorContext, bool globalTabDragActive); - void OnPaintMessage(); + EditorWindowFrameTransferRequests OnPaintMessage( + EditorContext& editorContext, + bool globalTabDragActive); void OnResize(UINT width, UINT height); void OnEnterSizeMove(); void OnExitSizeMove(); @@ -258,7 +256,6 @@ private: std::unique_ptr m_frameOrchestrator = {}; std::unique_ptr m_inputController = {}; std::unique_ptr m_runtime = {}; - std::uint32_t m_pendingFrameRequestReasons = 0u; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp b/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp index bde13386..ff4315eb 100644 --- a/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp @@ -6,7 +6,6 @@ #include "Platform/Win32/EditorWindowSupport.h" #include -#include #include #include @@ -230,8 +229,7 @@ bool EditorWindowChromeController::HandleSystemCommand( bool EditorWindowChromeController::HandleGetMinMaxInfo( const EditorWindow& window, LPARAM lParam) const { - const ::XCEngine::UI::UISize minimumOuterSize = - ResolveUIEditorDetachedWorkspaceMinimumOuterSize(window.GetWorkspaceController()); + const ::XCEngine::UI::UISize minimumOuterSize = window.m_runtime->ResolveMinimumOuterSize(); return Host::HandleBorderlessWindowGetMinMaxInfo( window.m_state->window.hwnd, lParam, @@ -311,8 +309,7 @@ bool EditorWindowChromeController::HandleResizePointerMove( return false; } - const ::XCEngine::UI::UISize minimumOuterSize = - ResolveUIEditorDetachedWorkspaceMinimumOuterSize(window.GetWorkspaceController()); + const ::XCEngine::UI::UISize minimumOuterSize = window.m_runtime->ResolveMinimumOuterSize(); RECT targetRect = Host::ComputeBorderlessWindowResizeRect( GetBorderlessResizeInitialWindowRect(), GetBorderlessResizeInitialScreenPoint(), @@ -330,7 +327,7 @@ bool EditorWindowChromeController::HandleResizePointerMove( static_cast(width), static_cast(height)); window.ApplyWindowResize(static_cast(width), static_cast(height)); - window.RequestFrame(EditorWindowFrameRequestReason::BorderlessTransition); + (void)window.RenderFrame(editorContext, globalTabDragActive); SetWindowPos( window.m_state->window.hwnd, @@ -618,9 +615,7 @@ Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLa float leadingOccupiedRight = 0.0f; if (ShouldUseDetachedTitleBarTabStrip(window)) { leadingOccupiedRight = ResolveDetachedTabWidth( - ResolveUIEditorDetachedWorkspaceTitle( - window.GetWorkspaceController(), - "Panel"), + window.m_runtime->ResolveTabStripTitleText("Panel"), &window.m_runtime->GetTextMeasurer()); } @@ -632,8 +627,7 @@ Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLa bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip( const EditorWindow& window) const { return !window.m_state->window.primary && - !IsUIEditorDetachedWorkspaceToolWindow(window.m_runtime->GetWorkspaceController()) && - HasUIEditorSingleVisibleRootTab(window.m_runtime->GetWorkspaceController()); + window.m_runtime->ShouldUseDetachedTitleBarTabStrip(); } void EditorWindowChromeController::AppendChrome( @@ -689,13 +683,10 @@ void EditorWindowChromeController::AppendChrome( 0.0f, (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), - IsUIEditorDetachedWorkspaceToolWindow(window.GetWorkspaceController()) - ? ResolveUIEditorDetachedWorkspaceTitle( - window.GetWorkspaceController(), - "Panel") - : (window.m_state->window.titleText.empty() - ? std::string("XCEngine Editor") - : window.m_state->window.titleText), + window.m_runtime->ResolveDetachedWindowTitleText( + window.m_state->window.titleText.empty() + ? std::string_view("XCEngine Editor") + : std::string_view(window.m_state->window.titleText)), kShellTextColor, kBorderlessTitleBarFontSize); } @@ -817,6 +808,7 @@ bool EditorWindowChromeController::ApplyPredictedWindowRectTransition( SetPredictedClientPixelSize(static_cast(width), static_cast(height)); window.ApplyWindowResize(static_cast(width), static_cast(height)); + (void)window.RenderFrame(editorContext, globalTabDragActive); SetWindowPos( window.m_state->window.hwnd, nullptr, @@ -825,7 +817,6 @@ bool EditorWindowChromeController::ApplyPredictedWindowRectTransition( width, height, SWP_NOZORDER | SWP_NOACTIVATE); - window.RequestFrame(EditorWindowFrameRequestReason::BorderlessTransition); window.InvalidateHostWindow(); return true; } diff --git a/new_editor/app/Platform/Win32/EditorWindowContentController.h b/new_editor/app/Platform/Win32/EditorWindowContentController.h new file mode 100644 index 00000000..40ed2031 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowContentController.h @@ -0,0 +1,126 @@ +#pragma once + +#include "Platform/Win32/EditorWindowTransferRequests.h" + +#include + +#include +#include +#include +#include +#include + +namespace XCEngine::Rendering { + +class RenderContext; + +} // namespace XCEngine::Rendering + +namespace XCEngine::UI { + +class UIDrawData; + +struct UIInputEvent; +struct UIPoint; +struct UIRect; +struct UISize; + +} // namespace XCEngine::UI + +namespace XCEngine::UI::Editor { + +struct UIEditorDockHostTabDropTarget; +class UIEditorWorkspaceController; + +struct UIEditorShellInteractionFrame; +struct UIEditorShellInteractionState; + +namespace Widgets { +struct UIEditorDockHostDropPreviewState; +} + +} // namespace XCEngine::UI::Editor + +namespace XCEngine::UI::Editor::Ports { + +class TexturePort; +class ViewportRenderPort; + +} // namespace XCEngine::UI::Editor::Ports + +namespace XCEngine::UI::Editor::App { + +class EditorContext; + +enum class EditorWindowContentCursorKind : std::uint8_t { + Arrow = 0, + ResizeEW, + ResizeNS, +}; + +struct EditorWindowContentInitializationContext { + const std::filesystem::path& repoRoot; + EditorContext& editorContext; + Ports::TexturePort& textureHost; + UIEditorTextMeasurer& textMeasurer; + Ports::ViewportRenderPort& viewportRenderer; +}; + +struct EditorWindowContentFrameContext { + EditorContext& editorContext; + const ::XCEngine::UI::UIRect& bounds; + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents; + std::string_view captureStatusText; + bool primary = false; + bool globalTabDragActive = false; + bool useDetachedTitleBarTabStrip = false; +}; + +class EditorWindowContentController { +public: + virtual ~EditorWindowContentController() = default; + + virtual const UIEditorWorkspaceController* TryGetWorkspaceController() const = 0; + virtual UIEditorWorkspaceController* TryGetMutableWorkspaceController() = 0; + virtual void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) = 0; + + virtual void Initialize(const EditorWindowContentInitializationContext& context) = 0; + virtual void Shutdown() = 0; + virtual void ResetInteractionState() = 0; + virtual void SetViewportSurfacePresentationEnabled(bool enabled) = 0; + + virtual EditorWindowFrameTransferRequests UpdateAndAppend( + const EditorWindowContentFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) = 0; + virtual void RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext) = 0; + + virtual const UIEditorShellInteractionFrame& GetShellFrame() const = 0; + virtual const UIEditorShellInteractionState& GetShellInteractionState() const = 0; + + virtual void SetExternalDockHostDropPreview( + const Widgets::UIEditorDockHostDropPreviewState& preview) = 0; + virtual void ClearExternalDockHostDropPreview() = 0; + + virtual bool TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const ::XCEngine::UI::UIPoint& point, + ::XCEngine::UI::UIPoint& outHotspot) const = 0; + virtual UIEditorDockHostTabDropTarget ResolveDockTabDropTarget( + const ::XCEngine::UI::UIPoint& point) const = 0; + + virtual bool HasHostedContentCapture() const = 0; + virtual bool HasShellInteractiveCapture() const = 0; + virtual bool HasInteractiveCapture() const = 0; + virtual EditorWindowContentCursorKind GetHostedContentCursorKind() const = 0; + virtual EditorWindowContentCursorKind GetDockCursorKind() const = 0; + + virtual ::XCEngine::UI::UISize ResolveMinimumOuterSize() const = 0; + virtual bool ShouldUseDetachedTitleBarTabStrip() const = 0; + virtual std::string ResolveTabStripTitleText(std::string_view fallbackTitle) const = 0; + virtual std::string ResolveDetachedWindowTitleText( + std::string_view fallbackWindowTitle) const = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp index 7dd1d91e..5b66588f 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp @@ -2,11 +2,12 @@ #include "Composition/EditorContext.h" #include "Composition/EditorPanelIds.h" +#include "Composition/EditorShellRuntime.h" #include "Composition/EditorShellVariant.h" -#include "Platform/Win32/EditorWindowRuntimeController.h" #include "Platform/Win32/EditorWindowSupport.h" #include +#include #include #include @@ -48,25 +49,21 @@ std::string DescribeInputEventType(const UIInputEvent& event) { EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend( EditorContext& editorContext, - EditorWindowRuntimeController& runtimeController, + UIEditorWorkspaceController& workspaceController, + EditorShellRuntime& shellRuntime, const ::XCEngine::UI::UIRect& workspaceBounds, - const std::vector& frameEvents, - std::string_view captureStatusText, - bool primary, - bool globalTabDragActive, - bool useDetachedTitleBarTabStrip, - UIDrawData& drawData) const { - LogInputTrace(editorContext, runtimeController, frameEvents); + const std::vector& frameEvents, + std::string_view captureStatusText, + bool primary, + bool globalTabDragActive, + bool useDetachedTitleBarTabStrip, + UIDrawData& drawData) const { + LogInputTrace( + editorContext, + workspaceController, + shellRuntime.GetShellInteractionState(), + frameEvents); - const Host::D3D12WindowRenderLoopFrameContext frameContext = runtimeController.BeginFrame(); - if (!frameContext.warning.empty()) { - LogRuntimeTrace("viewport", frameContext.warning); - } - - editorContext.AttachTextMeasurer(runtimeController.GetTextMeasurer()); - UIEditorWorkspaceController& workspaceController = - runtimeController.GetMutableWorkspaceController(); - EditorShellRuntime& shellRuntime = runtimeController.GetShellRuntime(); shellRuntime.Update( editorContext, workspaceController, @@ -106,10 +103,6 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend } shellRuntime.Append(drawData); - if (frameContext.canRenderViewports) { - shellRuntime.RenderRequestedViewports(frameContext.renderContext); - } - return transferRequests; } @@ -152,7 +145,8 @@ std::string EditorWindowFrameOrchestrator::DescribeInputEvents( void EditorWindowFrameOrchestrator::LogInputTrace( EditorContext& editorContext, - const EditorWindowRuntimeController& runtimeController, + const UIEditorWorkspaceController& workspaceController, + const UIEditorShellInteractionState& shellInteractionState, const std::vector& frameEvents) const { if (frameEvents.empty() || !IsVerboseRuntimeTraceEnabled()) { return; @@ -162,8 +156,8 @@ void EditorWindowFrameOrchestrator::LogInputTrace( "input", DescribeInputEvents(frameEvents) + " | " + editorContext.DescribeWorkspaceState( - runtimeController.GetWorkspaceController(), - runtimeController.GetShellInteractionState())); + workspaceController, + shellInteractionState)); } void EditorWindowFrameOrchestrator::LogFrameInteractionTrace( diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h index cc703ab0..bbe7386b 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h @@ -24,13 +24,14 @@ class UIEditorWorkspaceController; struct UIEditorDockHostInteractionState; struct UIEditorShellInteractionFrame; +struct UIEditorShellInteractionState; } // namespace XCEngine::UI::Editor namespace XCEngine::UI::Editor::App { class EditorContext; -class EditorWindowRuntimeController; +class EditorShellRuntime; class EditorWindowFrameOrchestrator final { public: @@ -44,7 +45,8 @@ public: EditorWindowFrameTransferRequests UpdateAndAppend( EditorContext& editorContext, - EditorWindowRuntimeController& runtimeController, + UIEditorWorkspaceController& workspaceController, + EditorShellRuntime& shellRuntime, const ::XCEngine::UI::UIRect& workspaceBounds, const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents, std::string_view captureStatusText, @@ -61,7 +63,8 @@ public: private: void LogInputTrace( EditorContext& editorContext, - const EditorWindowRuntimeController& runtimeController, + const UIEditorWorkspaceController& workspaceController, + const UIEditorShellInteractionState& shellInteractionState, const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents) const; void LogFrameInteractionTrace( const UIEditorWorkspaceController& workspaceController, diff --git a/new_editor/app/Platform/Win32/EditorWindowManager.h b/new_editor/app/Platform/Win32/EditorWindowManager.h index aa47abb5..1d4c684c 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManager.h +++ b/new_editor/app/Platform/Win32/EditorWindowManager.h @@ -26,6 +26,7 @@ namespace XCEngine::UI::Editor::App { class EditorContext; class EditorWindow; +class EditorWindowContentController; class EditorWindowHostRuntime; class EditorWindowLifecycleCoordinator; class EditorWindowWorkspaceCoordinator; @@ -66,7 +67,7 @@ public: EditorWindowManager& operator=(EditorWindowManager&&) = delete; EditorWindow* CreateEditorWindow( - UIEditorWorkspaceController workspaceController, + std::unique_ptr contentController, const CreateParams& params); void HandlePendingNativeWindowCreated(HWND hwnd); void Shutdown(); diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp index 78e6a304..e776ffff 100644 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp @@ -5,10 +5,13 @@ #include "Platform/Win32/EditorWindowSupport.h" #include "Support/EmbeddedPngLoader.h" +#include + #include +#include #include -#include #include +#include #include namespace XCEngine::UI::Editor::App { @@ -24,9 +27,8 @@ constexpr float kFrameStatsDisplayRefreshIntervalSeconds = 0.25f; } EditorWindowRuntimeController::EditorWindowRuntimeController( - UIEditorWorkspaceController workspaceController) - : m_workspaceController(std::move(workspaceController)) { -} + std::unique_ptr contentController) + : m_contentController(std::move(contentController)) {} EditorWindowRuntimeController::~EditorWindowRuntimeController() = default; @@ -34,43 +36,127 @@ bool EditorWindowRuntimeController::IsReady() const { return m_ready; } +const UIEditorWorkspaceController* EditorWindowRuntimeController::TryGetWorkspaceController() const { + return m_contentController != nullptr + ? m_contentController->TryGetWorkspaceController() + : nullptr; +} + +UIEditorWorkspaceController* EditorWindowRuntimeController::TryGetMutableWorkspaceController() { + return m_contentController != nullptr + ? m_contentController->TryGetMutableWorkspaceController() + : nullptr; +} + const UIEditorWorkspaceController& EditorWindowRuntimeController::GetWorkspaceController() const { - return m_workspaceController; + const UIEditorWorkspaceController* workspaceController = TryGetWorkspaceController(); + assert(workspaceController != nullptr); + return *workspaceController; } UIEditorWorkspaceController& EditorWindowRuntimeController::GetMutableWorkspaceController() { - return m_workspaceController; + UIEditorWorkspaceController* workspaceController = TryGetMutableWorkspaceController(); + assert(workspaceController != nullptr); + return *workspaceController; } void EditorWindowRuntimeController::ReplaceWorkspaceController( UIEditorWorkspaceController workspaceController) { - m_workspaceController = std::move(workspaceController); -} - -const EditorShellRuntime& EditorWindowRuntimeController::GetShellRuntime() const { - return m_shellRuntime; -} - -EditorShellRuntime& EditorWindowRuntimeController::GetShellRuntime() { - return m_shellRuntime; + assert(m_contentController != nullptr); + m_contentController->ReplaceWorkspaceController(std::move(workspaceController)); } const UIEditorShellInteractionFrame& EditorWindowRuntimeController::GetShellFrame() const { - return m_shellRuntime.GetShellFrame(); + assert(m_contentController != nullptr); + return m_contentController->GetShellFrame(); } const UIEditorShellInteractionState& EditorWindowRuntimeController::GetShellInteractionState() const { - return m_shellRuntime.GetShellInteractionState(); + assert(m_contentController != nullptr); + return m_contentController->GetShellInteractionState(); } void EditorWindowRuntimeController::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { - m_shellRuntime.SetExternalDockHostDropPreview(preview); + assert(m_contentController != nullptr); + m_contentController->SetExternalDockHostDropPreview(preview); } void EditorWindowRuntimeController::ClearExternalDockHostDropPreview() { - m_shellRuntime.ClearExternalDockHostDropPreview(); + if (m_contentController != nullptr) { + m_contentController->ClearExternalDockHostDropPreview(); + } +} + +bool EditorWindowRuntimeController::TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const ::XCEngine::UI::UIPoint& point, + ::XCEngine::UI::UIPoint& outHotspot) const { + return m_contentController != nullptr && + m_contentController->TryResolveDockTabDragHotspot( + nodeId, + panelId, + point, + outHotspot); +} + +UIEditorDockHostTabDropTarget EditorWindowRuntimeController::ResolveDockTabDropTarget( + const ::XCEngine::UI::UIPoint& point) const { + assert(m_contentController != nullptr); + return m_contentController->ResolveDockTabDropTarget(point); +} + +bool EditorWindowRuntimeController::HasHostedContentCapture() const { + return m_contentController != nullptr && + m_contentController->HasHostedContentCapture(); +} + +bool EditorWindowRuntimeController::HasShellInteractiveCapture() const { + return m_contentController != nullptr && + m_contentController->HasShellInteractiveCapture(); +} + +bool EditorWindowRuntimeController::HasInteractiveCapture() const { + return m_contentController != nullptr && + m_contentController->HasInteractiveCapture(); +} + +EditorWindowContentCursorKind EditorWindowRuntimeController::GetHostedContentCursorKind() const { + return m_contentController != nullptr + ? m_contentController->GetHostedContentCursorKind() + : EditorWindowContentCursorKind::Arrow; +} + +EditorWindowContentCursorKind EditorWindowRuntimeController::GetDockCursorKind() const { + return m_contentController != nullptr + ? m_contentController->GetDockCursorKind() + : EditorWindowContentCursorKind::Arrow; +} + +::XCEngine::UI::UISize EditorWindowRuntimeController::ResolveMinimumOuterSize() const { + assert(m_contentController != nullptr); + return m_contentController->ResolveMinimumOuterSize(); +} + +bool EditorWindowRuntimeController::ShouldUseDetachedTitleBarTabStrip() const { + return m_contentController != nullptr && + m_contentController->ShouldUseDetachedTitleBarTabStrip(); +} + +std::string EditorWindowRuntimeController::ResolveTabStripTitleText( + std::string_view fallbackTitle) const { + return m_contentController != nullptr + ? m_contentController->ResolveTabStripTitleText(fallbackTitle) + : std::string(fallbackTitle); +} + +std::string EditorWindowRuntimeController::ResolveDetachedWindowTitleText( + std::string_view fallbackWindowTitle) const { + return m_contentController != nullptr + ? m_contentController->ResolveDetachedWindowTitleText(fallbackWindowTitle) + : std::string(fallbackWindowTitle); } void EditorWindowRuntimeController::SetDpiScale(float dpiScale) { @@ -142,9 +228,15 @@ bool EditorWindowRuntimeController::Initialize( } editorContext.AttachTextMeasurer(m_textSystem); - m_shellRuntime.Initialize(repoRoot, m_textureHost, m_textSystem); - m_shellRuntime.AttachViewportWindowRenderer(m_windowRenderer); - m_shellRuntime.SetViewportSurfacePresentationEnabled( + assert(m_contentController != nullptr); + m_contentController->Initialize(EditorWindowContentInitializationContext{ + .repoRoot = repoRoot, + .editorContext = editorContext, + .textureHost = m_textureHost, + .textMeasurer = m_textSystem, + .viewportRenderer = m_windowRenderer, + }); + m_contentController->SetViewportSurfacePresentationEnabled( attachResult.hasViewportSurfacePresentation); std::string titleBarLogoError = {}; @@ -155,16 +247,19 @@ bool EditorWindowRuntimeController::Initialize( titleBarLogoError)) { LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); } - if (!m_shellRuntime.GetBuiltInIconError().empty()) { - LogRuntimeTrace("icons", m_shellRuntime.GetBuiltInIconError()); + + if (const UIEditorWorkspaceController* workspaceController = TryGetWorkspaceController(); + workspaceController != nullptr) { + LogRuntimeTrace( + "app", + "shell runtime initialized: " + + editorContext.DescribeWorkspaceState( + *workspaceController, + m_contentController->GetShellInteractionState())); + } else { + LogRuntimeTrace("app", "window content initialized: non-workspace content"); } - LogRuntimeTrace( - "app", - "shell runtime initialized: " + - editorContext.DescribeWorkspaceState( - m_workspaceController, - m_shellRuntime.GetShellInteractionState())); ResetFrameTiming(); m_ready = true; @@ -186,8 +281,10 @@ void EditorWindowRuntimeController::Shutdown() { "window-close", "EditorWindowRuntimeController::Shutdown stage=ScreenshotController"); m_screenshotController.Shutdown(); - LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=ShellRuntime"); - m_shellRuntime.Shutdown(); + LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=WindowContent"); + if (m_contentController != nullptr) { + m_contentController->Shutdown(); + } LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=RenderLoopDetach"); m_windowRenderLoop.Detach(); LogRuntimeTrace("window-close", "EditorWindowRuntimeController::Shutdown stage=UiRenderer"); @@ -205,7 +302,9 @@ void EditorWindowRuntimeController::Shutdown() { } void EditorWindowRuntimeController::ResetInteractionState() { - m_shellRuntime.ResetInteractionState(); + if (m_contentController != nullptr) { + m_contentController->ResetInteractionState(); + } ResetFrameTiming(); } @@ -216,8 +315,10 @@ bool EditorWindowRuntimeController::ApplyResize(UINT width, UINT height) { const Host::D3D12WindowRenderLoopResizeResult resizeResult = m_windowRenderLoop.ApplyResize(width, height); - m_shellRuntime.SetViewportSurfacePresentationEnabled( - resizeResult.hasViewportSurfacePresentation); + if (m_contentController != nullptr) { + m_contentController->SetViewportSurfacePresentationEnabled( + resizeResult.hasViewportSurfacePresentation); + } if (!resizeResult.windowRendererWarning.empty()) { LogRuntimeTrace("present", resizeResult.windowRendererWarning); @@ -263,6 +364,20 @@ Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present( return result; } +EditorWindowFrameTransferRequests EditorWindowRuntimeController::UpdateAndAppend( + const EditorWindowContentFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) { + assert(m_contentController != nullptr); + return m_contentController->UpdateAndAppend(context, drawData); +} + +void EditorWindowRuntimeController::RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext) { + if (m_contentController != nullptr) { + m_contentController->RenderRequestedViewports(renderContext); + } +} + void EditorWindowRuntimeController::RequestManualScreenshot(std::string reason) { m_screenshotController.RequestCapture(std::move(reason)); } @@ -352,4 +467,3 @@ void EditorWindowRuntimeController::RefreshDisplayedFrameStats() { } } // namespace XCEngine::UI::Editor::App - diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h index 6c3f8f03..189d0603 100644 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h @@ -4,7 +4,7 @@ #define NOMINMAX #endif -#include "Composition/EditorShellRuntime.h" +#include "Platform/Win32/EditorWindowContentController.h" #include "Platform/Win32/EditorWindowScreenshotController.h" #include @@ -13,35 +13,23 @@ #include #include -#include - #include -#include #include #include #include +#include #include -namespace XCEngine::UI::Editor { - -struct UIEditorShellInteractionFrame; -struct UIEditorShellInteractionState; - -namespace Widgets { -struct UIEditorDockHostDropPreviewState; -} - -} // namespace XCEngine::UI::Editor - namespace XCEngine::UI::Editor::App { class EditorContext; class EditorWindowRuntimeController final { public: - explicit EditorWindowRuntimeController(UIEditorWorkspaceController workspaceController); + explicit EditorWindowRuntimeController( + std::unique_ptr contentController); ~EditorWindowRuntimeController(); EditorWindowRuntimeController(const EditorWindowRuntimeController&) = delete; @@ -51,18 +39,35 @@ public: bool IsReady() const; + const UIEditorWorkspaceController* TryGetWorkspaceController() const; + UIEditorWorkspaceController* TryGetMutableWorkspaceController(); const UIEditorWorkspaceController& GetWorkspaceController() const; UIEditorWorkspaceController& GetMutableWorkspaceController(); void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController); - const EditorShellRuntime& GetShellRuntime() const; - EditorShellRuntime& GetShellRuntime(); const UIEditorShellInteractionFrame& GetShellFrame() const; const UIEditorShellInteractionState& GetShellInteractionState() const; void SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview); void ClearExternalDockHostDropPreview(); + bool TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const ::XCEngine::UI::UIPoint& point, + ::XCEngine::UI::UIPoint& outHotspot) const; + UIEditorDockHostTabDropTarget ResolveDockTabDropTarget( + const ::XCEngine::UI::UIPoint& point) const; + bool HasHostedContentCapture() const; + bool HasShellInteractiveCapture() const; + bool HasInteractiveCapture() const; + EditorWindowContentCursorKind GetHostedContentCursorKind() const; + EditorWindowContentCursorKind GetDockCursorKind() const; + ::XCEngine::UI::UISize ResolveMinimumOuterSize() const; + bool ShouldUseDetachedTitleBarTabStrip() const; + std::string ResolveTabStripTitleText(std::string_view fallbackTitle) const; + std::string ResolveDetachedWindowTitleText( + std::string_view fallbackWindowTitle) const; void SetDpiScale(float dpiScale); ::XCEngine::UI::Editor::UIEditorTextMeasurer& GetTextMeasurer(); @@ -82,6 +87,11 @@ public: Host::D3D12WindowRenderLoopFrameContext BeginFrame(); Host::D3D12WindowRenderLoopPresentResult Present( const ::XCEngine::UI::UIDrawData& drawData); + EditorWindowFrameTransferRequests UpdateAndAppend( + const EditorWindowContentFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData); + void RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext); void RequestManualScreenshot(std::string reason); std::string BuildCaptureStatusText() const; @@ -99,8 +109,7 @@ private: Host::D3D12WindowRenderLoop m_windowRenderLoop = {}; EditorWindowScreenshotController m_screenshotController = {}; ::XCEngine::UI::UITextureHandle m_titleBarLogoIcon = {}; - UIEditorWorkspaceController m_workspaceController = {}; - EditorShellRuntime m_shellRuntime = {}; + std::unique_ptr m_contentController = {}; std::chrono::steady_clock::time_point m_lastFrameTime = {}; bool m_hasLastFrameTime = false; float m_smoothedDeltaTimeSeconds = 0.0f; diff --git a/new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.cpp b/new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.cpp new file mode 100644 index 00000000..c1fc560c --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.cpp @@ -0,0 +1,182 @@ +#include "Platform/Win32/EditorWorkspaceWindowContentController.h" + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +EditorWindowContentCursorKind ToContentCursorKind(ProjectPanel::CursorKind cursorKind) { + switch (cursorKind) { + case ProjectPanel::CursorKind::ResizeEW: + return EditorWindowContentCursorKind::ResizeEW; + case ProjectPanel::CursorKind::Arrow: + default: + return EditorWindowContentCursorKind::Arrow; + } +} + +EditorWindowContentCursorKind ToContentCursorKind( + Widgets::UIEditorDockHostCursorKind cursorKind) { + switch (cursorKind) { + case Widgets::UIEditorDockHostCursorKind::ResizeEW: + return EditorWindowContentCursorKind::ResizeEW; + case Widgets::UIEditorDockHostCursorKind::ResizeNS: + return EditorWindowContentCursorKind::ResizeNS; + case Widgets::UIEditorDockHostCursorKind::Arrow: + default: + return EditorWindowContentCursorKind::Arrow; + } +} + +} // namespace + +EditorWorkspaceWindowContentController::EditorWorkspaceWindowContentController( + UIEditorWorkspaceController workspaceController) + : m_workspaceController(std::move(workspaceController)) {} + +EditorWorkspaceWindowContentController::~EditorWorkspaceWindowContentController() = default; + +const UIEditorWorkspaceController* +EditorWorkspaceWindowContentController::TryGetWorkspaceController() const { + return &m_workspaceController; +} + +UIEditorWorkspaceController* +EditorWorkspaceWindowContentController::TryGetMutableWorkspaceController() { + return &m_workspaceController; +} + +void EditorWorkspaceWindowContentController::ReplaceWorkspaceController( + UIEditorWorkspaceController workspaceController) { + m_workspaceController = std::move(workspaceController); +} + +void EditorWorkspaceWindowContentController::Initialize( + const EditorWindowContentInitializationContext& context) { + m_shellRuntime.Initialize(context.repoRoot, context.textureHost, context.textMeasurer); + m_shellRuntime.AttachViewportWindowRenderer(context.viewportRenderer); +} + +void EditorWorkspaceWindowContentController::Shutdown() { + m_shellRuntime.Shutdown(); +} + +void EditorWorkspaceWindowContentController::ResetInteractionState() { + m_shellRuntime.ResetInteractionState(); +} + +void EditorWorkspaceWindowContentController::SetViewportSurfacePresentationEnabled(bool enabled) { + m_shellRuntime.SetViewportSurfacePresentationEnabled(enabled); +} + +EditorWindowFrameTransferRequests EditorWorkspaceWindowContentController::UpdateAndAppend( + const EditorWindowContentFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) { + return m_frameOrchestrator.UpdateAndAppend( + context.editorContext, + m_workspaceController, + m_shellRuntime, + context.bounds, + context.inputEvents, + context.captureStatusText, + context.primary, + context.globalTabDragActive, + context.useDetachedTitleBarTabStrip, + drawData); +} + +void EditorWorkspaceWindowContentController::RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext) { + m_shellRuntime.RenderRequestedViewports(renderContext); +} + +const UIEditorShellInteractionFrame& +EditorWorkspaceWindowContentController::GetShellFrame() const { + return m_shellRuntime.GetShellFrame(); +} + +const UIEditorShellInteractionState& +EditorWorkspaceWindowContentController::GetShellInteractionState() const { + return m_shellRuntime.GetShellInteractionState(); +} + +void EditorWorkspaceWindowContentController::SetExternalDockHostDropPreview( + const Widgets::UIEditorDockHostDropPreviewState& preview) { + m_shellRuntime.SetExternalDockHostDropPreview(preview); +} + +void EditorWorkspaceWindowContentController::ClearExternalDockHostDropPreview() { + m_shellRuntime.ClearExternalDockHostDropPreview(); +} + +bool EditorWorkspaceWindowContentController::TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const ::XCEngine::UI::UIPoint& point, + ::XCEngine::UI::UIPoint& outHotspot) const { + return m_shellRuntime.TryResolveDockTabDragHotspot( + nodeId, + panelId, + point, + outHotspot); +} + +UIEditorDockHostTabDropTarget EditorWorkspaceWindowContentController::ResolveDockTabDropTarget( + const ::XCEngine::UI::UIPoint& point) const { + return m_shellRuntime.ResolveDockTabDropTarget(point); +} + +bool EditorWorkspaceWindowContentController::HasHostedContentCapture() const { + return m_shellRuntime.HasHostedContentCapture(); +} + +bool EditorWorkspaceWindowContentController::HasShellInteractiveCapture() const { + return m_shellRuntime.HasShellInteractiveCapture(); +} + +bool EditorWorkspaceWindowContentController::HasInteractiveCapture() const { + return m_shellRuntime.HasInteractiveCapture(); +} + +EditorWindowContentCursorKind +EditorWorkspaceWindowContentController::GetHostedContentCursorKind() const { + return ToContentCursorKind(m_shellRuntime.GetHostedContentCursorKind()); +} + +EditorWindowContentCursorKind EditorWorkspaceWindowContentController::GetDockCursorKind() const { + return ToContentCursorKind(m_shellRuntime.GetDockCursorKind()); +} + +::XCEngine::UI::UISize EditorWorkspaceWindowContentController::ResolveMinimumOuterSize() const { + return ResolveUIEditorDetachedWorkspaceMinimumOuterSize(m_workspaceController); +} + +bool EditorWorkspaceWindowContentController::ShouldUseDetachedTitleBarTabStrip() const { + return !IsUIEditorDetachedWorkspaceToolWindow(m_workspaceController) && + HasUIEditorSingleVisibleRootTab(m_workspaceController); +} + +std::string EditorWorkspaceWindowContentController::ResolveTabStripTitleText( + std::string_view fallbackTitle) const { + return ResolveUIEditorDetachedWorkspaceTitle(m_workspaceController, fallbackTitle); +} + +std::string EditorWorkspaceWindowContentController::ResolveDetachedWindowTitleText( + std::string_view fallbackWindowTitle) const { + if (IsUIEditorDetachedWorkspaceToolWindow(m_workspaceController)) { + return ResolveUIEditorDetachedWorkspaceTitle( + m_workspaceController, + fallbackWindowTitle); + } + + return std::string(fallbackWindowTitle); +} + +std::unique_ptr CreateEditorWorkspaceWindowContentController( + UIEditorWorkspaceController workspaceController) { + return std::make_unique( + std::move(workspaceController)); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.h b/new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.h new file mode 100644 index 00000000..2abfd915 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWorkspaceWindowContentController.h @@ -0,0 +1,69 @@ +#pragma once + +#include "Composition/EditorShellRuntime.h" +#include "Platform/Win32/EditorWindowContentController.h" +#include "Platform/Win32/EditorWindowFrameOrchestrator.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +class EditorWorkspaceWindowContentController final : public EditorWindowContentController { +public: + explicit EditorWorkspaceWindowContentController(UIEditorWorkspaceController workspaceController); + ~EditorWorkspaceWindowContentController() override; + + const UIEditorWorkspaceController* TryGetWorkspaceController() const override; + UIEditorWorkspaceController* TryGetMutableWorkspaceController() override; + void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) override; + + void Initialize(const EditorWindowContentInitializationContext& context) override; + void Shutdown() override; + void ResetInteractionState() override; + void SetViewportSurfacePresentationEnabled(bool enabled) override; + + EditorWindowFrameTransferRequests UpdateAndAppend( + const EditorWindowContentFrameContext& context, + ::XCEngine::UI::UIDrawData& drawData) override; + void RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext) override; + + const UIEditorShellInteractionFrame& GetShellFrame() const override; + const UIEditorShellInteractionState& GetShellInteractionState() const override; + + void SetExternalDockHostDropPreview( + const Widgets::UIEditorDockHostDropPreviewState& preview) override; + void ClearExternalDockHostDropPreview() override; + + bool TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const ::XCEngine::UI::UIPoint& point, + ::XCEngine::UI::UIPoint& outHotspot) const override; + UIEditorDockHostTabDropTarget ResolveDockTabDropTarget( + const ::XCEngine::UI::UIPoint& point) const override; + + bool HasHostedContentCapture() const override; + bool HasShellInteractiveCapture() const override; + bool HasInteractiveCapture() const override; + EditorWindowContentCursorKind GetHostedContentCursorKind() const override; + EditorWindowContentCursorKind GetDockCursorKind() const override; + + ::XCEngine::UI::UISize ResolveMinimumOuterSize() const override; + bool ShouldUseDetachedTitleBarTabStrip() const override; + std::string ResolveTabStripTitleText(std::string_view fallbackTitle) const override; + std::string ResolveDetachedWindowTitleText( + std::string_view fallbackWindowTitle) const override; + +private: + UIEditorWorkspaceController m_workspaceController = {}; + EditorShellRuntime m_shellRuntime = {}; + EditorWindowFrameOrchestrator m_frameOrchestrator = {}; +}; + +std::unique_ptr CreateEditorWorkspaceWindowContentController( + UIEditorWorkspaceController workspaceController); + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp index 9c86f62a..bd3acf92 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp @@ -3,6 +3,7 @@ #include "Bootstrap/EditorResources.h" #include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowContentController.h" #include "Platform/Win32/EditorWindowFrameDriver.h" #include "Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h" #include "Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h" @@ -62,13 +63,13 @@ EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( - UIEditorWorkspaceController workspaceController, + std::unique_ptr contentController, const CreateParams& params) { auto windowPtr = std::make_unique( params.windowId, params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title, params.primary, - std::move(workspaceController)); + std::move(contentController)); EditorWindow* const rawWindow = windowPtr.get(); m_windows.push_back(std::move(windowPtr)); @@ -188,7 +189,7 @@ void EditorWindowHostRuntime::RenderAllWindows( *window, m_editorContext, globalTabDragActive); - workspaceCoordinator.CommitWindowProjection(*window); + workspaceCoordinator.RefreshWindowPresentation(*window); if (!transferRequests.HasPendingRequests()) { continue; } diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h index ee2e8d51..fefd5131 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h @@ -2,6 +2,8 @@ #include "Platform/Win32/EditorWindowManager.h" +#include + #include #include #include @@ -11,6 +13,7 @@ namespace XCEngine::UI::Editor::App { class EditorContext; class EditorWindow; +class EditorWindowContentController; class EditorWindowLifecycleCoordinator; class EditorWindowWorkspaceCoordinator; @@ -25,7 +28,7 @@ public: ~EditorWindowHostRuntime(); EditorWindow* CreateEditorWindow( - UIEditorWorkspaceController workspaceController, + std::unique_ptr contentController, const CreateParams& params); void BindLifecycleCoordinator(EditorWindowLifecycleCoordinator& lifecycleCoordinator); void HandlePendingNativeWindowCreated(HWND hwnd); diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp index e370162c..0c97b74a 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp @@ -1,6 +1,7 @@ #include "Platform/Win32/EditorWindowManager.h" #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowContentController.h" #include "Platform/Win32/WindowManager/EditorWindowHostRuntime.h" #include "Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h" #include "Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h" @@ -30,10 +31,10 @@ EditorWindowManager::EditorWindowManager( EditorWindowManager::~EditorWindowManager() = default; EditorWindow* EditorWindowManager::CreateEditorWindow( - UIEditorWorkspaceController workspaceController, + std::unique_ptr contentController, const CreateParams& params) { EditorWindow* const window = - m_hostRuntime->CreateEditorWindow(std::move(workspaceController), params); + m_hostRuntime->CreateEditorWindow(std::move(contentController), params); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); } diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp index 6157279f..13d85593 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp @@ -1,8 +1,8 @@ #include "Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h" -#include "Composition/EditorShellRuntime.h" #include "Platform/Win32/BorderlessWindowChrome.h" #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowFrameDriver.h" #include "Platform/Win32/EditorWindowPointerCapture.h" #include "Platform/Win32/WindowManager/EditorWindowHostRuntime.h" #include "Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h" @@ -61,6 +61,18 @@ struct EditorWindowMessageDispatcher::DispatchContext { EditorWindow& window; }; +void EditorWindowMessageDispatcher::RenderAndHandleWindowFrame(const DispatchContext& context) { + EditorWindowFrameTransferRequests transferRequests = EditorWindowFrameDriver::DriveFrame( + context.window, + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive()); + if (transferRequests.HasPendingRequests()) { + context.workspaceCoordinator.HandleWindowFrameTransferRequests( + context.window, + std::move(transferRequests)); + } +} + bool EditorWindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) { if (context.window.IsTrackingMouseLeave()) { return true; @@ -84,8 +96,8 @@ bool EditorWindowMessageDispatcher::TryHandleChromeHoverConsumption( LRESULT& outResult) { if (!CanConsumeEditorWindowChromeHover( context.window.GetPointerCaptureOwner(), - context.window.GetShellRuntime().HasShellInteractiveCapture(), - context.window.GetShellRuntime().HasHostedContentCapture())) { + context.window.HasShellInteractiveCapture(), + context.window.HasHostedContentCapture())) { return false; } @@ -383,7 +395,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( context.window.OnDpiChanged( static_cast(LOWORD(wParam)), *reinterpret_cast(lParam)); - context.window.InvalidateHostWindow(); + RenderAndHandleWindowFrame(context); outResult = 0; return true; case WM_ENTERSIZEMOVE: @@ -392,7 +404,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( return true; case WM_EXITSIZEMOVE: context.window.OnExitSizeMove(); - context.window.InvalidateHostWindow(); + RenderAndHandleWindowFrame(context); outResult = 0; return true; case WM_SIZE: @@ -400,7 +412,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( context.window.OnResize( static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); - context.window.InvalidateHostWindow(); + RenderAndHandleWindowFrame(context); } outResult = 0; return true; @@ -409,7 +421,14 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( outResult = 0; return true; case WM_PAINT: - context.window.OnPaintMessage(); + if (EditorWindowFrameTransferRequests transferRequests = context.window.OnPaintMessage( + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive()); + transferRequests.HasPendingRequests()) { + context.workspaceCoordinator.HandleWindowFrameTransferRequests( + context.window, + std::move(transferRequests)); + } outResult = 0; return true; case WM_ERASEBKGND: diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp index ac5d61df..e3eba85b 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp @@ -2,6 +2,7 @@ #include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWorkspaceWindowContentController.h" #include "Platform/Win32/WindowManager/EditorWindowHostRuntime.h" #include "Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h" @@ -81,20 +82,28 @@ std::string DescribeWindowSetState(const UIEditorWindowWorkspaceSet& windowSet) } UIEditorWindowWorkspaceSet BuildLiveWindowWorkspaceSet( - const EditorWindowHostRuntime& hostRuntime) { + const EditorWindowHostRuntime& hostRuntime, + std::string_view excludedWindowId = {}, + bool includeClosingWindows = false) { UIEditorWindowWorkspaceSet windowSet = {}; for (const std::unique_ptr& window : hostRuntime.GetWindows()) { if (window == nullptr || window->GetHwnd() == nullptr || - window->IsClosing()) { + window->GetWindowId() == excludedWindowId || + (!includeClosingWindows && window->IsClosing())) { continue; } UIEditorWindowWorkspaceState state = {}; state.windowId = std::string(window->GetWindowId()); - state.workspace = window->GetWorkspaceController().GetWorkspace(); - state.session = window->GetWorkspaceController().GetSession(); + const UIEditorWorkspaceController* const workspaceController = + window->TryGetWorkspaceController(); + if (workspaceController == nullptr) { + continue; + } + state.workspace = workspaceController->GetWorkspace(); + state.session = workspaceController->GetSession(); if (window->IsPrimary()) { windowSet.primaryWindowId = state.windowId; } @@ -128,53 +137,23 @@ void EditorWindowWorkspaceCoordinator::BindLifecycleCoordinator( } void EditorWindowWorkspaceCoordinator::RegisterExistingWindow(EditorWindow& window) { - std::string error = {}; - if (!m_workspaceStore.RegisterWindowProjection( - window.GetWindowId(), - window.IsPrimary(), - window.GetWorkspaceController(), - error)) { - LogRuntimeTrace( - "window", - "failed to register window '" + std::string(window.GetWindowId()) + - "' in workspace store: " + error); - return; - } - RefreshWindowTitle(window); } -void EditorWindowWorkspaceCoordinator::CommitWindowProjection(EditorWindow& window) { - std::string error = {}; - if (!m_workspaceStore.CommitWindowProjection( - window.GetWindowId(), - window.GetWorkspaceController(), - error)) { - if (m_workspaceStore.RegisterWindowProjection( - window.GetWindowId(), - window.IsPrimary(), - window.GetWorkspaceController(), - error)) { - RefreshWindowTitle(window); - return; - } - - LogRuntimeTrace( - "window", - "failed to commit window projection for '" + std::string(window.GetWindowId()) + - "': " + error); - return; - } - +void EditorWindowWorkspaceCoordinator::RefreshWindowPresentation(EditorWindow& window) const { RefreshWindowTitle(window); } bool EditorWindowWorkspaceCoordinator::IsPrimaryWindowId(std::string_view windowId) const { - return m_workspaceStore.GetWindowSet().primaryWindowId == windowId; + if (const EditorWindow* window = m_hostRuntime.FindWindow(windowId); window != nullptr) { + return window->IsPrimary(); + } + + return false; } std::string EditorWindowWorkspaceCoordinator::DescribeWindowSet() const { - return DescribeWindowSetState(m_workspaceStore.GetWindowSet()); + return DescribeWindowSetState(BuildLiveWindowWorkspaceSet(m_hostRuntime, {}, true)); } void EditorWindowWorkspaceCoordinator::RemoveWindowProjection( @@ -185,11 +164,15 @@ void EditorWindowWorkspaceCoordinator::RemoveWindowProjection( "workspace remove begin windowId='" + std::string(windowId) + "' primaryArg=" + (primary ? "1" : "0") + " stateBefore=" + DescribeWindowSet()); - m_workspaceStore.RemoveWindow(windowId, primary); + + const UIEditorWindowWorkspaceSet stateAfter = + primary + ? UIEditorWindowWorkspaceSet{} + : BuildLiveWindowWorkspaceSet(m_hostRuntime, windowId, true); LogRuntimeTrace( "window-close", "workspace remove end windowId='" + std::string(windowId) + - "' stateAfter=" + DescribeWindowSet()); + "' stateAfter=" + DescribeWindowSetState(stateAfter)); } UIEditorWindowWorkspaceController @@ -224,6 +207,10 @@ void EditorWindowWorkspaceCoordinator::RefreshWindowTitle(EditorWindow& window) return; } + if (window.TryGetWorkspaceController() == nullptr) { + return; + } + const std::wstring title = BuildWindowTitle(window.GetWorkspaceController()); if (title == window.GetTitle()) { return; @@ -354,7 +341,8 @@ bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( } if (m_hostRuntime.CreateEditorWindow( - BuildWorkspaceControllerForWindow(entry), + CreateEditorWorkspaceWindowContentController( + BuildWorkspaceControllerForWindow(entry)), createParams) == nullptr) { for (const ExistingWindowSnapshot& snapshot : existingWindowSnapshots) { restoreWindowSnapshot(snapshot); @@ -411,7 +399,6 @@ bool EditorWindowWorkspaceCoordinator::CommitWindowWorkspaceMutation( return false; } - m_workspaceStore.ReplaceWindowSet(nextWindowSet); return true; }