Explorar o código

Allow Geometry classes to be instantiated in XAML

Eli Arbel %!s(int64=8) %!d(string=hai) anos
pai
achega
f05cb39a36

+ 39 - 12
src/Avalonia.Visuals/Media/EllipseGeometry.cs

@@ -11,16 +11,51 @@ namespace Avalonia.Media
     /// </summary>
     public class EllipseGeometry : Geometry
     {
+        /// <summary>
+        /// Defines the <see cref="Rect"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Rect> RectProperty =
+            AvaloniaProperty.Register<EllipseGeometry, Rect>(nameof(Rect));
+
+        public Rect Rect
+        {
+            get => GetValue(RectProperty);
+            set => SetValue(RectProperty, value);
+        }
+
+        static EllipseGeometry()
+        {
+            RectProperty.Changed.AddClassHandler<EllipseGeometry>(x => x.RectChanged);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="EllipseGeometry"/> class.
         /// </summary>
-        /// <param name="rect">The rectangle that the ellipse should fill.</param>
-        public EllipseGeometry(Rect rect)
+        public EllipseGeometry()
         {
             IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+            PlatformImpl = factory.CreateStreamGeometry();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="EllipseGeometry"/> class.
+        /// </summary>
+        /// <param name="rect">The rectangle that the ellipse should fill.</param>
+        public EllipseGeometry(Rect rect) : this()
+        {
+            Rect = rect;
+        }
+
+        /// <inheritdoc/>
+        public override Geometry Clone()
+        {
+            return new EllipseGeometry(Rect);
+        }
 
-            using (IStreamGeometryContextImpl ctx = impl.Open())
+        private void RectChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var rect = (Rect)e.NewValue;
+            using (var ctx = ((IStreamGeometryImpl)PlatformImpl).Open())
             {
                 double controlPointRatio = (Math.Sqrt(2) - 1) * 4 / 3;
                 var center = rect.Center;
@@ -45,14 +80,6 @@ namespace Avalonia.Media
                 ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0));
                 ctx.EndFigure(true);
             }
-
-            PlatformImpl = impl;
-        }
-
-        /// <inheritdoc/>
-        public override Geometry Clone()
-        {
-            return new EllipseGeometry(Bounds);
         }
     }
 }

+ 67 - 13
src/Avalonia.Visuals/Media/LineGeometry.cs

@@ -10,35 +10,89 @@ namespace Avalonia.Media
     /// </summary>
     public class LineGeometry : Geometry
     {
-        private Point _startPoint;
-        private Point _endPoint;
+        /// <summary>
+        /// Defines the <see cref="StartPoint"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Point> StartPointProperty =
+            AvaloniaProperty.Register<LineGeometry, Point>(nameof(StartPoint));
+
+        public Point StartPoint
+        {
+            get => GetValue(StartPointProperty);
+            set => SetValue(StartPointProperty, value);
+        }
+
+        /// <summary>
+        /// Defines the <see cref="EndPoint"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Point> EndPointProperty =
+            AvaloniaProperty.Register<LineGeometry, Point>(nameof(EndPoint));
+        private bool _isDirty;
+
+        public Point EndPoint
+        {
+            get => GetValue(EndPointProperty);
+            set => SetValue(EndPointProperty, value);
+        }
+
+        static LineGeometry()
+        {
+            StartPointProperty.Changed.AddClassHandler<LineGeometry>(x => x.PointsChanged);
+            EndPointProperty.Changed.AddClassHandler<LineGeometry>(x => x.PointsChanged);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LineGeometry"/> class.
+        /// </summary>
+        public LineGeometry()
+        {
+            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
+            PlatformImpl = factory.CreateStreamGeometry();
+        }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="LineGeometry"/> class.
         /// </summary>
         /// <param name="startPoint">The start point.</param>
         /// <param name="endPoint">The end point.</param>
-        public LineGeometry(Point startPoint, Point endPoint)
+        public LineGeometry(Point startPoint, Point endPoint) : this()
         {
-            _startPoint = startPoint;
-            _endPoint = endPoint;
-            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+            StartPoint = startPoint;
+            EndPoint = endPoint;
+        }
 
-            using (IStreamGeometryContextImpl context = impl.Open())
+        public override IGeometryImpl PlatformImpl
+        {
+            get
             {
-                context.BeginFigure(_startPoint, false);
-                context.LineTo(_endPoint);
-                context.EndFigure(false);
+                PrepareIfNeeded();
+                return base.PlatformImpl;
             }
+            protected set => base.PlatformImpl = value;
+        }
 
-            PlatformImpl = impl;
+        public void PrepareIfNeeded()
+        {
+            if (_isDirty)
+            {
+                _isDirty = false;
+
+                using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
+                {
+                    context.BeginFigure(StartPoint, false);
+                    context.LineTo(EndPoint);
+                    context.EndFigure(false);
+                }
+            }
         }
 
         /// <inheritdoc/>
         public override Geometry Clone()
         {
-            return new LineGeometry(_startPoint, _endPoint);
+            PrepareIfNeeded();
+            return new LineGeometry(StartPoint, EndPoint);
         }
+
+        private void PointsChanged(AvaloniaPropertyChangedEventArgs e) => _isDirty = true;
     }
 }

+ 102 - 17
src/Avalonia.Visuals/Media/PolylineGeometry.cs

@@ -3,10 +3,9 @@
 
 using System;
 using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using Avalonia.Platform;
+using Avalonia.Metadata;
+using Avalonia.Collections;
 
 namespace Avalonia.Media
 {
@@ -15,36 +14,122 @@ namespace Avalonia.Media
     /// </summary>
     public class PolylineGeometry : Geometry
     {
-        private IList<Point> _points;
-        private bool _isFilled;
+        /// <summary>
+        /// Defines the <see cref="Points"/> property.
+        /// </summary>
+        public static readonly DirectProperty<PolylineGeometry, Points> PointsProperty =
+            AvaloniaProperty.RegisterDirect<PolylineGeometry, Points>(nameof(Points), g => g.Points, (g, f) => g.Points = f);
 
-        public PolylineGeometry(IList<Point> points, bool isFilled)
+        /// <summary>
+        /// Defines the <see cref="IsFilled"/> property.
+        /// </summary>
+        public static readonly AvaloniaProperty<bool> IsFilledProperty =
+            AvaloniaProperty.Register<PolylineGeometry, bool>(nameof(IsFilled));
+
+        static PolylineGeometry()
+        {
+            PointsProperty.Changed.Subscribe(onNext: v =>
+            {
+                (v.Sender as PolylineGeometry)?.OnPointsChanged(v.OldValue as Points, v.NewValue as Points);
+            });
+            IsFilledProperty.Changed.AddClassHandler<PolylineGeometry>(x => a => x.NotifyChanged());
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PolylineGeometry"/> class.
+        /// </summary>
+        public PolylineGeometry()
         {
-            _points = points;
-            _isFilled = isFilled;
             IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+            PlatformImpl = factory.CreateStreamGeometry();
+
+            Points = new Points();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PolylineGeometry"/> class.
+        /// </summary>
+        public PolylineGeometry(IEnumerable<Point> points, bool isFilled) : this()
+        {
+            Points.AddRange(points);
+            IsFilled = isFilled;
+
+            PrepareIfNeeded();
+        }
 
-            using (IStreamGeometryContextImpl context = impl.Open())
+        public void PrepareIfNeeded()
+        {
+            if (_isDirty)
             {
-                if (points.Count > 0)
+                _isDirty = false;
+
+                using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
                 {
-                    context.BeginFigure(points[0], isFilled);
-                    for (int i = 1; i < points.Count; i++)
+                    var points = Points;
+                    var isFilled = IsFilled;
+                    if (points.Count > 0)
                     {
-                        context.LineTo(points[i]);
+                        context.BeginFigure(points[0], isFilled);
+                        for (int i = 1; i < points.Count; i++)
+                        {
+                            context.LineTo(points[i]);
+                        }
+                        context.EndFigure(isFilled);
                     }
-                    context.EndFigure(isFilled);
                 }
             }
+        }
 
-            PlatformImpl = impl;
+        /// <summary>
+        /// Gets or sets the figures.
+        /// </summary>
+        /// <value>
+        /// The points.
+        /// </value>
+        [Content]
+        public Points Points
+        {
+            get => _points;
+            set => SetAndRaise(PointsProperty, ref _points, value);
         }
 
+        public bool IsFilled
+        {
+            get => GetValue(IsFilledProperty);
+            set => SetValue(IsFilledProperty, value);
+        }
+
+        public override IGeometryImpl PlatformImpl
+        {
+            get
+            {
+                PrepareIfNeeded();
+                return base.PlatformImpl;
+            }
+            protected set => base.PlatformImpl = value;
+        }
+
+        private Points _points;
+        private bool _isDirty;
+        private IDisposable _pointsObserver;
+
         /// <inheritdoc/>
         public override Geometry Clone()
         {
-            return new PolylineGeometry(new List<Point>(_points), _isFilled);
+            PrepareIfNeeded();
+            return new PolylineGeometry(Points, IsFilled);
+        }
+
+        private void OnPointsChanged(Points oldValue, Points newValue)
+        {
+            _pointsObserver?.Dispose();
+
+            _pointsObserver = newValue?.ForEachItem(f => NotifyChanged(), f => NotifyChanged(), () => NotifyChanged());
+        }
+
+        internal void NotifyChanged()
+        {
+            _isDirty = true;
         }
     }
 }

+ 39 - 12
src/Avalonia.Visuals/Media/RectangleGeometry.cs

@@ -10,16 +10,51 @@ namespace Avalonia.Media
     /// </summary>
     public class RectangleGeometry : Geometry
     {
+        /// <summary>
+        /// Defines the <see cref="Rect"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Rect> RectProperty =
+            AvaloniaProperty.Register<RectangleGeometry, Rect>(nameof(Rect));
+
+        public Rect Rect
+        {
+            get => GetValue(RectProperty);
+            set => SetValue(RectProperty, value);
+        }
+
+        static RectangleGeometry()
+        {
+            RectProperty.Changed.AddClassHandler<RectangleGeometry>(x => x.RectChanged);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="RectangleGeometry"/> class.
         /// </summary>
-        /// <param name="rect">The rectangle bounds.</param>
-        public RectangleGeometry(Rect rect)
+        public RectangleGeometry()
         {
             IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+            PlatformImpl = factory.CreateStreamGeometry();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RectangleGeometry"/> class.
+        /// </summary>
+        /// <param name="rect">The rectangle bounds.</param>
+        public RectangleGeometry(Rect rect) : this()
+        {
+            Rect = rect;
+        }
+
+        /// <inheritdoc/>
+        public override Geometry Clone()
+        {
+            return new RectangleGeometry(Rect);
+        }
 
-            using (IStreamGeometryContextImpl context = impl.Open())
+        private void RectChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var rect = (Rect)e.NewValue;
+            using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
             {
                 context.BeginFigure(rect.TopLeft, true);
                 context.LineTo(rect.TopRight);
@@ -27,14 +62,6 @@ namespace Avalonia.Media
                 context.LineTo(rect.BottomLeft);
                 context.EndFigure(true);
             }
-
-            PlatformImpl = impl;
-        }
-
-        /// <inheritdoc/>
-        public override Geometry Clone()
-        {
-            return new RectangleGeometry(Bounds);
         }
     }
 }

+ 9 - 0
src/Avalonia.Visuals/Points.cs

@@ -0,0 +1,9 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Collections;
+
+namespace Avalonia
+{
+    public sealed class Points : AvaloniaList<Point> { }
+}