diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 0c6949465b..ee67f303f3 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -1,3 +1,5 @@ +using Avalonia.Collections; +using Avalonia.Controls.Shapes; using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Media; @@ -41,7 +43,31 @@ namespace Avalonia.Controls /// public static readonly StyledProperty BoxShadowProperty = AvaloniaProperty.Register(nameof(BoxShadow)); - + + /// + /// Defines the property. + /// + public static readonly StyledProperty BorderDashOffsetProperty = + AvaloniaProperty.Register(nameof(BorderDashOffset)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty?> BorderDashArrayProperty = + AvaloniaProperty.Register?>(nameof(BorderDashArray)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty BorderLineCapProperty = + AvaloniaProperty.Register(nameof(BorderLineCap), PenLineCap.Flat); + + /// + /// Defines the property. + /// + public static readonly StyledProperty BorderLineJoinProperty = + AvaloniaProperty.Register(nameof(BorderLineJoin), PenLineJoin.Miter); + private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); /// @@ -54,6 +80,10 @@ namespace Avalonia.Controls BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty, + BorderDashArrayProperty, + BorderLineCapProperty, + BorderLineJoinProperty, + BorderDashOffsetProperty, BoxShadowProperty); AffectsMeasure(BorderThicknessProperty); } @@ -76,6 +106,15 @@ namespace Avalonia.Controls set { SetValue(BorderBrushProperty, value); } } + /// + /// Gets or sets a collection of values that indicate the pattern of dashes and gaps that is used to outline shapes. + /// + public AvaloniaList? BorderDashArray + { + get { return GetValue(BorderDashArrayProperty); } + set { SetValue(BorderDashArrayProperty, value); } + } + /// /// Gets or sets the thickness of the border. /// @@ -85,6 +124,33 @@ namespace Avalonia.Controls set { SetValue(BorderThicknessProperty, value); } } + /// + /// Gets or sets a value that specifies the distance within the dash pattern where a dash begins. + /// + public double BorderDashOffset + { + get { return GetValue(BorderDashOffsetProperty); } + set { SetValue(BorderDashOffsetProperty, value); } + } + + /// + /// Gets or sets a enumeration value that describes the shape at the ends of a line. + /// + public PenLineCap BorderLineCap + { + get { return GetValue(BorderLineCapProperty); } + set { SetValue(BorderLineCapProperty, value); } + } + + /// + /// Gets or sets a enumeration value that specifies the type of join that is used at the vertices of a Shape. + /// + public PenLineJoin BorderLineJoin + { + get { return GetValue(BorderLineJoinProperty); } + set { SetValue(BorderLineJoinProperty, value); } + } + /// /// Gets or sets the radius of the border rounded corners. /// @@ -93,7 +159,7 @@ namespace Avalonia.Controls get { return GetValue(CornerRadiusProperty); } set { SetValue(CornerRadiusProperty, value); } } - + /// /// Gets or sets the box shadow effect parameters /// @@ -102,7 +168,7 @@ namespace Avalonia.Controls get => GetValue(BoxShadowProperty); set => SetValue(BoxShadowProperty, value); } - + /// /// Renders the control. /// @@ -110,7 +176,7 @@ namespace Avalonia.Controls public override void Render(DrawingContext context) { _borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush, - BoxShadow); + BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray); } /// diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index f8ab58d46e..bae787f0ed 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -1,8 +1,10 @@ using System; +using Avalonia.Collections; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; using Avalonia.Utilities; +using JetBrains.Annotations; namespace Avalonia.Controls.Utils { @@ -15,8 +17,13 @@ namespace Avalonia.Controls.Utils private Size _size; private Thickness _borderThickness; private CornerRadius _cornerRadius; + private AvaloniaList _borderDashArray; + private double _borderDashOffset; + private PenLineCap _borderLineCap; + private PenLineJoin _borderJoin; private bool _initialized; + void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius) { _backendSupportsIndividualCorners ??= AvaloniaLocator.Current.GetService() @@ -60,7 +67,8 @@ namespace Avalonia.Controls.Utils if (boundRect.Width != 0 && innerRect.Height != 0) { - var borderGeometryKeypoints = new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false); + var borderGeometryKeypoints = + new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false); var borderGeometry = new StreamGeometry(); using (var ctx = borderGeometry.Open()) @@ -84,17 +92,22 @@ namespace Avalonia.Controls.Utils public void Render(DrawingContext context, Size finalSize, Thickness borderThickness, CornerRadius cornerRadius, - IBrush background, IBrush borderBrush, BoxShadows boxShadows) + IBrush background, IBrush borderBrush, BoxShadows boxShadows, double borderDashOffset = 0, + PenLineCap borderLineCap = PenLineCap.Flat, PenLineJoin borderLineJoin = PenLineJoin.Miter, + AvaloniaList borderDashArray = null) { if (_size != finalSize || _borderThickness != borderThickness || _cornerRadius != cornerRadius || !_initialized) Update(finalSize, borderThickness, cornerRadius); - RenderCore(context, background, borderBrush, boxShadows); + RenderCore(context, background, borderBrush, boxShadows, borderDashOffset, borderLineCap, borderLineJoin, + borderDashArray); } - void RenderCore(DrawingContext context, IBrush background, IBrush borderBrush, BoxShadows boxShadows) + void RenderCore(DrawingContext context, IBrush background, IBrush borderBrush, BoxShadows boxShadows, + double borderDashOffset, PenLineCap borderLineCap, PenLineJoin borderLineJoin, + AvaloniaList borderDashArray) { if (_useComplexRendering) { @@ -115,11 +128,25 @@ namespace Avalonia.Controls.Utils var borderThickness = _borderThickness.Top; IPen pen = null; + + ImmutableDashStyle? dashStyle = null; + + if (borderDashArray != null && borderDashArray.Count > 0) + { + dashStyle = new ImmutableDashStyle(borderDashArray, borderDashOffset); + } + if (borderBrush != null && borderThickness > 0) { - pen = new ImmutablePen(borderBrush.ToImmutable(), borderThickness); + pen = new ImmutablePen( + borderBrush.ToImmutable(), + borderThickness, + dashStyle, + borderLineCap, + borderLineJoin); } + var rect = new Rect(_size); if (!MathUtilities.IsZero(borderThickness)) rect = rect.Deflate(borderThickness * 0.5); @@ -130,7 +157,8 @@ namespace Avalonia.Controls.Utils } } - private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints) + private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, + BorderGeometryKeypoints keypoints) { context.BeginFigure(keypoints.TopLeft, true); @@ -184,7 +212,8 @@ namespace Avalonia.Controls.Utils private class BorderGeometryKeypoints { - internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, bool inner) + internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, + bool inner) { var left = 0.5 * borderThickness.Left; var top = 0.5 * borderThickness.Top; @@ -206,10 +235,13 @@ namespace Avalonia.Controls.Utils topLeftX = Math.Max(0, cornerRadius.TopLeft - left) + boundRect.TopLeft.X; topRightX = boundRect.Width - Math.Max(0, cornerRadius.TopRight - top) + boundRect.TopLeft.X; rightTopY = Math.Max(0, cornerRadius.TopRight - right) + boundRect.TopLeft.Y; - rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) + boundRect.TopLeft.Y; - bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) + boundRect.TopLeft.X; + rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) + + boundRect.TopLeft.Y; + bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) + + boundRect.TopLeft.X; bottomLeftX = Math.Max(0, cornerRadius.BottomLeft - left) + boundRect.TopLeft.X; - leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) + boundRect.TopLeft.Y; + leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) + + boundRect.TopLeft.Y; } else {