diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs
index d0eb67ba39..caf86cb234 100644
--- a/src/Avalonia.Visuals/Media/PathFigure.cs
+++ b/src/Avalonia.Visuals/Media/PathFigure.cs
@@ -1,3 +1,7 @@
+#nullable enable
+using System;
+using System.Linq;
+using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Media
@@ -8,22 +12,36 @@ 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);
+ public static readonly DirectProperty SegmentsProperty
+ = 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;
+
+ private PathSegments? _segments;
+
+ private IDisposable? _segmentsDisposable;
+
+ private IDisposable? _segmentsPropertiesDisposable;
///
/// Initializes a new instance of the class.
@@ -33,6 +51,31 @@ namespace Avalonia.Media
Segments = new PathSegments();
}
+ static PathFigure()
+ {
+ SegmentsProperty.Changed.AddClassHandler(
+ (s, e) =>
+ s.OnSegmentsChanged());
+ }
+
+ private void OnSegmentsChanged()
+ {
+ _segmentsDisposable?.Dispose();
+ _segmentsPropertiesDisposable?.Dispose();
+
+ _segmentsDisposable = _segments?.ForEachItem(
+ _ => InvalidateSegments(),
+ _ => InvalidateSegments(),
+ InvalidateSegments);
+
+ _segmentsPropertiesDisposable = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments());
+ }
+
+ private void InvalidateSegments()
+ {
+ SegmentsInvalidated?.Invoke(this, EventArgs.Empty);
+ }
+
///
/// Gets or sets a value indicating whether this instance is closed.
///
@@ -64,7 +107,7 @@ namespace Avalonia.Media
/// The segments.
///
[Content]
- public PathSegments Segments
+ public PathSegments? Segments
{
get { return _segments; }
set { SetAndRaise(SegmentsProperty, ref _segments, value); }
@@ -81,22 +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);
}
-
- private PathSegments _segments;
-
- 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..3d11c19b7d 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());
+
}
+ private void InvalidateGeometryFromSegments(object _, EventArgs __)
+ {
+ InvalidateGeometry();
+ }
public override string ToString()
=> $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}";
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);
+ }
+ }
+}