diff --git a/editor/AGENTS.md b/editor/AGENTS.md index 5fdf3739..6ffbd1df 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -88,7 +88,9 @@ EditorContext::BuildWorkspaceController() -> EditorWindowContentFactory::CreateWorkspaceContentController(...) -> EditorWindowRenderRuntimeFactory::CreateWindowRenderRuntime() -> EditorWindowRuntimeController(EditorContext, contentController, renderRuntime) - -> EditorWindowHostRuntime::CreateHostWindow(runtimeController, ...) + -> EditorWindowInstance(runtimeController) + -> EditorWindowHostRuntime::CreateHostWindow(windowInstance, ...) + -> Win32 EditorWindow native peer ``` Keep authoritative window bootstrap in `EditorWindowSystem`; do not move it @@ -104,17 +106,19 @@ must not own the editor frame loop or fetch `EditorContext`. `EditorWindowRuntimeController` lives under `editor/app/Windowing/Runtime`. App windowing creates it from `EditorContext` plus a workspace or utility content controller plus a backend-neutral `EditorWindowRenderRuntime`, then -passes it to the native host. The Win32 host stores and calls the runtime -controller, but it does not create content controllers and it does not receive -`EditorContext`. +wraps it in app-owned `EditorWindowInstance`. The Win32 host receives the +instance only as an `EditorHostWindow` owner/native-peer binding; it must not +store or call `EditorWindowRuntimeController` directly, create content +controllers, or receive `EditorContext`. Concrete renderer selection is composed above both app windowing and the native host. `Application` creates the current `D3D12EditorWindowRenderRuntimeFactory` and passes it into `EditorWindowManager`; app windowing asks the factory for backend-neutral -`Rendering::Host::EditorWindowRenderRuntime` instances. The Win32 host receives -runtime controllers that already contain their render runtime; it must not -create concrete render backends. `EditorWindowRuntimeController` may call +`Rendering::Host::EditorWindowRenderRuntime` instances. `EditorWindowInstance` +owns runtime controllers that already contain their render runtime; the Win32 +host must not create concrete render backends or own render runtimes. +`EditorWindowRuntimeController` may call `EditorWindowRenderRuntime`, `UiTextureHost`, and `ViewportRenderHost`, but it must not include `Rendering/D3D12` headers, `windows.h`, or HWND types. @@ -124,8 +128,9 @@ The host contract direction is: EditorWindowManager / coordinators -> ask EditorWindowRenderRuntimeFactory for EditorWindowRenderRuntime -> create EditorWindowRuntimeController - -> EditorWindowHostRuntime::CreateHostWindow(runtimeController, ...) - -> Win32 EditorWindow owns HWND and delegates frame work to the runtime + -> wrap it in app-owned EditorWindowInstance + -> EditorWindowHostRuntime::CreateHostWindow(windowInstance, ...) + -> Win32 EditorWindow owns HWND/input/chrome and calls back through native-peer contract ``` ## Window Authority Model @@ -193,9 +198,10 @@ content controllers. - Workspace windows use `EditorWorkspaceWindowContentController`. - Utility windows use `EditorUtilityWindowContentController`. - App windowing creates content through `EditorWindowContentFactory`. -- App windowing wraps content in `EditorWindowRuntimeController`. -- The concrete host validates `EditorWindowContentCapabilities` through the - runtime controller when a native host window is created. +- App windowing wraps content in `EditorWindowRuntimeController`, then owns that + runtime through `EditorWindowInstance`. +- The concrete host creates a native peer for an existing app-owned + `EditorHostWindow`; it does not receive or validate runtime controllers. Utility windows are descriptor driven through `EditorUtilityWindowDescriptor`, `EditorUtilityWindowRegistry`, and `CreateEditorUtilityWindowPanel(...)`. @@ -238,7 +244,8 @@ logic. - Do not spread D3D12 host types into app windowing content, coordinator, frame-transfer, runtime-controller, or public host-contract headers. - Do not let Win32 host code create workspace or utility content directly. - It should receive an `EditorWindowRuntimeController` created by app windowing. + It should receive an app-owned `EditorHostWindow`/`EditorWindowInstance` + created by app windowing, not an `EditorWindowRuntimeController`. - Use direct authoritative workspace binding for ordinary in-window workspace mutations. - Use synchronization plans for operations that create, close, replace, or @@ -252,8 +259,8 @@ owns the reusable window authority, synchronization planner, workspace store, and presentation projection under `XCEditor/Windowing`. `XCUIEditorAppWindowing` owns the app-internal content controller factory, runtime controllers, coordinators, frame transfer flow, host contracts, and manager. The Win32 host -is the concrete native adapter and creates native host windows from runtime -controllers supplied by app windowing. +is the concrete native adapter and creates native host windows for app-owned +`EditorWindowInstance` objects supplied by app windowing. The framework/app compile boundary is also enforced. `XCUIEditorLib` does not receive `editor/app` includes, and `XCUIEditorApp` does not directly compile @@ -265,7 +272,15 @@ The main windowing debt that was previously in the Win32 host has been cut: frame iteration, immediate frame driving, content-controller construction, and `EditorContext` ownership now live in app windowing. `EditorWindowHostCoordinator` does not expose `GetEditorContext()`, and the native host receives an -`EditorWindowRuntimeController` rather than raw workspace/utility content. +app-owned `EditorWindowInstance` rather than raw workspace/utility content or a +runtime controller. + +The native-peer ownership cut has also been made: `EditorWindowManager` owns +`EditorWindowInstance` objects, each instance owns its +`EditorWindowRuntimeController`, and `editor/app/Platform/Win32/Windowing` +`EditorWindow` is a native peer for HWND/input/chrome/message integration. The +Win32 platform code must stay free of +`Windowing/Runtime/EditorWindowRuntimeController.h`. The renderer ownership cut has also been made: `XCUIEditorAppWindowing` no longer owns the concrete D3D12 window render loop or Win32 surface setup. @@ -285,12 +300,13 @@ back into `editor/app/Windowing`, and do not move renderer factory ownership back into the Win32 host. Do not take another architecture cut just to keep carving this area. After the -render-runtime boundary, the next default improvement should be boundary +native-peer ownership boundary, the next default improvement should be boundary guardrails, such as checks that keep `editor/app/Windowing` free of -`Rendering/D3D12`, `windows.h`, and HWND types, and keep -`editor/app/Platform/Win32` free of `Rendering/D3D12`. Consider a second -structural cut only when there is concrete pressure, such as another native host, -another render backend, or headless editor/window tests. +`Rendering/D3D12`, `Platform/Win32`, `windows.h`, and HWND types, and keep +`editor/app/Platform/Win32` free of `Rendering/D3D12` and +`Windowing/Runtime/EditorWindowRuntimeController.h`. Consider another structural +cut only when there is concrete pressure, such as another native host, another +render backend, or headless editor/window tests. ## Validation @@ -336,6 +352,7 @@ Start with these files for editor/windowing work: - `editor/app/Windowing/Frame/EditorWindowTransferRequests.h` - `editor/app/Windowing/Runtime/EditorWindowRuntimeController.*` - `editor/app/Windowing/Runtime/EditorWindowScreenshotController.*` +- `editor/app/Windowing/EditorWindowInstance.*` - `editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.*` - `editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.*` - `editor/app/Windowing/EditorWindowManager.*` @@ -363,6 +380,12 @@ Start with these files for editor/windowing work: `EditorWindowRuntimeController`, frame driving, frame transfer, host contracts, and `EditorWindowManager` are under `editor/app/Windowing`; Win32 remains the native adapter and no longer exposes `EditorContext`. +- The native-peer ownership cut is sealed: app windowing now owns live + `EditorWindowInstance` objects, those instances own + `EditorWindowRuntimeController`, and Win32 `EditorWindow` only owns the + native peer side for HWND/input/chrome/message work. `CreateHostWindow(...)` + binds a native peer to an existing `EditorHostWindow`; it does not receive a + runtime controller. - The concrete renderer cut is sealed: app windowing consumes `Rendering::Host::EditorWindowRenderRuntime` and `Rendering::Host::EditorWindowRenderRuntimeFactory`, while the current D3D12 diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index f6fd2aa6..9eb3cdc0 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -177,6 +177,7 @@ target_link_libraries(XCUIEditorLib PUBLIC xcui_editor_apply_common_target_settings(XCUIEditorLib PUBLIC) set(XCUI_EDITOR_APP_WINDOWING_SOURCES + app/Windowing/EditorWindowInstance.cpp app/Windowing/EditorWindowManager.cpp app/Windowing/Coordinator/EditorWindowLifecycleCoordinator.cpp app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp diff --git a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp index c77d0f3d..ca526344 100644 --- a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp +++ b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.cpp @@ -2,8 +2,8 @@ #include "Platform/Win32/Windowing/EditorWindow.h" #include "Platform/Win32/Windowing/EditorWindowSupport.h" +#include "Windowing/Content/EditorWindowContentController.h" #include "Windowing/Host/EditorWindowHostCoordinator.h" -#include "Windowing/Runtime/EditorWindowRuntimeController.h" #include #include @@ -271,7 +271,8 @@ bool EditorWindowChromeController::HandleSystemCommand( bool EditorWindowChromeController::HandleGetMinMaxInfo( const EditorWindow& window, LPARAM lParam) const { - const ::XCEngine::UI::UISize minimumOuterSize = window.m_runtime->ResolveMinimumOuterSize(); + const ::XCEngine::UI::UISize minimumOuterSize = + window.GetOwner().ResolveMinimumOuterSize(); return Host::HandleBorderlessWindowGetMinMaxInfo( window.GetHwnd(), lParam, @@ -348,7 +349,8 @@ bool EditorWindowChromeController::HandleResizePointerMove( return false; } - const ::XCEngine::UI::UISize minimumOuterSize = window.m_runtime->ResolveMinimumOuterSize(); + const ::XCEngine::UI::UISize minimumOuterSize = + window.GetOwner().ResolveMinimumOuterSize(); RECT targetRect = Host::ComputeBorderlessWindowResizeRect( GetBorderlessResizeInitialWindowRect(), GetBorderlessResizeInitialScreenPoint(), @@ -370,7 +372,7 @@ bool EditorWindowChromeController::HandleResizePointerMove( if (window.ApplyWindowResize(static_cast(width), static_cast(height))) { const auto immediateFrameBegin = std::chrono::steady_clock::now(); window.QueueCompletedImmediateFrame( - hostCoordinator.DriveImmediateWindowFrame(window)); + hostCoordinator.DriveImmediateWindowFrame(window.GetOwner())); const auto immediateFrameEnd = std::chrono::steady_clock::now(); MarkPredictedClientPixelSizePresented(); if (IsVerboseResizeTraceEnabled()) { @@ -678,24 +680,24 @@ Host::BorderlessWindowChromeHitTarget EditorWindowChromeController::HitTestChrom const float clientWidthDips = window.PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); const Host::BorderlessWindowChromeLayout layout = - ResolveChromeLayout(window, clientWidthDips); + ResolveChromeLayout(window.GetOwner(), clientWidthDips); return Host::HitTestBorderlessWindowChrome( layout, window.ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); } Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLayout( - const EditorWindow& window, + const EditorHostWindow& window, float clientWidthDips) const { float leadingOccupiedRight = 0.0f; if (ShouldUseDetachedTitleBarTabStrip(window)) { const EditorWindowTitleBarBinding* titleBarBinding = - window.m_runtime->TryGetTitleBarBinding(); + window.TryGetTitleBarBinding(); leadingOccupiedRight = ResolveDetachedTabWidth( titleBarBinding != nullptr ? titleBarBinding->ResolveTabStripTitleText("Panel") : std::string("Panel"), - &window.m_runtime->GetTextMeasurer()); + &window.GetTextMeasurer()); } return Host::BuildBorderlessWindowChromeLayout( @@ -705,8 +707,8 @@ Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLa } bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip( - const EditorWindow& window) const { - const EditorWindowTitleBarBinding* titleBarBinding = window.m_runtime->TryGetTitleBarBinding(); + const EditorHostWindow& window) const { + const EditorWindowTitleBarBinding* titleBarBinding = window.TryGetTitleBarBinding(); return !window.IsPrimary() && window.GetChromePolicy().allowDetachedTitleBarTabStrip && titleBarBinding != nullptr && @@ -714,7 +716,7 @@ bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip( } void EditorWindowChromeController::AppendChrome( - const EditorWindow& window, + const EditorHostWindow& window, UIDrawList& drawList, float clientWidthDips) const { const Host::BorderlessWindowChromeLayout layout = @@ -733,10 +735,10 @@ void EditorWindowChromeController::AppendChrome( if (!window.IsPrimary()) { if (useDetachedTitleBarTabStrip) { - if (window.m_runtime->GetTitleBarLogoIcon().IsValid()) { + if (window.GetTitleBarLogoIcon().IsValid()) { drawList.AddImage( BuildDetachedTitleLogoRect(layout), - window.m_runtime->GetTitleBarLogoIcon(), + window.GetTitleBarLogoIcon(), UIColor(1.0f, 1.0f, 1.0f, 1.0f)); } } else { @@ -744,17 +746,17 @@ void EditorWindowChromeController::AppendChrome( const float iconY = layout.titleBarRect.y + (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); - if (window.m_runtime->GetTitleBarLogoIcon().IsValid()) { + if (window.GetTitleBarLogoIcon().IsValid()) { drawList.AddImage( UIRect(iconX, iconY, kTitleBarLogoExtent, kTitleBarLogoExtent), - window.m_runtime->GetTitleBarLogoIcon(), + window.GetTitleBarLogoIcon(), UIColor(1.0f, 1.0f, 1.0f, 1.0f)); } drawList.AddText( UIPoint( iconX + - (window.m_runtime->GetTitleBarLogoIcon().IsValid() + (window.GetTitleBarLogoIcon().IsValid() ? (kTitleBarLogoExtent + kTitleBarLogoTextGap) : 4.0f), layout.titleBarRect.y + @@ -769,7 +771,7 @@ void EditorWindowChromeController::AppendChrome( ? std::string_view("XCEngine Editor") : cachedTitleText; const EditorWindowTitleBarBinding* titleBarBinding = - window.m_runtime->TryGetTitleBarBinding(); + window.TryGetTitleBarBinding(); return titleBarBinding != nullptr ? titleBarBinding->ResolveDetachedWindowTitleText(fallbackWindowTitle) : std::string(fallbackWindowTitle); @@ -782,10 +784,10 @@ void EditorWindowChromeController::AppendChrome( const float iconY = layout.titleBarRect.y + (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); - if (window.m_runtime->GetTitleBarLogoIcon().IsValid()) { + if (window.GetTitleBarLogoIcon().IsValid()) { drawList.AddImage( UIRect(iconX, iconY, kTitleBarLogoExtent, kTitleBarLogoExtent), - window.m_runtime->GetTitleBarLogoIcon(), + window.GetTitleBarLogoIcon(), UIColor(1.0f, 1.0f, 1.0f, 1.0f)); } @@ -795,12 +797,12 @@ void EditorWindowChromeController::AppendChrome( : std::string(cachedTitleText); const std::string frameRateText = window.GetChromePolicy().showFrameStats - ? window.m_runtime->BuildFrameRateText() + ? window.BuildFrameRateText() : std::string(); drawList.AddText( UIPoint( iconX + - (window.m_runtime->GetTitleBarLogoIcon().IsValid() + (window.GetTitleBarLogoIcon().IsValid() ? (kTitleBarLogoExtent + kTitleBarLogoTextGap) : 4.0f), layout.titleBarRect.y + @@ -812,7 +814,7 @@ void EditorWindowChromeController::AppendChrome( kBorderlessTitleBarFontSize); if (!frameRateText.empty()) { const float frameRateTextWidth = - window.m_runtime->GetTextMeasurer().MeasureTextWidth( + window.GetTextMeasurer().MeasureTextWidth( UIEditorTextMeasureRequest{ frameRateText, kBorderlessTitleBarFontSize }); @@ -898,7 +900,7 @@ bool EditorWindowChromeController::ApplyPredictedWindowRectTransition( SetPredictedClientPixelSize(static_cast(width), static_cast(height)); if (window.ApplyWindowResize(static_cast(width), static_cast(height))) { window.QueueCompletedImmediateFrame( - hostCoordinator.DriveImmediateWindowFrame(window)); + hostCoordinator.DriveImmediateWindowFrame(window.GetOwner())); MarkPredictedClientPixelSizePresented(); } SetWindowPos( diff --git a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h index 82fff432..5d2153d6 100644 --- a/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h +++ b/editor/app/Platform/Win32/Chrome/EditorWindowChromeController.h @@ -11,6 +11,7 @@ class UIDrawList; namespace XCEngine::UI::Editor::App { +class EditorHostWindow; class EditorWindow; class EditorWindowHostCoordinator; @@ -114,11 +115,11 @@ public: const EditorWindow& window, LPARAM lParam) const; Host::BorderlessWindowChromeLayout ResolveChromeLayout( - const EditorWindow& window, + const EditorHostWindow& window, float clientWidthDips) const; - bool ShouldUseDetachedTitleBarTabStrip(const EditorWindow& window) const; + bool ShouldUseDetachedTitleBarTabStrip(const EditorHostWindow& window) const; void AppendChrome( - const EditorWindow& window, + const EditorHostWindow& window, ::XCEngine::UI::UIDrawList& drawList, float clientWidthDips) const; void ApplyResizeCursorHoverPriority(); diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp index b5ac0e3c..a64a63e6 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.cpp @@ -4,7 +4,7 @@ #include "Platform/Win32/Windowing/EditorWindowSession.h" #include "Platform/Win32/Windowing/EditorWindowSupport.h" #include "Platform/Win32/Runtime/EditorWindowInputController.h" -#include "Windowing/Runtime/EditorWindowRuntimeController.h" +#include "Windowing/Content/EditorWindowContentController.h" #include #include #include @@ -80,27 +80,33 @@ EditorWindowScreenPoint EditorWindow::FromNativePoint(const POINT& screenPoint) return point; } -EditorWindow::EditorWindow( - std::string windowId, - std::wstring title, - EditorWindowCategory category, - EditorWindowChromePolicy chromePolicy, - bool primary, - std::unique_ptr runtimeController) - : m_session(std::make_unique( - std::move(windowId), - std::move(title), - category, - chromePolicy, - primary)) +EditorWindow::EditorWindow(EditorHostWindow& owner) + : m_owner(owner) + , m_session(std::make_unique( + std::string(owner.GetWindowId()), + std::wstring(owner.GetTitle()), + owner.IsWorkspaceWindow() + ? EditorWindowCategory::Workspace + : EditorWindowCategory::Utility, + owner.GetChromePolicy(), + owner.IsPrimary())) , m_chromeController(std::make_unique()) - , m_inputController(std::make_unique()) - , m_runtime(std::move(runtimeController)) {} + , m_inputController(std::make_unique()) {} -EditorWindow::~EditorWindow() = default; +EditorWindow::~EditorWindow() { + m_owner.DetachNativePeer(*this); +} + +EditorHostWindow& EditorWindow::GetOwner() { + return m_owner; +} + +const EditorHostWindow& EditorWindow::GetOwner() const { + return m_owner; +} std::string_view EditorWindow::GetWindowId() const { - return m_session->GetWindowId(); + return m_owner.GetWindowId(); } HWND EditorWindow::GetHwnd() const { @@ -112,35 +118,37 @@ bool EditorWindow::HasHwnd() const { } EditorWindowCategory EditorWindow::GetCategory() const { - return m_session->GetCategory(); + return m_owner.IsWorkspaceWindow() + ? EditorWindowCategory::Workspace + : EditorWindowCategory::Utility; } const EditorWindowChromePolicy& EditorWindow::GetChromePolicy() const { - return m_session->GetChromePolicy(); + return m_owner.GetChromePolicy(); } EditorWindowLifecycleState EditorWindow::GetLifecycleState() const { - return m_session->GetLifecycleState(); + return m_owner.GetLifecycleState(); } bool EditorWindow::IsPrimary() const { - return m_session->IsPrimary(); + return m_owner.IsPrimary(); } bool EditorWindow::IsWorkspaceWindow() const { - return m_session->IsWorkspaceWindow(); + return m_owner.IsWorkspaceWindow(); } bool EditorWindow::IsUtilityWindow() const { - return m_session->IsUtilityWindow(); + return m_owner.IsUtilityWindow(); } bool EditorWindow::IsClosing() const { - return m_session->IsClosing(); + return m_owner.IsClosing(); } bool EditorWindow::IsDestroyed() const { - return m_session->IsDestroyed(); + return m_owner.IsDestroyed(); } bool EditorWindow::HasLiveHostWindow() const { @@ -148,74 +156,37 @@ bool EditorWindow::HasLiveHostWindow() const { return hwnd != nullptr && IsWindow(hwnd); } -bool EditorWindow::IsRenderReady() const { - return m_runtime->IsReady(); -} - const std::wstring& EditorWindow::GetTitle() const { - return m_session->GetTitle(); + return m_owner.GetTitle(); } std::string_view EditorWindow::GetCachedTitleText() const { - return m_session->GetCachedTitleText(); + return m_owner.GetCachedTitleText(); } const EditorWorkspaceWindowProjection* EditorWindow::TryGetWorkspaceProjection() const { - const EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding(); - return workspaceBinding != nullptr - ? workspaceBinding->TryGetWorkspaceProjection() - : nullptr; + return m_owner.TryGetWorkspaceProjection(); } EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() { - return m_runtime->TryGetDockHostBinding(); + return m_owner.TryGetDockHostBinding(); } const EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() const { - return m_runtime->TryGetDockHostBinding(); + return m_owner.TryGetDockHostBinding(); } void EditorWindow::AttachHwnd(HWND hwnd) { m_session->AttachHwnd(hwnd); + m_owner.MarkNativeAttached(); } -void EditorWindow::MarkInitializing() { - m_session->MarkInitializing(); -} - -void EditorWindow::MarkRunning() { - m_session->MarkRunning(); -} - -void EditorWindow::MarkDestroyed() { - m_session->MarkDestroyed(); - m_inputController->ResetWindowState(); -} - -void EditorWindow::MarkClosing() { - m_session->MarkClosing(); -} - -void EditorWindow::SetPrimary(bool primary) { - m_session->SetPrimary(primary); -} - -void EditorWindow::SetTitle(std::wstring title) { - m_session->SetTitle(std::move(title)); -} - -void EditorWindow::ApplyHostWindowTitle() { +void EditorWindow::ApplyHostWindowTitle(const std::wstring& title) { if (HasLiveHostWindow()) { - SetWindowTextW(m_session->GetHwnd(), GetTitle().c_str()); + SetWindowTextW(m_session->GetHwnd(), title.c_str()); } } -void EditorWindow::RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) { - EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding(); - assert(workspaceBinding != nullptr); - workspaceBinding->RefreshWorkspaceProjection(std::move(projection)); -} - void EditorWindow::InvalidateHostWindow() const { if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr && IsWindow(hwnd)) { @@ -223,75 +194,41 @@ void EditorWindow::InvalidateHostWindow() const { } } -bool EditorWindow::InitializeRuntime( - const EditorHostWindowRuntimeInitializationParams& params) { - if (m_session->GetHwnd() == nullptr) { - LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); - return false; - } +void* EditorWindow::GetNativeWindowHandle() const { + return m_session->GetHwnd(); +} +void EditorWindow::PrepareRuntimeInitialization(EditorHostWindow& window) { + (void)window; Host::RefreshBorderlessWindowDwmDecorations(m_session->GetHwnd()); m_chromeController->Reset(); m_chromeController->SetWindowDpi(QueryWindowDpi(m_session->GetHwnd())); m_chromeController->InitializeWindowChrome(*this); - m_runtime->SetDpiScale(GetDpiScale()); - - std::ostringstream dpiTrace = {}; - dpiTrace << "initial dpi=" << m_chromeController->GetWindowDpi() - << " scale=" << GetDpiScale(); - LogRuntimeTrace("window", dpiTrace.str()); - - MarkInitializing(); - UINT clientWidth = 0u; - UINT clientHeight = 0u; - if (!QueryCurrentClientPixelSize(clientWidth, clientHeight)) { - clientWidth = 1u; - clientHeight = 1u; - } - const bool initialized = m_runtime->Initialize( - Rendering::Host::EditorWindowRenderRuntimeSurface{ - .nativeWindowHandle = m_session->GetHwnd(), - .widthPixels = clientWidth, - .heightPixels = clientHeight, - }, - params.repoRoot, - params.captureRoot, - params.autoCaptureOnStartup); - if (initialized) { - MarkRunning(); - } else { - m_session->MarkNativeAttached(); - } - return initialized; } -void EditorWindow::Shutdown() { +void EditorWindow::ShutdownNativeInteraction() { std::ostringstream trace = {}; - trace << "EditorWindow::Shutdown begin windowId='" << GetWindowId() + trace << "EditorWindow::ShutdownNativeInteraction begin windowId='" << GetWindowId() << "' hwnd=0x" << std::hex << std::uppercase << reinterpret_cast(GetHwnd()) << std::dec << " primary=" << (IsPrimary() ? 1 : 0) - << " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState()) - << " runtimeReady=" << (m_runtime->IsReady() ? 1 : 0); + << " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState()); LogRuntimeTrace("window-close", trace.str()); ForceReleasePointerCapture(); - if (m_runtime->IsReady()) { - m_runtime->Shutdown(); - } m_inputController->ClearPendingEvents(); m_chromeController->Reset(); LogRuntimeTrace( "window-close", - "EditorWindow::Shutdown end windowId='" + std::string(GetWindowId()) + "'"); + "EditorWindow::ShutdownNativeInteraction end windowId='" + + std::string(GetWindowId()) + "'"); } -void EditorWindow::ResetInteractionState() { +void EditorWindow::ResetNativeInteractionState() { ForceReleasePointerCapture(); m_inputController->ResetInteractionState(); - m_runtime->ResetInteractionState(); m_chromeController->ResetChromeState(); m_chromeController->EndBorderlessResize(); m_chromeController->EndBorderlessWindowDragRestore(); @@ -302,13 +239,15 @@ void EditorWindow::ResetInteractionState() { } bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { - if (!m_runtime->IsReady() || width == 0u || height == 0u) { + if (!m_owner.IsRenderReady() || width == 0u || height == 0u) { return false; } - return m_runtime->ApplyResize(width, height); + return m_owner.ApplyResize(width, height); } -bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { +bool EditorWindow::QueryCurrentClientPixelSize( + std::uint32_t& outWidth, + std::uint32_t& outHeight) const { outWidth = 0u; outHeight = 0u; const HWND hwnd = m_session->GetHwnd(); @@ -327,13 +266,19 @@ bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) return false; } - outWidth = static_cast(width); - outHeight = static_cast(height); + outWidth = static_cast(width); + outHeight = static_cast(height); return true; } -bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const { - if (m_chromeController->TryGetPredictedClientPixelSize(outWidth, outHeight)) { +bool EditorWindow::ResolveRenderClientPixelSize( + std::uint32_t& outWidth, + std::uint32_t& outHeight) const { + UINT nativeWidth = 0u; + UINT nativeHeight = 0u; + if (m_chromeController->TryGetPredictedClientPixelSize(nativeWidth, nativeHeight)) { + outWidth = nativeWidth; + outHeight = nativeHeight; return true; } @@ -378,7 +323,7 @@ bool EditorWindow::TryResolveDockTabDragHotspot( std::string_view panelId, const EditorWindowScreenPoint& screenPoint, EditorWindowScreenPoint& outHotspot) const { - const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding(); + const EditorWindowDockHostBinding* dockHostBinding = m_owner.TryGetDockHostBinding(); if (dockHostBinding == nullptr) { outHotspot = {}; return false; @@ -404,7 +349,7 @@ bool EditorWindow::TryResolveDockTabDragHotspot( bool EditorWindow::TryResolveDockTabDropTarget( const EditorWindowScreenPoint& screenPoint, UIEditorDockHostTabDropTarget& outTarget) const { - const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding(); + const EditorWindowDockHostBinding* dockHostBinding = m_owner.TryGetDockHostBinding(); if (dockHostBinding == nullptr) { outTarget = {}; return false; @@ -492,10 +437,10 @@ void EditorWindow::OnEnterSizeMove() { bool EditorWindow::OnExitSizeMove() { m_chromeController->EndInteractiveResize(); m_chromeController->ClearPredictedClientPixelSize(); - UINT width = 0u; - UINT height = 0u; + std::uint32_t width = 0u; + std::uint32_t height = 0u; if (QueryCurrentClientPixelSize(width, height)) { - ApplyWindowResize(width, height); + ApplyWindowResize(static_cast(width), static_cast(height)); return true; } return false; @@ -503,7 +448,7 @@ bool EditorWindow::OnExitSizeMove() { void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); - m_runtime->SetDpiScale(GetDpiScale()); + m_owner.SetDpiScale(GetDpiScale()); if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr) { const LONG windowWidth = suggestedRect.right - suggestedRect.left; @@ -516,10 +461,10 @@ void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { windowWidth, windowHeight, SWP_NOZORDER | SWP_NOACTIVATE); - UINT clientWidth = 0u; - UINT clientHeight = 0u; + std::uint32_t clientWidth = 0u; + std::uint32_t clientHeight = 0u; if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { - ApplyWindowResize(clientWidth, clientHeight); + ApplyWindowResize(static_cast(clientWidth), static_cast(clientHeight)); } Host::RefreshBorderlessWindowDwmDecorations(hwnd); } @@ -549,7 +494,7 @@ using ::XCEngine::UI::UIRect; namespace { -std::optional QueryCursorScreenPoint() { +std::optional QueryNativeCursorScreenPoint() { POINT screenPoint = {}; if (!GetCursorPos(&screenPoint)) { return std::nullopt; @@ -617,47 +562,6 @@ std::uint8_t ResolveExpectedShellCaptureButtons( } // namespace -EditorWindowFrameTransferRequests EditorWindow::RenderHostFrame( - bool globalTabDragActive) { - if (!m_runtime->IsReady() || m_session->GetHwnd() == nullptr) { - return {}; - } - - UINT pixelWidth = 0u; - UINT pixelHeight = 0u; - if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { - return {}; - } - - const float width = PixelsToDips(static_cast(pixelWidth)); - const float height = PixelsToDips(static_cast(pixelHeight)); - const UIRect workspaceBounds = ResolveWorkspaceBounds(width, height); - - UIDrawData drawData = {}; - UIDrawList& backgroundDrawList = drawData.EmplaceDrawList("XCEditorWindow.Surface"); - backgroundDrawList.AddFilledRect( - UIRect(0.0f, 0.0f, width, height), - kShellSurfaceColor); - - EditorWindowFrameTransferRequests transferRequests = {}; - if (m_runtime->IsEditorContextValid()) { - transferRequests = - RenderRuntimeFrame(globalTabDragActive, workspaceBounds, drawData); - } else { - UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid"); - m_runtime->AppendInvalidFrame(invalidDrawList); - } - - UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome"); - m_chromeController->AppendChrome(*this, windowChromeDrawList, width); - - const auto presentResult = m_runtime->Present(drawData); - if (!presentResult.warning.empty()) { - LogRuntimeTrace("present", presentResult.warning); - } - return transferRequests; -} - void EditorWindow::ValidateHostFrame() const { if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr && IsWindow(hwnd)) { @@ -687,8 +591,11 @@ bool EditorWindow::ConsumeSkipNextSteadyStateFrame() { return m_chromeController->ConsumeSkipNextSteadyStateFrame(); } -UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const { - if (m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this)) { +UIRect EditorWindow::ResolveWorkspaceBounds( + const EditorHostWindow& window, + float clientWidthDips, + float clientHeightDips) const { + if (ShouldUseDetachedTitleBarTabStrip(window)) { return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips); } @@ -700,43 +607,24 @@ UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientH (std::max)(0.0f, clientHeightDips - titleBarHeight)); } -EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( - bool globalTabDragActive, - const UIRect& workspaceBounds, - UIDrawData& drawData) { - SyncShellCapturedPointerButtonsFromSystemState(); - std::vector frameEvents = m_inputController->TakePendingEvents(); - const bool useDetachedTitleBarTabStrip = - m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this); - m_runtime->PrepareEditorContext(); - const auto frameContext = m_runtime->BeginFrame(); - if (!frameContext.warning.empty()) { - LogRuntimeTrace("viewport", frameContext.warning); - } - - const EditorWindowFrameTransferRequests transferRequests = - m_runtime->UpdateAndAppend( - workspaceBounds, - frameEvents, - QueryCursorScreenPoint(), - m_session->IsPrimary(), - globalTabDragActive, - useDetachedTitleBarTabStrip, - drawData); - if (frameContext.canRenderViewports) { - m_runtime->RenderRequestedViewports(frameContext.renderContext); - } - - ApplyShellRuntimePointerCapture(); - ApplyCurrentCursor(); - return transferRequests; +bool EditorWindow::ShouldUseDetachedTitleBarTabStrip( + const EditorHostWindow& window) const { + return m_chromeController->ShouldUseDetachedTitleBarTabStrip(window); } -void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { +std::vector EditorWindow::TakePendingInputEvents() { + return m_inputController->TakePendingEvents(); +} + +std::optional EditorWindow::QueryCursorScreenPoint() const { + return QueryNativeCursorScreenPoint(); +} + +void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState( + const UIEditorShellInteractionState& shellState) { m_inputController->SyncInputModifiersFromSystemState(); - const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons( - m_runtime->GetShellInteractionState()); + const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons(shellState); if (expectedButtons == 0u || m_inputController->HasPendingPointerStateReconciliationEvent()) { return; @@ -750,9 +638,9 @@ void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { QueueSyntheticPointerStateSyncEvent(modifiers); } -void EditorWindow::ApplyShellRuntimePointerCapture() { +void EditorWindow::ApplyShellRuntimePointerCapture(const EditorHostWindow& window) { const EditorWindowInputFeedbackBinding* inputFeedbackBinding = - m_runtime->TryGetInputFeedbackBinding(); + window.TryGetInputFeedbackBinding(); if (inputFeedbackBinding != nullptr && inputFeedbackBinding->HasShellInteractiveCapture()) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell); @@ -774,6 +662,13 @@ void EditorWindow::ApplyShellRuntimePointerCapture() { } } +void EditorWindow::AppendChrome( + const EditorHostWindow& window, + UIDrawList& drawList, + float clientWidthDips) const { + m_chromeController->AppendChrome(window, drawList, clientWidthDips); +} + } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { @@ -820,7 +715,7 @@ bool EditorWindow::ApplyCurrentCursor() const { bool EditorWindow::HasInteractiveCaptureState() const { const EditorWindowInputFeedbackBinding* inputFeedbackBinding = - m_runtime->TryGetInputFeedbackBinding(); + m_owner.TryGetInputFeedbackBinding(); return (inputFeedbackBinding != nullptr && inputFeedbackBinding->HasInteractiveCapture()) || m_chromeController->IsBorderlessWindowDragRestoreArmed() || m_chromeController->IsBorderlessResizeActive() || @@ -855,7 +750,7 @@ void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if (!ShouldStartImmediateUIEditorShellPointerCapture( - m_runtime->GetShellFrame(), + m_owner.GetShellFrame(), clientPoint)) { return; } @@ -949,7 +844,7 @@ bool EditorWindow::IsPointerInsideClientArea() const { LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { const EditorWindowInputFeedbackBinding* inputFeedbackBinding = - m_runtime->TryGetInputFeedbackBinding(); + m_owner.TryGetInputFeedbackBinding(); const Host::BorderlessWindowResizeEdge borderlessResizeEdge = m_chromeController->IsBorderlessResizeActive() ? m_chromeController->GetBorderlessResizeEdge() diff --git a/editor/app/Platform/Win32/Windowing/EditorWindow.h b/editor/app/Platform/Win32/Windowing/EditorWindow.h index 1d73f68a..467d3438 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindow.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindow.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -55,19 +56,12 @@ class EditorWindowHostRuntime; class EditorWindowInputController; class EditorWindowLifecycleCoordinator; class EditorWindowMessageDispatcher; -class EditorWindowRuntimeController; class EditorWindowWorkspaceCoordinator; class EditorWindowSession; -class EditorWindow final : public EditorHostWindow { +class EditorWindow final : public EditorWindowNativePeer { public: - EditorWindow( - std::string windowId, - std::wstring title, - EditorWindowCategory category, - EditorWindowChromePolicy chromePolicy, - bool primary, - std::unique_ptr runtimeController); + explicit EditorWindow(EditorHostWindow& owner); ~EditorWindow(); EditorWindow(const EditorWindow&) = delete; @@ -75,23 +69,53 @@ public: EditorWindow(EditorWindow&&) = delete; EditorWindow& operator=(EditorWindow&&) = delete; - std::string_view GetWindowId() const override; + EditorHostWindow& GetOwner(); + const EditorHostWindow& GetOwner() const; + std::string_view GetWindowId() const; HWND GetHwnd() const; bool HasHwnd() const; EditorWindowCategory GetCategory() const; const EditorWindowChromePolicy& GetChromePolicy() const; - EditorWindowLifecycleState GetLifecycleState() const override; - bool IsPrimary() const override; - bool IsWorkspaceWindow() const override; - bool IsUtilityWindow() const override; - bool IsClosing() const override; - bool IsDestroyed() const override; + EditorWindowLifecycleState GetLifecycleState() const; + bool IsPrimary() const; + bool IsWorkspaceWindow() const; + bool IsUtilityWindow() const; + bool IsClosing() const; + bool IsDestroyed() const; bool HasLiveHostWindow() const override; - const std::wstring& GetTitle() const override; + const std::wstring& GetTitle() const; std::string_view GetCachedTitleText() const; - const EditorWorkspaceWindowProjection* TryGetWorkspaceProjection() const override; - EditorWindowDockHostBinding* TryGetDockHostBinding() override; - const EditorWindowDockHostBinding* TryGetDockHostBinding() const override; + const EditorWorkspaceWindowProjection* TryGetWorkspaceProjection() const; + EditorWindowDockHostBinding* TryGetDockHostBinding(); + const EditorWindowDockHostBinding* TryGetDockHostBinding() const; + void* GetNativeWindowHandle() const override; + bool QueryCurrentClientPixelSize( + std::uint32_t& outWidth, + std::uint32_t& outHeight) const override; + bool ResolveRenderClientPixelSize( + std::uint32_t& outWidth, + std::uint32_t& outHeight) const override; + float GetDpiScale() const override; + float PixelsToDips(float pixels) const override; + ::XCEngine::UI::UIRect ResolveWorkspaceBounds( + const EditorHostWindow& window, + float clientWidthDips, + float clientHeightDips) const override; + bool ShouldUseDetachedTitleBarTabStrip( + const EditorHostWindow& window) const override; + std::vector<::XCEngine::UI::UIInputEvent> TakePendingInputEvents() override; + std::optional QueryCursorScreenPoint() const override; + void SyncShellCapturedPointerButtonsFromSystemState( + const UIEditorShellInteractionState& shellState) override; + void ApplyShellRuntimePointerCapture(const EditorHostWindow& window) override; + bool ApplyCurrentCursor() const override; + void AppendChrome( + const EditorHostWindow& window, + ::XCEngine::UI::UIDrawList& drawList, + float clientWidthDips) const override; + void PrepareRuntimeInitialization(EditorHostWindow& window) override; + void ShutdownNativeInteraction() override; + void ResetNativeInteractionState() override; ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips( const EditorWindowScreenPoint& screenPoint) const override; @@ -102,39 +126,23 @@ private: friend class EditorWindowLifecycleCoordinator; friend class EditorWindowWorkspaceCoordinator; - bool IsRenderReady() const override; bool TryResolveDockTabDragHotspot( std::string_view nodeId, std::string_view panelId, const EditorWindowScreenPoint& screenPoint, - EditorWindowScreenPoint& outHotspot) const override; + EditorWindowScreenPoint& outHotspot) const; bool TryResolveDockTabDropTarget( const EditorWindowScreenPoint& screenPoint, - UIEditorDockHostTabDropTarget& outTarget) const override; + UIEditorDockHostTabDropTarget& outTarget) const; void InvalidateHostWindow() const override; void AttachHwnd(HWND hwnd); - void MarkInitializing(); - void MarkRunning(); - void MarkDestroyed() override; - void MarkClosing() override; - void SetPrimary(bool primary) override; - void SetTitle(std::wstring title) override; - void ApplyHostWindowTitle() override; - void RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) override; - - bool InitializeRuntime( - const EditorHostWindowRuntimeInitializationParams& params) override; - void Shutdown() override; - void ResetInteractionState() override; + void ApplyHostWindowTitle(const std::wstring& title) override; bool TryGetHostScreenRect(EditorWindowScreenRect& outRect) const override; void SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) override; void FocusHostWindow() override; void PostCloseToHost() override; void DestroyHostWindow() override; - - EditorWindowFrameTransferRequests RenderHostFrame( - bool globalTabDragActive) override; void ValidateHostFrame() const override; void QueueCompletedImmediateFrame( EditorWindowFrameTransferRequests transferRequests); @@ -147,7 +155,6 @@ private: bool OnExitSizeMove(); void OnDpiChanged(UINT dpi, const RECT& suggestedRect); - bool ApplyCurrentCursor() const; bool HasInteractiveCaptureState() const; bool OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const; void AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) override; @@ -165,32 +172,18 @@ private: const ::XCEngine::UI::UIInputModifiers& modifiers); void QueuePointerLeaveEvent(); void QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam); - void SyncShellCapturedPointerButtonsFromSystemState(); - bool ApplyWindowResize(UINT width, UINT height); - bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const; - bool ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const; - ::XCEngine::UI::UIRect ResolveWorkspaceBounds( - float clientWidthDips, - float clientHeightDips) const; static POINT ToNativePoint(const EditorWindowScreenPoint& screenPoint); static EditorWindowScreenPoint FromNativePoint(const POINT& screenPoint); - EditorWindowFrameTransferRequests RenderRuntimeFrame( - bool globalTabDragActive, - const ::XCEngine::UI::UIRect& workspaceBounds, - ::XCEngine::UI::UIDrawData& drawData); bool IsPointerInsideClientArea() const; LPCWSTR ResolveCurrentCursorResource() const; - float GetDpiScale() const; - float PixelsToDips(float pixels) const; ::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const; - void ApplyShellRuntimePointerCapture(); static bool IsVerboseRuntimeTraceEnabled(); + EditorHostWindow& m_owner; std::unique_ptr m_session = {}; std::unique_ptr m_chromeController = {}; std::unique_ptr m_inputController = {}; - std::unique_ptr m_runtime = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp index c6463ed9..400041a8 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -5,7 +5,6 @@ #include "Platform/Win32/Windowing/EditorFloatingWindowPlacement.h" #include "Platform/Win32/Windowing/EditorWindow.h" #include "Windowing/Host/EditorWindowHostCoordinator.h" -#include "Windowing/Runtime/EditorWindowRuntimeController.h" #include @@ -39,10 +38,11 @@ std::string DescribeHostWindows( continue; } - stream << window->GetWindowId() + stream << window->GetOwner().GetWindowId() << "{hwnd=" << DescribeHwnd(window->GetHwnd()) - << ",primary=" << (window->IsPrimary() ? '1' : '0') - << ",state=" << GetEditorWindowLifecycleStateName(window->GetLifecycleState()) + << ",primary=" << (window->GetOwner().IsPrimary() ? '1' : '0') + << ",state=" << GetEditorWindowLifecycleStateName( + window->GetOwner().GetLifecycleState()) << '}'; } stream << ']'; @@ -61,33 +61,21 @@ EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; -EditorWindow* EditorWindowHostRuntime::CreateHostWindow( - std::unique_ptr runtimeController, +bool EditorWindowHostRuntime::CreateHostWindow( + EditorHostWindow& window, const EditorWindowCreateParams& params) { - if (runtimeController == nullptr) { - LogRuntimeTrace("window", "window creation failed: runtime controller is null"); - return nullptr; - } - - const EditorWindowContentCapabilities capabilities = - runtimeController->GetCapabilities(); - if (params.category == EditorWindowCategory::Workspace && !capabilities.workspace) { + if (params.category == EditorWindowCategory::Workspace && !window.IsWorkspaceWindow()) { LogRuntimeTrace("window", "workspace window creation rejected: content is not workspace"); - return nullptr; + return false; } - if (params.category == EditorWindowCategory::Utility && !capabilities.utilityPanel) { + if (params.category == EditorWindowCategory::Utility && !window.IsUtilityWindow()) { LogRuntimeTrace("window", "utility window creation rejected: content is not utility"); - return nullptr; + return false; } - auto windowPtr = std::make_unique( - params.windowId, - params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title, - params.category, - params.chromePolicy, - params.primary, - std::move(runtimeController)); + auto windowPtr = std::make_unique(window); EditorWindow* const rawWindow = windowPtr.get(); + window.AttachNativePeer(*rawWindow); m_windows.push_back(std::move(windowPtr)); const auto eraseRawWindow = [this, rawWindow]() { @@ -119,7 +107,9 @@ EditorWindow* EditorWindowHostRuntime::CreateHostWindow( const HWND hwnd = CreateWindowExW( extendedWindowStyle, m_hostConfig.windowClassName, - rawWindow->GetTitle().c_str(), + rawWindow->GetTitle().empty() + ? L"XCEngine Editor" + : rawWindow->GetTitle().c_str(), windowStyle, initialX, initialY, @@ -131,8 +121,9 @@ EditorWindow* EditorWindowHostRuntime::CreateHostWindow( m_hostConfig.windowUserData); m_pendingCreateWindow = nullptr; if (hwnd == nullptr) { + rawWindow->GetOwner().DetachNativePeer(*rawWindow); eraseRawWindow(); - return nullptr; + return false; } if (!rawWindow->HasHwnd()) { @@ -142,11 +133,12 @@ EditorWindow* EditorWindowHostRuntime::CreateHostWindow( auto failWindowInitialization = [&](std::string_view message) { LogRuntimeTrace("window", std::string(message)); if (m_hostCoordinator != nullptr) { - m_hostCoordinator->AbortUnregisteredWindow(*rawWindow); + m_hostCoordinator->AbortUnregisteredWindow(window); } else { + rawWindow->GetOwner().DetachNativePeer(*rawWindow); eraseRawWindow(); } - return static_cast(nullptr); + return false; }; const HICON bigIcon = static_cast( @@ -177,7 +169,7 @@ EditorWindow* EditorWindowHostRuntime::CreateHostWindow( } if (!m_hostCoordinator->InitializeHostWindow( - *rawWindow, + window, EditorHostWindowRuntimeInitializationParams{ .repoRoot = m_repoRoot, .captureRoot = m_captureRoot, @@ -188,7 +180,7 @@ EditorWindow* EditorWindowHostRuntime::CreateHostWindow( ShowWindow(hwnd, params.showCommand); UpdateWindow(hwnd); - return rawWindow; + return true; } void EditorWindowHostRuntime::BindHostCoordinator( @@ -214,7 +206,7 @@ std::vector EditorWindowHostRuntime::GetWindows() { windows.reserve(m_windows.size()); for (const std::unique_ptr& window : m_windows) { if (window != nullptr) { - windows.push_back(window.get()); + windows.push_back(&window->GetOwner()); } } return windows; @@ -225,7 +217,7 @@ std::vector EditorWindowHostRuntime::GetWindows() const windows.reserve(m_windows.size()); for (const std::unique_ptr& window : m_windows) { if (window != nullptr) { - windows.push_back(window.get()); + windows.push_back(&window->GetOwner()); } } return windows; @@ -279,7 +271,8 @@ EditorHostWindow* EditorWindowHostRuntime::FindWindowFromScreenPoint( return nullptr; } - return FindWindow(GetAncestor(hitWindow, GA_ROOT)); + EditorWindow* const nativeWindow = FindWindow(GetAncestor(hitWindow, GA_ROOT)); + return nativeWindow != nullptr ? &nativeWindow->GetOwner() : nullptr; } const EditorHostWindow* EditorWindowHostRuntime::FindWindowFromScreenPoint( @@ -290,7 +283,7 @@ const EditorHostWindow* EditorWindowHostRuntime::FindWindowFromScreenPoint( void EditorWindowHostRuntime::ReapDestroyedWindows() { for (auto it = m_windows.begin(); it != m_windows.end();) { EditorWindow* const window = it->get(); - if (window == nullptr || !window->IsDestroyed()) { + if (window == nullptr || !window->GetOwner().IsDestroyed()) { ++it; continue; } @@ -301,7 +294,8 @@ void EditorWindowHostRuntime::ReapDestroyedWindows() { LogRuntimeTrace( "window-close", - "ReapDestroyedWindows erase windowId='" + std::string(window->GetWindowId()) + + "ReapDestroyedWindows erase windowId='" + + std::string(window->GetOwner().GetWindowId()) + "' hostBefore=" + DescribeHostWindows(m_windows)); it = m_windows.erase(it); LogRuntimeTrace( @@ -338,7 +332,7 @@ EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view windo } for (const std::unique_ptr& window : m_windows) { - if (window != nullptr && window->GetWindowId() == windowId) { + if (window != nullptr && window->GetOwner().GetWindowId() == windowId) { return window.get(); } } @@ -351,16 +345,18 @@ const EditorWindow* EditorWindowHostRuntime::FindWindowByIdImpl(std::string_view } EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) { - return FindWindowByIdImpl(windowId); + EditorWindow* const window = FindWindowByIdImpl(windowId); + return window != nullptr ? &window->GetOwner() : nullptr; } const EditorHostWindow* EditorWindowHostRuntime::FindWindowById(std::string_view windowId) const { - return FindWindowByIdImpl(windowId); + const EditorWindow* const window = FindWindowByIdImpl(windowId); + return window != nullptr ? &window->GetOwner() : nullptr; } EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() { for (const std::unique_ptr& window : m_windows) { - if (window != nullptr && window->IsPrimary()) { + if (window != nullptr && window->GetOwner().IsPrimary()) { return window.get(); } } diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h index 71c8dc7c..29bc2ac4 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h +++ b/editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.h @@ -13,8 +13,6 @@ namespace XCEngine::UI::Editor::App { class EditorWindow; -class EditorWindowRuntimeController; - class EditorWindowHostRuntime final : public EditorWindowHostRuntimeServices { public: EditorWindowHostRuntime( @@ -23,8 +21,8 @@ public: std::filesystem::path captureRoot); ~EditorWindowHostRuntime(); - EditorWindow* CreateHostWindow( - std::unique_ptr runtimeController, + bool CreateHostWindow( + EditorHostWindow& window, const EditorWindowCreateParams& params) override; void BindHostCoordinator(EditorWindowHostCoordinator& hostCoordinator) override; void HandlePendingNativeWindowCreated(HWND hwnd); diff --git a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp index bab4c5b0..99703834 100644 --- a/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp +++ b/editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.cpp @@ -4,8 +4,8 @@ #include "Platform/Win32/Chrome/EditorWindowChromeController.h" #include "Platform/Win32/Runtime/EditorWindowInputController.h" #include "Platform/Win32/Windowing/EditorWindow.h" -#include "Windowing/Runtime/EditorWindowRuntimeController.h" #include "Platform/Win32/Windowing/EditorWindowPointerCapture.h" +#include "Windowing/Content/EditorWindowContentController.h" #include "Windowing/Host/EditorWindowHostCoordinator.h" #include @@ -31,14 +31,14 @@ void EditorWindowMessageDispatcher::DispatchWindowFrameTransferRequests( const DispatchContext& context, const EditorWindowFrameTransferRequests& transferRequests) { context.hostCoordinator.DispatchWindowFrameTransferRequests( - context.window, + context.window.GetOwner(), transferRequests); } void EditorWindowMessageDispatcher::FinalizeImmediateFrame( const DispatchContext& context, const EditorWindowFrameTransferRequests& transferRequests) { - context.hostCoordinator.RefreshWindowPresentation(context.window); + context.hostCoordinator.RefreshWindowPresentation(context.window.GetOwner()); if (!transferRequests.HasPendingRequests()) { return; } @@ -60,7 +60,7 @@ void EditorWindowMessageDispatcher::FlushQueuedCompletedImmediateFrame( void EditorWindowMessageDispatcher::RenderAndHandleWindowFrame(const DispatchContext& context) { FinalizeImmediateFrame( context, - context.hostCoordinator.DriveImmediateWindowFrame(context.window)); + context.hostCoordinator.DriveImmediateWindowFrame(context.window.GetOwner())); } bool EditorWindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) { @@ -87,7 +87,7 @@ bool EditorWindowMessageDispatcher::TryHandleChromeHoverConsumption( EditorWindowInputController& inputController = *context.window.m_inputController; EditorWindowChromeController& chromeController = *context.window.m_chromeController; const EditorWindowInputFeedbackBinding* inputFeedbackBinding = - context.window.m_runtime->TryGetInputFeedbackBinding(); + context.window.GetOwner().TryGetInputFeedbackBinding(); if (!CanConsumeEditorWindowChromeHover( inputController.GetPointerCaptureOwner(), @@ -139,8 +139,10 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowPointerMessage( case WM_MOUSEMOVE: if (CanRouteEditorWindowGlobalTabDragPointerMessages( inputController.GetPointerCaptureOwner(), - context.hostCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && - context.hostCoordinator.HandleGlobalTabDragPointerMove(context.window)) { + context.hostCoordinator.OwnsActiveGlobalTabDrag( + context.window.GetOwner().GetWindowId())) && + context.hostCoordinator.HandleGlobalTabDragPointerMove( + context.window.GetOwner())) { outResult = 0; return true; } @@ -180,7 +182,8 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowPointerMessage( outResult = 0; return true; case WM_LBUTTONDOWN: - if (context.hostCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { + if (context.hostCoordinator.OwnsActiveGlobalTabDrag( + context.window.GetOwner().GetWindowId())) { context.hostCoordinator.EndGlobalTabDragSession(); } if (chromeController.HandleResizeButtonDown(context.window, lParam)) { @@ -226,8 +229,10 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowPointerMessage( case WM_LBUTTONUP: if (CanRouteEditorWindowGlobalTabDragPointerMessages( inputController.GetPointerCaptureOwner(), - context.hostCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && - context.hostCoordinator.HandleGlobalTabDragPointerButtonUp(context.window)) { + context.hostCoordinator.OwnsActiveGlobalTabDrag( + context.window.GetOwner().GetWindowId())) && + context.hostCoordinator.HandleGlobalTabDragPointerButtonUp( + context.window.GetOwner())) { outResult = 0; return true; } @@ -343,7 +348,8 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowInputMessage( if (reinterpret_cast(lParam) != context.hwnd) { inputController.ClearPointerCaptureOwner(); } - if (context.hostCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) && + if (context.hostCoordinator.OwnsActiveGlobalTabDrag( + context.window.GetOwner().GetWindowId()) && reinterpret_cast(lParam) != context.hwnd) { context.hostCoordinator.EndGlobalTabDragSession(); outResult = 0; @@ -367,7 +373,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowInputMessage( case WM_KEYDOWN: case WM_SYSKEYDOWN: if (wParam == VK_F12) { - context.window.m_runtime->RequestManualScreenshot("manual_f12"); + context.window.GetOwner().RequestManualScreenshot("manual_f12"); } inputController.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyDown, wParam, lParam); outResult = 0; @@ -425,7 +431,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( outResult = 0; return true; case WM_CLOSE: - context.hostCoordinator.ExecuteCloseRequest(context.window); + context.hostCoordinator.ExecuteCloseRequest(context.window.GetOwner()); outResult = 0; return true; case WM_PAINT: @@ -433,7 +439,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( PAINTSTRUCT paintStruct = {}; BeginPaint(context.hwnd, &paintStruct); const EditorWindowFrameTransferRequests transferRequests = - context.hostCoordinator.DriveImmediateWindowFrame(context.window); + context.hostCoordinator.DriveImmediateWindowFrame(context.window.GetOwner()); EndPaint(context.hwnd, &paintStruct); FinalizeImmediateFrame(context, transferRequests); outResult = 0; @@ -443,7 +449,7 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( outResult = 1; return true; case WM_DESTROY: - context.hostCoordinator.HandleNativeWindowDestroyed(context.window); + context.hostCoordinator.HandleNativeWindowDestroyed(context.window.GetOwner()); outResult = 0; return true; default: diff --git a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp index 72bf0539..2ac91169 100644 --- a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.cpp @@ -4,6 +4,7 @@ #include "Windowing/Content/EditorWindowContentController.h" #include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" +#include "Windowing/EditorWindowManager.h" #include "Windowing/Runtime/EditorWindowRuntimeController.h" #include @@ -32,9 +33,11 @@ int ResolveOuterDimension(float value, int fallback) { EditorUtilityWindowCoordinator::EditorUtilityWindowCoordinator( EditorContext& editorContext, EditorWindowHost& hostRuntime, + EditorWindowManager& windowManager, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, EditorWindowContentFactory& contentFactory) : m_hostRuntime(hostRuntime), + m_windowManager(windowManager), m_editorContext(editorContext), m_renderRuntimeFactory(renderRuntimeFactory), m_contentFactory(contentFactory) {} @@ -129,11 +132,8 @@ bool EditorUtilityWindowCoordinator::TryProcessOpenUtilityWindowRequest( createParams.initialHeight = rect.Height(); } - EditorHostWindow* utilityWindow = m_hostRuntime.CreateHostWindow( - std::make_unique( - m_editorContext, - m_contentFactory.CreateUtilityContentController(*descriptor), - m_renderRuntimeFactory.CreateWindowRenderRuntime()), + EditorHostWindow* utilityWindow = m_windowManager.CreateUtilityWindow( + *descriptor, createParams); if (utilityWindow == nullptr) { LogRuntimeTrace("utility", "failed to create utility window"); diff --git a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h index 4dd80817..0899b62f 100644 --- a/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h +++ b/editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.h @@ -14,12 +14,14 @@ namespace XCEngine::UI::Editor::App { class EditorContext; class EditorWindowContentFactory; class EditorWindowLifecycleCoordinator; +class EditorWindowManager; class EditorUtilityWindowCoordinator final { public: EditorUtilityWindowCoordinator( EditorContext& editorContext, EditorWindowHost& hostRuntime, + EditorWindowManager& windowManager, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, EditorWindowContentFactory& contentFactory); ~EditorUtilityWindowCoordinator(); @@ -36,6 +38,7 @@ private: void LogRuntimeTrace(std::string_view channel, std::string_view message) const; EditorWindowHost& m_hostRuntime; + EditorWindowManager& m_windowManager; EditorContext& m_editorContext; Rendering::Host::EditorWindowRenderRuntimeFactory& m_renderRuntimeFactory; EditorWindowContentFactory& m_contentFactory; diff --git a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp index 03496136..bfd89e1e 100644 --- a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp +++ b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.cpp @@ -3,6 +3,7 @@ #include "Windowing/Content/EditorWindowContentFactory.h" #include "Windowing/Content/EditorWindowContentController.h" #include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" +#include "Windowing/EditorWindowManager.h" #include "Windowing/Runtime/EditorWindowRuntimeController.h" #include @@ -94,10 +95,12 @@ std::string DescribeWindowSetState(const UIEditorWindowWorkspaceSet& windowSet) EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator( EditorContext& editorContext, EditorWindowHost& hostRuntime, + EditorWindowManager& windowManager, EditorWindowSystem& windowSystem, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, EditorWindowContentFactory& contentFactory) : m_hostRuntime(hostRuntime), + m_windowManager(windowManager), m_editorContext(editorContext), m_windowSystem(windowSystem), m_renderRuntimeFactory(renderRuntimeFactory), @@ -372,11 +375,8 @@ bool EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan( createParams.initialHeight = action.create.placement.initialHeight; } - EditorHostWindow* const createdWindow = m_hostRuntime.CreateHostWindow( - std::make_unique( - m_editorContext, - m_contentFactory.CreateWorkspaceContentController(action.create.windowState), - m_renderRuntimeFactory.CreateWindowRenderRuntime()), + EditorHostWindow* const createdWindow = m_windowManager.CreateWorkspaceWindow( + action.create.windowState, createParams); if (createdWindow == nullptr) { for (const ExistingWindowSnapshot& snapshot : existingWindowSnapshots) { diff --git a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h index 14e7b723..d658e85e 100644 --- a/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h +++ b/editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h @@ -29,12 +29,14 @@ namespace XCEngine::UI::Editor::App { class EditorContext; class EditorWindowContentFactory; class EditorWindowLifecycleCoordinator; +class EditorWindowManager; class EditorWindowWorkspaceCoordinator final { public: EditorWindowWorkspaceCoordinator( EditorContext& editorContext, EditorWindowHost& hostRuntime, + EditorWindowManager& windowManager, EditorWindowSystem& windowSystem, Rendering::Host::EditorWindowRenderRuntimeFactory& renderRuntimeFactory, EditorWindowContentFactory& contentFactory); @@ -121,6 +123,7 @@ private: void LogRuntimeTrace(std::string_view channel, std::string_view message) const; EditorWindowHost& m_hostRuntime; + EditorWindowManager& m_windowManager; EditorContext& m_editorContext; EditorWindowSystem& m_windowSystem; Rendering::Host::EditorWindowRenderRuntimeFactory& m_renderRuntimeFactory; diff --git a/editor/app/Windowing/EditorWindowInstance.cpp b/editor/app/Windowing/EditorWindowInstance.cpp new file mode 100644 index 00000000..307b31e7 --- /dev/null +++ b/editor/app/Windowing/EditorWindowInstance.cpp @@ -0,0 +1,496 @@ +#include "Windowing/EditorWindowInstance.h" + +#include "Support/StringEncoding.h" +#include "Windowing/Content/EditorWindowContentController.h" +#include "Windowing/Runtime/EditorWindowRuntimeController.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawData; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +EditorWindowInstance::EditorWindowInstance( + std::string windowId, + std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, + bool primary, + std::unique_ptr runtimeController) + : m_windowId(std::move(windowId)) + , m_title(std::move(title)) + , m_category(category) + , m_chromePolicy(chromePolicy) + , m_primary(primary) + , m_runtime(std::move(runtimeController)) { + UpdateCachedTitleText(); +} + +EditorWindowInstance::~EditorWindowInstance() = default; + +std::string_view EditorWindowInstance::GetWindowId() const { + return m_windowId; +} + +EditorWindowLifecycleState EditorWindowInstance::GetLifecycleState() const { + return m_lifecycle; +} + +const EditorWindowChromePolicy& EditorWindowInstance::GetChromePolicy() const { + return m_chromePolicy; +} + +bool EditorWindowInstance::IsPrimary() const { + return m_primary; +} + +bool EditorWindowInstance::IsWorkspaceWindow() const { + return m_category == EditorWindowCategory::Workspace; +} + +bool EditorWindowInstance::IsUtilityWindow() const { + return m_category == EditorWindowCategory::Utility; +} + +bool EditorWindowInstance::IsClosing() const { + return m_lifecycle == EditorWindowLifecycleState::Closing; +} + +bool EditorWindowInstance::IsDestroyed() const { + return m_lifecycle == EditorWindowLifecycleState::Destroyed; +} + +bool EditorWindowInstance::HasLiveHostWindow() const { + return m_nativePeer != nullptr && m_nativePeer->HasLiveHostWindow(); +} + +const std::wstring& EditorWindowInstance::GetTitle() const { + return m_title; +} + +std::string_view EditorWindowInstance::GetCachedTitleText() const { + return m_cachedTitleText; +} + +const EditorWorkspaceWindowProjection* +EditorWindowInstance::TryGetWorkspaceProjection() const { + const EditorWindowWorkspaceBinding* workspaceBinding = + m_runtime != nullptr ? m_runtime->TryGetWorkspaceBinding() : nullptr; + return workspaceBinding != nullptr + ? workspaceBinding->TryGetWorkspaceProjection() + : nullptr; +} + +EditorWindowDockHostBinding* EditorWindowInstance::TryGetDockHostBinding() { + return m_runtime != nullptr ? m_runtime->TryGetDockHostBinding() : nullptr; +} + +const EditorWindowDockHostBinding* EditorWindowInstance::TryGetDockHostBinding() const { + return m_runtime != nullptr ? m_runtime->TryGetDockHostBinding() : nullptr; +} + +const EditorWindowInputFeedbackBinding* +EditorWindowInstance::TryGetInputFeedbackBinding() const { + return m_runtime != nullptr ? m_runtime->TryGetInputFeedbackBinding() : nullptr; +} + +const EditorWindowTitleBarBinding* EditorWindowInstance::TryGetTitleBarBinding() const { + return m_runtime != nullptr ? m_runtime->TryGetTitleBarBinding() : nullptr; +} + +const UIEditorShellInteractionFrame& EditorWindowInstance::GetShellFrame() const { + return m_runtime->GetShellFrame(); +} + +const UIEditorShellInteractionState& EditorWindowInstance::GetShellInteractionState() const { + return m_runtime->GetShellInteractionState(); +} + +::XCEngine::UI::UISize EditorWindowInstance::ResolveMinimumOuterSize() const { + return m_runtime->ResolveMinimumOuterSize(); +} + +UIEditorTextMeasurer& EditorWindowInstance::GetTextMeasurer() { + return m_runtime->GetTextMeasurer(); +} + +const UIEditorTextMeasurer& EditorWindowInstance::GetTextMeasurer() const { + return m_runtime->GetTextMeasurer(); +} + +const ::XCEngine::UI::UITextureHandle& EditorWindowInstance::GetTitleBarLogoIcon() const { + return m_runtime->GetTitleBarLogoIcon(); +} + +std::string EditorWindowInstance::BuildFrameRateText() const { + return m_runtime->BuildFrameRateText(); +} + +::XCEngine::UI::UIPoint EditorWindowInstance::ConvertScreenPixelsToClientDips( + const EditorWindowScreenPoint& screenPoint) const { + return m_nativePeer != nullptr + ? m_nativePeer->ConvertScreenPixelsToClientDips(screenPoint) + : ::XCEngine::UI::UIPoint{}; +} + +bool EditorWindowInstance::TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const EditorWindowScreenPoint& screenPoint, + EditorWindowScreenPoint& outHotspot) const { + const EditorWindowDockHostBinding* dockHostBinding = TryGetDockHostBinding(); + if (dockHostBinding == nullptr || m_nativePeer == nullptr) { + outHotspot = {}; + return false; + } + + const UIPoint clientPointDips = m_nativePeer->ConvertScreenPixelsToClientDips(screenPoint); + UIPoint hotspotDips = {}; + if (!dockHostBinding->TryResolveDockTabDragHotspot( + nodeId, + panelId, + clientPointDips, + hotspotDips)) { + outHotspot = {}; + return false; + } + + const float dpiScale = m_nativePeer->GetDpiScale(); + outHotspot.x = static_cast(std::lround(hotspotDips.x * dpiScale)); + outHotspot.y = static_cast(std::lround(hotspotDips.y * dpiScale)); + return true; +} + +bool EditorWindowInstance::TryResolveDockTabDropTarget( + const EditorWindowScreenPoint& screenPoint, + UIEditorDockHostTabDropTarget& outTarget) const { + const EditorWindowDockHostBinding* dockHostBinding = TryGetDockHostBinding(); + if (dockHostBinding == nullptr || m_nativePeer == nullptr) { + outTarget = {}; + return false; + } + + outTarget = dockHostBinding->ResolveDockTabDropTarget( + m_nativePeer->ConvertScreenPixelsToClientDips(screenPoint)); + return outTarget.valid; +} + +void EditorWindowInstance::InvalidateHostWindow() const { + if (m_nativePeer != nullptr) { + m_nativePeer->InvalidateHostWindow(); + } +} + +void EditorWindowInstance::SetPrimary(bool primary) { + m_primary = primary; +} + +void EditorWindowInstance::SetTitle(std::wstring title) { + m_title = std::move(title); + UpdateCachedTitleText(); +} + +void EditorWindowInstance::ApplyHostWindowTitle() { + if (m_nativePeer != nullptr) { + m_nativePeer->ApplyHostWindowTitle(m_title); + } +} + +void EditorWindowInstance::RefreshWorkspaceProjection( + EditorWorkspaceWindowProjection projection) { + EditorWindowWorkspaceBinding* workspaceBinding = + m_runtime != nullptr ? m_runtime->TryGetWorkspaceBinding() : nullptr; + assert(workspaceBinding != nullptr); + workspaceBinding->RefreshWorkspaceProjection(std::move(projection)); +} + +void EditorWindowInstance::ResetInteractionState() { + if (m_nativePeer != nullptr) { + m_nativePeer->ResetNativeInteractionState(); + } + if (m_runtime != nullptr) { + m_runtime->ResetInteractionState(); + } +} + +void EditorWindowInstance::SetDpiScale(float dpiScale) { + if (m_runtime != nullptr) { + m_runtime->SetDpiScale(dpiScale); + } +} + +bool EditorWindowInstance::ApplyResize(std::uint32_t width, std::uint32_t height) { + if (m_runtime == nullptr || !m_runtime->IsReady() || width == 0u || height == 0u) { + return false; + } + return m_runtime->ApplyResize(width, height); +} + +void EditorWindowInstance::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) { + if (m_nativePeer != nullptr) { + m_nativePeer->AcquirePointerCapture(owner); + } +} + +void EditorWindowInstance::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) { + if (m_nativePeer != nullptr) { + m_nativePeer->ReleasePointerCapture(owner); + } +} + +void EditorWindowInstance::AttachNativePeer(EditorWindowNativePeer& nativePeer) { + m_nativePeer = &nativePeer; +} + +void EditorWindowInstance::DetachNativePeer(EditorWindowNativePeer& nativePeer) { + if (m_nativePeer == &nativePeer) { + m_nativePeer = nullptr; + } +} + +void EditorWindowInstance::MarkNativeAttached() { + m_lifecycle = EditorWindowLifecycleState::NativeAttached; +} + +void EditorWindowInstance::MarkInitializing() { + m_lifecycle = EditorWindowLifecycleState::Initializing; +} + +void EditorWindowInstance::MarkRunning() { + m_lifecycle = EditorWindowLifecycleState::Running; +} + +void EditorWindowInstance::MarkClosing() { + m_lifecycle = EditorWindowLifecycleState::Closing; +} + +void EditorWindowInstance::MarkDestroyed() { + m_lifecycle = EditorWindowLifecycleState::Destroyed; +} + +bool EditorWindowInstance::IsRenderReady() const { + return m_runtime != nullptr && m_runtime->IsReady(); +} + +bool EditorWindowInstance::InitializeRuntime( + const EditorHostWindowRuntimeInitializationParams& params) { + if (m_nativePeer == nullptr || m_nativePeer->GetNativeWindowHandle() == nullptr) { + AppendUIEditorRuntimeTrace("app", "window initialize skipped: native window is null"); + return false; + } + + m_nativePeer->PrepareRuntimeInitialization(*this); + m_runtime->SetDpiScale(m_nativePeer->GetDpiScale()); + + std::ostringstream dpiTrace = {}; + dpiTrace << "initial dpiScale=" << m_nativePeer->GetDpiScale(); + AppendUIEditorRuntimeTrace("window", dpiTrace.str()); + + MarkInitializing(); + std::uint32_t clientWidth = 0u; + std::uint32_t clientHeight = 0u; + if (!m_nativePeer->QueryCurrentClientPixelSize(clientWidth, clientHeight)) { + clientWidth = 1u; + clientHeight = 1u; + } + + const bool initialized = m_runtime->Initialize( + Rendering::Host::EditorWindowRenderRuntimeSurface{ + .nativeWindowHandle = m_nativePeer->GetNativeWindowHandle(), + .widthPixels = clientWidth, + .heightPixels = clientHeight, + }, + params.repoRoot, + params.captureRoot, + params.autoCaptureOnStartup); + if (initialized) { + MarkRunning(); + } else { + MarkNativeAttached(); + } + return initialized; +} + +EditorWindowFrameTransferRequests EditorWindowInstance::RenderHostFrame( + bool globalTabDragActive) { + if (m_runtime == nullptr || !m_runtime->IsReady() || m_nativePeer == nullptr) { + return {}; + } + + std::uint32_t pixelWidth = 0u; + std::uint32_t pixelHeight = 0u; + if (!m_nativePeer->ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { + return {}; + } + + const float width = m_nativePeer->PixelsToDips(static_cast(pixelWidth)); + const float height = m_nativePeer->PixelsToDips(static_cast(pixelHeight)); + const UIRect workspaceBounds = + m_nativePeer->ResolveWorkspaceBounds(*this, width, height); + + UIDrawData drawData = {}; + UIDrawList& backgroundDrawList = drawData.EmplaceDrawList("XCEditorWindow.Surface"); + backgroundDrawList.AddFilledRect( + UIRect(0.0f, 0.0f, width, height), + kShellSurfaceColor); + + EditorWindowFrameTransferRequests transferRequests = {}; + if (m_runtime->IsEditorContextValid()) { + transferRequests = + RenderRuntimeFrame(globalTabDragActive, workspaceBounds, drawData); + } else { + UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid"); + m_runtime->AppendInvalidFrame(invalidDrawList); + } + + UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome"); + m_nativePeer->AppendChrome(*this, windowChromeDrawList, width); + + const auto presentResult = m_runtime->Present(drawData); + if (!presentResult.warning.empty()) { + AppendUIEditorRuntimeTrace("present", presentResult.warning); + } + return transferRequests; +} + +void EditorWindowInstance::ValidateHostFrame() const { + if (m_nativePeer != nullptr) { + m_nativePeer->ValidateHostFrame(); + } +} + +void EditorWindowInstance::RequestSkipNextSteadyStateFrame() { + if (m_nativePeer != nullptr) { + m_nativePeer->RequestSkipNextSteadyStateFrame(); + } +} + +bool EditorWindowInstance::ConsumeSkipNextSteadyStateFrame() { + return m_nativePeer != nullptr && m_nativePeer->ConsumeSkipNextSteadyStateFrame(); +} + +void EditorWindowInstance::Shutdown() { + std::ostringstream trace = {}; + trace << "EditorWindowInstance::Shutdown begin windowId='" << GetWindowId() + << "' primary=" << (IsPrimary() ? 1 : 0) + << " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState()) + << " runtimeReady=" << (IsRenderReady() ? 1 : 0); + AppendUIEditorRuntimeTrace("window-close", trace.str()); + + if (m_nativePeer != nullptr) { + m_nativePeer->ShutdownNativeInteraction(); + } + if (m_runtime != nullptr && m_runtime->IsReady()) { + m_runtime->Shutdown(); + } + AppendUIEditorRuntimeTrace( + "window-close", + "EditorWindowInstance::Shutdown end windowId='" + std::string(GetWindowId()) + "'"); +} + +bool EditorWindowInstance::TryGetHostScreenRect(EditorWindowScreenRect& outRect) const { + outRect = {}; + return m_nativePeer != nullptr && m_nativePeer->TryGetHostScreenRect(outRect); +} + +void EditorWindowInstance::SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) { + if (m_nativePeer != nullptr) { + m_nativePeer->SetHostScreenPosition(screenPoint); + } +} + +void EditorWindowInstance::FocusHostWindow() { + if (m_nativePeer != nullptr) { + m_nativePeer->FocusHostWindow(); + } +} + +void EditorWindowInstance::PostCloseToHost() { + if (m_nativePeer != nullptr) { + m_nativePeer->PostCloseToHost(); + } +} + +void EditorWindowInstance::DestroyHostWindow() { + if (m_nativePeer != nullptr) { + m_nativePeer->DestroyHostWindow(); + } +} + +void EditorWindowInstance::RequestManualScreenshot(std::string reason) { + if (m_runtime != nullptr) { + m_runtime->RequestManualScreenshot(std::move(reason)); + } +} + +void EditorWindowInstance::UpdateCachedTitleText() { + m_cachedTitleText = WideToUtf8(m_title); +} + +EditorWindowFrameTransferRequests EditorWindowInstance::RenderRuntimeFrame( + bool globalTabDragActive, + const UIRect& workspaceBounds, + UIDrawData& drawData) { + if (m_nativePeer == nullptr) { + return {}; + } + + m_nativePeer->SyncShellCapturedPointerButtonsFromSystemState( + m_runtime->GetShellInteractionState()); + std::vector<::XCEngine::UI::UIInputEvent> frameEvents = + m_nativePeer->TakePendingInputEvents(); + m_runtime->PrepareEditorContext(); + const auto frameContext = m_runtime->BeginFrame(); + if (!frameContext.warning.empty()) { + AppendUIEditorRuntimeTrace("viewport", frameContext.warning); + } + + const bool useDetachedTitleBarTabStrip = + m_nativePeer->ShouldUseDetachedTitleBarTabStrip(*this); + const EditorWindowFrameTransferRequests transferRequests = + m_runtime->UpdateAndAppend( + workspaceBounds, + frameEvents, + m_nativePeer->QueryCursorScreenPoint(), + IsPrimary(), + globalTabDragActive, + useDetachedTitleBarTabStrip, + drawData); + if (frameContext.canRenderViewports) { + m_runtime->RenderRequestedViewports(frameContext.renderContext); + } + + m_nativePeer->ApplyShellRuntimePointerCapture(*this); + m_nativePeer->ApplyCurrentCursor(); + return transferRequests; +} + +std::unique_ptr CreateEditorWindowInstance( + std::string windowId, + std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, + bool primary, + std::unique_ptr runtimeController) { + return std::make_unique( + std::move(windowId), + std::move(title), + category, + chromePolicy, + primary, + std::move(runtimeController)); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/EditorWindowInstance.h b/editor/app/Windowing/EditorWindowInstance.h new file mode 100644 index 00000000..ec13c566 --- /dev/null +++ b/editor/app/Windowing/EditorWindowInstance.h @@ -0,0 +1,125 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "Windowing/Host/EditorWindowHostInterfaces.h" + +#include +#include + +namespace XCEngine::UI::Editor::App { + +class EditorWindowRuntimeController; + +class EditorWindowInstance final : public EditorHostWindow { +public: + EditorWindowInstance( + std::string windowId, + std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, + bool primary, + std::unique_ptr runtimeController); + ~EditorWindowInstance() override; + + EditorWindowInstance(const EditorWindowInstance&) = delete; + EditorWindowInstance& operator=(const EditorWindowInstance&) = delete; + EditorWindowInstance(EditorWindowInstance&&) = delete; + EditorWindowInstance& operator=(EditorWindowInstance&&) = delete; + + std::string_view GetWindowId() const override; + EditorWindowLifecycleState GetLifecycleState() const override; + const EditorWindowChromePolicy& GetChromePolicy() const override; + bool IsPrimary() const override; + bool IsWorkspaceWindow() const override; + bool IsUtilityWindow() const override; + bool IsClosing() const override; + bool IsDestroyed() const override; + bool HasLiveHostWindow() const override; + const std::wstring& GetTitle() const override; + std::string_view GetCachedTitleText() const override; + const EditorWorkspaceWindowProjection* TryGetWorkspaceProjection() const override; + EditorWindowDockHostBinding* TryGetDockHostBinding() override; + const EditorWindowDockHostBinding* TryGetDockHostBinding() const override; + const EditorWindowInputFeedbackBinding* TryGetInputFeedbackBinding() const override; + const EditorWindowTitleBarBinding* TryGetTitleBarBinding() const override; + const UIEditorShellInteractionFrame& GetShellFrame() const override; + const UIEditorShellInteractionState& GetShellInteractionState() const override; + ::XCEngine::UI::UISize ResolveMinimumOuterSize() const override; + UIEditorTextMeasurer& GetTextMeasurer() override; + const UIEditorTextMeasurer& GetTextMeasurer() const override; + const ::XCEngine::UI::UITextureHandle& GetTitleBarLogoIcon() const override; + std::string BuildFrameRateText() const override; + + ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips( + const EditorWindowScreenPoint& screenPoint) const override; + bool TryResolveDockTabDragHotspot( + std::string_view nodeId, + std::string_view panelId, + const EditorWindowScreenPoint& screenPoint, + EditorWindowScreenPoint& outHotspot) const override; + bool TryResolveDockTabDropTarget( + const EditorWindowScreenPoint& screenPoint, + UIEditorDockHostTabDropTarget& outTarget) const override; + void InvalidateHostWindow() const override; + void SetPrimary(bool primary) override; + void SetTitle(std::wstring title) override; + void ApplyHostWindowTitle() override; + void RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) override; + void ResetInteractionState() override; + void SetDpiScale(float dpiScale) override; + bool ApplyResize(std::uint32_t width, std::uint32_t height) override; + void AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) override; + void ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) override; + void AttachNativePeer(EditorWindowNativePeer& nativePeer) override; + void DetachNativePeer(EditorWindowNativePeer& nativePeer) override; + void MarkNativeAttached() override; + void MarkInitializing() override; + void MarkRunning() override; + void MarkClosing() override; + void MarkDestroyed() override; + bool IsRenderReady() const override; + bool InitializeRuntime( + const EditorHostWindowRuntimeInitializationParams& params) override; + EditorWindowFrameTransferRequests RenderHostFrame( + bool globalTabDragActive) override; + void ValidateHostFrame() const override; + void RequestSkipNextSteadyStateFrame() override; + bool ConsumeSkipNextSteadyStateFrame() override; + void Shutdown() override; + bool TryGetHostScreenRect(EditorWindowScreenRect& outRect) const override; + void SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) override; + void FocusHostWindow() override; + void PostCloseToHost() override; + void DestroyHostWindow() override; + void RequestManualScreenshot(std::string reason) override; + +private: + void UpdateCachedTitleText(); + EditorWindowFrameTransferRequests RenderRuntimeFrame( + bool globalTabDragActive, + const ::XCEngine::UI::UIRect& workspaceBounds, + ::XCEngine::UI::UIDrawData& drawData); + + std::string m_windowId = {}; + std::wstring m_title = {}; + std::string m_cachedTitleText = {}; + EditorWindowCategory m_category = EditorWindowCategory::Workspace; + EditorWindowChromePolicy m_chromePolicy = {}; + bool m_primary = false; + EditorWindowLifecycleState m_lifecycle = EditorWindowLifecycleState::PendingNativeCreate; + EditorWindowNativePeer* m_nativePeer = nullptr; + std::unique_ptr m_runtime = {}; +}; + +std::unique_ptr CreateEditorWindowInstance( + std::string windowId, + std::wstring title, + EditorWindowCategory category, + EditorWindowChromePolicy chromePolicy, + bool primary, + std::unique_ptr runtimeController); + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/EditorWindowManager.cpp b/editor/app/Windowing/EditorWindowManager.cpp index 725a7222..d495eed2 100644 --- a/editor/app/Windowing/EditorWindowManager.cpp +++ b/editor/app/Windowing/EditorWindowManager.cpp @@ -5,6 +5,7 @@ #include "Windowing/Coordinator/EditorWindowLifecycleCoordinator.h" #include "Windowing/Coordinator/EditorUtilityWindowCoordinator.h" #include "Windowing/Coordinator/EditorWindowWorkspaceCoordinator.h" +#include "Windowing/EditorWindowInstance.h" #include "Windowing/Runtime/EditorWindowRuntimeController.h" #include @@ -12,6 +13,7 @@ #include #include +#include #include #include @@ -30,6 +32,7 @@ EditorWindowManager::EditorWindowManager( std::make_unique( m_editorContext, m_hostRuntime, + *this, windowSystem, m_renderRuntimeFactory, *m_contentFactory); @@ -37,6 +40,7 @@ EditorWindowManager::EditorWindowManager( std::make_unique( m_editorContext, m_hostRuntime, + *this, m_renderRuntimeFactory, *m_contentFactory); m_lifecycleCoordinator = std::make_unique( @@ -62,12 +66,21 @@ EditorHostWindow* EditorWindowManager::CreateWorkspaceWindow( return nullptr; } - EditorHostWindow* const window = m_hostRuntime.CreateHostWindow( + auto windowInstance = CreateEditorWindowInstance( + params.windowId, + params.title, + params.category, + params.chromePolicy, + params.primary, std::make_unique( m_editorContext, m_contentFactory->CreateWorkspaceContentController(windowState), - m_renderRuntimeFactory.CreateWindowRenderRuntime()), - params); + m_renderRuntimeFactory.CreateWindowRenderRuntime())); + EditorHostWindow* const window = windowInstance.get(); + if (!m_hostRuntime.CreateHostWindow(*window, params)) { + return nullptr; + } + m_windows.push_back(std::move(windowInstance)); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); } @@ -81,12 +94,21 @@ EditorHostWindow* EditorWindowManager::CreateUtilityWindow( return nullptr; } - EditorHostWindow* const window = m_hostRuntime.CreateHostWindow( + auto windowInstance = CreateEditorWindowInstance( + params.windowId, + params.title, + params.category, + params.chromePolicy, + params.primary, std::make_unique( m_editorContext, m_contentFactory->CreateUtilityContentController(descriptor), - m_renderRuntimeFactory.CreateWindowRenderRuntime()), - params); + m_renderRuntimeFactory.CreateWindowRenderRuntime())); + EditorHostWindow* const window = windowInstance.get(); + if (!m_hostRuntime.CreateHostWindow(*window, params)) { + return nullptr; + } + m_windows.push_back(std::move(windowInstance)); if (window != nullptr) { m_workspaceCoordinator->RegisterExistingWindow(*window); } @@ -96,6 +118,8 @@ EditorHostWindow* EditorWindowManager::CreateUtilityWindow( void EditorWindowManager::Shutdown() { m_workspaceCoordinator->EndGlobalTabDragSession(); m_lifecycleCoordinator->ShutdownAllWindows(); + ReapDestroyedWindows(); + m_windows.clear(); } bool EditorWindowManager::RequestPrimaryWindowClose() { @@ -103,7 +127,7 @@ bool EditorWindowManager::RequestPrimaryWindowClose() { return false; } - for (EditorHostWindow* window : m_hostRuntime.GetWindows()) { + for (const std::unique_ptr& window : m_windows) { if (window == nullptr || !window->IsPrimary()) { continue; } @@ -116,11 +140,11 @@ bool EditorWindowManager::RequestPrimaryWindowClose() { } bool EditorWindowManager::HasWindows() const { - return !m_hostRuntime.GetWindows().empty(); + return !m_windows.empty(); } void EditorWindowManager::DestroyClosedWindows() { - m_lifecycleCoordinator->ReapDestroyedWindows(); + ReapDestroyedWindows(); } void EditorWindowManager::RenderAllWindows() { @@ -130,7 +154,13 @@ void EditorWindowManager::RenderAllWindows() { }; std::vector transferBatches = {}; - const std::vector windows = m_hostRuntime.GetWindows(); + std::vector windows = {}; + windows.reserve(m_windows.size()); + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr) { + windows.push_back(window.get()); + } + } transferBatches.reserve(windows.size()); for (EditorHostWindow* window : windows) { @@ -274,6 +304,14 @@ void EditorWindowManager::ReapDestroyedWindows() { if (m_lifecycleCoordinator != nullptr) { m_lifecycleCoordinator->ReapDestroyedWindows(); } + m_windows.erase( + std::remove_if( + m_windows.begin(), + m_windows.end(), + [](const std::unique_ptr& window) { + return window == nullptr || window->IsDestroyed(); + }), + m_windows.end()); } } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/EditorWindowManager.h b/editor/app/Windowing/EditorWindowManager.h index 33a5dd62..904676e4 100644 --- a/editor/app/Windowing/EditorWindowManager.h +++ b/editor/app/Windowing/EditorWindowManager.h @@ -7,8 +7,10 @@ #include "Windowing/Host/EditorWindowHostCoordinator.h" #include "Windowing/Host/EditorWindowHostInterfaces.h" #include "Windowing/Host/EditorWindowHostTypes.h" +#include "Windowing/EditorWindowInstance.h" #include #include +#include namespace XCEngine::UI::Editor { @@ -91,6 +93,7 @@ private: std::unique_ptr m_lifecycleCoordinator = {}; std::unique_ptr m_utilityCoordinator = {}; std::unique_ptr m_workspaceCoordinator = {}; + std::vector> m_windows = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Windowing/Host/EditorWindowHostInterfaces.h b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h index 6f618b06..e08c7c5b 100644 --- a/editor/app/Windowing/Host/EditorWindowHostInterfaces.h +++ b/editor/app/Windowing/Host/EditorWindowHostInterfaces.h @@ -6,10 +6,14 @@ #include "Windowing/Host/EditorWindowPointerCapture.h" #include "Windowing/Host/EditorWindowTypes.h" +#include +#include #include +#include -#include #include +#include +#include #include #include #include @@ -30,7 +34,8 @@ namespace XCEngine::UI::Editor::App { class EditorWindowHostCoordinator; class EditorWindowDockHostBinding; -class EditorWindowRuntimeController; +class EditorWindowInputFeedbackBinding; +class EditorWindowTitleBarBinding; struct EditorHostWindowRuntimeInitializationParams { std::filesystem::path repoRoot = {}; @@ -38,12 +43,15 @@ struct EditorHostWindowRuntimeInitializationParams { bool autoCaptureOnStartup = false; }; +class EditorWindowNativePeer; + class EditorHostWindow { public: virtual ~EditorHostWindow() = default; virtual std::string_view GetWindowId() const = 0; virtual EditorWindowLifecycleState GetLifecycleState() const = 0; + virtual const EditorWindowChromePolicy& GetChromePolicy() const = 0; virtual bool IsPrimary() const = 0; virtual bool IsWorkspaceWindow() const = 0; virtual bool IsUtilityWindow() const = 0; @@ -51,9 +59,19 @@ public: virtual bool IsDestroyed() const = 0; virtual bool HasLiveHostWindow() const = 0; virtual const std::wstring& GetTitle() const = 0; + virtual std::string_view GetCachedTitleText() const = 0; virtual const EditorWorkspaceWindowProjection* TryGetWorkspaceProjection() const = 0; virtual EditorWindowDockHostBinding* TryGetDockHostBinding() = 0; virtual const EditorWindowDockHostBinding* TryGetDockHostBinding() const = 0; + virtual const EditorWindowInputFeedbackBinding* TryGetInputFeedbackBinding() const = 0; + virtual const EditorWindowTitleBarBinding* TryGetTitleBarBinding() const = 0; + virtual const UIEditorShellInteractionFrame& GetShellFrame() const = 0; + virtual const UIEditorShellInteractionState& GetShellInteractionState() const = 0; + virtual ::XCEngine::UI::UISize ResolveMinimumOuterSize() const = 0; + virtual UIEditorTextMeasurer& GetTextMeasurer() = 0; + virtual const UIEditorTextMeasurer& GetTextMeasurer() const = 0; + virtual const ::XCEngine::UI::UITextureHandle& GetTitleBarLogoIcon() const = 0; + virtual std::string BuildFrameRateText() const = 0; virtual ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips( const EditorWindowScreenPoint& screenPoint) const = 0; virtual bool TryResolveDockTabDragHotspot( @@ -70,8 +88,15 @@ public: virtual void ApplyHostWindowTitle() = 0; virtual void RefreshWorkspaceProjection(EditorWorkspaceWindowProjection projection) = 0; virtual void ResetInteractionState() = 0; + virtual void SetDpiScale(float dpiScale) = 0; + virtual bool ApplyResize(std::uint32_t width, std::uint32_t height) = 0; virtual void AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) = 0; virtual void ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) = 0; + virtual void AttachNativePeer(EditorWindowNativePeer& nativePeer) = 0; + virtual void DetachNativePeer(EditorWindowNativePeer& nativePeer) = 0; + virtual void MarkNativeAttached() = 0; + virtual void MarkInitializing() = 0; + virtual void MarkRunning() = 0; virtual void MarkClosing() = 0; virtual void MarkDestroyed() = 0; virtual bool IsRenderReady() const = 0; @@ -88,14 +113,64 @@ public: virtual void FocusHostWindow() = 0; virtual void PostCloseToHost() = 0; virtual void DestroyHostWindow() = 0; + virtual void RequestManualScreenshot(std::string reason) = 0; +}; + +class EditorWindowNativePeer { +public: + virtual ~EditorWindowNativePeer() = default; + + virtual bool HasLiveHostWindow() const = 0; + virtual void* GetNativeWindowHandle() const = 0; + virtual bool QueryCurrentClientPixelSize( + std::uint32_t& outWidth, + std::uint32_t& outHeight) const = 0; + virtual bool ResolveRenderClientPixelSize( + std::uint32_t& outWidth, + std::uint32_t& outHeight) const = 0; + virtual float GetDpiScale() const = 0; + virtual float PixelsToDips(float pixels) const = 0; + virtual ::XCEngine::UI::UIRect ResolveWorkspaceBounds( + const EditorHostWindow& window, + float clientWidthDips, + float clientHeightDips) const = 0; + virtual bool ShouldUseDetachedTitleBarTabStrip( + const EditorHostWindow& window) const = 0; + virtual std::vector<::XCEngine::UI::UIInputEvent> TakePendingInputEvents() = 0; + virtual std::optional QueryCursorScreenPoint() const = 0; + virtual void SyncShellCapturedPointerButtonsFromSystemState( + const UIEditorShellInteractionState& shellState) = 0; + virtual void ApplyShellRuntimePointerCapture(const EditorHostWindow& window) = 0; + virtual bool ApplyCurrentCursor() const = 0; + virtual void AppendChrome( + const EditorHostWindow& window, + ::XCEngine::UI::UIDrawList& drawList, + float clientWidthDips) const = 0; + virtual void PrepareRuntimeInitialization(EditorHostWindow& window) = 0; + virtual void ShutdownNativeInteraction() = 0; + virtual void ResetNativeInteractionState() = 0; + virtual void ValidateHostFrame() const = 0; + virtual void InvalidateHostWindow() const = 0; + virtual ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips( + const EditorWindowScreenPoint& screenPoint) const = 0; + virtual bool TryGetHostScreenRect(EditorWindowScreenRect& outRect) const = 0; + virtual void SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) = 0; + virtual void FocusHostWindow() = 0; + virtual void PostCloseToHost() = 0; + virtual void DestroyHostWindow() = 0; + virtual void ApplyHostWindowTitle(const std::wstring& title) = 0; + virtual void AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) = 0; + virtual void ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) = 0; + virtual void RequestSkipNextSteadyStateFrame() = 0; + virtual bool ConsumeSkipNextSteadyStateFrame() = 0; }; class EditorWindowHost { public: virtual ~EditorWindowHost() = default; - virtual EditorHostWindow* CreateHostWindow( - std::unique_ptr runtimeController, + virtual bool CreateHostWindow( + EditorHostWindow& window, const EditorWindowCreateParams& params) = 0; virtual EditorHostWindow* FindWindowById(std::string_view windowId) = 0; virtual const EditorHostWindow* FindWindowById(std::string_view windowId) const = 0;