Browse Source

[iOS] Enable Pointer/Trackpad scrolling (#19342)

* [iOS] Enable Pointer/Trackpad scrolling

* Cleanup

* Update comments, make sure StopMomentumScrolling is called when momentum stops

* Wrap RawMouseWheel Invoke call with null check

* Replace check
release/11.3.3
Tim Miller 6 months ago
committed by Julien Lebosquain
parent
commit
3da7aa4840
  1. 11
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  2. 132
      src/iOS/Avalonia.iOS/InputHandler.cs

11
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -76,6 +76,17 @@ namespace Avalonia.iOS
{
#if !TVOS
MultipleTouchEnabled = true;
if (OperatingSystem.IsIOSVersionAtLeast(13, 4) || OperatingSystem.IsMacCatalyst())
{
var scrollGestureRecognizer = new UIPanGestureRecognizer(_input.HandleScrollWheel)
{
// Only respond to scroll events, not touches
MaximumNumberOfTouches = 0,
AllowedScrollTypesMask = UIScrollTypeMask.Discrete | UIScrollTypeMask.Continuous
};
AddGestureRecognizer(scrollGestureRecognizer);
}
#endif
}
}

132
src/iOS/Avalonia.iOS/InputHandler.cs

@ -6,6 +6,9 @@ using Avalonia.Input.Raw;
using Avalonia.Platform;
using Foundation;
using UIKit;
#if !TVOS
using CoreAnimation;
#endif
namespace Avalonia.iOS;
@ -20,6 +23,14 @@ internal sealed class InputHandler
private readonly PenDevice _penDevice = new(releasePointerOnPenUp: true);
private static long _nextTouchPointId = 1;
private readonly Dictionary<UITouch, long> _knownTouches = new();
private Point? _cachedScrollLocation;
#if !TVOS
private CADisplayLink? _momentumDisplayLink;
private double _momentumVelocityX;
private double _momentumVelocityY;
private const double DecelerationRate = 0.95;
#endif
public InputHandler(AvaloniaView view, ITopLevelImpl tl)
{
@ -249,6 +260,127 @@ internal sealed class InputHandler
return modifier;
}
public void HandleScrollWheel(UIPanGestureRecognizer recognizer)
{
switch (recognizer.State)
{
case UIGestureRecognizerState.Began:
// We've started scrolling, stop any previous inertia scrolling
// and cache the current scroll location.
StopMomentumScrolling();
_cachedScrollLocation = recognizer.LocationInView(_view).ToAvalonia();
return;
case UIGestureRecognizerState.Changed:
// When you are actively scrolling, we send the scroll events
SendActiveScrollEvent(recognizer);
return;
case UIGestureRecognizerState.Ended:
// When you stop scrolling, we start inertia scrolling
// UpdateInertiaScrolling will check when the inertia stops
// and will call StopMomentumScrolling
StartInertiaScrolling(recognizer);
return;
case UIGestureRecognizerState.Cancelled:
case UIGestureRecognizerState.Failed:
// If the gesture is cancelled or failed, stop.
StopMomentumScrolling();
return;
default:
return;
}
}
private void SendActiveScrollEvent(UIPanGestureRecognizer recognizer)
{
#if !TVOS
// iOS 13.4+ and Catalyst support scroll wheel events
if (!OperatingSystem.IsIOSVersionAtLeast(13, 4) && !OperatingSystem.IsMacCatalyst())
return;
var velocity = recognizer.VelocityInView(_view);
// Use much more sensitive scaling for active scrolling to match AppKit.
// macOS uses small deltas, so we need much larger divisors
var scaleFactor = 3000.0;
var deltaX = velocity.X / scaleFactor;
var deltaY = velocity.Y / scaleFactor;
_tl.Input?.Invoke(new RawMouseWheelEventArgs(
_mouseDevice,
(ulong)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()),
Root,
_cachedScrollLocation ?? new Point(0, 0),
new Vector(deltaX, deltaY),
RawInputModifiers.None
));
#endif
}
private void StartInertiaScrolling(UIPanGestureRecognizer recognizer)
{
#if !TVOS
// iOS 13.4+ and Catalyst support scroll wheel events
if (!OperatingSystem.IsIOSVersionAtLeast(13, 4) && !OperatingSystem.IsMacCatalyst())
return;
var velocity = recognizer.VelocityInView(_view);
var scaleFactor = 800.0;
_momentumVelocityX = velocity.X / scaleFactor;
_momentumVelocityY = velocity.Y / scaleFactor;
_momentumDisplayLink = CADisplayLink.Create(UpdateInertiaScrolling);
_momentumDisplayLink.AddToRunLoop(NSRunLoop.Main, NSRunLoopMode.Common);
#endif
}
private void StopMomentumScrolling()
{
#if !TVOS
if (_momentumDisplayLink != null)
{
// Invalidate removes it from all run loops
// https://developer.apple.com/documentation/quartzcore/cadisplaylink
_momentumDisplayLink.Invalidate();
_momentumDisplayLink = null;
}
_momentumVelocityX = 0;
_momentumVelocityY = 0;
_cachedScrollLocation = null;
#endif
}
private void UpdateInertiaScrolling()
{
#if !TVOS
_momentumVelocityX *= DecelerationRate;
_momentumVelocityY *= DecelerationRate;
var currentMagnitude = Math.Sqrt(_momentumVelocityX * _momentumVelocityX + _momentumVelocityY * _momentumVelocityY);
if (currentMagnitude < 0.0001 || _cachedScrollLocation is null)
{
StopMomentumScrolling();
return;
}
// UIPanGestureRecognizer will continue to upload the location of the pointer,
// to where it would be if it was moving with the current velocity,
// even though the pointer on screen is not moving.
// We can cache the location when we start scrolling and keep it static
// until the inertia stops.
_tl.Input?.Invoke(new RawMouseWheelEventArgs(
_mouseDevice,
(ulong)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()),
Root,
_cachedScrollLocation.Value,
new Vector(_momentumVelocityX, _momentumVelocityY),
RawInputModifiers.None
));
#endif
}
#pragma warning disable CA1416
private static Dictionary<UIKeyboardHidUsage, PhysicalKey> s_keys = new()
{

Loading…
Cancel
Save