Browse Source

Merge pull request #5042 from AvaloniaUI/update_geometry_segments_onpropchange

Update Path from PathSegments
pull/5075/head
Dan Walmsley 6 years ago
committed by GitHub
parent
commit
3ab8222475
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 72
      src/Avalonia.Visuals/Media/PathFigure.cs
  2. 20
      src/Avalonia.Visuals/Media/PathGeometry.cs
  3. 34
      tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs

72
src/Avalonia.Visuals/Media/PathFigure.cs

@ -1,3 +1,7 @@
#nullable enable
using System;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Media namespace Avalonia.Media
@ -8,22 +12,36 @@ namespace Avalonia.Media
/// Defines the <see cref="IsClosed"/> property. /// Defines the <see cref="IsClosed"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsClosedProperty public static readonly StyledProperty<bool> IsClosedProperty
= AvaloniaProperty.Register<PathFigure, bool>(nameof(IsClosed), true); = AvaloniaProperty.Register<PathFigure, bool>(nameof(IsClosed), true);
/// <summary> /// <summary>
/// Defines the <see cref="IsFilled"/> property. /// Defines the <see cref="IsFilled"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsFilledProperty public static readonly StyledProperty<bool> IsFilledProperty
= AvaloniaProperty.Register<PathFigure, bool>(nameof(IsFilled), true); = AvaloniaProperty.Register<PathFigure, bool>(nameof(IsFilled), true);
/// <summary> /// <summary>
/// Defines the <see cref="Segments"/> property. /// Defines the <see cref="Segments"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<PathFigure, PathSegments> SegmentsProperty public static readonly DirectProperty<PathFigure, PathSegments?> SegmentsProperty
= AvaloniaProperty.RegisterDirect<PathFigure, PathSegments>(nameof(Segments), f => f.Segments, (f, s) => f.Segments = s); = AvaloniaProperty.RegisterDirect<PathFigure, PathSegments?>(
nameof(Segments),
f => f.Segments,
(f, s) => f.Segments = s);
/// <summary> /// <summary>
/// Defines the <see cref="StartPoint"/> property. /// Defines the <see cref="StartPoint"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Point> StartPointProperty public static readonly StyledProperty<Point> StartPointProperty
= AvaloniaProperty.Register<PathFigure, Point>(nameof(StartPoint)); = AvaloniaProperty.Register<PathFigure, Point>(nameof(StartPoint));
internal event EventHandler? SegmentsInvalidated;
private PathSegments? _segments;
private IDisposable? _segmentsDisposable;
private IDisposable? _segmentsPropertiesDisposable;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PathFigure"/> class. /// Initializes a new instance of the <see cref="PathFigure"/> class.
@ -33,6 +51,31 @@ namespace Avalonia.Media
Segments = new PathSegments(); Segments = new PathSegments();
} }
static PathFigure()
{
SegmentsProperty.Changed.AddClassHandler<PathFigure>(
(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);
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is closed. /// Gets or sets a value indicating whether this instance is closed.
/// </summary> /// </summary>
@ -64,7 +107,7 @@ namespace Avalonia.Media
/// The segments. /// The segments.
/// </value> /// </value>
[Content] [Content]
public PathSegments Segments public PathSegments? Segments
{ {
get { return _segments; } get { return _segments; }
set { SetAndRaise(SegmentsProperty, ref _segments, value); } set { SetAndRaise(SegmentsProperty, ref _segments, value); }
@ -81,22 +124,23 @@ namespace Avalonia.Media
get { return GetValue(StartPointProperty); } get { return GetValue(StartPointProperty); }
set { SetValue(StartPointProperty, value); } set { SetValue(StartPointProperty, value); }
} }
public override string ToString()
=> $"M {StartPoint} {string.Join(" ", _segments ?? Enumerable.Empty<PathSegment>())}{(IsClosed ? "Z" : "")}";
internal void ApplyTo(StreamGeometryContext ctx) internal void ApplyTo(StreamGeometryContext ctx)
{ {
ctx.BeginFigure(StartPoint, IsFilled); 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); ctx.EndFigure(IsClosed);
} }
private PathSegments _segments;
public override string ToString()
=> $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}";
} }
} }

20
src/Avalonia.Visuals/Media/PathGeometry.cs

@ -104,12 +104,26 @@ namespace Avalonia.Media
_figuresPropertiesObserver?.Dispose(); _figuresPropertiesObserver?.Dispose();
_figuresObserver = figures?.ForEachItem( _figuresObserver = figures?.ForEachItem(
_ => InvalidateGeometry(), s =>
_ => InvalidateGeometry(), {
() => InvalidateGeometry()); s.SegmentsInvalidated += InvalidateGeometryFromSegments;
InvalidateGeometry();
},
s =>
{
s.SegmentsInvalidated -= InvalidateGeometryFromSegments;
InvalidateGeometry();
},
InvalidateGeometry);
_figuresPropertiesObserver = figures?.TrackItemPropertyChanged(_ => InvalidateGeometry()); _figuresPropertiesObserver = figures?.TrackItemPropertyChanged(_ => InvalidateGeometry());
} }
private void InvalidateGeometryFromSegments(object _, EventArgs __)
{
InvalidateGeometry();
}
public override string ToString() public override string ToString()
=> $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}"; => $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}";

34
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);
}
}
}
Loading…
Cancel
Save