From f556db57b439f8474d8ed36834a27577ea974442 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 28 May 2019 23:33:54 +0300 Subject: [PATCH] Refactor pointer events to support touch events --- .../Calendar/CalendarButton.cs | 13 -- .../Calendar/CalendarDayButton.cs | 13 -- .../Calendar/CalendarItem.cs | 32 +---- src/Avalonia.Controls/MenuItem.cs | 18 +-- src/Avalonia.Input/IMouseDevice.cs | 2 + src/Avalonia.Input/IPointer.cs | 17 +++ src/Avalonia.Input/IPointerDevice.cs | 2 - src/Avalonia.Input/MouseDevice.cs | 134 ++++++++++-------- src/Avalonia.Input/PointerEventArgs.cs | 98 ++++++++++--- src/Avalonia.Input/PointerPoint.cs | 45 ++++++ src/Avalonia.Input/PointerWheelEventArgs.cs | 11 ++ src/Avalonia.Input/Raw/RawMouseEventArgs.cs | 3 + src/Avalonia.Input/Raw/RawTouchEventArgs.cs | 15 ++ .../ButtonTests.cs | 106 +++++--------- .../ComboBoxTests.cs | 14 +- .../ContextMenuTests.cs | 43 ++---- .../ListBoxTests.cs | 9 +- .../ListBoxTests_Single.cs | 41 ++---- .../MouseTestHelper.cs | 114 +++++++++++++++ .../DefaultMenuInteractionHandlerTests.cs | 52 +++++-- .../Primitives/SelectingItemsControlTests.cs | 15 +- .../TreeViewTests.cs | 89 +++++------- .../Avalonia.Interactivity.UnitTests.csproj | 2 + .../GestureTests.cs | 14 +- .../Xaml/XamlIlTests.cs | 13 +- 25 files changed, 515 insertions(+), 400 deletions(-) create mode 100644 src/Avalonia.Input/IPointer.cs create mode 100644 src/Avalonia.Input/PointerPoint.cs create mode 100644 src/Avalonia.Input/Raw/RawTouchEventArgs.cs create mode 100644 tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs 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.Input/IMouseDevice.cs b/src/Avalonia.Input/IMouseDevice.cs index a1d1bb3eb8..76be8ba308 100644 --- a/src/Avalonia.Input/IMouseDevice.cs +++ b/src/Avalonia.Input/IMouseDevice.cs @@ -12,5 +12,7 @@ namespace Avalonia.Input /// Gets the mouse position, in screen coordinates. /// 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..15f1fef531 --- /dev/null +++ b/src/Avalonia.Input/IPointer.cs @@ -0,0 +1,17 @@ +namespace Avalonia.Input +{ + public interface IPointer + { + 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..8613717f60 100644 --- a/src/Avalonia.Input/IPointerDevice.cs +++ b/src/Avalonia.Input/IPointerDevice.cs @@ -12,7 +12,5 @@ namespace Avalonia.Input void Capture(IInputElement control); 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..352c69a726 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -14,14 +14,17 @@ 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; + /// /// Gets the control that is currently capturing by the mouse, if any. /// @@ -117,6 +120,18 @@ namespace Avalonia.Input } } + int ButtonCount(PointerPointProperties props) + { + var rv = 0; + if (props.IsLeftButtonPressed) + rv++; + if (props.IsMiddleButtonPressed) + rv++; + if (props.IsRightButtonPressed) + rv++; + return rv; + } + private void ProcessRawEvent(RawMouseEventArgs e) { Contract.Requires(e != null); @@ -124,7 +139,7 @@ namespace Avalonia.Input var mouse = (IMouseDevice)e.Device; Position = e.Root.PointToScreen(e.Position); - + var props = CreateProperties(e); switch (e.Type) { case RawMouseEventType.LeaveWindow: @@ -133,26 +148,25 @@ namespace Avalonia.Input 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); + 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); + 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); + 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); + e.Handled = MouseWheel(mouse, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers); break; } } @@ -165,7 +179,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(RawMouseEventArgs args) + { + var rv = new PointerPointProperties(args.InputModifiers); + + if (args.Type == RawMouseEventType.LeftButtonDown) + rv.IsLeftButtonPressed = true; + if (args.Type == RawMouseEventType.MiddleButtonDown) + rv.IsMiddleButtonPressed = true; + if (args.Type == RawMouseEventType.RightButtonDown) + rv.IsRightButtonPressed = true; + if (args.Type == RawMouseEventType.LeftButtonUp) + rv.IsLeftButtonPressed = false; + if (args.Type == RawMouseEventType.MiddleButtonUp) + rv.IsMiddleButtonPressed = false; + if (args.Type == RawMouseEventType.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 +227,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 +238,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 +256,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 +274,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 +283,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 +295,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 +320,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 +399,6 @@ namespace Avalonia.Input IInputElement branch = null; - var e = new PointerEventArgs { Device = device, InputModifiers = inputModifiers }; var el = element; while (el != null) @@ -398,8 +412,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/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 7b2497c460..91bfb694a0 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,68 @@ 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; +#pragma warning disable 612 +#pragma warning disable 618 + Device = new EmulatedDevice(this); +#pragma warning restore 618 +#pragma warning restore 612 } - 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; } + + [Obsolete("Use Pointer to get pointer-specific information")] + public IPointerDevice Device { get; } - 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 +84,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/RawMouseEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseEventArgs.cs index c5637d66cc..42dca6f3a6 100644 --- a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawMouseEventArgs.cs @@ -17,6 +17,9 @@ namespace Avalonia.Input.Raw Move, Wheel, NonClientLeftButtonDown, + TouchBegin, + TouchUpdate, + TouchEnd } /// diff --git a/src/Avalonia.Input/Raw/RawTouchEventArgs.cs b/src/Avalonia.Input/Raw/RawTouchEventArgs.cs new file mode 100644 index 0000000000..9a266d090d --- /dev/null +++ b/src/Avalonia.Input/Raw/RawTouchEventArgs.cs @@ -0,0 +1,15 @@ +namespace Avalonia.Input.Raw +{ + public class RawTouchEventArgs : RawMouseEventArgs + { + public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root, + RawMouseEventType type, Point position, InputModifiers inputModifiers, + long touchPointId) + : base(device, timestamp, root, type, position, inputModifiers) + { + TouchPointId = touchPointId; + } + + public long TouchPointId { get; set; } + } +} 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..8b61f82c28 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs @@ -0,0 +1,114 @@ +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.UnitTests +{ + public class MouseTestHelper + { + + class TestPointer : IPointer + { + 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..94a95b6db4 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,28 @@ namespace Avalonia.Controls.UnitTests.Platform { public class DefaultMenuInteractionHandlerTests { + class FakePointer : IPointer + { + public void Capture(IInputElement control) + { + Captured = control; + } + + public IInputElement Captured { get; set; } + public PointerType Type { get; } + public bool IsPrimary { get; } = true; + } + + 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 +144,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 +165,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 +185,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 +200,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 +354,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 +370,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 +390,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 +410,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 +427,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 +443,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 +458,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 +476,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 +512,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); 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.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 931c1b5268..d40e08e748 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); } 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 -