Browse Source

Merge pull request #11518 from AvaloniaUI/emmauss/capture_gesture

Rework GestureRecognizer handling
pull/11592/head
Dan Walmsley 3 years ago
committed by GitHub
parent
commit
1643618ed0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
  2. 73
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  3. 23
      src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs
  4. 30
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  5. 31
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  6. 38
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  7. 1
      src/Avalonia.Base/Input/IPointer.cs
  8. 46
      src/Avalonia.Base/Input/InputElement.cs
  9. 18
      src/Avalonia.Base/Input/MouseDevice.cs
  10. 16
      src/Avalonia.Base/Input/PenDevice.cs
  11. 31
      src/Avalonia.Base/Input/Pointer.cs
  12. 10
      src/Avalonia.Base/Input/PointerEventArgs.cs
  13. 37
      src/Avalonia.Base/Input/TouchDevice.cs
  14. 2
      src/Avalonia.Controls/Primitives/Thumb.cs
  15. 3
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  16. 1
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  17. 20
      tests/Avalonia.UnitTests/TouchTestHelper.cs

37
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs

@ -0,0 +1,37 @@
namespace Avalonia.Input.GestureRecognizers
{
public abstract class GestureRecognizer : StyledElement
{
protected internal IInputElement? Target { get; internal set; }
protected abstract void PointerPressed(PointerPressedEventArgs e);
protected abstract void PointerReleased(PointerReleasedEventArgs e);
protected abstract void PointerMoved(PointerEventArgs e);
protected abstract void PointerCaptureLost(IPointer pointer);
internal void PointerPressedInternal(PointerPressedEventArgs e)
{
PointerPressed(e);
}
internal void PointerReleasedInternal(PointerReleasedEventArgs e)
{
PointerReleased(e);
}
internal void PointerMovedInternal(PointerEventArgs e)
{
PointerMoved(e);
}
internal void PointerCaptureLostInternal(IPointer pointer)
{
PointerCaptureLost(pointer);
}
protected void Capture(IPointer pointer)
{
(pointer as Pointer)?.CaptureGestureRecognizer(this);
}
}
}

73
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -6,29 +6,26 @@ using Avalonia.Reactive;
namespace Avalonia.Input.GestureRecognizers
{
public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher
public class GestureRecognizerCollection : IReadOnlyCollection<GestureRecognizer>
{
private readonly IInputElement _inputElement;
private List<IGestureRecognizer>? _recognizers;
private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
private List<GestureRecognizer>? _recognizers;
public GestureRecognizerCollection(IInputElement inputElement)
{
_inputElement = inputElement;
}
public void Add(IGestureRecognizer recognizer)
public void Add(GestureRecognizer recognizer)
{
if (_recognizers == null)
{
// We initialize the collection when the first recognizer is added
_recognizers = new List<IGestureRecognizer>();
_pointerGrabs = new Dictionary<IPointer, IGestureRecognizer>();
_recognizers = new List<GestureRecognizer>();
}
_recognizers.Add(recognizer);
recognizer.Initialize(_inputElement, this);
recognizer.Target = _inputElement;
// Hacks to make bindings work
@ -41,25 +38,22 @@ namespace Avalonia.Input.GestureRecognizers
}
}
static readonly List<IGestureRecognizer> s_Empty = new List<IGestureRecognizer>();
static readonly List<GestureRecognizer> s_Empty = new List<GestureRecognizer>();
public IEnumerator<IGestureRecognizer> GetEnumerator()
public IEnumerator<GestureRecognizer> GetEnumerator()
=> _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => _recognizers?.Count ?? 0;
internal bool HandlePointerPressed(PointerPressedEventArgs e)
{
if (_recognizers == null)
return false;
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerPressed(e);
r.PointerPressedInternal(e);
}
return e.Handled;
@ -69,17 +63,15 @@ namespace Avalonia.Input.GestureRecognizers
{
if (_recognizers == null)
return false;
if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
var pointer = e.Pointer as Pointer;
foreach (var r in _recognizers)
{
capture.PointerReleased(e);
if (pointer?.CapturedGestureRecognizer != null)
break;
r.PointerReleasedInternal(e);
}
else
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerReleased(e);
}
return e.Handled;
}
@ -87,41 +79,16 @@ namespace Avalonia.Input.GestureRecognizers
{
if (_recognizers == null)
return false;
if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
{
capture.PointerMoved(e);
}
else
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerMoved(e);
}
return e.Handled;
}
var pointer = e.Pointer as Pointer;
internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e)
{
if (_recognizers == null)
return;
_pointerGrabs!.Remove(e.Pointer);
foreach (var r in _recognizers)
{
r.PointerCaptureLost(e.Pointer);
}
}
if (pointer?.CapturedGestureRecognizer != null)
break;
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer)
{
pointer.Capture(_inputElement);
_pointerGrabs![pointer] = recognizer;
foreach (var r in _recognizers!)
{
if (r != recognizer)
r.PointerCaptureLost(pointer);
r.PointerMovedInternal(e);
}
return e.Handled;
}
}
}

23
src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs

@ -1,23 +0,0 @@
namespace Avalonia.Input.GestureRecognizers
{
public interface IGestureRecognizer
{
void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions);
void PointerPressed(PointerPressedEventArgs e);
void PointerReleased(PointerReleasedEventArgs e);
void PointerMoved(PointerEventArgs e);
void PointerCaptureLost(IPointer pointer);
}
public interface IGestureRecognizerActionsDispatcher
{
void Capture(IPointer pointer, IGestureRecognizer recognizer);
}
public enum GestureRecognizerResult
{
None,
Capture,
ReleaseCapture
}
}

30
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@ -2,10 +2,8 @@
namespace Avalonia.Input
{
public class PinchGestureRecognizer : StyledElement, IGestureRecognizer
public class PinchGestureRecognizer : GestureRecognizer
{
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private float _initialDistance;
private IPointer? _firstContact;
private Point _firstPoint;
@ -13,12 +11,6 @@ namespace Avalonia.Input
private Point _secondPoint;
private Point _origin;
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
@ -29,14 +21,14 @@ namespace Avalonia.Input
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
protected override void PointerCaptureLost(IPointer pointer)
{
RemoveContact(pointer);
}
public void PointerMoved(PointerEventArgs e)
protected override void PointerMoved(PointerEventArgs e)
{
if (_target != null && _target is Visual visual)
if (Target != null && Target is Visual visual)
{
if(_firstContact == e.Pointer)
{
@ -58,16 +50,16 @@ namespace Avalonia.Input
var scale = distance / _initialDistance;
var pinchEventArgs = new PinchEventArgs(scale, _origin);
_target?.RaiseEvent(pinchEventArgs);
Target?.RaiseEvent(pinchEventArgs);
e.Handled = pinchEventArgs.Handled;
}
}
}
public void PointerPressed(PointerPressedEventArgs e)
protected override void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
if (_firstContact == null)
{
@ -92,13 +84,13 @@ namespace Avalonia.Input
_origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
_actions!.Capture(_firstContact, this);
_actions!.Capture(_secondContact, this);
Capture(_firstContact);
Capture(_secondContact);
}
}
}
public void PointerReleased(PointerReleasedEventArgs e)
protected override void PointerReleased(PointerReleasedEventArgs e)
{
RemoveContact(e.Pointer);
}
@ -118,7 +110,7 @@ namespace Avalonia.Input
_secondContact = null;
}
_target?.RaiseEvent(new PinchEndedEventArgs());
Target?.RaiseEvent(new PinchEndedEventArgs());
}
}

31
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@ -1,15 +1,11 @@
using System;
using System.Diagnostics;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
public class PullGestureRecognizer : StyledElement, IGestureRecognizer
public class PullGestureRecognizer : GestureRecognizer
{
internal static int MinPullDetectionSize = 50;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private Point _initialPosition;
private int _gestureId;
private IPointer? _tracking;
@ -34,13 +30,7 @@ namespace Avalonia.Input
public PullGestureRecognizer() { }
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
public void PointerCaptureLost(IPointer pointer)
protected override void PointerCaptureLost(IPointer pointer)
{
if (_tracking == pointer)
{
@ -48,12 +38,13 @@ namespace Avalonia.Input
}
}
public void PointerMoved(PointerEventArgs e)
protected override void PointerMoved(PointerEventArgs e)
{
if (_tracking == e.Pointer && _target is Visual visual)
if (_tracking == e.Pointer && Target is Visual visual)
{
var currentPosition = e.GetPosition(visual);
_actions!.Capture(e.Pointer, this);
Capture(e.Pointer);
e.PreventGestureRecognition();
Vector delta = default;
switch (PullDirection)
@ -86,15 +77,15 @@ namespace Avalonia.Input
_pullInProgress = true;
var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection);
_target?.RaiseEvent(pullEventArgs);
Target?.RaiseEvent(pullEventArgs);
e.Handled = pullEventArgs.Handled;
}
}
public void PointerPressed(PointerPressedEventArgs e)
protected override void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
var position = e.GetPosition(visual);
@ -127,7 +118,7 @@ namespace Avalonia.Input
}
}
public void PointerReleased(PointerReleasedEventArgs e)
protected override void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer && _pullInProgress)
{
@ -141,7 +132,7 @@ namespace Avalonia.Input
_initialPosition = default;
_pullInProgress = false;
_target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
Target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
}
}
}

38
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -4,7 +4,7 @@ using Avalonia.Threading;
namespace Avalonia.Input.GestureRecognizers
{
public class ScrollGestureRecognizer : AvaloniaObject, IGestureRecognizer
public class ScrollGestureRecognizer : GestureRecognizer
{
// Pixels per second speed that is considered to be the stop of inertial scroll
internal const double InertialScrollSpeedEnd = 5;
@ -18,8 +18,6 @@ namespace Avalonia.Input.GestureRecognizers
private bool _scrolling;
private Point _trackedRootPoint;
private IPointer? _tracking;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private int _gestureId;
private Point _pointerPressedPoint;
private VelocityTracker? _velocityTracker;
@ -91,15 +89,9 @@ namespace Avalonia.Input.GestureRecognizers
{
get => _scrollStartDistance;
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
}
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
public void PointerPressed(PointerPressedEventArgs e)
protected override void PointerPressed(PointerPressedEventArgs e)
{
if (e.Pointer.IsPrimary &&
(e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
@ -107,15 +99,15 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
_tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId();
_trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target);
_trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)Target);
}
}
public void PointerMoved(PointerEventArgs e)
protected override void PointerMoved(PointerEventArgs e)
{
if (e.Pointer == _tracking)
{
var rootPoint = e.GetPosition((Visual?)_target);
var rootPoint = e.GetPosition((Visual?)Target);
if (!_scrolling)
{
if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance)
@ -131,7 +123,9 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
_actions!.Capture(e.Pointer, this);
Capture(e.Pointer);
e.PreventGestureRecognition();
}
}
@ -143,13 +137,13 @@ namespace Avalonia.Input.GestureRecognizers
_lastMoveTimestamp = e.Timestamp;
_trackedRootPoint = rootPoint;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true;
}
}
}
public void PointerCaptureLost(IPointer pointer)
protected override void PointerCaptureLost(IPointer pointer)
{
if (pointer == _tracking) EndGesture();
}
@ -161,7 +155,7 @@ namespace Avalonia.Input.GestureRecognizers
{
_inertia = default;
_scrolling = false;
_target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_gestureId = 0;
_lastMoveTimestamp = null;
}
@ -169,7 +163,7 @@ namespace Avalonia.Input.GestureRecognizers
}
public void PointerReleased(PointerReleasedEventArgs e)
protected override void PointerReleased(PointerReleasedEventArgs e)
{
if (e.Pointer == _tracking && _scrolling)
{
@ -188,7 +182,7 @@ namespace Avalonia.Input.GestureRecognizers
var savedGestureId = _gestureId;
var st = Stopwatch.StartNew();
var lastTime = TimeSpan.Zero;
_target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));
Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));
DispatcherTimer.Run(() =>
{
// Another gesture has started, finish the current one
@ -203,7 +197,7 @@ namespace Avalonia.Input.GestureRecognizers
var speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds);
var distance = speed * elapsedSinceLastTick.TotalSeconds;
var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance);
_target!.RaiseEvent(scrollGestureEventArgs);
Target!.RaiseEvent(scrollGestureEventArgs);
if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture)
{

1
src/Avalonia.Base/Input/IPointer.cs

@ -1,3 +1,4 @@
using Avalonia.Input.GestureRecognizers;
using Avalonia.Metadata;
namespace Avalonia.Input

46
src/Avalonia.Base/Input/InputElement.cs

@ -225,6 +225,11 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
// Gesture only handlers
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true);
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true);
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true);
}
public InputElement()
@ -583,10 +588,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerMoved(PointerEventArgs e)
{
if (_gestureRecognizers?.HandlePointerMoved(e) == true)
{
e.Handled = true;
}
}
/// <summary>
@ -595,10 +596,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerPressed(PointerPressedEventArgs e)
{
if (_gestureRecognizers?.HandlePointerPressed(e) == true)
{
e.Handled = true;
}
}
/// <summary>
@ -607,10 +604,33 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerReleased(PointerReleasedEventArgs e)
{
if (_gestureRecognizers?.HandlePointerReleased(e) == true)
{
e.Handled = true;
}
}
private void OnGesturePointerReleased(PointerReleasedEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)
if (_gestureRecognizers?.HandlePointerReleased(e) == true)
{
e.Handled = true;
}
}
private void OnGesturePointerPressed(PointerPressedEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)
if (_gestureRecognizers?.HandlePointerPressed(e) == true)
{
e.Handled = true;
}
}
private void OnGesturePointerMoved(PointerEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)
if (_gestureRecognizers?.HandlePointerMoved(e) == true)
{
e.Handled = true;
}
}
/// <summary>
@ -619,7 +639,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{
_gestureRecognizers?.HandlePointerCaptureLost(e);
}
/// <summary>

18
src/Avalonia.Base/Input/MouseDevice.cs

@ -7,7 +7,7 @@ using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using Avalonia.Input.GestureRecognizers;
#pragma warning disable CS0618
namespace Avalonia.Input
@ -163,17 +163,21 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var source = _pointer.Captured ?? hitTest;
var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest;
if (source is object)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
source.RaiseEvent(e);
if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerMovedInternal(e);
else
source.RaiseEvent(e);
return e.Handled;
}
return false;
}
@ -183,15 +187,19 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var source = _pointer.Captured ?? hitTest;
var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
_lastMouseDownButton);
source?.RaiseEvent(e);
if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerReleasedInternal(e);
else
source?.RaiseEvent(e);
_pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default;
return e.Handled;
}

16
src/Avalonia.Base/Input/PenDevice.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Metadata;
@ -114,14 +115,17 @@ namespace Avalonia.Input
KeyModifiers inputModifiers, IInputElement? hitTest,
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints)
{
var source = pointer.Captured ?? hitTest;
var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
source.RaiseEvent(e);
if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerMovedInternal(e);
else
source.RaiseEvent(e);
return e.Handled;
}
@ -132,15 +136,19 @@ namespace Avalonia.Input
IInputElement root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
var source = pointer.Captured ?? hitTest;
var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers,
_lastMouseDownButton);
source.RaiseEvent(e);
if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerReleasedInternal(e);
else
source.RaiseEvent(e);
pointer.Capture(null);
pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default;
return e.Handled;
}

31
src/Avalonia.Base/Input/Pointer.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.GestureRecognizers;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -52,6 +53,9 @@ namespace Avalonia.Input
if (Captured is Visual v3)
v3.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null)
CaptureGestureRecognizer(null);
}
static IInputElement? GetNextCapture(Visual parent)
@ -69,6 +73,31 @@ namespace Avalonia.Input
public PointerType Type { get; }
public bool IsPrimary { get; }
public void Dispose() => Capture(null);
/// <summary>
/// Gets the gesture recognizer that is currently capturing by the pointer, if any.
/// </summary>
internal GestureRecognizer? CapturedGestureRecognizer { get; private set; }
public void Dispose()
{
Capture(null);
}
/// <summary>
/// Captures pointer input to the specified gesture recognizer.
/// </summary>
/// <param name="gestureRecognizer">The gesture recognizer.</param>
/// </remarks>
internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
{
if (CapturedGestureRecognizer != gestureRecognizer)
CapturedGestureRecognizer?.PointerCaptureLostInternal(this);
if (gestureRecognizer != null)
Capture(null);
CapturedGestureRecognizer = gestureRecognizer;
}
}
}

10
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -58,6 +58,8 @@ namespace Avalonia.Input
/// </summary>
public ulong Timestamp { get; }
internal bool IsGestureRecognitionSkipped { get; private set; }
/// <summary>
/// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
/// </summary>
@ -121,6 +123,14 @@ namespace Avalonia.Input
return points;
}
/// <summary>
/// Prevents this event from being handled by other gesture recognizers in the route
/// </summary>
public void PreventGestureRecognition()
{
IsGestureRecognitionSkipped = true;
}
/// <summary>
/// Returns the current pointer point properties
/// </summary>

37
src/Avalonia.Base/Input/TouchDevice.cs

@ -52,6 +52,7 @@ namespace Avalonia.Input
}
var target = pointer.Captured ?? args.Root;
var gestureTarget = pointer.CapturedGestureRecognizer?.Target;
var updateKind = args.Type.ToUpdateKind();
var keyModifier = args.InputModifiers.ToKeyModifiers();
@ -95,10 +96,19 @@ namespace Avalonia.Input
_pointers.Remove(args.RawPointerId);
using (pointer)
{
target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
(Visual)args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left));
target = gestureTarget ?? target;
var e = new PointerReleasedEventArgs(target, pointer,
(Visual)args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left);
if (gestureTarget != null)
{
pointer?.CapturedGestureRecognizer?.PointerReleasedInternal(e);
}
else
{
target.RaiseEvent(e);
}
}
}
@ -106,15 +116,28 @@ namespace Avalonia.Input
{
_pointers.Remove(args.RawPointerId);
using (pointer)
pointer.Capture(null);
{
pointer?.Capture(null);
pointer?.CaptureGestureRecognizer(null);
}
}
if (args.Type == RawPointerEventType.TouchUpdate)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root,
target = gestureTarget ?? target;
var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, (Visual)args.Root,
args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
keyModifier, args.IntermediatePoints));
keyModifier, args.IntermediatePoints);
if (gestureTarget != null)
{
pointer?.CapturedGestureRecognizer?.PointerMovedInternal(e);
}
else
{
target.RaiseEvent(e);
}
}
}

2
src/Avalonia.Controls/Primitives/Thumb.cs

@ -113,6 +113,8 @@ namespace Avalonia.Controls.Primitives
PseudoClasses.Add(":pressed");
e.PreventGestureRecognition();
RaiseEvent(ev);
}

3
src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs

@ -238,7 +238,6 @@ namespace Avalonia.Controls
visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3(visualizerVisual.Offset.X, 0, 0) :
new Vector3(0, visualizerVisual.Offset.Y, 0);
visual.Offset = default;
_content.InvalidateMeasure();
break;
case RefreshVisualizerState.Interacting:
@ -452,8 +451,6 @@ namespace Avalonia.Controls
_interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty)
.Subscribe(InteractionRatioObserver);
var visual = RefreshInfoProvider.Visual;
_executingRatio = RefreshInfoProvider.ExecutionRatio;
}
else

1
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@ -2,6 +2,7 @@
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Moq;

20
tests/Avalonia.UnitTests/TouchTestHelper.cs

@ -27,8 +27,13 @@ namespace Avalonia.UnitTests
public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position,
Timestamp(), PointerPointProperties.None, modifiers));
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position,
Timestamp(), PointerPointProperties.None, modifiers);
if (_pointer.CapturedGestureRecognizer != null)
_pointer.CapturedGestureRecognizer.PointerMovedInternal(e);
else
target.RaiseEvent(e);
}
public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default)
@ -36,9 +41,16 @@ namespace Avalonia.UnitTests
public void Up(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default)
{
source.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None,
modifiers, MouseButton.None));
var e = new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None,
modifiers, MouseButton.None);
if (_pointer.CapturedGestureRecognizer != null)
_pointer.CapturedGestureRecognizer.PointerReleasedInternal(e);
else
source.RaiseEvent(e);
_pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
}
public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default)

Loading…
Cancel
Save