diff --git a/dirs.proj b/dirs.proj index 4939a158bb..e56320e73f 100644 --- a/dirs.proj +++ b/dirs.proj @@ -8,10 +8,11 @@ - - + + + diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 93f5611ec4..c35b4a7919 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -46,6 +46,8 @@ namespace ControlCatalog.NetCore public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() + .With(new X11PlatformOptions {EnableMultiTouch = true}) + .With(new Win32PlatformOptions {EnableMultitouch = true}) .UseSkia() .UseReactiveUI(); diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 9f1899acc5..1cddb9d295 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -31,6 +31,7 @@ + diff --git a/samples/ControlCatalog/Pages/PointersPage.cs b/samples/ControlCatalog/Pages/PointersPage.cs new file mode 100644 index 0000000000..a1359519e6 --- /dev/null +++ b/samples/ControlCatalog/Pages/PointersPage.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Immutable; + +namespace ControlCatalog.Pages +{ + public class PointersPage : Control + { + class PointerInfo + { + public Point Point { get; set; } + public Color Color { get; set; } + } + + private static Color[] AllColors = new[] + { + Colors.Aqua, + Colors.Beige, + Colors.Chartreuse, + Colors.Coral, + Colors.Fuchsia, + Colors.Crimson, + Colors.Lavender, + Colors.Orange, + Colors.Orchid, + Colors.ForestGreen, + Colors.SteelBlue, + Colors.PapayaWhip, + Colors.PaleVioletRed, + Colors.Goldenrod, + Colors.Maroon, + Colors.Moccasin, + Colors.Navy, + Colors.Wheat, + Colors.Violet, + Colors.Sienna, + Colors.Indigo, + Colors.Honeydew + }; + + private Dictionary _pointers = new Dictionary(); + + public PointersPage() + { + ClipToBounds = true; + } + + void UpdatePointer(PointerEventArgs e) + { + if (!_pointers.TryGetValue(e.Pointer, out var info)) + { + if (e.RoutedEvent == PointerMovedEvent) + return; + var colors = AllColors.Except(_pointers.Values.Select(c => c.Color)).ToArray(); + var color = colors[new Random().Next(0, colors.Length - 1)]; + _pointers[e.Pointer] = info = new PointerInfo {Color = color}; + } + + info.Point = e.GetPosition(this); + InvalidateVisual(); + } + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + UpdatePointer(e); + e.Pointer.Capture(this); + base.OnPointerPressed(e); + } + + protected override void OnPointerMoved(PointerEventArgs e) + { + UpdatePointer(e); + base.OnPointerMoved(e); + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + _pointers.Remove(e.Pointer); + InvalidateVisual(); + } + + public override void Render(DrawingContext context) + { + context.FillRectangle(Brushes.Transparent, new Rect(default, Bounds.Size)); + foreach (var pt in _pointers.Values) + { + var brush = new ImmutableSolidColorBrush(pt.Color); + context.DrawGeometry(brush, null, new EllipseGeometry(new Rect(pt.Point.X - 75, pt.Point.Y - 75, + 150, 150))); + } + + } + } +} diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs index 112925ab0f..463d499aad 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs @@ -33,7 +33,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers return null; } - RawMouseEventType? mouseEventType = null; + RawPointerEventType? mouseEventType = null; var eventTime = DateTime.Now; //Basic touch support switch (e.Action) @@ -42,17 +42,17 @@ namespace Avalonia.Android.Platform.Specific.Helpers //may be bot flood the evnt system with too many event especially on not so powerfull mobile devices if ((eventTime - _lastTouchMoveEventTime).TotalMilliseconds > 10) { - mouseEventType = RawMouseEventType.Move; + mouseEventType = RawPointerEventType.Move; } break; case MotionEventActions.Down: - mouseEventType = RawMouseEventType.LeftButtonDown; + mouseEventType = RawPointerEventType.LeftButtonDown; break; case MotionEventActions.Up: - mouseEventType = RawMouseEventType.LeftButtonUp; + mouseEventType = RawPointerEventType.LeftButtonUp; break; } @@ -75,14 +75,14 @@ namespace Avalonia.Android.Platform.Specific.Helpers //we need to generate mouse move before first mouse down event //as this is the way buttons are working every time //otherwise there is a problem sometimes - if (mouseEventType == RawMouseEventType.LeftButtonDown) + if (mouseEventType == RawPointerEventType.LeftButtonDown) { - var me = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, - RawMouseEventType.Move, _point, InputModifiers.None); + var me = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, + RawPointerEventType.Move, _point, InputModifiers.None); _view.Input(me); } - var mouseEvent = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, + var mouseEvent = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, mouseEventType.Value, _point, InputModifiers.LeftMouseButton); _view.Input(mouseEvent); diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 224d9b782b..53852defb3 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -176,18 +176,5 @@ namespace Avalonia.Controls.Primitives if (e.MouseButton == MouseButton.Left) CalendarLeftMouseButtonUp?.Invoke(this, e); } - - /// - /// We need to simulate the MouseLeftButtonUp event for the - /// CalendarButton that stays in Pressed state after MouseCapture is - /// released since there is no actual MouseLeftButtonUp event for the - /// release. - /// - /// Event arguments. - internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e) - { - e.Handled = false; - base.OnPointerReleased(e); - } } } diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs index 1b36f92fd3..cb2a98e5ca 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs @@ -234,18 +234,5 @@ namespace Avalonia.Controls.Primitives if (e.MouseButton == MouseButton.Left) CalendarDayButtonMouseUp?.Invoke(this, e); } - - /// - /// We need to simulate the MouseLeftButtonUp event for the - /// CalendarDayButton that stays in Pressed state after MouseCapture is - /// released since there is no actual MouseLeftButtonUp event for the - /// release. - /// - /// Event arguments. - internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e) - { - e.Handled = false; - base.OnPointerReleased(e); - } } } diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index fb6dacaf81..8232697c18 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -934,22 +934,6 @@ namespace Avalonia.Controls.Primitives // The button is in Pressed state. Change the state to normal. if (e.Device.Captured == b) e.Device.Capture(null); - // null check is added for unit tests - if (_downEventArg != null) - { - var arg = - new PointerReleasedEventArgs() - { - Device = _downEventArg.Device, - MouseButton = _downEventArg.MouseButton, - Handled = _downEventArg.Handled, - InputModifiers = _downEventArg.InputModifiers, - Route = _downEventArg.Route, - Source = _downEventArg.Source - }; - - b.SendMouseLeftButtonUp(arg); - } _lastCalendarDayButton = b; } } @@ -1221,21 +1205,7 @@ namespace Avalonia.Controls.Primitives if (e.Device.Captured == b) e.Device.Capture(null); //b.ReleaseMouseCapture(); - if (_downEventArgYearView != null) - { - var args = - new PointerReleasedEventArgs() - { - Device = _downEventArgYearView.Device, - MouseButton = _downEventArgYearView.MouseButton, - Handled = _downEventArgYearView.Handled, - InputModifiers = _downEventArgYearView.InputModifiers, - Route = _downEventArgYearView.Route, - Source = _downEventArgYearView.Source - }; - - b.SendMouseLeftButtonUp(args); - } + _lastCalendarButton = b; } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index d8473dc613..d0ab0a0c8b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -337,12 +337,9 @@ namespace Avalonia.Controls { base.OnPointerEnter(e); - RaiseEvent(new PointerEventArgs - { - Device = e.Device, - RoutedEvent = PointerEnterItemEvent, - Source = this, - }); + var point = e.GetPointerPoint(null); + RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position, + point.Properties, e.InputModifiers)); } /// @@ -350,12 +347,9 @@ namespace Avalonia.Controls { base.OnPointerLeave(e); - RaiseEvent(new PointerEventArgs - { - Device = e.Device, - RoutedEvent = PointerLeaveItemEvent, - Source = this, - }); + var point = e.GetPointerPoint(null); + RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position, + point.Properties, e.InputModifiers)); } /// diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 73854b9f60..5f63a44717 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -373,9 +373,9 @@ namespace Avalonia.Controls.Platform protected internal virtual void RawInput(RawInputEventArgs e) { - var mouse = e as RawMouseEventArgs; + var mouse = e as RawPointerEventArgs; - if (mouse?.Type == RawMouseEventType.NonClientLeftButtonDown) + if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown) { Menu.Close(); } diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 0918da1a90..76f17332bf 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -43,7 +43,7 @@ namespace Avalonia.Platform _lastPosition = default(Point); _allowedEffects = allowedEffects; - using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) + using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) { using (_inputManager.PreProcess.OfType().Subscribe(ProcessKeyEvents)) { @@ -153,7 +153,7 @@ namespace Avalonia.Platform } } - private void ProcessMouseEvents(RawMouseEventArgs e) + private void ProcessMouseEvents(RawPointerEventArgs e) { if (!_initialInputModifiers.HasValue) _initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS; @@ -174,22 +174,22 @@ namespace Avalonia.Platform switch (e.Type) { - case RawMouseEventType.LeftButtonDown: - case RawMouseEventType.RightButtonDown: - case RawMouseEventType.MiddleButtonDown: - case RawMouseEventType.NonClientLeftButtonDown: + case RawPointerEventType.LeftButtonDown: + case RawPointerEventType.RightButtonDown: + case RawPointerEventType.MiddleButtonDown: + case RawPointerEventType.NonClientLeftButtonDown: CancelDragging(); e.Handled = true; return; - case RawMouseEventType.LeaveWindow: + case RawPointerEventType.LeaveWindow: RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break; - case RawMouseEventType.LeftButtonUp: + case RawPointerEventType.LeftButtonUp: CheckDraggingAccepted(InputModifiers.LeftMouseButton); break; - case RawMouseEventType.MiddleButtonUp: + case RawPointerEventType.MiddleButtonUp: CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break; - case RawMouseEventType.RightButtonUp: + case RawPointerEventType.RightButtonUp: CheckDraggingAccepted(InputModifiers.RightMouseButton); break; - case RawMouseEventType.Move: + case RawPointerEventType.Move: var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS; if (_initialInputModifiers.Value != mods) { diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index f349bcf059..e02d46c1df 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -421,9 +421,9 @@ namespace Avalonia.Controls.Primitives private void ListenForNonClientClick(RawInputEventArgs e) { - var mouse = e as RawMouseEventArgs; + var mouse = e as RawPointerEventArgs; - if (!StaysOpen && mouse?.Type == RawMouseEventType.NonClientLeftButtonDown) + if (!StaysOpen && mouse?.Type == RawPointerEventType.NonClientLeftButtonDown) { Close(); } diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 6293cbfbfd..1ef03b49ce 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -39,21 +39,21 @@ namespace Avalonia.Controls.Remote.Server KeyboardDevice = AvaloniaLocator.Current.GetService(); } - private static RawMouseEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed) + private static RawPointerEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed) { switch (button) { case Avalonia.Remote.Protocol.Input.MouseButton.Left: - return pressed ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp; + return pressed ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp; case Avalonia.Remote.Protocol.Input.MouseButton.Middle: - return pressed ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp; + return pressed ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp; case Avalonia.Remote.Protocol.Input.MouseButton.Right: - return pressed ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp; + return pressed ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp; default: - return RawMouseEventType.Move; + return RawPointerEventType.Move; } } @@ -166,11 +166,11 @@ namespace Avalonia.Controls.Remote.Server { Dispatcher.UIThread.Post(() => { - Input?.Invoke(new RawMouseEventArgs( + Input?.Invoke(new RawPointerEventArgs( MouseDevice, 0, InputRoot, - RawMouseEventType.Move, + RawPointerEventType.Move, new Point(pointer.X, pointer.Y), GetAvaloniaInputModifiers(pointer.Modifiers))); }, DispatcherPriority.Input); @@ -179,7 +179,7 @@ namespace Avalonia.Controls.Remote.Server { Dispatcher.UIThread.Post(() => { - Input?.Invoke(new RawMouseEventArgs( + Input?.Invoke(new RawPointerEventArgs( MouseDevice, 0, InputRoot, @@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server { Dispatcher.UIThread.Post(() => { - Input?.Invoke(new RawMouseEventArgs( + Input?.Invoke(new RawPointerEventArgs( MouseDevice, 0, InputRoot, diff --git a/src/Avalonia.Input/IMouseDevice.cs b/src/Avalonia.Input/IMouseDevice.cs index a1d1bb3eb8..7e6bf657ae 100644 --- a/src/Avalonia.Input/IMouseDevice.cs +++ b/src/Avalonia.Input/IMouseDevice.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia.Input { /// @@ -11,6 +13,9 @@ namespace Avalonia.Input /// /// Gets the mouse position, in screen coordinates. /// + [Obsolete("Use PointerEventArgs.GetPosition")] PixelPoint Position { get; } + + void SceneInvalidated(IInputRoot root, Rect rect); } } diff --git a/src/Avalonia.Input/IPointer.cs b/src/Avalonia.Input/IPointer.cs new file mode 100644 index 0000000000..a3f051ce7f --- /dev/null +++ b/src/Avalonia.Input/IPointer.cs @@ -0,0 +1,18 @@ +namespace Avalonia.Input +{ + public interface IPointer + { + int Id { get; } + void Capture(IInputElement control); + IInputElement Captured { get; } + PointerType Type { get; } + bool IsPrimary { get; } + + } + + public enum PointerType + { + Mouse, + Touch + } +} diff --git a/src/Avalonia.Input/IPointerDevice.cs b/src/Avalonia.Input/IPointerDevice.cs index 932cbc989f..de775d90f2 100644 --- a/src/Avalonia.Input/IPointerDevice.cs +++ b/src/Avalonia.Input/IPointerDevice.cs @@ -1,18 +1,20 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using Avalonia.VisualTree; namespace Avalonia.Input { public interface IPointerDevice : IInputDevice { + [Obsolete("Use IPointer")] IInputElement Captured { get; } - + + [Obsolete("Use IPointer")] void Capture(IInputElement control); + [Obsolete("Use PointerEventArgs.GetPosition")] Point GetPosition(IVisual relativeTo); - - void SceneInvalidated(IInputRoot root, Rect rect); } } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index c195209305..90d9c37bd4 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -14,14 +14,18 @@ namespace Avalonia.Input /// /// Represents a mouse device. /// - public class MouseDevice : IMouseDevice + public class MouseDevice : IMouseDevice, IPointer { private int _clickCount; private Rect _lastClickRect; private ulong _lastClickTime; private IInputElement _captured; private IDisposable _capturedSubscription; - + + PointerType IPointer.Type => PointerType.Mouse; + bool IPointer.IsPrimary => true; + int IPointer.Id { get; } = Pointer.GetNextFreeId(); + /// /// Gets the control that is currently capturing by the mouse, if any. /// @@ -96,7 +100,7 @@ namespace Avalonia.Input public void ProcessRawEvent(RawInputEventArgs e) { - if (!e.Handled && e is RawMouseEventArgs margs) + if (!e.Handled && e is RawPointerEventArgs margs) ProcessRawEvent(margs); } @@ -117,42 +121,53 @@ namespace Avalonia.Input } } - private void ProcessRawEvent(RawMouseEventArgs e) + int ButtonCount(PointerPointProperties props) + { + var rv = 0; + if (props.IsLeftButtonPressed) + rv++; + if (props.IsMiddleButtonPressed) + rv++; + if (props.IsRightButtonPressed) + rv++; + return rv; + } + + private void ProcessRawEvent(RawPointerEventArgs e) { Contract.Requires(e != null); var mouse = (IMouseDevice)e.Device; Position = e.Root.PointToScreen(e.Position); - + var props = CreateProperties(e); switch (e.Type) { - case RawMouseEventType.LeaveWindow: + case RawPointerEventType.LeaveWindow: LeaveWindow(mouse, e.Root, e.InputModifiers); break; - case RawMouseEventType.LeftButtonDown: - case RawMouseEventType.RightButtonDown: - case RawMouseEventType.MiddleButtonDown: - e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position, - e.Type == RawMouseEventType.LeftButtonDown - ? MouseButton.Left - : e.Type == RawMouseEventType.RightButtonDown ? MouseButton.Right : MouseButton.Middle, - e.InputModifiers); + case RawPointerEventType.LeftButtonDown: + case RawPointerEventType.RightButtonDown: + case RawPointerEventType.MiddleButtonDown: + if (ButtonCount(props) > 1) + e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers); + else + e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position, + props, e.InputModifiers); break; - case RawMouseEventType.LeftButtonUp: - case RawMouseEventType.RightButtonUp: - case RawMouseEventType.MiddleButtonUp: - e.Handled = MouseUp(mouse, e.Root, e.Position, - e.Type == RawMouseEventType.LeftButtonUp - ? MouseButton.Left - : e.Type == RawMouseEventType.RightButtonUp ? MouseButton.Right : MouseButton.Middle, - e.InputModifiers); + case RawPointerEventType.LeftButtonUp: + case RawPointerEventType.RightButtonUp: + case RawPointerEventType.MiddleButtonUp: + if (ButtonCount(props) != 0) + e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers); + else + e.Handled = MouseUp(mouse, e.Root, e.Position, props, e.InputModifiers); break; - case RawMouseEventType.Move: - e.Handled = MouseMove(mouse, e.Root, e.Position, e.InputModifiers); + case RawPointerEventType.Move: + e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers); break; - case RawMouseEventType.Wheel: - e.Handled = MouseWheel(mouse, e.Root, e.Position, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers); + case RawPointerEventType.Wheel: + e.Handled = MouseWheel(mouse, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers); break; } } @@ -165,7 +180,30 @@ namespace Avalonia.Input ClearPointerOver(this, root, inputModifiers); } - private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers) + + PointerPointProperties CreateProperties(RawPointerEventArgs args) + { + var rv = new PointerPointProperties(args.InputModifiers); + + if (args.Type == RawPointerEventType.LeftButtonDown) + rv.IsLeftButtonPressed = true; + if (args.Type == RawPointerEventType.MiddleButtonDown) + rv.IsMiddleButtonPressed = true; + if (args.Type == RawPointerEventType.RightButtonDown) + rv.IsRightButtonPressed = true; + if (args.Type == RawPointerEventType.LeftButtonUp) + rv.IsLeftButtonPressed = false; + if (args.Type == RawPointerEventType.MiddleButtonUp) + rv.IsMiddleButtonPressed = false; + if (args.Type == RawPointerEventType.RightButtonDown) + rv.IsRightButtonPressed = false; + return rv; + } + + private MouseButton _lastMouseDownButton; + private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, + PointerPointProperties properties, + InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -190,16 +228,8 @@ namespace Avalonia.Input _lastClickTime = timestamp; _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2)); - - var e = new PointerPressedEventArgs - { - Device = this, - RoutedEvent = InputElement.PointerPressedEvent, - Source = source, - ClickCount = _clickCount, - MouseButton = button, - InputModifiers = inputModifiers - }; + _lastMouseDownButton = properties.GetObsoleteMouseButton(); + var e = new PointerPressedEventArgs(source, this, root, p, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; @@ -209,7 +239,8 @@ namespace Avalonia.Input return false; } - private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, InputModifiers inputModifiers) + private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties properties, + InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -226,19 +257,15 @@ namespace Avalonia.Input source = Captured; } - var e = new PointerEventArgs - { - Device = this, - RoutedEvent = InputElement.PointerMovedEvent, - Source = source, - InputModifiers = inputModifiers - }; + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, this, root, + p, properties, inputModifiers); source?.RaiseEvent(e); return e.Handled; } - private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, MouseButton button, InputModifiers inputModifiers) + private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties props, + InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -248,14 +275,7 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerReleasedEventArgs - { - Device = this, - RoutedEvent = InputElement.PointerReleasedEvent, - Source = source, - MouseButton = button, - InputModifiers = inputModifiers - }; + var e = new PointerReleasedEventArgs(source, this, root, p, props, inputModifiers, _lastMouseDownButton); source?.RaiseEvent(e); return e.Handled; @@ -264,7 +284,9 @@ namespace Avalonia.Input return false; } - private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, Vector delta, InputModifiers inputModifiers) + private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, + PointerPointProperties props, + Vector delta, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -274,14 +296,7 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerWheelEventArgs - { - Device = this, - RoutedEvent = InputElement.PointerWheelChangedEvent, - Source = source, - Delta = delta, - InputModifiers = inputModifiers - }; + var e = new PointerWheelEventArgs(source, this, root, p, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; @@ -306,18 +321,19 @@ namespace Avalonia.Input return Captured ?? root.InputHitTest(p); } + PointerEventArgs CreateSimpleEvent(RoutedEvent ev, IInteractive source, InputModifiers inputModifiers) + { + return new PointerEventArgs(ev, source, this, null, default, + new PointerPointProperties(inputModifiers), inputModifiers); + } + private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); var element = root.PointerOverElement; - var e = new PointerEventArgs - { - RoutedEvent = InputElement.PointerLeaveEvent, - Device = device, - InputModifiers = inputModifiers - }; + var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, element, inputModifiers); if (element!=null && !element.IsAttachedToVisualTree) { @@ -384,7 +400,6 @@ namespace Avalonia.Input IInputElement branch = null; - var e = new PointerEventArgs { Device = device, InputModifiers = inputModifiers }; var el = element; while (el != null) @@ -398,8 +413,8 @@ namespace Avalonia.Input } el = root.PointerOverElement; - e.RoutedEvent = InputElement.PointerLeaveEvent; + var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, el, inputModifiers); if (el!=null && branch!=null && !el.IsAttachedToVisualTree) { ClearChildrenPointerOver(e,branch,false); diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs new file mode 100644 index 0000000000..bdf2501b32 --- /dev/null +++ b/src/Avalonia.Input/Pointer.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using Avalonia.VisualTree; + +namespace Avalonia.Input +{ + public class Pointer : IPointer, IDisposable + { + private static int s_NextFreePointerId = 1000; + public static int GetNextFreeId() => s_NextFreePointerId++; + + public Pointer(int id, PointerType type, bool isPrimary, IInputElement implicitlyCaptured) + { + Id = id; + Type = type; + IsPrimary = isPrimary; + ImplicitlyCaptured = implicitlyCaptured; + if (ImplicitlyCaptured != null) + ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached; + } + + public int Id { get; } + + public void Capture(IInputElement control) + { + if (Captured != null) + Captured.DetachedFromVisualTree -= OnCaptureDetached; + Captured = control; + if (Captured != null) + Captured.DetachedFromVisualTree += OnCaptureDetached; + } + + IInputElement GetNextCapture(IVisual parent) => + parent as IInputElement ?? parent.GetVisualAncestors().OfType().FirstOrDefault(); + + private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e) + { + Capture(GetNextCapture(e.Parent)); + } + + private void OnImplicitCaptureDetached(object sender, VisualTreeAttachmentEventArgs e) + { + ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached; + ImplicitlyCaptured = GetNextCapture(e.Parent); + if (ImplicitlyCaptured != null) + ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached; + } + + public IInputElement Captured { get; private set; } + public IInputElement ImplicitlyCaptured { get; private set; } + public IInputElement GetEffectiveCapture() => Captured ?? ImplicitlyCaptured; + + public PointerType Type { get; } + public bool IsPrimary { get; } + public void Dispose() + { + if (ImplicitlyCaptured != null) + ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached; + if (Captured != null) + Captured.DetachedFromVisualTree -= OnCaptureDetached; + } + } +} diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 7b2497c460..1d07190a81 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.VisualTree; @@ -8,25 +10,65 @@ namespace Avalonia.Input { public class PointerEventArgs : RoutedEventArgs { - public PointerEventArgs() - { + private readonly IVisual _rootVisual; + private readonly Point _rootVisualPosition; + private readonly PointerPointProperties _properties; + public PointerEventArgs(RoutedEvent routedEvent, + IInteractive source, + IPointer pointer, + IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, + InputModifiers modifiers) + : base(routedEvent) + { + Source = source; + _rootVisual = rootVisual; + _rootVisualPosition = rootVisualPosition; + _properties = properties; + Pointer = pointer; + InputModifiers = modifiers; } - public PointerEventArgs(RoutedEvent routedEvent) - : base(routedEvent) + class EmulatedDevice : IPointerDevice { + private readonly PointerEventArgs _ev; + public EmulatedDevice(PointerEventArgs ev) + { + _ev = ev; + } + + public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException(); + + public IInputElement Captured => _ev.Pointer.Captured; + public void Capture(IInputElement control) + { + _ev.Pointer.Capture(control); + } + + public Point GetPosition(IVisual relativeTo) => _ev.GetPosition(relativeTo); } - public IPointerDevice Device { get; set; } + public IPointer Pointer { get; } + + private IPointerDevice _device; + + [Obsolete("Use Pointer to get pointer-specific information")] + public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this)); - public InputModifiers InputModifiers { get; set; } + public InputModifiers InputModifiers { get; } public Point GetPosition(IVisual relativeTo) { - return Device.GetPosition(relativeTo); + if (_rootVisual == null) + return default; + if (relativeTo == null) + return _rootVisualPosition; + return _rootVisualPosition * _rootVisual.TransformToVisual(relativeTo) ?? default; } + + public PointerPoint GetPointerPoint(IVisual relativeTo) + => new PointerPoint(Pointer, GetPosition(relativeTo), _properties); } public enum MouseButton @@ -39,32 +81,39 @@ namespace Avalonia.Input public class PointerPressedEventArgs : PointerEventArgs { - public PointerPressedEventArgs() - : base(InputElement.PointerPressedEvent) - { - } + private readonly int _obsoleteClickCount; - public PointerPressedEventArgs(RoutedEvent routedEvent) - : base(routedEvent) + public PointerPressedEventArgs( + IInteractive source, + IPointer pointer, + IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, + InputModifiers modifiers, + int obsoleteClickCount = 1) + : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, properties, + modifiers) { + _obsoleteClickCount = obsoleteClickCount; } - public int ClickCount { get; set; } - public MouseButton MouseButton { get; set; } + [Obsolete("Use DoubleTapped or DoubleRightTapped event instead")] + public int ClickCount => _obsoleteClickCount; + + [Obsolete] public MouseButton MouseButton => GetPointerPoint(null).Properties.GetObsoleteMouseButton(); } public class PointerReleasedEventArgs : PointerEventArgs { - public PointerReleasedEventArgs() - : base(InputElement.PointerReleasedEvent) - { - } - - public PointerReleasedEventArgs(RoutedEvent routedEvent) - : base(routedEvent) + public PointerReleasedEventArgs( + IInteractive source, IPointer pointer, + IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, InputModifiers modifiers, + MouseButton obsoleteMouseButton) + : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition, + properties, modifiers) { + MouseButton = obsoleteMouseButton; } - public MouseButton MouseButton { get; set; } + [Obsolete()] + public MouseButton MouseButton { get; private set; } } } diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs new file mode 100644 index 0000000000..7117b5709c --- /dev/null +++ b/src/Avalonia.Input/PointerPoint.cs @@ -0,0 +1,45 @@ +namespace Avalonia.Input +{ + public sealed class PointerPoint + { + public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties) + { + Pointer = pointer; + Position = position; + Properties = properties; + } + public IPointer Pointer { get; } + public PointerPointProperties Properties { get; } + public Point Position { get; } + } + + public sealed class PointerPointProperties + { + public bool IsLeftButtonPressed { get; set; } + public bool IsMiddleButtonPressed { get; set; } + public bool IsRightButtonPressed { get; set; } + + public PointerPointProperties() + { + + } + + public PointerPointProperties(InputModifiers modifiers) + { + IsLeftButtonPressed = modifiers.HasFlag(InputModifiers.LeftMouseButton); + IsMiddleButtonPressed = modifiers.HasFlag(InputModifiers.MiddleMouseButton); + IsRightButtonPressed = modifiers.HasFlag(InputModifiers.RightMouseButton); + } + + public MouseButton GetObsoleteMouseButton() + { + if (IsLeftButtonPressed) + return MouseButton.Left; + if (IsMiddleButtonPressed) + return MouseButton.Middle; + if (IsRightButtonPressed) + return MouseButton.Right; + return MouseButton.None; + } + } +} diff --git a/src/Avalonia.Input/PointerWheelEventArgs.cs b/src/Avalonia.Input/PointerWheelEventArgs.cs index 401d1d847c..b409cc81bd 100644 --- a/src/Avalonia.Input/PointerWheelEventArgs.cs +++ b/src/Avalonia.Input/PointerWheelEventArgs.cs @@ -1,10 +1,21 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Interactivity; +using Avalonia.VisualTree; + namespace Avalonia.Input { public class PointerWheelEventArgs : PointerEventArgs { public Vector Delta { get; set; } + + public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + Point rootVisualPosition, + PointerPointProperties properties, InputModifiers modifiers, Vector delta) + : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, properties, modifiers) + { + Delta = delta; + } } } diff --git a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs index d2e5faab6c..186ad99efc 100644 --- a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs @@ -4,7 +4,7 @@ namespace Avalonia.Input.Raw { - public class RawMouseWheelEventArgs : RawMouseEventArgs + public class RawMouseWheelEventArgs : RawPointerEventArgs { public RawMouseWheelEventArgs( IInputDevice device, @@ -12,7 +12,7 @@ namespace Avalonia.Input.Raw IInputRoot root, Point position, Vector delta, InputModifiers inputModifiers) - : base(device, timestamp, root, RawMouseEventType.Wheel, position, inputModifiers) + : base(device, timestamp, root, RawPointerEventType.Wheel, position, inputModifiers) { Delta = delta; } diff --git a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs similarity index 84% rename from src/Avalonia.Input/Raw/RawMouseEventArgs.cs rename to src/Avalonia.Input/Raw/RawPointerEventArgs.cs index c5637d66cc..b728844e97 100644 --- a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs @@ -5,7 +5,7 @@ using System; namespace Avalonia.Input.Raw { - public enum RawMouseEventType + public enum RawPointerEventType { LeaveWindow, LeftButtonDown, @@ -17,15 +17,18 @@ namespace Avalonia.Input.Raw Move, Wheel, NonClientLeftButtonDown, + TouchBegin, + TouchUpdate, + TouchEnd } /// /// A raw mouse event. /// - public class RawMouseEventArgs : RawInputEventArgs + public class RawPointerEventArgs : RawInputEventArgs { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The associated device. /// The event timestamp. @@ -33,11 +36,11 @@ namespace Avalonia.Input.Raw /// The type of the event. /// The mouse position, in client DIPs. /// The input modifiers. - public RawMouseEventArgs( + public RawPointerEventArgs( IInputDevice device, ulong timestamp, IInputRoot root, - RawMouseEventType type, + RawPointerEventType type, Point position, InputModifiers inputModifiers) : base(device, timestamp) @@ -64,7 +67,7 @@ namespace Avalonia.Input.Raw /// /// Gets the type of the event. /// - public RawMouseEventType Type { get; private set; } + public RawPointerEventType Type { get; private set; } /// /// Gets the input modifiers. diff --git a/src/Avalonia.Input/Raw/RawTouchEventArgs.cs b/src/Avalonia.Input/Raw/RawTouchEventArgs.cs new file mode 100644 index 0000000000..5299633b26 --- /dev/null +++ b/src/Avalonia.Input/Raw/RawTouchEventArgs.cs @@ -0,0 +1,15 @@ +namespace Avalonia.Input.Raw +{ + public class RawTouchEventArgs : RawPointerEventArgs + { + public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root, + RawPointerEventType type, Point position, InputModifiers inputModifiers, + long touchPointId) + : base(device, timestamp, root, type, position, inputModifiers) + { + TouchPointId = touchPointId; + } + + public long TouchPointId { get; set; } + } +} diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs new file mode 100644 index 0000000000..80d8a5e780 --- /dev/null +++ b/src/Avalonia.Input/TouchDevice.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.Input.Raw; +using Avalonia.VisualTree; + +namespace Avalonia.Input +{ + /// + /// Handles raw touch events + /// + /// This class is supposed to be used on per-toplevel basis, don't use a shared one + /// + /// + public class TouchDevice : IInputDevice + { + Dictionary _pointers = new Dictionary(); + + static InputModifiers GetModifiers(InputModifiers modifiers, bool left) + { + var mask = (InputModifiers)0x7fffffff ^ InputModifiers.LeftMouseButton ^ InputModifiers.MiddleMouseButton ^ + InputModifiers.RightMouseButton; + modifiers &= mask; + if (left) + modifiers |= InputModifiers.LeftMouseButton; + return modifiers; + } + + public void ProcessRawEvent(RawInputEventArgs ev) + { + var args = (RawTouchEventArgs)ev; + if (!_pointers.TryGetValue(args.TouchPointId, out var pointer)) + { + if (args.Type == RawPointerEventType.TouchEnd) + return; + var hit = args.Root.InputHitTest(args.Position); + + _pointers[args.TouchPointId] = pointer = new Pointer(Pointer.GetNextFreeId(), + PointerType.Touch, _pointers.Count == 0, hit); + } + + + var target = pointer.GetEffectiveCapture() ?? args.Root; + if (args.Type == RawPointerEventType.TouchBegin) + { + var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary); + target.RaiseEvent(new PointerPressedEventArgs(target, pointer, + args.Root, args.Position, new PointerPointProperties(modifiers), + modifiers)); + } + + if (args.Type == RawPointerEventType.TouchEnd) + { + _pointers.Remove(args.TouchPointId); + var modifiers = GetModifiers(args.InputModifiers, false); + using (pointer) + { + target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, + args.Root, args.Position, new PointerPointProperties(modifiers), + modifiers, pointer.IsPrimary ? MouseButton.Left : MouseButton.None)); + } + } + + if (args.Type == RawPointerEventType.TouchUpdate) + { + var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary); + target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root, + args.Position, new PointerPointProperties(modifiers), modifiers)); + } + } + + } +} diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 73b8834375..638879ba14 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -226,7 +226,7 @@ namespace Avalonia.Native break; default: - Input?.Invoke(new RawMouseEventArgs(_mouse, timeStamp, _inputRoot, (RawMouseEventType)type, point.ToAvaloniaPoint(), (InputModifiers)modifiers)); + Input?.Invoke(new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (InputModifiers)modifiers)); break; } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index ce03113169..cf5902eff7 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -28,6 +28,7 @@ namespace Avalonia.X11 public X11PlatformOptions Options { get; private set; } public void Initialize(X11PlatformOptions options) { + Options = options; XInitThreads(); Display = XOpenDisplay(IntPtr.Zero); DeferredDisplay = XOpenDisplay(IntPtr.Zero); @@ -66,7 +67,7 @@ namespace Avalonia.X11 GlxGlPlatformFeature.TryInitialize(Info); } - Options = options; + } public IntPtr DeferredDisplay { get; set; } @@ -96,6 +97,7 @@ namespace Avalonia public bool UseEGL { get; set; } public bool UseGpu { get; set; } = true; public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication"; + public bool? EnableMultiTouch { get; set; } } public static class AvaloniaX11PlatformExtensions { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index c5e77fe352..01beebfff1 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -314,9 +314,9 @@ namespace Avalonia.X11 else if (ev.type == XEventName.FocusOut) Deactivated?.Invoke(); else if (ev.type == XEventName.MotionNotify) - MouseEvent(RawMouseEventType.Move, ref ev, ev.MotionEvent.state); + MouseEvent(RawPointerEventType.Move, ref ev, ev.MotionEvent.state); else if (ev.type == XEventName.LeaveNotify) - MouseEvent(RawMouseEventType.LeaveWindow, ref ev, ev.CrossingEvent.state); + MouseEvent(RawPointerEventType.LeaveWindow, ref ev, ev.CrossingEvent.state); else if (ev.type == XEventName.PropertyNotify) { OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0); @@ -326,9 +326,9 @@ namespace Avalonia.X11 if (ActivateTransientChildIfNeeded()) return; if (ev.ButtonEvent.button < 4) - MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown - : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown - : RawMouseEventType.RightButtonDown, ref ev, ev.ButtonEvent.state); + MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonDown + : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonDown + : RawPointerEventType.RightButtonDown, ref ev, ev.ButtonEvent.state); else { var delta = ev.ButtonEvent.button == 4 @@ -347,9 +347,9 @@ namespace Avalonia.X11 else if (ev.type == XEventName.ButtonRelease) { if (ev.ButtonEvent.button < 4) - MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonUp - : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonUp - : RawMouseEventType.RightButtonUp, ref ev, ev.ButtonEvent.state); + MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonUp + : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonUp + : RawPointerEventType.RightButtonUp, ref ev, ev.ButtonEvent.state); } else if (ev.type == XEventName.ConfigureNotify) { @@ -577,7 +577,7 @@ namespace Avalonia.X11 public void ScheduleInput(RawInputEventArgs args) { - if (args is RawMouseEventArgs mouse) + if (args is RawPointerEventArgs mouse) mouse.Position = mouse.Position / Scaling; if (args is RawDragEvent drag) drag.Location = drag.Location / Scaling; @@ -598,13 +598,13 @@ namespace Avalonia.X11 } } - void MouseEvent(RawMouseEventType type, ref XEvent ev, XModifierMask mods) + void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods) { - var mev = new RawMouseEventArgs( + var mev = new RawPointerEventArgs( _mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot, type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods)); - if(type == RawMouseEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawMouseEventArgs ma) - if (ma.Type == RawMouseEventType.Move) + if(type == RawPointerEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawPointerEventArgs ma) + if (ma.Type == RawPointerEventType.Move) { _lastEvent.Event = mev; return; diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index ee73ccc907..0a78c0dfd9 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -11,6 +11,7 @@ namespace Avalonia.X11 unsafe class XI2Manager { private X11Info _x11; + private bool _multitouch; private Dictionary _clients = new Dictionary(); class DeviceInfo { @@ -77,11 +78,14 @@ namespace Avalonia.X11 private PointerDeviceInfo _pointerDevice; private AvaloniaX11Platform _platform; + private readonly TouchDevice _touchDevice = new TouchDevice(); + public bool Init(AvaloniaX11Platform platform) { _platform = platform; _x11 = platform.Info; + _multitouch = platform.Options?.EnableMultiTouch ?? false; var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display, (int)XiPredefinedDeviceId.XIAllMasterDevices, out int num); for (var c = 0; c < num; c++) @@ -121,16 +125,23 @@ namespace Avalonia.X11 public XEventMask AddWindow(IntPtr xid, IXI2Client window) { _clients[xid] = window; - - XiSelectEvents(_x11.Display, xid, new Dictionary> + var events = new List { - [_pointerDevice.Id] = new List() + XiEventType.XI_Motion, + XiEventType.XI_ButtonPress, + XiEventType.XI_ButtonRelease + }; + + if (_multitouch) + events.AddRange(new[] { - XiEventType.XI_Motion, - XiEventType.XI_ButtonPress, - XiEventType.XI_ButtonRelease, - } - }); + XiEventType.XI_TouchBegin, + XiEventType.XI_TouchUpdate, + XiEventType.XI_TouchEnd + }); + + XiSelectEvents(_x11.Display, xid, + new Dictionary> {[_pointerDevice.Id] = events}); // We are taking over mouse input handling from here return XEventMask.PointerMotionMask @@ -154,8 +165,9 @@ namespace Avalonia.X11 _pointerDevice.Update(changed->Classes, changed->NumClasses); } - //TODO: this should only be used for non-touch devices - if (xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion) + + if ((xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion) + || (xev->evtype>=XiEventType.XI_TouchBegin&&xev->evtype<=XiEventType.XI_TouchEnd)) { var dev = (XIDeviceEvent*)xev; if (_clients.TryGetValue(dev->EventWindow, out var client)) @@ -165,6 +177,23 @@ namespace Avalonia.X11 void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev) { + if (ev.Type == XiEventType.XI_TouchBegin + || ev.Type == XiEventType.XI_TouchUpdate + || ev.Type == XiEventType.XI_TouchEnd) + { + var type = ev.Type == XiEventType.XI_TouchBegin ? + RawPointerEventType.TouchBegin : + (ev.Type == XiEventType.XI_TouchUpdate ? + RawPointerEventType.TouchUpdate : + RawPointerEventType.TouchEnd); + client.ScheduleInput(new RawTouchEventArgs(_touchDevice, + ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail)); + return; + } + + if (_multitouch && ev.Emulated) + return; + if (ev.Type == XiEventType.XI_Motion) { Vector scrollDelta = default; @@ -194,23 +223,23 @@ namespace Avalonia.X11 client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, ev.Position, scrollDelta, ev.Modifiers)); if (_pointerDevice.HasMotion(ev)) - client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, - RawMouseEventType.Move, ev.Position, ev.Modifiers)); + client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, + RawPointerEventType.Move, ev.Position, ev.Modifiers)); } if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease) { var down = ev.Type == XiEventType.XI_ButtonPress; var type = - ev.Button == 1 ? (down ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp) - : ev.Button == 2 ? (down ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp) - : ev.Button == 3 ? (down ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp) - : (RawMouseEventType?)null; + ev.Button == 1 ? (down ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp) + : ev.Button == 2 ? (down ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp) + : ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp) + : (RawPointerEventType?)null; if (type.HasValue) - client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, + client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, type.Value, ev.Position, ev.Modifiers)); } - + _pointerDevice.UpdateValuators(ev.Valuators); } } @@ -222,6 +251,8 @@ namespace Avalonia.X11 public ulong Timestamp { get; } public Point Position { get; } public int Button { get; set; } + public int Detail { get; set; } + public bool Emulated { get; set; } public Dictionary Valuators { get; } public ParsedDeviceEvent(XIDeviceEvent* ev) { @@ -258,6 +289,8 @@ namespace Avalonia.X11 Valuators[c] = *values++; if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease) Button = ev->detail; + Detail = ev->detail; + Emulated = ev->flags.HasFlag(XiDeviceEventFlags.XIPointerEmulated); } } diff --git a/src/Avalonia.X11/XIStructs.cs b/src/Avalonia.X11/XIStructs.cs index ef49a72c43..4675ef47f2 100644 --- a/src/Avalonia.X11/XIStructs.cs +++ b/src/Avalonia.X11/XIStructs.cs @@ -230,13 +230,20 @@ namespace Avalonia.X11 public double root_y; public double event_x; public double event_y; - public int flags; + public XiDeviceEventFlags flags; public XIButtonState buttons; public XIValuatorState valuators; public XIModifierState mods; public XIModifierState group; } + [Flags] + public enum XiDeviceEventFlags : int + { + None = 0, + XIPointerEmulated = (1 << 16) + } + [StructLayout(LayoutKind.Sequential)] unsafe struct XIEvent { diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index c4697462d2..bff50a979d 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -145,17 +145,17 @@ namespace Avalonia.Gtk3 private unsafe bool OnButton(IntPtr w, IntPtr ev, IntPtr userdata) { var evnt = (GdkEventButton*)ev; - var e = new RawMouseEventArgs( + var e = new RawPointerEventArgs( Gtk3Platform.Mouse, evnt->time, _inputRoot, evnt->type == GdkEventType.ButtonRelease ? evnt->button == 1 - ? RawMouseEventType.LeftButtonUp - : evnt->button == 3 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp + ? RawPointerEventType.LeftButtonUp + : evnt->button == 3 ? RawPointerEventType.RightButtonUp : RawPointerEventType.MiddleButtonUp : evnt->button == 1 - ? RawMouseEventType.LeftButtonDown - : evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown, + ? RawPointerEventType.LeftButtonDown + : evnt->button == 3 ? RawPointerEventType.RightButtonDown : RawPointerEventType.MiddleButtonDown, new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state)); OnInput(e); return true; @@ -179,11 +179,11 @@ namespace Avalonia.Gtk3 var evnt = (GdkEventMotion*)ev; var position = new Point(evnt->x, evnt->y); Native.GdkEventRequestMotions(ev); - var e = new RawMouseEventArgs( + var e = new RawPointerEventArgs( Gtk3Platform.Mouse, evnt->time, _inputRoot, - RawMouseEventType.Move, + RawPointerEventType.Move, position, GetModifierKeys(evnt->state)); OnInput(e); @@ -237,10 +237,10 @@ namespace Avalonia.Gtk3 { var evnt = (GdkEventCrossing*) pev; var position = new Point(evnt->x, evnt->y); - OnInput(new RawMouseEventArgs(Gtk3Platform.Mouse, + OnInput(new RawPointerEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot, - RawMouseEventType.Move, + RawPointerEventType.Move, position, GetModifierKeys(evnt->state))); return true; } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs index 4fa57dbf00..b982b98d38 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs @@ -76,9 +76,9 @@ namespace Avalonia.LinuxFramebuffer _y = Math.Min(_height, Math.Max(0, _y + ev.value)); else return; - Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice, + Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice, LinuxFramebufferPlatform.Timestamp, - LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y), + LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y), InputModifiers.None)); } if (ev.type ==(int) EvType.EV_ABS) @@ -89,24 +89,24 @@ namespace Avalonia.LinuxFramebuffer _y = TranslateAxis(device.AbsY.Value, ev.value, _height); else return; - Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice, + Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice, LinuxFramebufferPlatform.Timestamp, - LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y), + LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y), InputModifiers.None)); } if (ev.type == (short) EvType.EV_KEY) { - RawMouseEventType? type = null; + RawPointerEventType? type = null; if (ev.code == (ushort) EvKey.BTN_LEFT) - type = ev.value == 1 ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp; + type = ev.value == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp; if (ev.code == (ushort)EvKey.BTN_RIGHT) - type = ev.value == 1 ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp; + type = ev.value == 1 ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp; if (ev.code == (ushort) EvKey.BTN_MIDDLE) - type = ev.value == 1 ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp; + type = ev.value == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp; if (!type.HasValue) return; - Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice, + Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice, LinuxFramebufferPlatform.Timestamp, LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers))); } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 7005459487..c89d0a15cf 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -160,19 +160,19 @@ namespace Avalonia.Win32.Interop.Wpf return rv; } - void MouseEvent(RawMouseEventType type, MouseEventArgs e) - => _ttl.Input?.Invoke(new RawMouseEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type, + void MouseEvent(RawPointerEventType type, MouseEventArgs e) + => _ttl.Input?.Invoke(new RawPointerEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type, e.GetPosition(this).ToAvaloniaPoint(), GetModifiers())); protected override void OnMouseDown(MouseButtonEventArgs e) { - RawMouseEventType type; + RawPointerEventType type; if(e.ChangedButton == MouseButton.Left) - type = RawMouseEventType.LeftButtonDown; + type = RawPointerEventType.LeftButtonDown; else if (e.ChangedButton == MouseButton.Middle) - type = RawMouseEventType.MiddleButtonDown; + type = RawPointerEventType.MiddleButtonDown; else if (e.ChangedButton == MouseButton.Right) - type = RawMouseEventType.RightButtonDown; + type = RawPointerEventType.RightButtonDown; else return; MouseEvent(type, e); @@ -181,13 +181,13 @@ namespace Avalonia.Win32.Interop.Wpf protected override void OnMouseUp(MouseButtonEventArgs e) { - RawMouseEventType type; + RawPointerEventType type; if (e.ChangedButton == MouseButton.Left) - type = RawMouseEventType.LeftButtonUp; + type = RawPointerEventType.LeftButtonUp; else if (e.ChangedButton == MouseButton.Middle) - type = RawMouseEventType.MiddleButtonUp; + type = RawPointerEventType.MiddleButtonUp; else if (e.ChangedButton == MouseButton.Right) - type = RawMouseEventType.RightButtonUp; + type = RawPointerEventType.RightButtonUp; else return; MouseEvent(type, e); @@ -196,14 +196,14 @@ namespace Avalonia.Win32.Interop.Wpf protected override void OnMouseMove(MouseEventArgs e) { - MouseEvent(RawMouseEventType.Move, e); + MouseEvent(RawPointerEventType.Move, e); } protected override void OnMouseWheel(MouseWheelEventArgs e) => _ttl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, (uint) e.Timestamp, _inputRoot, e.GetPosition(this).ToAvaloniaPoint(), new Vector(0, e.Delta), GetModifiers())); - protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawMouseEventType.LeaveWindow, e); + protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawPointerEventType.LeaveWindow, e); protected override void OnKeyDown(KeyEventArgs e) => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, RawKeyEventType.KeyDown, 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..7f07f36de8 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,34 +530,40 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN: - e = new RawMouseEventArgs( + if(ShouldIgnoreTouchEmulatedMessage()) + break; + e = new RawPointerEventArgs( WindowsMouseDevice.Instance, timestamp, _owner, msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN - ? RawMouseEventType.LeftButtonDown + ? RawPointerEventType.LeftButtonDown : msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN - ? RawMouseEventType.RightButtonDown - : RawMouseEventType.MiddleButtonDown, + ? RawPointerEventType.RightButtonDown + : RawPointerEventType.MiddleButtonDown, DipFromLParam(lParam), GetMouseModifiers(wParam)); break; case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP: - e = new RawMouseEventArgs( + if(ShouldIgnoreTouchEmulatedMessage()) + break; + e = new RawPointerEventArgs( WindowsMouseDevice.Instance, timestamp, _owner, msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP - ? RawMouseEventType.LeftButtonUp + ? RawPointerEventType.LeftButtonUp : msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONUP - ? RawMouseEventType.RightButtonUp - : RawMouseEventType.MiddleButtonUp, + ? RawPointerEventType.RightButtonUp + : RawPointerEventType.MiddleButtonUp, DipFromLParam(lParam), GetMouseModifiers(wParam)); break; case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE: + if(ShouldIgnoreTouchEmulatedMessage()) + break; if (!_trackingMouse) { var tm = new UnmanagedMethods.TRACKMOUSEEVENT @@ -560,11 +577,11 @@ namespace Avalonia.Win32 UnmanagedMethods.TrackMouseEvent(ref tm); } - e = new RawMouseEventArgs( + e = new RawPointerEventArgs( WindowsMouseDevice.Instance, timestamp, _owner, - RawMouseEventType.Move, + RawPointerEventType.Move, DipFromLParam(lParam), GetMouseModifiers(wParam)); break; @@ -589,29 +606,52 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE: _trackingMouse = false; - e = new RawMouseEventArgs( + e = new RawPointerEventArgs( WindowsMouseDevice.Instance, timestamp, _owner, - RawMouseEventType.LeaveWindow, + RawPointerEventType.LeaveWindow, new Point(), WindowsKeyboardDevice.Instance.Modifiers); break; case UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN: - e = new RawMouseEventArgs( + e = new RawPointerEventArgs( WindowsMouseDevice.Instance, timestamp, _owner, msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN - ? RawMouseEventType.NonClientLeftButtonDown + ? RawPointerEventType.NonClientLeftButtonDown : msg == (int)UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN - ? RawMouseEventType.RightButtonDown - : RawMouseEventType.MiddleButtonDown, + ? RawPointerEventType.RightButtonDown + : RawPointerEventType.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) ? + RawPointerEventType.TouchEnd : + touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_DOWN) ? + RawPointerEventType.TouchBegin : + RawPointerEventType.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; diff --git a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj index 683c256b7b..e57fcc643f 100644 --- a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj +++ b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj @@ -6,6 +6,7 @@ + diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 83d10b8b44..15e8b35056 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -86,11 +86,11 @@ namespace Avalonia.iOS { var location = touch.LocationInView(this).ToAvalonia(); - Input?.Invoke(new RawMouseEventArgs( + Input?.Invoke(new RawPointerEventArgs( iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot, - RawMouseEventType.LeftButtonUp, + RawPointerEventType.LeftButtonUp, location, InputModifiers.None)); } @@ -104,11 +104,11 @@ namespace Avalonia.iOS { var location = touch.LocationInView(this).ToAvalonia(); _touchLastPoint = location; - Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot, - RawMouseEventType.Move, location, InputModifiers.None)); + Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot, + RawPointerEventType.Move, location, InputModifiers.None)); - Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot, - RawMouseEventType.LeftButtonDown, location, InputModifiers.None)); + Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot, + RawPointerEventType.LeftButtonDown, location, InputModifiers.None)); } } @@ -119,8 +119,8 @@ namespace Avalonia.iOS { var location = touch.LocationInView(this).ToAvalonia(); if (iOSPlatform.MouseDevice.Captured != null) - Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot, - RawMouseEventType.Move, location, InputModifiers.LeftMouseButton)); + Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot, + RawPointerEventType.Move, location, InputModifiers.LeftMouseButton)); else { //magic number based on test - correction of 0.02 is working perfect diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 9a751d4953..20b2ac5d9d 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -14,6 +14,8 @@ namespace Avalonia.Controls.UnitTests { public class ButtonTests { + private MouseTestHelper _helper = new MouseTestHelper(); + [Fact] public void Button_Is_Disabled_When_Command_Is_Disabled() { @@ -102,12 +104,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Raises_Click() { - var mouse = Mock.Of(); var renderer = Mock.Of(); - IInputElement captured = null; - Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny())).Returns(new Point(50, 50)); - Mock.Get(mouse).Setup(m => m.Capture(It.IsAny())).Callback(v => captured = v); - Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured); + var pt = new Point(50, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns>((p, r, f) => r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]); @@ -122,15 +120,15 @@ namespace Avalonia.Controls.UnitTests target.Click += (s, e) => clicked = true; - RaisePointerEnter(target, mouse); - RaisePointerMove(target, mouse); - RaisePointerPressed(target, mouse, 1, MouseButton.Left); + RaisePointerEnter(target); + RaisePointerMove(target, pt); + RaisePointerPressed(target, 1, MouseButton.Left, pt); - Assert.Equal(captured, target); + Assert.Equal(_helper.Captured, target); - RaisePointerReleased(target, mouse, MouseButton.Left); + RaisePointerReleased(target, MouseButton.Left, pt); - Assert.Equal(captured, null); + Assert.Equal(_helper.Captured, null); Assert.True(clicked); } @@ -138,12 +136,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { - var mouse = Mock.Of(); var renderer = Mock.Of(); - IInputElement captured = null; - Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny())).Returns(new Point(200, 50)); - Mock.Get(mouse).Setup(m => m.Capture(It.IsAny())).Callback(v => captured = v); - Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured); + Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns>((p, r, f) => r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]); @@ -158,16 +152,16 @@ namespace Avalonia.Controls.UnitTests target.Click += (s, e) => clicked = true; - RaisePointerEnter(target, mouse); - RaisePointerMove(target, mouse); - RaisePointerPressed(target, mouse, 1, MouseButton.Left); - RaisePointerLeave(target, mouse); + RaisePointerEnter(target); + RaisePointerMove(target, new Point(50,50)); + RaisePointerPressed(target, 1, MouseButton.Left, new Point(50, 50)); + RaisePointerLeave(target); - Assert.Equal(captured, target); + Assert.Equal(_helper.Captured, target); - RaisePointerReleased(target, mouse, MouseButton.Left); + RaisePointerReleased(target, MouseButton.Left, new Point(200, 50)); - Assert.Equal(captured, null); + Assert.Equal(_helper.Captured, null); Assert.False(clicked); } @@ -175,12 +169,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_With_RenderTransform_Raises_Click() { - var mouse = Mock.Of(); var renderer = Mock.Of(); - IInputElement captured = null; - Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny())).Returns(new Point(150, 50)); - Mock.Get(mouse).Setup(m => m.Capture(It.IsAny())).Callback(v => captured = v); - Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured); + var pt = new Point(150, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns>((p, r, f) => r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ? @@ -204,15 +194,15 @@ namespace Avalonia.Controls.UnitTests target.Click += (s, e) => clicked = true; - RaisePointerEnter(target, mouse); - RaisePointerMove(target, mouse); - RaisePointerPressed(target, mouse, 1, MouseButton.Left); + RaisePointerEnter(target); + RaisePointerMove(target, pt); + RaisePointerPressed(target, 1, MouseButton.Left, pt); - Assert.Equal(captured, target); + Assert.Equal(_helper.Captured, target); - RaisePointerReleased(target, mouse, MouseButton.Left); + RaisePointerReleased(target, MouseButton.Left, pt); - Assert.Equal(captured, null); + Assert.Equal(_helper.Captured, null); Assert.True(clicked); } @@ -278,57 +268,29 @@ namespace Avalonia.Controls.UnitTests public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); } - private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton) + private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position) { - button.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - Source = button, - MouseButton = mouseButton, - ClickCount = clickCount, - Device = device, - }); + _helper.Down(button, mouseButton, position, clickCount: clickCount); } - private void RaisePointerReleased(Button button, IMouseDevice device, MouseButton mouseButton) + private void RaisePointerReleased(Button button, MouseButton mouseButton, Point pt) { - button.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - Source = button, - MouseButton = mouseButton, - Device = device, - }); + _helper.Up(button, mouseButton, pt); } - private void RaisePointerEnter(Button button, IMouseDevice device) + private void RaisePointerEnter(Button button) { - button.RaiseEvent(new PointerEventArgs - { - RoutedEvent = InputElement.PointerEnterEvent, - Source = button, - Device = device, - }); + _helper.Enter(button); } - private void RaisePointerLeave(Button button, IMouseDevice device) + private void RaisePointerLeave(Button button) { - button.RaiseEvent(new PointerEventArgs - { - RoutedEvent = InputElement.PointerLeaveEvent, - Source = button, - Device = device, - }); + _helper.Leave(button); } - private void RaisePointerMove(Button button, IMouseDevice device) + private void RaisePointerMove(Button button, Point pos) { - button.RaiseEvent(new PointerEventArgs - { - RoutedEvent = InputElement.PointerMovedEvent, - Source = button, - Device = device, - }); + _helper.Move(button, pos); } private class TestCommand : ICommand diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index fdbca70350..70ec6c1408 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests { public class ComboBoxTests { + MouseTestHelper _helper = new MouseTestHelper(); + [Fact] public void Clicking_On_Control_Toggles_IsDropDownOpen() { @@ -23,17 +25,11 @@ namespace Avalonia.Controls.UnitTests Items = new[] { "Foo", "Bar" }, }; - target.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - }); - + _helper.Down(target); + _helper.Up(target); Assert.True(target.IsDropDownOpen); - target.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - }); + _helper.Down(target); Assert.False(target.IsDropDownOpen); } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 834c49ba6b..067c66969f 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -14,6 +14,7 @@ namespace Avalonia.Controls.UnitTests public class ContextMenuTests { private Mock popupImpl; + private MouseTestHelper _mouse = new MouseTestHelper(); [Fact] public void Clicking_On_Control_Toggles_ContextMenu() @@ -31,19 +32,11 @@ namespace Avalonia.Controls.UnitTests new Window { Content = target }; - target.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - MouseButton = MouseButton.Right - }); + _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); - target.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - MouseButton = MouseButton.None - }); + _mouse.Click(target); Assert.False(sut.IsOpen); popupImpl.Verify(x => x.Show(), Times.Once); @@ -69,19 +62,11 @@ namespace Avalonia.Controls.UnitTests Avalonia.Application.Current.MainWindow = window; - target.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - MouseButton = MouseButton.Right - }); + _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); - target.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - MouseButton = MouseButton.Right - }); + _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Once); @@ -106,11 +91,7 @@ namespace Avalonia.Controls.UnitTests sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; }; - target.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - MouseButton = MouseButton.Right - }); + _mouse.Click(target, MouseButton.Right); Assert.True(eventCalled); Assert.False(sut.IsOpen); @@ -136,19 +117,11 @@ namespace Avalonia.Controls.UnitTests sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; }; - target.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - MouseButton = MouseButton.Right - }); + _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); - target.RaiseEvent(new PointerReleasedEventArgs - { - RoutedEvent = InputElement.PointerReleasedEvent, - MouseButton = MouseButton.None - }); + _mouse.Click(target, MouseButton.Right); Assert.True(eventCalled); Assert.True(sut.IsOpen); diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 343d8d41f3..238e214a5d 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -16,6 +16,8 @@ namespace Avalonia.Controls.UnitTests { public class ListBoxTests { + private MouseTestHelper _mouse = new MouseTestHelper(); + [Fact] public void Should_Use_ItemTemplate_To_Create_Item_Content() { @@ -225,12 +227,7 @@ namespace Avalonia.Controls.UnitTests private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton) { - listBox.RaiseEvent(new PointerPressedEventArgs - { - Source = item, - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = mouseButton - }); + _mouse.Click(listBox, item, mouseButton); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 70d59e82c8..de34558ad1 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -18,6 +18,8 @@ namespace Avalonia.Controls.UnitTests { public class ListBoxTests_Single { + MouseTestHelper _mouse = new MouseTestHelper(); + [Fact] public void Focusing_Item_With_Tab_Should_Not_Select_It() { @@ -68,12 +70,7 @@ namespace Avalonia.Controls.UnitTests }; ApplyTemplate(target); - - target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _mouse.Click(target.Presenter.Panel.Children[0]); Assert.Equal(0, target.SelectedIndex); } @@ -90,11 +87,7 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); target.SelectedIndex = 0; - target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _mouse.Click(target.Presenter.Panel.Children[0]); Assert.Equal(0, target.SelectedIndex); } @@ -111,11 +104,7 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); - target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _mouse.Click(target.Presenter.Panel.Children[0]); Assert.Equal(0, target.SelectedIndex); } @@ -133,11 +122,7 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); target.SelectedIndex = 0; - target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _mouse.Click(target.Presenter.Panel.Children[0]); Assert.Equal(-1, target.SelectedIndex); } @@ -155,11 +140,7 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); target.SelectedIndex = 0; - target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _mouse.Click(target.Presenter.Panel.Children[0]); Assert.Equal(0, target.SelectedIndex); } @@ -177,11 +158,7 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); target.SelectedIndex = 1; - target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _mouse.Click(target.Presenter.Panel.Children[0]); Assert.Equal(0, target.SelectedIndex); } @@ -306,4 +283,4 @@ namespace Avalonia.Controls.UnitTests target.Presenter.ApplyTemplate(); } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs new file mode 100644 index 0000000000..d6542d23f0 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs @@ -0,0 +1,116 @@ +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.UnitTests +{ + public class MouseTestHelper + { + + class TestPointer : IPointer + { + public int Id { get; } = Pointer.GetNextFreeId(); + + public void Capture(IInputElement control) + { + Captured = control; + } + + public IInputElement Captured { get; set; } + public PointerType Type => PointerType.Mouse; + public bool IsPrimary => true; + } + + TestPointer _pointer = new TestPointer(); + + private InputModifiers _pressedButtons; + public IInputElement Captured => _pointer.Captured; + + InputModifiers Convert(MouseButton mouseButton) + => (mouseButton == MouseButton.Left ? InputModifiers.LeftMouseButton + : mouseButton == MouseButton.Middle ? InputModifiers.MiddleMouseButton + : mouseButton == MouseButton.Right ? InputModifiers.RightMouseButton : InputModifiers.None); + + int ButtonCount(PointerPointProperties props) + { + var rv = 0; + if (props.IsLeftButtonPressed) + rv++; + if (props.IsMiddleButtonPressed) + rv++; + if (props.IsRightButtonPressed) + rv++; + return rv; + } + + private MouseButton _pressedButton; + + InputModifiers GetModifiers(InputModifiers modifiers) => modifiers | _pressedButtons; + + public void Down(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default, + InputModifiers modifiers = default, int clickCount = 1) + => Down(target, target, mouseButton, position, modifiers, clickCount); + + public void Down(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left, + Point position = default, InputModifiers modifiers = default, int clickCount = 1) + { + _pressedButtons |= Convert(mouseButton); + var props = new PointerPointProperties(_pressedButtons); + if (ButtonCount(props) > 1) + Move(target, source, position); + else + { + _pressedButton = mouseButton; + target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, props, + GetModifiers(modifiers), clickCount)); + } + } + + public void Move(IInteractive target, in Point position, InputModifiers modifiers = default) => Move(target, target, position, modifiers); + public void Move(IInteractive target, IInteractive source, in Point position, InputModifiers modifiers = default) + { + target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (IVisual)target, position, + new PointerPointProperties(_pressedButtons), GetModifiers(modifiers))); + } + + public void Up(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default, + InputModifiers modifiers = default) + => Up(target, target, mouseButton, position, modifiers); + + public void Up(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left, + Point position = default, InputModifiers modifiers = default) + { + var conv = Convert(mouseButton); + _pressedButtons = (_pressedButtons | conv) ^ conv; + var props = new PointerPointProperties(_pressedButtons); + if (ButtonCount(props) == 0) + target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, props, + GetModifiers(modifiers), _pressedButton)); + else + Move(target, source, position); + } + + public void Click(IInteractive target, MouseButton button = MouseButton.Left, Point position = default, + InputModifiers modifiers = default) + => Click(target, target, button, position, modifiers); + public void Click(IInteractive target, IInteractive source, MouseButton button = MouseButton.Left, + Point position = default, InputModifiers modifiers = default) + { + Down(target, source, button, position, modifiers); + Up(target, source, button, position, modifiers); + } + + public void Enter(IInteractive target) + { + target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default, + new PointerPointProperties(_pressedButtons), _pressedButtons)); + } + + public void Leave(IInteractive target) + { + target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default, + new PointerPointProperties(_pressedButtons), _pressedButtons)); + } + + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index df1846c617..fb3a5bfefb 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.VisualTree; using Moq; using Xunit; @@ -9,6 +10,16 @@ namespace Avalonia.Controls.UnitTests.Platform { public class DefaultMenuInteractionHandlerTests { + static PointerEventArgs CreateArgs(RoutedEvent ev, IInteractive source) + => new PointerEventArgs(ev, source, new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default); + + static PointerPressedEventArgs CreatePressed(IInteractive source) => new PointerPressedEventArgs(source, + new FakePointer(), (IVisual)source, default, new PointerPointProperties {IsLeftButtonPressed = true}, + default); + + static PointerReleasedEventArgs CreateReleased(IInteractive source) => new PointerReleasedEventArgs(source, + new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default, MouseButton.Left); + public class TopLevel { [Fact] @@ -121,7 +132,8 @@ namespace Avalonia.Controls.UnitTests.Platform x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); - var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item }; + + var e = CreatePressed(item); target.PointerPressed(item, e); Mock.Get(menu).Verify(x => x.Close()); @@ -141,7 +153,7 @@ namespace Avalonia.Controls.UnitTests.Platform x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu.Object); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = nextItem }; + var e = CreateArgs(MenuItem.PointerEnterItemEvent, nextItem); menu.SetupGet(x => x.SelectedItem).Returns(item); @@ -161,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Platform var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); menu.SetupGet(x => x.SelectedItem).Returns(item); target.PointerLeave(item, e); @@ -176,7 +188,7 @@ namespace Avalonia.Controls.UnitTests.Platform var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); menu.SetupGet(x => x.IsOpen).Returns(true); menu.SetupGet(x => x.SelectedItem).Returns(item); @@ -330,7 +342,7 @@ namespace Avalonia.Controls.UnitTests.Platform var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerEnterItemEvent, item); target.PointerEnter(item, e); @@ -346,7 +358,7 @@ namespace Avalonia.Controls.UnitTests.Platform var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerEnterItemEvent, item); target.PointerEnter(item, e); Mock.Get(item).Verify(x => x.Open(), Times.Never); @@ -366,7 +378,7 @@ namespace Avalonia.Controls.UnitTests.Platform var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); var sibling = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerEnterItemEvent, item); Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling }); @@ -386,7 +398,7 @@ namespace Avalonia.Controls.UnitTests.Platform var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item); target.PointerLeave(item, e); @@ -403,7 +415,7 @@ namespace Avalonia.Controls.UnitTests.Platform var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); var sibling = Mock.Of(x => x.Parent == parentItem); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling); target.PointerLeave(item, e); @@ -419,7 +431,7 @@ namespace Avalonia.Controls.UnitTests.Platform var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true); - var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; + var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item); target.PointerLeave(item, e); @@ -434,7 +446,7 @@ namespace Avalonia.Controls.UnitTests.Platform var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); - var e = new PointerReleasedEventArgs { MouseButton = MouseButton.Left, Source = item }; + var e = CreateReleased(item); target.PointerReleased(item, e); @@ -452,8 +464,8 @@ namespace Avalonia.Controls.UnitTests.Platform var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); var childItem = Mock.Of(x => x.Parent == item); - var enter = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item }; - var leave = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; + var enter = CreateArgs(MenuItem.PointerEnterItemEvent, item); + var leave = CreateArgs(MenuItem.PointerLeaveItemEvent, item); // Pointer enters item; item is selected. target.PointerEnter(item, enter); @@ -488,7 +500,7 @@ namespace Avalonia.Controls.UnitTests.Platform var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); - var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item }; + var e = CreatePressed(item); target.PointerPressed(item, e); @@ -537,5 +549,19 @@ namespace Avalonia.Controls.UnitTests.Platform _action = action; } } + + class FakePointer : IPointer + { + public int Id { get; } = Pointer.GetNextFreeId(); + + public void Capture(IInputElement control) + { + Captured = control; + } + + public IInputElement Captured { get; set; } + public PointerType Type { get; } + public bool IsPrimary { get; } = true; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 037c16e231..3b5aa53d56 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -22,6 +22,8 @@ namespace Avalonia.Controls.UnitTests.Primitives { public class SelectingItemsControlTests { + private MouseTestHelper _helper = new MouseTestHelper(); + [Fact] public void SelectedIndex_Should_Initially_Be_Minus_1() { @@ -675,12 +677,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); target.Presenter.ApplyTemplate(); - - target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _helper.Down((Interactive)target.Presenter.Panel.Children[1]); var panel = target.Presenter.Panel; @@ -703,11 +700,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.ApplyTemplate(); target.Presenter.ApplyTemplate(); - target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _helper.Down(target.Presenter.Panel.Children[1]); items.RemoveAt(1); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 15081b184c..b66d6ed11c 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -19,6 +19,8 @@ namespace Avalonia.Controls.UnitTests { public class TreeViewTests { + MouseTestHelper _mouse = new MouseTestHelper(); + [Fact] public void Items_Should_Be_Created() { @@ -130,11 +132,7 @@ namespace Avalonia.Controls.UnitTests Assert.NotNull(container); - container.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - }); + _mouse.Click(container); Assert.Equal(item, target.SelectedItem); Assert.True(container.IsSelected); @@ -165,12 +163,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(container.IsSelected); - container.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - InputModifiers = InputModifiers.Control - }); + _mouse.Click(container, modifiers: InputModifiers.Control); Assert.Null(target.SelectedItem); Assert.False(container.IsSelected); @@ -205,13 +198,8 @@ namespace Avalonia.Controls.UnitTests Assert.True(container1.IsSelected); - container2.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - InputModifiers = InputModifiers.Control - }); - + _mouse.Click(container2, modifiers: InputModifiers.Control); + Assert.Equal(item2, target.SelectedItem); Assert.False(container1.IsSelected); Assert.True(container2.IsSelected); @@ -242,15 +230,15 @@ namespace Avalonia.Controls.UnitTests var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); - TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control); + ClickContainer(item1Container, InputModifiers.Control); Assert.True(item1Container.IsSelected); - TreeTestHelper.ClickContainer(item2Container, InputModifiers.Control); + ClickContainer(item2Container, InputModifiers.Control); Assert.True(item2Container.IsSelected); Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType()); - TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control); + ClickContainer(item1Container, InputModifiers.Control); Assert.False(item1Container.IsSelected); Assert.DoesNotContain(item1, target.SelectedItems.OfType()); @@ -281,12 +269,12 @@ namespace Avalonia.Controls.UnitTests var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); + ClickContainer(fromContainer, InputModifiers.None); Assert.True(fromContainer.IsSelected); - TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); - TreeTestHelper.AssertChildrenSelected(target, rootNode); + ClickContainer(toContainer, InputModifiers.Shift); + AssertChildrenSelected(target, rootNode); } [Fact] @@ -314,12 +302,12 @@ namespace Avalonia.Controls.UnitTests var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); + ClickContainer(fromContainer, InputModifiers.None); Assert.True(fromContainer.IsSelected); - TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); - TreeTestHelper.AssertChildrenSelected(target, rootNode); + ClickContainer(toContainer, InputModifiers.Shift); + AssertChildrenSelected(target, rootNode); } [Fact] @@ -347,12 +335,12 @@ namespace Avalonia.Controls.UnitTests var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); + ClickContainer(fromContainer, InputModifiers.None); - TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); - TreeTestHelper.AssertChildrenSelected(target, rootNode); + ClickContainer(toContainer, InputModifiers.Shift); + AssertChildrenSelected(target, rootNode); - TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); + ClickContainer(fromContainer, InputModifiers.None); Assert.True(fromContainer.IsSelected); @@ -656,7 +644,7 @@ namespace Avalonia.Controls.UnitTests target.RaiseEvent(keyEvent); - TreeTestHelper.AssertChildrenSelected(target, rootNode); + AssertChildrenSelected(target, rootNode); } } @@ -687,8 +675,8 @@ namespace Avalonia.Controls.UnitTests var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); - TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); + ClickContainer(fromContainer, InputModifiers.None); + ClickContainer(toContainer, InputModifiers.Shift); var keymap = AvaloniaLocator.Current.GetService(); var selectAllGesture = keymap.SelectAll.First(); @@ -702,7 +690,7 @@ namespace Avalonia.Controls.UnitTests target.RaiseEvent(keyEvent); - TreeTestHelper.AssertChildrenSelected(target, rootNode); + AssertChildrenSelected(target, rootNode); } } @@ -733,8 +721,8 @@ namespace Avalonia.Controls.UnitTests var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); - TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); + ClickContainer(fromContainer, InputModifiers.None); + ClickContainer(toContainer, InputModifiers.Shift); var keymap = AvaloniaLocator.Current.GetService(); var selectAllGesture = keymap.SelectAll.First(); @@ -748,7 +736,7 @@ namespace Avalonia.Controls.UnitTests target.RaiseEvent(keyEvent); - TreeTestHelper.AssertChildrenSelected(target, rootNode); + AssertChildrenSelected(target, rootNode); } } @@ -871,29 +859,22 @@ namespace Avalonia.Controls.UnitTests } } - private static class TreeTestHelper + void ClickContainer(IControl container, InputModifiers modifiers) { - public static void ClickContainer(IControl container, InputModifiers modifiers) - { - container.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - InputModifiers = modifiers - }); - } + _mouse.Click(container, modifiers: modifiers); + } - public static void AssertChildrenSelected(TreeView treeView, Node rootNode) + void AssertChildrenSelected(TreeView treeView, Node rootNode) + { + foreach (var child in rootNode.Children) { - foreach (var child in rootNode.Children) - { - var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child); + var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child); - Assert.True(container.IsSelected); - } + Assert.True(container.IsSelected); } } + private class Node : NotifyingBase { private IAvaloniaList _children; diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs index af034f541b..8f1c071695 100644 --- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs @@ -225,11 +225,11 @@ namespace Avalonia.Input.UnitTests private void SendMouseMove(IInputManager inputManager, TestRoot root, Point p = new Point()) { - inputManager.ProcessInput(new RawMouseEventArgs( + inputManager.ProcessInput(new RawPointerEventArgs( root.MouseDevice, 0, root, - RawMouseEventType.Move, + RawPointerEventType.Move, p, InputModifiers.None)); } diff --git a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj index 6b19d81034..7316b1de3d 100644 --- a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj +++ b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj @@ -4,6 +4,7 @@ netcoreapp2.0;net47 Library true + latest @@ -19,6 +20,7 @@ + diff --git a/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs b/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs index bebf7f9bc9..69bdf58f9d 100644 --- a/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs +++ b/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Avalonia.Controls; +using Avalonia.Controls.UnitTests; using Avalonia.Input; using Xunit; @@ -10,6 +11,8 @@ namespace Avalonia.Interactivity.UnitTests { public class GestureTests { + private MouseTestHelper _mouse = new MouseTestHelper(); + [Fact] public void Tapped_Should_Follow_Pointer_Pressed_Released() { @@ -27,8 +30,7 @@ namespace Avalonia.Interactivity.UnitTests border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br")); border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); - border.RaiseEvent(new PointerPressedEventArgs()); - border.RaiseEvent(new PointerReleasedEventArgs()); + _mouse.Click(border); Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt" }, result); } @@ -47,8 +49,7 @@ namespace Avalonia.Interactivity.UnitTests decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt")); border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); - border.RaiseEvent(new PointerPressedEventArgs()); - border.RaiseEvent(new PointerReleasedEventArgs()); + _mouse.Click(border); Assert.Equal(new[] { "bt", "dt" }, result); } @@ -72,9 +73,8 @@ namespace Avalonia.Interactivity.UnitTests border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt")); - border.RaiseEvent(new PointerPressedEventArgs()); - border.RaiseEvent(new PointerReleasedEventArgs()); - border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 }); + _mouse.Click(border); + _mouse.Down(border, clickCount: 2); Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp", "bdt", "ddt" }, result); } @@ -103,9 +103,8 @@ namespace Avalonia.Interactivity.UnitTests border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt")); - border.RaiseEvent(new PointerPressedEventArgs()); - border.RaiseEvent(new PointerReleasedEventArgs()); - border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 }); + _mouse.Click(border); + _mouse.Down(border, clickCount: 2); Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp" }, result); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 795aba153a..3511919e39 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -137,8 +137,9 @@ namespace Avalonia.Markup.Xaml.UnitTests .GetVisualChildren().First() .GetVisualChildren().First() .GetVisualChildren().First(); - ((Control)item).RaiseEvent(new PointerPressedEventArgs {ClickCount = 20}); - Assert.Equal(20, w.Args.ClickCount); + + ((Control)item).DataContext = "test"; + Assert.Equal("test", w.SavedContext); } } @@ -161,10 +162,10 @@ namespace Avalonia.Markup.Xaml.UnitTests public class XamlIlBugTestsEventHandlerCodeBehind : Window { - public PointerPressedEventArgs Args; - public void HandlePointerPressed(object sender, PointerPressedEventArgs args) + public object SavedContext; + public void HandleDataContextChanged(object sender, EventArgs args) { - Args = args; + SavedContext = ((Control)sender).DataContext; } public XamlIlBugTestsEventHandlerCodeBehind() @@ -178,7 +179,7 @@ namespace Avalonia.Markup.Xaml.UnitTests -