From 2852c149e8f0147d30311b3977d782b52e54771e Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Thu, 23 Nov 2023 09:30:03 +0100 Subject: [PATCH] feat(PinchGestureRecognizer): Report Pinch angle in degrees (#13244) * feat(PinchGestureRecognizer): Report Pinch angle in degrees * fix: Address review * fix: Angle calculation * fix: Prevent Gesture Recognition from other Recognition * feat: Add Sample * fix: make GetDistance static * feat(PinchGestureRecognizer): AngleDelta --- samples/ControlCatalog/Pages/GesturePage.cs | 13 +- samples/ControlCatalog/Pages/GesturePage.xaml | 263 +++++++++++------- .../PinchGestureRecognizer.cs | 39 ++- src/Avalonia.Base/Input/PinchEventArgs.cs | 28 +- 4 files changed, 228 insertions(+), 115 deletions(-) diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs index c276397a4d..9164384eae 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -58,7 +58,18 @@ namespace ControlCatalog.Pages image.InvalidateMeasure(); } }; - + + + + if (this.Find("AngleSlider") is { } slider && + this.Find("RotationGesture") is { } rotationGesture + ) + { + rotationGesture.AddHandler(Gestures.PinchEvent, (s, e) => + { + slider.Value = e.Angle; + }); + } } private void SetPinchHandlers(Control? control) diff --git a/samples/ControlCatalog/Pages/GesturePage.xaml b/samples/ControlCatalog/Pages/GesturePage.xaml index 37978b306e..00d36d6cea 100644 --- a/samples/ControlCatalog/Pages/GesturePage.xaml +++ b/samples/ControlCatalog/Pages/GesturePage.xaml @@ -4,114 +4,167 @@ d:DesignHeight="800" d:DesignWidth="400" x:Class="ControlCatalog.Pages.GesturePage"> - - Pull Gexture (Touch / Pen) - Pull from colored rectangles - - - - - - - - - - - - - - - - - - - + + + + Pull Gexture (Touch / Pen) + + + + Pull from colored rectangles + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + Pinch/Zoom Gexture (Multi Touch) + + + + + + + + + - - + + + - Pinch/Zoom Gexture (Multi Touch) - - - - - - - - - - + + + Pinch/Rotation Gexture (Multi Touch) + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index d628830187..6cb0a57cf2 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -10,6 +10,7 @@ namespace Avalonia.Input private IPointer? _secondContact; private Point _secondPoint; private Point _origin; + private double _previousAngle; protected override void PointerCaptureLost(IPointer pointer) { @@ -20,7 +21,7 @@ namespace Avalonia.Input { if (Target is Visual visual) { - if(_firstContact == e.Pointer) + if (_firstContact == e.Pointer) { _firstPoint = e.GetPosition(visual); } @@ -39,10 +40,13 @@ namespace Avalonia.Input var scale = distance / _initialDistance; - var pinchEventArgs = new PinchEventArgs(scale, _origin); - Target?.RaiseEvent(pinchEventArgs); + var degree = GetAngleDegreeFromPoints(_firstPoint, _secondPoint); + var pinchEventArgs = new PinchEventArgs(scale, _origin, degree, _previousAngle - degree); + _previousAngle = degree; + Target?.RaiseEvent(pinchEventArgs); e.Handled = pinchEventArgs.Handled; + e.PreventGestureRecognition(); } } } @@ -74,18 +78,24 @@ namespace Avalonia.Input _origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f); + _previousAngle = GetAngleDegreeFromPoints(_firstPoint, _secondPoint); + Capture(_firstContact); Capture(_secondContact); + e.PreventGestureRecognition(); } } } protected override void PointerReleased(PointerReleasedEventArgs e) { - RemoveContact(e.Pointer); + if(RemoveContact(e.Pointer)) + { + e.PreventGestureRecognition(); + } } - private void RemoveContact(IPointer pointer) + private bool RemoveContact(IPointer pointer) { if (_firstContact == pointer || _secondContact == pointer) { @@ -102,13 +112,28 @@ namespace Avalonia.Input } Target?.RaiseEvent(new PinchEndedEventArgs()); + return true; } + return false; } - private float GetDistance(Point a, Point b) + private static float GetDistance(Point a, Point b) { - var length = _secondPoint - _firstPoint; + var length = b - a; return (float)new Vector(length.X, length.Y).Length; } + + private static double GetAngleDegreeFromPoints(Point a, Point b) + { + // https://stackoverflow.com/a/15994225/20894223 + + var deltaX = a.X - b.X; + var deltaY = -(a.Y - b.Y); // I reverse the sign, because on the screen the Y axes + // are reversed with respect to the Cartesian plane. + var rad = System.Math.Atan2(deltaX, deltaY); // radians from -π to +π + var degree = ((rad * (180 / System.Math.PI))) + 180; // Atan2 returns a radian value between -π to +π, in degrees -180 to +180. + // To get the angle between 0 and 360 degrees you need to add 180 degrees. + return degree; + } } } diff --git a/src/Avalonia.Base/Input/PinchEventArgs.cs b/src/Avalonia.Base/Input/PinchEventArgs.cs index 31c760eb51..a1aaffe6dc 100644 --- a/src/Avalonia.Base/Input/PinchEventArgs.cs +++ b/src/Avalonia.Base/Input/PinchEventArgs.cs @@ -4,20 +4,44 @@ namespace Avalonia.Input { public class PinchEventArgs : RoutedEventArgs { - public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent) + public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent) { Scale = scale; ScaleOrigin = scaleOrigin; } + public PinchEventArgs(double scale, Point scaleOrigin, double angle, double angleDelta) : base(Gestures.PinchEvent) + { + Scale = scale; + ScaleOrigin = scaleOrigin; + Angle = angle; + AngleDelta = angleDelta; + } + public double Scale { get; } = 1; public Point ScaleOrigin { get; } + + /// + /// Gets the angle of the pinch gesture, in degrees. + /// + /// + /// A pinch gesture is the movement of two pressed points closer together. This property is the measured angle of the line between those two points. Remember zero degrees is a line pointing up. + /// + public double Angle { get; } + + /// + /// Gets the difference from the previous and current pinch angle. + /// + /// + /// The AngleDelta value includes the sign of rotation. Positive for clockwise, negative counterclockwise. + /// + public double AngleDelta { get; } } public class PinchEndedEventArgs : RoutedEventArgs { - public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent) + public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent) { } }