From 3b18572dd60cc0222ef7717d9455557ce01b1676 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 14 Nov 2020 10:49:34 +0800 Subject: [PATCH 1/9] add property listening for path segments --- src/Avalonia.Visuals/Media/PathFigure.cs | 32 +++++++++++++++++++++- src/Avalonia.Visuals/Media/PathGeometry.cs | 20 ++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index d0eb67ba39..ff63a6286c 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -1,3 +1,6 @@ +using System; +using System.ComponentModel; +using Avalonia.Collections; using Avalonia.Metadata; namespace Avalonia.Media @@ -25,14 +28,41 @@ namespace Avalonia.Media public static readonly StyledProperty StartPointProperty = AvaloniaProperty.Register(nameof(StartPoint)); + internal event EventHandler SegmentsInvalidated; + + private IDisposable? _segmentsObserver; + + private IDisposable? _segmentsPropertiesObserver; + /// /// Initializes a new instance of the class. /// public PathFigure() { + SegmentsProperty.Changed.AddClassHandler((s, e) => + s.OnSegmentsChanged(e.NewValue as PathSegments)); + Segments = new PathSegments(); } + private void OnSegmentsChanged(PathSegments? arg2NewValue) + { + _segmentsObserver?.Dispose(); + _segmentsPropertiesObserver?.Dispose(); + + _segmentsObserver = _segments?.ForEachItem( + _ => InvalidateSegments(), + _ => InvalidateSegments(), + InvalidateSegments); + + _segmentsPropertiesObserver = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); + } + + private void InvalidateSegments() + { + SegmentsInvalidated?.Invoke(this, EventArgs.Empty); + } + /// /// Gets or sets a value indicating whether this instance is closed. /// @@ -99,4 +129,4 @@ namespace Avalonia.Media public override string ToString() => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}"; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs index fbc29aedc8..819669d86e 100644 --- a/src/Avalonia.Visuals/Media/PathGeometry.cs +++ b/src/Avalonia.Visuals/Media/PathGeometry.cs @@ -104,12 +104,26 @@ namespace Avalonia.Media _figuresPropertiesObserver?.Dispose(); _figuresObserver = figures?.ForEachItem( - _ => InvalidateGeometry(), - _ => InvalidateGeometry(), - () => InvalidateGeometry()); + s => + { + s.SegmentsInvalidated += InvalidateGeometryFromSegments; + InvalidateGeometry(); + }, + s => + { + s.SegmentsInvalidated -= InvalidateGeometryFromSegments; + InvalidateGeometry(); + }, + InvalidateGeometry); + _figuresPropertiesObserver = figures?.TrackItemPropertyChanged(_ => InvalidateGeometry()); + } + public void InvalidateGeometryFromSegments(object _, EventArgs __) + { + InvalidateGeometry(); + } public override string ToString() => $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}"; From d2339a041df94ca65878140435a351a3932f7ee4 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 14 Nov 2020 11:02:30 +0800 Subject: [PATCH 2/9] try making a unit test --- .../Avalonia.RenderTests/Shapes/PathTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs index bac16cca88..24e420d972 100644 --- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs @@ -353,7 +353,47 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task PathSegment_Triggers_Invalidation_On_Property_Change() + { + var targetSegment = new ArcSegment() + { + Size = new Size(10,10), + Point = new Point(5,5) + }; + + var targetPath = new Path + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + Fill = Brushes.Red, + Data = new PathGeometry + { + Figures = new PathFigures + { + new PathFigure { IsClosed = false, Segments = new PathSegments { targetSegment } } + } + } + }; + + var root = new Border + { + Width = 100, + Height = 100, + Background = Brushes.White, + Child =targetPath + }; + + Assert.Equal(10, targetPath.Bounds.Height); + Assert.Equal(10, targetPath.Bounds.Width); + + targetSegment.Size = new Size(20, 20); + Assert.Equal(20, targetPath.Bounds.Height); + Assert.Equal(20, targetPath.Bounds.Width); + } + [Fact] public async Task Path_With_Rotated_Geometry() { From e90a5d285c7bebf478a97b4c357681a049702250 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 17 Nov 2020 16:54:08 +0800 Subject: [PATCH 3/9] add change listener on static ctor --- src/Avalonia.Visuals/Media/PathFigure.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index ff63a6286c..a9e3c6d628 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -38,11 +38,14 @@ namespace Avalonia.Media /// Initializes a new instance of the class. /// public PathFigure() + { + Segments = new PathSegments(); + } + + static PathFigure() { SegmentsProperty.Changed.AddClassHandler((s, e) => s.OnSegmentsChanged(e.NewValue as PathSegments)); - - Segments = new PathSegments(); } private void OnSegmentsChanged(PathSegments? arg2NewValue) From 7df1d2ad4d07f30fe41aaf8e477c1be8832f7de4 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 17 Nov 2020 16:58:53 +0800 Subject: [PATCH 4/9] add test, thanks @donandren ! :D --- .../Media/PathSegmentTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs new file mode 100644 index 0000000000..0737b4dc88 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs @@ -0,0 +1,34 @@ +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class PathSegmentTests + { + [Fact] + public void PathSegment_Triggers_Invalidation_On_Property_Change() + { + var targetSegment = new ArcSegment() + { + Size = new Size(10, 10), + Point = new Point(5, 5) + }; + + var target = new PathGeometry + { + Figures = new PathFigures + { + new PathFigure { IsClosed = false, Segments = new PathSegments { targetSegment } } + } + }; + + var changed = false; + + target.Changed += (s, e) => changed = true; + + targetSegment.Size = new Size(20, 20); + + Assert.True(changed); + } + } +} From 4e180c88cdb5f4e22e177f41a83010165e7836fb Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 17 Nov 2020 16:59:43 +0800 Subject: [PATCH 5/9] remove old test --- .../Avalonia.RenderTests/Shapes/PathTests.cs | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs index 24e420d972..bac16cca88 100644 --- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs @@ -353,47 +353,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes await RenderToFile(target); CompareImages(); } - - [Fact] - public async Task PathSegment_Triggers_Invalidation_On_Property_Change() - { - var targetSegment = new ArcSegment() - { - Size = new Size(10,10), - Point = new Point(5,5) - }; - - var targetPath = new Path - { - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - Fill = Brushes.Red, - Data = new PathGeometry - { - Figures = new PathFigures - { - new PathFigure { IsClosed = false, Segments = new PathSegments { targetSegment } } - } - } - }; - - var root = new Border - { - Width = 100, - Height = 100, - Background = Brushes.White, - Child =targetPath - }; - - Assert.Equal(10, targetPath.Bounds.Height); - Assert.Equal(10, targetPath.Bounds.Width); - - targetSegment.Size = new Size(20, 20); - Assert.Equal(20, targetPath.Bounds.Height); - Assert.Equal(20, targetPath.Bounds.Width); - } - [Fact] public async Task Path_With_Rotated_Geometry() { From 823ab3a40ddd8f37bb3384f1a386d4f67ee13fc2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Nov 2020 18:21:07 +0800 Subject: [PATCH 6/9] address review --- src/Avalonia.Visuals/Media/PathFigure.cs | 8 ++++---- src/Avalonia.Visuals/Media/PathGeometry.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index a9e3c6d628..311a70a098 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -30,9 +30,9 @@ namespace Avalonia.Media internal event EventHandler SegmentsInvalidated; - private IDisposable? _segmentsObserver; + private IDisposable _segmentsDisposable; - private IDisposable? _segmentsPropertiesObserver; + private IDisposable _segmentsPropertiesObserver; /// /// Initializes a new instance of the class. @@ -50,10 +50,10 @@ namespace Avalonia.Media private void OnSegmentsChanged(PathSegments? arg2NewValue) { - _segmentsObserver?.Dispose(); + _segmentsDisposable?.Dispose(); _segmentsPropertiesObserver?.Dispose(); - _segmentsObserver = _segments?.ForEachItem( + _segmentsDisposable = _segments?.ForEachItem( _ => InvalidateSegments(), _ => InvalidateSegments(), InvalidateSegments); diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs index 819669d86e..3d11c19b7d 100644 --- a/src/Avalonia.Visuals/Media/PathGeometry.cs +++ b/src/Avalonia.Visuals/Media/PathGeometry.cs @@ -120,7 +120,7 @@ namespace Avalonia.Media } - public void InvalidateGeometryFromSegments(object _, EventArgs __) + private void InvalidateGeometryFromSegments(object _, EventArgs __) { InvalidateGeometry(); } From 90e6e072212b1fb5924ba7f21027ab9a8dd993ac Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Nov 2020 18:23:28 +0800 Subject: [PATCH 7/9] address review --- src/Avalonia.Visuals/Media/PathFigure.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index 311a70a098..0c4cc592af 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -32,7 +32,7 @@ namespace Avalonia.Media private IDisposable _segmentsDisposable; - private IDisposable _segmentsPropertiesObserver; + private IDisposable _segmentsPropertiesDisposable; /// /// Initializes a new instance of the class. @@ -51,14 +51,14 @@ namespace Avalonia.Media private void OnSegmentsChanged(PathSegments? arg2NewValue) { _segmentsDisposable?.Dispose(); - _segmentsPropertiesObserver?.Dispose(); + _segmentsPropertiesDisposable?.Dispose(); _segmentsDisposable = _segments?.ForEachItem( _ => InvalidateSegments(), _ => InvalidateSegments(), InvalidateSegments); - _segmentsPropertiesObserver = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); + _segmentsPropertiesDisposable = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); } private void InvalidateSegments() From 538b723c2d25916bd1e1d2f0898a7923862d2281 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 18 Nov 2020 20:53:07 +0800 Subject: [PATCH 8/9] address review --- src/Avalonia.Visuals/Media/PathFigure.cs | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index 0c4cc592af..2f8d11b06e 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -1,5 +1,5 @@ +#nullable enable using System; -using System.ComponentModel; using Avalonia.Collections; using Avalonia.Metadata; @@ -11,29 +11,35 @@ namespace Avalonia.Media /// Defines the property. /// public static readonly StyledProperty IsClosedProperty - = AvaloniaProperty.Register(nameof(IsClosed), true); + = AvaloniaProperty.Register(nameof(IsClosed), true); + /// /// Defines the property. /// public static readonly StyledProperty IsFilledProperty - = AvaloniaProperty.Register(nameof(IsFilled), true); + = AvaloniaProperty.Register(nameof(IsFilled), true); + /// /// Defines the property. /// public static readonly DirectProperty SegmentsProperty - = AvaloniaProperty.RegisterDirect(nameof(Segments), f => f.Segments, (f, s) => f.Segments = s); + = AvaloniaProperty.RegisterDirect(nameof(Segments), f => f.Segments, + (f, s) => f.Segments = s); + /// /// Defines the property. /// public static readonly StyledProperty StartPointProperty - = AvaloniaProperty.Register(nameof(StartPoint)); + = AvaloniaProperty.Register(nameof(StartPoint)); + + internal event EventHandler? SegmentsInvalidated; - internal event EventHandler SegmentsInvalidated; + private PathSegments? _segments; - private IDisposable _segmentsDisposable; + private IDisposable? _segmentsDisposable; + + private IDisposable? _segmentsPropertiesDisposable; - private IDisposable _segmentsPropertiesDisposable; - /// /// Initializes a new instance of the class. /// @@ -57,7 +63,7 @@ namespace Avalonia.Media _ => InvalidateSegments(), _ => InvalidateSegments(), InvalidateSegments); - + _segmentsPropertiesDisposable = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); } @@ -127,8 +133,6 @@ namespace Avalonia.Media ctx.EndFigure(IsClosed); } - private PathSegments _segments; - public override string ToString() => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}"; } From 1800ad01257216c30ae5c40b8567ab437a0432f2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 18 Nov 2020 13:57:16 +0000 Subject: [PATCH 9/9] fix nullable warnings. --- src/Avalonia.Visuals/Media/PathFigure.cs | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index 2f8d11b06e..caf86cb234 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Linq; using Avalonia.Collections; using Avalonia.Metadata; @@ -22,8 +23,10 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly DirectProperty SegmentsProperty - = AvaloniaProperty.RegisterDirect(nameof(Segments), f => f.Segments, + public static readonly DirectProperty SegmentsProperty + = AvaloniaProperty.RegisterDirect( + nameof(Segments), + f => f.Segments, (f, s) => f.Segments = s); /// @@ -50,11 +53,12 @@ namespace Avalonia.Media static PathFigure() { - SegmentsProperty.Changed.AddClassHandler((s, e) => - s.OnSegmentsChanged(e.NewValue as PathSegments)); + SegmentsProperty.Changed.AddClassHandler( + (s, e) => + s.OnSegmentsChanged()); } - private void OnSegmentsChanged(PathSegments? arg2NewValue) + private void OnSegmentsChanged() { _segmentsDisposable?.Dispose(); _segmentsPropertiesDisposable?.Dispose(); @@ -103,7 +107,7 @@ namespace Avalonia.Media /// The segments. /// [Content] - public PathSegments Segments + public PathSegments? Segments { get { return _segments; } set { SetAndRaise(SegmentsProperty, ref _segments, value); } @@ -120,20 +124,23 @@ namespace Avalonia.Media get { return GetValue(StartPointProperty); } set { SetValue(StartPointProperty, value); } } + + public override string ToString() + => $"M {StartPoint} {string.Join(" ", _segments ?? Enumerable.Empty())}{(IsClosed ? "Z" : "")}"; internal void ApplyTo(StreamGeometryContext ctx) { ctx.BeginFigure(StartPoint, IsFilled); - foreach (var segment in Segments) + if (Segments != null) { - segment.ApplyTo(ctx); + foreach (var segment in Segments) + { + segment.ApplyTo(ctx); + } } ctx.EndFigure(IsClosed); } - - public override string ToString() - => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}"; } }