Browse Source

rework gesture routing #11522

pull/11518/head
Emmanuel Hansen 3 years ago
parent
commit
8c0dfbaf08
  1. 50
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  2. 1
      src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs
  3. 4
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  4. 6
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  5. 6
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  6. 46
      src/Avalonia.Base/Input/InputElement.cs
  7. 36
      src/Avalonia.Base/Input/MouseDevice.cs
  8. 35
      src/Avalonia.Base/Input/PenDevice.cs
  9. 5
      src/Avalonia.Base/Input/Pointer.cs
  10. 10
      src/Avalonia.Base/Input/PointerEventArgs.cs
  11. 35
      src/Avalonia.Base/Input/TouchDevice.cs
  12. 2
      src/Avalonia.Controls/Primitives/Thumb.cs

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

@ -10,8 +10,6 @@ namespace Avalonia.Input.GestureRecognizers
{ {
private readonly IInputElement _inputElement; private readonly IInputElement _inputElement;
private List<IGestureRecognizer>? _recognizers; private List<IGestureRecognizer>? _recognizers;
private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
public GestureRecognizerCollection(IInputElement inputElement) public GestureRecognizerCollection(IInputElement inputElement)
{ {
@ -24,7 +22,6 @@ namespace Avalonia.Input.GestureRecognizers
{ {
// We initialize the collection when the first recognizer is added // We initialize the collection when the first recognizer is added
_recognizers = new List<IGestureRecognizer>(); _recognizers = new List<IGestureRecognizer>();
_pointerGrabs = new Dictionary<IPointer, IGestureRecognizer>();
} }
_recognizers.Add(recognizer); _recognizers.Add(recognizer);
@ -57,8 +54,6 @@ namespace Avalonia.Input.GestureRecognizers
return false; return false;
foreach (var r in _recognizers) foreach (var r in _recognizers)
{ {
if (e.Handled)
break;
r.PointerPressed(e); r.PointerPressed(e);
} }
@ -69,17 +64,15 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; 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.PointerReleased(e);
} }
else
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerReleased(e);
}
return e.Handled; return e.Handled;
} }
@ -87,41 +80,24 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; return false;
if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture)) var pointer = e.Pointer as Pointer;
{
capture.PointerMoved(e);
}
else
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerMoved(e);
}
return e.Handled;
}
internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e)
{
if (_recognizers == null)
return;
_pointerGrabs!.Remove(e.Pointer);
foreach (var r in _recognizers) foreach (var r in _recognizers)
{ {
r.PointerCaptureLost(e.Pointer); if (pointer?.CapturedGestureRecognizer != null)
break;
r.PointerMoved(e);
} }
return e.Handled;
} }
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer)
{ {
var p = pointer as Pointer; var p = pointer as Pointer;
if (p != null && p.CapturedGestureRecognizer != null && recognizer != p.CapturedGestureRecognizer)
return;
pointer.Capture(_inputElement);
p?.CaptureGestureRecognizer(recognizer); p?.CaptureGestureRecognizer(recognizer);
_pointerGrabs![pointer] = recognizer;
foreach (var r in _recognizers!) foreach (var r in _recognizers!)
{ {
if (r != recognizer) if (r != recognizer)

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

@ -2,6 +2,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
public interface IGestureRecognizer public interface IGestureRecognizer
{ {
IInputElement? Target { get; }
void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions); void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions);
void PointerPressed(PointerPressedEventArgs e); void PointerPressed(PointerPressedEventArgs e);
void PointerReleased(PointerReleasedEventArgs e); void PointerReleased(PointerReleasedEventArgs e);

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

@ -2,7 +2,7 @@
namespace Avalonia.Input namespace Avalonia.Input
{ {
public class PinchGestureRecognizer : StyledElement, IGestureRecognizer public class PinchGestureRecognizer : AvaloniaObject, IGestureRecognizer
{ {
private IInputElement? _target; private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions; private IGestureRecognizerActionsDispatcher? _actions;
@ -13,6 +13,8 @@ namespace Avalonia.Input
private Point _secondPoint; private Point _secondPoint;
private Point _origin; private Point _origin;
public IInputElement? Target => _target;
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{ {
_target = target; _target = target;

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

@ -1,10 +1,9 @@
using System; using System;
using System.Diagnostics;
using Avalonia.Input.GestureRecognizers; using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input namespace Avalonia.Input
{ {
public class PullGestureRecognizer : StyledElement, IGestureRecognizer public class PullGestureRecognizer : AvaloniaObject, IGestureRecognizer
{ {
internal static int MinPullDetectionSize = 50; internal static int MinPullDetectionSize = 50;
@ -27,6 +26,8 @@ namespace Avalonia.Input
set => SetValue(PullDirectionProperty, value); set => SetValue(PullDirectionProperty, value);
} }
public IInputElement? Target => _target;
public PullGestureRecognizer(PullDirection pullDirection) public PullGestureRecognizer(PullDirection pullDirection)
{ {
PullDirection = pullDirection; PullDirection = pullDirection;
@ -54,6 +55,7 @@ namespace Avalonia.Input
{ {
var currentPosition = e.GetPosition(visual); var currentPosition = e.GetPosition(visual);
_actions!.Capture(e.Pointer, this); _actions!.Capture(e.Pointer, this);
e.PreventGestureRecognition();
Vector delta = default; Vector delta = default;
switch (PullDirection) switch (PullDirection)

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

@ -91,7 +91,9 @@ namespace Avalonia.Input.GestureRecognizers
{ {
get => _scrollStartDistance; get => _scrollStartDistance;
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
} }
public IInputElement? Target => _target;
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{ {
@ -132,6 +134,8 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance)); _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
_actions!.Capture(e.Pointer, this); _actions!.Capture(e.Pointer, this);
e.PreventGestureRecognition();
} }
} }

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

@ -225,6 +225,11 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e)); PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e)); PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(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() public InputElement()
@ -583,10 +588,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerMoved(PointerEventArgs e) protected virtual void OnPointerMoved(PointerEventArgs e)
{ {
if (_gestureRecognizers?.HandlePointerMoved(e) == true)
{
e.Handled = true;
}
} }
/// <summary> /// <summary>
@ -595,10 +596,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerPressed(PointerPressedEventArgs e) protected virtual void OnPointerPressed(PointerPressedEventArgs e)
{ {
if (_gestureRecognizers?.HandlePointerPressed(e) == true)
{
e.Handled = true;
}
} }
/// <summary> /// <summary>
@ -607,10 +604,33 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerReleased(PointerReleasedEventArgs e) 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> /// <summary>
@ -619,7 +639,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e) protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{ {
_gestureRecognizers?.HandlePointerCaptureLost(e);
} }
/// <summary> /// <summary>

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

@ -163,7 +163,22 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device)); device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root)); root = root ?? throw new ArgumentNullException(nameof(root));
var source = _pointer.Captured ?? hitTest; IInputElement source;
if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer)
{
source = gestureRecognizer.Target ?? hitTest;
if(source != null)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
gestureRecognizer.PointerMoved(e);
return e.Handled;
}
}
source = _pointer.Captured ?? hitTest;
if (source is object) if (source is object)
{ {
@ -174,6 +189,7 @@ namespace Avalonia.Input
return e.Handled; return e.Handled;
} }
return false; return false;
} }
@ -183,7 +199,22 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device)); device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root)); root = root ?? throw new ArgumentNullException(nameof(root));
var source = _pointer.Captured ?? hitTest; IInputElement source;
if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer)
{
source = gestureRecognizer.Target ?? hitTest;
if (source != null)
{
var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
_lastMouseDownButton);
gestureRecognizer.PointerReleased(e);
return e.Handled;
}
}
source = _pointer.Captured ?? hitTest;
if (source is not null) if (source is not null)
{ {
@ -192,6 +223,7 @@ namespace Avalonia.Input
source?.RaiseEvent(e); source?.RaiseEvent(e);
_pointer.Capture(null); _pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default; _lastMouseDownButton = default;
return e.Handled; return e.Handled;
} }

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

@ -114,7 +114,22 @@ namespace Avalonia.Input
KeyModifiers inputModifiers, IInputElement? hitTest, KeyModifiers inputModifiers, IInputElement? hitTest,
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints) Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints)
{ {
var source = pointer.Captured ?? hitTest; IInputElement source;
if (pointer.CapturedGestureRecognizer is { } gestureRecognizer)
{
source = gestureRecognizer.Target ?? hitTest;
if (source != null)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
gestureRecognizer.PointerMoved(e);
return e.Handled;
}
}
source = pointer.Captured ?? hitTest;
if (source is not null) if (source is not null)
{ {
@ -132,7 +147,22 @@ namespace Avalonia.Input
IInputElement root, Point p, PointerPointProperties properties, IInputElement root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest) KeyModifiers inputModifiers, IInputElement? hitTest)
{ {
var source = pointer.Captured ?? hitTest; IInputElement source;
if (pointer.CapturedGestureRecognizer is { } gestureRecognizer)
{
source = gestureRecognizer.Target ?? hitTest;
if (source != null)
{
var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers,
_lastMouseDownButton);
gestureRecognizer.PointerReleased(e);
return e.Handled;
}
}
source = pointer.Captured ?? hitTest;
if (source is not null) if (source is not null)
{ {
@ -141,6 +171,7 @@ namespace Avalonia.Input
source.RaiseEvent(e); source.RaiseEvent(e);
pointer.Capture(null); pointer.Capture(null);
pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default; _lastMouseDownButton = default;
return e.Handled; return e.Handled;
} }

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

@ -54,7 +54,7 @@ namespace Avalonia.Input
if (Captured is Visual v3) if (Captured is Visual v3)
v3.DetachedFromVisualTree += OnCaptureDetached; v3.DetachedFromVisualTree += OnCaptureDetached;
if (Captured == null) if (Captured != null)
CaptureGestureRecognizer(null); CaptureGestureRecognizer(null);
} }
@ -94,6 +94,9 @@ namespace Avalonia.Input
if (CapturedGestureRecognizer != gestureRecognizer) if (CapturedGestureRecognizer != gestureRecognizer)
CapturedGestureRecognizer?.PointerCaptureLost(this); CapturedGestureRecognizer?.PointerCaptureLost(this);
if (gestureRecognizer != null)
Capture(null);
CapturedGestureRecognizer = gestureRecognizer; CapturedGestureRecognizer = gestureRecognizer;
} }
} }

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

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

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

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

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

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

Loading…
Cancel
Save