15 changed files with 521 additions and 77 deletions
@ -0,0 +1,127 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Input.GestureRecognizers |
||||
|
{ |
||||
|
public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher |
||||
|
{ |
||||
|
private readonly IInputElement _inputElement; |
||||
|
private List<IGestureRecognizer> _recognizers; |
||||
|
private Dictionary<IPointer, IGestureRecognizer> _pointerGrabs; |
||||
|
|
||||
|
|
||||
|
public GestureRecognizerCollection(IInputElement inputElement) |
||||
|
{ |
||||
|
_inputElement = inputElement; |
||||
|
} |
||||
|
|
||||
|
public void Add(IGestureRecognizer recognizer) |
||||
|
{ |
||||
|
if (_recognizers == null) |
||||
|
{ |
||||
|
// We initialize the collection when the first recognizer is added
|
||||
|
_recognizers = new List<IGestureRecognizer>(); |
||||
|
_pointerGrabs = new Dictionary<IPointer, IGestureRecognizer>(); |
||||
|
} |
||||
|
|
||||
|
_recognizers.Add(recognizer); |
||||
|
recognizer.Initialize(_inputElement, this); |
||||
|
|
||||
|
// Hacks to make bindings work
|
||||
|
|
||||
|
if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical) |
||||
|
{ |
||||
|
logical.SetParent(logicalParent); |
||||
|
if (recognizer is IStyleable styleableRecognizer |
||||
|
&& _inputElement is IStyleable styleableParent) |
||||
|
styleableRecognizer.Bind(StyledElement.TemplatedParentProperty, |
||||
|
styleableParent.GetObservable(StyledElement.TemplatedParentProperty)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static readonly List<IGestureRecognizer> s_Empty = new List<IGestureRecognizer>(); |
||||
|
|
||||
|
public IEnumerator<IGestureRecognizer> 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); |
||||
|
} |
||||
|
|
||||
|
return e.Handled; |
||||
|
} |
||||
|
|
||||
|
internal bool HandlePointerReleased(PointerReleasedEventArgs e) |
||||
|
{ |
||||
|
if (_recognizers == null) |
||||
|
return false; |
||||
|
if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) |
||||
|
{ |
||||
|
capture.PointerReleased(e); |
||||
|
} |
||||
|
else |
||||
|
foreach (var r in _recognizers) |
||||
|
{ |
||||
|
if (e.Handled) |
||||
|
break; |
||||
|
r.PointerReleased(e); |
||||
|
} |
||||
|
return e.Handled; |
||||
|
} |
||||
|
|
||||
|
internal bool HandlePointerMoved(PointerEventArgs e) |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e) |
||||
|
{ |
||||
|
if (_recognizers == null) |
||||
|
return; |
||||
|
_pointerGrabs.Remove(e.Pointer); |
||||
|
foreach (var r in _recognizers) |
||||
|
{ |
||||
|
if(e.Handled) |
||||
|
break; |
||||
|
r.PointerCaptureLost(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) |
||||
|
{ |
||||
|
pointer.Capture(_inputElement); |
||||
|
_pointerGrabs[pointer] = recognizer; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
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(PointerCaptureLostEventArgs e); |
||||
|
} |
||||
|
|
||||
|
public interface IGestureRecognizerActionsDispatcher |
||||
|
{ |
||||
|
void Capture(IPointer pointer, IGestureRecognizer recognizer); |
||||
|
} |
||||
|
|
||||
|
public enum GestureRecognizerResult |
||||
|
{ |
||||
|
None, |
||||
|
Capture, |
||||
|
ReleaseCapture |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,129 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Input.GestureRecognizers |
||||
|
{ |
||||
|
public class ScrollGestureRecognizer |
||||
|
: StyledElement, // It's not an "element" in any way, shape or form, but TemplateBinding refuse to work otherwise
|
||||
|
IGestureRecognizer |
||||
|
{ |
||||
|
private bool _scrolling; |
||||
|
private Point _trackedRootPoint; |
||||
|
private IPointer _tracking; |
||||
|
private IInputElement _target; |
||||
|
private IGestureRecognizerActionsDispatcher _actions; |
||||
|
private bool _canHorizontallyScroll; |
||||
|
private bool _canVerticallyScroll; |
||||
|
private int _gestureId; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="CanHorizontallyScroll"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanHorizontallyScrollProperty = |
||||
|
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>( |
||||
|
nameof(CanHorizontallyScroll), |
||||
|
o => o.CanHorizontallyScroll, |
||||
|
(o, v) => o.CanHorizontallyScroll = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="CanVerticallyScroll"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanVerticallyScrollProperty = |
||||
|
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>( |
||||
|
nameof(CanVerticallyScroll), |
||||
|
o => o.CanVerticallyScroll, |
||||
|
(o, v) => o.CanVerticallyScroll = v); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
||||
|
/// </summary>
|
||||
|
public bool CanHorizontallyScroll |
||||
|
{ |
||||
|
get => _canHorizontallyScroll; |
||||
|
set => SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
||||
|
/// </summary>
|
||||
|
public bool CanVerticallyScroll |
||||
|
{ |
||||
|
get => _canVerticallyScroll; |
||||
|
set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) |
||||
|
{ |
||||
|
_target = target; |
||||
|
_actions = actions; |
||||
|
} |
||||
|
|
||||
|
public void PointerPressed(PointerPressedEventArgs e) |
||||
|
{ |
||||
|
if (e.Pointer.IsPrimary && e.Pointer.Type == PointerType.Touch) |
||||
|
{ |
||||
|
_tracking = e.Pointer; |
||||
|
_scrolling = false; |
||||
|
_trackedRootPoint = e.GetPosition(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Arbitrary chosen value, probably need to move that to platform settings or something
|
||||
|
private const double ScrollStartDistance = 30; |
||||
|
public void PointerMoved(PointerEventArgs e) |
||||
|
{ |
||||
|
if (e.Pointer == _tracking) |
||||
|
{ |
||||
|
var rootPoint = e.GetPosition(null); |
||||
|
if (!_scrolling) |
||||
|
{ |
||||
|
if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) |
||||
|
_scrolling = true; |
||||
|
if (CanVerticallyScroll && Math.Abs(_trackedRootPoint.Y - rootPoint.Y) > ScrollStartDistance) |
||||
|
_scrolling = true; |
||||
|
if (_scrolling) |
||||
|
{ |
||||
|
_actions.Capture(e.Pointer, this); |
||||
|
_gestureId = ScrollGestureEventArgs.GetNextFreeId(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (_scrolling) |
||||
|
{ |
||||
|
var vector = _trackedRootPoint - rootPoint; |
||||
|
_trackedRootPoint = rootPoint; |
||||
|
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void PointerCaptureLost(PointerCaptureLostEventArgs e) |
||||
|
{ |
||||
|
if (e.Pointer == _tracking) EndGesture(); |
||||
|
} |
||||
|
|
||||
|
void EndGesture() |
||||
|
{ |
||||
|
_tracking = null; |
||||
|
if (_scrolling) |
||||
|
{ |
||||
|
_scrolling = false; |
||||
|
_target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void PointerReleased(PointerReleasedEventArgs e) |
||||
|
{ |
||||
|
// TODO: handle inertia
|
||||
|
if (e.Pointer == _tracking && _scrolling) |
||||
|
{ |
||||
|
e.Handled = true; |
||||
|
EndGesture(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using Avalonia.Interactivity; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
public class ScrollGestureEventArgs : RoutedEventArgs |
||||
|
{ |
||||
|
public int Id { get; } |
||||
|
public Vector Delta { get; } |
||||
|
private static int _nextId = 1; |
||||
|
|
||||
|
public static int GetNextFreeId() => _nextId++; |
||||
|
|
||||
|
public ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent) |
||||
|
{ |
||||
|
Id = id; |
||||
|
Delta = delta; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class ScrollGestureEndedEventArgs : RoutedEventArgs |
||||
|
{ |
||||
|
public int Id { get; } |
||||
|
|
||||
|
public ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent) |
||||
|
{ |
||||
|
Id = id; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue