diff --git a/src/Avalonia.Visuals/Media/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs index 414fd9ab59..591b55cf58 100644 --- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs +++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs @@ -11,16 +11,51 @@ namespace Avalonia.Media /// public class EllipseGeometry : Geometry { + /// + /// Defines the property. + /// + public static readonly StyledProperty RectProperty = + AvaloniaProperty.Register(nameof(Rect)); + + public Rect Rect + { + get => GetValue(RectProperty); + set => SetValue(RectProperty, value); + } + + static EllipseGeometry() + { + RectProperty.Changed.AddClassHandler(x => x.RectChanged); + } + /// /// Initializes a new instance of the class. /// - /// The rectangle that the ellipse should fill. - public EllipseGeometry(Rect rect) + public EllipseGeometry() { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + PlatformImpl = factory.CreateStreamGeometry(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The rectangle that the ellipse should fill. + public EllipseGeometry(Rect rect) : this() + { + Rect = rect; + } + + /// + 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; - } - - /// - public override Geometry Clone() - { - return new EllipseGeometry(Bounds); } } } diff --git a/src/Avalonia.Visuals/Media/LineGeometry.cs b/src/Avalonia.Visuals/Media/LineGeometry.cs index 2783d7fb26..323bfa5a7e 100644 --- a/src/Avalonia.Visuals/Media/LineGeometry.cs +++ b/src/Avalonia.Visuals/Media/LineGeometry.cs @@ -10,35 +10,89 @@ namespace Avalonia.Media /// public class LineGeometry : Geometry { - private Point _startPoint; - private Point _endPoint; + /// + /// Defines the property. + /// + public static readonly StyledProperty StartPointProperty = + AvaloniaProperty.Register(nameof(StartPoint)); + + public Point StartPoint + { + get => GetValue(StartPointProperty); + set => SetValue(StartPointProperty, value); + } + + /// + /// Defines the property. + /// + public static readonly StyledProperty EndPointProperty = + AvaloniaProperty.Register(nameof(EndPoint)); + private bool _isDirty; + + public Point EndPoint + { + get => GetValue(EndPointProperty); + set => SetValue(EndPointProperty, value); + } + + static LineGeometry() + { + StartPointProperty.Changed.AddClassHandler(x => x.PointsChanged); + EndPointProperty.Changed.AddClassHandler(x => x.PointsChanged); + } + + /// + /// Initializes a new instance of the class. + /// + public LineGeometry() + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + PlatformImpl = factory.CreateStreamGeometry(); + } /// /// Initializes a new instance of the class. /// /// The start point. /// The end point. - public LineGeometry(Point startPoint, Point endPoint) + public LineGeometry(Point startPoint, Point endPoint) : this() { - _startPoint = startPoint; - _endPoint = endPoint; - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - 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); + } + } } /// public override Geometry Clone() { - return new LineGeometry(_startPoint, _endPoint); + PrepareIfNeeded(); + return new LineGeometry(StartPoint, EndPoint); } + + private void PointsChanged(AvaloniaPropertyChangedEventArgs e) => _isDirty = true; } } diff --git a/src/Avalonia.Visuals/Media/PolylineGeometry.cs b/src/Avalonia.Visuals/Media/PolylineGeometry.cs index 709ad7a9f5..c8d7ac163c 100644 --- a/src/Avalonia.Visuals/Media/PolylineGeometry.cs +++ b/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 /// public class PolylineGeometry : Geometry { - private IList _points; - private bool _isFilled; + /// + /// Defines the property. + /// + public static readonly DirectProperty PointsProperty = + AvaloniaProperty.RegisterDirect(nameof(Points), g => g.Points, (g, f) => g.Points = f); - public PolylineGeometry(IList points, bool isFilled) + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty IsFilledProperty = + AvaloniaProperty.Register(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(x => a => x.NotifyChanged()); + } + + /// + /// Initializes a new instance of the class. + /// + public PolylineGeometry() { - _points = points; - _isFilled = isFilled; IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + PlatformImpl = factory.CreateStreamGeometry(); + + Points = new Points(); + } + + /// + /// Initializes a new instance of the class. + /// + public PolylineGeometry(IEnumerable 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; + /// + /// Gets or sets the figures. + /// + /// + /// The points. + /// + [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; + /// public override Geometry Clone() { - return new PolylineGeometry(new List(_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; } } } diff --git a/src/Avalonia.Visuals/Media/RectangleGeometry.cs b/src/Avalonia.Visuals/Media/RectangleGeometry.cs index ef7deaa6f6..1aa449d9e1 100644 --- a/src/Avalonia.Visuals/Media/RectangleGeometry.cs +++ b/src/Avalonia.Visuals/Media/RectangleGeometry.cs @@ -10,16 +10,51 @@ namespace Avalonia.Media /// public class RectangleGeometry : Geometry { + /// + /// Defines the property. + /// + public static readonly StyledProperty RectProperty = + AvaloniaProperty.Register(nameof(Rect)); + + public Rect Rect + { + get => GetValue(RectProperty); + set => SetValue(RectProperty, value); + } + + static RectangleGeometry() + { + RectProperty.Changed.AddClassHandler(x => x.RectChanged); + } + /// /// Initializes a new instance of the class. /// - /// The rectangle bounds. - public RectangleGeometry(Rect rect) + public RectangleGeometry() { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - IStreamGeometryImpl impl = factory.CreateStreamGeometry(); + PlatformImpl = factory.CreateStreamGeometry(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The rectangle bounds. + public RectangleGeometry(Rect rect) : this() + { + Rect = rect; + } + + /// + 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; - } - - /// - public override Geometry Clone() - { - return new RectangleGeometry(Bounds); } } } diff --git a/src/Avalonia.Visuals/Points.cs b/src/Avalonia.Visuals/Points.cs new file mode 100644 index 0000000000..867d3d4d24 --- /dev/null +++ b/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 { } +}