diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index 002c5ea3f2..06ad8a4837 100644
--- a/src/Avalonia.Controls/Border.cs
+++ b/src/Avalonia.Controls/Border.cs
@@ -1,6 +1,8 @@
// 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 Avalonia;
+using Avalonia.Controls.Utils;
using Avalonia.Media;
namespace Avalonia.Controls
@@ -8,7 +10,7 @@ namespace Avalonia.Controls
///
/// A control which decorates a child with a border and background.
///
- public class Border : Decorator
+ public partial class Border : Decorator
{
///
/// Defines the property.
@@ -25,14 +27,16 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty BorderThicknessProperty =
- AvaloniaProperty.Register(nameof(BorderThickness));
+ public static readonly StyledProperty BorderThicknessProperty =
+ AvaloniaProperty.Register(nameof(BorderThickness));
///
/// Defines the property.
///
- public static readonly StyledProperty CornerRadiusProperty =
- AvaloniaProperty.Register(nameof(CornerRadius));
+ public static readonly StyledProperty CornerRadiusProperty =
+ AvaloniaProperty.Register(nameof(CornerRadius));
+
+ private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
///
/// Initializes static members of the class.
@@ -63,7 +67,7 @@ namespace Avalonia.Controls
///
/// Gets or sets the thickness of the border.
///
- public double BorderThickness
+ public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@@ -72,7 +76,7 @@ namespace Avalonia.Controls
///
/// Gets or sets the radius of the border rounded corners.
///
- public float CornerRadius
+ public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@@ -84,21 +88,7 @@ namespace Avalonia.Controls
/// The drawing context.
public override void Render(DrawingContext context)
{
- var background = Background;
- var borderBrush = BorderBrush;
- var borderThickness = BorderThickness;
- var cornerRadius = CornerRadius;
- var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
-
- if (background != null)
- {
- context.FillRectangle(background, rect, cornerRadius);
- }
-
- if (borderBrush != null && borderThickness > 0)
- {
- context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
- }
+ _borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
}
///
@@ -120,10 +110,12 @@ namespace Avalonia.Controls
{
if (Child != null)
{
- var padding = Padding + new Thickness(BorderThickness);
+ var padding = Padding + BorderThickness;
Child.Arrange(new Rect(finalSize).Deflate(padding));
}
+ _borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
+
return finalSize;
}
@@ -131,19 +123,17 @@ namespace Avalonia.Controls
Size availableSize,
IControl child,
Thickness padding,
- double borderThickness)
+ Thickness borderThickness)
{
- padding += new Thickness(borderThickness);
+ padding += borderThickness;
if (child != null)
{
child.Measure(availableSize.Deflate(padding));
return child.DesiredSize.Inflate(padding);
}
- else
- {
- return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
- }
+
+ return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 54626e9e97..6acbc047ae 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -4,6 +4,7 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
@@ -31,7 +32,7 @@ namespace Avalonia.Controls.Presenters
///
/// Defines the property.
///
- public static readonly StyledProperty BorderThicknessProperty =
+ public static readonly StyledProperty BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner();
///
@@ -57,7 +58,7 @@ namespace Avalonia.Controls.Presenters
///
/// Defines the property.
///
- public static readonly StyledProperty CornerRadiusProperty =
+ public static readonly StyledProperty CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner();
///
@@ -76,11 +77,12 @@ namespace Avalonia.Controls.Presenters
/// Defines the property.
///
public static readonly StyledProperty PaddingProperty =
- Border.PaddingProperty.AddOwner();
+ Decorator.PaddingProperty.AddOwner();
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
+ private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
///
/// Initializes static members of the class.
@@ -120,7 +122,7 @@ namespace Avalonia.Controls.Presenters
///
/// Gets or sets the thickness of the border.
///
- public double BorderThickness
+ public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@@ -157,7 +159,7 @@ namespace Avalonia.Controls.Presenters
///
/// Gets or sets the radius of the border rounded corners.
///
- public float CornerRadius
+ public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@@ -277,21 +279,7 @@ namespace Avalonia.Controls.Presenters
///
public override void Render(DrawingContext context)
{
- var background = Background;
- var borderBrush = BorderBrush;
- var borderThickness = BorderThickness;
- var cornerRadius = CornerRadius;
- var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
-
- if (background != null)
- {
- context.FillRectangle(background, rect, cornerRadius);
- }
-
- if (borderBrush != null && borderThickness > 0)
- {
- context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
- }
+ _borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
}
///
@@ -344,7 +332,11 @@ namespace Avalonia.Controls.Presenters
///
protected override Size ArrangeOverride(Size finalSize)
{
- return ArrangeOverrideImpl(finalSize, new Vector());
+ finalSize = ArrangeOverrideImpl(finalSize, new Vector());
+
+ _borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
+
+ return finalSize;
}
///
@@ -372,70 +364,69 @@ namespace Avalonia.Controls.Presenters
internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
{
- if (Child != null)
- {
- var padding = Padding;
- var borderThickness = BorderThickness;
- var horizontalContentAlignment = HorizontalContentAlignment;
- var verticalContentAlignment = VerticalContentAlignment;
- var useLayoutRounding = UseLayoutRounding;
- var availableSizeMinusMargins = new Size(
- Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness),
- Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness));
- var size = availableSizeMinusMargins;
- var scale = GetLayoutScale();
- var originX = offset.X + padding.Left + borderThickness;
- var originY = offset.Y + padding.Top + borderThickness;
-
- if (horizontalContentAlignment != HorizontalAlignment.Stretch)
- {
- size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
- }
+ if (Child == null) return finalSize;
- if (verticalContentAlignment != VerticalAlignment.Stretch)
- {
- size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
- }
-
- if (useLayoutRounding)
- {
- size = new Size(
- Math.Ceiling(size.Width * scale) / scale,
- Math.Ceiling(size.Height * scale) / scale);
- availableSizeMinusMargins = new Size(
- Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
- Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
- }
+ var padding = Padding;
+ var borderThickness = BorderThickness;
+ var horizontalContentAlignment = HorizontalContentAlignment;
+ var verticalContentAlignment = VerticalContentAlignment;
+ var useLayoutRounding = UseLayoutRounding;
+ var availableSizeMinusMargins = new Size(
+ Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness.Left - borderThickness.Right),
+ Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness.Top - borderThickness.Bottom));
+ var size = availableSizeMinusMargins;
+ var scale = GetLayoutScale();
+ var originX = offset.X + padding.Left + borderThickness.Left;
+ var originY = offset.Y + padding.Top + borderThickness.Top;
+
+ if (horizontalContentAlignment != HorizontalAlignment.Stretch)
+ {
+ size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
+ }
+
+ if (verticalContentAlignment != VerticalAlignment.Stretch)
+ {
+ size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
+ }
- switch (horizontalContentAlignment)
- {
- case HorizontalAlignment.Center:
- originX += (availableSizeMinusMargins.Width - size.Width) / 2;
- break;
- case HorizontalAlignment.Right:
- originX += availableSizeMinusMargins.Width - size.Width;
- break;
- }
+ if (useLayoutRounding)
+ {
+ size = new Size(
+ Math.Ceiling(size.Width * scale) / scale,
+ Math.Ceiling(size.Height * scale) / scale);
+ availableSizeMinusMargins = new Size(
+ Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
+ Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
+ }
- switch (verticalContentAlignment)
- {
- case VerticalAlignment.Center:
- originY += (availableSizeMinusMargins.Height - size.Height) / 2;
- break;
- case VerticalAlignment.Bottom:
- originY += availableSizeMinusMargins.Height - size.Height;
- break;
- }
+ switch (horizontalContentAlignment)
+ {
+ case HorizontalAlignment.Center:
+ originX += (availableSizeMinusMargins.Width - size.Width) / 2;
+ break;
+ case HorizontalAlignment.Right:
+ originX += availableSizeMinusMargins.Width - size.Width;
+ break;
+ }
- if (useLayoutRounding)
- {
- originX = Math.Floor(originX * scale) / scale;
- originY = Math.Floor(originY * scale) / scale;
- }
+ switch (verticalContentAlignment)
+ {
+ case VerticalAlignment.Center:
+ originY += (availableSizeMinusMargins.Height - size.Height) / 2;
+ break;
+ case VerticalAlignment.Bottom:
+ originY += availableSizeMinusMargins.Height - size.Height;
+ break;
+ }
- Child.Arrange(new Rect(originX, originY, size.Width, size.Height));
+ if (useLayoutRounding)
+ {
+ originX = Math.Floor(originX * scale) / scale;
+ originY = Math.Floor(originY * scale) / scale;
}
+ Child.Arrange(new Rect(originX, originY, size.Width, size.Height));
+
return finalSize;
}
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index 6deef7c7b9..77735f3f12 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives
///
/// Defines the property.
///
- public static readonly StyledProperty BorderThicknessProperty =
+ public static readonly StyledProperty BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner();
///
@@ -132,7 +132,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets or sets the thickness of the control's border.
///
- public double BorderThickness
+ public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
new file mode 100644
index 0000000000..d9169e51f3
--- /dev/null
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -0,0 +1,279 @@
+// 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;
+
+namespace Avalonia.Controls.Utils
+{
+ internal class BorderRenderHelper
+ {
+ private bool _useComplexRendering;
+ private StreamGeometry _backgroundGeometryCache;
+ private StreamGeometry _borderGeometryCache;
+
+ public void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
+ {
+ if (borderThickness.IsUniform && cornerRadius.IsUniform)
+ {
+ _backgroundGeometryCache = null;
+ _borderGeometryCache = null;
+ _useComplexRendering = false;
+ }
+ else
+ {
+ _useComplexRendering = true;
+
+ var boundRect = new Rect(finalSize);
+ var innerRect = boundRect.Deflate(borderThickness);
+ var innerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, false);
+
+ StreamGeometry backgroundGeometry = null;
+
+ if (innerRect.Width != 0 && innerRect.Height != 0)
+ {
+ backgroundGeometry = new StreamGeometry();
+
+ using (var ctx = backgroundGeometry.Open())
+ {
+ CreateGeometry(ctx, innerRect, innerCoordinates);
+ }
+
+ _backgroundGeometryCache = backgroundGeometry;
+ }
+ else
+ {
+ _backgroundGeometryCache = null;
+ }
+
+ if (boundRect.Width != 0 && innerRect.Height != 0)
+ {
+ var outerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, true);
+ var borderGeometry = new StreamGeometry();
+
+ using (var ctx = borderGeometry.Open())
+ {
+ CreateGeometry(ctx, boundRect, outerCoordinates);
+
+ if (backgroundGeometry != null)
+ {
+ CreateGeometry(ctx, innerRect, innerCoordinates);
+ }
+ }
+
+ _borderGeometryCache = borderGeometry;
+ }
+ else
+ {
+ _borderGeometryCache = null;
+ }
+ }
+ }
+
+ public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush)
+ {
+ if (_useComplexRendering)
+ {
+ var backgroundGeometry = _backgroundGeometryCache;
+ if (backgroundGeometry != null)
+ {
+ context.DrawGeometry(background, null, backgroundGeometry);
+ }
+
+ var borderGeometry = _borderGeometryCache;
+ if (borderGeometry != null)
+ {
+ context.DrawGeometry(borderBrush, null, borderGeometry);
+ }
+ }
+ else
+ {
+ var borderThickness = borders.Left;
+ var cornerRadius = (float)radii.TopLeft;
+ var rect = new Rect(size);
+
+ if (background != null)
+ {
+ context.FillRectangle(background, rect.Deflate(borders), cornerRadius);
+ }
+
+ if (borderBrush != null && borderThickness > 0)
+ {
+ context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius);
+ }
+ }
+ }
+
+ private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderCoordinates borderCoordinates)
+ {
+ var topLeft = new Point(borderCoordinates.LeftTop, 0);
+ var topRight = new Point(boundRect.Width - borderCoordinates.RightTop, 0);
+ var rightTop = new Point(boundRect.Width, borderCoordinates.TopRight);
+ var rightBottom = new Point(boundRect.Width, boundRect.Height - borderCoordinates.BottomRight);
+ var bottomRight = new Point(boundRect.Width - borderCoordinates.RightBottom, boundRect.Height);
+ var bottomLeft = new Point(borderCoordinates.LeftBottom, boundRect.Height);
+ var leftBottom = new Point(0, boundRect.Height - borderCoordinates.BottomLeft);
+ var leftTop = new Point(0, borderCoordinates.TopLeft);
+
+
+ if (topLeft.X > topRight.X)
+ {
+ var scaledX = borderCoordinates.LeftTop / (borderCoordinates.LeftTop + borderCoordinates.RightTop) * boundRect.Width;
+ topLeft = new Point(scaledX, topLeft.Y);
+ topRight = new Point(scaledX, topRight.Y);
+ }
+
+ if (rightTop.Y > rightBottom.Y)
+ {
+ var scaledY = borderCoordinates.TopRight / (borderCoordinates.TopRight + borderCoordinates.BottomRight) * boundRect.Height;
+ rightTop = new Point(rightTop.X, scaledY);
+ rightBottom = new Point(rightBottom.X, scaledY);
+ }
+
+ if (bottomRight.X < bottomLeft.X)
+ {
+ var scaledX = borderCoordinates.LeftBottom / (borderCoordinates.LeftBottom + borderCoordinates.RightBottom) * boundRect.Width;
+ bottomRight = new Point(scaledX, bottomRight.Y);
+ bottomLeft = new Point(scaledX, bottomLeft.Y);
+ }
+
+ if (leftBottom.Y < leftTop.Y)
+ {
+ var scaledY = borderCoordinates.TopLeft / (borderCoordinates.TopLeft + borderCoordinates.BottomLeft) * boundRect.Height;
+ leftBottom = new Point(leftBottom.X, scaledY);
+ leftTop = new Point(leftTop.X, scaledY);
+ }
+
+ var offset = new Vector(boundRect.TopLeft.X, boundRect.TopLeft.Y);
+ topLeft += offset;
+ topRight += offset;
+ rightTop += offset;
+ rightBottom += offset;
+ bottomRight += offset;
+ bottomLeft += offset;
+ leftBottom += offset;
+ leftTop += offset;
+
+ context.BeginFigure(topLeft, true);
+
+ //Top
+ context.LineTo(topRight);
+
+ //TopRight corner
+ var radiusX = boundRect.TopRight.X - topRight.X;
+ var radiusY = rightTop.Y - boundRect.TopRight.Y;
+ if (radiusX != 0 || radiusY != 0)
+ {
+ context.ArcTo(rightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ //Right
+ context.LineTo(rightBottom);
+
+ //BottomRight corner
+ radiusX = boundRect.BottomRight.X - bottomRight.X;
+ radiusY = boundRect.BottomRight.Y - rightBottom.Y;
+ if (radiusX != 0 || radiusY != 0)
+ {
+ context.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ //Bottom
+ context.LineTo(bottomLeft);
+
+ //BottomLeft corner
+ radiusX = bottomLeft.X - boundRect.BottomLeft.X;
+ radiusY = boundRect.BottomLeft.Y - leftBottom.Y;
+ if (radiusX != 0 || radiusY != 0)
+ {
+ context.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ //Left
+ context.LineTo(leftTop);
+
+ //TopLeft corner
+ radiusX = topLeft.X - boundRect.TopLeft.X;
+ radiusY = leftTop.Y - boundRect.TopLeft.Y;
+
+ if (radiusX != 0 || radiusY != 0)
+ {
+ context.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ context.EndFigure(true);
+ }
+
+ private struct BorderCoordinates
+ {
+ internal BorderCoordinates(CornerRadius cornerRadius, Thickness borderThickness, bool isOuter)
+ {
+ var left = 0.5 * borderThickness.Left;
+ var top = 0.5 * borderThickness.Top;
+ var right = 0.5 * borderThickness.Right;
+ var bottom = 0.5 * borderThickness.Bottom;
+
+ if (isOuter)
+ {
+ if (cornerRadius.TopLeft == 0)
+ {
+ LeftTop = TopLeft = 0.0;
+ }
+ else
+ {
+ LeftTop = cornerRadius.TopLeft + left;
+ TopLeft = cornerRadius.TopLeft + top;
+ }
+ if (cornerRadius.TopRight == 0)
+ {
+ TopRight = RightTop = 0;
+ }
+ else
+ {
+ TopRight = cornerRadius.TopRight + top;
+ RightTop = cornerRadius.TopRight + right;
+ }
+ if (cornerRadius.BottomRight == 0)
+ {
+ RightBottom = BottomRight = 0;
+ }
+ else
+ {
+ RightBottom = cornerRadius.BottomRight + right;
+ BottomRight = cornerRadius.BottomRight + bottom;
+ }
+ if (cornerRadius.BottomLeft == 0)
+ {
+ BottomLeft = LeftBottom = 0;
+ }
+ else
+ {
+ BottomLeft = cornerRadius.BottomLeft + bottom;
+ LeftBottom = cornerRadius.BottomLeft + left;
+ }
+ }
+ else
+ {
+ LeftTop = Math.Max(0, cornerRadius.TopLeft - left);
+ TopLeft = Math.Max(0, cornerRadius.TopLeft - top);
+ TopRight = Math.Max(0, cornerRadius.TopRight - top);
+ RightTop = Math.Max(0, cornerRadius.TopRight - right);
+ RightBottom = Math.Max(0, cornerRadius.BottomRight - right);
+ BottomRight = Math.Max(0, cornerRadius.BottomRight - bottom);
+ BottomLeft = Math.Max(0, cornerRadius.BottomLeft - bottom);
+ LeftBottom = Math.Max(0, cornerRadius.BottomLeft - left);
+ }
+ }
+
+ internal readonly double LeftTop;
+ internal readonly double TopLeft;
+ internal readonly double TopRight;
+ internal readonly double RightTop;
+ internal readonly double RightBottom;
+ internal readonly double BottomRight;
+ internal readonly double BottomLeft;
+ internal readonly double LeftBottom;
+ }
+
+ }
+}
diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
index cb86598a42..4c85e172ff 100644
--- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
+++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
@@ -20,7 +20,7 @@
Red
#10ff0000
- 2
+ 2
0.5
10
diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs
new file mode 100644
index 0000000000..db0ac97d49
--- /dev/null
+++ b/src/Avalonia.Visuals/CornerRadius.cs
@@ -0,0 +1,97 @@
+// 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.Globalization;
+using System.Linq;
+
+namespace Avalonia
+{
+ public struct CornerRadius
+ {
+ public CornerRadius(double uniformRadius)
+ {
+ TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius;
+
+ }
+ public CornerRadius(double top, double bottom)
+ {
+ TopLeft = TopRight = top;
+ BottomLeft = BottomRight = bottom;
+ }
+ public CornerRadius(double topLeft, double topRight, double bottomRight, double bottomLeft)
+ {
+ TopLeft = topLeft;
+ TopRight = topRight;
+ BottomRight = bottomRight;
+ BottomLeft = bottomLeft;
+ }
+
+ public double TopLeft { get; }
+ public double TopRight { get; }
+ public double BottomRight { get; }
+ public double BottomLeft { get; }
+ public bool IsEmpty => TopLeft.Equals(0) && IsUniform;
+ public bool IsUniform => TopLeft.Equals(TopRight) && BottomLeft.Equals(BottomRight) && TopRight.Equals(BottomRight);
+
+ public override bool Equals(object obj)
+ {
+ if (obj is CornerRadius)
+ {
+ return this == (CornerRadius)obj;
+ }
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return TopLeft.GetHashCode() ^ TopRight.GetHashCode() ^ BottomLeft.GetHashCode() ^ BottomRight.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return $"{TopLeft},{TopRight},{BottomRight},{BottomLeft}";
+ }
+
+ public static CornerRadius Parse(string s, CultureInfo culture)
+ {
+ var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.Trim())
+ .ToList();
+
+ switch (parts.Count)
+ {
+ case 1:
+ var uniform = double.Parse(parts[0], culture);
+ return new CornerRadius(uniform);
+ case 2:
+ var top = double.Parse(parts[0], culture);
+ var bottom = double.Parse(parts[1], culture);
+ return new CornerRadius(top, bottom);
+ case 4:
+ var topLeft = double.Parse(parts[0], culture);
+ var topRight = double.Parse(parts[1], culture);
+ var bottomRight = double.Parse(parts[2], culture);
+ var bottomLeft = double.Parse(parts[3], culture);
+ return new CornerRadius(topLeft, topRight, bottomRight, bottomLeft);
+ default:
+ {
+ throw new FormatException("Invalid CornerRadius.");
+ }
+ }
+ }
+
+ public static bool operator ==(CornerRadius cr1, CornerRadius cr2)
+ {
+ return cr1.TopLeft.Equals(cr2.TopLeft)
+ && cr1.TopRight.Equals(cr2.TopRight)
+ && cr1.BottomRight.Equals(cr2.BottomRight)
+ && cr1.BottomLeft.Equals(cr2.BottomLeft);
+ }
+
+ public static bool operator !=(CornerRadius cr1, CornerRadius cr2)
+ {
+ return !(cr1 == cr2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
index 87347d64b1..900746d05a 100644
--- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
+++ b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
@@ -9,6 +9,7 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")]
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs
index dc9be7341d..ead9fd004a 100644
--- a/src/Avalonia.Visuals/Thickness.cs
+++ b/src/Avalonia.Visuals/Thickness.cs
@@ -90,7 +90,12 @@ namespace Avalonia
///
/// Gets a value indicating whether all sides are set to 0.
///
- public bool IsEmpty => Left == 0 && Top == 0 && Right == 0 && Bottom == 0;
+ public bool IsEmpty => Left.Equals(0) && IsUniform;
+
+ ///
+ /// Gets a value indicating whether all sides are equal.
+ ///
+ public bool IsUniform => Left.Equals(Right) && Top.Equals(Bottom) && Right.Equals(Bottom);
///
/// Compares two Thicknesses.
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 0ce2a1a992..bd6acfdad1 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -31,6 +31,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs
new file mode 100644
index 0000000000..5da0efae1b
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs
@@ -0,0 +1,19 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Avalonia.Markup.Xaml.Converters
+{
+ public class CornerRadiusTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ return CornerRadius.Parse((string)value, culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
index 1cf5b6a58e..1ae24c8a34 100644
--- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
@@ -43,6 +43,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{ typeof(Selector), typeof(SelectorTypeConverter)},
{ typeof(SolidColorBrush), typeof(BrushTypeConverter) },
{ typeof(Thickness), typeof(ThicknessTypeConverter) },
+ { typeof(CornerRadius), typeof(CornerRadiusTypeConverter) },
{ typeof(TimeSpan), typeof(TimeSpanTypeConverter) },
//{ typeof(Uri), typeof(Converters.UriTypeConverter) },
{ typeof(Cursor), typeof(CursorTypeConverter) },
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
index 3e89dcc9b7..f057d191b6 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
@@ -1,7 +1,6 @@
// 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.Platform;
using SharpDX.Direct2D1;
@@ -20,7 +19,6 @@ namespace Avalonia.Direct2D1.Media
///
public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
- ///
public Geometry Geometry { get; }
///
diff --git a/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs b/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
index 33af55fdf9..d24a646f74 100644
--- a/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
+++ b/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
@@ -33,7 +33,7 @@ namespace Avalonia.Benchmarks.Styling
var border = (Border)textBox.GetVisualChildren().Single();
- if (border.BorderThickness != 2)
+ if (border.BorderThickness != new Thickness(2))
{
throw new Exception("Styles not applied.");
}
diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs
index c0d2a39ab2..3660a7b4ca 100644
--- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Border
{
Padding = new Thickness(6),
- BorderThickness = 4,
+ BorderThickness = new Thickness(4)
};
target.Measure(new Size(100, 100));
diff --git a/tests/Avalonia.RenderTests/Controls/BorderTests.cs b/tests/Avalonia.RenderTests/Controls/BorderTests.cs
index 3bd5a6e1cb..7d2e40c3b4 100644
--- a/tests/Avalonia.RenderTests/Controls/BorderTests.cs
+++ b/tests/Avalonia.RenderTests/Controls/BorderTests.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 1,
+ BorderThickness = new Thickness(1),
}
};
@@ -50,7 +50,47 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ [Fact]
+ public async Task Border_Uniform_CornerRadius()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ BorderBrush = Brushes.Black,
+ BorderThickness = new Thickness(2),
+ CornerRadius = new CornerRadius(16),
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ [Fact]
+ public async Task Border_NonUniform_CornerRadius()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ BorderBrush = Brushes.Black,
+ BorderThickness = new Thickness(2),
+ CornerRadius = new CornerRadius(16, 4, 7, 10),
}
};
@@ -87,7 +127,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new Border
{
Background = Brushes.Red,
@@ -110,7 +150,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Padding = new Thickness(2),
Child = new Border
{
@@ -134,7 +174,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new Border
{
Background = Brushes.Red,
@@ -159,7 +199,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@@ -186,7 +226,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@@ -213,7 +253,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@@ -240,7 +280,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@@ -267,7 +307,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@@ -294,7 +334,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@@ -321,7 +361,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@@ -348,7 +388,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
diff --git a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
index 099b022862..cfa15ae304 100644
--- a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
new Border
{
BorderBrush = Brushes.Blue,
- BorderThickness = 2,
+ BorderThickness = new Thickness(2),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Child = new TextBlock
diff --git a/tests/Avalonia.RenderTests/Shapes/PathTests.cs b/tests/Avalonia.RenderTests/Shapes/PathTests.cs
index fab867f428..4703daca25 100644
--- a/tests/Avalonia.RenderTests/Shapes/PathTests.cs
+++ b/tests/Avalonia.RenderTests/Shapes/PathTests.cs
@@ -316,7 +316,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
Child = new Border
{
BorderBrush = Brushes.Red,
- BorderThickness = 1,
+ BorderThickness = new Thickness(1),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Child = new Path
diff --git a/tests/Avalonia.Styling.UnitTests/StyleTests.cs b/tests/Avalonia.Styling.UnitTests/StyleTests.cs
index a7c559668b..5ef559b887 100644
--- a/tests/Avalonia.Styling.UnitTests/StyleTests.cs
+++ b/tests/Avalonia.Styling.UnitTests/StyleTests.cs
@@ -151,7 +151,7 @@ namespace Avalonia.Styling.UnitTests
{
Setters = new[]
{
- new Setter(Border.BorderThicknessProperty, 4),
+ new Setter(Border.BorderThicknessProperty, new Thickness(4)),
}
};
@@ -162,9 +162,9 @@ namespace Avalonia.Styling.UnitTests
style.Attach(border, null);
- Assert.Equal(4, border.BorderThickness);
+ Assert.Equal(new Thickness(4), border.BorderThickness);
root.Child = null;
- Assert.Equal(0, border.BorderThickness);
+ Assert.Equal(new Thickness(0), border.BorderThickness);
}
private class Class1 : Control
diff --git a/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs
new file mode 100644
index 0000000000..bc0bbdc867
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs
@@ -0,0 +1,43 @@
+// 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.Globalization;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests
+{
+ public class CornerRadiusTests
+ {
+ [Fact]
+ public void Parse_Parses_Single_Uniform_Radius()
+ {
+ var result = CornerRadius.Parse("3.4", CultureInfo.InvariantCulture);
+
+ Assert.Equal(new CornerRadius(3.4), result);
+ }
+
+ [Fact]
+ public void Parse_Parses_Top_Bottom()
+ {
+ var result = CornerRadius.Parse("1.1,2.2", CultureInfo.InvariantCulture);
+
+ Assert.Equal(new CornerRadius(1.1, 2.2), result);
+ }
+
+ [Fact]
+ public void Parse_Parses_TopLeft_TopRight_BottomRight_BottomLeft()
+ {
+ var result = CornerRadius.Parse("1.1,2.2,3.3,4.4", CultureInfo.InvariantCulture);
+
+ Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result);
+ }
+
+ [Fact]
+ public void Parse_Accepts_Spaces()
+ {
+ var result = CornerRadius.Parse("1.1 2.2 3.3 4.4", CultureInfo.InvariantCulture);
+
+ Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs
index bd694d073a..03bf395d1e 100644
--- a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs
@@ -4,7 +4,7 @@
using System.Globalization;
using Xunit;
-namespace Avalonia.Visuals.UnitTests.Media
+namespace Avalonia.Visuals.UnitTests
{
public class ThicknessTests
{
@@ -40,4 +40,4 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result);
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/TestFiles/Direct2D1/Controls/Border/Border_NonUniform_CornerRadius.expected.png b/tests/TestFiles/Direct2D1/Controls/Border/Border_NonUniform_CornerRadius.expected.png
new file mode 100644
index 0000000000..9deb45aaeb
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/Border/Border_NonUniform_CornerRadius.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Controls/Border/Border_Uniform_CornerRadius.expected.png b/tests/TestFiles/Direct2D1/Controls/Border/Border_Uniform_CornerRadius.expected.png
new file mode 100644
index 0000000000..a4bfa75eb8
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/Border/Border_Uniform_CornerRadius.expected.png differ
diff --git a/tests/TestFiles/Skia/Controls/Border/Border_NonUniform_CornerRadius.expected.png b/tests/TestFiles/Skia/Controls/Border/Border_NonUniform_CornerRadius.expected.png
new file mode 100644
index 0000000000..9deb45aaeb
Binary files /dev/null and b/tests/TestFiles/Skia/Controls/Border/Border_NonUniform_CornerRadius.expected.png differ
diff --git a/tests/TestFiles/Skia/Controls/Border/Border_Uniform_CornerRadius.expected.png b/tests/TestFiles/Skia/Controls/Border/Border_Uniform_CornerRadius.expected.png
new file mode 100644
index 0000000000..a4bfa75eb8
Binary files /dev/null and b/tests/TestFiles/Skia/Controls/Border/Border_Uniform_CornerRadius.expected.png differ