#include "Platform/Win32/Windowing/EditorWindow.h" #include "Bootstrap/EditorResources.h" #include "Platform/Win32/Chrome/EditorWindowChromeController.h" #include "Platform/Win32/Windowing/EditorWindowSession.h" #include "Platform/Win32/Windowing/EditorWindowSupport.h" #include "Platform/Win32/Windowing/Win32EditorWindowRenderRuntimeSurface.h" #include "Platform/Win32/Runtime/EditorWindowInputController.h" #include "Windowing/Host/EditorWindowContentBindings.h" #include #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(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()) {} 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_owner.GetWindowId(); } HWND EditorWindow::GetHwnd() const { return m_session->GetHwnd(); } bool EditorWindow::HasHwnd() const { return m_session->HasHwnd(); } EditorWindowCategory EditorWindow::GetCategory() const { return m_owner.IsWorkspaceWindow() ? EditorWindowCategory::Workspace : EditorWindowCategory::Utility; } const EditorWindowChromePolicy& EditorWindow::GetChromePolicy() const { return m_owner.GetChromePolicy(); } EditorWindowLifecycleState EditorWindow::GetLifecycleState() const { return m_owner.GetLifecycleState(); } bool EditorWindow::IsPrimary() const { return m_owner.IsPrimary(); } bool EditorWindow::IsWorkspaceWindow() const { return m_owner.IsWorkspaceWindow(); } bool EditorWindow::IsUtilityWindow() const { return m_owner.IsUtilityWindow(); } bool EditorWindow::IsClosing() const { return m_owner.IsClosing(); } bool EditorWindow::IsDestroyed() const { return m_owner.IsDestroyed(); } bool EditorWindow::HasLiveHostWindow() const { const HWND hwnd = m_session->GetHwnd(); return hwnd != nullptr && IsWindow(hwnd); } EditorNativeWindowMetrics EditorWindow::CaptureMetrics() const { return EditorNativeWindowMetrics{ .dpiScale = GetDpiScale(), }; } bool EditorWindow::CaptureRuntimeSurface( const EditorHostWindow& window, EditorNativeWindowRuntimeSurface& outSurface) { (void)window; outSurface = {}; const HWND hwnd = m_session->GetHwnd(); if (hwnd == nullptr || !IsWindow(hwnd)) { return false; } std::uint32_t clientWidth = 0u; std::uint32_t clientHeight = 0u; if (!QueryCurrentClientPixelSize(clientWidth, clientHeight)) { clientWidth = 1u; clientHeight = 1u; } outSurface.renderSurface = std::make_shared(hwnd); outSurface.widthPixels = clientWidth; outSurface.heightPixels = clientHeight; outSurface.dpiScale = GetDpiScale(); return true; } bool EditorWindow::CaptureFrameSnapshot( const EditorHostWindow& window, const UIEditorShellInteractionState& shellState, EditorNativeWindowFrameSnapshot& outSnapshot) { outSnapshot = {}; if (!HasLiveHostWindow()) { return false; } std::uint32_t pixelWidth = 0u; std::uint32_t pixelHeight = 0u; if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { return false; } SyncShellCapturedPointerButtonsFromSystemState(shellState); const float width = PixelsToDips(static_cast(pixelWidth)); const float height = PixelsToDips(static_cast(pixelHeight)); const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(window); outSnapshot.widthPixels = pixelWidth; outSnapshot.heightPixels = pixelHeight; outSnapshot.widthDips = width; outSnapshot.heightDips = height; outSnapshot.dpiScale = GetDpiScale(); outSnapshot.workspaceBounds = ResolveWorkspaceBounds(window, width, height); outSnapshot.inputEvents = TakePendingInputEvents(); outSnapshot.cursorScreenPoint = QueryCursorScreenPoint(); outSnapshot.useDetachedTitleBarTabStrip = useDetachedTitleBarTabStrip; return outSnapshot.IsValid(); } void EditorWindow::ApplyFrameCommands( const EditorHostWindow& window, const EditorNativeWindowFrameCommands& commands) { if (commands.applyShellRuntimePointerCapture) { ApplyShellRuntimePointerCapture(window); } if (commands.applyCurrentCursor) { ApplyCurrentCursor(); } } const std::wstring& EditorWindow::GetTitle() const { return m_owner.GetTitle(); } std::string_view EditorWindow::GetCachedTitleText() const { return m_owner.GetCachedTitleText(); } const EditorWorkspaceWindowProjection* EditorWindow::TryGetWorkspaceProjection() const { return m_owner.TryGetWorkspaceProjection(); } EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() { return m_owner.TryGetDockHostBinding(); } const EditorWindowDockHostBinding* EditorWindow::TryGetDockHostBinding() const { return m_owner.TryGetDockHostBinding(); } void EditorWindow::AttachHwnd(HWND hwnd) { m_session->AttachHwnd(hwnd); m_owner.MarkNativeAttached(); } void EditorWindow::ApplyHostWindowTitle(const std::wstring& title) { if (HasLiveHostWindow()) { SetWindowTextW(m_session->GetHwnd(), title.c_str()); } } void EditorWindow::InvalidateHostWindow() const { if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr && IsWindow(hwnd)) { InvalidateRect(hwnd, nullptr, FALSE); } } 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); } void EditorWindow::ShutdownNativeInteraction() { std::ostringstream trace = {}; trace << "EditorWindow::ShutdownNativeInteraction begin windowId='" << GetWindowId() << "' hwnd=0x" << std::hex << std::uppercase << reinterpret_cast(GetHwnd()) << std::dec << " primary=" << (IsPrimary() ? 1 : 0) << " lifecycle=" << GetEditorWindowLifecycleStateName(GetLifecycleState()); LogRuntimeTrace("window-close", trace.str()); ForceReleasePointerCapture(); m_inputController->ClearPendingEvents(); m_chromeController->Reset(); LogRuntimeTrace( "window-close", "EditorWindow::ShutdownNativeInteraction end windowId='" + std::string(GetWindowId()) + "'"); } void EditorWindow::ResetNativeInteractionState() { ForceReleasePointerCapture(); m_inputController->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_owner.IsRenderReady() || width == 0u || height == 0u) { return false; } return m_owner.ApplyResize(width, height); } bool EditorWindow::QueryCurrentClientPixelSize( std::uint32_t& outWidth, std::uint32_t& 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( 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; } 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_owner.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_owner.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(); std::uint32_t width = 0u; std::uint32_t height = 0u; if (QueryCurrentClientPixelSize(width, height)) { ApplyWindowResize(static_cast(width), static_cast(height)); return true; } return false; } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); m_owner.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); std::uint32_t clientWidth = 0u; std::uint32_t clientHeight = 0u; if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { ApplyWindowResize(static_cast(clientWidth), static_cast(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 QueryNativeCursorScreenPoint() { 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 void EditorWindow::ValidateHostFrame() const { if (const HWND hwnd = m_session->GetHwnd(); hwnd != nullptr && IsWindow(hwnd)) { ValidateRect(hwnd, nullptr); } } 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( const EditorHostWindow& window, float clientWidthDips, float clientHeightDips) const { if (ShouldUseDetachedTitleBarTabStrip(window)) { 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)); } bool EditorWindow::ShouldUseDetachedTitleBarTabStrip( const EditorHostWindow& window) const { return m_chromeController->ShouldUseDetachedTitleBarTabStrip(window); } 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(shellState); 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 EditorHostWindow& window) { const EditorWindowInputFeedbackBinding* inputFeedbackBinding = window.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); } } 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 { 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_owner.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_owner.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_owner.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