diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs index c276397a4db..9164384eae2 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -58,7 +58,18 @@ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) 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 37978b306e2..00d36d6cea9 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 d6288301872..6cb0a57cf2d 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -10,6 +10,7 @@ public class PinchGestureRecognizer : GestureRecognizer private IPointer? _secondContact; private Point _secondPoint; private Point _origin; + private double _previousAngle; protected override void PointerCaptureLost(IPointer pointer) { @@ -20,7 +21,7 @@ protected override void PointerMoved(PointerEventArgs e) { if (Target is Visual visual) { - if(_firstContact == e.Pointer) + if (_firstContact == e.Pointer) { _firstPoint = e.GetPosition(visual); } @@ -39,10 +40,13 @@ protected override void PointerMoved(PointerEventArgs e) 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 @@ protected override void PointerPressed(PointerPressedEventArgs e) _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 @@ private void RemoveContact(IPointer pointer) } 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 31c760eb51f..a1aaffe6dc2 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) { } }