diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 50a9c62136..da3414ba6a 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -2298,6 +2298,13 @@ namespace Avalonia.Win32.Interop public int dwHoverTime; } + // TrackMouseEvent flags + public const uint TME_HOVER = 0x00000001; + public const uint TME_LEAVE = 0x00000002; + public const uint TME_NONCLIENT = 0x00000010; + public const uint TME_QUERY = 0x40000000; + public const uint TME_CANCEL = 0x80000000; + [Flags] public enum WindowPlacementFlags : uint { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index fc7fe731d0..664315c853 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; @@ -141,24 +142,64 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCMOUSEMOVE when !IsMouseInPointerEnabled: case WindowsMessage.WM_NCLBUTTONDOWN when !IsMouseInPointerEnabled: case WindowsMessage.WM_NCLBUTTONUP when !IsMouseInPointerEnabled: - if (lRet == IntPtr.Zero - && ShouldRedirectNonClientInput(hWnd, wParam, lParam)) + if (lRet == IntPtr.Zero) { - e = new RawPointerEventArgs( - _mouseDevice, - unchecked((uint)GetMessageTime()), - Owner, - (WindowsMessage)msg switch + var shouldRedirect = ShouldRedirectNonClientInput(hWnd, wParam, lParam); + + if (shouldRedirect) + { + // Track non-client mouse to receive WM_NCMOUSELEAVE + if (!_trackingNonClientMouse) { - WindowsMessage.WM_NCMOUSEMOVE => RawPointerEventType.Move, - WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType.LeftButtonDown, - WindowsMessage.WM_NCLBUTTONUP => RawPointerEventType.LeftButtonUp, - _ => throw new ArgumentOutOfRangeException(nameof(msg), msg, null) - }, - PointToClient(PointFromLParam(lParam)), - RawInputModifiers.None); + var tm = new TRACKMOUSEEVENT + { + cbSize = Marshal.SizeOf(), + dwFlags = TME_LEAVE | TME_NONCLIENT, + hwndTrack = _hwnd, + dwHoverTime = 0, + }; + TrackMouseEvent(ref tm); + _trackingNonClientMouse = true; + } + + e = new RawPointerEventArgs( + _mouseDevice, + unchecked((uint)GetMessageTime()), + Owner, + (WindowsMessage)msg switch + { + WindowsMessage.WM_NCMOUSEMOVE => RawPointerEventType.Move, + WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType.LeftButtonDown, + WindowsMessage.WM_NCLBUTTONUP => RawPointerEventType.LeftButtonUp, + _ => throw new ArgumentOutOfRangeException(nameof(msg), msg, null) + }, + PointToClient(PointFromLParam(lParam)), + RawInputModifiers.None); + } + else if (_trackingNonClientMouse && (WindowsMessage)msg == WindowsMessage.WM_NCMOUSEMOVE) + { + // Mouse moved in NC area but not over caption buttons - send leave event + _trackingNonClientMouse = false; + e = new RawPointerEventArgs( + _mouseDevice, + unchecked((uint)GetMessageTime()), + Owner, + RawPointerEventType.LeaveWindow, + new Point(-1, -1), + RawInputModifiers.None); + } } break; + case WindowsMessage.WM_NCMOUSELEAVE when !IsMouseInPointerEnabled: + _trackingNonClientMouse = false; + e = new RawPointerEventArgs( + _mouseDevice, + unchecked((uint)GetMessageTime()), + Owner, + RawPointerEventType.LeaveWindow, + new Point(-1, -1), + RawInputModifiers.None); + break; case WindowsMessage.WM_NCPOINTERUPDATE when _wmPointerEnabled: case WindowsMessage.WM_NCPOINTERDOWN when _wmPointerEnabled: case WindowsMessage.WM_NCPOINTERUP when _wmPointerEnabled: diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9cc07b8075..1318ef721f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -89,6 +89,7 @@ namespace Avalonia.Win32 private IconImpl? _iconImpl; private readonly Dictionary<(Icons type, uint dpi), Win32Icon> _iconCache = new(); private bool _trackingMouse;//ToDo - there is something missed. Needs investigation @Steven Kirk + private bool _trackingNonClientMouse; private bool _topmost; private double _scaling = 1; private uint _dpi = 96;