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(