diff --git a/src/Avalonia.Visuals/Media/BrushExtensions.cs b/src/Avalonia.Visuals/Media/BrushExtensions.cs index 081029e205..265337476a 100644 --- a/src/Avalonia.Visuals/Media/BrushExtensions.cs +++ b/src/Avalonia.Visuals/Media/BrushExtensions.cs @@ -24,12 +24,27 @@ namespace Avalonia.Media } /// - /// Converts a pen to a pen with an immutable brush + /// Converts a dash style to an immutable dash style. + /// + /// The dash style. + /// + /// The result of calling if the style is mutable, + /// otherwise . + /// + public static ImmutableDashStyle ToImmutable(this IDashStyle style) + { + Contract.Requires(style != null); + + return style as ImmutableDashStyle ?? ((DashStyle)style).ToImmutable(); + } + + /// + /// Converts a pen to an immutable pen. /// /// The pen. /// - /// A copy of the pen with an immutable brush, or if the pen's brush - /// is already immutable or null. + /// The result of calling if the brush is mutable, + /// otherwise . /// public static ImmutablePen ToImmutable(this IPen pen) { diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index 9f03a0bd85..7784c73736 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -1,72 +1,114 @@ namespace Avalonia.Media { + using System; using System.Collections.Generic; + using System.Linq; using Avalonia.Animation; + using Avalonia.Media.Immutable; - public class DashStyle : Animatable + /// + /// Represents the sequence of dashes and gaps that will be applied by a . + /// + public class DashStyle : Animatable, IDashStyle, IAffectsRender { - private static DashStyle dash; - public static DashStyle Dash - { - get - { - if (dash == null) - { - dash = new DashStyle(new double[] { 2, 2 }, 1); - } - - return dash; - } - } + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty> DashesProperty = + AvaloniaProperty.Register>(nameof(Dashes)); + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty OffsetProperty = + AvaloniaProperty.Register(nameof(Offset)); + private static ImmutableDashStyle s_dash; + private static ImmutableDashStyle s_dot; + private static ImmutableDashStyle s_dashDot; + private static ImmutableDashStyle s_dashDotDot; - private static DashStyle dot; - public static DashStyle Dot + /// + /// Initializes a new instance of the class. + /// + public DashStyle() + : this(null, 0) { - get { return dot ?? (dot = new DashStyle(new double[] {0, 2}, 0)); } } - private static DashStyle dashDot; - public static DashStyle DashDot + /// + /// Initializes a new instance of the class. + /// + /// The dashes collection. + /// The dash sequence offset. + public DashStyle(IEnumerable dashes, double offset) { - get - { - if (dashDot == null) - { - dashDot = new DashStyle(new double[] { 2, 2, 0, 2 }, 1); - } - - return dashDot; - } + Dashes = (IReadOnlyList)dashes?.ToList() ?? Array.Empty(); + Offset = offset; } - private static DashStyle dashDotDot; - public static DashStyle DashDotDot + static DashStyle() { - get + void RaiseInvalidated(AvaloniaPropertyChangedEventArgs e) { - if (dashDotDot == null) - { - dashDotDot = new DashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1); - } - - return dashDotDot; + ((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty); } + + DashesProperty.Changed.Subscribe(RaiseInvalidated); + OffsetProperty.Changed.Subscribe(RaiseInvalidated); } + /// + /// Represents a dashed . + /// + public static IDashStyle Dash => + s_dash ?? (s_dash = new ImmutableDashStyle(new double[] { 2, 2 }, 1)); + + /// + /// Represents a dotted . + /// + public static IDashStyle Dot => + s_dot ?? (s_dot = new ImmutableDashStyle(new double[] { 0, 2 }, 0)); + + /// + /// Represents a dashed dotted . + /// + public static IDashStyle DashDot => + s_dashDot ?? (s_dashDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2 }, 1)); + + /// + /// Represents a dashed double dotted . + /// + public static IDashStyle DashDotDot => + s_dashDotDot ?? (s_dashDotDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1)); - public DashStyle(IReadOnlyList dashes = null, double offset = 0.0) + /// + /// Gets or sets the length of alternating dashes and gaps. + /// + public IReadOnlyList Dashes { - this.Dashes = dashes; - this.Offset = offset; + get => GetValue(DashesProperty); + set => SetValue(DashesProperty, value); } /// - /// Gets and sets the length of alternating dashes and gaps. + /// Gets or sets how far in the dash sequence the stroke will start. /// - public IReadOnlyList Dashes { get; } + public double Offset + { + get => GetValue(OffsetProperty); + set => SetValue(OffsetProperty, value); + } - public double Offset { get; } + /// + /// Raised when the dash style changes. + /// + public event EventHandler Invalidated; + + /// + /// Returns an immutable clone of the . + /// + /// + public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset); } } diff --git a/src/Avalonia.Visuals/Media/IDashStyle.cs b/src/Avalonia.Visuals/Media/IDashStyle.cs new file mode 100644 index 0000000000..7835c7a1e9 --- /dev/null +++ b/src/Avalonia.Visuals/Media/IDashStyle.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Avalonia.Media +{ + /// + /// Represents the sequence of dashes and gaps that will be applied by a . + /// + public interface IDashStyle + { + /// + /// Gets or sets the length of alternating dashes and gaps. + /// + IReadOnlyList Dashes { get; } + + /// + /// Gets or sets how far in the dash sequence the stroke will start. + /// + double Offset { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/IPen.cs b/src/Avalonia.Visuals/Media/IPen.cs index 589595bb5c..0cdac312cc 100644 --- a/src/Avalonia.Visuals/Media/IPen.cs +++ b/src/Avalonia.Visuals/Media/IPen.cs @@ -13,7 +13,7 @@ /// /// Gets the style of dashed lines drawn with a object. /// - DashStyle DashStyle { get; } + IDashStyle DashStyle { get; } /// /// Gets the type of shape to use on both ends of a line. diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs new file mode 100644 index 0000000000..a40682babd --- /dev/null +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Avalonia.Media.Immutable +{ + /// + /// Represents the sequence of dashes and gaps that will be applied by an + /// . + /// + public class ImmutableDashStyle : IDashStyle + { + /// + /// Initializes a new instance of the class. + /// + /// The dashes collection. + /// The dash sequence offset. + public ImmutableDashStyle(IEnumerable dashes, double offset) + { + Dashes = (IReadOnlyList)dashes?.ToList() ?? Array.Empty(); + Offset = offset; + } + + /// + public IReadOnlyList Dashes { get; } + + /// + public double Offset { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs index 326083dec1..ddc050aa5c 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs @@ -22,7 +22,7 @@ namespace Avalonia.Media.Immutable public ImmutablePen( uint color, double thickness = 1.0, - DashStyle dashStyle = null, + ImmutableDashStyle dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) @@ -41,7 +41,7 @@ namespace Avalonia.Media.Immutable public ImmutablePen( IBrush brush, double thickness = 1.0, - DashStyle dashStyle = null, + ImmutableDashStyle dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) @@ -67,7 +67,7 @@ namespace Avalonia.Media.Immutable /// /// Specifies the style of dashed lines drawn with a object. /// - public DashStyle DashStyle { get; } + public IDashStyle DashStyle { get; } /// /// Specifies the type of graphic shape to use on both ends of a line. diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index b89ca15a0e..c0963adc08 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -28,8 +28,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty DashStyleProperty = - AvaloniaProperty.Register(nameof(DashStyle)); + public static readonly StyledProperty DashStyleProperty = + AvaloniaProperty.Register(nameof(DashStyle)); /// /// Defines the property. @@ -68,7 +68,7 @@ namespace Avalonia.Media public Pen( uint color, double thickness = 1.0, - DashStyle dashStyle = null, + IDashStyle dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) @@ -87,7 +87,7 @@ namespace Avalonia.Media public Pen( IBrush brush, double thickness = 1.0, - DashStyle dashStyle = null, + IDashStyle dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) @@ -132,7 +132,7 @@ namespace Avalonia.Media /// /// Gets or sets the style of dashed lines drawn with a object. /// - public DashStyle DashStyle + public IDashStyle DashStyle { get => GetValue(DashStyleProperty); set => SetValue(DashStyleProperty, value); @@ -189,7 +189,7 @@ namespace Avalonia.Media return new ImmutablePen( Brush?.ToImmutable(), Thickness, - DashStyle, + DashStyle?.ToImmutable(), LineCap, LineJoin, MiterLimit); @@ -247,7 +247,7 @@ namespace Avalonia.Media var hashCode = 1181807663; hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(pen.Brush); hashCode = hashCode * -1521134295 + pen.Thickness.GetHashCode(); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(pen.DashStyle); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(pen.DashStyle); hashCode = hashCode * -1521134295 + pen.LineCap.GetHashCode(); hashCode = hashCode * -1521134295 + pen.LineJoin.GetHashCode(); hashCode = hashCode * -1521134295 + pen.MiterLimit.GetHashCode(); @@ -267,7 +267,7 @@ namespace Avalonia.Media return EqualityComparer.Default.Equals(a.Brush, b.Brush) && a.Thickness == b.Thickness && - EqualityComparer.Default.Equals(a.DashStyle, b.DashStyle) && + EqualityComparer.Default.Equals(a.DashStyle, b.DashStyle) && a.LineCap == b.LineCap && a.LineJoin == b.LineJoin && a.MiterLimit == b.MiterLimit; diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs index c4fe0155f9..70b4281083 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -31,6 +31,19 @@ namespace Avalonia.Visuals.UnitTests.Media Assert.True(raised); } + [Fact] + public void Changing_DashStyle_Dashes_Raises_Invalidated() + { + var dashes = new DashStyle(); + var target = new Pen { DashStyle = dashes }; + var raised = false; + + target.Invalidated += (s, e) => raised = true; + dashes.Dashes = new[] { 0.1, 0.2 }; + + Assert.True(raised); + } + [Fact] public void Equality_Is_Implemented_Between_Mutable_And_Immutable_Pens() { @@ -45,7 +58,7 @@ namespace Avalonia.Visuals.UnitTests.Media var target2 = new ImmutablePen( brush: brush, thickness: 2, - dashStyle: DashStyle.Dash, + dashStyle: (ImmutableDashStyle)DashStyle.Dash, lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round, miterLimit: 21);