Browse Source

Make DashStyle mutable.

pull/2744/head
Steven Kirk 7 years ago
parent
commit
2af5a03c2d
  1. 21
      src/Avalonia.Visuals/Media/BrushExtensions.cs
  2. 128
      src/Avalonia.Visuals/Media/DashStyle.cs
  3. 20
      src/Avalonia.Visuals/Media/IDashStyle.cs
  4. 2
      src/Avalonia.Visuals/Media/IPen.cs
  5. 30
      src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs
  6. 6
      src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs
  7. 16
      src/Avalonia.Visuals/Media/Pen.cs
  8. 15
      tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs

21
src/Avalonia.Visuals/Media/BrushExtensions.cs

@ -24,12 +24,27 @@ namespace Avalonia.Media
}
/// <summary>
/// Converts a pen to a pen with an immutable brush
/// Converts a dash style to an immutable dash style.
/// </summary>
/// <param name="style">The dash style.</param>
/// <returns>
/// The result of calling <see cref="DashStyle.ToImmutable"/> if the style is mutable,
/// otherwise <paramref name="style"/>.
/// </returns>
public static ImmutableDashStyle ToImmutable(this IDashStyle style)
{
Contract.Requires<ArgumentNullException>(style != null);
return style as ImmutableDashStyle ?? ((DashStyle)style).ToImmutable();
}
/// <summary>
/// Converts a pen to an immutable pen.
/// </summary>
/// <param name="pen">The pen.</param>
/// <returns>
/// A copy of the pen with an immutable brush, or <paramref name="pen"/> if the pen's brush
/// is already immutable or null.
/// The result of calling <see cref="Pen.ToImmutable"/> if the brush is mutable,
/// otherwise <paramref name="pen"/>.
/// </returns>
public static ImmutablePen ToImmutable(this IPen pen)
{

128
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
/// <summary>
/// Represents the sequence of dashes and gaps that will be applied by a <see cref="Pen"/>.
/// </summary>
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;
}
}
/// <summary>
/// Defines the <see cref="Dashes"/> property.
/// </summary>
public static readonly AvaloniaProperty<IReadOnlyList<double>> DashesProperty =
AvaloniaProperty.Register<DashStyle, IReadOnlyList<double>>(nameof(Dashes));
/// <summary>
/// Defines the <see cref="Offset"/> property.
/// </summary>
public static readonly AvaloniaProperty<double> OffsetProperty =
AvaloniaProperty.Register<DashStyle, double>(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
/// <summary>
/// Initializes a new instance of the <see cref="DashStyle"/> class.
/// </summary>
public DashStyle()
: this(null, 0)
{
get { return dot ?? (dot = new DashStyle(new double[] {0, 2}, 0)); }
}
private static DashStyle dashDot;
public static DashStyle DashDot
/// <summary>
/// Initializes a new instance of the <see cref="DashStyle"/> class.
/// </summary>
/// <param name="dashes">The dashes collection.</param>
/// <param name="offset">The dash sequence offset.</param>
public DashStyle(IEnumerable<double> dashes, double offset)
{
get
{
if (dashDot == null)
{
dashDot = new DashStyle(new double[] { 2, 2, 0, 2 }, 1);
}
return dashDot;
}
Dashes = (IReadOnlyList<double>)dashes?.ToList() ?? Array.Empty<double>();
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);
}
/// <summary>
/// Represents a dashed <see cref="DashStyle"/>.
/// </summary>
public static IDashStyle Dash =>
s_dash ?? (s_dash = new ImmutableDashStyle(new double[] { 2, 2 }, 1));
/// <summary>
/// Represents a dotted <see cref="DashStyle"/>.
/// </summary>
public static IDashStyle Dot =>
s_dot ?? (s_dot = new ImmutableDashStyle(new double[] { 0, 2 }, 0));
/// <summary>
/// Represents a dashed dotted <see cref="DashStyle"/>.
/// </summary>
public static IDashStyle DashDot =>
s_dashDot ?? (s_dashDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2 }, 1));
/// <summary>
/// Represents a dashed double dotted <see cref="DashStyle"/>.
/// </summary>
public static IDashStyle DashDotDot =>
s_dashDotDot ?? (s_dashDotDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1));
public DashStyle(IReadOnlyList<double> dashes = null, double offset = 0.0)
/// <summary>
/// Gets or sets the length of alternating dashes and gaps.
/// </summary>
public IReadOnlyList<double> Dashes
{
this.Dashes = dashes;
this.Offset = offset;
get => GetValue(DashesProperty);
set => SetValue(DashesProperty, value);
}
/// <summary>
/// Gets and sets the length of alternating dashes and gaps.
/// Gets or sets how far in the dash sequence the stroke will start.
/// </summary>
public IReadOnlyList<double> Dashes { get; }
public double Offset
{
get => GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
public double Offset { get; }
/// <summary>
/// Raised when the dash style changes.
/// </summary>
public event EventHandler Invalidated;
/// <summary>
/// Returns an immutable clone of the <see cref="DashStyle"/>.
/// </summary>
/// <returns></returns>
public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset);
}
}

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

@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Avalonia.Media
{
/// <summary>
/// Represents the sequence of dashes and gaps that will be applied by a <see cref="Pen"/>.
/// </summary>
public interface IDashStyle
{
/// <summary>
/// Gets or sets the length of alternating dashes and gaps.
/// </summary>
IReadOnlyList<double> Dashes { get; }
/// <summary>
/// Gets or sets how far in the dash sequence the stroke will start.
/// </summary>
double Offset { get; }
}
}

2
src/Avalonia.Visuals/Media/IPen.cs

@ -13,7 +13,7 @@
/// <summary>
/// Gets the style of dashed lines drawn with a <see cref="Pen"/> object.
/// </summary>
DashStyle DashStyle { get; }
IDashStyle DashStyle { get; }
/// <summary>
/// Gets the type of shape to use on both ends of a line.

30
src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Media.Immutable
{
/// <summary>
/// Represents the sequence of dashes and gaps that will be applied by an
/// <see cref="ImmutablePen"/>.
/// </summary>
public class ImmutableDashStyle : IDashStyle
{
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableDashStyle"/> class.
/// </summary>
/// <param name="dashes">The dashes collection.</param>
/// <param name="offset">The dash sequence offset.</param>
public ImmutableDashStyle(IEnumerable<double> dashes, double offset)
{
Dashes = (IReadOnlyList<double>)dashes?.ToList() ?? Array.Empty<double>();
Offset = offset;
}
/// <inheritdoc/>
public IReadOnlyList<double> Dashes { get; }
/// <inheritdoc/>
public double Offset { get; }
}
}

6
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
/// <summary>
/// Specifies the style of dashed lines drawn with a <see cref="Pen"/> object.
/// </summary>
public DashStyle DashStyle { get; }
public IDashStyle DashStyle { get; }
/// <summary>
/// Specifies the type of graphic shape to use on both ends of a line.

16
src/Avalonia.Visuals/Media/Pen.cs

@ -28,8 +28,8 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="DashStyle"/> property.
/// </summary>
public static readonly StyledProperty<DashStyle> DashStyleProperty =
AvaloniaProperty.Register<Pen, DashStyle>(nameof(DashStyle));
public static readonly StyledProperty<IDashStyle> DashStyleProperty =
AvaloniaProperty.Register<Pen, IDashStyle>(nameof(DashStyle));
/// <summary>
/// Defines the <see cref="LineCap"/> 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
/// <summary>
/// Gets or sets the style of dashed lines drawn with a <see cref="Pen"/> object.
/// </summary>
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<IBrush>.Default.GetHashCode(pen.Brush);
hashCode = hashCode * -1521134295 + pen.Thickness.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<DashStyle>.Default.GetHashCode(pen.DashStyle);
hashCode = hashCode * -1521134295 + EqualityComparer<IDashStyle>.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<IBrush>.Default.Equals(a.Brush, b.Brush) &&
a.Thickness == b.Thickness &&
EqualityComparer<DashStyle>.Default.Equals(a.DashStyle, b.DashStyle) &&
EqualityComparer<IDashStyle>.Default.Equals(a.DashStyle, b.DashStyle) &&
a.LineCap == b.LineCap &&
a.LineJoin == b.LineJoin &&
a.MiterLimit == b.MiterLimit;

15
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);

Loading…
Cancel
Save