diff --git a/src/Avalonia.Visuals/Media/BrushExtensions.cs b/src/Avalonia.Visuals/Media/BrushExtensions.cs
index 522953eb04..87e698e705 100644
--- a/src/Avalonia.Visuals/Media/BrushExtensions.cs
+++ b/src/Avalonia.Visuals/Media/BrushExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
@@ -23,27 +24,33 @@ 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 Pen ToImmutable(this Pen pen)
+ public static ImmutablePen ToImmutable(this IPen pen)
{
Contract.Requires(pen != null);
- var brush = pen.Brush?.ToImmutable();
- return ReferenceEquals(pen.Brush, brush) ?
- pen :
- new Pen(
- brush,
- thickness: pen.Thickness,
- dashStyle: pen.DashStyle,
- lineCap: pen.LineCap,
- lineJoin: pen.LineJoin,
- miterLimit: pen.MiterLimit);
+ return pen as ImmutablePen ?? ((Pen)pen).ToImmutable();
}
}
}
diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs
index c7e1db57b2..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 (dashDotDot == 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/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index d3af71ffcb..4c9bf9ebd4 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -94,7 +94,7 @@ namespace Avalonia.Media
/// The stroke pen.
/// The first point of the line.
/// The second point of the line.
- public void DrawLine(Pen pen, Point p1, Point p2)
+ public void DrawLine(IPen pen, Point p1, Point p2)
{
if (PenIsVisible(pen))
{
@@ -108,7 +108,7 @@ namespace Avalonia.Media
/// The fill brush.
/// The stroke pen.
/// The geometry.
- public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry)
+ public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry)
{
Contract.Requires(geometry != null);
@@ -124,7 +124,7 @@ namespace Avalonia.Media
/// The pen.
/// The rectangle bounds.
/// The corner radius.
- public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f)
+ public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0.0f)
{
if (PenIsVisible(pen))
{
@@ -328,7 +328,7 @@ namespace Avalonia.Media
PlatformImpl.Dispose();
}
- private static bool PenIsVisible(Pen pen)
+ private static bool PenIsVisible(IPen pen)
{
return pen?.Brush != null && pen.Thickness > 0;
}
diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs
index ac0cc1c17d..3dad10fb8f 100644
--- a/src/Avalonia.Visuals/Media/GeometryDrawing.cs
+++ b/src/Avalonia.Visuals/Media/GeometryDrawing.cs
@@ -23,7 +23,7 @@
public static readonly StyledProperty PenProperty =
AvaloniaProperty.Register(nameof(Pen));
- public Pen Pen
+ public IPen Pen
{
get => GetValue(PenProperty);
set => SetValue(PenProperty, value);
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
new file mode 100644
index 0000000000..0cdac312cc
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/IPen.cs
@@ -0,0 +1,39 @@
+namespace Avalonia.Media
+{
+ ///
+ /// Describes how a stroke is drawn.
+ ///
+ public interface IPen
+ {
+ ///
+ /// Gets the brush used to draw the stroke.
+ ///
+ IBrush Brush { get; }
+
+ ///
+ /// Gets the style of dashed lines drawn with a object.
+ ///
+ IDashStyle DashStyle { get; }
+
+ ///
+ /// Gets the type of shape to use on both ends of a line.
+ ///
+ PenLineCap LineCap { get; }
+
+ ///
+ /// Gets a value describing how to join consecutive line or curve segments in a
+ /// contained in a object.
+ ///
+ PenLineJoin LineJoin { get; }
+
+ ///
+ /// Gets the limit of the thickness of the join on a mitered corner.
+ ///
+ double MiterLimit { get; }
+
+ ///
+ /// Gets the stroke thickness.
+ ///
+ double Thickness { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs
new file mode 100644
index 0000000000..e9a52fe6ed
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs
@@ -0,0 +1,93 @@
+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, IEquatable
+ {
+ ///
+ /// 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; }
+
+ ///
+ public override bool Equals(object obj) => Equals(obj as IDashStyle);
+
+ ///
+ 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);
+ }
+
+ ///
+ 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 left, IReadOnlyList 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;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs
new file mode 100644
index 0000000000..4b3bd640cb
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs
@@ -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
+{
+ ///
+ /// Describes how a stroke is drawn.
+ ///
+ public class ImmutablePen : IPen, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The stroke color.
+ /// The stroke thickness.
+ /// The dash style.
+ /// Specifies the type of graphic shape to use on both ends of a line.
+ /// The line join.
+ /// The miter limit.
+ 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)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The brush used to draw.
+ /// The stroke thickness.
+ /// The dash style.
+ /// The line cap.
+ /// The line join.
+ /// The miter limit.
+ 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;
+ }
+
+ ///
+ /// Gets the brush used to draw the stroke.
+ ///
+ public IBrush Brush { get; }
+
+ ///
+ /// Gets the stroke thickness.
+ ///
+ public double Thickness { get; }
+
+ ///
+ /// Specifies the style of dashed lines drawn with a object.
+ ///
+ public IDashStyle DashStyle { get; }
+
+ ///
+ /// Specifies the type of graphic shape to use on both ends of a line.
+ ///
+ public PenLineCap LineCap { get; }
+
+ ///
+ /// Specifies how to join consecutive line or curve segments in a
+ /// (subpaths) contained in a object.
+ ///
+ public PenLineJoin LineJoin { get; }
+
+ ///
+ /// The limit on the ratio of the miter length to half this pen's Thickness.
+ ///
+ public double MiterLimit { get; }
+
+ ///
+ public override bool Equals(object obj) => Equals(obj as IPen);
+
+ ///
+ public bool Equals(IPen other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+ else if (other is null)
+ {
+ return false;
+ }
+
+ return EqualityComparer.Default.Equals(Brush, other.Brush) &&
+ Thickness == other.Thickness &&
+ EqualityComparer.Default.Equals(DashStyle, other.DashStyle) &&
+ LineCap == other.LineCap &&
+ LineJoin == other.LineJoin &&
+ MiterLimit == other.MiterLimit;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return (Brush, Thickness, DashStyle, LineCap, LineJoin, MiterLimit).GetHashCode();
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs
index ee427c913b..b88fae28ff 100644
--- a/src/Avalonia.Visuals/Media/Pen.cs
+++ b/src/Avalonia.Visuals/Media/Pen.cs
@@ -1,13 +1,61 @@
// 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;
+using Avalonia.Media.Immutable;
+using Avalonia.Utilities;
+
namespace Avalonia.Media
{
///
/// Describes how a stroke is drawn.
///
- public class Pen
+ public class Pen : AvaloniaObject, IPen
{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty BrushProperty =
+ AvaloniaProperty.Register(nameof(Brush));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ThicknessProperty =
+ AvaloniaProperty.Register(nameof(Thickness), 1.0);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty DashStyleProperty =
+ AvaloniaProperty.Register(nameof(DashStyle));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty LineCapProperty =
+ AvaloniaProperty.Register(nameof(LineCap));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty LineJoinProperty =
+ AvaloniaProperty.Register(nameof(LineJoin));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MiterLimitProperty =
+ AvaloniaProperty.Register(nameof(MiterLimit), 10.0);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Pen()
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -20,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)
@@ -39,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)
@@ -52,34 +100,139 @@ namespace Avalonia.Media
DashStyle = dashStyle;
}
+ static Pen()
+ {
+ AffectsRender(
+ BrushProperty,
+ ThicknessProperty,
+ DashStyleProperty,
+ LineCapProperty,
+ LineJoinProperty,
+ MiterLimitProperty);
+ }
+
+ ///
+ /// Gets or sets the brush used to draw the stroke.
+ ///
+ public IBrush Brush
+ {
+ get => GetValue(BrushProperty);
+ set => SetValue(BrushProperty, value);
+ }
+
+ ///
+ /// Gets or sets the stroke thickness.
+ ///
+ public double Thickness
+ {
+ get => GetValue(ThicknessProperty);
+ set => SetValue(ThicknessProperty, value);
+ }
+
+ ///
+ /// Gets or sets the style of dashed lines drawn with a object.
+ ///
+ public IDashStyle DashStyle
+ {
+ get => GetValue(DashStyleProperty);
+ set => SetValue(DashStyleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the type of shape to use on both ends of a line.
+ ///
+ public PenLineCap LineCap
+ {
+ get => GetValue(LineCapProperty);
+ set => SetValue(LineCapProperty, value);
+ }
+
///
- /// Gets the brush used to draw the stroke.
+ /// Gets or sets the join style for the ends of two consecutive lines drawn with this
+ /// .
///
- public IBrush Brush { get; }
+ public PenLineJoin LineJoin
+ {
+ get => GetValue(LineJoinProperty);
+ set => SetValue(LineJoinProperty, value);
+ }
///
- /// Gets the stroke thickness.
+ /// Gets or sets the limit of the thickness of the join on a mitered corner.
///
- public double Thickness { get; }
+ public double MiterLimit
+ {
+ get => GetValue(MiterLimitProperty);
+ set => SetValue(MiterLimitProperty, value);
+ }
///
- /// Specifies the style of dashed lines drawn with a object.
+ /// Raised when the pen changes.
///
- public DashStyle DashStyle { get; }
+ public event EventHandler Invalidated;
///
- /// Specifies the type of graphic shape to use on both ends of a line.
+ /// Creates an immutable clone of the brush.
///
- public PenLineCap LineCap { get; }
+ /// The immutable clone.
+ public ImmutablePen ToImmutable()
+ {
+ return new ImmutablePen(
+ Brush?.ToImmutable(),
+ Thickness,
+ DashStyle?.ToImmutable(),
+ LineCap,
+ LineJoin,
+ MiterLimit);
+ }
///
- /// Specifies how to join consecutive line or curve segments in a (subpath) contained in a object.
+ /// Marks a property as affecting the pen's visual representation.
///
- public PenLineJoin LineJoin { get; }
+ /// The properties.
+ ///
+ /// After a call to this method in a pen's static constructor, any change to the
+ /// property will cause the event to be raised on the pen.
+ ///
+ protected static void AffectsRender(params AvaloniaProperty[] properties)
+ where T : Pen
+ {
+ void Invalidate(AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Sender is T sender)
+ {
+ if (e.OldValue is IAffectsRender oldValue)
+ {
+ WeakEventHandlerManager.Unsubscribe(
+ oldValue,
+ nameof(oldValue.Invalidated),
+ sender.AffectsRenderInvalidated);
+ }
+
+ if (e.NewValue is IAffectsRender newValue)
+ {
+ WeakEventHandlerManager.Subscribe(
+ newValue,
+ nameof(newValue.Invalidated),
+ sender.AffectsRenderInvalidated);
+ }
+
+ sender.RaiseInvalidated(EventArgs.Empty);
+ }
+ }
+
+ foreach (var property in properties)
+ {
+ property.Changed.Subscribe(Invalidate);
+ }
+ }
///
- /// The limit on the ratio of the miter length to half this pen's Thickness.
+ /// Raises the event.
///
- public double MiterLimit { get; }
+ /// The event args.
+ protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
+
+ private void AffectsRenderInvalidated(object sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty);
}
}
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
index e5be04ebf9..f74c551fe0 100644
--- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
@@ -50,7 +50,7 @@ namespace Avalonia.Platform
/// The stroke pen.
/// The first point of the line.
/// The second point of the line.
- void DrawLine(Pen pen, Point p1, Point p2);
+ void DrawLine(IPen pen, Point p1, Point p2);
///
/// Draws a geometry.
@@ -58,7 +58,7 @@ namespace Avalonia.Platform
/// The fill brush.
/// The stroke pen.
/// The geometry.
- void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry);
+ void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry);
///
/// Draws the outline of a rectangle.
@@ -66,7 +66,7 @@ namespace Avalonia.Platform
/// The pen.
/// The rectangle bounds.
/// The corner radius.
- void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f);
+ void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0.0f);
///
/// Draws text.
diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
index 4e8e6521bd..b762859d1d 100644
--- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Platform
///
/// The pen to use. May be null.
/// The bounding rectangle.
- Rect GetRenderBounds(Pen pen);
+ Rect GetRenderBounds(IPen pen);
///
/// Indicates whether the geometry's fill contains the specified point.
@@ -42,7 +42,7 @@ namespace Avalonia.Platform
/// The stroke to use.
/// The point.
/// true if the geometry contains the point; otherwise, false.
- bool StrokeContains(Pen pen, Point point);
+ bool StrokeContains(IPen pen, Point point);
///
/// Makes a clone of the geometry with the specified transform.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
index 4c09dc2ddd..b2c0581388 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph
///
internal abstract class BrushDrawOperation : DrawOperation
{
- public BrushDrawOperation(Rect bounds, Matrix transform, Pen pen)
+ public BrushDrawOperation(Rect bounds, Matrix transform, IPen pen)
: base(bounds, transform, pen)
{
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index 0b33851911..3af56f5215 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -100,7 +100,7 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
+ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
var next = NextDrawAs();
@@ -137,7 +137,7 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public void DrawLine(Pen pen, Point p1, Point p2)
+ public void DrawLine(IPen pen, Point p1, Point p2)
{
var next = NextDrawAs();
@@ -152,7 +152,7 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
+ public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0)
{
var next = NextDrawAs();
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
index 1a5a6fad3f..d9dfd8bd55 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
///
internal abstract class DrawOperation : IDrawOperation
{
- public DrawOperation(Rect bounds, Matrix transform, Pen pen)
+ public DrawOperation(Rect bounds, Matrix transform, IPen pen)
{
bounds = bounds.Inflate((pen?.Thickness ?? 0) / 2).TransformToAABB(transform);
Bounds = new Rect(
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
index 2d01b117d9..d5aa1251f3 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using Avalonia.Media;
+using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
@@ -24,7 +25,7 @@ namespace Avalonia.Rendering.SceneGraph
public GeometryNode(
Matrix transform,
IBrush brush,
- Pen pen,
+ IPen pen,
IGeometryImpl geometry,
IDictionary childScenes = null)
: base(geometry.GetRenderBounds(pen), transform, null)
@@ -49,7 +50,7 @@ namespace Avalonia.Rendering.SceneGraph
///
/// Gets the stroke pen.
///
- public Pen Pen { get; }
+ public ImmutablePen Pen { get; }
///
/// Gets the geometry to draw.
@@ -71,11 +72,11 @@ namespace Avalonia.Rendering.SceneGraph
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
///
- public bool Equals(Matrix transform, IBrush brush, Pen pen, IGeometryImpl geometry)
+ public bool Equals(Matrix transform, IBrush brush, IPen pen, IGeometryImpl geometry)
{
return transform == Transform &&
Equals(brush, Brush) &&
- pen == Pen &&
+ Equals(Pen, pen) &&
Equals(geometry, Geometry);
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
index 11c763fcc9..9a65fac078 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using Avalonia.Media;
+using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
@@ -23,7 +24,7 @@ namespace Avalonia.Rendering.SceneGraph
/// Child scenes for drawing visual brushes.
public LineNode(
Matrix transform,
- Pen pen,
+ IPen pen,
Point p1,
Point p2,
IDictionary childScenes = null)
@@ -44,7 +45,7 @@ namespace Avalonia.Rendering.SceneGraph
///
/// Gets the stroke pen.
///
- public Pen Pen { get; }
+ public ImmutablePen Pen { get; }
///
/// Gets the start point of the line.
@@ -71,9 +72,9 @@ namespace Avalonia.Rendering.SceneGraph
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
///
- public bool Equals(Matrix transform, Pen pen, Point p1, Point p2)
+ public bool Equals(Matrix transform, IPen pen, Point p1, Point p2)
{
- return transform == Transform && pen == Pen && p1 == P1 && p2 == P2;
+ return transform == Transform && Equals(Pen, pen) && p1 == P1 && p2 == P2;
}
public override void Render(IDrawingContextImpl context)
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
index c622dc8a43..0f3581b84c 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using Avalonia.Media;
+using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
@@ -25,7 +26,7 @@ namespace Avalonia.Rendering.SceneGraph
public RectangleNode(
Matrix transform,
IBrush brush,
- Pen pen,
+ IPen pen,
Rect rect,
float cornerRadius,
IDictionary childScenes = null)
@@ -52,7 +53,7 @@ namespace Avalonia.Rendering.SceneGraph
///
/// Gets the stroke pen.
///
- public Pen Pen { get; }
+ public ImmutablePen Pen { get; }
///
/// Gets the rectangle to draw.
@@ -80,11 +81,11 @@ namespace Avalonia.Rendering.SceneGraph
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
///
- public bool Equals(Matrix transform, IBrush brush, Pen pen, Rect rect, float cornerRadius)
+ public bool Equals(Matrix transform, IBrush brush, IPen pen, Rect rect, float cornerRadius)
{
return transform == Transform &&
Equals(brush, Brush) &&
- pen == Pen &&
+ Equals(Pen, pen) &&
rect == Rect &&
cornerRadius == CornerRadius;
}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 262d87d8b6..47e651ce91 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -154,7 +154,7 @@ namespace Avalonia.Skia
}
///
- public void DrawLine(Pen pen, Point p1, Point p2)
+ public void DrawLine(IPen pen, Point p1, Point p2)
{
using (var paint = CreatePaint(pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
{
@@ -163,7 +163,7 @@ namespace Avalonia.Skia
}
///
- public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
+ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
var impl = (GeometryImpl) geometry;
var size = geometry.Bounds.Size;
@@ -184,7 +184,7 @@ namespace Avalonia.Skia
}
///
- public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
+ public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0)
{
using (var paint = CreatePaint(pen, rect.Size))
{
@@ -561,7 +561,7 @@ namespace Avalonia.Skia
/// Source pen.
/// Target size.
///
- private PaintWrapper CreatePaint(Pen pen, Size targetSize)
+ private PaintWrapper CreatePaint(IPen pen, Size targetSize)
{
// In Skia 0 thickness means - use hairline rendering
// and for us it means - there is nothing rendered.
diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs
index 5940de418e..23980fb913 100644
--- a/src/Skia/Avalonia.Skia/GeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs
@@ -26,7 +26,7 @@ namespace Avalonia.Skia
}
///
- public bool StrokeContains(Pen pen, Point point)
+ public bool StrokeContains(IPen pen, Point point)
{
// Skia requires to compute stroke path to check for point containment.
// Due to that we are caching using stroke width.
@@ -89,7 +89,7 @@ namespace Avalonia.Skia
}
///
- public Rect GetRenderBounds(Pen pen)
+ public Rect GetRenderBounds(IPen pen)
{
var strokeWidth = (float)(pen?.Thickness ?? 0);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index e90d444c44..39d801eb2f 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -174,7 +174,7 @@ namespace Avalonia.Direct2D1.Media
/// The stroke pen.
/// The first point of the line.
/// The second point of the line.
- public void DrawLine(Pen pen, Point p1, Point p2)
+ public void DrawLine(IPen pen, Point p1, Point p2)
{
if (pen != null)
{
@@ -202,7 +202,7 @@ namespace Avalonia.Direct2D1.Media
/// The fill brush.
/// The stroke pen.
/// The geometry.
- public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
+ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
if (brush != null)
{
@@ -236,7 +236,7 @@ namespace Avalonia.Direct2D1.Media
/// The pen.
/// The rectangle bounds.
/// The corner radius.
- public void DrawRectangle(Pen pen, Rect rect, float cornerRadius)
+ public void DrawRectangle(IPen pen, Rect rect, float cornerRadius)
{
using (var brush = CreateBrush(pen.Brush, rect.Size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
index 7c8ddaca3f..51ca2520ad 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Direct2D1.Media
public Geometry Geometry { get; }
///
- public Rect GetRenderBounds(Avalonia.Media.Pen pen)
+ public Rect GetRenderBounds(Avalonia.Media.IPen pen)
{
return Geometry.GetWidenedBounds((float)(pen?.Thickness ?? 0)).ToAvalonia();
}
@@ -46,7 +46,7 @@ namespace Avalonia.Direct2D1.Media
}
///
- public bool StrokeContains(Avalonia.Media.Pen pen, Point point)
+ public bool StrokeContains(Avalonia.Media.IPen pen, Point point)
{
return Geometry.StrokeContainsPoint(point.ToSharpDX(), (float)(pen?.Thickness ?? 0));
}
diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
index 6b0d30f250..065895859d 100644
--- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
+++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
@@ -109,7 +109,7 @@ namespace Avalonia.Direct2D1
/// The pen to convert.
/// The render target.
/// The Direct2D brush.
- public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, SharpDX.Direct2D1.RenderTarget renderTarget)
+ public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.IPen pen, SharpDX.Direct2D1.RenderTarget renderTarget)
{
return pen.ToDirect2DStrokeStyle(renderTarget.Factory);
}
@@ -120,7 +120,7 @@ namespace Avalonia.Direct2D1
/// The pen to convert.
/// The factory associated with this resource.
/// The Direct2D brush.
- public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory)
+ public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.IPen pen, Factory factory)
{
var d2dLineCap = pen.LineCap.ToDirect2D();
diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
index 63da9ed3f0..4fa3fbf523 100644
--- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
+++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
@@ -47,12 +47,12 @@ namespace Avalonia.UnitTests
return _context.FillContains(point);
}
- public bool StrokeContains(Pen pen, Point point)
+ public bool StrokeContains(IPen pen, Point point)
{
return false;
}
- public Rect GetRenderBounds(Pen pen) => Bounds;
+ public Rect GetRenderBounds(IPen pen) => Bounds;
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs
new file mode 100644
index 0000000000..418ac7576b
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs
@@ -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));
+ }
+ }
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
index 03470670d2..d31210bc71 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
@@ -96,7 +96,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
return _impl.FillContains(point);
}
- public Rect GetRenderBounds(Pen pen)
+ public Rect GetRenderBounds(IPen pen)
{
throw new NotImplementedException();
}
@@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
return _impl;
}
- public bool StrokeContains(Pen pen, Point point)
+ public bool StrokeContains(IPen pen, Point point)
{
throw new NotImplementedException();
}