From a539f34f943a10604547881993384cf4f084dea8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 3 Nov 2022 22:44:53 -0400 Subject: [PATCH 1/3] Implement windows IntermediatePoints for the Mouse input --- .../Interop/UnmanagedMethods.cs | 14 ++++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 84 ++++++++++++++++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 + 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f1ed53ea99..67d1deb9b7 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() { From 10351871303d92e01cd6149d8999798062679bda Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 Nov 2022 12:08:41 +0100 Subject: [PATCH 2/3] Added desktop solution filter. Loads on the projects required to run on desktop platforms. --- Avalonia.Desktop.slnf | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Avalonia.Desktop.slnf diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf new file mode 100644 index 0000000000..6ba05332be --- /dev/null +++ b/Avalonia.Desktop.slnf @@ -0,0 +1,60 @@ +{ + "solution": { + "path": "Avalonia.sln", + "projects": [ + "packages\\Avalonia\\Avalonia.csproj", + "samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj", + "samples\\ControlCatalog\\ControlCatalog.csproj", + "samples\\IntegrationTestApp\\IntegrationTestApp.csproj", + "samples\\MiniMvvm\\MiniMvvm.csproj", + "samples\\SampleControls\\ControlSamples.csproj", + "samples\\Sandbox\\Sandbox.csproj", + "src\\Avalonia.Base\\Avalonia.Base.csproj", + "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj", + "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj", + "src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj", + "src\\Avalonia.Controls\\Avalonia.Controls.csproj", + "src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj", + "src\\Avalonia.Desktop\\Avalonia.Desktop.csproj", + "src\\Avalonia.Diagnostics\\Avalonia.Diagnostics.csproj", + "src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj", + "src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj", + "src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj", + "src\\Avalonia.Headless\\Avalonia.Headless.csproj", + "src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj", + "src\\Avalonia.Native\\Avalonia.Native.csproj", + "src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj", + "src\\Avalonia.ReactiveUI\\Avalonia.ReactiveUI.csproj", + "src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj", + "src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj", + "src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj", + "src\\Avalonia.X11\\Avalonia.X11.csproj", + "src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj", + "src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj", + "src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj", + "src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj", + "src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj", + "src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj", + "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj", + "src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj", + "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", + "src\\tools\\DevGenerators\\DevGenerators.csproj", + "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj", + "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj", + "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj", + "tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj", + "tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj", + "tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj", + "tests\\Avalonia.Direct2D1.RenderTests\\Avalonia.Direct2D1.RenderTests.csproj", + "tests\\Avalonia.Direct2D1.UnitTests\\Avalonia.Direct2D1.UnitTests.csproj", + "tests\\Avalonia.IntegrationTests.Appium\\Avalonia.IntegrationTests.Appium.csproj", + "tests\\Avalonia.LeakTests\\Avalonia.LeakTests.csproj", + "tests\\Avalonia.Markup.UnitTests\\Avalonia.Markup.UnitTests.csproj", + "tests\\Avalonia.Markup.Xaml.UnitTests\\Avalonia.Markup.Xaml.UnitTests.csproj", + "tests\\Avalonia.ReactiveUI.UnitTests\\Avalonia.ReactiveUI.UnitTests.csproj", + "tests\\Avalonia.Skia.RenderTests\\Avalonia.Skia.RenderTests.csproj", + "tests\\Avalonia.Skia.UnitTests\\Avalonia.Skia.UnitTests.csproj", + "tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj" + ] + } +} \ No newline at end of file From 848ca76216ea23ea43a9da0f24faa6b660553b2d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 Nov 2022 12:12:34 +0100 Subject: [PATCH 3/3] Update build instructions. --- Documentation/build.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/Documentation/build.md b/Documentation/build.md index ddd38be887..fd6b26337c 100644 --- a/Documentation/build.md +++ b/Documentation/build.md @@ -1,8 +1,8 @@ # Windows -Avalonia requires at least Visual Studio 2022 and dotnet 6 SDK 6.0.100 to build on all platforms. +Avalonia requires at least Visual Studio 2022 and dotnet 7-rc2 SDK 7.0.100-rc.2 to build on all platforms. -### Clone the Avalonia repository +## Clone the Avalonia repository ``` git clone https://github.com/AvaloniaUI/Avalonia.git @@ -10,15 +10,30 @@ cd Avalonia git submodule update --init ``` -### Install the required version of the .NET Core SDK +## Install the required version of the .NET Core SDK Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the latest version of the .NET Core SDK compatible with Avalonia UI. Make sure to download the SDK (not just the "runtime") package. The version compatible is indicated within the [global.json](https://github.com/AvaloniaUI/Avalonia/blob/master/global.json) file. Note that Avalonia UI does not always use the latest version and is hardcoded to use the last version known to be compatible (SDK releases may break the builds from time-to-time). -### Open in Visual Studio +## Build and Run Avalonia -Open the `Avalonia.sln` solution in Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application. +``` +cd samples\ControlCatalog.NetCore +dotnet restore +dotnet run +``` + +## Opening in Visual Studio -### Troubleshooting +If you want to open Avalonia in Visual Studio you have two options: + +- Avalonia.sln: This contains the whole of Avalonia in including desktop, mobile and web. You must have a number of dotnet workloads installed in order to build everything in this solution +- Avalonia.Desktop.slnf: This solution filter opens only the parts of Avalonia required to run on desktop. This requires no extra workloads to be installed. + +Avalonia requires Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. + +Build and run `ControlCatalog.NetCore` project to see the sample application. + +### Visual Studio Troubleshooting * **Error CS0006: Avalonia.DesktopRuntime.dll could not be found** @@ -35,16 +50,15 @@ It's *not* possible to build the *whole* project on Linux/macOS. You can only bu MonoDevelop, Xamarin Studio and Visual Studio for Mac aren't capable of properly opening our solution. You can use Rider (at least 2017.2 EAP) or VSCode instead. They will fail to load most of platform specific projects, but you don't need them to run on .NET Core. -### Install the latest version of the .NET Core SDK +## Install the latest version of the .NET Core SDK Go to https://www.microsoft.com/net/core and follow the instructions for your OS. Make sure to download the SDK (not just the "runtime") package. -### Additional requirements for macOS +## Additional requirements for macOS The build process needs [Xcode](https://developer.apple.com/xcode/) to build the native library. Following the install instructions at the [Xcode](https://developer.apple.com/xcode/) website to properly install. - -### Clone the Avalonia repository +## Clone the Avalonia repository ``` git clone https://github.com/AvaloniaUI/Avalonia.git @@ -52,7 +66,7 @@ cd Avalonia git submodule update --init --recursive ``` -### Build native libraries (macOS only) +## Build native libraries (macOS only) On macOS it is necessary to build and manually install the respective native libraries using [Xcode](https://developer.apple.com/xcode/). Execute the build script in the root project with the `CompileNative` task. It will build the headers, build the libraries, and place them in the appropriate place to allow .NET to find them at compilation and run time. @@ -60,7 +74,7 @@ On macOS it is necessary to build and manually install the respective native lib ./build.sh CompileNative ``` -### Build and Run Avalonia +## Build and Run Avalonia ``` cd samples/ControlCatalog.NetCore