diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index be25e0fad1..c35b4a7919 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -46,7 +46,8 @@ namespace ControlCatalog.NetCore public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .With(new X11PlatformOptions{EnableMultiTouch = true}) + .With(new X11PlatformOptions {EnableMultiTouch = true}) + .With(new Win32PlatformOptions {EnableMultitouch = true}) .UseSkia() .UseReactiveUI(); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 7d6e8fc8ce..bc7fc1c9fa 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -574,6 +574,7 @@ namespace Avalonia.Win32.Interop WM_AFXLAST = 0x037F, WM_PENWINFIRST = 0x0380, WM_PENWINLAST = 0x038F, + WM_TOUCH = 0x0240, WM_APP = 0x8000, WM_USER = 0x0400, @@ -836,10 +837,16 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); + + [DllImport("user32")] + public static extern IntPtr GetMessageExtraInfo(); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")] public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx); + [DllImport("user32.dll")] + public static extern void RegisterTouchWindow(IntPtr hWnd, int flags); + [DllImport("user32.dll")] public static extern bool ReleaseCapture(); @@ -1035,6 +1042,17 @@ namespace Avalonia.Win32.Interop [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi); + [DllImport("user32")] + public static extern bool GetTouchInputInfo( + IntPtr hTouchInput, + uint cInputs, + [Out]TOUCHINPUT[] pInputs, + int cbSize + ); + + [DllImport("user32")] + public static extern bool CloseTouchInputHandle(IntPtr hTouchInput); + [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); @@ -1309,6 +1327,60 @@ namespace Avalonia.Win32.Interop public IntPtr hIconSm; } + [StructLayout(LayoutKind.Sequential)] + public struct TOUCHINPUT + { + public int X; + public int Y; + public IntPtr Source; + public uint Id; + public TouchInputFlags Flags; + public int Mask; + public uint Time; + public IntPtr ExtraInfo; + public int CxContact; + public int CyContact; + } + + [Flags] + public enum TouchInputFlags + { + /// + /// Movement has occurred. Cannot be combined with TOUCHEVENTF_DOWN. + /// + TOUCHEVENTF_MOVE = 0x0001, + + /// + /// The corresponding touch point was established through a new contact. Cannot be combined with TOUCHEVENTF_MOVE or TOUCHEVENTF_UP. + /// + TOUCHEVENTF_DOWN = 0x0002, + + /// + /// A touch point was removed. + /// + TOUCHEVENTF_UP = 0x0004, + + /// + /// A touch point is in range. This flag is used to enable touch hover support on compatible hardware. Applications that do not want support for hover can ignore this flag. + /// + TOUCHEVENTF_INRANGE = 0x0008, + + /// + /// Indicates that this TOUCHINPUT structure corresponds to a primary contact point. See the following text for more information on primary touch points. + /// + TOUCHEVENTF_PRIMARY = 0x0010, + + /// + /// When received using GetTouchInputInfo, this input was not coalesced. + /// + TOUCHEVENTF_NOCOALESCE = 0x0020, + + /// + /// The touch event came from the user's palm. + /// + TOUCHEVENTF_PALM = 0x0080 + } + [Flags] public enum OpenFileNameFlags { diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index f679c2410e..c45bf6389e 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -40,6 +40,7 @@ namespace Avalonia { public bool UseDeferredRendering { get; set; } = true; public bool AllowEglInitialization { get; set; } + public bool? EnableMultitouch { get; set; } } } @@ -59,7 +60,8 @@ namespace Avalonia.Win32 CreateMessageWindow(); } - public static bool UseDeferredRendering { get; set; } + public static bool UseDeferredRendering => Options.UseDeferredRendering; + public static Win32PlatformOptions Options { get; private set; } public Size DoubleClickSize => new Size( UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK), @@ -74,6 +76,7 @@ namespace Avalonia.Win32 public static void Initialize(Win32PlatformOptions options) { + Options = options; AvaloniaLocator.CurrentMutable .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) @@ -88,7 +91,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance); if (options.AllowEglInitialization) Win32GlManager.Initialize(); - UseDeferredRendering = options.UseDeferredRendering; + _uiThread = Thread.CurrentThread; if (OleContext.Current != null) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 081a713e95..cbccb16ac0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -30,6 +30,8 @@ namespace Avalonia.Win32 private UnmanagedMethods.WndProc _wndProcDelegate; private string _className; private IntPtr _hwnd; + private bool _multitouch; + private TouchDevice _touchDevice = new TouchDevice(); private IInputRoot _owner; private bool _trackingMouse; private bool _decorated = true; @@ -414,6 +416,15 @@ namespace Avalonia.Win32 IntPtr.Zero); } + bool ShouldIgnoreTouchEmulatedMessage() + { + if (!_multitouch) + return false; + var marker = 0xFF515700L; + var info = GetMessageExtraInfo().ToInt64(); + return (info & marker) == marker; + } + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -519,6 +530,8 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN: + if(ShouldIgnoreTouchEmulatedMessage()) + break; e = new RawMouseEventArgs( WindowsMouseDevice.Instance, timestamp, @@ -534,6 +547,8 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP: + if(ShouldIgnoreTouchEmulatedMessage()) + break; e = new RawMouseEventArgs( WindowsMouseDevice.Instance, timestamp, @@ -547,6 +562,8 @@ namespace Avalonia.Win32 break; case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE: + if(ShouldIgnoreTouchEmulatedMessage()) + break; if (!_trackingMouse) { var tm = new UnmanagedMethods.TRACKMOUSEEVENT @@ -611,7 +628,30 @@ namespace Avalonia.Win32 : RawMouseEventType.MiddleButtonDown, new Point(0, 0), GetMouseModifiers(wParam)); break; - + case WindowsMessage.WM_TOUCH: + var touchInputs = new TOUCHINPUT[wParam.ToInt32()]; + if (GetTouchInputInfo(lParam, (uint)wParam.ToInt32(), touchInputs, Marshal.SizeOf())) + { + foreach (var touchInput in touchInputs) + { + var pt = new POINT {X = touchInput.X / 100, Y = touchInput.Y / 100}; + UnmanagedMethods.ScreenToClient(_hwnd, ref pt); + Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, + _owner, + touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_UP) ? + RawMouseEventType.TouchEnd : + touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_DOWN) ? + RawMouseEventType.TouchBegin : + RawMouseEventType.TouchUpdate, + new Point(pt.X, pt.Y), + WindowsKeyboardDevice.Instance.Modifiers, + touchInput.Id)); + } + CloseTouchInputHandle(lParam); + return IntPtr.Zero; + } + + break; case WindowsMessage.WM_NCPAINT: if (!_decorated) { @@ -754,6 +794,10 @@ namespace Avalonia.Win32 Handle = new PlatformHandle(_hwnd, PlatformConstants.WindowHandleType); + _multitouch = Win32Platform.Options.EnableMultitouch ?? false; + if (_multitouch) + RegisterTouchWindow(_hwnd, 0); + if (UnmanagedMethods.ShCoreAvailable) { uint dpix, dpiy;