|
@@ -0,0 +1,158 @@
|
|
|
+using Avalonia;
|
|
|
+using Avalonia.Input;
|
|
|
+using Avalonia.Input.GestureRecognizers;
|
|
|
+using Avalonia.Interactivity;
|
|
|
+
|
|
|
+namespace InABox.Avalonia.Components.ImageEditing;
|
|
|
+
|
|
|
+public class PanAndZoomEventArgs : RoutedEventArgs
|
|
|
+{
|
|
|
+ public PanAndZoomEventArgs(double scale, Point scaleOrigin, Vector pan) : base(PanAndZoomGestureRecognizer.PanAndZoomEvent)
|
|
|
+ {
|
|
|
+ Scale = scale;
|
|
|
+ ScaleOrigin = scaleOrigin;
|
|
|
+ Pan = pan;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double Scale { get; } = 1;
|
|
|
+
|
|
|
+ public Point ScaleOrigin { get; }
|
|
|
+
|
|
|
+ public Vector Pan { get; set; }
|
|
|
+}
|
|
|
+
|
|
|
+public class PanAndZoomEndedEventArgs : RoutedEventArgs
|
|
|
+{
|
|
|
+ public PanAndZoomEndedEventArgs() : base(PanAndZoomGestureRecognizer.PanAndZoomEndedEvent)
|
|
|
+ {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Adapted from the PinchGestureRecognizer built in to Avalonia, but with panning added.
|
|
|
+public class PanAndZoomGestureRecognizer : GestureRecognizer
|
|
|
+{
|
|
|
+ public static readonly RoutedEvent<PanAndZoomEventArgs> PanAndZoomEvent =
|
|
|
+ RoutedEvent.Register<PanAndZoomEventArgs>(
|
|
|
+ "PanAndZoom", RoutingStrategies.Bubble, typeof(PanAndZoomGestureRecognizer));
|
|
|
+
|
|
|
+ public static readonly RoutedEvent<PanAndZoomEndedEventArgs> PanAndZoomEndedEvent =
|
|
|
+ RoutedEvent.Register<PanAndZoomEndedEventArgs>(
|
|
|
+ "PanAndZoomEnded", RoutingStrategies.Bubble, typeof(PanAndZoomGestureRecognizer));
|
|
|
+
|
|
|
+ private float _initialDistance;
|
|
|
+ private IPointer? _firstContact;
|
|
|
+ private Point _firstPoint;
|
|
|
+ private IPointer? _secondContact;
|
|
|
+ private Point _secondPoint;
|
|
|
+ private Point _previousOrigin;
|
|
|
+
|
|
|
+ protected override void PointerCaptureLost(IPointer pointer)
|
|
|
+ {
|
|
|
+ RemoveContact(pointer);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void PointerMoved(PointerEventArgs e)
|
|
|
+ {
|
|
|
+ if (Target is Visual visual)
|
|
|
+ {
|
|
|
+ if (_firstContact == e.Pointer)
|
|
|
+ {
|
|
|
+ _firstPoint = e.GetPosition(visual);
|
|
|
+ }
|
|
|
+ else if (_secondContact == e.Pointer)
|
|
|
+ {
|
|
|
+ _secondPoint = e.GetPosition(visual);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_firstContact != null && _secondContact != null)
|
|
|
+ {
|
|
|
+ var distance = GetDistance(_firstPoint, _secondPoint);
|
|
|
+
|
|
|
+ var scale = distance / _initialDistance;
|
|
|
+
|
|
|
+ var origin = (_firstPoint + _secondPoint) / 2;
|
|
|
+
|
|
|
+ var pinchEventArgs = new PanAndZoomEventArgs(scale, origin, origin - _previousOrigin);
|
|
|
+ _previousOrigin = origin;
|
|
|
+
|
|
|
+ Target?.RaiseEvent(pinchEventArgs);
|
|
|
+ e.Handled = pinchEventArgs.Handled;
|
|
|
+ e.PreventGestureRecognition();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void PointerPressed(PointerPressedEventArgs e)
|
|
|
+ {
|
|
|
+ if (Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
|
|
|
+ {
|
|
|
+ if (_firstContact == null)
|
|
|
+ {
|
|
|
+ _firstContact = e.Pointer;
|
|
|
+ _firstPoint = e.GetPosition(visual);
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (_secondContact == null && _firstContact != e.Pointer)
|
|
|
+ {
|
|
|
+ _secondContact = e.Pointer;
|
|
|
+ _secondPoint = e.GetPosition(visual);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_firstContact != null && _secondContact != null)
|
|
|
+ {
|
|
|
+ _initialDistance = GetDistance(_firstPoint, _secondPoint);
|
|
|
+
|
|
|
+ _previousOrigin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
|
|
|
+
|
|
|
+ Capture(_firstContact);
|
|
|
+ Capture(_secondContact);
|
|
|
+ e.PreventGestureRecognition();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void PointerReleased(PointerReleasedEventArgs e)
|
|
|
+ {
|
|
|
+ if(RemoveContact(e.Pointer))
|
|
|
+ {
|
|
|
+ e.PreventGestureRecognition();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool RemoveContact(IPointer pointer)
|
|
|
+ {
|
|
|
+ if (_firstContact == pointer || _secondContact == pointer)
|
|
|
+ {
|
|
|
+ if (_secondContact == pointer)
|
|
|
+ {
|
|
|
+ _secondContact = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_firstContact == pointer)
|
|
|
+ {
|
|
|
+ _firstContact = _secondContact;
|
|
|
+
|
|
|
+ _secondContact = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ Target?.RaiseEvent(new PanAndZoomEndedEventArgs());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static float GetDistance(Point a, Point b)
|
|
|
+ {
|
|
|
+ var length = b - a;
|
|
|
+ return (float)new Vector(length.X, length.Y).Length;
|
|
|
+ }
|
|
|
+}
|