diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 57dbeba1cc..499dfb5320 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -21,13 +21,19 @@ namespace Avalonia.Controls.Shapes public static readonly StyledProperty> StrokeDashArrayProperty = AvaloniaProperty.Register>(nameof(StrokeDashArray)); - + public static readonly StyledProperty StrokeDashOffsetProperty = AvaloniaProperty.Register(nameof(StrokeDashOffset)); public static readonly StyledProperty StrokeThicknessProperty = AvaloniaProperty.Register(nameof(StrokeThickness)); + public static readonly StyledProperty StrokeLineCapProperty = + AvaloniaProperty.Register(nameof(StrokeLineCap), PenLineCap.Flat); + + public static readonly StyledProperty StrokeJoinProperty = + AvaloniaProperty.Register(nameof(StrokeJoin), PenLineJoin.Miter); + private Matrix _transform = Matrix.Identity; private Geometry _definingGeometry; private Geometry _renderedGeometry; @@ -36,7 +42,9 @@ namespace Avalonia.Controls.Shapes static Shape() { AffectsMeasure(StretchProperty, StrokeThicknessProperty); - AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); + + AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty, StrokeDashOffsetProperty, + StrokeThicknessProperty, StrokeLineCapProperty, StrokeJoinProperty); } public Geometry DefiningGeometry @@ -106,7 +114,7 @@ namespace Avalonia.Controls.Shapes get { return GetValue(StrokeDashArrayProperty); } set { SetValue(StrokeDashArrayProperty, value); } } - + public double StrokeDashOffset { get { return GetValue(StrokeDashOffsetProperty); } @@ -119,13 +127,17 @@ namespace Avalonia.Controls.Shapes set { SetValue(StrokeThicknessProperty, value); } } - public PenLineCap StrokeDashCap { get; set; } = PenLineCap.Flat; - - public PenLineCap StrokeStartLineCap { get; set; } = PenLineCap.Flat; - - public PenLineCap StrokeEndLineCap { get; set; } = PenLineCap.Flat; + public PenLineCap StrokeLineCap + { + get { return GetValue(StrokeLineCapProperty); } + set { SetValue(StrokeLineCapProperty, value); } + } - public PenLineJoin StrokeJoin { get; set; } = PenLineJoin.Miter; + public PenLineJoin StrokeJoin + { + get { return GetValue(StrokeJoinProperty); } + set { SetValue(StrokeJoinProperty, value); } + } public override void Render(DrawingContext context) { @@ -133,8 +145,8 @@ namespace Avalonia.Controls.Shapes if (geometry != null) { - var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset), - StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap, StrokeJoin); + var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset), + StrokeLineCap, StrokeJoin); context.DrawGeometry(Fill, pen, geometry); } } @@ -169,11 +181,11 @@ namespace Avalonia.Controls.Shapes protected void InvalidateGeometry() { - this._renderedGeometry = null; - this._definingGeometry = null; + _renderedGeometry = null; + _definingGeometry = null; InvalidateMeasure(); } - + protected override Size MeasureOverride(Size availableSize) { bool deferCalculateTransform; @@ -203,10 +215,10 @@ namespace Avalonia.Controls.Shapes return CalculateShapeSizeAndSetTransform(availableSize); } } - + protected override Size ArrangeOverride(Size finalSize) { - if(_calculateTransformOnArrange) + if (_calculateTransformOnArrange) { _calculateTransformOnArrange = false; CalculateShapeSizeAndSetTransform(finalSize); @@ -312,25 +324,25 @@ namespace Avalonia.Controls.Shapes private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e) { - var control = e.Sender as Shape; + if (!(e.Sender is Shape control)) + { + return; + } - if (control != null) + // If the geometry is invalidated when Bounds changes, only invalidate when the Size + // portion changes. + if (e.Property == BoundsProperty) { - // If the geometry is invalidated when Bounds changes, only invalidate when the Size - // portion changes. - if (e.Property == BoundsProperty) - { - var oldBounds = (Rect)e.OldValue; - var newBounds = (Rect)e.NewValue; + var oldBounds = (Rect)e.OldValue; + var newBounds = (Rect)e.NewValue; - if (oldBounds.Size == newBounds.Size) - { - return; - } + if (oldBounds.Size == newBounds.Size) + { + return; } - - control.InvalidateGeometry(); } + + control.InvalidateGeometry(); } } } diff --git a/src/Avalonia.Visuals/Media/BrushExtensions.cs b/src/Avalonia.Visuals/Media/BrushExtensions.cs index cd351071dd..522953eb04 100644 --- a/src/Avalonia.Visuals/Media/BrushExtensions.cs +++ b/src/Avalonia.Visuals/Media/BrushExtensions.cs @@ -34,16 +34,14 @@ namespace Avalonia.Media { Contract.Requires(pen != null); - var brush = pen?.Brush?.ToImmutable(); - return pen == null || ReferenceEquals(pen?.Brush, brush) ? + var brush = pen.Brush?.ToImmutable(); + return ReferenceEquals(pen.Brush, brush) ? pen : new Pen( brush, thickness: pen.Thickness, - dashStyle: pen.DashStyle, - dashCap: pen.DashCap, - startLineCap: pen.StartLineCap, - endLineCap: pen.EndLineCap, + dashStyle: pen.DashStyle, + lineCap: pen.LineCap, lineJoin: pen.LineJoin, miterLimit: pen.MiterLimit); } diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index ddd8091801..ee427c913b 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -11,63 +11,45 @@ namespace Avalonia.Media /// /// Initializes a new instance of the class. /// - /// The brush used to draw. + /// The stroke color. /// The stroke thickness. /// The dash style. - /// The dash cap. - /// The start line cap. - /// The end line cap. + /// Specifies the type of graphic shape to use on both ends of a line. /// The line join. /// The miter limit. public Pen( - IBrush brush, + uint color, double thickness = 1.0, - DashStyle dashStyle = null, - PenLineCap dashCap = PenLineCap.Flat, - PenLineCap startLineCap = PenLineCap.Flat, - PenLineCap endLineCap = PenLineCap.Flat, - PenLineJoin lineJoin = PenLineJoin.Miter, - double miterLimit = 10.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) { - Brush = brush; - Thickness = thickness; - DashCap = dashCap; - StartLineCap = startLineCap; - EndLineCap = endLineCap; - LineJoin = lineJoin; - MiterLimit = miterLimit; - DashStyle = dashStyle; } /// /// Initializes a new instance of the class. /// - /// The stroke color. + /// The brush used to draw. /// The stroke thickness. /// The dash style. - /// The dash cap. - /// The start line cap. - /// The end line cap. + /// The line cap. /// The line join. /// The miter limit. public Pen( - uint color, + IBrush brush, double thickness = 1.0, - DashStyle dashStyle = null, - PenLineCap dashCap = PenLineCap.Flat, - PenLineCap startLineCap = PenLineCap.Flat, - PenLineCap endLineCap = PenLineCap.Flat, - PenLineJoin lineJoin = PenLineJoin.Miter, + DashStyle dashStyle = null, + PenLineCap lineCap = PenLineCap.Flat, + PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) { - Brush = new SolidColorBrush(color); + Brush = brush; Thickness = thickness; - StartLineCap = startLineCap; - EndLineCap = endLineCap; + LineCap = lineCap; LineJoin = lineJoin; MiterLimit = miterLimit; DashStyle = dashStyle; - DashCap = dashCap; } /// @@ -78,18 +60,26 @@ namespace Avalonia.Media /// /// Gets the stroke thickness. /// - public double Thickness { get; } = 1.0; + public double Thickness { get; } + /// + /// Specifies the style of dashed lines drawn with a object. + /// public DashStyle DashStyle { get; } - public PenLineCap DashCap { get; } - - public PenLineCap StartLineCap { get; } = PenLineCap.Flat; - - public PenLineCap EndLineCap { get; } = PenLineCap.Flat; + /// + /// Specifies the type of graphic shape to use on both ends of a line. + /// + public PenLineCap LineCap { get; } - public PenLineJoin LineJoin { get; } = PenLineJoin.Miter; + /// + /// Specifies how to join consecutive line or curve segments in a (subpath) contained in a object. + /// + public PenLineJoin LineJoin { get; } - public double MiterLimit { get; } = 10.0; + /// + /// 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/PenLineCap.cs b/src/Avalonia.Visuals/Media/PenLineCap.cs index 56c5c040eb..83d8f11613 100644 --- a/src/Avalonia.Visuals/Media/PenLineCap.cs +++ b/src/Avalonia.Visuals/Media/PenLineCap.cs @@ -4,7 +4,6 @@ namespace Avalonia.Media { Flat, Round, - Square, - Triangle + Square } } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index e69d155305..ef37f69f45 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -573,25 +573,17 @@ namespace Avalonia.Skia // Need to modify dashes due to Skia modifying their lengths // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/dots // TODO: Still something is off, dashes are now present, but don't look the same as D2D ones. - float dashLengthModifier; - float gapLengthModifier; - switch (pen.StartLineCap) + switch (pen.LineCap) { case PenLineCap.Round: paint.StrokeCap = SKStrokeCap.Round; - dashLengthModifier = -paint.StrokeWidth; - gapLengthModifier = paint.StrokeWidth; break; case PenLineCap.Square: paint.StrokeCap = SKStrokeCap.Square; - dashLengthModifier = -paint.StrokeWidth; - gapLengthModifier = paint.StrokeWidth; break; default: paint.StrokeCap = SKStrokeCap.Butt; - dashLengthModifier = 0.0f; - gapLengthModifier = 0.0f; break; } @@ -617,13 +609,12 @@ namespace Avalonia.Skia for (var i = 0; i < srcDashes.Count; ++i) { - var lengthModifier = i % 2 == 0 ? dashLengthModifier : gapLengthModifier; - - // Avalonia dash lengths are relative, but Skia takes absolute sizes - need to scale - dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth + lengthModifier; + dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth; } - var pe = SKPathEffect.CreateDash(dashesArray, (float) pen.DashStyle.Offset); + var offset = (float)(pen.DashStyle.Offset * pen.Thickness); + + var pe = SKPathEffect.CreateDash(dashesArray, offset); paint.PathEffect = pe; rv.AddDisposable(pe); diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs index 5209014271..6b0d30f250 100644 --- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs @@ -122,14 +122,16 @@ namespace Avalonia.Direct2D1 /// The Direct2D brush. public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory) { + var d2dLineCap = pen.LineCap.ToDirect2D(); + var properties = new StrokeStyleProperties { DashStyle = DashStyle.Solid, MiterLimit = (float)pen.MiterLimit, LineJoin = pen.LineJoin.ToDirect2D(), - StartCap = pen.StartLineCap.ToDirect2D(), - EndCap = pen.EndLineCap.ToDirect2D(), - DashCap = pen.DashCap.ToDirect2D() + StartCap = d2dLineCap, + EndCap = d2dLineCap, + DashCap = d2dLineCap }; float[] dashes = null; if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0) diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs index 4703daca25..2fa93b49e2 100644 --- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs @@ -334,11 +334,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes CompareImages(); } -#if AVALONIA_SKIA_SKIP_FAIL - [Fact(Skip = "FIXME")] -#else [Fact] -#endif public async Task Path_With_PenLineCap() { Decorator target = new Decorator @@ -351,10 +347,8 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes StrokeThickness = 10, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - StrokeDashCap = PenLineCap.Triangle, StrokeDashArray = new AvaloniaList(3, 1), - StrokeStartLineCap = PenLineCap.Round, - StrokeEndLineCap = PenLineCap.Square, + StrokeLineCap = PenLineCap.Round, Data = StreamGeometry.Parse("M 20,20 L 180,180"), } }; diff --git a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs index 3b586d55ea..8afaeb8838 100644 --- a/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs +++ b/tests/Avalonia.RenderTests/Shapes/PolylineTests.cs @@ -61,8 +61,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes Points = polylinePoints, Stretch = Stretch.Uniform, StrokeJoin = PenLineJoin.Round, - StrokeStartLineCap = PenLineCap.Round, - StrokeEndLineCap = PenLineCap.Round, + StrokeLineCap = PenLineCap.Round, StrokeThickness = 10 } }; diff --git a/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png b/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png index d33068d62c..a61ba9f080 100644 Binary files a/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png and b/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png differ diff --git a/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png b/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png index d33068d62c..a61ba9f080 100644 Binary files a/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png and b/tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png differ