#include "Platform/Win32/Windowing/EditorWindow.h" #include "Bootstrap/EditorResources.h" #include "Platform/Win32/Chrome/EditorWindowChromeController.h" #include "Windowing/Content/EditorWindowContentController.h" #include "Platform/Win32/Runtime/EditorWindowFrameDriver.h" #include "Platform/Win32/Windowing/EditorWindowSession.h" #include "Platform/Win32/Windowing/EditorWindowSupport.h" #include "Windowing/Frame/EditorWindowFrameOrchestrator.h" #include "Platform/Win32/Runtime/EditorWindowInputController.h" #include "Platform/Win32/Runtime/EditorWindowRuntimeController.h" #include "Composition/EditorContext.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace XCEngine::UI::Editor::App::EditorWindowSupport { UINT QuerySystemDpi() { HDC screenDc = GetDC(nullptr); if (screenDc == nullptr) { return kDefaultDpi; } const int dpiX = GetDeviceCaps(screenDc, LOGPIXELSX); ReleaseDC(nullptr, screenDc); return dpiX > 0 ? static_cast(dpiX) : kDefaultDpi; } UINT QueryWindowDpi(HWND hwnd) { if (hwnd != nullptr) { const HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32 != nullptr) { using GetDpiForWindowFn = UINT(WINAPI*)(HWND); const auto getDpiForWindow = reinterpret_cast(GetProcAddress(user32, "GetDpiForWindow")); if (getDpiForWindow != nullptr) { const UINT dpi = getDpiForWindow(hwnd); if (dpi != 0u) { return dpi; } } } } return QuerySystemDpi(); } bool ResolveVerboseRuntimeTraceEnabled() { return IsEditorWindowVerboseRuntimeTraceEnabled(); } void LogRuntimeTrace(std::string_view channel, std::string_view message) { AppendUIEditorRuntimeTrace(channel, message); } } // namespace XCEngine::UI::Editor::App::EditorWindowSupport namespace XCEngine::UI::Editor::App { using namespace EditorWindowSupport; using ::XCEngine::UI::UIPoint; POINT EditorWindow::ToNativePoint(const EditorWindowScreenPoint& screenPoint) { return POINT{ static_cast(screenPoint.x), static_cast(screenPoint.y), }; } EditorWindowScreenPoint EditorWindow::FromNativePoint(const POINT& screenPoint) { EditorWindowScreenPoint point = {}; point.x = screenPoint.x; point.y = screenPoint.y; return point; } EditorWindow::EditorWindow( std::string windowId, std::wstring title, EditorWindowCategory category, EditorWindowChromePolicy chromePolicy, bool primary, std::unique_ptr contentController) : m_session(std::make_unique( std::move(windowId), std::move(title), category, chromePolicy, primary)) , m_chromeController(std::make_unique()) , m_frameOrchestrator(std::make_unique()) , m_inputController(std::make_unique()) , m_runtime(std::make_unique( std::move(contentController))) {} EditorWindow::~EditorWindow() = default; std::string_view EditorWindow::GetWindowId() const { return m_session->GetWindowId(); } HWND EditorWindow::GetHwnd() const { return m_session->GetHwnd(); } bool EditorWindow::HasHwnd() const { return m_session->HasHwnd(); } EditorWindowCategory EditorWindow::GetCategory() const { return m_session->GetCategory(); } const EditorWindowChromePolicy& EditorWindow::GetChromePolicy() const { return m_session->GetChromePolicy(); } EditorWindowLifecycleState EditorWindow::GetLifecycleState() const { return m_session->GetLifecycleState(); } bool EditorWindow::IsPrimary() const { return m_session->IsPrimary(); } bool EditorWindow::IsWorkspaceWindow() const { return m_session->IsWorkspaceWindow(); } bool EditorWindow::IsUtilityWindow() const { return m_session->IsUtilityWindow(); } bool EditorWindow::IsClosing() const { return m_session->IsClosing(); } bool EditorWindow::IsDestroyed() const { return m_session->IsDestroyed(); } bool EditorWindow::HasLiveHostWindow() const { const HWND hwnd = m_session->GetHwnd(); return hwnd != nullptr && IsWindow(hwnd); } bool EditorWindow::IsRenderReady() const { return m_runtime->IsReady(); } const std::wstring& EditorWindow::GetTitle() const { return m_session->GetTitle(); } std::string_view EditorWindow::GetCachedTitleText() const { return m_session->GetCachedTitleText(); } const UIEditorWorkspaceController* EditorWindow::TryGetWorkspaceController() const { const EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding(); return workspaceBinding != nullptr ? workspaceBinding->TryGetWorkspaceController() : nullptr; } const EditorWorkspaceWindowProjection* EditorWindow::TryGetWorkspaceProjection() const { const EditorWindowWorkspaceBinding* workspaceBinding = m_runtime->TryGetWorkspaceBinding(); return workspaceBinding != nullptr ? workspaceBinding->TryGetWorkspaceProjection() : nullptr; } const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const { const UIEditorWorkspaceController* workspaceController = TryGetWorkspaceController(); assert(workspaceController != nullptr); return *workspaceController; } EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() { return m_runtime->TryGetDockHostBinding(); } const EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() const { return m_runtime->TryGetDockHostBinding(); } void EditorWindow::AttachHwnd(HWND hwnd) { m_session->AttachHwnd(hwnd); } 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() { if (HasLiveHostWindow()) { SetWindowTextW(m_session->GetHwnd(), GetTitle().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)) { InvalidateRect(hwnd, nullptr, FALSE); } } bool EditorWindow::Initialize( const std::filesystem::path& repoRoot, EditorContext& editorContext, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup) { if (m_session->GetHwnd() == nullptr) { LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); return false; } 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(); const bool initialized = m_runtime->Initialize( m_session->GetHwnd(), repoRoot, editorContext, captureRoot, autoCaptureOnStartup); if (initialized) { MarkRunning(); } else { m_session->MarkNativeAttached(); } return initialized; } void EditorWindow::Shutdown() { std::ostringstream trace = {}; trace << "EditorWindow::Shutdown 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); 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()) + "'"); } void EditorWindow::ResetInteractionState() { ForceReleasePointerCapture(); m_inputController->ResetInteractionState(); m_runtime->ResetInteractionState(); m_chromeController->ResetChromeState(); m_chromeController->EndBorderlessResize(); m_chromeController->EndBorderlessWindowDragRestore(); m_chromeController->EndInteractiveResize(); m_chromeController->SetHoveredBorderlessResizeEdge( Host::BorderlessWindowResizeEdge::None); m_chromeController->ClearPredictedClientPixelSize(); } bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { if (!m_runtime->IsReady() || width == 0u || height == 0u) { return false; } return m_runtime->ApplyResize(width, height); } bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { outWidth = 0u; outHeight = 0u; const HWND hwnd = m_session->GetHwnd(); if (hwnd == nullptr || !IsWindow(hwnd)) { return false; } RECT clientRect = {}; if (!GetClientRect(hwnd, &clientRect)) { return false; } const LONG width = clientRect.right - clientRect.left; const LONG height = clientRect.bottom - clientRect.top; if (width <= 0 || height <= 0) { return false; } outWidth = static_cast(width); outHeight = static_cast(height); return true; } bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const { if (m_chromeController->TryGetPredictedClientPixelSize(outWidth, outHeight)) { return true; } return QueryCurrentClientPixelSize(outWidth, outHeight); } float EditorWindow::GetDpiScale() const { return m_chromeController->GetDpiScale(kBaseDpiScale); } float EditorWindow::PixelsToDips(float pixels) const { const float dpiScale = GetDpiScale(); return dpiScale > 0.0f ? pixels / dpiScale : pixels; } UIPoint EditorWindow::ConvertClientPixelsToDips(LONG x, LONG y) const { return UIPoint( PixelsToDips(static_cast(x)), PixelsToDips(static_cast(y))); } UIPoint EditorWindow::ConvertScreenPixelsToClientDips( const EditorWindowScreenPoint& screenPoint) const { POINT clientPoint = ToNativePoint(screenPoint); if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr) { ScreenToClient(hwnd, &clientPoint); } const float dpiScale = m_chromeController->GetDpiScale(kBaseDpiScale); return UIPoint( dpiScale > 0.0f ? static_cast(clientPoint.x) / dpiScale : static_cast(clientPoint.x), dpiScale > 0.0f ? static_cast(clientPoint.y) / dpiScale : static_cast(clientPoint.y)); } bool EditorWindow::TryResolveDockTabDragHotspot( std::string_view nodeId, std::string_view panelId, const EditorWindowScreenPoint& screenPoint, EditorWindowScreenPoint& outHotspot) const { const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding(); if (dockHostBinding == nullptr) { outHotspot = {}; return false; } const UIPoint clientPointDips = ConvertScreenPixelsToClientDips(screenPoint); UIPoint hotspotDips = {}; if (!dockHostBinding->TryResolveDockTabDragHotspot( nodeId, panelId, clientPointDips, hotspotDips)) { outHotspot = {}; return false; } const float dpiScale = GetDpiScale(); outHotspot.x = static_cast(std::lround(hotspotDips.x * dpiScale)); outHotspot.y = static_cast(std::lround(hotspotDips.y * dpiScale)); return true; } bool EditorWindow::TryResolveDockTabDropTarget( const EditorWindowScreenPoint& screenPoint, UIEditorDockHostTabDropTarget& outTarget) const { const EditorWindowDockHostBinding* dockHostBinding = m_runtime->TryGetDockHostBinding(); if (dockHostBinding == nullptr) { outTarget = {}; return false; } outTarget = dockHostBinding->ResolveDockTabDropTarget( ConvertScreenPixelsToClientDips(screenPoint)); return outTarget.valid; } bool EditorWindow::TryGetHostScreenRect(EditorWindowScreenRect& outRect) const { outRect = {}; RECT nativeRect = {}; if (!HasLiveHostWindow() || !GetWindowRect(m_session->GetHwnd(), &nativeRect)) { return false; } outRect.left = nativeRect.left; outRect.top = nativeRect.top; outRect.right = nativeRect.right; outRect.bottom = nativeRect.bottom; return true; } void EditorWindow::SetHostScreenPosition(const EditorWindowScreenPoint& screenPoint) { if (!HasLiveHostWindow()) { return; } SetWindowPos( m_session->GetHwnd(), nullptr, screenPoint.x, screenPoint.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } void EditorWindow::FocusHostWindow() { if (!HasLiveHostWindow()) { return; } ShowWindow(m_session->GetHwnd(), SW_RESTORE); SetForegroundWindow(m_session->GetHwnd()); } void EditorWindow::PostCloseToHost() { if (HasLiveHostWindow()) { PostMessageW(m_session->GetHwnd(), WM_CLOSE, 0, 0); } } void EditorWindow::DestroyHostWindow() { if (HasLiveHostWindow()) { DestroyWindow(m_session->GetHwnd()); } } bool EditorWindow::OnResize(UINT width, UINT height) { const bool matchedPresentedPrediction = m_chromeController->ConsumePresentedPredictedClientPixelSizeMatch(width, height); if (!matchedPresentedPrediction) { m_chromeController->ClearPredictedClientPixelSize(); } if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr) { Host::RefreshBorderlessWindowDwmDecorations(hwnd); } if (matchedPresentedPrediction) { return false; } ApplyWindowResize(width, height); return true; } void EditorWindow::OnEnterSizeMove() { m_chromeController->BeginInteractiveResize(); } bool EditorWindow::OnExitSizeMove() { m_chromeController->EndInteractiveResize(); m_chromeController->ClearPredictedClientPixelSize(); UINT width = 0u; UINT height = 0u; if (QueryCurrentClientPixelSize(width, height)) { ApplyWindowResize(width, height); return true; } return false; } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); m_runtime->SetDpiScale(GetDpiScale()); if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr) { const LONG windowWidth = suggestedRect.right - suggestedRect.left; const LONG windowHeight = suggestedRect.bottom - suggestedRect.top; SetWindowPos( hwnd, nullptr, suggestedRect.left, suggestedRect.top, windowWidth, windowHeight, SWP_NOZORDER | SWP_NOACTIVATE); UINT clientWidth = 0u; UINT clientHeight = 0u; if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { ApplyWindowResize(clientWidth, clientHeight); } Host::RefreshBorderlessWindowDwmDecorations(hwnd); } std::ostringstream trace = {}; trace << "dpi changed to " << m_chromeController->GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", trace.str()); } bool EditorWindow::IsVerboseRuntimeTraceEnabled() { static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled(); return s_enabled; } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { using namespace EditorWindowSupport; using ::XCEngine::UI::UIDrawData; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputModifiers; using ::XCEngine::UI::UIPointerButton; using ::XCEngine::UI::UIRect; namespace { std::optional QueryCursorScreenPoint() { POINT screenPoint = {}; if (!GetCursorPos(&screenPoint)) { return std::nullopt; } return EditorWindowScreenPoint{ .x = screenPoint.x, .y = screenPoint.y, }; } std::uint8_t ButtonMask(UIPointerButton button) { switch (button) { case UIPointerButton::Left: return 1u << 0u; case UIPointerButton::Right: return 1u << 1u; case UIPointerButton::Middle: return 1u << 2u; case UIPointerButton::X1: return 1u << 3u; case UIPointerButton::X2: return 1u << 4u; case UIPointerButton::None: default: return 0u; } } std::uint8_t ButtonMaskFromModifiers(const UIInputModifiers& modifiers) { std::uint8_t mask = 0u; if (modifiers.leftMouse) { mask |= ButtonMask(UIPointerButton::Left); } if (modifiers.rightMouse) { mask |= ButtonMask(UIPointerButton::Right); } if (modifiers.middleMouse) { mask |= ButtonMask(UIPointerButton::Middle); } if (modifiers.x1Mouse) { mask |= ButtonMask(UIPointerButton::X1); } if (modifiers.x2Mouse) { mask |= ButtonMask(UIPointerButton::X2); } return mask; } std::uint8_t ResolveExpectedShellCaptureButtons( const UIEditorShellInteractionState& shellState) { std::uint8_t expectedButtons = 0u; const auto& dockHostState = shellState.workspaceInteractionState.dockHostInteractionState; if (dockHostState.splitterDragState.active || !dockHostState.activeTabDragNodeId.empty()) { expectedButtons |= ButtonMask(UIPointerButton::Left); } for (const auto& panelState : shellState.workspaceInteractionState.composeState.panelStates) { const auto& inputBridgeState = panelState.viewportShellState.inputBridgeState; if (inputBridgeState.captured) { expectedButtons |= ButtonMask(inputBridgeState.captureButton); } } return expectedButtons; } } // namespace EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorContext& editorContext, 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 (editorContext.IsValid()) { transferRequests = RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawData); } else { UIDrawList& invalidDrawList = drawData.EmplaceDrawList("XCEditorWindow.Invalid"); m_frameOrchestrator->AppendInvalidFrame(editorContext, invalidDrawList); } UIDrawList& windowChromeDrawList = drawData.EmplaceDrawList("XCEditorWindow.Chrome"); m_chromeController->AppendChrome(*this, windowChromeDrawList, width); const Host::D3D12WindowRenderLoopPresentResult presentResult = m_runtime->Present(drawData); if (!presentResult.warning.empty()) { LogRuntimeTrace("present", presentResult.warning); } return transferRequests; } EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage( EditorContext& editorContext, bool globalTabDragActive) { if (!m_runtime->IsReady() || m_session->GetHwnd() == nullptr) { return {}; } PAINTSTRUCT paintStruct = {}; BeginPaint(m_session->GetHwnd(), &paintStruct); const EditorWindowFrameTransferRequests transferRequests = EditorWindowFrameDriver::DriveImmediateFrame( *this, editorContext, globalTabDragActive); EndPaint(m_session->GetHwnd(), &paintStruct); return transferRequests; } void EditorWindow::QueueCompletedImmediateFrame( EditorWindowFrameTransferRequests transferRequests) { m_session->QueueCompletedImmediateFrame(std::move(transferRequests)); } bool EditorWindow::HasQueuedCompletedImmediateFrame() const { return m_session->HasQueuedCompletedImmediateFrame(); } EditorWindowFrameTransferRequests EditorWindow::ConsumeQueuedCompletedImmediateFrameTransferRequests() { return m_session->ConsumeQueuedCompletedImmediateFrameTransferRequests(); } void EditorWindow::RequestSkipNextSteadyStateFrame() { m_chromeController->RequestSkipNextSteadyStateFrame(); } bool EditorWindow::ConsumeSkipNextSteadyStateFrame() { return m_chromeController->ConsumeSkipNextSteadyStateFrame(); } UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const { if (m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this)) { return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips); } const float titleBarHeight = (std::min)(kBorderlessTitleBarHeightDips, clientHeightDips); return UIRect( 0.0f, titleBarHeight, clientWidthDips, (std::max)(0.0f, clientHeightDips - titleBarHeight)); } EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( EditorContext& editorContext, bool globalTabDragActive, const UIRect& workspaceBounds, UIDrawData& drawData) { SyncShellCapturedPointerButtonsFromSystemState(); std::vector frameEvents = m_inputController->TakePendingEvents(); const bool useDetachedTitleBarTabStrip = m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this); editorContext.AttachTextMeasurer(m_runtime->GetTextMeasurer()); const Host::D3D12WindowRenderLoopFrameContext frameContext = m_runtime->BeginFrame(); if (!frameContext.warning.empty()) { LogRuntimeTrace("viewport", frameContext.warning); } const EditorWindowFrameTransferRequests transferRequests = m_runtime->UpdateAndAppend( EditorWindowContentFrameContext{ .editorContext = editorContext, .bounds = workspaceBounds, .inputEvents = frameEvents, .cursorScreenPoint = QueryCursorScreenPoint(), .captureStatusText = m_runtime->BuildCaptureStatusText(), .primary = m_session->IsPrimary(), .globalTabDragActive = globalTabDragActive, .useDetachedTitleBarTabStrip = useDetachedTitleBarTabStrip, }, drawData); if (frameContext.canRenderViewports) { m_runtime->RenderRequestedViewports(frameContext.renderContext); } ApplyShellRuntimePointerCapture(); ApplyCurrentCursor(); return transferRequests; } void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { m_inputController->SyncInputModifiersFromSystemState(); const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons( m_runtime->GetShellInteractionState()); if (expectedButtons == 0u || m_inputController->HasPendingPointerStateReconciliationEvent()) { return; } const UIInputModifiers modifiers = m_inputController->GetCurrentModifiers(); if ((ButtonMaskFromModifiers(modifiers) & expectedButtons) == expectedButtons) { return; } QueueSyntheticPointerStateSyncEvent(modifiers); } void EditorWindow::ApplyShellRuntimePointerCapture() { const EditorWindowInputFeedbackBinding* inputFeedbackBinding = m_runtime->TryGetInputFeedbackBinding(); if (inputFeedbackBinding != nullptr && inputFeedbackBinding->HasShellInteractiveCapture()) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell); return; } if (inputFeedbackBinding != nullptr && inputFeedbackBinding->HasHostedContentCapture()) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::HostedContent); return; } if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::Shell)) { ReleasePointerCapture(EditorWindowPointerCaptureOwner::Shell); } if (OwnsPointerCapture(EditorWindowPointerCaptureOwner::HostedContent)) { ReleasePointerCapture(EditorWindowPointerCaptureOwner::HostedContent); } } } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App { using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPointerButton; namespace { bool IsScreenPointOverWindow(HWND hwnd, const POINT& screenPoint) { if (hwnd == nullptr || !IsWindow(hwnd)) { return false; } const HWND hitWindow = WindowFromPoint(screenPoint); if (hitWindow == nullptr || GetAncestor(hitWindow, GA_ROOT) != hwnd) { return false; } RECT windowRect = {}; if (!GetWindowRect(hwnd, &windowRect)) { return false; } return screenPoint.x >= windowRect.left && screenPoint.x < windowRect.right && screenPoint.y >= windowRect.top && screenPoint.y < windowRect.bottom; } } // namespace bool EditorWindow::ApplyCurrentCursor() const { if (!HasInteractiveCaptureState() && !IsPointerInsideClientArea()) { return false; } const HCURSOR cursor = LoadCursorW(nullptr, ResolveCurrentCursorResource()); if (cursor == nullptr) { return false; } SetCursor(cursor); return true; } bool EditorWindow::HasInteractiveCaptureState() const { const EditorWindowInputFeedbackBinding* inputFeedbackBinding = m_runtime->TryGetInputFeedbackBinding(); return (inputFeedbackBinding != nullptr && inputFeedbackBinding->HasInteractiveCapture()) || m_chromeController->IsBorderlessWindowDragRestoreArmed() || m_chromeController->IsBorderlessResizeActive() || m_inputController->HasPointerCaptureOwner(); } bool EditorWindow::OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const { return m_inputController->OwnsPointerCapture(owner); } void EditorWindow::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) { m_inputController->AcquirePointerCapture(m_session->GetHwnd(), owner); } void EditorWindow::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) { m_inputController->ReleasePointerCapture(m_session->GetHwnd(), owner); } void EditorWindow::ForceReleasePointerCapture() { m_inputController->ForceReleasePointerCapture(m_session->GetHwnd()); } void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) { const HWND hwnd = m_session->GetHwnd(); if (hwnd == nullptr || !IsWindow(hwnd) || GetCapture() == hwnd) { return; } const ::XCEngine::UI::UIPoint clientPoint = ConvertClientPixelsToDips( GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if (!ShouldStartImmediateUIEditorShellPointerCapture( m_runtime->GetShellFrame(), clientPoint)) { return; } AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell); } void EditorWindow::QueuePointerEvent( UIInputEventType type, UIPointerButton button, WPARAM wParam, LPARAM lParam, bool doubleClick) { m_inputController->QueuePointerEvent( type, button, ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), wParam, doubleClick); } void EditorWindow::QueueSyntheticPointerStateSyncEvent( const ::XCEngine::UI::UIInputModifiers& modifiers) { const HWND hwnd = m_session->GetHwnd(); if (hwnd == nullptr || !IsWindow(hwnd)) { return; } POINT screenPoint = {}; if (!GetCursorPos(&screenPoint)) { return; } if (!ScreenToClient(hwnd, &screenPoint)) { return; } m_inputController->QueueSyntheticPointerStateSyncEvent( ConvertClientPixelsToDips(screenPoint.x, screenPoint.y), modifiers); } void EditorWindow::QueuePointerLeaveEvent() { ::XCEngine::UI::UIPoint position = {}; if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr) { POINT clientPoint = {}; GetCursorPos(&clientPoint); ScreenToClient(hwnd, &clientPoint); position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); } m_inputController->QueuePointerLeaveEvent(position); } void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { if (m_session->GetHwnd() == nullptr) { return; } POINT screenPoint = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(m_session->GetHwnd(), &screenPoint); m_inputController->QueuePointerWheelEvent( ConvertClientPixelsToDips(screenPoint.x, screenPoint.y), wheelDelta, wParam); } bool EditorWindow::IsPointerInsideClientArea() const { const HWND hwnd = m_session->GetHwnd(); if (hwnd == nullptr || !IsWindow(hwnd)) { return false; } POINT screenPoint = {}; if (!GetCursorPos(&screenPoint)) { return false; } if (!IsScreenPointOverWindow(hwnd, screenPoint)) { return false; } const LPARAM pointParam = MAKELPARAM( static_cast(screenPoint.x), static_cast(screenPoint.y)); return SendMessageW(hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT; } LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { const EditorWindowInputFeedbackBinding* inputFeedbackBinding = m_runtime->TryGetInputFeedbackBinding(); const Host::BorderlessWindowResizeEdge borderlessResizeEdge = m_chromeController->IsBorderlessResizeActive() ? m_chromeController->GetBorderlessResizeEdge() : m_chromeController->GetHoveredBorderlessResizeEdge(); if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) { return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge); } const EditorWindowContentCursorKind hostedContentCursorKind = inputFeedbackBinding != nullptr ? inputFeedbackBinding->GetHostedContentCursorKind() : EditorWindowContentCursorKind::Arrow; switch (hostedContentCursorKind) { case EditorWindowContentCursorKind::ResizeEW: return IDC_SIZEWE; case EditorWindowContentCursorKind::ResizeNS: return IDC_SIZENS; case EditorWindowContentCursorKind::Arrow: default: break; } const EditorWindowContentCursorKind dockCursorKind = inputFeedbackBinding != nullptr ? inputFeedbackBinding->GetDockCursorKind() : EditorWindowContentCursorKind::Arrow; switch (dockCursorKind) { case EditorWindowContentCursorKind::ResizeEW: return IDC_SIZEWE; case EditorWindowContentCursorKind::ResizeNS: return IDC_SIZENS; case EditorWindowContentCursorKind::Arrow: default: return IDC_ARROW; } } } // namespace XCEngine::UI::Editor::App