From c811dd3ec1d8f3f1c9b3e027a08c98465fe00586 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 22 Jan 2026 17:10:43 +0100 Subject: [PATCH 01/11] feat(Skia): Implement PlaneProjection and Matrix3DProjection Add support for UIElement.Projection on Skia target: - Implement Matrix3D struct with all operations (constructor, equality, multiply, invert) - Add Projection base class for Skia - Implement PlaneProjection with all properties and matrix calculation - Implement Matrix3DProjection - Modify NativeRenderTransformAdapter to combine RenderTransform with Projection - Add Projection property handling to UIElement for Skia Co-Authored-By: Claude Opus 4.5 --- .../Matrix3DProjection.cs | 16 +- .../PlaneProjection.cs | 110 +++--- .../Microsoft.UI.Xaml.Media/Projection.cs | 6 +- .../3.0.0.0/Microsoft.UI.Xaml/UIElement.cs | 8 +- .../NativeRenderTransformAdapter.skia.cs | 23 +- .../UI/Xaml/Media/Matrix3DProjection.cs | 57 +++ src/Uno.UI/UI/Xaml/Media/Media3D/Matrix3D.cs | 260 +++++++++--- src/Uno.UI/UI/Xaml/Media/PlaneProjection.cs | 373 ++++++++++++++++++ src/Uno.UI/UI/Xaml/Media/Projection.cs | 51 +++ src/Uno.UI/UI/Xaml/UIElement.cs | 68 ++++ 10 files changed, 851 insertions(+), 121 deletions(-) create mode 100644 src/Uno.UI/UI/Xaml/Media/Matrix3DProjection.cs create mode 100644 src/Uno.UI/UI/Xaml/Media/PlaneProjection.cs create mode 100644 src/Uno.UI/UI/Xaml/Media/Projection.cs diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Matrix3DProjection.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Matrix3DProjection.cs index aa1d127d0d47..2196e289a547 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Matrix3DProjection.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Matrix3DProjection.cs @@ -3,14 +3,16 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Media { +#if !__SKIA__ [global::Microsoft.UI.Xaml.Markup.ContentPropertyAttribute(Name = "ProjectionMatrix")] -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ +#endif +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ [global::Uno.NotImplemented] #endif public partial class Matrix3DProjection : global::Microsoft.UI.Xaml.Media.Projection { -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public global::Microsoft.UI.Xaml.Media.Media3D.Matrix3D ProjectionMatrix { get @@ -23,16 +25,16 @@ public partial class Matrix3DProjection : global::Microsoft.UI.Xaml.Media.Projec } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty ProjectionMatrixProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(ProjectionMatrix), typeof(global::Microsoft.UI.Xaml.Media.Media3D.Matrix3D), typeof(global::Microsoft.UI.Xaml.Media.Matrix3DProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.Media.Media3D.Matrix3D))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public Matrix3DProjection() : base() { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Media.Matrix3DProjection", "Matrix3DProjection.Matrix3DProjection()"); diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/PlaneProjection.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/PlaneProjection.cs index 5aef17852423..ebf57d463af2 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/PlaneProjection.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/PlaneProjection.cs @@ -3,13 +3,13 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Media { -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ [global::Uno.NotImplemented] #endif public partial class PlaneProjection : global::Microsoft.UI.Xaml.Media.Projection { -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double RotationZ { get @@ -22,8 +22,8 @@ public double RotationZ } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double RotationY { get @@ -36,8 +36,8 @@ public double RotationY } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double RotationX { get @@ -50,8 +50,8 @@ public double RotationX } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double LocalOffsetZ { get @@ -64,8 +64,8 @@ public double LocalOffsetZ } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double LocalOffsetY { get @@ -78,8 +78,8 @@ public double LocalOffsetY } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double LocalOffsetX { get @@ -92,8 +92,8 @@ public double LocalOffsetX } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double GlobalOffsetZ { get @@ -106,8 +106,8 @@ public double GlobalOffsetZ } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double GlobalOffsetY { get @@ -120,8 +120,8 @@ public double GlobalOffsetY } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double GlobalOffsetX { get @@ -134,8 +134,8 @@ public double GlobalOffsetX } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double CenterOfRotationZ { get @@ -148,8 +148,8 @@ public double CenterOfRotationZ } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double CenterOfRotationY { get @@ -162,8 +162,8 @@ public double CenterOfRotationY } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public double CenterOfRotationX { get @@ -176,8 +176,8 @@ public double CenterOfRotationX } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public global::Microsoft.UI.Xaml.Media.Media3D.Matrix3D ProjectionMatrix { get @@ -186,112 +186,112 @@ public double CenterOfRotationX } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty CenterOfRotationXProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(CenterOfRotationX), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty CenterOfRotationYProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(CenterOfRotationY), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty CenterOfRotationZProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(CenterOfRotationZ), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty GlobalOffsetXProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(GlobalOffsetX), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty GlobalOffsetYProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(GlobalOffsetY), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty GlobalOffsetZProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(GlobalOffsetZ), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty LocalOffsetXProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(LocalOffsetX), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty LocalOffsetYProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(LocalOffsetY), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty LocalOffsetZProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(LocalOffsetZ), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty ProjectionMatrixProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(ProjectionMatrix), typeof(global::Microsoft.UI.Xaml.Media.Media3D.Matrix3D), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(global::Microsoft.UI.Xaml.Media.Media3D.Matrix3D))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty RotationXProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(RotationX), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty RotationYProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(RotationY), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty RotationZProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(RotationZ), typeof(double), typeof(global::Microsoft.UI.Xaml.Media.PlaneProjection), new Microsoft.UI.Xaml.FrameworkPropertyMetadata(default(double))); #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public PlaneProjection() : base() { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Media.PlaneProjection", "PlaneProjection.PlaneProjection()"); diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Projection.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Projection.cs index 932e8d7518b5..4005f7feb9f5 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Projection.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Media/Projection.cs @@ -3,13 +3,13 @@ #pragma warning disable 114 // new keyword hiding namespace Microsoft.UI.Xaml.Media { -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ [global::Uno.NotImplemented] #endif public partial class Projection : global::Microsoft.UI.Xaml.DependencyObject { -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] protected Projection() : base() { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.Media.Projection", "Projection.Projection()"); diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs index e53650934b24..1ee20528d667 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs @@ -365,8 +365,8 @@ public double RasterizationScale } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public global::Microsoft.UI.Xaml.Media.Projection Projection { get @@ -657,8 +657,8 @@ public double RasterizationScale } } #endif -#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")] +#if __ANDROID__ || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __NETSTD_REFERENCE__ + [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] public static global::Microsoft.UI.Xaml.DependencyProperty ProjectionProperty { get; } = Microsoft.UI.Xaml.DependencyProperty.Register( nameof(Projection), typeof(global::Microsoft.UI.Xaml.Media.Projection), diff --git a/src/Uno.UI/Media/NativeRenderTransformAdapter.skia.cs b/src/Uno.UI/Media/NativeRenderTransformAdapter.skia.cs index aed8a5d4323f..83a3529829d2 100644 --- a/src/Uno.UI/Media/NativeRenderTransformAdapter.skia.cs +++ b/src/Uno.UI/Media/NativeRenderTransformAdapter.skia.cs @@ -1,9 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Numerics; using System.Text; using Uno.Extensions; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Windows.Foundation; namespace Uno.UI.Media { @@ -19,14 +21,29 @@ partial void Apply(bool isSizeChanged, bool isOriginChanged) { FlowDirectionTransform = Owner.GetFlowDirectionTransform(); + // Get base 2D transform (RenderTransform + FlowDirection) + Matrix3x2 transform2D; if (Transform is null) { - Owner.Visual.TransformMatrix = new Matrix4x4(FlowDirectionTransform); + transform2D = FlowDirectionTransform; } else { - Owner.Visual.TransformMatrix = new Matrix4x4(Transform.ToMatrix(CurrentOrigin, CurrentSize) * FlowDirectionTransform); + transform2D = Transform.ToMatrix(CurrentOrigin, CurrentSize) * FlowDirectionTransform; } + + // Convert to 4x4 matrix + var finalMatrix = new Matrix4x4(transform2D); + + // Apply projection if set + if (Owner is UIElement element && element.GetProjection() is Projection projection) + { + var projectionMatrix = projection.GetProjectionMatrix(CurrentSize); + // Projection is applied after RenderTransform + finalMatrix = finalMatrix * projectionMatrix; + } + + Owner.Visual.TransformMatrix = finalMatrix; } partial void Cleanup() diff --git a/src/Uno.UI/UI/Xaml/Media/Matrix3DProjection.cs b/src/Uno.UI/UI/Xaml/Media/Matrix3DProjection.cs new file mode 100644 index 000000000000..ed62b9f52945 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/Matrix3DProjection.cs @@ -0,0 +1,57 @@ +#if __SKIA__ +using System; +using System.Numerics; +using Windows.Foundation; +using Microsoft.UI.Xaml.Media.Media3D; + +namespace Microsoft.UI.Xaml.Media; + +/// +/// Applies a Matrix3D projection to an object. +/// +[Microsoft.UI.Xaml.Markup.ContentProperty(Name = nameof(ProjectionMatrix))] +public partial class Matrix3DProjection : Projection +{ + /// + /// Initializes a new instance of the Matrix3DProjection class. + /// + public Matrix3DProjection() + { + } + + /// + /// Gets or sets the Matrix3D that is used for the projection that is applied to the object. + /// + public Matrix3D ProjectionMatrix + { + get => (Matrix3D)GetValue(ProjectionMatrixProperty); + set => SetValue(ProjectionMatrixProperty, value); + } + + /// + /// Identifies the ProjectionMatrix dependency property. + /// + public static DependencyProperty ProjectionMatrixProperty { get; } = + DependencyProperty.Register( + nameof(ProjectionMatrix), + typeof(Matrix3D), + typeof(Matrix3DProjection), + new FrameworkPropertyMetadata(Matrix3D.Identity, OnProjectionMatrixChanged)); + + private static void OnProjectionMatrixChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is Matrix3DProjection projection) + { + projection.OnPropertyChanged(); + } + } + + /// + /// Returns the projection matrix directly from the ProjectionMatrix property. + /// + internal override Matrix4x4 GetProjectionMatrix(Size elementSize) + { + return ProjectionMatrix.ToMatrix4x4(); + } +} +#endif diff --git a/src/Uno.UI/UI/Xaml/Media/Media3D/Matrix3D.cs b/src/Uno.UI/UI/Xaml/Media/Media3D/Matrix3D.cs index 473a4cfd770c..edbd1cfd21b6 100644 --- a/src/Uno.UI/UI/Xaml/Media/Media3D/Matrix3D.cs +++ b/src/Uno.UI/UI/Xaml/Media/Media3D/Matrix3D.cs @@ -1,63 +1,225 @@ -#region Assembly System.Runtime.WindowsRuntime.UI.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 -// C:\Users\jerome.laban\.nuget\packages\System.Runtime.WindowsRuntime.UI.Xaml\4.0.0\ref\netcore50\System.Runtime.WindowsRuntime.UI.Xaml.dll -#endregion - using System; -using System.Security; +using System.Numerics; + +namespace Microsoft.UI.Xaml.Media.Media3D; -namespace Microsoft.UI.Xaml.Media.Media3D +/// +/// Represents a 4 × 4 matrix that is used for transformations in a three-dimensional (3-D) space. +/// +public partial struct Matrix3D : IFormattable, IEquatable { - // - // Summary: - // Represents a 4 × 4 matrix that is used for transformations in a three-dimensional - // (3-D) space. - [SecurityCritical] - public partial struct Matrix3D : IFormattable + public double M11; + public double M12; + public double M13; + public double M14; + public double M21; + public double M22; + public double M23; + public double M24; + public double M31; + public double M32; + public double M33; + public double M34; + public double OffsetX; + public double OffsetY; + public double OffsetZ; + public double M44; + + /// + /// Initializes a new instance of the Matrix3D structure. + /// + public Matrix3D( + double m11, double m12, double m13, double m14, + double m21, double m22, double m23, double m24, + double m31, double m32, double m33, double m34, + double offsetX, double offsetY, double offsetZ, double m44) { - public Matrix3D(double m11, double m12, double m13, double m14, double m21, double m22, double m23, double m24, double m31, double m32, double m33, double m34, double offsetX, double offsetY, double offsetZ, double m44) { throw new NotImplementedException(); } - - public static Matrix3D Identity { get; } - - public bool HasInverse { get; } - - public bool IsIdentity { get; } - - public double M11; - public double M12; - public double M13; - public double M14; - public double M21; - public double M22; - public double M23; - public double M24; - public double M31; - public double M32; - public double M33; - public double M34; - public double M44; - public double OffsetX; - public double OffsetY; - public double OffsetZ; - - public override bool Equals(object o) { throw new NotImplementedException(); } - - public bool Equals(Matrix3D value) { throw new NotImplementedException(); } + M11 = m11; + M12 = m12; + M13 = m13; + M14 = m14; + M21 = m21; + M22 = m22; + M23 = m23; + M24 = m24; + M31 = m31; + M32 = m32; + M33 = m33; + M34 = m34; + OffsetX = offsetX; + OffsetY = offsetY; + OffsetZ = offsetZ; + M44 = m44; + } - [SecuritySafeCritical] - public override int GetHashCode() { throw new NotImplementedException(); } + /// + /// Gets an identity Matrix3D. + /// + public static Matrix3D Identity => new Matrix3D( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + + /// + /// Gets a value that indicates whether this Matrix3D is an identity matrix. + /// + public bool IsIdentity => + M11 == 1 && M12 == 0 && M13 == 0 && M14 == 0 && + M21 == 0 && M22 == 1 && M23 == 0 && M24 == 0 && + M31 == 0 && M32 == 0 && M33 == 1 && M34 == 0 && + OffsetX == 0 && OffsetY == 0 && OffsetZ == 0 && M44 == 1; + + /// + /// Gets a value that indicates whether this Matrix3D is invertible. + /// + public bool HasInverse => GetDeterminant() != 0; + + /// + /// Inverts this Matrix3D structure. + /// + /// The matrix is not invertible. + public void Invert() + { + var matrix4x4 = ToMatrix4x4(); + if (!Matrix4x4.Invert(matrix4x4, out var inverted)) + { + throw new InvalidOperationException("Matrix is not invertible."); + } + this = FromMatrix4x4(inverted); + } - public void Invert() { throw new NotImplementedException(); } + /// + /// Multiplies two Matrix3D structures. + /// + public static Matrix3D operator *(Matrix3D matrix1, Matrix3D matrix2) + { + return new Matrix3D( + matrix1.M11 * matrix2.M11 + matrix1.M12 * matrix2.M21 + matrix1.M13 * matrix2.M31 + matrix1.M14 * matrix2.OffsetX, + matrix1.M11 * matrix2.M12 + matrix1.M12 * matrix2.M22 + matrix1.M13 * matrix2.M32 + matrix1.M14 * matrix2.OffsetY, + matrix1.M11 * matrix2.M13 + matrix1.M12 * matrix2.M23 + matrix1.M13 * matrix2.M33 + matrix1.M14 * matrix2.OffsetZ, + matrix1.M11 * matrix2.M14 + matrix1.M12 * matrix2.M24 + matrix1.M13 * matrix2.M34 + matrix1.M14 * matrix2.M44, + + matrix1.M21 * matrix2.M11 + matrix1.M22 * matrix2.M21 + matrix1.M23 * matrix2.M31 + matrix1.M24 * matrix2.OffsetX, + matrix1.M21 * matrix2.M12 + matrix1.M22 * matrix2.M22 + matrix1.M23 * matrix2.M32 + matrix1.M24 * matrix2.OffsetY, + matrix1.M21 * matrix2.M13 + matrix1.M22 * matrix2.M23 + matrix1.M23 * matrix2.M33 + matrix1.M24 * matrix2.OffsetZ, + matrix1.M21 * matrix2.M14 + matrix1.M22 * matrix2.M24 + matrix1.M23 * matrix2.M34 + matrix1.M24 * matrix2.M44, + + matrix1.M31 * matrix2.M11 + matrix1.M32 * matrix2.M21 + matrix1.M33 * matrix2.M31 + matrix1.M34 * matrix2.OffsetX, + matrix1.M31 * matrix2.M12 + matrix1.M32 * matrix2.M22 + matrix1.M33 * matrix2.M32 + matrix1.M34 * matrix2.OffsetY, + matrix1.M31 * matrix2.M13 + matrix1.M32 * matrix2.M23 + matrix1.M33 * matrix2.M33 + matrix1.M34 * matrix2.OffsetZ, + matrix1.M31 * matrix2.M14 + matrix1.M32 * matrix2.M24 + matrix1.M33 * matrix2.M34 + matrix1.M34 * matrix2.M44, + + matrix1.OffsetX * matrix2.M11 + matrix1.OffsetY * matrix2.M21 + matrix1.OffsetZ * matrix2.M31 + matrix1.M44 * matrix2.OffsetX, + matrix1.OffsetX * matrix2.M12 + matrix1.OffsetY * matrix2.M22 + matrix1.OffsetZ * matrix2.M32 + matrix1.M44 * matrix2.OffsetY, + matrix1.OffsetX * matrix2.M13 + matrix1.OffsetY * matrix2.M23 + matrix1.OffsetZ * matrix2.M33 + matrix1.M44 * matrix2.OffsetZ, + matrix1.OffsetX * matrix2.M14 + matrix1.OffsetY * matrix2.M24 + matrix1.OffsetZ * matrix2.M34 + matrix1.M44 * matrix2.M44 + ); + } - public override string ToString() { throw new NotImplementedException(); } + /// + /// Compares two Matrix3D instances for equality. + /// + public static bool operator ==(Matrix3D matrix1, Matrix3D matrix2) => matrix1.Equals(matrix2); + + /// + /// Compares two Matrix3D instances for inequality. + /// + public static bool operator !=(Matrix3D matrix1, Matrix3D matrix2) => !matrix1.Equals(matrix2); + + /// + public override bool Equals(object o) => o is Matrix3D matrix && Equals(matrix); + + /// + /// Compares two Matrix3D instances for equality. + /// + public bool Equals(Matrix3D value) => + M11 == value.M11 && M12 == value.M12 && M13 == value.M13 && M14 == value.M14 && + M21 == value.M21 && M22 == value.M22 && M23 == value.M23 && M24 == value.M24 && + M31 == value.M31 && M32 == value.M32 && M33 == value.M33 && M34 == value.M34 && + OffsetX == value.OffsetX && OffsetY == value.OffsetY && OffsetZ == value.OffsetZ && M44 == value.M44; + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(M11); + hash.Add(M12); + hash.Add(M13); + hash.Add(M14); + hash.Add(M21); + hash.Add(M22); + hash.Add(M23); + hash.Add(M24); + hash.Add(M31); + hash.Add(M32); + hash.Add(M33); + hash.Add(M34); + hash.Add(OffsetX); + hash.Add(OffsetY); + hash.Add(OffsetZ); + hash.Add(M44); + return hash.ToHashCode(); + } - public string ToString(IFormatProvider provider) { throw new NotImplementedException(); } + /// + public override string ToString() => ToString(null, null); - public string ToString(string format, IFormatProvider provider) { throw new NotImplementedException(); } + /// + /// Creates a string representation of this Matrix3D. + /// + public string ToString(IFormatProvider provider) => ToString(null, provider); - public static Matrix3D operator *(Matrix3D matrix1, Matrix3D matrix2) { throw new NotImplementedException(); } + /// + /// Creates a string representation of this Matrix3D. + /// + public string ToString(string format, IFormatProvider provider) + { + var separator = ","; + return string.Format(provider, + "{1}{0}{2}{0}{3}{0}{4}{0}{5}{0}{6}{0}{7}{0}{8}{0}{9}{0}{10}{0}{11}{0}{12}{0}{13}{0}{14}{0}{15}{0}{16}", + separator, + M11, M12, M13, M14, + M21, M22, M23, M24, + M31, M32, M33, M34, + OffsetX, OffsetY, OffsetZ, M44); + } - public static bool operator ==(Matrix3D matrix1, Matrix3D matrix2) { throw new NotImplementedException(); } + /// + /// Converts this Matrix3D to a System.Numerics.Matrix4x4. + /// + internal Matrix4x4 ToMatrix4x4() => new Matrix4x4( + (float)M11, (float)M12, (float)M13, (float)M14, + (float)M21, (float)M22, (float)M23, (float)M24, + (float)M31, (float)M32, (float)M33, (float)M34, + (float)OffsetX, (float)OffsetY, (float)OffsetZ, (float)M44); + + /// + /// Creates a Matrix3D from a System.Numerics.Matrix4x4. + /// + internal static Matrix3D FromMatrix4x4(Matrix4x4 matrix) => new Matrix3D( + matrix.M11, matrix.M12, matrix.M13, matrix.M14, + matrix.M21, matrix.M22, matrix.M23, matrix.M24, + matrix.M31, matrix.M32, matrix.M33, matrix.M34, + matrix.M41, matrix.M42, matrix.M43, matrix.M44); + + private double GetDeterminant() + { + // Using cofactor expansion along the first row + double a = M11 * GetMinor3x3(M22, M23, M24, M32, M33, M34, OffsetY, OffsetZ, M44); + double b = M12 * GetMinor3x3(M21, M23, M24, M31, M33, M34, OffsetX, OffsetZ, M44); + double c = M13 * GetMinor3x3(M21, M22, M24, M31, M32, M34, OffsetX, OffsetY, M44); + double d = M14 * GetMinor3x3(M21, M22, M23, M31, M32, M33, OffsetX, OffsetY, OffsetZ); + return a - b + c - d; + } - public static bool operator !=(Matrix3D matrix1, Matrix3D matrix2) { throw new NotImplementedException(); } + private static double GetMinor3x3( + double m11, double m12, double m13, + double m21, double m22, double m23, + double m31, double m32, double m33) + { + return m11 * (m22 * m33 - m23 * m32) + - m12 * (m21 * m33 - m23 * m31) + + m13 * (m21 * m32 - m22 * m31); } } diff --git a/src/Uno.UI/UI/Xaml/Media/PlaneProjection.cs b/src/Uno.UI/UI/Xaml/Media/PlaneProjection.cs new file mode 100644 index 000000000000..44a8f985dfe0 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/PlaneProjection.cs @@ -0,0 +1,373 @@ +#if __SKIA__ +using System; +using System.Numerics; +using Windows.Foundation; +using Microsoft.UI.Xaml.Media.Media3D; + +namespace Microsoft.UI.Xaml.Media; + +/// +/// Represents a perspective transform (a 3-D-like effect) on an object. +/// +public partial class PlaneProjection : Projection +{ + // Constants from WinUI PlaneProjection.h + private const bool RightHanded = true; + private const float NearPlane = 1.0f; + private const float FarPlane = 1001.0f; + private const float FieldOfView = 57.0f; // degrees + private const float ZOffset = -999.0f; // for right-handed coordinate system + + /// + /// Initializes a new instance of the PlaneProjection class. + /// + public PlaneProjection() + { + } + + #region Dependency Properties + + /// + /// Gets or sets the x-coordinate of the center of rotation of the object you rotate. + /// + public double CenterOfRotationX + { + get => (double)GetValue(CenterOfRotationXProperty); + set => SetValue(CenterOfRotationXProperty, value); + } + + /// + /// Identifies the CenterOfRotationX dependency property. + /// + public static DependencyProperty CenterOfRotationXProperty { get; } = + DependencyProperty.Register( + nameof(CenterOfRotationX), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.5, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the y-coordinate of the center of rotation of the object you rotate. + /// + public double CenterOfRotationY + { + get => (double)GetValue(CenterOfRotationYProperty); + set => SetValue(CenterOfRotationYProperty, value); + } + + /// + /// Identifies the CenterOfRotationY dependency property. + /// + public static DependencyProperty CenterOfRotationYProperty { get; } = + DependencyProperty.Register( + nameof(CenterOfRotationY), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.5, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the z-coordinate of the center of rotation of the object you rotate. + /// + public double CenterOfRotationZ + { + get => (double)GetValue(CenterOfRotationZProperty); + set => SetValue(CenterOfRotationZProperty, value); + } + + /// + /// Identifies the CenterOfRotationZ dependency property. + /// + public static DependencyProperty CenterOfRotationZProperty { get; } = + DependencyProperty.Register( + nameof(CenterOfRotationZ), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the distance that the object is rotated along the x-axis of the plane of the object. + /// + public double RotationX + { + get => (double)GetValue(RotationXProperty); + set => SetValue(RotationXProperty, value); + } + + /// + /// Identifies the RotationX dependency property. + /// + public static DependencyProperty RotationXProperty { get; } = + DependencyProperty.Register( + nameof(RotationX), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the number of degrees to rotate the object around the y-axis of rotation. + /// + public double RotationY + { + get => (double)GetValue(RotationYProperty); + set => SetValue(RotationYProperty, value); + } + + /// + /// Identifies the RotationY dependency property. + /// + public static DependencyProperty RotationYProperty { get; } = + DependencyProperty.Register( + nameof(RotationY), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the number of degrees to rotate the object around the z-axis of rotation. + /// + public double RotationZ + { + get => (double)GetValue(RotationZProperty); + set => SetValue(RotationZProperty, value); + } + + /// + /// Identifies the RotationZ dependency property. + /// + public static DependencyProperty RotationZProperty { get; } = + DependencyProperty.Register( + nameof(RotationZ), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the distance the object is translated along the x-axis of the plane of the object. + /// + public double LocalOffsetX + { + get => (double)GetValue(LocalOffsetXProperty); + set => SetValue(LocalOffsetXProperty, value); + } + + /// + /// Identifies the LocalOffsetX dependency property. + /// + public static DependencyProperty LocalOffsetXProperty { get; } = + DependencyProperty.Register( + nameof(LocalOffsetX), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the distance the object is translated along the y-axis of the plane of the object. + /// + public double LocalOffsetY + { + get => (double)GetValue(LocalOffsetYProperty); + set => SetValue(LocalOffsetYProperty, value); + } + + /// + /// Identifies the LocalOffsetY dependency property. + /// + public static DependencyProperty LocalOffsetYProperty { get; } = + DependencyProperty.Register( + nameof(LocalOffsetY), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the distance the object is translated along the z-axis of the plane of the object. + /// + public double LocalOffsetZ + { + get => (double)GetValue(LocalOffsetZProperty); + set => SetValue(LocalOffsetZProperty, value); + } + + /// + /// Identifies the LocalOffsetZ dependency property. + /// + public static DependencyProperty LocalOffsetZProperty { get; } = + DependencyProperty.Register( + nameof(LocalOffsetZ), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the distance the object is translated along the x-axis of the screen. + /// + public double GlobalOffsetX + { + get => (double)GetValue(GlobalOffsetXProperty); + set => SetValue(GlobalOffsetXProperty, value); + } + + /// + /// Identifies the GlobalOffsetX dependency property. + /// + public static DependencyProperty GlobalOffsetXProperty { get; } = + DependencyProperty.Register( + nameof(GlobalOffsetX), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the distance the object is translated along the y-axis of the screen. + /// + public double GlobalOffsetY + { + get => (double)GetValue(GlobalOffsetYProperty); + set => SetValue(GlobalOffsetYProperty, value); + } + + /// + /// Identifies the GlobalOffsetY dependency property. + /// + public static DependencyProperty GlobalOffsetYProperty { get; } = + DependencyProperty.Register( + nameof(GlobalOffsetY), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets or sets the distance the object is translated along the z-axis of the screen. + /// + public double GlobalOffsetZ + { + get => (double)GetValue(GlobalOffsetZProperty); + set => SetValue(GlobalOffsetZProperty, value); + } + + /// + /// Identifies the GlobalOffsetZ dependency property. + /// + public static DependencyProperty GlobalOffsetZProperty { get; } = + DependencyProperty.Register( + nameof(GlobalOffsetZ), + typeof(double), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(0.0, OnProjectionPropertyChanged)); + + /// + /// Gets the projection matrix that represents this PlaneProjection. + /// + public Matrix3D ProjectionMatrix + { + get => (Matrix3D)GetValue(ProjectionMatrixProperty); + private set => SetValue(ProjectionMatrixProperty, value); + } + + /// + /// Identifies the ProjectionMatrix dependency property. + /// + public static DependencyProperty ProjectionMatrixProperty { get; } = + DependencyProperty.Register( + nameof(ProjectionMatrix), + typeof(Matrix3D), + typeof(PlaneProjection), + new FrameworkPropertyMetadata(Matrix3D.Identity)); + + private static void OnProjectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PlaneProjection projection) + { + projection.OnPropertyChanged(); + } + } + + #endregion + + /// + /// Calculates the projection matrix for the specified element size. + /// + /// + /// Matrix composition order (ported from WinUI PlaneProjection.cpp): + /// [Centering] × [LocalOffset] × [RotateCenter] × [RotateX] × [RotateY] × [RotateZ] × + /// [UndoRotateCenter] × [GlobalOffset] × [ZOffset] × [Perspective] × [UndoCentering] + /// + internal override Matrix4x4 GetProjectionMatrix(Size elementSize) + { + float width = (float)elementSize.Width; + float height = (float)elementSize.Height; + + // Calculate the absolute center of rotation from the relative values + // CenterOfRotationX/Y are relative (0.0 to 1.0), CenterOfRotationZ is absolute + float centerX = (float)(CenterOfRotationX * width); + float centerY = (float)(CenterOfRotationY * height); + float centerZ = (float)CenterOfRotationZ; + + // Convert degrees to radians + float rotX = (float)(RotationX * Math.PI / 180.0); + float rotY = (float)(RotationY * Math.PI / 180.0); + float rotZ = (float)(RotationZ * Math.PI / 180.0); + + // For right-handed coordinate system, invert Y rotation + if (RightHanded) + { + rotY = -rotY; + } + + // 1. Move element center to origin + var centering = Matrix4x4.CreateTranslation(-width / 2, -height / 2, 0); + + // 2. Apply local offset (in object space, before rotation) + var localOffset = Matrix4x4.CreateTranslation( + (float)LocalOffsetX, + (float)-LocalOffsetY, // Y is inverted in the visual coordinate system + (float)LocalOffsetZ); + + // 3. Move to rotation center + var rotateCenter = Matrix4x4.CreateTranslation(-centerX + width / 2, -centerY + height / 2, -centerZ); + + // 4. Apply rotations (X, then Y, then Z) + var rotationX = Matrix4x4.CreateRotationX(rotX); + var rotationY = Matrix4x4.CreateRotationY(rotY); + var rotationZ = Matrix4x4.CreateRotationZ(rotZ); + + // 5. Undo rotation center translation + var undoRotateCenter = Matrix4x4.CreateTranslation(centerX - width / 2, centerY - height / 2, centerZ); + + // 6. Apply global offset (in screen space, after rotation) + var globalOffset = Matrix4x4.CreateTranslation( + (float)GlobalOffsetX, + (float)-GlobalOffsetY, // Y is inverted in the visual coordinate system + (float)GlobalOffsetZ); + + // 7. Apply Z offset for perspective (moves away from camera) + var zOffset = Matrix4x4.CreateTranslation(0, 0, ZOffset); + + // 8. Apply perspective projection + // Based on WinUI's field of view calculation + float fovRadians = FieldOfView * (float)Math.PI / 180.0f; + float cotFov = 1.0f / (float)Math.Tan(fovRadians / 2.0f); + + var perspective = new Matrix4x4( + cotFov, 0, 0, 0, + 0, cotFov, 0, 0, + 0, 0, FarPlane / (FarPlane - NearPlane), 1, + 0, 0, -NearPlane * FarPlane / (FarPlane - NearPlane), 0); + + // 9. Undo Z offset and centering + var undoZOffset = Matrix4x4.CreateTranslation(0, 0, -ZOffset); + var undoCentering = Matrix4x4.CreateTranslation(width / 2, height / 2, 0); + + // Compose all matrices + var result = centering * localOffset * rotateCenter * + rotationX * rotationY * rotationZ * + undoRotateCenter * globalOffset * + zOffset * perspective * undoZOffset * undoCentering; + + // Update the ProjectionMatrix property + ProjectionMatrix = Matrix3D.FromMatrix4x4(result); + + return result; + } +} +#endif diff --git a/src/Uno.UI/UI/Xaml/Media/Projection.cs b/src/Uno.UI/UI/Xaml/Media/Projection.cs new file mode 100644 index 000000000000..9f858344d3ea --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/Projection.cs @@ -0,0 +1,51 @@ +#if __SKIA__ +using System; +using System.Numerics; +using Windows.Foundation; + +namespace Microsoft.UI.Xaml.Media; + +/// +/// Provides a base class for projections, which describe how to transform an object in 3-D space using perspective transforms. +/// +public partial class Projection : DependencyObject +{ + private WeakReference _owner; + + /// + /// Initializes a new instance of the Projection class. + /// + protected Projection() + { + } + + /// + /// Event raised when any property affecting the projection changes. + /// + internal event EventHandler Changed; + + /// + /// Gets or sets the UIElement that owns this projection. + /// + internal UIElement Owner + { + get => _owner?.TryGetTarget(out var target) == true ? target : null; + set => _owner = value is not null ? new WeakReference(value) : null; + } + + /// + /// Calculates the projection matrix for the specified element size. + /// + /// The size of the element being projected. + /// The 4x4 projection matrix. + internal virtual Matrix4x4 GetProjectionMatrix(Size elementSize) => Matrix4x4.Identity; + + /// + /// Raises the Changed event. + /// + protected void OnPropertyChanged() + { + Changed?.Invoke(this, EventArgs.Empty); + } +} +#endif diff --git a/src/Uno.UI/UI/Xaml/UIElement.cs b/src/Uno.UI/UI/Xaml/UIElement.cs index 95adc31acbbc..6eb81f331f50 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.cs @@ -481,6 +481,74 @@ private void OnRenderTransformOriginChanged(Point _, Point origin) => _renderTransform?.UpdateOrigin(origin); #endregion +#if __SKIA__ + #region Projection Dependency Property + + private Media.Projection _projection; + + /// + /// Gets or sets the perspective projection (3-D effect) to apply when rendering this element. + /// + public Media.Projection Projection + { + get => GetProjectionValue(); + set => SetProjectionValue(value); + } + + /// + /// Backing dependency property for + /// + [GeneratedDependencyProperty(DefaultValue = null, ChangedCallback = true)] + public static DependencyProperty ProjectionProperty { get; } = CreateProjectionProperty(); + + private void OnProjectionChanged(Media.Projection oldValue, Media.Projection newValue) + { + if (oldValue is not null) + { + oldValue.Changed -= OnProjectionPropertyChanged; + oldValue.Owner = null; + } + + _projection = newValue; + + if (newValue is not null) + { + newValue.Owner = this; + newValue.Changed += OnProjectionPropertyChanged; + } + + // Update the visual transform + UpdateProjection(); + } + + private void OnProjectionPropertyChanged(object sender, EventArgs e) + { + UpdateProjection(); + } + + private void UpdateProjection() + { + // Trigger a transform update through the render transform adapter + // The adapter will combine RenderTransform with Projection + if (_renderTransform is not null) + { + _renderTransform.UpdateSize(_renderTransform.CurrentSize); + } + else if (_projection is not null) + { + // Create a minimal adapter to apply the projection + _renderTransform = new Uno.UI.Media.NativeRenderTransformAdapter(this, RenderTransform, RenderTransformOrigin); + } + } + + /// + /// Gets the current projection for internal use. + /// + internal Media.Projection GetProjection() => _projection; + + #endregion +#endif + /// /// Attempts to set the focus on the UIElement. /// From 3852c1626c5883832cf82465f6c8c0f53bc11ed9 Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Thu, 22 Jan 2026 17:16:45 +0100 Subject: [PATCH 02/11] test: Add runtime tests and SamplesApp samples for Projection Add tests and samples for the PlaneProjection and Matrix3DProjection implementation: - Runtime tests for Matrix3D struct operations - Runtime tests for PlaneProjection properties and element integration - Runtime tests for Matrix3DProjection - SamplesApp sample for PlaneProjection with interactive controls - SamplesApp sample for Matrix3DProjection with custom matrices Co-Authored-By: Claude Opus 4.5 --- .../Projection/Matrix3DProjection_Basic.xaml | 98 +++++++ .../Matrix3DProjection_Basic.xaml.cs | 84 ++++++ .../Projection/PlaneProjection_Basic.xaml | 219 +++++++++++++++ .../Projection/PlaneProjection_Basic.xaml.cs | 13 + .../Windows_UI_Xaml_Media/Given_Matrix3D.cs | 250 ++++++++++++++++++ .../Given_Matrix3DProjection.cs | 172 ++++++++++++ .../Given_PlaneProjection.cs | 220 +++++++++++++++ 7 files changed, 1056 insertions(+) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media/Projection/Matrix3DProjection_Basic.xaml create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media/Projection/Matrix3DProjection_Basic.xaml.cs create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media/Projection/PlaneProjection_Basic.xaml create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media/Projection/PlaneProjection_Basic.xaml.cs create mode 100644 src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media/Given_Matrix3D.cs create mode 100644 src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media/Given_Matrix3DProjection.cs create mode 100644 src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media/Given_PlaneProjection.cs diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media/Projection/Matrix3DProjection_Basic.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media/Projection/Matrix3DProjection_Basic.xaml new file mode 100644 index 000000000000..fc937c746c41 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Media/Projection/Matrix3DProjection_Basic.xaml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + +