#include "WindowMessageDispatcher.h" #include "Platform/Win32/EditorWindow.h" #include "WindowMessageHost.h" #include namespace XCEngine::UI::Editor::Host { namespace { constexpr UINT kMessageNcUaDrawCaption = 0x00AEu; constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; } // namespace struct WindowMessageDispatcher::DispatchContext { HWND hwnd = nullptr; WindowMessageHost& windowHost; App::EditorWindow& window; }; void WindowMessageDispatcher::RenderAndValidateWindow(const DispatchContext& context) { if (!context.window.IsRenderReady()) { return; } App::EditorWindowFrameTransferRequests transferRequests = context.window.RenderFrame( context.windowHost.GetEditorContext(), context.windowHost.IsGlobalTabDragActive()); if (transferRequests.HasPendingRequests()) { context.windowHost.HandleWindowFrameTransferRequests( context.window, std::move(transferRequests)); } if (context.hwnd != nullptr && IsWindow(context.hwnd)) { ValidateRect(context.hwnd, nullptr); } } bool WindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) { if (context.window.IsTrackingMouseLeave()) { return true; } TRACKMOUSEEVENT trackMouseEvent = {}; trackMouseEvent.cbSize = sizeof(trackMouseEvent); trackMouseEvent.dwFlags = TME_LEAVE; trackMouseEvent.hwndTrack = context.hwnd; if (!TrackMouseEvent(&trackMouseEvent)) { return false; } context.window.SetTrackingMouseLeave(true); return true; } bool WindowMessageDispatcher::TryHandleChromeHoverConsumption( const DispatchContext& context, LPARAM lParam, LRESULT& outResult) { const bool resizeHoverChanged = context.window.UpdateBorderlessWindowResizeHover(lParam); if (context.window.UpdateBorderlessWindowChromeHover(lParam)) { context.window.InvalidateHostWindow(); } if (resizeHoverChanged) { context.window.InvalidateHostWindow(); } EnsureTrackingMouseLeave(context); if (context.window.HasHoveredBorderlessResizeEdge()) { outResult = 0; return true; } const BorderlessWindowChromeHitTarget chromeHitTarget = context.window.HitTestBorderlessWindowChrome(lParam); if (chromeHitTarget == BorderlessWindowChromeHitTarget::MinimizeButton || chromeHitTarget == BorderlessWindowChromeHitTarget::MaximizeRestoreButton || chromeHitTarget == BorderlessWindowChromeHitTarget::CloseButton) { outResult = 0; return true; } return false; } bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( const DispatchContext& context, UINT message, WPARAM wParam, LPARAM lParam, LRESULT& outResult) { switch (message) { case WM_MOUSEMOVE: if (context.windowHost.HandleGlobalTabDragPointerMove(context.hwnd)) { outResult = 0; return true; } if (context.window.HandleBorderlessWindowResizePointerMove( context.windowHost.GetEditorContext(), context.windowHost.IsGlobalTabDragActive())) { outResult = 0; return true; } if (context.window.HandleBorderlessWindowChromeDragRestorePointerMove( context.windowHost.GetEditorContext(), context.windowHost.IsGlobalTabDragActive())) { outResult = 0; return true; } if (TryHandleChromeHoverConsumption(context, lParam, outResult)) { return true; } context.window.QueuePointerEvent( ::XCEngine::UI::UIInputEventType::PointerMove, ::XCEngine::UI::UIPointerButton::None, wParam, lParam); outResult = 0; return true; case WM_MOUSELEAVE: context.window.SetTrackingMouseLeave(false); context.window.ClearBorderlessWindowResizeState(); context.window.ClearBorderlessWindowChromeDragRestoreState(); context.window.ClearBorderlessWindowChromeState(); context.window.QueuePointerLeaveEvent(); outResult = 0; return true; case WM_LBUTTONDOWN: if (context.window.HandleBorderlessWindowResizeButtonDown(lParam)) { outResult = 0; return true; } if (context.window.HandleBorderlessWindowChromeButtonDown(lParam)) { outResult = 0; return true; } SetFocus(context.hwnd); context.window.QueuePointerEvent( ::XCEngine::UI::UIInputEventType::PointerButtonDown, ::XCEngine::UI::UIPointerButton::Left, wParam, lParam); outResult = 0; return true; case WM_LBUTTONUP: if (context.windowHost.HandleGlobalTabDragPointerButtonUp(context.hwnd)) { outResult = 0; return true; } if (context.window.HandleBorderlessWindowResizeButtonUp()) { outResult = 0; return true; } if (context.window.HandleBorderlessWindowChromeButtonUp( context.windowHost.GetEditorContext(), context.windowHost.IsGlobalTabDragActive(), lParam)) { outResult = 0; return true; } context.window.QueuePointerEvent( ::XCEngine::UI::UIInputEventType::PointerButtonUp, ::XCEngine::UI::UIPointerButton::Left, wParam, lParam); outResult = 0; return true; case WM_LBUTTONDBLCLK: if (context.window.HandleBorderlessWindowChromeDoubleClick( context.windowHost.GetEditorContext(), context.windowHost.IsGlobalTabDragActive(), lParam)) { outResult = 0; return true; } return false; case WM_MOUSEWHEEL: context.window.QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); outResult = 0; return true; default: return false; } } bool WindowMessageDispatcher::TryDispatchWindowInputMessage( const DispatchContext& context, UINT message, WPARAM wParam, LPARAM lParam, LRESULT& outResult) { if (TryDispatchWindowPointerMessage(context, message, wParam, lParam, outResult)) { return true; } switch (message) { case WM_SETFOCUS: context.window.SyncInputModifiersFromSystemState(); context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusGained); outResult = 0; return true; case WM_KILLFOCUS: context.window.ResetInputModifiers(); context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); outResult = 0; return true; case WM_CAPTURECHANGED: if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) && reinterpret_cast(lParam) != context.hwnd) { context.windowHost.EndGlobalTabDragSession(); outResult = 0; return true; } if (reinterpret_cast(lParam) != context.hwnd && context.window.HasInteractiveCaptureState()) { context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); context.window.ForceClearBorderlessWindowResizeState(); context.window.ClearBorderlessWindowChromeDragRestoreState(); context.window.ClearBorderlessWindowChromeState(); outResult = 0; return true; } if (reinterpret_cast(lParam) != context.hwnd) { context.window.ForceClearBorderlessWindowResizeState(); context.window.ClearBorderlessWindowChromeDragRestoreState(); context.window.ClearBorderlessWindowChromeState(); } return false; case WM_KEYDOWN: case WM_SYSKEYDOWN: if (wParam == VK_F12) { context.window.RequestManualScreenshot(); } context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyDown, wParam, lParam); outResult = 0; return true; case WM_KEYUP: case WM_SYSKEYUP: context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyUp, wParam, lParam); outResult = 0; return true; case WM_CHAR: context.window.QueueCharacterEvent(wParam, lParam); outResult = 0; return true; default: return false; } } bool WindowMessageDispatcher::TryDispatchWindowLifecycleMessage( const DispatchContext& context, UINT message, WPARAM wParam, LPARAM lParam, LRESULT& outResult) { switch (message) { case WM_DPICHANGED: if (lParam == 0) { return false; } context.window.OnDpiChanged( static_cast(LOWORD(wParam)), *reinterpret_cast(lParam)); RenderAndValidateWindow(context); outResult = 0; return true; case WM_ENTERSIZEMOVE: context.window.OnEnterSizeMove(); outResult = 0; return true; case WM_EXITSIZEMOVE: context.window.OnExitSizeMove(); RenderAndValidateWindow(context); outResult = 0; return true; case WM_SIZE: if (wParam != SIZE_MINIMIZED) { context.window.OnResize( static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); RenderAndValidateWindow(context); } outResult = 0; return true; case WM_PAINT: if (App::EditorWindowFrameTransferRequests transferRequests = context.window.OnPaintMessage( context.windowHost.GetEditorContext(), context.windowHost.IsGlobalTabDragActive()); transferRequests.HasPendingRequests()) { context.windowHost.HandleWindowFrameTransferRequests( context.window, std::move(transferRequests)); } outResult = 0; return true; case WM_ERASEBKGND: outResult = 1; return true; case WM_DESTROY: if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { context.windowHost.EndGlobalTabDragSession(); } context.windowHost.HandleDestroyedWindow(context.hwnd); outResult = 0; return true; default: return false; } } bool WindowMessageDispatcher::TryDispatchWindowChromeMessage( const DispatchContext& context, UINT message, WPARAM wParam, LPARAM lParam, LRESULT& outResult) { switch (message) { case WM_GETMINMAXINFO: if (context.window.IsBorderlessWindowEnabled() && context.window.HandleBorderlessWindowGetMinMaxInfo(lParam)) { outResult = 0; return true; } return false; case WM_NCCALCSIZE: if (context.window.IsBorderlessWindowEnabled()) { outResult = context.window.HandleBorderlessWindowNcCalcSize(wParam, lParam); return true; } return false; case WM_NCACTIVATE: if (context.window.IsBorderlessWindowEnabled()) { outResult = TRUE; return true; } return false; case WM_NCHITTEST: if (context.window.IsBorderlessWindowEnabled()) { outResult = HTCLIENT; return true; } return false; case WM_NCPAINT: case kMessageNcUaDrawCaption: case kMessageNcUaDrawFrame: if (context.window.IsBorderlessWindowEnabled()) { outResult = 0; return true; } return false; case WM_SYSCOMMAND: if (context.window.HandleBorderlessWindowSystemCommand( context.windowHost.GetEditorContext(), context.windowHost.IsGlobalTabDragActive(), wParam)) { outResult = 0; return true; } return false; case WM_SETCURSOR: if (LOWORD(lParam) == HTCLIENT && context.window.ApplyCurrentCursor()) { outResult = TRUE; return true; } return false; default: return false; } } bool WindowMessageDispatcher::TryDispatch( HWND hwnd, WindowMessageHost& windowHost, App::EditorWindow& window, UINT message, WPARAM wParam, LPARAM lParam, LRESULT& outResult) { const DispatchContext context = { .hwnd = hwnd, .windowHost = windowHost, .window = window, }; return TryDispatchWindowChromeMessage(context, message, wParam, lParam, outResult) || TryDispatchWindowLifecycleMessage(context, message, wParam, lParam, outResult) || TryDispatchWindowInputMessage(context, message, wParam, lParam, outResult); } } // namespace XCEngine::UI::Editor::Host