From ec45084e6a662c488047bff17a51abf5dddd9a64 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Jul 2019 20:54:40 +0200 Subject: [PATCH 01/12] Make Pen mutable. And add `IPen` interface and `ImmutablePen` class. --- src/Avalonia.Visuals/Media/BrushExtensions.cs | 14 +-- src/Avalonia.Visuals/Media/DrawingContext.cs | 8 +- src/Avalonia.Visuals/Media/GeometryDrawing.cs | 2 +- src/Avalonia.Visuals/Media/IMutablePen.cs | 17 +++ src/Avalonia.Visuals/Media/IPen.cs | 39 ++++++ .../Media/Immutable/ImmutablePen.cs | 85 +++++++++++++ src/Avalonia.Visuals/Media/Pen.cs | 117 ++++++++++++++++-- .../Platform/IDrawingContextImpl.cs | 6 +- .../Platform/IGeometryImpl.cs | 4 +- .../SceneGraph/BrushDrawOperation.cs | 2 +- .../SceneGraph/DeferredDrawingContextImpl.cs | 6 +- .../Rendering/SceneGraph/DrawOperation.cs | 2 +- .../Rendering/SceneGraph/GeometryNode.cs | 9 +- .../Rendering/SceneGraph/LineNode.cs | 7 +- .../Rendering/SceneGraph/RectangleNode.cs | 9 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 8 +- src/Skia/Avalonia.Skia/GeometryImpl.cs | 4 +- .../Media/DrawingContextImpl.cs | 6 +- .../Avalonia.Direct2D1/Media/GeometryImpl.cs | 4 +- .../Avalonia.Direct2D1/PrimitiveExtensions.cs | 4 +- .../MockStreamGeometryImpl.cs | 4 +- .../VisualTree/MockRenderInterface.cs | 4 +- 22 files changed, 294 insertions(+), 67 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/IMutablePen.cs create mode 100644 src/Avalonia.Visuals/Media/IPen.cs create mode 100644 src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs diff --git a/src/Avalonia.Visuals/Media/BrushExtensions.cs b/src/Avalonia.Visuals/Media/BrushExtensions.cs index 522953eb04..081029e205 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 { @@ -30,20 +31,11 @@ namespace Avalonia.Media /// A copy of the pen with an immutable brush, or if the pen's brush /// is already immutable or null. /// - 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 ?? ((IMutablePen)pen).ToImmutable(); } } } 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/IMutablePen.cs b/src/Avalonia.Visuals/Media/IMutablePen.cs new file mode 100644 index 0000000000..8b02929dec --- /dev/null +++ b/src/Avalonia.Visuals/Media/IMutablePen.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Media.Immutable; + +namespace Avalonia.Media +{ + /// + /// Represents a mutable pen which can return an immutable clone of itself. + /// + public interface IMutablePen : IPen, IAffectsRender + { + /// + /// Creates an immutable clone of the pen. + /// + /// The immutable clone. + ImmutablePen ToImmutable(); + } +} diff --git a/src/Avalonia.Visuals/Media/IPen.cs b/src/Avalonia.Visuals/Media/IPen.cs new file mode 100644 index 0000000000..589595bb5c --- /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. + /// + DashStyle 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/ImmutablePen.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs new file mode 100644 index 0000000000..bcfec5dccf --- /dev/null +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs @@ -0,0 +1,85 @@ +// 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. + +namespace Avalonia.Media.Immutable +{ + /// + /// Describes how a stroke is drawn. + /// + public class ImmutablePen : IPen + { + /// + /// 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, + DashStyle 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, + DashStyle 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 DashStyle 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 (subpath) 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; } + } +} diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index ee427c913b..2bfd4f472e 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -1,13 +1,59 @@ // 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 Avalonia.Media.Immutable; + namespace Avalonia.Media { /// /// Describes how a stroke is drawn. /// - public class Pen + public class Pen : AvaloniaObject, IMutablePen { + /// + /// 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. /// @@ -53,33 +99,78 @@ namespace Avalonia.Media } /// - /// Gets the brush used to draw the stroke. + /// 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 IBrush Brush { get; } + public DashStyle DashStyle + { + get => GetValue(DashStyleProperty); + set => SetValue(DashStyleProperty, value); + } /// - /// Gets the stroke thickness. + /// Gets or sets the type of shape to use on both ends of a line. /// - public double Thickness { get; } + public PenLineCap LineCap + { + get => GetValue(LineCapProperty); + set => SetValue(LineCapProperty, value); + } /// - /// Specifies the style of dashed lines drawn with a object. + /// Gets or sets the join style for the ends of two consecutive lines drawn with this + /// . /// - public DashStyle DashStyle { get; } + public PenLineJoin LineJoin + { + get => GetValue(LineJoinProperty); + set => SetValue(LineJoinProperty, value); + } /// - /// Specifies the type of graphic shape to use on both ends of a line. + /// Gets or sets the limit of the thickness of the join on a mitered corner. /// - public PenLineCap LineCap { get; } + public double MiterLimit + { + get => GetValue(MiterLimitProperty); + set => SetValue(MiterLimitProperty, value); + } /// - /// Specifies how to join consecutive line or curve segments in a (subpath) contained in a object. + /// Raised when the pen changes. /// - public PenLineJoin LineJoin { get; } + public event EventHandler Invalidated; /// - /// The limit on the ratio of the miter length to half this pen's Thickness. + /// Creates an immutable clone of the brush. /// - public double MiterLimit { get; } + /// The immutable clone. + public ImmutablePen ToImmutable() + { + return new ImmutablePen( + Brush, + Thickness, + DashStyle, + LineCap, + LineJoin, + MiterLimit); + } } } 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..0940533070 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..0781a233f7 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,7 +72,7 @@ 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; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index c622dc8a43..a10364e9ba 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/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(); } From cad119ebfbf16d5d792e63d0991cb153000baad7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jul 2019 11:35:36 +0200 Subject: [PATCH 02/12] Invalidate Pen when properties change. --- src/Avalonia.Visuals/Media/Pen.cs | 61 +++++++++++++++++++ .../Media/PenTests.cs | 33 ++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index 2bfd4f472e..64c4e4ce44 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Media.Immutable; +using Avalonia.Utilities; namespace Avalonia.Media { @@ -98,6 +99,17 @@ namespace Avalonia.Media DashStyle = dashStyle; } + static Pen() + { + AffectsRender( + BrushProperty, + ThicknessProperty, + DashStyleProperty, + LineCapProperty, + LineJoinProperty, + MiterLimitProperty); + } + /// /// Gets or sets the brush used to draw the stroke. /// @@ -172,5 +184,54 @@ namespace Avalonia.Media LineJoin, MiterLimit); } + + /// + /// Marks a property as affecting the pen's visual representation. + /// + /// 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); + } + } + + /// + /// Raises the event. + /// + /// 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/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs new file mode 100644 index 0000000000..a3746f80b5 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -0,0 +1,33 @@ +using Avalonia.Media; +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); + } + } +} From 21093cac31f6391f848cb8881c15aa4beb13f4ec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jul 2019 11:46:21 +0200 Subject: [PATCH 03/12] Make sure immutable pen has immutable brush. --- src/Avalonia.Visuals/Media/Pen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index 64c4e4ce44..d609335d68 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -177,7 +177,7 @@ namespace Avalonia.Media public ImmutablePen ToImmutable() { return new ImmutablePen( - Brush, + Brush?.ToImmutable(), Thickness, DashStyle, LineCap, From afa594cd4893aa5411da56bb29a3d27b5c7ff48c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jul 2019 12:21:04 +0200 Subject: [PATCH 04/12] Implemented equality for Pens. --- .../Media/Immutable/ImmutablePen.cs | 13 +++++- src/Avalonia.Visuals/Media/Pen.cs | 43 ++++++++++++++++++- .../Rendering/SceneGraph/LineNode.cs | 2 +- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs index bcfec5dccf..326083dec1 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs @@ -1,12 +1,14 @@ // 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; + namespace Avalonia.Media.Immutable { /// /// Describes how a stroke is drawn. /// - public class ImmutablePen : IPen + public class ImmutablePen : IPen, IEquatable { /// /// Initializes a new instance of the class. @@ -81,5 +83,14 @@ namespace Avalonia.Media.Immutable /// 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) => Pen.PenEquals(this, obj as IPen); + + /// + public bool Equals(IPen other) => Pen.PenEquals(this, other); + + /// + public override int GetHashCode() => Pen.GetHashCode(this); } } diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index d609335d68..b89ca15a0e 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -2,6 +2,7 @@ // 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; @@ -10,7 +11,7 @@ namespace Avalonia.Media /// /// Describes how a stroke is drawn. /// - public class Pen : AvaloniaObject, IMutablePen + public class Pen : AvaloniaObject, IMutablePen, IEquatable { /// /// Defines the property. @@ -170,6 +171,15 @@ namespace Avalonia.Media /// public event EventHandler Invalidated; + /// + public override bool Equals(object obj) => PenEquals(this, obj as IPen); + + /// + public bool Equals(IPen other) => PenEquals(this, other); + + /// + public override int GetHashCode() => GetHashCode(this); + /// /// Creates an immutable clone of the brush. /// @@ -232,6 +242,37 @@ namespace Avalonia.Media /// The event args. protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); + internal static int GetHashCode(IPen pen) + { + 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 + pen.LineCap.GetHashCode(); + hashCode = hashCode * -1521134295 + pen.LineJoin.GetHashCode(); + hashCode = hashCode * -1521134295 + pen.MiterLimit.GetHashCode(); + return hashCode; + } + + internal static bool PenEquals(IPen a, IPen b) + { + if (ReferenceEquals(a, b)) + { + return true; + } + else if (a is null && !(b is null) || (b is null && !(a is null))) + { + return false; + } + + return EqualityComparer.Default.Equals(a.Brush, b.Brush) && + a.Thickness == b.Thickness && + EqualityComparer.Default.Equals(a.DashStyle, b.DashStyle) && + a.LineCap == b.LineCap && + a.LineJoin == b.LineJoin && + a.MiterLimit == b.MiterLimit; + } + private void AffectsRenderInvalidated(object sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs index 0781a233f7..ff97ced349 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -74,7 +74,7 @@ namespace Avalonia.Rendering.SceneGraph /// 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) From 3c1dfcfcf5e9eb4e05b596ad6d9f3203e1632911 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jul 2019 12:40:52 +0200 Subject: [PATCH 05/12] Added equality test. And fix bug in `DashStyle`. --- src/Avalonia.Visuals/Media/DashStyle.cs | 2 +- .../Media/PenTests.cs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index c7e1db57b2..9f03a0bd85 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media { get { - if (dashDotDot == null) + if (dash == null) { dash = new DashStyle(new double[] { 2, 2 }, 1); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs index a3746f80b5..c4fe0155f9 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using Avalonia.Media.Immutable; using Xunit; namespace Avalonia.Visuals.UnitTests.Media @@ -29,5 +30,27 @@ namespace Avalonia.Visuals.UnitTests.Media Assert.True(raised); } + + [Fact] + public void Equality_Is_Implemented_Between_Mutable_And_Immutable_Pens() + { + var brush = new SolidColorBrush(Colors.Red); + var target1 = new Pen( + brush: brush, + thickness: 2, + dashStyle: DashStyle.Dash, + lineCap: PenLineCap.Round, + lineJoin: PenLineJoin.Round, + miterLimit: 21); + var target2 = new ImmutablePen( + brush: brush, + thickness: 2, + dashStyle: DashStyle.Dash, + lineCap: PenLineCap.Round, + lineJoin: PenLineJoin.Round, + miterLimit: 21); + + Assert.True(Equals(target1, target2)); + } } } From 2af5a03c2db8e5295b073bef311eb939cc536fb8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jul 2019 22:35:34 +0200 Subject: [PATCH 06/12] Make DashStyle mutable. --- src/Avalonia.Visuals/Media/BrushExtensions.cs | 21 ++- src/Avalonia.Visuals/Media/DashStyle.cs | 128 ++++++++++++------ src/Avalonia.Visuals/Media/IDashStyle.cs | 20 +++ src/Avalonia.Visuals/Media/IPen.cs | 2 +- .../Media/Immutable/ImmutableDashStyle.cs | 30 ++++ .../Media/Immutable/ImmutablePen.cs | 6 +- src/Avalonia.Visuals/Media/Pen.cs | 16 +-- .../Media/PenTests.cs | 15 +- 8 files changed, 179 insertions(+), 59 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/IDashStyle.cs create mode 100644 src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs 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); From 2523e7a2901aba996224d978ab729fd028fa4153 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jul 2019 22:37:30 +0200 Subject: [PATCH 07/12] Remove IMutablePen. It's not really needed. Mutable pens will always be `Pen`s. --- src/Avalonia.Visuals/Media/BrushExtensions.cs | 2 +- src/Avalonia.Visuals/Media/IMutablePen.cs | 17 ----------------- src/Avalonia.Visuals/Media/Pen.cs | 2 +- 3 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 src/Avalonia.Visuals/Media/IMutablePen.cs diff --git a/src/Avalonia.Visuals/Media/BrushExtensions.cs b/src/Avalonia.Visuals/Media/BrushExtensions.cs index 265337476a..87e698e705 100644 --- a/src/Avalonia.Visuals/Media/BrushExtensions.cs +++ b/src/Avalonia.Visuals/Media/BrushExtensions.cs @@ -50,7 +50,7 @@ namespace Avalonia.Media { Contract.Requires(pen != null); - return pen as ImmutablePen ?? ((IMutablePen)pen).ToImmutable(); + return pen as ImmutablePen ?? ((Pen)pen).ToImmutable(); } } } diff --git a/src/Avalonia.Visuals/Media/IMutablePen.cs b/src/Avalonia.Visuals/Media/IMutablePen.cs deleted file mode 100644 index 8b02929dec..0000000000 --- a/src/Avalonia.Visuals/Media/IMutablePen.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Avalonia.Media.Immutable; - -namespace Avalonia.Media -{ - /// - /// Represents a mutable pen which can return an immutable clone of itself. - /// - public interface IMutablePen : IPen, IAffectsRender - { - /// - /// Creates an immutable clone of the pen. - /// - /// The immutable clone. - ImmutablePen ToImmutable(); - } -} diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index c0963adc08..fdf006e054 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media /// /// Describes how a stroke is drawn. /// - public class Pen : AvaloniaObject, IMutablePen, IEquatable + public class Pen : AvaloniaObject, IPen, IEquatable { /// /// Defines the property. From e235efe3881a29d5b0fe4d82f1aae59bce61a988 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Jul 2019 23:03:48 +0200 Subject: [PATCH 08/12] Implement DashStyle equality. --- src/Avalonia.Visuals/Media/DashStyle.cs | 56 ++++++++++++++++++- .../Media/Immutable/ImmutableDashStyle.cs | 12 +++- .../Media/PenTests.cs | 22 ++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index 7784c73736..bd81cb1d03 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -9,7 +9,7 @@ namespace Avalonia.Media /// /// Represents the sequence of dashes and gaps that will be applied by a . /// - public class DashStyle : Animatable, IDashStyle, IAffectsRender + public class DashStyle : Animatable, IDashStyle, IAffectsRender, IEquatable { /// /// Defines the property. @@ -105,10 +105,64 @@ namespace Avalonia.Media /// public event EventHandler Invalidated; + /// + public override bool Equals(object obj) => DashEquals(this, obj as IDashStyle); + + /// + public bool Equals(IDashStyle other) => DashEquals(this, other); + + /// + public override int GetHashCode() => GetHashCode(this); + /// /// Returns an immutable clone of the . /// /// public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset); + + internal static bool DashEquals(IDashStyle a, IDashStyle b) + { + if (ReferenceEquals(a, b)) + { + return true; + } + else if ((a is null && !(b is null)) || (b is null && !(a is null))) + { + return false; + } + + if (a.Offset != b.Offset) + { + return false; + } + + if (ReferenceEquals(a.Dashes, b.Dashes)) + { + return true; + } + + if ((a.Dashes is null && !(b.Dashes is null)) || (b.Dashes is null && !(a.Dashes is null))) + { + return false; + } + + return a.Dashes.SequenceEqual(b.Dashes); + } + + internal static int GetHashCode(IDashStyle style) + { + var hashCode = 717868523; + hashCode = hashCode * -1521134295 + style.Offset.GetHashCode(); + + if (style.Dashes != null) + { + foreach (var i in style.Dashes) + { + hashCode = hashCode * -1521134295 + i.GetHashCode(); + } + } + + return hashCode; + } } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs index a40682babd..65e27bf9b5 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.Immutable /// Represents the sequence of dashes and gaps that will be applied by an /// . /// - public class ImmutableDashStyle : IDashStyle + public class ImmutableDashStyle : IDashStyle, IEquatable { /// /// Initializes a new instance of the class. @@ -26,5 +26,15 @@ namespace Avalonia.Media.Immutable /// public double Offset { get; } + + /// + public override bool Equals(object obj) => DashStyle.DashEquals(this, obj as IDashStyle); + + /// + public bool Equals(IDashStyle other) => DashStyle.DashEquals(this, other); + + /// + public override int GetHashCode() => DashStyle.GetHashCode(this); + } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs index 70b4281083..d5601c7497 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -65,5 +65,27 @@ namespace Avalonia.Visuals.UnitTests.Media 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 Pen( + brush: brush, + thickness: 2, + dashStyle: new DashStyle(new[] { 0.1, 0.2 }, 5), + lineCap: PenLineCap.Round, + lineJoin: PenLineJoin.Round, + miterLimit: 21); + var target2 = new ImmutablePen( + brush: brush, + thickness: 2, + dashStyle: new ImmutableDashStyle(new[] { 0.1, 0.2 }, 5), + lineCap: PenLineCap.Round, + lineJoin: PenLineJoin.Round, + miterLimit: 21); + + Assert.True(Equals(target1, target2)); + } } } From c5aca416918032404429c613d7e2e7aeb68e76bb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 17 Jul 2019 10:21:26 +0200 Subject: [PATCH 09/12] Use custom SequenceEqual. LINQ version is slower and this is potentially a hot path. --- src/Avalonia.Visuals/Media/DashStyle.cs | 35 +++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index bd81cb1d03..4ae2285dfd 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -136,17 +136,7 @@ namespace Avalonia.Media return false; } - if (ReferenceEquals(a.Dashes, b.Dashes)) - { - return true; - } - - if ((a.Dashes is null && !(b.Dashes is null)) || (b.Dashes is null && !(a.Dashes is null))) - { - return false; - } - - return a.Dashes.SequenceEqual(b.Dashes); + return SequenceEqual(a.Dashes, b.Dashes); } internal static int GetHashCode(IDashStyle style) @@ -164,5 +154,28 @@ namespace Avalonia.Media 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; + } } } From 52c3b6de59b5a56dc8d657a52a9da4ac91ad1d6d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 18 Jul 2019 14:34:54 +0200 Subject: [PATCH 10/12] Use ValueTuple to calculate hash. --- src/Avalonia.Visuals/Media/Pen.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index fdf006e054..b7219bbc6a 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -244,14 +244,8 @@ namespace Avalonia.Media internal static int GetHashCode(IPen pen) { - 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 + pen.LineCap.GetHashCode(); - hashCode = hashCode * -1521134295 + pen.LineJoin.GetHashCode(); - hashCode = hashCode * -1521134295 + pen.MiterLimit.GetHashCode(); - return hashCode; + return (pen.Brush, pen.Thickness, pen.DashStyle, pen.LineCap, pen.LineJoin, pen.MiterLimit) + .GetHashCode(); } internal static bool PenEquals(IPen a, IPen b) From 1a34920a795f1e81c898224a6b6ab2867425de1b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jul 2019 09:22:05 +0200 Subject: [PATCH 11/12] Don't implement equality for mutable pens. See https://github.com/AvaloniaUI/Avalonia/pull/2747#issuecomment-513210645 --- src/Avalonia.Visuals/Media/DashStyle.cs | 69 +------------------ .../Media/Immutable/ImmutableDashStyle.cs | 59 +++++++++++++++- .../Media/Immutable/ImmutablePen.cs | 30 ++++++-- src/Avalonia.Visuals/Media/Pen.cs | 36 +--------- .../Rendering/SceneGraph/GeometryNode.cs | 2 +- .../Rendering/SceneGraph/LineNode.cs | 2 +- .../Rendering/SceneGraph/RectangleNode.cs | 2 +- 7 files changed, 87 insertions(+), 113 deletions(-) diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index 4ae2285dfd..7784c73736 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -9,7 +9,7 @@ namespace Avalonia.Media /// /// Represents the sequence of dashes and gaps that will be applied by a . /// - public class DashStyle : Animatable, IDashStyle, IAffectsRender, IEquatable + public class DashStyle : Animatable, IDashStyle, IAffectsRender { /// /// Defines the property. @@ -105,77 +105,10 @@ namespace Avalonia.Media /// public event EventHandler Invalidated; - /// - public override bool Equals(object obj) => DashEquals(this, obj as IDashStyle); - - /// - public bool Equals(IDashStyle other) => DashEquals(this, other); - - /// - public override int GetHashCode() => GetHashCode(this); - /// /// Returns an immutable clone of the . /// /// public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset); - - internal static bool DashEquals(IDashStyle a, IDashStyle b) - { - if (ReferenceEquals(a, b)) - { - return true; - } - else if ((a is null && !(b is null)) || (b is null && !(a is null))) - { - return false; - } - - if (a.Offset != b.Offset) - { - return false; - } - - return SequenceEqual(a.Dashes, b.Dashes); - } - - internal static int GetHashCode(IDashStyle style) - { - var hashCode = 717868523; - hashCode = hashCode * -1521134295 + style.Offset.GetHashCode(); - - if (style.Dashes != null) - { - foreach (var i in style.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/ImmutableDashStyle.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs index 65e27bf9b5..dbd681931c 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs @@ -28,13 +28,66 @@ namespace Avalonia.Media.Immutable public double Offset { get; } /// - public override bool Equals(object obj) => DashStyle.DashEquals(this, obj as IDashStyle); + public override bool Equals(object obj) => Equals(this, obj as IDashStyle); /// - public bool Equals(IDashStyle other) => DashStyle.DashEquals(this, other); + 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() => DashStyle.GetHashCode(this); + 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 index ddc050aa5c..4b3bd640cb 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs @@ -2,6 +2,7 @@ // 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 { @@ -75,7 +76,8 @@ namespace Avalonia.Media.Immutable public PenLineCap LineCap { get; } /// - /// Specifies how to join consecutive line or curve segments in a (subpath) contained in a object. + /// Specifies how to join consecutive line or curve segments in a + /// (subpaths) contained in a object. /// public PenLineJoin LineJoin { get; } @@ -85,12 +87,32 @@ namespace Avalonia.Media.Immutable public double MiterLimit { get; } /// - public override bool Equals(object obj) => Pen.PenEquals(this, obj as IPen); + public override bool Equals(object obj) => Equals(obj as IPen); /// - public bool Equals(IPen other) => Pen.PenEquals(this, other); + 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() => Pen.GetHashCode(this); + 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 b7219bbc6a..b88fae28ff 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media /// /// Describes how a stroke is drawn. /// - public class Pen : AvaloniaObject, IPen, IEquatable + public class Pen : AvaloniaObject, IPen { /// /// Defines the property. @@ -171,15 +171,6 @@ namespace Avalonia.Media /// public event EventHandler Invalidated; - /// - public override bool Equals(object obj) => PenEquals(this, obj as IPen); - - /// - public bool Equals(IPen other) => PenEquals(this, other); - - /// - public override int GetHashCode() => GetHashCode(this); - /// /// Creates an immutable clone of the brush. /// @@ -242,31 +233,6 @@ namespace Avalonia.Media /// The event args. protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); - internal static int GetHashCode(IPen pen) - { - return (pen.Brush, pen.Thickness, pen.DashStyle, pen.LineCap, pen.LineJoin, pen.MiterLimit) - .GetHashCode(); - } - - internal static bool PenEquals(IPen a, IPen b) - { - if (ReferenceEquals(a, b)) - { - return true; - } - else if (a is null && !(b is null) || (b is null && !(a is null))) - { - return false; - } - - return EqualityComparer.Default.Equals(a.Brush, b.Brush) && - a.Thickness == b.Thickness && - EqualityComparer.Default.Equals(a.DashStyle, b.DashStyle) && - a.LineCap == b.LineCap && - a.LineJoin == b.LineJoin && - a.MiterLimit == b.MiterLimit; - } - private void AffectsRenderInvalidated(object sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index 0940533070..d5aa1251f3 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -76,7 +76,7 @@ namespace Avalonia.Rendering.SceneGraph { return transform == Transform && Equals(brush, Brush) && - Equals(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 ff97ced349..9a65fac078 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -74,7 +74,7 @@ namespace Avalonia.Rendering.SceneGraph /// public bool Equals(Matrix transform, IPen pen, Point p1, Point p2) { - return transform == Transform && Equals(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 a10364e9ba..0f3581b84c 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -85,7 +85,7 @@ namespace Avalonia.Rendering.SceneGraph { return transform == Transform && Equals(brush, Brush) && - Equals(pen, Pen) && + Equals(Pen, pen) && rect == Rect && cornerRadius == CornerRadius; } From b8c4c0e873d60da9925245ac10f56659fa02b8fb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jul 2019 16:23:07 +0200 Subject: [PATCH 12/12] Fix failing tests. --- .../Media/Immutable/ImmutableDashStyle.cs | 2 +- .../Media/PenTests.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs index dbd681931c..e9a52fe6ed 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs @@ -28,7 +28,7 @@ namespace Avalonia.Media.Immutable public double Offset { get; } /// - public override bool Equals(object obj) => Equals(this, obj as IDashStyle); + public override bool Equals(object obj) => Equals(obj as IDashStyle); /// public bool Equals(IDashStyle other) diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs index d5601c7497..418ac7576b 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -45,20 +45,20 @@ namespace Avalonia.Visuals.UnitTests.Media } [Fact] - public void Equality_Is_Implemented_Between_Mutable_And_Immutable_Pens() + public void Equality_Is_Implemented_Between_Immutable_And_Mmutable_Pens() { var brush = new SolidColorBrush(Colors.Red); - var target1 = new Pen( + var target1 = new ImmutablePen( brush: brush, thickness: 2, - dashStyle: DashStyle.Dash, + dashStyle: (ImmutableDashStyle)DashStyle.Dash, lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round, miterLimit: 21); - var target2 = new ImmutablePen( + var target2 = new Pen( brush: brush, thickness: 2, - dashStyle: (ImmutableDashStyle)DashStyle.Dash, + dashStyle: DashStyle.Dash, lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round, miterLimit: 21); @@ -70,17 +70,17 @@ namespace Avalonia.Visuals.UnitTests.Media public void Equality_Is_Implemented_Between_Mutable_And_Immutable_DashStyles() { var brush = new SolidColorBrush(Colors.Red); - var target1 = new Pen( + var target1 = new ImmutablePen( brush: brush, thickness: 2, - dashStyle: new DashStyle(new[] { 0.1, 0.2 }, 5), + dashStyle: new ImmutableDashStyle(new[] { 0.1, 0.2 }, 5), lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round, miterLimit: 21); - var target2 = new ImmutablePen( + var target2 = new Pen( brush: brush, thickness: 2, - dashStyle: new ImmutableDashStyle(new[] { 0.1, 0.2 }, 5), + dashStyle: new DashStyle(new[] { 0.1, 0.2 }, 5), lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round, miterLimit: 21);