diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs
index 10549b967d..4e5cd61994 100644
--- a/src/Avalonia.Visuals/Matrix.cs
+++ b/src/Avalonia.Visuals/Matrix.cs
@@ -150,6 +150,19 @@ namespace Avalonia
return new Matrix(cos, sin, -sin, cos, 0, 0);
}
+ ///
+ /// Creates a skew matrix from the given axis skew angles in radians.
+ ///
+ /// The amount of skew along the X-axis, in radians.
+ /// The amount of skew along the Y-axis, in radians.
+ /// A rotation matrix.
+ public static Matrix CreateSkew(double xAngle, double yAngle)
+ {
+ double tanX = Math.Tan(xAngle);
+ double tanY = Math.Tan(yAngle);
+ return new Matrix(1.0, tanY, tanX, 1.0, 0.0, 0.0);
+ }
+
///
/// Creates a scale matrix from the given X and Y components.
///
@@ -215,7 +228,6 @@ namespace Avalonia
return (_m11 * _m22) - (_m12 * _m21);
}
-
///
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
///
diff --git a/src/Avalonia.Visuals/Media/SkewTransform.cs b/src/Avalonia.Visuals/Media/SkewTransform.cs
new file mode 100644
index 0000000000..880b73750b
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/SkewTransform.cs
@@ -0,0 +1,69 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Skews an .
+ ///
+ public class SkewTransform : Transform
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty AngleXProperty =
+ AvaloniaProperty.Register(nameof(AngleX));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty AngleYProperty =
+ AvaloniaProperty.Register(nameof(AngleY));
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SkewTransform()
+ {
+ this.GetObservable(AngleXProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(AngleYProperty).Subscribe(_ => RaiseChanged());
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The skew angle of X-axis, in degrees.
+ /// The skew angle of Y-axis, in degrees.
+ public SkewTransform(double angleX, double angleY) : this()
+ {
+ AngleX = angleX;
+ AngleY = angleY;
+ }
+
+ ///
+ /// Gets or sets the AngleX property.
+ ///
+ public double AngleX
+ {
+ get { return GetValue(AngleXProperty); }
+ set { SetValue(AngleXProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the AngleY property.
+ ///
+ public double AngleY
+ {
+ get { return GetValue(AngleYProperty); }
+ set { SetValue(AngleYProperty, value); }
+ }
+
+ ///
+ /// Gets the tranform's .
+ ///
+ public override Matrix Value => Matrix.CreateSkew(Matrix.ToRadians(AngleX), Matrix.ToRadians(AngleY));
+ }
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
index b6ef550da6..d5f9818f89 100644
--- a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
@@ -30,6 +30,52 @@ namespace Avalonia.Controls.UnitTests
new Size(50, 25));
}
+ [Fact]
+ public void Measure_On_Skew_X_axis_45_degrees_Is_Correct()
+ {
+ TransformMeasureSizeTest(
+ new Size(100, 100),
+ new SkewTransform() { AngleX = 45 },
+ new Size(200, 100));
+
+ }
+
+ [Fact]
+ public void Measure_On_Skew_Y_axis_45_degrees_Is_Correct()
+ {
+ TransformMeasureSizeTest(
+ new Size(100, 100),
+ new SkewTransform() { AngleY = 45 },
+ new Size(100, 200));
+ }
+
+ [Fact]
+ public void Measure_On_Skew_X_axis_minus_45_degrees_Is_Correct()
+ {
+ TransformMeasureSizeTest(
+ new Size(100, 100),
+ new SkewTransform() { AngleX = -45 },
+ new Size(200, 100));
+ }
+
+ [Fact]
+ public void Measure_On_Skew_Y_axis_minus_45_degrees_Is_Correct()
+ {
+ TransformMeasureSizeTest(
+ new Size(100, 100),
+ new SkewTransform() { AngleY = -45 },
+ new Size(100, 200));
+ }
+
+ [Fact]
+ public void Measure_On_Skew_0_degrees_Is_Correct()
+ {
+ TransformMeasureSizeTest(
+ new Size(100, 100),
+ new SkewTransform() { AngleX = 0, AngleY = 0 },
+ new Size(100, 100));
+ }
+
[Fact]
public void Measure_On_Rotate_90_degrees_Is_Correct()
{
@@ -125,7 +171,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
- public void Should_Generate_RenderTransform_90_degrees()
+ public void Should_Generate_RotateTransform_90_degrees()
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
@@ -147,7 +193,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
- public void Should_Generate_RenderTransform_minus_90_degrees()
+ public void Should_Generate_RotateTransform_minus_90_degrees()
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
@@ -189,6 +235,50 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(m.M32, res.M32, 3);
}
+ [Fact]
+ public void Should_Generate_SkewTransform_45_degrees()
+ {
+ LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
+ 100,
+ 100,
+ new SkewTransform() { AngleX = 45, AngleY = 45 });
+
+ Assert.NotNull(lt.TransformRoot.RenderTransform);
+
+ Matrix m = lt.TransformRoot.RenderTransform.Value;
+
+ Matrix res = Matrix.CreateSkew(Matrix.ToRadians(45), Matrix.ToRadians(45));
+
+ Assert.Equal(m.M11, res.M11, 3);
+ Assert.Equal(m.M12, res.M12, 3);
+ Assert.Equal(m.M21, res.M21, 3);
+ Assert.Equal(m.M22, res.M22, 3);
+ Assert.Equal(m.M31, res.M31, 3);
+ Assert.Equal(m.M32, res.M32, 3);
+ }
+
+ [Fact]
+ public void Should_Generate_SkewTransform_minus_45_degrees()
+ {
+ LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
+ 100,
+ 100,
+ new SkewTransform() { AngleX = -45, AngleY = -45 });
+
+ Assert.NotNull(lt.TransformRoot.RenderTransform);
+
+ Matrix m = lt.TransformRoot.RenderTransform.Value;
+
+ Matrix res = Matrix.CreateSkew(Matrix.ToRadians(-45), Matrix.ToRadians(-45));
+
+ Assert.Equal(m.M11, res.M11, 3);
+ Assert.Equal(m.M12, res.M12, 3);
+ Assert.Equal(m.M21, res.M21, 3);
+ Assert.Equal(m.M22, res.M22, 3);
+ Assert.Equal(m.M31, res.M31, 3);
+ Assert.Equal(m.M32, res.M32, 3);
+ }
+
private static void TransformMeasureSizeTest(Size size, Transform transform, Size expectedSize)
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(