From f41a6340f908e189204104385cd73e6e614da125 Mon Sep 17 00:00:00 2001 From: Andreas Schauerte Date: Mon, 5 Jul 2021 19:11:36 +0200 Subject: [PATCH 01/31] Cherry Pick and merge. --- src/Avalonia.Visuals/Matrix.cs | 192 +++++++++++++++++++++++++-------- 1 file changed, 145 insertions(+), 47 deletions(-) diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index b08a0eb98a..9dd5bced86 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -1,12 +1,21 @@ using System; using System.Globalization; +using System.Linq; using Avalonia.Utilities; namespace Avalonia { /// - /// A 2x3 matrix. + /// A 3x3 matrix. /// + /// Matrix layout: + /// | 1st col | 2nd col | 3r col | + /// 1st row | scaleX | skrewY | persX | + /// 2nd row | skrewX | scaleY | persY | + /// 3rd row | transX | transY | persZ | + /// + /// Note: Skia.SkMatrix uses a transposed layout (where for example skrewX/skrewY and persX/transX are swapped). + /// #if !BUILDTASK public #endif @@ -14,40 +23,77 @@ namespace Avalonia { private readonly double _m11; private readonly double _m12; + private readonly double _m13; private readonly double _m21; private readonly double _m22; + private readonly double _m23; private readonly double _m31; private readonly double _m32; + private readonly double _m33; + + + /// + /// Initializes a new instance of the struct (equivalent to a 2x3 Matrix without perspective). + /// + /// The first element of the first row. + /// The second element of the first row. + /// The first element of the second row. + /// The second element of the second row. + /// The first element of the third row. + /// The second element of the third row. + public Matrix( + double scaleX, + double skrewY, + double skrewX, + double scaleY, + double offsetX, + double offsetY) : this( scaleX, skrewY, 0, skrewX, scaleY, 0, offsetX, offsetY, 1) + { + } + + /// /// Initializes a new instance of the struct. /// - /// The first element of the first row. - /// The second element of the first row. - /// The first element of the second row. - /// The second element of the second row. + /// The first element of the first row. + /// The second element of the first row. + /// The third element of the first row. + /// The first element of the second row. + /// The second element of the second row. + /// The third element of the second row. /// The first element of the third row. /// The second element of the third row. + /// The third element of the third row. public Matrix( - double m11, - double m12, - double m21, - double m22, + double scaleX, + double skrewY, + double persX, + double skrewX, + double scaleY, + double persY, double offsetX, - double offsetY) + double offsetY, + double persZ) { - _m11 = m11; - _m12 = m12; - _m21 = m21; - _m22 = m22; + _m11 = scaleX; + _m12 = skrewY; + _m13 = persX; + _m21 = skrewX; + _m22 = scaleY; + _m23 = persY; _m31 = offsetX; _m32 = offsetY; + _m33 = persZ; } /// /// Returns the multiplicative identity matrix. /// - public static Matrix Identity { get; } = new Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); + public static Matrix Identity { get; } = new Matrix( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0); /// /// Returns whether the matrix is the identity matrix. @@ -60,35 +106,50 @@ namespace Avalonia public bool HasInverse => !MathUtilities.IsZero(GetDeterminant()); /// - /// The first element of the first row + /// The first element of the first row (scaleX). /// public double M11 => _m11; /// - /// The second element of the first row + /// The second element of the first row (skrewY). /// public double M12 => _m12; /// - /// The first element of the second row + /// The third element of the first row (persX: input x-axis perspective factor). + /// + public double M13 => _m13; + + /// + /// The first element of the second row (skrewX). /// public double M21 => _m21; /// - /// The second element of the second row + /// The second element of the second row (scaleY). /// public double M22 => _m22; /// - /// The first element of the third row + /// The third element of the second row (persY: input y-axis perspective factor). + /// + public double M23 => _m23; + + /// + /// The first element of the third row (offsetX/translateX). /// public double M31 => _m31; /// - /// The second element of the third row + /// The second element of the third row (offsetY/translateY). /// public double M32 => _m32; + /// + /// The third element of the third row (persZ: perspective scale factor). + /// + public double M33 => _m33; + /// /// Multiplies two matrices together and returns the resulting matrix. /// @@ -103,7 +164,7 @@ namespace Avalonia (value1.M21 * value2.M11) + (value1.M22 * value2.M21), (value1.M21 * value2.M12) + (value1.M22 * value2.M22), (value1._m31 * value2.M11) + (value1._m32 * value2.M21) + value2._m31, - (value1._m31 * value2.M12) + (value1._m32 * value2.M22) + value2._m32); + (value1._m31 * value2.M12) + (value1._m32 * value2.M22) + value2._m32); //TODO: include perspective } /// @@ -171,7 +232,7 @@ namespace Avalonia /// A scaling matrix. public static Matrix CreateScale(double xScale, double yScale) { - return CreateScale(new Vector(xScale, yScale)); + return new Matrix(xScale, 0, 0, yScale, 0, 0); } /// @@ -181,7 +242,7 @@ namespace Avalonia /// A scaling matrix. public static Matrix CreateScale(Vector scales) { - return new Matrix(scales.X, 0, 0, scales.Y, 0, 0); + return CreateScale(scales.X, scales.Y); } /// @@ -247,7 +308,12 @@ namespace Avalonia /// public double GetDeterminant() { - return (_m11 * _m22) - (_m12 * _m21); + //return (_m11 * _m22) - (_m12 * _m21); //TODO: ensure new implementation yields the same result as before, when pers is 0,0,1 + + // implemented using "Laplace expansion": + return _m11 * (_m22 * _m33 - _m23 * _m32) + - _m12 * (_m21 * _m33 - _m23 * _m31) + + _m13 * (_m21 * _m32 - _m22 * _m31); } /// @@ -260,10 +326,13 @@ namespace Avalonia // ReSharper disable CompareOfFloatsByEqualityOperator return _m11 == other.M11 && _m12 == other.M12 && + _m13 == other.M13 && _m21 == other.M21 && _m22 == other.M22 && + _m23 == other.M23 && _m31 == other.M31 && - _m32 == other.M32; + _m32 == other.M32 && + _m33 == other.M33; // ReSharper restore CompareOfFloatsByEqualityOperator } @@ -280,9 +349,18 @@ namespace Avalonia /// The hash code. public override int GetHashCode() { - return M11.GetHashCode() + M12.GetHashCode() + - M21.GetHashCode() + M22.GetHashCode() + - M31.GetHashCode() + M32.GetHashCode(); + return (_m11, _m12, _m13, _m21, _m22, _m23, _m31, _m32, _m33).GetHashCode(); + } + + /// + /// Determines if the current matrix contains perspective (non-affine) transforms (true) or only (affine) transforms that could be mapped into an 2x3 matrix (false). + /// + private bool ContainsPerspective() + { + + // ReSharper disable CompareOfFloatsByEqualityOperator + return _m13 != 0 || _m23 != 0 || _m33 != 1; + // ReSharper restore CompareOfFloatsByEqualityOperator } /// @@ -292,15 +370,25 @@ namespace Avalonia public override string ToString() { CultureInfo ci = CultureInfo.CurrentCulture; + + string msg; + double[] values; + + if (ContainsPerspective()) + { + msg = "{{ {{M11:{0} M12:{1} M13:{2}}} {{M21:{3} M22:{4} M23:{5}}} {{M31:{6} M32:{7} M33:{8}}} }}"; + values = new[] { M11, M12, M13, M21, M22, M23, M31, M32, M33 }; + } + else + { + msg = "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}"; + values = new[] { M11, M12, M21, M22, M31, M32 }; + } + return string.Format( ci, - "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}", - M11.ToString(ci), - M12.ToString(ci), - M21.ToString(ci), - M22.ToString(ci), - M31.ToString(ci), - M32.ToString(ci)); + msg, + values.Select((v) => v.ToString(ci)).ToArray()); } /// @@ -311,7 +399,7 @@ namespace Avalonia { double d = GetDeterminant(); - if (MathUtilities.IsZero(d)) + if (MathUtilities.IsZero(d)) //TODO: decide if special handling is required for perspective { inverted = default; @@ -347,20 +435,30 @@ namespace Avalonia /// /// Parses a string. /// - /// Six comma-delimited double values (m11, m12, m21, m22, offsetX, offsetY) that describe the new + /// Six or nine comma-delimited double values (m11, m12, m21, m22, offsetX, offsetY[, persX, persY, persZ]) that describe the new /// The . public static Matrix Parse(string s) { + // initialize to satisfy compiler - only used when retrieved from string. + double v8 = 0; + double v9 = 0; + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix.")) { - return new Matrix( - tokenizer.ReadDouble(), - tokenizer.ReadDouble(), - tokenizer.ReadDouble(), - tokenizer.ReadDouble(), - tokenizer.ReadDouble(), - tokenizer.ReadDouble() - ); + var v1 = tokenizer.ReadDouble(); + var v2 = tokenizer.ReadDouble(); + var v3 = tokenizer.ReadDouble(); + var v4 = tokenizer.ReadDouble(); + var v5 = tokenizer.ReadDouble(); + var v6 = tokenizer.ReadDouble(); + var pers = tokenizer.TryReadDouble(out var v7); + pers = pers && tokenizer.TryReadDouble(out v8); + pers = pers && tokenizer.TryReadDouble(out v9); + + if (pers) + return new Matrix(v1, v2, v7, v3, v4, v8, v5, v6, v9); + else + return new Matrix(v1, v2, v3, v4, v5, v6); } } @@ -376,7 +474,7 @@ namespace Avalonia var determinant = matrix.GetDeterminant(); - if (MathUtilities.IsZero(determinant)) + if (MathUtilities.IsZero(determinant) || matrix.ContainsPerspective()) { return false; } From c40410cdb371f37071fb5df91367c60f057ea317 Mon Sep 17 00:00:00 2001 From: Andreas Schauerte Date: Mon, 5 Jul 2021 18:29:12 +0200 Subject: [PATCH 02/31] Add basic 3d transformation. --- .../Resources/Resource.Designer.cs | 2 +- samples/RenderDemo/MainWindow.xaml | 3 + .../RenderDemo/Pages/Transform3DPage.axaml | 81 ++++++++ .../RenderDemo/Pages/Transform3DPage.axaml.cs | 21 ++ .../ViewModels/Transform3DPageViewModel.cs | 61 ++++++ .../Resources/Resource.Designer.cs | 2 +- .../Animation/Animators/TransformAnimator.cs | 1 + src/Avalonia.Visuals/Matrix.cs | 50 +++-- src/Avalonia.Visuals/Media/Transform3D.cs | 192 ++++++++++++++++++ src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs | 6 +- 10 files changed, 393 insertions(+), 26 deletions(-) create mode 100644 samples/RenderDemo/Pages/Transform3DPage.axaml create mode 100644 samples/RenderDemo/Pages/Transform3DPage.axaml.cs create mode 100644 samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs create mode 100644 src/Avalonia.Visuals/Media/Transform3D.cs diff --git a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs index dccc3f7159..b1ca548e2c 100644 --- a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs +++ b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace ControlCatalog.Android { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] public partial class Resource { diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index a4c6299278..2b5bba928a 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -63,5 +63,8 @@ + + + diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml new file mode 100644 index 0000000000..ebd838eb67 --- /dev/null +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + X-Rotation: + + Y-Rotation: + + Z-Rotation: + + X: + + Y: + + Z: + + + + + + diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml.cs b/samples/RenderDemo/Pages/Transform3DPage.axaml.cs new file mode 100644 index 0000000000..5083189c4c --- /dev/null +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml.cs @@ -0,0 +1,21 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using RenderDemo.ViewModels; + +namespace RenderDemo.Pages; + +public class Transform3DPage : UserControl +{ + public Transform3DPage() + { + InitializeComponent(); + this.DataContext = new Transform3DPageViewModel(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} + diff --git a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs new file mode 100644 index 0000000000..7e7849bd01 --- /dev/null +++ b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs @@ -0,0 +1,61 @@ +using System; +using MiniMvvm; +using Avalonia.Animation; + +namespace RenderDemo.ViewModels +{ + public class Transform3DPageViewModel : ViewModelBase + { + private double _roationX = 0; + private double _rotationY = 0; + private double _rotationZ = 0; + + private double _x = 0; + private double _y = 0; + private double _z = 0; + + private double _depth = 200; + + public double RoationX + { + get => _roationX; + set => RaiseAndSetIfChanged(ref _roationX, value); + } + + public double RotationY + { + get => _rotationY; + set => RaiseAndSetIfChanged(ref _rotationY, value); + } + + public double RotationZ + { + get => _rotationZ; + set => RaiseAndSetIfChanged(ref _rotationZ, value); + } + + public double Depth + { + get => _depth; + set => RaiseAndSetIfChanged(ref _depth, value); + } + + public double X + { + get => _x; + set => RaiseAndSetIfChanged(ref _x, value); + } + + public double Y + { + get => _y; + set => RaiseAndSetIfChanged(ref _y, value); + } + + public double Z + { + get => _z; + set => RaiseAndSetIfChanged(ref _z, value); + } + } +} diff --git a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs index 87fd47df25..83db67fcee 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs +++ b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace Avalonia.AndroidTestApplication { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] public partial class Resource { diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 34ec8ac503..a98f2ec65e 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -43,6 +43,7 @@ namespace Avalonia.Animation.Animators normalTransform.Children.Add(new SkewTransform()); normalTransform.Children.Add(new RotateTransform()); normalTransform.Children.Add(new TranslateTransform()); + normalTransform.Children.Add(new Transform3D()); ctrl.RenderTransform = normalTransform; } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 9dd5bced86..e949d728d0 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -14,7 +14,7 @@ namespace Avalonia /// 2nd row | skrewX | scaleY | persY | /// 3rd row | transX | transY | persZ | /// - /// Note: Skia.SkMatrix uses a transposed layout (where for example skrewX/skrewY and persX/transX are swapped). + /// Note: Skia.SkMatrix uses a transposed layout (where for example skrewX/skrewY and perspp0/tranX are swapped). /// #if !BUILDTASK public @@ -30,8 +30,7 @@ namespace Avalonia private readonly double _m31; private readonly double _m32; private readonly double _m33; - - + /// /// Initializes a new instance of the struct (equivalent to a 2x3 Matrix without perspective). /// @@ -159,12 +158,15 @@ namespace Avalonia public static Matrix operator *(Matrix value1, Matrix value2) { return new Matrix( - (value1.M11 * value2.M11) + (value1.M12 * value2.M21), - (value1.M11 * value2.M12) + (value1.M12 * value2.M22), - (value1.M21 * value2.M11) + (value1.M22 * value2.M21), - (value1.M21 * value2.M12) + (value1.M22 * value2.M22), - (value1._m31 * value2.M11) + (value1._m32 * value2.M21) + value2._m31, - (value1._m31 * value2.M12) + (value1._m32 * value2.M22) + value2._m32); //TODO: include perspective + (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31), + (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32), + (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33), + (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31), + (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32), + (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33), + (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31), + (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32), + (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33)); } /// @@ -288,7 +290,7 @@ namespace Avalonia } /// - /// Prpends another matrix as pre-multiplication operation. + /// Prepends another matrix as pre-multiplication operation. /// Equivalent to value * this; /// /// A matrix. @@ -359,7 +361,7 @@ namespace Avalonia { // ReSharper disable CompareOfFloatsByEqualityOperator - return _m13 != 0 || _m23 != 0 || _m33 != 1; + return _m31 != 0 || _m32 != 0 || _m33 != 1; // ReSharper restore CompareOfFloatsByEqualityOperator } @@ -399,21 +401,27 @@ namespace Avalonia { double d = GetDeterminant(); - if (MathUtilities.IsZero(d)) //TODO: decide if special handling is required for perspective + if (MathUtilities.IsZero(d)) { inverted = default; return false; } + var invdet = 1 / d; + inverted = new Matrix( - _m22 / d, - -_m12 / d, - -_m21 / d, - _m11 / d, - ((_m21 * _m32) - (_m22 * _m31)) / d, - ((_m12 * _m31) - (_m11 * _m32)) / d); - + (_m22 * _m33 - _m32 * _m23) * invdet, + (_m13 * _m31 - _m12 * _m33) * invdet, + (_m12 * _m23 - _m13 * _m22) * invdet, + (_m23 * _m31 - _m21 * _m33) * invdet, + (_m11 * _m33 - _m13 * _m31) * invdet, + (_m21 * _m13 - _m11 * _m23) * invdet, + (_m21 * _m32 - _m31 * _m22) * invdet, + (_m21 * _m12 - _m11 * _m32) * invdet, + (_m11 * _m22 - _m21 * _m12) * invdet + ); + return true; } @@ -424,7 +432,7 @@ namespace Avalonia /// The inverted matrix. public Matrix Invert() { - if (!TryInvert(out Matrix inverted)) + if (!TryInvert(out var inverted)) { throw new InvalidOperationException("Transform is not invertible."); } @@ -467,7 +475,7 @@ namespace Avalonia /// /// Matrix to decompose. /// Decomposed matrix. - /// The status of the operation. + /// The status of the operation. public static bool TryDecomposeTransform(Matrix matrix, out Decomposed decomposed) { decomposed = default; diff --git a/src/Avalonia.Visuals/Media/Transform3D.cs b/src/Avalonia.Visuals/Media/Transform3D.cs new file mode 100644 index 0000000000..e063f76556 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Transform3D.cs @@ -0,0 +1,192 @@ +using System; +using System.Numerics; + +namespace Avalonia.Media; + +public class Transform3D : Transform +{ + /// + /// Defines the property. + /// + public static readonly StyledProperty s_rotationXProperty = + AvaloniaProperty.Register(nameof(RotationX)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_rotationYProperty = + AvaloniaProperty.Register(nameof(RotationY)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_rotationZProperty = + AvaloniaProperty.Register(nameof(RotationZ)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_depthProperty = + AvaloniaProperty.Register(nameof(Depth)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_centerXProperty = + AvaloniaProperty.Register(nameof(CenterX)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_centerYProperty = + AvaloniaProperty.Register(nameof(CenterY)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_xProperty = + AvaloniaProperty.Register(nameof(X)); + + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_yProperty = + AvaloniaProperty.Register(nameof(Y)); + + + /// + /// Defines the property. + /// + public static readonly StyledProperty s_zProperty = + AvaloniaProperty.Register(nameof(Z)); + + + /// + /// Initializes a new instance of the class. + /// + public Transform3D() + { + this.GetObservable(s_rotationXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_rotationYProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_rotationZProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_depthProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_centerXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_centerYProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_xProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_yProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(s_zProperty).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 Transform3D( + double rotationX, + double rotationY, + double rotationZ, + double depth, + double centerX, + double centerY) : this() + { + RotationX = rotationX; + RotationY = rotationY; + RotationZ = rotationZ; + Depth = depth; + CenterX = centerX; + CenterY = centerY; + } + + /// + /// Gets or sets the X property. + /// + public double RotationX + { + get => GetValue(s_rotationXProperty); + set => SetValue(s_rotationXProperty, value); + } + + /// + /// Gets or sets the Y property. + /// + public double RotationY + { + get => GetValue(s_rotationYProperty); + set => SetValue(s_rotationYProperty, value); + } + + public double RotationZ + { + get => GetValue(s_rotationZProperty); + set => SetValue(s_rotationZProperty, value); + } + + public double Depth + { + get => GetValue(s_depthProperty); + set => SetValue(s_depthProperty, value); + } + + public double CenterX + { + get => GetValue(s_centerXProperty); + set => SetValue(s_centerXProperty, value); + } + + public double CenterY + { + get => GetValue(s_centerYProperty); + set => SetValue(s_centerYProperty, value); + } + + public double X + { + get => GetValue(s_xProperty); + set => SetValue(s_xProperty, value); + } + + public double Y + { + get => GetValue(s_yProperty); + set => SetValue(s_yProperty, value); + } + + public double Z + { + get => GetValue(s_zProperty); + set => SetValue(s_zProperty, value); + } + + /// + /// Gets the transform's . + /// + public override Matrix Value + { + get + { + var matrix44 = Matrix4x4.Identity; + + matrix44 *= Matrix4x4.CreateTranslation((float)X, (float)Y, (float)Z); + + matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(RotationX)); + matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(RotationY)); + matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(RotationZ)); + + var matrix = new Matrix( + matrix44.M11, + matrix44.M12, + matrix44.M14, + matrix44.M21, + matrix44.M22, + matrix44.M24, + matrix44.M41, + matrix44.M42, + matrix44.M44); + + return matrix; + } + } +} diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs index 75b4231640..c0afc8bd9c 100644 --- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs @@ -103,9 +103,9 @@ namespace Avalonia.Skia SkewY = (float)m.M12, ScaleY = (float)m.M22, TransY = (float)m.M32, - Persp0 = 0, - Persp1 = 0, - Persp2 = 1 + Persp0 = (float)m.M13, + Persp1 = (float)m.M23, + Persp2 = (float)m.M33 }; return sm; From 0a831b978483699b1dfa75ca177307aebef98e6e Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Wed, 19 Jan 2022 11:16:33 +0100 Subject: [PATCH 03/31] Refactor names. --- .../RenderDemo/Pages/Transform3DPage.axaml | 7 +- .../ViewModels/Transform3DPageViewModel.cs | 8 +- src/Avalonia.Visuals/Media/Transform3D.cs | 135 ++++++++---------- 3 files changed, 64 insertions(+), 86 deletions(-) diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index ebd838eb67..e66c498bbc 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -54,12 +54,11 @@ + CenterX="{Binding X}" + CenterY="{Binding Y}" + CenterZ="{Binding Z}" /> X-Rotation: diff --git a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs index 7e7849bd01..d28705fc0d 100644 --- a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs @@ -6,7 +6,7 @@ namespace RenderDemo.ViewModels { public class Transform3DPageViewModel : ViewModelBase { - private double _roationX = 0; + private double _rotationX = 0; private double _rotationY = 0; private double _rotationZ = 0; @@ -16,10 +16,10 @@ namespace RenderDemo.ViewModels private double _depth = 200; - public double RoationX + public double RotationX { - get => _roationX; - set => RaiseAndSetIfChanged(ref _roationX, value); + get => _rotationX; + set => RaiseAndSetIfChanged(ref _rotationX, value); } public double RotationY diff --git a/src/Avalonia.Visuals/Media/Transform3D.cs b/src/Avalonia.Visuals/Media/Transform3D.cs index e063f76556..cfbde3204a 100644 --- a/src/Avalonia.Visuals/Media/Transform3D.cs +++ b/src/Avalonia.Visuals/Media/Transform3D.cs @@ -8,58 +8,41 @@ public class Transform3D : Transform /// /// Defines the property. /// - public static readonly StyledProperty s_rotationXProperty = + public static readonly StyledProperty RotationXProperty = AvaloniaProperty.Register(nameof(RotationX)); /// /// Defines the property. /// - public static readonly StyledProperty s_rotationYProperty = + public static readonly StyledProperty RotationYProperty = AvaloniaProperty.Register(nameof(RotationY)); /// /// Defines the property. /// - public static readonly StyledProperty s_rotationZProperty = + public static readonly StyledProperty RotationZProperty = AvaloniaProperty.Register(nameof(RotationZ)); - /// - /// Defines the property. - /// - public static readonly StyledProperty s_depthProperty = - AvaloniaProperty.Register(nameof(Depth)); /// /// Defines the property. /// - public static readonly StyledProperty s_centerXProperty = + public static readonly StyledProperty CenterXProperty = AvaloniaProperty.Register(nameof(CenterX)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty s_centerYProperty = - AvaloniaProperty.Register(nameof(CenterY)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty s_xProperty = - AvaloniaProperty.Register(nameof(X)); /// /// Defines the property. /// - public static readonly StyledProperty s_yProperty = - AvaloniaProperty.Register(nameof(Y)); + public static readonly StyledProperty CenterYProperty = + AvaloniaProperty.Register(nameof(CenterY)); /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty s_zProperty = - AvaloniaProperty.Register(nameof(Z)); + public static readonly StyledProperty CenterZProperty = + AvaloniaProperty.Register(nameof(CenterZ)); /// @@ -67,97 +50,93 @@ public class Transform3D : Transform /// public Transform3D() { - this.GetObservable(s_rotationXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_rotationYProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_rotationZProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_depthProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_centerXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_centerYProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_xProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_yProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(s_zProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(RotationXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(RotationYProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(RotationZProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterYProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterYProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterZProperty).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. - /// + /// The rotation around the X-Axis + /// The rotation around the Y-Axis + /// The rotation around the Z-Axis + /// The origin of the X-Axis + /// The origin of the Y-Axis + /// The origin of the Z-Axis public Transform3D( double rotationX, double rotationY, double rotationZ, - double depth, - double centerX, - double centerY) : this() + double originCenterX, + double originCenterY, + double originCenterZ) : this() { RotationX = rotationX; RotationY = rotationY; RotationZ = rotationZ; - Depth = depth; - CenterX = centerX; - CenterY = centerY; + CenterX = originCenterX; + CenterY = originCenterY; + CenterZ = originCenterZ; } /// - /// Gets or sets the X property. + /// Sets the rotation around the X-Axis /// public double RotationX { - get => GetValue(s_rotationXProperty); - set => SetValue(s_rotationXProperty, value); + get => GetValue(RotationXProperty); + set => SetValue(RotationXProperty, value); } /// - /// Gets or sets the Y property. + /// Sets the rotation around the Y-Axis /// public double RotationY { - get => GetValue(s_rotationYProperty); - set => SetValue(s_rotationYProperty, value); + get => GetValue(RotationYProperty); + set => SetValue(RotationYProperty, value); } + /// + /// Sets the rotation around the Z-Axis + /// public double RotationZ { - get => GetValue(s_rotationZProperty); - set => SetValue(s_rotationZProperty, value); - } - - public double Depth - { - get => GetValue(s_depthProperty); - set => SetValue(s_depthProperty, value); + get => GetValue(RotationZProperty); + set => SetValue(RotationZProperty, value); } + /// + /// Moves the origin of the X-Axis + /// public double CenterX { - get => GetValue(s_centerXProperty); - set => SetValue(s_centerXProperty, value); + get => GetValue(CenterXProperty); + set => SetValue(CenterXProperty, value); } + /// + /// Moves the origin of the Y-Axis + /// public double CenterY { - get => GetValue(s_centerYProperty); - set => SetValue(s_centerYProperty, value); + get => GetValue(CenterYProperty); + set => SetValue(CenterYProperty, value); } - public double X - { - get => GetValue(s_xProperty); - set => SetValue(s_xProperty, value); - } - - public double Y - { - get => GetValue(s_yProperty); - set => SetValue(s_yProperty, value); - } - - public double Z + /// + /// Moves the origin of the Z-Axis + /// + public double CenterZ { - get => GetValue(s_zProperty); - set => SetValue(s_zProperty, value); + get => GetValue(CenterZProperty); + set => SetValue(CenterZProperty, value); } /// @@ -169,7 +148,7 @@ public class Transform3D : Transform { var matrix44 = Matrix4x4.Identity; - matrix44 *= Matrix4x4.CreateTranslation((float)X, (float)Y, (float)Z); + matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ); matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(RotationX)); matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(RotationY)); From 9a01f13114f77a889d68af2e06fbfebd0aa87882 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Wed, 19 Jan 2022 13:39:57 +0100 Subject: [PATCH 04/31] Fix order of transforms. --- samples/RenderDemo/Pages/Transform3DPage.axaml | 2 +- src/Avalonia.Visuals/Media/Transform3D.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index e66c498bbc..0bf2279dd3 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650" x:Class="RenderDemo.Pages.Transform3DPage"> diff --git a/src/Avalonia.Visuals/Media/Transform3D.cs b/src/Avalonia.Visuals/Media/Transform3D.cs index cfbde3204a..cbc1fc958d 100644 --- a/src/Avalonia.Visuals/Media/Transform3D.cs +++ b/src/Avalonia.Visuals/Media/Transform3D.cs @@ -154,6 +154,13 @@ public class Transform3D : Transform matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(RotationY)); matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(RotationZ)); + matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ); + + var perspectiveMatrix = Matrix4x4.Identity; + perspectiveMatrix.M34 = -1 / (float)50; + + matrix44 *= perspectiveMatrix; + var matrix = new Matrix( matrix44.M11, matrix44.M12, From 07f45cf1d724535d483f2c9b8acdfe6d393630cf Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Wed, 19 Jan 2022 15:15:56 +0100 Subject: [PATCH 05/31] Add animation. Add controls instead of Vector to prof usability. --- .../RenderDemo/Pages/Transform3DPage.axaml | 252 +++++++++++++----- 1 file changed, 179 insertions(+), 73 deletions(-) diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index 0bf2279dd3..438611976a 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -2,79 +2,185 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650" + mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200" x:Class="RenderDemo.Pages.Transform3DPage"> - - - - - - - - - - - - - - - - - - - - X-Rotation: - - Y-Rotation: - - Z-Rotation: - - X: - - Y: - - Z: - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - From 9fdb48d0c4328ea6332851ed0758bd24ca692ee8 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Wed, 19 Jan 2022 17:29:31 +0100 Subject: [PATCH 06/31] Rename transform. Re-add depth. Add slider to preview for showing effect of depth. --- .../RenderDemo/Pages/Transform3DPage.axaml | 89 ++++---- .../ViewModels/Transform3DPageViewModel.cs | 44 ---- .../Animation/Animators/TransformAnimator.cs | 2 +- .../Media/Rotate3DTransform.cs | 195 ++++++++++++++++++ src/Avalonia.Visuals/Media/Transform3D.cs | 178 ---------------- 5 files changed, 235 insertions(+), 273 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/Rotate3DTransform.cs delete mode 100644 src/Avalonia.Visuals/Media/Transform3D.cs diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index 438611976a..1093399c0c 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="200" + mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="400" x:Class="RenderDemo.Pages.Transform3DPage"> @@ -16,10 +16,10 @@ - + @@ -142,45 +142,34 @@ - + - + - + - + + + Depth: + + + diff --git a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs index d28705fc0d..0c1caac8ea 100644 --- a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs @@ -6,56 +6,12 @@ namespace RenderDemo.ViewModels { public class Transform3DPageViewModel : ViewModelBase { - private double _rotationX = 0; - private double _rotationY = 0; - private double _rotationZ = 0; - - private double _x = 0; - private double _y = 0; - private double _z = 0; - private double _depth = 200; - public double RotationX - { - get => _rotationX; - set => RaiseAndSetIfChanged(ref _rotationX, value); - } - - public double RotationY - { - get => _rotationY; - set => RaiseAndSetIfChanged(ref _rotationY, value); - } - - public double RotationZ - { - get => _rotationZ; - set => RaiseAndSetIfChanged(ref _rotationZ, value); - } - public double Depth { get => _depth; set => RaiseAndSetIfChanged(ref _depth, value); } - - public double X - { - get => _x; - set => RaiseAndSetIfChanged(ref _x, value); - } - - public double Y - { - get => _y; - set => RaiseAndSetIfChanged(ref _y, value); - } - - public double Z - { - get => _z; - set => RaiseAndSetIfChanged(ref _z, value); - } } } diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index a98f2ec65e..e12ca722f9 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -43,7 +43,7 @@ namespace Avalonia.Animation.Animators normalTransform.Children.Add(new SkewTransform()); normalTransform.Children.Add(new RotateTransform()); normalTransform.Children.Add(new TranslateTransform()); - normalTransform.Children.Add(new Transform3D()); + normalTransform.Children.Add(new Rotate3DTransform()); ctrl.RenderTransform = normalTransform; } diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs new file mode 100644 index 0000000000..9a81ae306c --- /dev/null +++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs @@ -0,0 +1,195 @@ +using System; +using System.Numerics; + +namespace Avalonia.Media; + +/// +/// Non-Affine 3D transformation for rotating an visual around a definable axis +/// +public class Rotate3DTransform : Transform +{ + /// + /// Defines the property. + /// + public static readonly StyledProperty RotationXProperty = + AvaloniaProperty.Register(nameof(RotationX)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RotationYProperty = + AvaloniaProperty.Register(nameof(RotationY)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RotationZProperty = + AvaloniaProperty.Register(nameof(RotationZ)); + + + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterXProperty = + AvaloniaProperty.Register(nameof(CenterX)); + + + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterYProperty = + AvaloniaProperty.Register(nameof(CenterY)); + + + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterZProperty = + AvaloniaProperty.Register(nameof(CenterZ)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty DepthProperty = + AvaloniaProperty.Register(nameof(Depth)); + + /// + /// Initializes a new instance of the class. + /// + public Rotate3DTransform() + { + this.GetObservable(RotationXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(RotationYProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(RotationZProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterYProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(CenterZProperty).Subscribe(_ => RaiseChanged()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The rotation around the X-Axis + /// The rotation around the Y-Axis + /// The rotation around the Z-Axis + /// The origin of the X-Axis + /// The origin of the Y-Axis + /// The origin of the Z-Axis + public Rotate3DTransform( + double rotationX, + double rotationY, + double rotationZ, + double centerX, + double centerY, + double centerZ) : this() + { + RotationX = rotationX; + RotationY = rotationY; + RotationZ = rotationZ; + CenterX = centerX; + CenterY = centerY; + CenterZ = centerZ; + } + + /// + /// Sets the rotation around the X-Axis + /// + public double RotationX + { + get => GetValue(RotationXProperty); + set => SetValue(RotationXProperty, value); + } + + /// + /// Sets the rotation around the Y-Axis + /// + public double RotationY + { + get => GetValue(RotationYProperty); + set => SetValue(RotationYProperty, value); + } + + /// + /// Sets the rotation around the Z-Axis + /// + public double RotationZ + { + get => GetValue(RotationZProperty); + set => SetValue(RotationZProperty, value); + } + + /// + /// Moves the origin of the X-Axis + /// + public double CenterX + { + get => GetValue(CenterXProperty); + set => SetValue(CenterXProperty, value); + } + + /// + /// Moves the origin of the Y-Axis + /// + public double CenterY + { + get => GetValue(CenterYProperty); + set => SetValue(CenterYProperty, value); + } + + /// + /// Moves the origin of the Z-Axis + /// + public double CenterZ + { + get => GetValue(CenterZProperty); + set => SetValue(CenterZProperty, value); + } + + /// + /// Affects the depth of the rotation effect + /// + public double Depth + { + get => GetValue(DepthProperty); + set => SetValue(DepthProperty, value); + } + + /// + /// Gets the transform's . + /// + public override Matrix Value + { + get + { + var matrix44 = Matrix4x4.Identity; + + matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ); + + matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(RotationX)); + matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(RotationY)); + matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(RotationZ)); + + matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ); + + if (Depth != 0) + { + var perspectiveMatrix = Matrix4x4.Identity; + perspectiveMatrix.M34 = -1 / (float)Depth; + matrix44 *= perspectiveMatrix; + } + + var matrix = new Matrix( + matrix44.M11, + matrix44.M12, + matrix44.M14, + matrix44.M21, + matrix44.M22, + matrix44.M24, + matrix44.M41, + matrix44.M42, + matrix44.M44); + + return matrix; + } + } +} diff --git a/src/Avalonia.Visuals/Media/Transform3D.cs b/src/Avalonia.Visuals/Media/Transform3D.cs deleted file mode 100644 index cbc1fc958d..0000000000 --- a/src/Avalonia.Visuals/Media/Transform3D.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Numerics; - -namespace Avalonia.Media; - -public class Transform3D : Transform -{ - /// - /// Defines the property. - /// - public static readonly StyledProperty RotationXProperty = - AvaloniaProperty.Register(nameof(RotationX)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty RotationYProperty = - AvaloniaProperty.Register(nameof(RotationY)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty RotationZProperty = - AvaloniaProperty.Register(nameof(RotationZ)); - - - /// - /// Defines the property. - /// - public static readonly StyledProperty CenterXProperty = - AvaloniaProperty.Register(nameof(CenterX)); - - - /// - /// Defines the property. - /// - public static readonly StyledProperty CenterYProperty = - AvaloniaProperty.Register(nameof(CenterY)); - - - /// - /// Defines the property. - /// - public static readonly StyledProperty CenterZProperty = - AvaloniaProperty.Register(nameof(CenterZ)); - - - /// - /// Initializes a new instance of the class. - /// - public Transform3D() - { - this.GetObservable(RotationXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(RotationYProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(RotationZProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(CenterXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(CenterYProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(CenterXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(CenterYProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(CenterZProperty).Subscribe(_ => RaiseChanged()); - } - - /// - /// Initializes a new instance of the class. - /// - /// The rotation around the X-Axis - /// The rotation around the Y-Axis - /// The rotation around the Z-Axis - /// The origin of the X-Axis - /// The origin of the Y-Axis - /// The origin of the Z-Axis - public Transform3D( - double rotationX, - double rotationY, - double rotationZ, - double originCenterX, - double originCenterY, - double originCenterZ) : this() - { - RotationX = rotationX; - RotationY = rotationY; - RotationZ = rotationZ; - CenterX = originCenterX; - CenterY = originCenterY; - CenterZ = originCenterZ; - } - - /// - /// Sets the rotation around the X-Axis - /// - public double RotationX - { - get => GetValue(RotationXProperty); - set => SetValue(RotationXProperty, value); - } - - /// - /// Sets the rotation around the Y-Axis - /// - public double RotationY - { - get => GetValue(RotationYProperty); - set => SetValue(RotationYProperty, value); - } - - /// - /// Sets the rotation around the Z-Axis - /// - public double RotationZ - { - get => GetValue(RotationZProperty); - set => SetValue(RotationZProperty, value); - } - - /// - /// Moves the origin of the X-Axis - /// - public double CenterX - { - get => GetValue(CenterXProperty); - set => SetValue(CenterXProperty, value); - } - - /// - /// Moves the origin of the Y-Axis - /// - public double CenterY - { - get => GetValue(CenterYProperty); - set => SetValue(CenterYProperty, value); - } - - /// - /// Moves the origin of the Z-Axis - /// - public double CenterZ - { - get => GetValue(CenterZProperty); - set => SetValue(CenterZProperty, value); - } - - /// - /// Gets the transform's . - /// - public override Matrix Value - { - get - { - var matrix44 = Matrix4x4.Identity; - - matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ); - - matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(RotationX)); - matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(RotationY)); - matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(RotationZ)); - - matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ); - - var perspectiveMatrix = Matrix4x4.Identity; - perspectiveMatrix.M34 = -1 / (float)50; - - matrix44 *= perspectiveMatrix; - - var matrix = new Matrix( - matrix44.M11, - matrix44.M12, - matrix44.M14, - matrix44.M21, - matrix44.M22, - matrix44.M24, - matrix44.M41, - matrix44.M42, - matrix44.M44); - - return matrix; - } - } -} From 8c131ba100ac3890595216c4936feb54840a2688 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 20 Jan 2022 06:13:00 +0100 Subject: [PATCH 07/31] Improve readability in RenderDemo, slow down animation. --- .../RenderDemo/Pages/Transform3DPage.axaml | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index 1093399c0c..8646dc6870 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -8,10 +8,15 @@ @@ -24,9 +29,16 @@ + + @@ -140,25 +156,25 @@ - + - + - + - + From e6d86fb898ce92569cb590bc2466d8ec7542627c Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 20 Jan 2022 06:54:00 +0100 Subject: [PATCH 08/31] Fix ContainsPerspective method. --- src/Avalonia.Visuals/Matrix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index e949d728d0..4326dc3670 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -361,7 +361,7 @@ namespace Avalonia { // ReSharper disable CompareOfFloatsByEqualityOperator - return _m31 != 0 || _m32 != 0 || _m33 != 1; + return _m13 != 0 || _m23 != 0 || _m33 != 1; // ReSharper restore CompareOfFloatsByEqualityOperator } From bde9c078c56eb3f7864cac17a46315af783fa123 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 20 Jan 2022 13:58:34 +0100 Subject: [PATCH 09/31] Add observable. Remove borders from sample to better visualize error. --- samples/RenderDemo/Pages/Transform3DPage.axaml | 8 ++++---- src/Avalonia.Visuals/Media/Rotate3DTransform.cs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index 8646dc6870..9f369dae98 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -162,24 +162,24 @@ Depth="{Binding Depth}"/> - + - + diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs index 9a81ae306c..a3af6b8c66 100644 --- a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs +++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs @@ -64,6 +64,7 @@ public class Rotate3DTransform : Transform this.GetObservable(CenterXProperty).Subscribe(_ => RaiseChanged()); this.GetObservable(CenterYProperty).Subscribe(_ => RaiseChanged()); this.GetObservable(CenterZProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(DepthProperty).Subscribe(_ => RaiseChanged()); } /// From 9078ca7dc8b2512322e5a2b26e91c2ca8348f9e1 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Wed, 26 Jan 2022 14:46:54 +0100 Subject: [PATCH 10/31] Fix Rotate3DTransform for DeferredRenderer. --- .../Avalonia.Build.Tasks.csproj | 1 + src/Avalonia.Visuals/Matrix.cs | 2 +- src/Avalonia.Visuals/Point.cs | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index e864ea2007..7e4272f5d2 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -94,5 +94,6 @@ + diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 4326dc3670..38d0508ffb 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -357,7 +357,7 @@ namespace Avalonia /// /// Determines if the current matrix contains perspective (non-affine) transforms (true) or only (affine) transforms that could be mapped into an 2x3 matrix (false). /// - private bool ContainsPerspective() + public bool ContainsPerspective() { // ReSharper disable CompareOfFloatsByEqualityOperator diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 67e7d71fbc..473de8e501 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Numerics; #if !BUILDTASK using Avalonia.Animation.Animators; #endif @@ -244,6 +245,22 @@ namespace Avalonia /// The transformed point. public Point Transform(Matrix transform) { + if (transform.ContainsPerspective()) + { + var m44 = new Matrix4x4( + (float)transform.M11, (float)transform.M12, (float)transform.M13, 0, + (float)transform.M21, (float)transform.M22, (float)transform.M23, 0, + (float)transform.M31, (float)transform.M32, (float)transform.M33, 0, + 0, 0, 0, 1 + ); + + var vector = new Vector3((float)X, (float)Y, 1); + var transformedVector = Vector3.Transform(vector, m44); + var z = 1 / transformedVector.Z; + + return new Point(transformedVector.X * z, transformedVector.Y * z); + } + var x = X; var y = Y; var xadd = y * transform.M21 + transform.M31; From e8f2d3e3f3372fa226a1cb57b447974fe1da8f79 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Wed, 26 Jan 2022 16:25:18 +0100 Subject: [PATCH 11/31] Add comments, remove todos, extend example. --- .../RenderDemo/Pages/Transform3DPage.axaml | 162 ++++++++++++------ .../ViewModels/Transform3DPageViewModel.cs | 44 +++++ src/Avalonia.Visuals/Matrix.cs | 50 +++++- .../Media/Rotate3DTransform.cs | 73 ++++---- src/Avalonia.Visuals/Point.cs | 29 +--- 5 files changed, 235 insertions(+), 123 deletions(-) diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index 9f369dae98..bf63b6d1c6 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -2,12 +2,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="400" + mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="700" x:Class="RenderDemo.Pages.Transform3DPage"> - + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs index 2a6bdcb434..c8d1d40e3a 100644 --- a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs @@ -8,7 +8,6 @@ namespace RenderDemo.ViewModels { private double _depth = 200; - private double _depth2 = 200; private double _centerX = 0; private double _centerY = 0; private double _centerZ = 0; @@ -22,11 +21,6 @@ namespace RenderDemo.ViewModels set => RaiseAndSetIfChanged(ref _depth, value); } - public double Depth2 - { - get => _depth2; - set => RaiseAndSetIfChanged(ref _depth2, value); - } public double CenterX { get => _centerX; From 3e7f190ff9881fc0785e4698aca96099212e5b71 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 27 Jan 2022 13:39:27 +0100 Subject: [PATCH 16/31] Add code review changes: Fix typos. Simplify code. Generalize code. Remove duplications. Improve performance. --- .../RenderDemo/Pages/Transform3DPage.axaml | 234 +++++++++--------- .../Resources/Resource.Designer.cs | 2 +- .../Transitions/Rotate3DTransition.cs | 124 ++++------ src/Avalonia.Visuals/Matrix.cs | 12 +- .../Media/Rotate3DTransform.cs | 2 +- src/Avalonia.Visuals/Point.cs | 7 +- 6 files changed, 163 insertions(+), 218 deletions(-) diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml index fa03c7ba49..30ed35bbac 100644 --- a/samples/RenderDemo/Pages/Transform3DPage.axaml +++ b/samples/RenderDemo/Pages/Transform3DPage.axaml @@ -21,149 +21,139 @@ + + + + + + + + + - - - - - - - - - - - + - - - - + - - - - + - - - - + diff --git a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs index 83db67fcee..87fd47df25 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs +++ b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace Avalonia.AndroidTestApplication { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")] public partial class Resource { diff --git a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs index b603fe5f7b..56567f5fea 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Avalonia.Collections; using Avalonia.Media; using Avalonia.Styling; @@ -9,18 +10,26 @@ namespace Avalonia.Animation; public class Rotate3DTransition: PageSlide { - + /// - /// Creates a new instance if the + /// Creates a new instance of the /// /// How long the rotation should take place /// The orientation of the rotation - public Rotate3DTransition(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal) + public Rotate3DTransition(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal, double? depth = null) : base(duration, orientation) - {} + { + Depth = depth; + } + + /// + /// Defines the depth of the 3D Effect. If null, depth will be calculated automatically from the width or height + /// of the common parent of the visual being rotated. + /// + public double? Depth { get; set; } /// - /// Creates a new instance if the + /// Creates a new instance of the /// public Rotate3DTransition() { } @@ -41,49 +50,32 @@ public class Rotate3DTransition: PageSlide _ => throw new ArgumentOutOfRangeException() }; - var depthSetter = new Setter {Property = Rotate3DTransform.DepthProperty, Value = center}; + var depthSetter = new Setter {Property = Rotate3DTransform.DepthProperty, Value = Depth ?? center}; var centerZSetter = new Setter {Property = Rotate3DTransform.CenterZProperty, Value = -center / 2}; + KeyFrame CreateKeyFrame(double cue, double rotation, int zIndex) => + new() { + Setters = + { + new Setter { Property = rotateProperty, Value = rotation }, + new Setter { Property = Visual.ZIndexProperty, Value = zIndex }, + centerZSetter, + depthSetter + }, + Cue = new Cue(cue) + }; + if (from != null) { var animation = new Animation { + Easing = SlideOutEasing, Duration = Duration, Children = { - new KeyFrame - { - Setters = - { - new Setter { Property = rotateProperty, Value = 0d }, - new Setter { Property = Visual.ZIndexProperty, Value = 2 }, - centerZSetter, - depthSetter, - }, - Cue = new Cue(0d) - }, - new KeyFrame - { - Setters = - { - new Setter { Property = rotateProperty, Value = 45d * (forward ? -1 : 1) }, - new Setter { Property = Visual.ZIndexProperty, Value = 1 }, - centerZSetter, - depthSetter - }, - Cue = new Cue(0.5d) - }, - new KeyFrame - { - Setters = - { - new Setter { Property = rotateProperty, Value = 90d * (forward ? -1 : 1) }, - new Setter { Property = Visual.ZIndexProperty, Value = 1 }, - centerZSetter, - depthSetter - }, - Cue = new Cue(1d) - } + CreateKeyFrame(0d, 0d, 2), + CreateKeyFrame(0.5d, 45d * (forward ? -1 : 1), 1), + CreateKeyFrame(1d, 90d * (forward ? -1 : 1), 1) } }; @@ -95,42 +87,13 @@ public class Rotate3DTransition: PageSlide to.IsVisible = true; var animation = new Animation { + Easing = SlideInEasing, Duration = Duration, Children = { - new KeyFrame - { - Setters = - { - new Setter { Property = rotateProperty, Value = 90d * (forward ? 1 : -1) }, - new Setter { Property = Visual.ZIndexProperty, Value = 1 }, - centerZSetter, - depthSetter - }, - Cue = new Cue(0d) - }, - new KeyFrame - { - Setters = - { - new Setter { Property = Visual.ZIndexProperty, Value = 1 }, - new Setter { Property = rotateProperty, Value = 45d * (forward ? 1 : -1) }, - centerZSetter, - depthSetter - }, - Cue = new Cue(0.5d) - }, - new KeyFrame - { - Setters = - { - new Setter { Property = rotateProperty, Value = 0d }, - new Setter { Property = Visual.ZIndexProperty, Value = 2 }, - centerZSetter, - depthSetter, - }, - Cue = new Cue(1d) - } + CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1), + CreateKeyFrame(0.5d, 45d * (forward ? 1 : -1), 1), + CreateKeyFrame(1d, 0d, 2) } }; @@ -139,15 +102,18 @@ public class Rotate3DTransition: PageSlide await Task.WhenAll(tasks); - if (from != null && !cancellationToken.IsCancellationRequested) + if (!cancellationToken.IsCancellationRequested) { - from.IsVisible = false; - from.ZIndex = 1; - } + if (from != null) + { + from.IsVisible = false; + from.ZIndex = 1; + } - if (to != null && !cancellationToken.IsCancellationRequested) - { - to.ZIndex = 2; + if (to != null) + { + to.ZIndex = 2; + } } } } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 5c7d42676a..5bbc657385 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -348,15 +348,9 @@ namespace Avalonia } else { - var x = p.X; - var y = p.Y; - var xAdd = y * M21 + M31; - var yAdd = x * M12 + M32; - x *= M11; - x += xAdd; - y *= M22; - y += yAdd; - transformedResult = new Point(x, y); + return new Point( + (p.X * M11) + (p.Y * M21) + M31, + (p.X * M12) + (p.Y * M22) + M32); } return transformedResult; diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs index 0fea9d73a0..4feca7acd8 100644 --- a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs +++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs @@ -5,7 +5,7 @@ using Avalonia.Animation.Animators; namespace Avalonia.Media; /// -/// Non-Affine 3D transformation for rotating an visual around a definable axis +/// Non-Affine 3D transformation for rotating a visual around a definable axis /// public class Rotate3DTransform : Transform { diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 8e01ea47c5..fbdf0db800 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -169,12 +169,7 @@ namespace Avalonia /// The point. /// The matrix. /// The resulting point. - public static Point operator *(Point point, Matrix matrix) - { - return new Point( - (point.X * matrix.M11) + (point.Y * matrix.M21) + matrix.M31, - (point.X * matrix.M12) + (point.Y * matrix.M22) + matrix.M32); - } + public static Point operator *(Point point, Matrix matrix) => matrix.Transform(point); /// /// Parses a string. From 43de185f2fb181faa01d244d5312283ffd385ed3 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 27 Jan 2022 16:52:35 +0100 Subject: [PATCH 17/31] Add unit tests. Add param to constructor. --- .../Media/Rotate3DTransform.cs | 5 +- .../Avalonia.Visuals.UnitTests/MatrixTests.cs | 95 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Visuals.UnitTests/MatrixTests.cs diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs index 4feca7acd8..dbcf749789 100644 --- a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs +++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs @@ -70,13 +70,15 @@ public class Rotate3DTransform : Transform /// The origin of the X-Axis /// The origin of the Y-Axis /// The origin of the Z-Axis + /// The depth of the 3D effect public Rotate3DTransform( double angleX, double angleY, double angleZ, double centerX, double centerY, - double centerZ) : this() + double centerZ, + double depth) : this() { _isInitializing = true; AngleX = angleX; @@ -85,6 +87,7 @@ public class Rotate3DTransform : Transform CenterX = centerX; CenterY = centerY; CenterZ = centerZ; + Depth = depth; _isInitializing = false; } diff --git a/tests/Avalonia.Visuals.UnitTests/MatrixTests.cs b/tests/Avalonia.Visuals.UnitTests/MatrixTests.cs new file mode 100644 index 0000000000..cb5e2390c7 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/MatrixTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests; + +/// +/// These tests use the "official" Matrix4x4 and Matrix3x2 from the System.Numerics namespace, to validate +/// that Avalonias own implementation of a 3x3 Matrix works correctly. +/// +public class MatrixTests +{ + private double ReducePrecision(double input) + { + return Math.Truncate(input * 10000); + } + + /// + /// Because Avalonia is working internally with doubles, but System.Numerics Vector and Matrix implementations + /// only make use of floats, we need to reduce precision, comparing them. It should be sufficient to compare + /// 5 fractional digits to ensure, that the result is correct. + /// + /// The expected vector + /// The actual transformed point + private void AssertCoordinatesEqualWithReducedPrecision(Vector2 expected, Point actual) + { + var expectedX = ReducePrecision(expected.X); + var expectedY = ReducePrecision(expected.Y); + + var actualX = ReducePrecision(actual.X); + var actualY = ReducePrecision(actual.Y); + + Assert.Equal(expectedX, actualX); + Assert.Equal(expectedY, actualY); + } + + [Fact] + public void Transform_Point_Should_Return_Correct_Value_For_Translated_Matrix() + { + var vector2 = Vector2.Transform( + new Vector2(1, 1), + Matrix3x2.CreateTranslation(2, 2)); + var expected = new Point(vector2.X, vector2.Y); + + var matrix = Matrix.CreateTranslation(2, 2); + var point = new Point(1, 1); + var transformedPoint = matrix.Transform(point); + + Assert.Equal(expected, transformedPoint); + } + + [Fact] + public void Transform_Point_Should_Return_Correct_Value_For_Rotated_Matrix() + { + var expected = Vector2.Transform( + new Vector2(0, 10), + Matrix3x2.CreateRotation((float)Matrix.ToRadians(45))); + + var matrix = Matrix.CreateRotation(Matrix.ToRadians(45)); + var point = new Point(0, 10); + var actual = matrix.Transform(point); + + AssertCoordinatesEqualWithReducedPrecision(expected, actual); + } + + [Fact] + public void Transform_Point_Should_Return_Correct_Value_For_Scaled_Matrix() + { + var vector2 = Vector2.Transform( + new Vector2(1, 1), + Matrix3x2.CreateScale(2, 2)); + var expected = new Point(vector2.X, vector2.Y); + var matrix = Matrix.CreateScale(2, 2); + var point = new Point(1, 1); + var actual = matrix.Transform(point); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Transform_Point_Should_Return_Correct_Value_For_Skewed_Matrix() + { + var expected = Vector2.Transform( + new Vector2(1, 1), + Matrix3x2.CreateSkew(30, 20)); + + var matrix = Matrix.CreateSkew(30, 20); + var point = new Point(1, 1); + var actual = matrix.Transform(point); + + AssertCoordinatesEqualWithReducedPrecision(expected, actual); + } +} From 30f99c847a767d2ff7776964ed209df6a55ddd0d Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 27 Jan 2022 16:54:26 +0100 Subject: [PATCH 18/31] Refactor test. --- tests/Avalonia.Visuals.UnitTests/MatrixTests.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Visuals.UnitTests/MatrixTests.cs b/tests/Avalonia.Visuals.UnitTests/MatrixTests.cs index cb5e2390c7..99fe4685d7 100644 --- a/tests/Avalonia.Visuals.UnitTests/MatrixTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/MatrixTests.cs @@ -12,11 +12,6 @@ namespace Avalonia.Visuals.UnitTests; /// public class MatrixTests { - private double ReducePrecision(double input) - { - return Math.Truncate(input * 10000); - } - /// /// Because Avalonia is working internally with doubles, but System.Numerics Vector and Matrix implementations /// only make use of floats, we need to reduce precision, comparing them. It should be sufficient to compare @@ -26,6 +21,8 @@ public class MatrixTests /// The actual transformed point private void AssertCoordinatesEqualWithReducedPrecision(Vector2 expected, Point actual) { + double ReducePrecision(double input) => Math.Truncate(input * 10000); + var expectedX = ReducePrecision(expected.X); var expectedY = ReducePrecision(expected.Y); From 69d65f6c95715934ff295780c05fe846d22052b7 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 27 Jan 2022 20:40:47 +0100 Subject: [PATCH 19/31] Revert unwanted change. --- samples/ControlCatalog.Android/Resources/Resource.Designer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs index b1ca548e2c..dccc3f7159 100644 --- a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs +++ b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace ControlCatalog.Android { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")] public partial class Resource { From b07a1b0222e194b559445a0a8fac0c7f903b8512 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Fri, 28 Jan 2022 13:53:03 +0100 Subject: [PATCH 20/31] Add double.Epsilon check instead of 0. --- src/Avalonia.Visuals/Media/Rotate3DTransform.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs index dbcf749789..e899f6b49b 100644 --- a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs +++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs @@ -164,13 +164,13 @@ public class Rotate3DTransform : Transform var matrix44 = Matrix4x4.Identity; var centerSum = CenterX + CenterY + CenterZ; - if (centerSum != 0) matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ); + if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ); if (AngleX != 0) matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(AngleX)); if (AngleY != 0) matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(AngleY)); if (AngleZ != 0) matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(AngleZ)); - if (centerSum != 0) matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ); + if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ); if (Depth != 0) { From 75788233170777fe8305bb57df8a72e11b0fd564 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Fri, 28 Jan 2022 15:36:25 +0100 Subject: [PATCH 21/31] Add copy of values as basis for matrix transform. It is not guaranteed, that values will not change during calculation. --- .../Media/Rotate3DTransform.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs index e899f6b49b..a24d6b26c3 100644 --- a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs +++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs @@ -162,20 +162,29 @@ public class Rotate3DTransform : Transform get { var matrix44 = Matrix4x4.Identity; - var centerSum = CenterX + CenterY + CenterZ; + //Copy values first, because it's not guaranteed, that values will not change during calculation + var (copyCenterX, + copyCenterY, + copyCenterZ, + copyAngleX, + copyAngleY, + copyAngleZ, + copyDepth) = (CenterX, CenterY, CenterZ, AngleX, AngleY, AngleZ, Depth); - if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation(-(float)CenterX, -(float)CenterY, -(float)CenterZ); + var centerSum = copyCenterX + copyCenterY + copyCenterZ; + + if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation(-(float)copyCenterX, -(float)copyCenterY, -(float)copyCenterZ); - if (AngleX != 0) matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(AngleX)); - if (AngleY != 0) matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(AngleY)); - if (AngleZ != 0) matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(AngleZ)); + if (AngleX != 0) matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(copyAngleX)); + if (AngleY != 0) matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(copyAngleY)); + if (AngleZ != 0) matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(copyAngleZ)); - if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation((float)CenterX, (float)CenterY, (float)CenterZ); + if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation((float)copyCenterX, (float)copyCenterY, (float)copyCenterZ); - if (Depth != 0) + if (copyDepth != 0) { var perspectiveMatrix = Matrix4x4.Identity; - perspectiveMatrix.M34 = -1 / (float)Depth; + perspectiveMatrix.M34 = -1 / (float)copyDepth; matrix44 *= perspectiveMatrix; } From e571559b64ca6c6c832aa6bf90e5db2743f3f74f Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Fri, 28 Jan 2022 16:47:22 +0100 Subject: [PATCH 22/31] Fix missing use of local copies. --- src/Avalonia.Visuals/Media/Rotate3DTransform.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs index a24d6b26c3..306363ec39 100644 --- a/src/Avalonia.Visuals/Media/Rotate3DTransform.cs +++ b/src/Avalonia.Visuals/Media/Rotate3DTransform.cs @@ -175,9 +175,9 @@ public class Rotate3DTransform : Transform if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation(-(float)copyCenterX, -(float)copyCenterY, -(float)copyCenterZ); - if (AngleX != 0) matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(copyAngleX)); - if (AngleY != 0) matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(copyAngleY)); - if (AngleZ != 0) matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(copyAngleZ)); + if (copyAngleX != 0) matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(copyAngleX)); + if (copyAngleY != 0) matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(copyAngleY)); + if (copyAngleZ != 0) matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(copyAngleZ)); if (Math.Abs(centerSum) > double.Epsilon) matrix44 *= Matrix4x4.CreateTranslation((float)copyCenterX, (float)copyCenterY, (float)copyCenterZ); From 662dbf78649c60fbdee853bb4076f5ac3836bf7e Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Wed, 9 Feb 2022 19:18:25 +0100 Subject: [PATCH 23/31] Switch from list to array. Fix flickering. --- .../Transitions/Rotate3DTransition.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs index 56567f5fea..ebc8f44278 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Avalonia.Collections; using Avalonia.Media; using Avalonia.Styling; @@ -41,7 +39,7 @@ public class Rotate3DTransition: PageSlide return; } - var tasks = new List(); + var tasks = new Task[2]; var parent = GetVisualParent(from, to); var (rotateProperty, center) = Orientation switch { @@ -53,12 +51,13 @@ public class Rotate3DTransition: PageSlide var depthSetter = new Setter {Property = Rotate3DTransform.DepthProperty, Value = Depth ?? center}; var centerZSetter = new Setter {Property = Rotate3DTransform.CenterZProperty, Value = -center / 2}; - KeyFrame CreateKeyFrame(double cue, double rotation, int zIndex) => + KeyFrame CreateKeyFrame(double cue, double rotation, int zIndex, bool isVisible = true) => new() { Setters = { new Setter { Property = rotateProperty, Value = rotation }, new Setter { Property = Visual.ZIndexProperty, Value = zIndex }, + new Setter { Property = Visual.IsVisibleProperty, Value = isVisible }, centerZSetter, depthSetter }, @@ -71,15 +70,16 @@ public class Rotate3DTransition: PageSlide { Easing = SlideOutEasing, Duration = Duration, + FillMode = FillMode.Forward, Children = { CreateKeyFrame(0d, 0d, 2), CreateKeyFrame(0.5d, 45d * (forward ? -1 : 1), 1), - CreateKeyFrame(1d, 90d * (forward ? -1 : 1), 1) + CreateKeyFrame(1d, 90d * (forward ? -1 : 1), 1, isVisible: false) } }; - tasks.Add(animation.RunAsync(from, null, cancellationToken)); + tasks[0] = animation.RunAsync(from, null, cancellationToken); } if (to != null) @@ -89,6 +89,7 @@ public class Rotate3DTransition: PageSlide { Easing = SlideInEasing, Duration = Duration, + FillMode = FillMode.Forward, Children = { CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1), @@ -97,23 +98,23 @@ public class Rotate3DTransition: PageSlide } }; - tasks.Add(animation.RunAsync(to, null, cancellationToken)); + tasks[1] = animation.RunAsync(to, null, cancellationToken); } await Task.WhenAll(tasks); if (!cancellationToken.IsCancellationRequested) { + if (to != null) + { + to.ZIndex = 2; + } + if (from != null) { from.IsVisible = false; from.ZIndex = 1; } - - if (to != null) - { - to.ZIndex = 2; - } } } } From 9c5b3ab4d8c7b454e5a33ae65a2d8d501fbf4ca7 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 10 Feb 2022 13:22:33 +0100 Subject: [PATCH 24/31] Fix null reference exception for Tasks.WhenAll. --- .../Animation/Transitions/Rotate3DTransition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs index ebc8f44278..01d04dff6b 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs @@ -39,7 +39,7 @@ public class Rotate3DTransition: PageSlide return; } - var tasks = new Task[2]; + var tasks = new Task[from != null && to != null ? 2 : 1]; var parent = GetVisualParent(from, to); var (rotateProperty, center) = Orientation switch { From 20c28efd90a7f4a6a636a7bb70745fcbbab8d473 Mon Sep 17 00:00:00 2001 From: Jan-Peter Zurek Date: Thu, 10 Feb 2022 13:24:33 +0100 Subject: [PATCH 25/31] Improve previous fix. --- .../Animation/Transitions/Rotate3DTransition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs index 01d04dff6b..60c7a97ced 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/Rotate3DTransition.cs @@ -98,7 +98,7 @@ public class Rotate3DTransition: PageSlide } }; - tasks[1] = animation.RunAsync(to, null, cancellationToken); + tasks[from != null ? 1 : 0] = animation.RunAsync(to, null, cancellationToken); } await Task.WhenAll(tasks); From a92a49b04fb44103c0ec5506564ee7f0ab87dbfa Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 9 May 2022 22:14:35 +0300 Subject: [PATCH 26/31] Updated Rotate3DTransform to the new OnPropertyChanged --- src/Avalonia.Base/Rotate3DTransform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rotate3DTransform.cs b/src/Avalonia.Base/Rotate3DTransform.cs index 306363ec39..2c4e515861 100644 --- a/src/Avalonia.Base/Rotate3DTransform.cs +++ b/src/Avalonia.Base/Rotate3DTransform.cs @@ -203,7 +203,7 @@ public class Rotate3DTransform : Transform } } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { if (!_isInitializing) RaiseChanged(); } From af237c6dd7067b7b5f43961f4fd64b635a2d20fc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 May 2022 11:24:53 +0100 Subject: [PATCH 27/31] fix file dialog filter nullable annotation, and osx platform. --- samples/ControlCatalog/ControlCatalog.csproj | 3 ++- samples/ControlCatalog/Pages/DialogsPage.xaml.cs | 3 +-- src/Avalonia.Controls/SystemDialog.cs | 2 +- src/Avalonia.Native/SystemDialogs.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index e5f07c90c3..bce924a3f2 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -1,7 +1,8 @@  netstandard2.0 - true + true + enable diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 3cadc7243a..fd908a33b6 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -8,7 +8,6 @@ using Avalonia.Dialogs; using Avalonia.Layout; using Avalonia.Markup.Xaml; #pragma warning disable 4014 - namespace ControlCatalog.Pages { public class DialogsPage : UserControl @@ -22,7 +21,7 @@ namespace ControlCatalog.Pages string lastSelectedDirectory = null; - List GetFilters() + List? GetFilters() { if (this.FindControl("UseFilters").IsChecked != true) return null; diff --git a/src/Avalonia.Controls/SystemDialog.cs b/src/Avalonia.Controls/SystemDialog.cs index 4a9e745e30..093f10be51 100644 --- a/src/Avalonia.Controls/SystemDialog.cs +++ b/src/Avalonia.Controls/SystemDialog.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls /// Gets or sets a collection of filters which determine the types of files displayed in an /// or an . /// - public List Filters { get; set; } = new List(); + public List? Filters { get; set; } = new List(); /// /// Gets or sets initial file name that is displayed when the dialog is opened. diff --git a/src/Avalonia.Native/SystemDialogs.cs b/src/Avalonia.Native/SystemDialogs.cs index 4372829df1..d1d9c17ae3 100644 --- a/src/Avalonia.Native/SystemDialogs.cs +++ b/src/Avalonia.Native/SystemDialogs.cs @@ -30,7 +30,7 @@ namespace Avalonia.Native ofd.Title ?? "", ofd.Directory ?? "", ofd.InitialFileName ?? "", - string.Join(";", dialog.Filters.SelectMany(f => f.Extensions))); + string.Join(";", dialog.Filters?.SelectMany(f => f.Extensions) ?? Array.Empty())); } else { @@ -39,7 +39,7 @@ namespace Avalonia.Native dialog.Title ?? "", dialog.Directory ?? "", dialog.InitialFileName ?? "", - string.Join(";", dialog.Filters.SelectMany(f => f.Extensions))); + string.Join(";", dialog.Filters?.SelectMany(f => f.Extensions) ?? Array.Empty())); } return events.Task.ContinueWith(t => { events.Dispose(); return t.Result; }); From 5b66f260657190e09698254cf5059b3efa8ef6df Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 May 2022 11:38:50 +0100 Subject: [PATCH 28/31] Fix osx dialog style mask. makes the titlebar look like normal window. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index d43a8beee4..d96fe717ab 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -523,7 +523,7 @@ bool WindowImpl::IsDialog() { } NSWindowStyleMask WindowImpl::GetStyle() { - unsigned long s = this->_isDialog ? NSWindowStyleMaskUtilityWindow : NSWindowStyleMaskBorderless; + unsigned long s = this->_isDialog ? NSWindowStyleMaskDocModalWindow : NSWindowStyleMaskBorderless; switch (_decorations) { case SystemDecorationsNone: From 8aa64bf839d26e3971456157038a717fc1a0562f Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 25 May 2022 12:48:22 +0200 Subject: [PATCH 29/31] Update SkiaSharp. --- build/SkiaSharp.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index a217a8272d..1ee4aa56a2 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,7 +1,7 @@  - - - + + + From ea9bd40dbd4c3f576f8e6a4efa14987983983fdd Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 25 May 2022 12:57:02 +0200 Subject: [PATCH 30/31] Update HarfBuzzSharp. --- build/HarfBuzzSharp.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props index e10de93530..85e7a1f34d 100644 --- a/build/HarfBuzzSharp.props +++ b/build/HarfBuzzSharp.props @@ -1,7 +1,7 @@  - - - + + + From ba7fd6c9e8be304db7bdc22900f22ead2efcd799 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 25 May 2022 15:30:24 +0200 Subject: [PATCH 31/31] fix: some xml comment --- src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs | 1 + src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs index 60c7a97ced..239f3aea08 100644 --- a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs @@ -14,6 +14,7 @@ public class Rotate3DTransition: PageSlide /// /// How long the rotation should take place /// The orientation of the rotation + /// Defines the depth of the 3D Effect. If null, depth will be calculated automatically from the width or height of the common parent of the visual being rotated public Rotate3DTransition(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal, double? depth = null) : base(duration, orientation) { diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs index 445f35aad2..45c67b9f48 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs @@ -55,7 +55,7 @@ namespace Avalonia /// /// /// This will usually be true, except in - /// + /// /// which receives notifications for all changes to property values, whether a value with a higher /// priority is present or not. When this property is false, the change that is being signaled /// has not resulted in a change to the property value on the object. diff --git a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs index a3095ad214..ae52e5f970 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs @@ -193,8 +193,11 @@ namespace Avalonia.Controls } } + /// Try get number of DataSource itmes. /// When "allowSlow" is false, method will not use Linq.Count() method and will return 0 or 1 instead. /// If "getAny" is true, method can use Linq.Any() method to speedup. + /// number of DataSource itmes. + /// true if able to retrieve number of DataSource itmes; otherwise, false. internal bool TryGetCount(bool allowSlow, bool getAny, out int count) { bool result;