committed by
GitHub
25 changed files with 682 additions and 116 deletions
@ -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 (dashDotDot == 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); |
|||
} |
|||
} |
|||
|
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Describes how a stroke is drawn.
|
|||
/// </summary>
|
|||
public interface IPen |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the brush used to draw the stroke.
|
|||
/// </summary>
|
|||
IBrush Brush { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the style of dashed lines drawn with a <see cref="Pen"/> object.
|
|||
/// </summary>
|
|||
IDashStyle DashStyle { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the type of shape to use on both ends of a line.
|
|||
/// </summary>
|
|||
PenLineCap LineCap { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value describing how to join consecutive line or curve segments in a
|
|||
/// <see cref="PathFigure"/> contained in a <see cref="PathGeometry"/> object.
|
|||
/// </summary>
|
|||
PenLineJoin LineJoin { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the limit of the thickness of the join on a mitered corner.
|
|||
/// </summary>
|
|||
double MiterLimit { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the stroke thickness.
|
|||
/// </summary>
|
|||
double Thickness { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
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, IEquatable<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; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => Equals(obj as IDashStyle); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(IDashStyle other) |
|||
{ |
|||
if (ReferenceEquals(this, other)) |
|||
{ |
|||
return true; |
|||
} |
|||
else if (other is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (Offset != other.Offset) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return SequenceEqual(Dashes, other.Dashes); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
var hashCode = 717868523; |
|||
hashCode = hashCode * -1521134295 + Offset.GetHashCode(); |
|||
|
|||
if (Dashes != null) |
|||
{ |
|||
foreach (var i in Dashes) |
|||
{ |
|||
hashCode = hashCode * -1521134295 + i.GetHashCode(); |
|||
} |
|||
} |
|||
|
|||
return hashCode; |
|||
} |
|||
|
|||
private static bool SequenceEqual(IReadOnlyList<double> left, IReadOnlyList<double> right) |
|||
{ |
|||
if (left == right) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (left == null || right == null || left.Count != right.Count) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
for (var c = 0; c < left.Count; c++) |
|||
{ |
|||
if (left[c] != right[c]) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Media.Immutable |
|||
{ |
|||
/// <summary>
|
|||
/// Describes how a stroke is drawn.
|
|||
/// </summary>
|
|||
public class ImmutablePen : IPen, IEquatable<IPen> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pen"/> class.
|
|||
/// </summary>
|
|||
/// <param name="color">The stroke color.</param>
|
|||
/// <param name="thickness">The stroke thickness.</param>
|
|||
/// <param name="dashStyle">The dash style.</param>
|
|||
/// <param name="lineCap">Specifies the type of graphic shape to use on both ends of a line.</param>
|
|||
/// <param name="lineJoin">The line join.</param>
|
|||
/// <param name="miterLimit">The miter limit.</param>
|
|||
public ImmutablePen( |
|||
uint color, |
|||
double thickness = 1.0, |
|||
ImmutableDashStyle dashStyle = null, |
|||
PenLineCap lineCap = PenLineCap.Flat, |
|||
PenLineJoin lineJoin = PenLineJoin.Miter, |
|||
double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pen"/> class.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush used to draw.</param>
|
|||
/// <param name="thickness">The stroke thickness.</param>
|
|||
/// <param name="dashStyle">The dash style.</param>
|
|||
/// <param name="lineCap">The line cap.</param>
|
|||
/// <param name="lineJoin">The line join.</param>
|
|||
/// <param name="miterLimit">The miter limit.</param>
|
|||
public ImmutablePen( |
|||
IBrush brush, |
|||
double thickness = 1.0, |
|||
ImmutableDashStyle dashStyle = null, |
|||
PenLineCap lineCap = PenLineCap.Flat, |
|||
PenLineJoin lineJoin = PenLineJoin.Miter, |
|||
double miterLimit = 10.0) |
|||
{ |
|||
Brush = brush; |
|||
Thickness = thickness; |
|||
LineCap = lineCap; |
|||
LineJoin = lineJoin; |
|||
MiterLimit = miterLimit; |
|||
DashStyle = dashStyle; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the brush used to draw the stroke.
|
|||
/// </summary>
|
|||
public IBrush Brush { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the stroke thickness.
|
|||
/// </summary>
|
|||
public double Thickness { get; } |
|||
|
|||
/// <summary>
|
|||
/// Specifies the style of dashed lines drawn with a <see cref="Pen"/> object.
|
|||
/// </summary>
|
|||
public IDashStyle DashStyle { get; } |
|||
|
|||
/// <summary>
|
|||
/// Specifies the type of graphic shape to use on both ends of a line.
|
|||
/// </summary>
|
|||
public PenLineCap LineCap { get; } |
|||
|
|||
/// <summary>
|
|||
/// Specifies how to join consecutive line or curve segments in a <see cref="PathFigure"/>
|
|||
/// (subpaths) contained in a <see cref="PathGeometry"/> object.
|
|||
/// </summary>
|
|||
public PenLineJoin LineJoin { get; } |
|||
|
|||
/// <summary>
|
|||
/// The limit on the ratio of the miter length to half this pen's Thickness.
|
|||
/// </summary>
|
|||
public double MiterLimit { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) => Equals(obj as IPen); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(IPen other) |
|||
{ |
|||
if (ReferenceEquals(this, other)) |
|||
{ |
|||
return true; |
|||
} |
|||
else if (other is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return EqualityComparer<IBrush>.Default.Equals(Brush, other.Brush) && |
|||
Thickness == other.Thickness && |
|||
EqualityComparer<IDashStyle>.Default.Equals(DashStyle, other.DashStyle) && |
|||
LineCap == other.LineCap && |
|||
LineJoin == other.LineJoin && |
|||
MiterLimit == other.MiterLimit; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return (Brush, Thickness, DashStyle, LineCap, LineJoin, MiterLimit).GetHashCode(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Visuals.UnitTests.Media |
|||
{ |
|||
public class PenTests |
|||
{ |
|||
[Fact] |
|||
public void Changing_Thickness_Raises_Invalidated() |
|||
{ |
|||
var target = new Pen(); |
|||
var raised = false; |
|||
|
|||
target.Invalidated += (s, e) => raised = true; |
|||
target.Thickness = 18; |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_Brush_Color_Raises_Invalidated() |
|||
{ |
|||
var brush = new SolidColorBrush(Colors.Red); |
|||
var target = new Pen { Brush = brush }; |
|||
var raised = false; |
|||
|
|||
target.Invalidated += (s, e) => raised = true; |
|||
brush.Color = Colors.Green; |
|||
|
|||
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_Immutable_And_Mmutable_Pens() |
|||
{ |
|||
var brush = new SolidColorBrush(Colors.Red); |
|||
var target1 = new ImmutablePen( |
|||
brush: brush, |
|||
thickness: 2, |
|||
dashStyle: (ImmutableDashStyle)DashStyle.Dash, |
|||
lineCap: PenLineCap.Round, |
|||
lineJoin: PenLineJoin.Round, |
|||
miterLimit: 21); |
|||
var target2 = new Pen( |
|||
brush: brush, |
|||
thickness: 2, |
|||
dashStyle: DashStyle.Dash, |
|||
lineCap: PenLineCap.Round, |
|||
lineJoin: PenLineJoin.Round, |
|||
miterLimit: 21); |
|||
|
|||
Assert.True(Equals(target1, target2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Equality_Is_Implemented_Between_Mutable_And_Immutable_DashStyles() |
|||
{ |
|||
var brush = new SolidColorBrush(Colors.Red); |
|||
var target1 = new ImmutablePen( |
|||
brush: brush, |
|||
thickness: 2, |
|||
dashStyle: new ImmutableDashStyle(new[] { 0.1, 0.2 }, 5), |
|||
lineCap: PenLineCap.Round, |
|||
lineJoin: PenLineJoin.Round, |
|||
miterLimit: 21); |
|||
var target2 = new Pen( |
|||
brush: brush, |
|||
thickness: 2, |
|||
dashStyle: new DashStyle(new[] { 0.1, 0.2 }, 5), |
|||
lineCap: PenLineCap.Round, |
|||
lineJoin: PenLineJoin.Round, |
|||
miterLimit: 21); |
|||
|
|||
Assert.True(Equals(target1, target2)); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue