#include "Windowing/EditorWindowInstance.h" #include "Support/StringEncoding.h" #include "Windowing/Host/EditorWindowContentBindings.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->CaptureMetrics().dpiScale; 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) { AppendUIEditorRuntimeTrace("app", "window initialize skipped: native window is null"); return false; } m_nativePeer->PrepareRuntimeInitialization(*this); EditorNativeWindowRuntimeSurface runtimeSurface = {}; if (!m_nativePeer->CaptureRuntimeSurface(*this, runtimeSurface) || !runtimeSurface.IsValid()) { AppendUIEditorRuntimeTrace("app", "window initialize skipped: native surface is invalid"); return false; } m_runtime->SetDpiScale(runtimeSurface.dpiScale); std::ostringstream dpiTrace = {}; dpiTrace << "initial dpiScale=" << runtimeSurface.dpiScale; AppendUIEditorRuntimeTrace("window", dpiTrace.str()); MarkInitializing(); const bool initialized = m_runtime->Initialize( Rendering::Host::EditorWindowRenderRuntimeInitializeParams{ .surface = runtimeSurface.renderSurface, .widthPixels = runtimeSurface.widthPixels, .heightPixels = runtimeSurface.heightPixels, }, 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 {}; } EditorNativeWindowFrameSnapshot frameSnapshot = {}; if (!m_nativePeer->CaptureFrameSnapshot( *this, m_runtime->GetShellInteractionState(), frameSnapshot) || !frameSnapshot.IsValid()) { return {}; } UIDrawData drawData = {}; UIDrawList& backgroundDrawList = drawData.EmplaceDrawList("XCEditorWindow.Surface"); backgroundDrawList.AddFilledRect( UIRect(0.0f, 0.0f, frameSnapshot.widthDips, frameSnapshot.heightDips), kShellSurfaceColor); EditorWindowFrameTransferRequests transferRequests = {}; if (m_runtime->IsEditorContextValid()) { transferRequests = RenderRuntimeFrame(globalTabDragActive, frameSnapshot, drawData); } else { UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid"); m_runtime->AppendInvalidFrame(invalidDrawList); } UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome"); m_nativePeer->AppendChrome(*this, windowChromeDrawList, frameSnapshot.widthDips); 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 EditorNativeWindowFrameSnapshot& frameSnapshot, UIDrawData& drawData) { if (m_nativePeer == nullptr) { return {}; } m_runtime->PrepareEditorContext(); const auto frameContext = m_runtime->BeginFrame(); if (!frameContext.warning.empty()) { AppendUIEditorRuntimeTrace("viewport", frameContext.warning); } const EditorWindowFrameTransferRequests transferRequests = m_runtime->UpdateAndAppend( frameSnapshot.workspaceBounds, frameSnapshot.inputEvents, frameSnapshot.cursorScreenPoint, IsPrimary(), globalTabDragActive, frameSnapshot.useDetachedTitleBarTabStrip, drawData); if (frameContext.canRenderViewports) { m_runtime->RenderRequestedViewports(frameContext.renderContext); } m_nativePeer->ApplyFrameCommands( *this, EditorNativeWindowFrameCommands{ .applyShellRuntimePointerCapture = true, .applyCurrentCursor = true, }); 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