diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 8136f843df..243dafe817 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -215,6 +215,28 @@ namespace Avalonia return angle * 0.0174532925; } + /// + /// Appends another matrix as post-multiplication operation. + /// Equivalent to this * value; + /// + /// A matrix. + /// Post-multiplied matrix. + public Matrix Append(Matrix value) + { + return this * value; + } + + /// + /// Prpends another matrix as pre-multiplication operation. + /// Equivalent to value * this; + /// + /// A matrix. + /// Pre-multiplied matrix. + public Matrix Prepend(Matrix value) + { + return value * this; + } + /// /// Calculates the determinant for this matrix. /// diff --git a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs index 742bb9c804..76c2deef3b 100644 --- a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs +++ b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs @@ -18,11 +18,11 @@ namespace Avalonia.Media.Transformation public static Matrix ComposeTransform(Matrix.Decomposed decomposed) { // According to https://www.w3.org/TR/css-transforms-1/#recomposing-to-a-2d-matrix - - return Matrix.CreateTranslation(decomposed.Translate) * - Matrix.CreateRotation(decomposed.Angle) * - Matrix.CreateSkew(decomposed.Skew.X, decomposed.Skew.Y) * - Matrix.CreateScale(decomposed.Scale); + return Matrix.Identity + .Prepend(Matrix.CreateTranslation(decomposed.Translate)) + .Prepend(Matrix.CreateRotation(decomposed.Angle)) + .Prepend(Matrix.CreateSkew(decomposed.Skew.X, decomposed.Skew.Y)) + .Prepend(Matrix.CreateScale(decomposed.Scale)); } public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progress) diff --git a/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs b/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs index 36f5dd98f1..13a24cd523 100644 --- a/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs +++ b/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs @@ -86,6 +86,8 @@ namespace Avalonia.Media.Transformation if (fromIdentity && toIdentity) { + result.Matrix = Matrix.Identity; + return true; } @@ -179,7 +181,8 @@ namespace Avalonia.Media.Transformation } case OperationType.Identity: { - // Do nothing. + result.Matrix = Matrix.Identity; + break; } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs index 856b4615a5..e4f88706d8 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs @@ -129,7 +129,7 @@ namespace Avalonia.Visuals.UnitTests.Media Assert.Single(operations); Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type); - + var expectedMatrix = new Matrix(1, 2, 3, 4, 5, 6); Assert.Equal(expectedMatrix, operations[0].Matrix); @@ -195,7 +195,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Theory] [InlineData(0d, 10d)] [InlineData(0.5d, 15d)] - [InlineData(1d,20d)] + [InlineData(1d, 20d)] public void Can_Interpolate_Rotation(double progress, double angle) { var from = TransformOperations.Parse("rotate(10deg)"); @@ -225,5 +225,73 @@ namespace Avalonia.Visuals.UnitTests.Media Assert.Single(operations); Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type); } + + [Fact] + public void Order_Of_Operations_Is_Preserved_No_Prefix() + { + var from = TransformOperations.Parse("scale(1)"); + var to = TransformOperations.Parse("translate(50px,50px) scale(0.5,0.5)"); + + var interpolated_0 = TransformOperations.Interpolate(from, to, 0); + + Assert.True(interpolated_0.IsIdentity); + + var interpolated_50 = TransformOperations.Interpolate(from, to, 0.5); + + AssertMatrix(interpolated_50.Value, scaleX: 0.75, scaleY: 0.75, translateX: 12.5, translateY: 12.5); + + var interpolated_100 = TransformOperations.Interpolate(from, to, 1); + + AssertMatrix(interpolated_100.Value, scaleX: 0.5, scaleY: 0.5, translateX: 25, translateY: 25); + } + + [Fact] + public void Order_Of_Operations_Is_Preserved_One_Prefix() + { + var from = TransformOperations.Parse("scale(1)"); + var to = TransformOperations.Parse("scale(0.5,0.5) translate(50px,50px)"); + + var interpolated_0 = TransformOperations.Interpolate(from, to, 0); + + Assert.True(interpolated_0.IsIdentity); + + var interpolated_50 = TransformOperations.Interpolate(from, to, 0.5); + + AssertMatrix(interpolated_50.Value, scaleX: 0.75, scaleY: 0.75, translateX: 25.0, translateY: 25); + + var interpolated_100 = TransformOperations.Interpolate(from, to, 1); + + AssertMatrix(interpolated_100.Value, scaleX: 0.5, scaleY: 0.5, translateX: 50, translateY: 50); + } + + private static void AssertMatrix(Matrix matrix, double? angle = null, double? scaleX = null, double? scaleY = null, double? translateX = null, double? translateY = null) + { + Assert.True(Matrix.TryDecomposeTransform(matrix, out var composed)); + + if (angle.HasValue) + { + Assert.Equal(angle.Value, composed.Angle); + } + + if (scaleX.HasValue) + { + Assert.Equal(scaleX.Value, composed.Scale.X); + } + + if (scaleY.HasValue) + { + Assert.Equal(scaleY.Value, composed.Scale.Y); + } + + if (translateX.HasValue) + { + Assert.Equal(translateX.Value, composed.Translate.X); + } + + if (translateY.HasValue) + { + Assert.Equal(translateY.Value, composed.Translate.Y); + } + } } }