diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 4afb1068df..9b86154043 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1083,6 +1083,20 @@ namespace Avalonia.Win32.Interop public const int SizeOf_BITMAPINFOHEADER = 40; + [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] + unsafe internal static extern int GetMouseMovePointsEx( + uint cbSize, MOUSEMOVEPOINT* pointsIn, + MOUSEMOVEPOINT* pointsBufferOut, int nBufPoints, uint resolution); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] // For GetMouseMovePointsEx + public struct MOUSEMOVEPOINT + { + public int x; //Specifies the x-coordinate of the mouse + public int y; //Specifies the x-coordinate of the mouse + public int time; //Specifies the time stamp of the mouse coordinate + public IntPtr dwExtraInfo; //Specifies extra information associated with this coordinate. + } + [DllImport("user32.dll", SetLastError = true)] public static extern bool IsMouseInPointerEnabled(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index f8785371d9..c4136a239e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -272,13 +272,34 @@ namespace Avalonia.Win32 TrackMouseEvent(ref tm); } + var point = DipFromLParam(lParam); + + // Prepare points for the IntermediatePoints call. + var p = new POINT() + { + X = (int)(point.X * RenderScaling), + Y = (int)(point.Y * RenderScaling) + }; + ClientToScreen(_hwnd, ref p); + var currPoint = new MOUSEMOVEPOINT() + { + x = p.X & 0xFFFF, + y = p.Y & 0xFFFF, + time = (int)timestamp + }; + var prevPoint = _lastWmMousePoint; + _lastWmMousePoint = currPoint; + e = new RawPointerEventArgs( _mouseDevice, timestamp, _owner, RawPointerEventType.Move, - DipFromLParam(lParam), - GetMouseModifiers(wParam)); + point, + GetMouseModifiers(wParam)) + { + IntermediatePoints = new Lazy>(() => CreateLazyIntermediatePoints(currPoint, prevPoint)) + }; break; } @@ -782,6 +803,65 @@ namespace Avalonia.Win32 return null; } + private unsafe IReadOnlyList CreateLazyIntermediatePoints(MOUSEMOVEPOINT movePoint, MOUSEMOVEPOINT prevMovePoint) + { + // To understand some of this code, please check MS docs: + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmousemovepointsex#remarks + + fixed (MOUSEMOVEPOINT* movePoints = s_mouseHistoryInfos) + { + var movePointCopy = movePoint; + movePointCopy.time = 0; // empty "time" as otherwise WinAPI will always fail + int pointsCount = GetMouseMovePointsEx( + (uint)(Marshal.SizeOf(movePointCopy)), + &movePointCopy, movePoints, s_mouseHistoryInfos.Length, + 1); + + // GetMouseMovePointsEx can return -1 if point wasn't found or there is so beeg delay that original points were erased from the buffer. + if (pointsCount <= 1) + { + return Array.Empty(); + } + + s_intermediatePointsPooledList.Clear(); + s_intermediatePointsPooledList.Capacity = pointsCount; + for (int i = pointsCount - 1; i >= 1; i--) + { + var historyInfo = s_mouseHistoryInfos[i]; + // Skip points newer than current point. + if (historyInfo.time > movePoint.time || + (historyInfo.time == movePoint.time && + historyInfo.x == movePoint.x && + historyInfo.y == movePoint.y)) + { + continue; + } + // Skip poins older from previous WM_MOUSEMOVE point. + if (historyInfo.time < prevMovePoint.time || + (historyInfo.time == prevMovePoint.time && + historyInfo.x == prevMovePoint.x && + historyInfo.y == prevMovePoint.y)) + { + continue; + } + + // To support multiple screens. + if (historyInfo.x > 32767) + historyInfo.x -= 65536; + + if (historyInfo.y > 32767) + historyInfo.y -= 65536; + + var point = PointToClient(new PixelPoint(historyInfo.x, historyInfo.y)); + s_intermediatePointsPooledList.Add(new RawPointerPoint + { + Position = point + }); + } + return s_intermediatePointsPooledList; + } + } + private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId) { return device is TouchDevice diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5374614379..9ed1a50ff2 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -65,6 +65,7 @@ namespace Avalonia.Win32 private bool _isUsingComposition; private IBlurHost _blurHost; private PlatformResizeReason _resizeReason; + private MOUSEMOVEPOINT _lastWmMousePoint; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -107,6 +108,7 @@ namespace Avalonia.Win32 private static readonly POINTER_TOUCH_INFO[] s_historyTouchInfos = new POINTER_TOUCH_INFO[MaxPointerHistorySize]; private static readonly POINTER_PEN_INFO[] s_historyPenInfos = new POINTER_PEN_INFO[MaxPointerHistorySize]; private static readonly POINTER_INFO[] s_historyInfos = new POINTER_INFO[MaxPointerHistorySize]; + private static readonly MOUSEMOVEPOINT[] s_mouseHistoryInfos = new MOUSEMOVEPOINT[64]; public WindowImpl() {