Browse Source

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
pull/13708/head
workgroupengineering 2 years ago
committed by GitHub
parent
commit
2852c149e8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      samples/ControlCatalog/Pages/GesturePage.cs
  2. 263
      samples/ControlCatalog/Pages/GesturePage.xaml
  3. 39
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  4. 28
      src/Avalonia.Base/Input/PinchEventArgs.cs

13
samples/ControlCatalog/Pages/GesturePage.cs

@ -58,7 +58,18 @@ namespace ControlCatalog.Pages
image.InvalidateMeasure();
}
};
if (this.Find<Slider>("AngleSlider") is { } slider &&
this.Find<Panel>("RotationGesture") is { } rotationGesture
)
{
rotationGesture.AddHandler(Gestures.PinchEvent, (s, e) =>
{
slider.Value = e.Angle;
});
}
}
private void SetPinchHandlers(Control? control)

263
samples/ControlCatalog/Pages/GesturePage.xaml

@ -4,114 +4,167 @@
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.GesturePage">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pull Gexture (Touch / Pen)</TextBlock>
<TextBlock Margin="5">Pull from colored rectangles</TextBlock>
<Border>
<DockPanel HorizontalAlignment="Stretch"
ClipToBounds="True"
Margin="5"
Height="200">
<Border DockPanel.Dock="Top"
Margin="2"
Name="TopPullZone"
Background="Transparent"
BorderBrush="Red"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="TopToBottom"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Name="TopBall"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Bottom"
BorderBrush="Green"
Margin="2"
Background="Transparent"
Name="BottomPullZone"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="BottomToTop"/>
</Border.GestureRecognizers>
<Border Width="10"
Name="BottomBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="10"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Right"
Margin="2"
Background="Transparent"
Name="RightPullZone"
BorderBrush="Blue"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="RightToLeft"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="RightBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
<TabControl>
<TabItem>
<TabItem.Header>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pull Gexture (Touch / Pen)</TextBlock>
</TabItem.Header>
<StackPanel>
<TextBlock Margin="5">Pull from colored rectangles</TextBlock>
<Border>
<DockPanel HorizontalAlignment="Stretch"
ClipToBounds="True"
Margin="5"
Height="200">
<Border DockPanel.Dock="Top"
Margin="2"
Name="TopPullZone"
Background="Transparent"
BorderBrush="Red"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="TopToBottom"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Name="TopBall"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Bottom"
BorderBrush="Green"
Margin="2"
Background="Transparent"
Name="BottomPullZone"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="BottomToTop"/>
</Border.GestureRecognizers>
<Border Width="10"
Name="BottomBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="10"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Right"
Margin="2"
Background="Transparent"
Name="RightPullZone"
BorderBrush="Blue"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="RightToLeft"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="RightBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Left"
Margin="2"
Background="Transparent"
Name="LeftPullZone"
BorderBrush="Orange"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="LeftToRight"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="LeftBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
</DockPanel>
</Border>
<Border DockPanel.Dock="Left"
Margin="2"
Background="Transparent"
Name="LeftPullZone"
BorderBrush="Orange"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="LeftToRight"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="LeftBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Zoom Gexture (Multi Touch)</TextBlock>
</TabItem.Header>
<StackPanel>
<Border ClipToBounds="True">
<Image Stretch="UniformToFill"
Margin="5"
Name="PinchImage"
Source="/Assets/delicate-arch-896885_640.jpg">
<Image.GestureRecognizers>
<PinchGestureRecognizer/>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True"/>
</Image.GestureRecognizers>
</Image>
</Border>
</DockPanel>
</Border>
<Button HorizontalAlignment="Center" Name="ResetButton">Reset</Button>
</StackPanel>
</TabItem>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Zoom Gexture (Multi Touch)</TextBlock>
<Border ClipToBounds="True">
<Image Stretch="UniformToFill"
Margin="5"
Name="PinchImage"
Source="/Assets/delicate-arch-896885_640.jpg">
<Image.GestureRecognizers>
<PinchGestureRecognizer/>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True"/>
</Image.GestureRecognizers>
</Image>
</Border>
<Button HorizontalAlignment="Center" Name="ResetButton">Reset</Button>
</StackPanel>
<TabItem>
<TabItem.Header>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Rotation Gexture (Multi Touch)</TextBlock>
</TabItem.Header>
<DockPanel>
<Slider Minimum="0"
Maximum="360"
DockPanel.Dock="Bottom"
x:Name="AngleSlider"
/>
<Panel x:Name="RotationGesture">
<Panel.GestureRecognizers>
<PinchGestureRecognizer/>
</Panel.GestureRecognizers>
<Border BorderThickness="1.5" BorderBrush="LawnGreen"/>
<Panel HorizontalAlignment="Center"
Width="100"
Height="100">
<Panel.RenderTransform>
<RotateTransform Angle="{Binding #AngleSlider.Value}"/>
</Panel.RenderTransform>
<Rectangle Fill="SkyBlue"/>
<Rectangle HorizontalAlignment="Center"
VerticalAlignment="Top"
Fill="Yellow"
Width="5"
Height="35"/>
</Panel>
<TextBlock Text="{Binding #AngleSlider.Value, StringFormat=0°}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="DemiBold"
FontSize="20"
/>
</Panel>
</DockPanel>
</TabItem>
</TabControl>
</UserControl>

39
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;
}
}
}

28
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; }
/// <summary>
/// Gets the angle of the pinch gesture, in degrees.
/// <summary>
/// <remarks>
/// 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.
/// </remarks>
public double Angle { get; }
/// <summary>
/// Gets the difference from the previous and current pinch angle.
/// </summary>
/// <remarks>
/// The AngleDelta value includes the sign of rotation. Positive for clockwise, negative counterclockwise.
/// </remarks>
public double AngleDelta { get; }
}
public class PinchEndedEventArgs : RoutedEventArgs
{
public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
{
}
}

Loading…
Cancel
Save