Browse Source

feat(Geometries): PolyBezierSegment (#16664)

* feat(Geometries): PolyBezierSegment

* fix: warning

* add PolyBezierSegment to CrossUI Test

* test: AddPolyBezierSegment CrossUI test
workgroupengineering 1 year ago
parent
commit
6e35fd2bbe

+ 90 - 0
src/Avalonia.Base/Media/PolyBezierSegment.cs

@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Utilities;
+
+namespace Avalonia.Media;
+
+/// <summary>
+/// PolyBezierSegment
+/// </summary>
+public sealed class PolyBezierSegment : PathSegment
+{
+    /// <summary>
+    /// Points DirectProperty definition
+    /// </summary>
+    public static readonly DirectProperty<PolyBezierSegment, Points?> PointsProperty =
+        AvaloniaProperty.RegisterDirect<PolyBezierSegment, Points?>(nameof(Points),
+            o => o.Points,
+            (o, v) => o.Points = v);
+
+    private Points? _points = [];
+
+    public PolyBezierSegment()
+    {
+
+    }
+
+    public PolyBezierSegment(IEnumerable<Point> points, bool isStroked)
+    {
+        if (points is null)
+        {
+            throw new ArgumentNullException(nameof(points));
+        }
+
+        Points = new Points(points);
+        IsStroked = isStroked;
+    }
+
+    /// <summary>
+    /// Gets or sets the Point collection that defines this <see cref="PolyBezierSegment"/> object.
+    /// </summary>
+    /// <value>
+    /// The points.
+    /// </value>
+    [Metadata.Content]
+    public Points? Points
+    {
+        get => _points;
+        set => SetAndRaise(PointsProperty, ref _points, value);
+    }
+
+    internal override void ApplyTo(StreamGeometryContext ctx)
+    {
+        var isStroken = this.IsStroked;
+        if (_points is { Count: > 0 } points)
+        {
+            var i = 0;
+            for (; i < points.Count; i += 3)
+            {
+                ctx.CubicBezierTo(points[i],
+                    points[i + 1],
+                    points[i + 2],
+                    isStroken);
+            }
+            var delta = i - points.Count;
+            if (delta != 0)
+            {
+                Logging.Logger.TryGet(Logging.LogEventLevel.Warning,
+                    Logging.LogArea.Visual)
+                    ?.Log(nameof(PolyBezierSegment),
+                        $"{nameof(PolyBezierSegment)} has ivalid number of points. Last {Math.Abs(delta)} points will be ignored.");
+            }
+        }
+    }
+
+    public override string ToString()
+    {
+        var builder = StringBuilderCache.Acquire();
+        if (_points is { Count: > 0 } points)
+        {
+            builder.Append('C').Append(' ');
+            foreach (var point in _points)
+            {
+                builder.Append(FormattableString.Invariant($"{point}"));
+                builder.Append(' ');
+            }
+            builder.Length = builder.Length - 1;
+        }
+        return StringBuilderCache.GetStringAndRelease(builder);
+    }
+}

+ 12 - 10
src/Avalonia.Base/Points.cs

@@ -1,18 +1,20 @@
 using System.Collections.Generic;
 using Avalonia.Collections;
 
-namespace Avalonia
+namespace Avalonia;
+
+/// <summary>
+/// Represents a collection of <see cref="Point"/> values that can be individually accessed by index.
+/// </summary>
+public sealed class Points : AvaloniaList<Point>
 {
-    public sealed class Points : AvaloniaList<Point>
+    public Points()
     {
-        public Points()
-        {
-            
-        }
+        
+    }
 
-        public Points(IEnumerable<Point> points) : base(points)
-        {
-            
-        }
+    public Points(IEnumerable<Point> points) : base(points)
+    {
+        
     }
 }

+ 1 - 0
tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs

@@ -191,6 +191,7 @@ namespace Avalonia.RenderTests.WpfCompare
                                 CrossPathSegment.CubicBezier cubicBezier => new BezierSegment(cubicBezier.Point1.ToWpf(), cubicBezier.Point2.ToWpf(), cubicBezier.Point3.ToWpf(), cubicBezier.IsStroked),
                                 CrossPathSegment.QuadraticBezier quadraticBezier => new QuadraticBezierSegment(quadraticBezier.Point1.ToWpf(), quadraticBezier.Point2.ToWpf(), quadraticBezier.IsStroked),
                                 CrossPathSegment.PolyLine polyLine => new PolyLineSegment(polyLine.Points.Select(p => p.ToWpf()).ToList(), polyLine.IsStroked),
+                                CrossPathSegment.PolyBezierSegment pb => new PolyBezierSegment(pb.Points.Select(p => p.ToWpf()), pb.IsStroked),
                                 _ => throw new NotImplementedException(),
                             }), f.Closed)))
                 };

+ 30 - 0
tests/Avalonia.RenderTests/CrossTests/CrossGeometryTests.cs

@@ -141,6 +141,36 @@ public class CrossGeometryTests : CrossTestBase
             $"{nameof(Should_Render_PolyLineSegment_With_Strokeless_Lines)}");
     }
 
+    [CrossFact]
+    public void Should_Render_PolyBezierSegment_With_Strokeless_Lines()
+    {
+        var brush = new CrossSolidColorBrush(Colors.Blue);
+        var pen = new CrossPen()
+        {
+            Brush = new CrossSolidColorBrush(Colors.Red),
+            Thickness = 8
+        };
+        var figure = new CrossPathFigure()
+        {
+            Start = new Point(10, 100),
+            Closed = false,
+            Segments =
+            {
+                new CrossPathSegment.PolyBezierSegment([new(0, 0), new(200, 0), new(300, 100), new(300, 0), new(500, 0), new(600,100)], false)
+            }
+        };
+        var geometry = new CrossPathGeometry { Figures = { figure } };
+
+        var control = new CrossFuncControl(ctx => ctx.DrawGeometry(brush, pen, geometry))
+        {
+            Width = 700,
+            Height = 400,
+        };
+
+        RenderAndCompare(control,
+            $"{nameof(Should_Render_PolyBezierSegment_With_Strokeless_Lines)}");
+    }
+
     // Skip the test for now
 #if !AVALONIA_SKIA
     [CrossTheory,

+ 61 - 54
tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs

@@ -156,61 +156,68 @@ namespace Avalonia.Direct2D1.RenderTests.CrossUI
 
         static Geometry ConvertGeometry(CrossGeometry g)
         {
-            if (g is CrossRectangleGeometry rg)
-                return new RectangleGeometry(rg.Rect);
-            else if (g is CrossSvgGeometry svg)
-                return PathGeometry.Parse(svg.Path);
-            else if (g is CrossEllipseGeometry ellipse)
-                return new EllipseGeometry(ellipse.Rect);
-            else if(g is CrossStreamGeometry streamGeometry)
-                return (StreamGeometry)streamGeometry.GetContext().GetGeometry();
-            else if (g is CrossPathGeometry path)
-                return new PathGeometry()
-                {
-                    Figures = RetAddRange(new PathFigures(), path.Figures.Select(f =>
-                        new PathFigure()
-                        {
-                            StartPoint = f.Start,
-                            IsClosed = f.Closed,
-                            Segments = RetAddRange<PathSegments, PathSegment>(new PathSegments(), f.Segments.Select<CrossPathSegment, PathSegment>(s =>
-                                s switch
-                                {
-                                    CrossPathSegment.Line l => new LineSegment()
-                                    {
-                                        Point = l.To, IsStroked = l.IsStroked
-                                    },
-                                    CrossPathSegment.Arc a => new ArcSegment()
-                                    {
-                                        Point = a.Point,
-                                        RotationAngle = a.RotationAngle,
-                                        Size = a.Size,
-                                        IsLargeArc = a.IsLargeArc,
-                                        SweepDirection = a.SweepDirection,
-                                        IsStroked = a.IsStroked
-                                    },
-                                    CrossPathSegment.CubicBezier c => new BezierSegment()
-                                    {
-                                        Point1 = c.Point1,
-                                        Point2 = c.Point2,
-                                        Point3 = c.Point3,
-                                        IsStroked = c.IsStroked
-                                    },
-                                    CrossPathSegment.QuadraticBezier q => new QuadraticBezierSegment()
-                                    {
-                                        Point1 = q.Point1,
-                                        Point2 = q.Point2,
-                                        IsStroked = q.IsStroked
-                                    },
-                                    CrossPathSegment.PolyLine p => new PolyLineSegment()
+            switch (g)
+            {
+                case CrossRectangleGeometry rg:
+                    return new RectangleGeometry(rg.Rect);
+                case CrossSvgGeometry svg:
+                    return PathGeometry.Parse(svg.Path);
+                case CrossEllipseGeometry ellipse:
+                    return new EllipseGeometry(ellipse.Rect);
+                case CrossStreamGeometry streamGeometry:
+                    return (StreamGeometry)streamGeometry.GetContext().GetGeometry();
+                case CrossPathGeometry path:
+                    return new PathGeometry()
+                    {
+                        Figures = RetAddRange(new PathFigures(), path.Figures.Select(f =>
+                            new PathFigure()
+                            {
+                                StartPoint = f.Start,
+                                IsClosed = f.Closed,
+                                Segments = RetAddRange<PathSegments, PathSegment>(new PathSegments(),
+                                    f.Segments.Select<CrossPathSegment, PathSegment>(s =>
+                                    s switch
                                     {
-                                        Points = p.Points.ToList(),
-                                        IsStroked = p.IsStroked
-                                    },
-                                    _ => throw new InvalidOperationException()
-                                }))
-                        }))
-                };
-            throw new NotSupportedException();
+                                        CrossPathSegment.Line l => new LineSegment()
+                                        {
+                                            Point = l.To,
+                                            IsStroked = l.IsStroked
+                                        },
+                                        CrossPathSegment.Arc a => new ArcSegment()
+                                        {
+                                            Point = a.Point,
+                                            RotationAngle = a.RotationAngle,
+                                            Size = a.Size,
+                                            IsLargeArc = a.IsLargeArc,
+                                            SweepDirection = a.SweepDirection,
+                                            IsStroked = a.IsStroked
+                                        },
+                                        CrossPathSegment.CubicBezier c => new BezierSegment()
+                                        {
+                                            Point1 = c.Point1,
+                                            Point2 = c.Point2,
+                                            Point3 = c.Point3,
+                                            IsStroked = c.IsStroked
+                                        },
+                                        CrossPathSegment.QuadraticBezier q => new QuadraticBezierSegment()
+                                        {
+                                            Point1 = q.Point1,
+                                            Point2 = q.Point2,
+                                            IsStroked = q.IsStroked
+                                        },
+                                        CrossPathSegment.PolyLine p => new PolyLineSegment()
+                                        {
+                                            Points = p.Points.ToList(),
+                                            IsStroked = p.IsStroked
+                                        },
+                                        CrossPathSegment.PolyBezierSegment p => new PolyBezierSegment(p.Points,p.IsStroked),
+                                        _ => throw new InvalidOperationException()
+                                    }))
+                            }))
+                    };
+                default:
+                    throw new NotSupportedException();
+            }
         }
 
         static TList RetAddRange<TList, T>(TList l, IEnumerable<T> en) where TList : IList<T>

+ 1 - 0
tests/Avalonia.RenderTests/CrossUI/CrossUI.cs

@@ -159,6 +159,7 @@ public abstract record class CrossPathSegment(bool IsStroked)
     public record CubicBezier(Point Point1, Point Point2, Point Point3, bool IsStroked) : CrossPathSegment(IsStroked);
     public record QuadraticBezier(Point Point1, Point Point2, bool IsStroked) : CrossPathSegment(IsStroked);
     public record PolyLine(IEnumerable<Point> Points, bool IsStroked) : CrossPathSegment(IsStroked);
+    public record PolyBezierSegment(IEnumerable<Point> Points, bool IsStroked) : CrossPathSegment(IsStroked);
 }
 
 public class CrossDrawingBrush : CrossTileBrush

BIN
tests/TestFiles/CrossTests/Media/Geometry/Should_Render_PolyBezierSegment_With_Strokeless_Lines.wpf.png