From 8309bc72d044b6313593f2649082db0d620c20eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=C4=9Bzslav=20Imr=C3=BD=C5=A1ek?= Date: Fri, 30 Jul 2021 14:16:18 +0200 Subject: [PATCH] feat: Added CompositionRectangleGeometry implementation --- .../CompositionRectangleGeometry.cs | 6 +- .../UI/Composition/CompositionGeometry.cs | 9 +- .../Composition/CompositionGeometry.skia.cs | 152 ++++++++++++++++++ .../UI/Composition/CompositionPathGeometry.cs | 12 +- .../CompositionRectangleGeometry.cs | 29 ++++ .../CompositionRectangleGeometry.skia.cs | 13 ++ .../Composition/SkiaGeometrySource2D.skia.cs | 16 +- 7 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 src/Uno.UWP/UI/Composition/CompositionGeometry.skia.cs create mode 100644 src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.cs create mode 100644 src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.skia.cs diff --git a/src/Uno.UWP/Generated/3.0.0.0/Windows.UI.Composition/CompositionRectangleGeometry.cs b/src/Uno.UWP/Generated/3.0.0.0/Windows.UI.Composition/CompositionRectangleGeometry.cs index b62b80257fdb..17917fed3276 100644 --- a/src/Uno.UWP/Generated/3.0.0.0/Windows.UI.Composition/CompositionRectangleGeometry.cs +++ b/src/Uno.UWP/Generated/3.0.0.0/Windows.UI.Composition/CompositionRectangleGeometry.cs @@ -2,12 +2,12 @@ #pragma warning disable 114 // new keyword hiding namespace Windows.UI.Composition { - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + #if false [global::Uno.NotImplemented] #endif public partial class CompositionRectangleGeometry : global::Windows.UI.Composition.CompositionGeometry { - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + #if false [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public global::System.Numerics.Vector2 Size { @@ -21,7 +21,7 @@ public partial class CompositionRectangleGeometry : global::Windows.UI.Composit } } #endif - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ + #if false [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public global::System.Numerics.Vector2 Offset { diff --git a/src/Uno.UWP/UI/Composition/CompositionGeometry.cs b/src/Uno.UWP/UI/Composition/CompositionGeometry.cs index 23148ed0bb86..dd5d49459703 100644 --- a/src/Uno.UWP/UI/Composition/CompositionGeometry.cs +++ b/src/Uno.UWP/UI/Composition/CompositionGeometry.cs @@ -1,5 +1,7 @@ #nullable enable +using Windows.Graphics; + namespace Windows.UI.Composition { public partial class CompositionGeometry : CompositionObject @@ -9,15 +11,12 @@ internal CompositionGeometry(Compositor compositor) : base(compositor) } - internal CompositionGeometry() - { - - } - public float TrimStart { get; set; } public float TrimOffset { get; set; } public float TrimEnd { get; set; } + + internal virtual IGeometrySource2D? BuildGeometry() => null; } } diff --git a/src/Uno.UWP/UI/Composition/CompositionGeometry.skia.cs b/src/Uno.UWP/UI/Composition/CompositionGeometry.skia.cs new file mode 100644 index 000000000000..b71ceaca017a --- /dev/null +++ b/src/Uno.UWP/UI/Composition/CompositionGeometry.skia.cs @@ -0,0 +1,152 @@ +#nullable enable + +using System; +using System.Numerics; +using SkiaSharp; + +namespace Windows.UI.Composition +{ + public partial class CompositionGeometry : CompositionObject + { + /// + /// Kappa = (sqrt(2) - 1) * 4/3; + // Used to calculate bezier control points for each of the circle four arcs. + // - Approximating a 1/4 circle with a bezier curve. + /// + private const double CIRCLE_BEZIER_KAPPA = 0.552284749830793398402251632279597438092895833835930764235; + + internal static SKPath BuildLineGeometry(Vector2 start, Vector2 end) + { + SKPath path = new SKPath(); + + path.MoveTo(start.ToSKPoint()); + path.LineTo(end.ToSKPoint()); + + return path; + } + + internal static SKPath BuildRectangleGeometry(Vector2 offset, Vector2 size) + { + SKPath path = new SKPath(); + + // Top left + path.MoveTo(new SKPoint(offset.X, offset.Y)); + // Top right + path.RLineTo(new SKPoint(size.X, 0)); + // Bottom right + path.RLineTo(new SKPoint(0, size.Y)); + // Bottom left + path.RLineTo(new SKPoint(-size.X, 0)); + // Top left + path.Close(); + + return path; + } + + internal static SKPath BuildRoundedRectangleGeometry(Vector2 offset, Vector2 size, Vector2 cornerRadius) + { + float radiusX = Clamp(cornerRadius.X, 0, size.X * 0.5f); + float radiusY = Clamp(cornerRadius.Y, 0, size.Y * 0.5f); + + float bezierX = (float)((1.0 - CIRCLE_BEZIER_KAPPA) * radiusX); + float bezierY = (float)((1.0 - CIRCLE_BEZIER_KAPPA) * radiusY); + + SKPath path = new SKPath(); + var lastPoint = new SKPoint(offset.X + radiusX, offset.Y); + + path.MoveTo(lastPoint); + // Top line + path.LineTo(lastPoint + new SKPoint(size.X - 2 * radiusX, 0)); + lastPoint += new SKPoint(size.X - 2 * radiusX, 0); + // Top-right Arc + path.CubicTo( + lastPoint + new SKPoint(radiusX - bezierX, 0), // 1st control point + lastPoint + new SKPoint(radiusX, bezierY), // 2nd control point + lastPoint + new SKPoint(radiusX, radiusY)); // End point + lastPoint += new SKPoint(radiusX, radiusY); + + // Right line + path.LineTo(lastPoint + new SKPoint(0, size.Y - 2 * radiusY)); + lastPoint += new SKPoint(0, size.Y - 2 * radiusY); + // Bottom-right Arc + path.CubicTo( + lastPoint + new SKPoint(0, bezierY), // 1st control point + lastPoint + new SKPoint(-bezierX, radiusY), // 2nd control point + lastPoint + new SKPoint(-radiusX, radiusY)); // End point + lastPoint += new SKPoint(-radiusX, radiusY); + + // Bottom line + path.LineTo(lastPoint + new SKPoint(-(size.X - 2 * radiusX), 0)); + lastPoint = lastPoint + new SKPoint(-(size.X - 2 * radiusX), 0); + // Bottom-left Arc + path.CubicTo( + lastPoint + new SKPoint(-radiusX + bezierX, 0), // 1st control point + lastPoint + new SKPoint(-radiusX, -bezierY), // 2nd control point + lastPoint + new SKPoint(-radiusX, -radiusY)); // End point + lastPoint += new SKPoint(-radiusX, -radiusY); + + // Left line + path.LineTo(lastPoint + new SKPoint(0, -(size.Y - 2 * radiusY))); + lastPoint += new SKPoint(0, -(size.Y - 2 * radiusY)); + // Top-left Arc + path.CubicTo( + lastPoint + new SKPoint(0, -radiusY + bezierY), // 1st control point + lastPoint + new SKPoint(bezierX, -radiusY), // 2nd control point + lastPoint + new SKPoint(radiusX, -radiusY)); // End point + + path.Close(); + + return path; + } + + internal static SKPath BuildEllipseGeometry(Vector2 center, Vector2 radius) + { + SKRect rect = SKRect.Create(center.X - radius.X, center.Y - radius.Y, radius.X * 2, radius.Y * 2); + + float bezierX = (float)((1.0 - CIRCLE_BEZIER_KAPPA) * radius.X); + float bezierY = (float)((1.0 - CIRCLE_BEZIER_KAPPA) * radius.Y); + + // IMPORTANT: + // - The order of following operations is important for dashed strokes. + // - Stroke might get merged in the end. + // - WPF starts with bottom right ellipse arc. + // - TODO: Verify UWP behavior + + SKPath path = new SKPath(); + + path.MoveTo(new SKPoint(rect.Right, rect.Top + radius.Y)); + // Bottom-right Arc + path.CubicTo( + new SKPoint(rect.Right, rect.Bottom - bezierY), // 1st control point + new SKPoint(rect.Right - bezierX, rect.Bottom), // 2nd control point + new SKPoint(rect.Right - radius.X, rect.Bottom)); // End point + + // Bottom-left Arc + path.CubicTo( + new SKPoint(rect.Left + bezierX, rect.Bottom), // 1st control point + new SKPoint(rect.Left, rect.Bottom - bezierY), // 2nd control point + new SKPoint(rect.Left, rect.Bottom - radius.Y)); // End point + + // Top-left Arc + path.CubicTo( + new SKPoint(rect.Left, rect.Top + bezierY), // 1st control point + new SKPoint(rect.Left + bezierX, rect.Top), // 2nd control point + new SKPoint(rect.Left + radius.X, rect.Top)); // End point + + // Top-right Arc + path.CubicTo( + new SKPoint(rect.Right - bezierX, rect.Top), // 1st control point + new SKPoint(rect.Right, rect.Top + bezierY), // 2nd control point + new SKPoint(rect.Right, rect.Top + radius.Y)); // End point + + path.Close(); + + return path; + } + + private static float Clamp(float value, float minValue, float maxValue) + { + return Math.Min(Math.Max(Math.Abs(value), minValue), maxValue); + } + } +} diff --git a/src/Uno.UWP/UI/Composition/CompositionPathGeometry.cs b/src/Uno.UWP/UI/Composition/CompositionPathGeometry.cs index ddf07bf72d9f..e60911ad7c4e 100644 --- a/src/Uno.UWP/UI/Composition/CompositionPathGeometry.cs +++ b/src/Uno.UWP/UI/Composition/CompositionPathGeometry.cs @@ -1,14 +1,24 @@ #nullable enable +using Windows.Graphics; + namespace Windows.UI.Composition { public partial class CompositionPathGeometry : CompositionGeometry { + private CompositionPath? _path; + internal CompositionPathGeometry(Compositor compositor, CompositionPath? path = null) : base(compositor) { Path = path; } - public CompositionPath? Path { get; set; } + public CompositionPath? Path + { + get => _path; + set => SetProperty(ref _path, value); + } + + internal override IGeometrySource2D? BuildGeometry() => Path?.GeometrySource; } } diff --git a/src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.cs b/src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.cs new file mode 100644 index 000000000000..e2fd1590d84a --- /dev/null +++ b/src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.cs @@ -0,0 +1,29 @@ +#nullable enable + +using System.Numerics; + +namespace Windows.UI.Composition +{ + public partial class CompositionRectangleGeometry : CompositionGeometry + { + private Vector2 _size; + private Vector2 _offset; + + internal CompositionRectangleGeometry(Compositor compositor) : base(compositor) + { + + } + + public Vector2 Size + { + get => _size; + set => SetProperty(ref _size, value); + } + + public Vector2 Offset + { + get => _offset; + set => SetProperty(ref _offset, value); + } + } +} diff --git a/src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.skia.cs b/src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.skia.cs new file mode 100644 index 000000000000..79a3c7768bf0 --- /dev/null +++ b/src/Uno.UWP/UI/Composition/CompositionRectangleGeometry.skia.cs @@ -0,0 +1,13 @@ +#nullable enable + +using SkiaSharp; +using Windows.Graphics; + +namespace Windows.UI.Composition +{ + public partial class CompositionRectangleGeometry : CompositionGeometry + { + internal override IGeometrySource2D? BuildGeometry() + => new SkiaGeometrySource2D(BuildRectangleGeometry(Offset, Size)); + } +} diff --git a/src/Uno.UWP/UI/Composition/SkiaGeometrySource2D.skia.cs b/src/Uno.UWP/UI/Composition/SkiaGeometrySource2D.skia.cs index 33a1822f7d20..6459714b66e9 100644 --- a/src/Uno.UWP/UI/Composition/SkiaGeometrySource2D.skia.cs +++ b/src/Uno.UWP/UI/Composition/SkiaGeometrySource2D.skia.cs @@ -2,19 +2,27 @@ using SkiaSharp; using System; -using System.Collections.Generic; -using System.Text; using Windows.Graphics; namespace Windows.UI.Composition { public class SkiaGeometrySource2D : IGeometrySource2D { - public SkiaGeometrySource2D(SKPath? source = null) + public SkiaGeometrySource2D() { Geometry = new SKPath(); } - public SKPath? Geometry { get; } + public SkiaGeometrySource2D(SKPath source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + Geometry = source; + } + + public SKPath Geometry { get; } } }