From 11b0de3481b8e9950b42fee8fc1869def260e397 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 8 Dec 2022 14:28:33 +0000 Subject: [PATCH 01/13] add hold gesture recognizer --- .../HoldGestureRecognizer.cs | 124 ++++++++++++++++++ src/Avalonia.Base/Input/Gestures.cs | 4 + .../Input/HoldGestureEventArgs.cs | 31 +++++ src/Avalonia.Controls/Control.cs | 19 +++ 4 files changed, 178 insertions(+) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs create mode 100644 src/Avalonia.Base/Input/HoldGestureEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs new file mode 100644 index 0000000000..02fd800baf --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs @@ -0,0 +1,124 @@ +using System.Timers; +using Avalonia.Input.GestureRecognizers; +using Avalonia.Threading; + +namespace Avalonia.Input +{ + public class HoldGestureRecognizer : StyledElement, IGestureRecognizer + { + private const int Tolerance = 30; + private IInputElement? _target; + private IGestureRecognizerActionsDispatcher? _actions; + private int _gestureId; + private IPointer? _tracking; + private PointerPressedEventArgs? _pointerEventArgs; + private Rect _trackingBounds; + private Timer? _holdTimer; + private bool _elasped; + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.Register( + nameof(IsHoldWithMouseEnabled)); + + /// + /// Gets or sets whether to detect hold from the mouse + /// + public bool IsHoldWithMouseEnabled + { + get => GetValue(IsHoldWithMouseEnabledProperty); + set => SetValue(IsHoldWithMouseEnabledProperty, value); + } + + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + { + _target = target; + _actions = actions; + + _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + + _holdTimer = new Timer(300); + _holdTimer.AutoReset = false; + _holdTimer.Elapsed += HoldTimer_Elapsed; + } + + private async void HoldTimer_Elapsed(object? sender, ElapsedEventArgs e) + { + _elasped = true; + _holdTimer?.Stop(); + + if(_tracking != null) + { + await Dispatcher.UIThread.InvokeAsync(() => _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, HoldingState.Started))); + } + } + + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + PointerPressed(e); + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + public void PointerCaptureLost(IPointer pointer) + { + if (_tracking == pointer) + { + EndHold(!_elasped); + } + } + + public void PointerMoved(PointerEventArgs e) + { + if (_tracking == e.Pointer && _target is Visual visual) + { + var currentPosition = e.GetPosition(visual); + + if (!_trackingBounds.Contains(currentPosition)) + { + EndHold(true); + } + } + } + + public void PointerPressed(PointerPressedEventArgs e) + { + if (_target != null && _target is Visual visual && (IsHoldWithMouseEnabled || e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + { + _elasped = false; + var position = e.GetPosition(visual); + _gestureId = HoldGestureEventArgs.GetNextFreeId(); + _tracking = e.Pointer; + _pointerEventArgs = e; + + _trackingBounds = new Rect(position.X - Tolerance / 2, position.Y - Tolerance / 2, Tolerance, Tolerance); + + _holdTimer?.Start(); + } + } + + public void PointerReleased(PointerReleasedEventArgs e) + { + if (_tracking == e.Pointer) + { + EndHold(!_elasped); + } + } + + private void EndHold(bool cancelled) + { + _holdTimer?.Stop(); + + _tracking = null; + _trackingBounds = default; + + _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, cancelled ? HoldingState.Cancelled : HoldingState.Completed)); + } + } +} diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index b4d5feaf3b..1c2220baf4 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -58,6 +58,10 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent HoldGestureEvent = + RoutedEvent.Register( + "HoldGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent PullGestureEndedEvent = RoutedEvent.Register( "PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); diff --git a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs new file mode 100644 index 0000000000..63a508b1f4 --- /dev/null +++ b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class HoldGestureEventArgs : RoutedEventArgs + { + public int Id { get; } + public Vector Delta { get; } + public HoldingState HoldingState { get; } + public PointerEventArgs? PointerEventArgs { get; } + + private static int _nextId = 1; + + internal static int GetNextFreeId() => _nextId++; + + public HoldGestureEventArgs(int id, PointerEventArgs? pointerEventArgs, HoldingState holdingState) : base(Gestures.HoldGestureEvent) + { + Id = id; + HoldingState = holdingState; + PointerEventArgs = pointerEventArgs; + } + } + + public enum HoldingState + { + Started, + Completed, + Cancelled, + } +} diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 88c9823952..de91fb83c9 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -216,6 +216,12 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + public Control() + { + // Add a default HoldGestureRecognizer + GestureRecognizers.Add(new HoldGestureRecognizer()); + } + /// void ISetterValue.Initialize(ISetter setter) { @@ -366,16 +372,29 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); + AddHandler(Gestures.HoldGestureEvent, OnHoldEvent); + InitializeIfNeeded(); ScheduleOnLoadedCore(); } + private void OnHoldEvent(object? sender, HoldGestureEventArgs e) + { + if(e.HoldingState == HoldingState.Started) + { + // Trigger ContentRequest when hold starts + RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!)); + } + } + /// protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTreeCore(e); + RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent); + OnUnloadedCore(); } From 8945e1b77c78223b4172f419c5d916593f88c62d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 9 Dec 2022 13:48:14 +0000 Subject: [PATCH 02/13] drop hold gesture recognizer --- .../HoldGestureRecognizer.cs | 124 ------------------ src/Avalonia.Base/Input/Gestures.cs | 93 ++++++++++++- .../Input/HoldGestureEventArgs.cs | 31 ----- .../Input/HoldingRoutedEventArgs.cs | 22 ++++ src/Avalonia.Base/Input/InputElement.cs | 46 +++++++ src/Avalonia.Controls/Control.cs | 18 +-- 6 files changed, 163 insertions(+), 171 deletions(-) delete mode 100644 src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs delete mode 100644 src/Avalonia.Base/Input/HoldGestureEventArgs.cs create mode 100644 src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs deleted file mode 100644 index 02fd800baf..0000000000 --- a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Timers; -using Avalonia.Input.GestureRecognizers; -using Avalonia.Threading; - -namespace Avalonia.Input -{ - public class HoldGestureRecognizer : StyledElement, IGestureRecognizer - { - private const int Tolerance = 30; - private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; - private int _gestureId; - private IPointer? _tracking; - private PointerPressedEventArgs? _pointerEventArgs; - private Rect _trackingBounds; - private Timer? _holdTimer; - private bool _elasped; - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.Register( - nameof(IsHoldWithMouseEnabled)); - - /// - /// Gets or sets whether to detect hold from the mouse - /// - public bool IsHoldWithMouseEnabled - { - get => GetValue(IsHoldWithMouseEnabledProperty); - set => SetValue(IsHoldWithMouseEnabledProperty, value); - } - - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) - { - _target = target; - _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - - _holdTimer = new Timer(300); - _holdTimer.AutoReset = false; - _holdTimer.Elapsed += HoldTimer_Elapsed; - } - - private async void HoldTimer_Elapsed(object? sender, ElapsedEventArgs e) - { - _elasped = true; - _holdTimer?.Stop(); - - if(_tracking != null) - { - await Dispatcher.UIThread.InvokeAsync(() => _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, HoldingState.Started))); - } - } - - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) - { - PointerPressed(e); - } - - private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) - { - PointerReleased(e); - } - - public void PointerCaptureLost(IPointer pointer) - { - if (_tracking == pointer) - { - EndHold(!_elasped); - } - } - - public void PointerMoved(PointerEventArgs e) - { - if (_tracking == e.Pointer && _target is Visual visual) - { - var currentPosition = e.GetPosition(visual); - - if (!_trackingBounds.Contains(currentPosition)) - { - EndHold(true); - } - } - } - - public void PointerPressed(PointerPressedEventArgs e) - { - if (_target != null && _target is Visual visual && (IsHoldWithMouseEnabled || e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) - { - _elasped = false; - var position = e.GetPosition(visual); - _gestureId = HoldGestureEventArgs.GetNextFreeId(); - _tracking = e.Pointer; - _pointerEventArgs = e; - - _trackingBounds = new Rect(position.X - Tolerance / 2, position.Y - Tolerance / 2, Tolerance, Tolerance); - - _holdTimer?.Start(); - } - } - - public void PointerReleased(PointerReleasedEventArgs e) - { - if (_tracking == e.Pointer) - { - EndHold(!_elasped); - } - } - - private void EndHold(bool cancelled) - { - _holdTimer?.Stop(); - - _tracking = null; - _trackingBounds = default; - - _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, cancelled ? HoldingState.Cancelled : HoldingState.Completed)); - } - } -} diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 1c2220baf4..06d90f1c27 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -1,6 +1,8 @@ using System; +using System.Threading; using Avalonia.Interactivity; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Input @@ -8,6 +10,16 @@ namespace Avalonia.Input public static class Gestures { private static bool s_isDoubleTapped = false; + private static bool s_isHolding; + private static CancellationTokenSource? s_holdCancellationToken; + + /* /// + /// Defines the property. + /// + public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.RegisterAttached( + "IsHoldWithMouseEnabled");*/ + public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -45,6 +57,7 @@ namespace Avalonia.Input private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; + private static IPointer? s_lastPointer; public static readonly RoutedEvent PinchEvent = RoutedEvent.Register( @@ -58,9 +71,9 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); - public static readonly RoutedEvent HoldGestureEvent = - RoutedEvent.Register( - "HoldGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent HoldingEvent = + RoutedEvent.Register( + "Holding", RoutingStrategies.Bubble, typeof(Gestures)); public static readonly RoutedEvent PullGestureEndedEvent = RoutedEvent.Register( @@ -70,6 +83,7 @@ namespace Avalonia.Input { InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed); InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); + InputElement.PointerMovedEvent.RouteFinished.Subscribe(PointerMoved); } public static void AddTappedHandler(Interactive element, EventHandler handler) @@ -114,11 +128,38 @@ namespace Avalonia.Input var e = (PointerPressedEventArgs)ev; var visual = (Visual)ev.Source; + if(s_lastPointer != null) + { + if(s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + } + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + + s_lastPointer = null; + } + + s_isHolding = false; + if (e.ClickCount % 2 == 1) { s_isDoubleTapped = false; s_lastPress.SetTarget(ev.Source); + s_lastPointer = e.Pointer; s_lastPressPoint = e.GetPosition((Visual)ev.Source); + s_holdCancellationToken = new CancellationTokenSource(); + var token = s_holdCancellationToken.Token; + var settings = AvaloniaLocator.Current.GetService(); + DispatcherTimer.RunOnce(() => + { + if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + { + s_isHolding = true; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started)); + } + }, TimeSpan.FromMilliseconds(300)); } else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { @@ -152,7 +193,12 @@ namespace Avalonia.Input if (tapRect.ContainsExclusive(point.Position)) { - if (e.InitialPressMouseButton == MouseButton.Right) + if(s_isHolding) + { + s_isHolding = false; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed)); + } + else if (e.InitialPressMouseButton == MouseButton.Right) { i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); } @@ -164,6 +210,45 @@ namespace Avalonia.Input } } } + + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + s_lastPointer = null; + } + } + + private static void PointerMoved(RoutedEventArgs ev) + { + if (ev.Route == RoutingStrategies.Bubble) + { + var e = (PointerEventArgs)ev; + if (s_lastPress.TryGetTarget(out var target)) + { + if (e.Pointer == s_lastPointer) + { + var point = e.GetCurrentPoint((Visual)target); + var settings = AvaloniaLocator.Current.GetService(); + var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); + var tapRect = new Rect(s_lastPressPoint, new Size()) + .Inflate(new Thickness(tapSize.Width, tapSize.Height)); + + if (tapRect.ContainsExclusive(point.Position)) + { + return; + } + } + + if (s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + } + } + + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + s_isHolding = false; } } } diff --git a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs deleted file mode 100644 index 63a508b1f4..0000000000 --- a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Avalonia.Interactivity; - -namespace Avalonia.Input -{ - public class HoldGestureEventArgs : RoutedEventArgs - { - public int Id { get; } - public Vector Delta { get; } - public HoldingState HoldingState { get; } - public PointerEventArgs? PointerEventArgs { get; } - - private static int _nextId = 1; - - internal static int GetNextFreeId() => _nextId++; - - public HoldGestureEventArgs(int id, PointerEventArgs? pointerEventArgs, HoldingState holdingState) : base(Gestures.HoldGestureEvent) - { - Id = id; - HoldingState = holdingState; - PointerEventArgs = pointerEventArgs; - } - } - - public enum HoldingState - { - Started, - Completed, - Cancelled, - } -} diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs new file mode 100644 index 0000000000..5826c5b994 --- /dev/null +++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs @@ -0,0 +1,22 @@ +using System; +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class HoldingRoutedEventArgs : RoutedEventArgs + { + public HoldingState HoldingState { get; } + + public HoldingRoutedEventArgs(HoldingState holdingState) : base(Gestures.HoldingEvent) + { + HoldingState = holdingState; + } + } + + public enum HoldingState + { + Started, + Completed, + Cancelled, + } +} diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index fa755277cc..7f2cbec299 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -45,6 +45,18 @@ namespace Avalonia.Input public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldingEnabledProperty = + AvaloniaProperty.Register(nameof(IsHoldingEnabled), true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.Register(nameof(IsHoldWithMouseEnabled), false); + /// /// Defines the property. /// @@ -188,6 +200,11 @@ namespace Avalonia.Input /// public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent; + /// + /// Defines the event. + /// + public static readonly RoutedEvent HoldingEvent = Gestures.HoldingEvent; + /// /// Defines the event. /// @@ -352,6 +369,15 @@ namespace Avalonia.Input add { AddHandler(TappedEvent, value); } remove { RemoveHandler(TappedEvent, value); } } + + /// + /// Occurs when a hold gesture occurs on the control. + /// + public event EventHandler? Holding + { + add { AddHandler(HoldingEvent, value); } + remove { RemoveHandler(HoldingEvent, value); } + } /// /// Occurs when a double-tap gesture occurs on the control. @@ -388,6 +414,26 @@ namespace Avalonia.Input get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } + + /// + /// Gets or sets a value that determines whether the Holding event can originate + /// from that element. + /// + public bool IsHoldingEnabled + { + get { return GetValue(IsHoldingEnabledProperty); } + set { SetValue(IsHoldingEnabledProperty, value); } + } + + /// + /// Gets or sets a value that determines whether the Holding event can originate + /// from that element. + /// + public bool IsHoldWithMouseEnabled + { + get { return GetValue(IsHoldWithMouseEnabledProperty); } + set { SetValue(IsHoldWithMouseEnabledProperty, value); } + } /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index de91fb83c9..bb83f9e8bb 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -216,12 +216,6 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; - public Control() - { - // Add a default HoldGestureRecognizer - GestureRecognizers.Add(new HoldGestureRecognizer()); - } - /// void ISetterValue.Initialize(ISetter setter) { @@ -372,19 +366,19 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); - AddHandler(Gestures.HoldGestureEvent, OnHoldEvent); + AddHandler(Gestures.HoldingEvent, OnHoldEvent); InitializeIfNeeded(); ScheduleOnLoadedCore(); } - private void OnHoldEvent(object? sender, HoldGestureEventArgs e) + private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e) { - if(e.HoldingState == HoldingState.Started) + if(e.HoldingState == HoldingState.Completed) { - // Trigger ContentRequest when hold starts - RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!)); + // Trigger ContentRequest when hold is complete + RaiseEvent(new ContextRequestedEventArgs()); } } @@ -393,7 +387,7 @@ namespace Avalonia.Controls { base.OnDetachedFromVisualTreeCore(e); - RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent); + RemoveHandler(Gestures.HoldingEvent, OnHoldEvent); OnUnloadedCore(); } From c804fa00cfe323a00ca7fa6f9e1c91f4ae0f064e Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 9 Dec 2022 18:13:32 +0000 Subject: [PATCH 03/13] fix tests --- .../Input/GesturesTests.cs | 11 +++++++ .../Input/TouchDeviceTests.cs | 2 +- .../ButtonTests.cs | 3 ++ .../ComboBoxTests.cs | 4 +++ .../ListBoxTests_Single.cs | 13 +++++---- .../Mixins/PressedMixinTests.cs | 3 ++ .../Primitives/SelectingItemsControlTests.cs | 8 ++--- .../SelectingItemsControlTests_Multiple.cs | 29 ++++++++++--------- .../TreeViewTests.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 3 +- 10 files changed, 52 insertions(+), 27 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 59085a21ce..20d121756e 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -3,7 +3,9 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; +using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -15,6 +17,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Follow_Pointer_Pressed_Released() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -32,6 +35,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -49,6 +53,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Middle_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -66,6 +71,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -83,6 +89,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void RightTapped_Should_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -100,6 +107,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -118,6 +126,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -136,6 +145,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -154,6 +164,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index c0c0182622..36587ea222 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager())); + new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 8bd51ec500..9cb83d3009 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -134,6 +134,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Raises_Click() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(50, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -166,6 +167,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -199,6 +201,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_With_RenderTransform_Raises_Click() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(150, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 1a8061bfd9..0afcd9ed49 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -11,6 +11,8 @@ using Avalonia.Media; using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; +using Moq; +using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { @@ -21,6 +23,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_Toggles_IsDropDownOpen() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, @@ -41,6 +44,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_PseudoClass() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 03aac5e2f4..7f9b38018b 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -8,6 +8,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -115,7 +116,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -153,7 +154,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -173,7 +174,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -195,7 +196,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -216,7 +217,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs index 0ff1f40121..74d46b18de 100644 --- a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs @@ -1,5 +1,7 @@ using Avalonia.Controls.Mixins; +using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Mixins @@ -19,6 +21,7 @@ namespace Avalonia.Controls.UnitTests.Mixins [Fact] public void Setting_IsSelected_Should_Add_Selected_Class() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new TestControl(); _mouse.Down(target); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index e20c03f67f..c416a5de47 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1118,7 +1118,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1140,7 +1140,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); @@ -1239,7 +1239,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1257,7 +1257,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index bc6147d64c..7fa3839ed5 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -12,6 +12,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; +using Avalonia.Platform; using Avalonia.UnitTests; using Moq; using Xunit; @@ -703,7 +704,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -726,7 +727,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -780,7 +781,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -810,7 +811,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -838,7 +839,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -862,7 +863,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -886,7 +887,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -910,7 +911,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -959,7 +960,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1189,7 +1190,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1216,7 +1217,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1241,7 +1242,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1293,7 +1294,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1316,7 +1317,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 81936711ef..3e1e25faf5 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -1466,7 +1467,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var tree = CreateTestTreeData(); var target = new TreeView diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 8f132433ec..a2096997c0 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -51,7 +51,8 @@ namespace Avalonia.UnitTests inputManager: new InputManager(), assetLoader: new AssetLoader(), renderInterface: new MockPlatformRenderInterface(), - fontManagerImpl: new MockFontManagerImpl(), + fontManagerImpl: new MockFontManagerImpl(), + threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), textShaperImpl: new MockTextShaperImpl()); public static readonly TestServices TextServices = new TestServices( From 6791a2bca129ae179da39dad3f58c6b41818dbbd Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 12 Dec 2022 09:08:41 +0000 Subject: [PATCH 04/13] Add pointer properties to hold event args, fixup some docs. --- src/Avalonia.Base/Input/Gestures.cs | 18 ++++------ .../Input/HoldingRoutedEventArgs.cs | 33 +++++++++++++++++-- src/Avalonia.Base/Input/InputElement.cs | 3 +- src/Avalonia.Controls/Control.cs | 4 +-- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 06d90f1c27..b1fda35c28 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -13,13 +13,6 @@ namespace Avalonia.Input private static bool s_isHolding; private static CancellationTokenSource? s_holdCancellationToken; - /* /// - /// Defines the property. - /// - public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.RegisterAttached( - "IsHoldWithMouseEnabled");*/ - public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -71,6 +64,9 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); + /// + /// Occurs when a user performs a press and hold gesture (with a single touch, mouse, or pen/stylus contact). + /// public static readonly RoutedEvent HoldingEvent = RoutedEvent.Register( "Holding", RoutingStrategies.Bubble, typeof(Gestures)); @@ -132,7 +128,7 @@ namespace Avalonia.Input { if(s_isHolding && ev.Source is Interactive i) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type)); } s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Dispose(); @@ -157,7 +153,7 @@ namespace Avalonia.Input if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) { s_isHolding = true; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); } }, TimeSpan.FromMilliseconds(300)); } @@ -196,7 +192,7 @@ namespace Avalonia.Input if(s_isHolding) { s_isHolding = false; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type)); } else if (e.InitialPressMouseButton == MouseButton.Right) { @@ -241,7 +237,7 @@ namespace Avalonia.Input if (s_isHolding && ev.Source is Interactive i) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); } } diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs index 5826c5b994..b9a877b2ed 100644 --- a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs +++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs @@ -5,18 +5,47 @@ namespace Avalonia.Input { public class HoldingRoutedEventArgs : RoutedEventArgs { + /// + /// Gets the state of the event. + /// public HoldingState HoldingState { get; } - - public HoldingRoutedEventArgs(HoldingState holdingState) : base(Gestures.HoldingEvent) + + /// + /// Gets the location of the touch, mouse, or pen/stylus contact. + /// + public Point Position { get; } + + /// + /// Gets the pointer type of the input source. + /// + public PointerType PointerType { get; } + + /// + /// Initializes a new instance of the class. + /// + public HoldingRoutedEventArgs(HoldingState holdingState, Point position, PointerType pointerType) : base(Gestures.HoldingEvent) { HoldingState = holdingState; + Position = position; + PointerType = pointerType; } } public enum HoldingState { + /// + /// A single contact has been detected and a time threshold is crossed without the contact being lifted, another contact detected, or another gesture started. + /// Started, + + /// + /// The single contact is lifted. + /// Completed, + + /// + /// An additional contact is detected or a subsequent gesture (such as a slide) is detected. + /// Cancelled, } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 7f2cbec299..4a65134552 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -426,8 +426,7 @@ namespace Avalonia.Input } /// - /// Gets or sets a value that determines whether the Holding event can originate - /// from that element. + /// Enables or disables support for the press and hold gesture through the left button on a mouse. /// public bool IsHoldWithMouseEnabled { diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index bb83f9e8bb..26db431a0e 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -375,9 +375,9 @@ namespace Avalonia.Controls private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e) { - if(e.HoldingState == HoldingState.Completed) + if(e.HoldingState == HoldingState.Started) { - // Trigger ContentRequest when hold is complete + // Trigger ContentRequest when hold has started RaiseEvent(new ContextRequestedEventArgs()); } } From 8516308e6335b63ac81d7ae57b966fe54c12ede5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:02:20 +0000 Subject: [PATCH 05/13] add HoldWaitDuration to IPlatformSettings --- src/Avalonia.Base/Input/Gestures.cs | 16 ++++++++++------ .../Platform/DefaultPlatformSettings.cs | 2 ++ src/Avalonia.Base/Platform/IPlatformSettings.cs | 2 ++ src/Windows/Avalonia.Win32/Win32Platform.cs | 2 ++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index b1fda35c28..a924b391ac 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -148,14 +148,18 @@ namespace Avalonia.Input s_holdCancellationToken = new CancellationTokenSource(); var token = s_holdCancellationToken.Token; var settings = AvaloniaLocator.Current.GetService(); - DispatcherTimer.RunOnce(() => + + if (settings != null) { - if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + DispatcherTimer.RunOnce(() => { - s_isHolding = true; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); - } - }, TimeSpan.FromMilliseconds(300)); + if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && (e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + { + s_isHolding = true; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); + } + }, settings.HoldWaitDuration); + } } else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index dd3e1a4cb1..d0b27b057f 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -26,5 +26,7 @@ namespace Avalonia.Platform }; } public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); + + public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); } } diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs index e7921883fd..7c4c1420eb 100644 --- a/src/Avalonia.Base/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs @@ -27,5 +27,7 @@ namespace Avalonia.Platform /// tap gesture. /// TimeSpan GetDoubleTapTime(PointerType type); + + TimeSpan HoldWaitDuration { get; set; } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 93d16b5768..585a9cdf19 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -258,6 +258,8 @@ namespace Avalonia.Win32 public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread; + public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); + public event Action Signaled; public event EventHandler ShutdownRequested; From f61c7f6cd949388e10faa0dc93818147e7ad4c3b Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:03:56 +0000 Subject: [PATCH 06/13] Revert "fix tests" This reverts commit 584e18f78d29480890711b1149d946f61e0d8447. --- .../Input/GesturesTests.cs | 10 ------- .../Input/TouchDeviceTests.cs | 2 +- .../ButtonTests.cs | 3 -- .../ComboBoxTests.cs | 4 --- .../ListBoxTests_Single.cs | 13 ++++----- .../Mixins/PressedMixinTests.cs | 3 -- .../Primitives/SelectingItemsControlTests.cs | 8 ++--- .../SelectingItemsControlTests_Multiple.cs | 29 +++++++++---------- .../TreeViewTests.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 3 +- 10 files changed, 27 insertions(+), 51 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 20d121756e..fedcdcfb1e 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -5,7 +5,6 @@ using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; -using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -17,7 +16,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Follow_Pointer_Pressed_Released() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -35,7 +33,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -53,7 +50,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Middle_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -71,7 +67,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -89,7 +84,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void RightTapped_Should_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -107,7 +101,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -126,7 +119,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -145,7 +137,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -164,7 +155,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index 36587ea222..c0c0182622 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); + new TestServices(inputManager: new InputManager())); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 9cb83d3009..8bd51ec500 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -134,7 +134,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Raises_Click() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(50, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -167,7 +166,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -201,7 +199,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_With_RenderTransform_Raises_Click() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(150, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 0afcd9ed49..1a8061bfd9 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -11,8 +11,6 @@ using Avalonia.Media; using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; -using Moq; -using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { @@ -23,7 +21,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_Toggles_IsDropDownOpen() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, @@ -44,7 +41,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_PseudoClass() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 7f9b38018b..03aac5e2f4 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -8,7 +8,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.LogicalTree; -using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -116,7 +115,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -134,7 +133,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -154,7 +153,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -174,7 +173,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -196,7 +195,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -217,7 +216,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs index 74d46b18de..0ff1f40121 100644 --- a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs @@ -1,7 +1,5 @@ using Avalonia.Controls.Mixins; -using Avalonia.Platform; using Avalonia.UnitTests; -using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Mixins @@ -21,7 +19,6 @@ namespace Avalonia.Controls.UnitTests.Mixins [Fact] public void Setting_IsSelected_Should_Add_Selected_Class() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new TestControl(); _mouse.Down(target); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index c416a5de47..e20c03f67f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1118,7 +1118,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1140,7 +1140,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); @@ -1239,7 +1239,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1257,7 +1257,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 7fa3839ed5..bc6147d64c 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -12,7 +12,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; -using Avalonia.Platform; using Avalonia.UnitTests; using Moq; using Xunit; @@ -704,7 +703,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -727,7 +726,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -781,7 +780,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -811,7 +810,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -839,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -863,7 +862,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -887,7 +886,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -911,7 +910,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -960,7 +959,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1190,7 +1189,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1217,7 +1216,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1242,7 +1241,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1294,7 +1293,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1317,7 +1316,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 3e1e25faf5..81936711ef 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -15,7 +15,6 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -1467,7 +1466,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var tree = CreateTestTreeData(); var target = new TreeView diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index a2096997c0..8f132433ec 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -51,8 +51,7 @@ namespace Avalonia.UnitTests inputManager: new InputManager(), assetLoader: new AssetLoader(), renderInterface: new MockPlatformRenderInterface(), - fontManagerImpl: new MockFontManagerImpl(), - threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), + fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl()); public static readonly TestServices TextServices = new TestServices( From be843f699a677154d7acca6ce9eba4b287efda42 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:19:01 +0000 Subject: [PATCH 07/13] add mock thread interface to touch device tests --- tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index c0c0182622..36587ea222 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager())); + new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); From 29e7666a679e7994ba6d857543bb135753b494b1 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 16:22:27 +0000 Subject: [PATCH 08/13] attempt at testing --- .../Input/GesturesTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index fedcdcfb1e..46df719d69 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,10 +1,13 @@ +using System; using System.Collections.Generic; +using System.Threading; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -170,6 +173,35 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(raised); } + [Fact] + public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + { + using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface); + + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = true); + + var secondMouse = new MouseTestHelper(); + + _mouse.Down(border, MouseButton.Left); + + Thread.Sleep(1000); + secondMouse.Down(border, MouseButton.Left); + + Assert.False(raised); + } + private static void AddHandlers( Decorator decorator, Border border, From 17765223976844e9ebb1908497bc8edc3d73506a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 11:59:33 -0500 Subject: [PATCH 09/13] Mock IPlatformThreadingInterface --- .../Input/GesturesTests.cs | 100 +++++++++++++++++- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 46df719d69..82aa3eab72 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -6,6 +6,7 @@ using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.UnitTests; using Moq; using Xunit; @@ -174,14 +175,52 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + public void Hold_Is_Raised_When_Pointer_Pressed() { - using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface); + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Started); + + _mouse.Down(border); + Assert.False(raised); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + Assert.True(raised); + } + + [Fact] + public void Hold_Is_Not_Raised_When_Pointer_Released_Before_Timer() + { + using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(iSettingsMock.Object); + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + Border border = new Border(); border.IsHoldWithMouseEnabled = true; var decorator = new Decorator @@ -190,18 +229,69 @@ namespace Avalonia.Base.UnitTests.Input }; var raised = false; - decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = true); + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed); + + _mouse.Down(border); + Assert.False(raised); + + _mouse.Up(border); + Assert.False(raised); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + Assert.False(raised); + } + + [Fact] + public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed); var secondMouse = new MouseTestHelper(); _mouse.Down(border, MouseButton.Left); - Thread.Sleep(1000); + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + secondMouse.Down(border, MouseButton.Left); Assert.False(raised); } + private static IPlatformThreadingInterface CreatePlatformThreadingInterface(Action<(TimeSpan, Action)> callback) + { + var threadingInterface = new Mock(); + threadingInterface.SetupGet(p => p.CurrentThreadIsLoopThread).Returns(true); + threadingInterface.Setup(p => p + .StartTimer(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, t, a) => callback((t, a))); + return threadingInterface.Object; + } + private static void AddHandlers( Decorator decorator, Border border, From 08f17fa2068f09c36f1730c359eb97e717c4dfa7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 23 Dec 2022 09:22:18 +0000 Subject: [PATCH 10/13] add more tests --- .../Input/GesturesTests.cs | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 82aa3eab72..33a01fc9cd 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; @@ -244,6 +243,78 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(raised); } + + [Fact] + public void Hold_Is_Cancelled_When_Second_Contact_Is_Detected() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var cancelled = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + + _mouse.Down(border); + Assert.False(cancelled); + + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + var secondMouse = new MouseTestHelper(); + + secondMouse.Down(border); + + Assert.True(cancelled); + } + + [Fact] + public void Hold_Is_Cancelled_When_Pointer_Moves() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var cancelled = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + + _mouse.Down(border); + Assert.False(cancelled); + + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + _mouse.Move(border, position: new Point(10, 10)); + + Assert.True(cancelled); + } [Fact] public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() From 306712e4cce1f63be61357a5f1c0456374bebf0c Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 23 Dec 2022 12:33:28 +0000 Subject: [PATCH 11/13] make IsHoldingEnabledProperty and IsHoldWithMouseEnabledProperty attached properties in Gestures --- src/Avalonia.Base/Input/Gestures.cs | 30 +++++++++++++++++++++++++ src/Avalonia.Base/Input/InputElement.cs | 26 ++++++++++----------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index a924b391ac..a91e37177a 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -13,6 +13,18 @@ namespace Avalonia.Input private static bool s_isHolding; private static CancellationTokenSource? s_holdCancellationToken; + /// + /// Defines the IsHoldingEnabled attached property. + /// + public static readonly AttachedProperty IsHoldingEnabledProperty = + AvaloniaProperty.RegisterAttached("IsHoldingEnabled", typeof(Gestures), true); + + /// + /// Defines the IsHoldWithMouseEnabled attached property. + /// + public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.RegisterAttached("IsHoldWithMouseEnabled", typeof(Gestures), false); + public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -75,6 +87,24 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); + public static bool GetIsHoldingEnabled(StyledElement element) + { + return element.GetValue(IsHoldingEnabledProperty); + } + public static void SetIsHoldingEnabled(StyledElement element, bool value) + { + element.SetValue(IsHoldingEnabledProperty, value); + } + + public static bool GetIsHoldWithMouseEnabled(StyledElement element) + { + return element.GetValue(IsHoldWithMouseEnabledProperty); + } + public static void SetIsHoldWithMouseEnabled(StyledElement element, bool value) + { + element.SetValue(IsHoldWithMouseEnabledProperty, value); + } + static Gestures() { InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed); diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 4a65134552..e1835241bf 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -45,18 +45,6 @@ namespace Avalonia.Input public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldingEnabledProperty = - AvaloniaProperty.Register(nameof(IsHoldingEnabled), true); - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.Register(nameof(IsHoldWithMouseEnabled), false); - /// /// Defines the property. /// @@ -209,7 +197,19 @@ namespace Avalonia.Input /// Defines the event. /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; - + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldingEnabledProperty = + Gestures.IsHoldingEnabledProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + Gestures.IsHoldWithMouseEnabledProperty.AddOwner(); + private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isKeyboardFocusWithin; From 57255586c27a1e1296f3b01f18bc3e9598d2ac69 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 27 Dec 2022 12:50:25 +0000 Subject: [PATCH 12/13] update hold tests --- src/Avalonia.Base/Input/Gestures.cs | 8 +-- .../Input/GesturesTests.cs | 68 ++++++++++++++++--- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index a91e37177a..9708ca2b48 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -267,11 +267,11 @@ namespace Avalonia.Input { return; } - } - if (s_isHolding && ev.Source is Interactive i) - { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); + if (s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); + } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 33a01fc9cd..1b0c941082 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -174,7 +174,46 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Raised_When_Pointer_Pressed() + public void Hold_Should_Be_Raised_After_Hold_Duration() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + HoldingState holding = HoldingState.Cancelled; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => holding = e.HoldingState); + + _mouse.Down(border); + Assert.False(holding != HoldingState.Cancelled); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + Assert.True(holding == HoldingState.Started); + + _mouse.Up(border); + + Assert.True(holding == HoldingState.Completed); + } + + [Fact] + public void Hold_Should_Not_Raised_When_Pointer_Released_Before_Timer() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -199,16 +238,19 @@ namespace Avalonia.Base.UnitTests.Input _mouse.Down(border); Assert.False(raised); + _mouse.Up(border); + Assert.False(raised); + // Verify timer duration, but execute it immediately. var timer = Assert.Single(scheduledTimers); Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); timer.action(); - Assert.True(raised); + Assert.False(raised); } [Fact] - public void Hold_Is_Not_Raised_When_Pointer_Released_Before_Timer() + public void Hold_Should_Not_Raised_When_Pointer_Is_Moved_Before_Timer() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -232,8 +274,8 @@ namespace Avalonia.Base.UnitTests.Input _mouse.Down(border); Assert.False(raised); - - _mouse.Up(border); + + _mouse.Move(border, position: new Point(20, 20)); Assert.False(raised); // Verify timer duration, but execute it immediately. @@ -245,7 +287,7 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Cancelled_When_Second_Contact_Is_Detected() + public void Hold_Should_Be_Cancelled_When_Second_Contact_Is_Detected() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -282,11 +324,12 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Cancelled_When_Pointer_Moves() + public void Hold_Should_Be_Cancelled_When_Pointer_Moves_Too_Far() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16)); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(iSettingsMock.Object); @@ -305,19 +348,22 @@ namespace Avalonia.Base.UnitTests.Input decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); _mouse.Down(border); - Assert.False(cancelled); var timer = Assert.Single(scheduledTimers); Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); timer.action(); - _mouse.Move(border, position: new Point(10, 10)); + _mouse.Move(border, position: new Point(3, 3)); + + Assert.False(cancelled); + + _mouse.Move(border, position: new Point(20, 20)); Assert.True(cancelled); } - + [Fact] - public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + public void Hold_Should_Not_Be_Raised_For_Multiple_Contacts() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); From 66ca4ca6399f964c30e09672aca42fb7d016b106 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 28 Dec 2022 08:55:21 +0000 Subject: [PATCH 13/13] remove hold styled properties in InputElement --- src/Avalonia.Base/Input/Gestures.cs | 2 +- src/Avalonia.Base/Input/InputElement.cs | 31 ------------------- .../Input/GesturesTests.cs | 12 +++---- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 9708ca2b48..54e61d89b2 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -183,7 +183,7 @@ namespace Avalonia.Input { DispatcherTimer.RunOnce(() => { - if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && (e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i))) { s_isHolding = true; i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index e1835241bf..f233fdce51 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -198,18 +198,6 @@ namespace Avalonia.Input /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldingEnabledProperty = - Gestures.IsHoldingEnabledProperty.AddOwner(); - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - Gestures.IsHoldWithMouseEnabledProperty.AddOwner(); - private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isKeyboardFocusWithin; @@ -414,25 +402,6 @@ namespace Avalonia.Input get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } - - /// - /// Gets or sets a value that determines whether the Holding event can originate - /// from that element. - /// - public bool IsHoldingEnabled - { - get { return GetValue(IsHoldingEnabledProperty); } - set { SetValue(IsHoldingEnabledProperty, value); } - } - - /// - /// Enables or disables support for the press and hold gesture through the left button on a mouse. - /// - public bool IsHoldWithMouseEnabled - { - get { return GetValue(IsHoldWithMouseEnabledProperty); } - set { SetValue(IsHoldWithMouseEnabledProperty, value); } - } /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 1b0c941082..84ee35ba61 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -226,7 +226,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -263,7 +263,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -300,7 +300,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -338,7 +338,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -376,7 +376,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border