committed by
GitHub
25 changed files with 682 additions and 116 deletions
@ -1,72 +1,114 @@ |
|||||
namespace Avalonia.Media |
namespace Avalonia.Media |
||||
{ |
{ |
||||
|
using System; |
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
using Avalonia.Animation; |
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; |
/// <summary>
|
||||
public static DashStyle Dash |
/// Defines the <see cref="Dashes"/> property.
|
||||
{ |
/// </summary>
|
||||
get |
public static readonly AvaloniaProperty<IReadOnlyList<double>> DashesProperty = |
||||
{ |
AvaloniaProperty.Register<DashStyle, IReadOnlyList<double>>(nameof(Dashes)); |
||||
if (dashDotDot == null) |
|
||||
{ |
|
||||
dash = new DashStyle(new double[] { 2, 2 }, 1); |
|
||||
} |
|
||||
|
|
||||
return dash; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
/// <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; |
/// <summary>
|
||||
public static DashStyle Dot |
/// 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; |
/// <summary>
|
||||
public static DashStyle DashDot |
/// 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 |
Dashes = (IReadOnlyList<double>)dashes?.ToList() ?? Array.Empty<double>(); |
||||
{ |
Offset = offset; |
||||
if (dashDot == null) |
|
||||
{ |
|
||||
dashDot = new DashStyle(new double[] { 2, 2, 0, 2 }, 1); |
|
||||
} |
|
||||
|
|
||||
return dashDot; |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
private static DashStyle dashDotDot; |
static DashStyle() |
||||
public static DashStyle DashDotDot |
|
||||
{ |
{ |
||||
get |
void RaiseInvalidated(AvaloniaPropertyChangedEventArgs e) |
||||
{ |
{ |
||||
if (dashDotDot == null) |
((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty); |
||||
{ |
|
||||
dashDotDot = new DashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1); |
|
||||
} |
|
||||
|
|
||||
return dashDotDot; |
|
||||
} |
} |
||||
|
|
||||
|
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; |
get => GetValue(DashesProperty); |
||||
this.Offset = offset; |
set => SetValue(DashesProperty, value); |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </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