diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index 002c5ea3f2..4ddf81565a 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 System;
+using Avalonia;
using Avalonia.Media;
namespace Avalonia.Controls
@@ -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 BorderRenderer _borderRenderer = new BorderRenderer();
///
/// 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);
- }
+ _borderRenderer.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));
}
+ _borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
+
return finalSize;
}
@@ -131,18 +123,307 @@ 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);
+ }
+
+
+ internal class BorderRenderer
+ {
+ 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 innerRadii = new Radii(cornerRadius, borderThickness, false);
+
+ StreamGeometry backgroundGeometry = null;
+
+ // calculate border / background rendering geometry
+ if (!innerRect.Width.Equals(0) && !innerRect.Height.Equals(0))
+ {
+ backgroundGeometry = new StreamGeometry();
+
+ using (var ctx = backgroundGeometry.Open())
+ {
+ GenerateGeometry(ctx, innerRect, innerRadii);
+ }
+
+ _backgroundGeometryCache = backgroundGeometry;
+ }
+ else
+ {
+ _backgroundGeometryCache = null;
+ }
+
+ if (!boundRect.Width.Equals(0) && !boundRect.Height.Equals(0))
+ {
+ var outerRadii = new Radii(cornerRadius, borderThickness, true);
+ var borderGeometry = new StreamGeometry();
+
+ using (var ctx = borderGeometry.Open())
+ {
+ GenerateGeometry(ctx, boundRect, outerRadii);
+
+ if (backgroundGeometry != null)
+ {
+ GenerateGeometry(ctx, innerRect, innerRadii);
+ }
+ }
+
+ _borderGeometryCache = borderGeometry;
+ }
+ else
+ {
+ _borderGeometryCache = null;
+ }
+ }
+ }
+
+ public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush)
{
- return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
+ if (_useComplexRendering)
+ {
+ IBrush brush;
+ var borderGeometry = _borderGeometryCache;
+ if (borderGeometry != null && (brush = borderBrush) != null)
+ {
+ context.DrawGeometry(brush, null, borderGeometry);
+ }
+
+ var backgroundGeometry = _backgroundGeometryCache;
+ if (backgroundGeometry != null && (brush = background) != null)
+ {
+ context.DrawGeometry(brush, null, backgroundGeometry);
+ }
+ }
+ 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 GenerateGeometry(StreamGeometryContext ctx, Rect rect, Radii radii)
+ {
+ //
+ // Compute the coordinates of the key points
+ //
+
+ var topLeft = new Point(radii.LeftTop, 0);
+ var topRight = new Point(rect.Width - radii.RightTop, 0);
+ var rightTop = new Point(rect.Width, radii.TopRight);
+ var rightBottom = new Point(rect.Width, rect.Height - radii.BottomRight);
+ var bottomRight = new Point(rect.Width - radii.RightBottom, rect.Height);
+ var bottomLeft = new Point(radii.LeftBottom, rect.Height);
+ var leftBottom = new Point(0, rect.Height - radii.BottomLeft);
+ var leftTop = new Point(0, radii.TopLeft);
+
+ //
+ // Check keypoints for overlap and resolve by partitioning radii according to
+ // the percentage of each one.
+ //
+
+ // Top edge is handled here
+ if (topLeft.X > topRight.X)
+ {
+ var x = radii.LeftTop / (radii.LeftTop + radii.RightTop) * rect.Width;
+ topLeft += new Point(x, 0);
+ topRight += new Point(x, 0);
+ }
+
+ // Right edge
+ if (rightTop.Y > rightBottom.Y)
+ {
+ var y = radii.TopRight / (radii.TopRight + radii.BottomRight) * rect.Height;
+ rightTop += new Point(0, y);
+ rightBottom += new Point(0, y);
+ }
+
+ // Bottom edge
+ if (bottomRight.X < bottomLeft.X)
+ {
+ var x = radii.LeftBottom / (radii.LeftBottom + radii.RightBottom) * rect.Width;
+ bottomRight += new Point(x, 0);
+ bottomLeft += new Point(x, 0);
+ }
+
+ // Left edge
+ if (leftBottom.Y < leftTop.Y)
+ {
+ var y = radii.TopLeft / (radii.TopLeft + radii.BottomLeft) * rect.Height;
+ leftBottom += new Point(0, y);
+ leftTop += new Point(0, y);
+ }
+
+ //
+ // Add on offsets
+ //
+
+ var offset = new Vector(rect.TopLeft.X, rect.TopLeft.Y);
+ topLeft += offset;
+ topRight += offset;
+ rightTop += offset;
+ rightBottom += offset;
+ bottomRight += offset;
+ bottomLeft += offset;
+ leftBottom += offset;
+ leftTop += offset;
+
+ //
+ // Create the border geometry
+ //
+ ctx.BeginFigure(topLeft, true);
+
+ // Top
+ ctx.LineTo(topRight);
+
+ // TopRight
+ var radiusX = rect.TopRight.X - topRight.X;
+ var radiusY = rightTop.Y - rect.TopRight.Y;
+ if (!radiusX.Equals(0) || !radiusY.Equals(0))
+ {
+ ctx.ArcTo(rightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ // Right
+ ctx.LineTo(rightBottom);
+
+ // BottomRight
+ radiusX = rect.BottomRight.X - bottomRight.X;
+ radiusY = rect.BottomRight.Y - rightBottom.Y;
+ if (!radiusX.Equals(0) || !radiusY.Equals(0))
+ {
+ ctx.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ // Bottom
+ ctx.LineTo(bottomLeft);
+
+ // BottomLeft
+ radiusX = bottomLeft.X - rect.BottomLeft.X;
+ radiusY = rect.BottomLeft.Y - leftBottom.Y;
+ if (!radiusX.Equals(0) || !radiusY.Equals(0))
+ {
+ ctx.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ // Left
+ ctx.LineTo(leftTop);
+
+ // TopLeft
+ radiusX = topLeft.X - rect.TopLeft.X;
+ radiusY = leftTop.Y - rect.TopLeft.Y;
+ if (!radiusX.Equals(0) || !radiusY.Equals(0))
+ {
+ ctx.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+ }
+
+ ctx.EndFigure(true);
+ }
+
+ private struct Radii
+ {
+ internal Radii(CornerRadius radii, Thickness borders, bool outer)
+ {
+ var left = 0.5 * borders.Left;
+ var top = 0.5 * borders.Top;
+ var right = 0.5 * borders.Right;
+ var bottom = 0.5 * borders.Bottom;
+
+ if (outer)
+ {
+ if (radii.TopLeft.Equals(0))
+ {
+ LeftTop = TopLeft = 0.0;
+ }
+ else
+ {
+ LeftTop = radii.TopLeft + left;
+ TopLeft = radii.TopLeft + top;
+ }
+ if (radii.TopRight.Equals(0))
+ {
+ TopRight = RightTop = 0.0;
+ }
+ else
+ {
+ TopRight = radii.TopRight + top;
+ RightTop = radii.TopRight + right;
+ }
+ if (radii.BottomRight.Equals(0))
+ {
+ RightBottom = BottomRight = 0.0;
+ }
+ else
+ {
+ RightBottom = radii.BottomRight + right;
+ BottomRight = radii.BottomRight + bottom;
+ }
+ if (radii.BottomLeft.Equals(0))
+ {
+ BottomLeft = LeftBottom = 0.0;
+ }
+ else
+ {
+ BottomLeft = radii.BottomLeft + bottom;
+ LeftBottom = radii.BottomLeft + left;
+ }
+ }
+ else
+ {
+ LeftTop = Math.Max(0.0, radii.TopLeft - left);
+ TopLeft = Math.Max(0.0, radii.TopLeft - top);
+ TopRight = Math.Max(0.0, radii.TopRight - top);
+ RightTop = Math.Max(0.0, radii.TopRight - right);
+ RightBottom = Math.Max(0.0, radii.BottomRight - right);
+ BottomRight = Math.Max(0.0, radii.BottomRight - bottom);
+ BottomLeft = Math.Max(0.0, radii.BottomLeft - bottom);
+ LeftBottom = Math.Max(0.0, radii.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.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index d0a438cc2b..2d623dcbf7 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Controls.Presenters
///
/// Defines the property.
///
- public static readonly StyledProperty BorderThicknessProperty =
+ public static readonly StyledProperty BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner();
///
@@ -57,7 +57,7 @@ namespace Avalonia.Controls.Presenters
///
/// Defines the property.
///
- public static readonly StyledProperty CornerRadiusProperty =
+ public static readonly StyledProperty CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner();
///
@@ -81,6 +81,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
+ private readonly Border.BorderRenderer _borderRenderer = new Border.BorderRenderer();
///
/// Initializes static members of the class.
@@ -120,7 +121,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 +158,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); }
@@ -221,7 +222,7 @@ namespace Avalonia.Controls.Presenters
{
var content = Content;
var oldChild = Child;
- var newChild = CreateChild();
+ var newChild = CreateChild();
// Remove the old child if we're not recycling it.
if (oldChild != null && newChild != oldChild)
@@ -277,21 +278,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 +331,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,74 +363,74 @@ 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));
- }
+ var padding = Padding;
+ var borderThickness = BorderThickness;
+ var horizontalContentAlignment = HorizontalContentAlignment;
+ var verticalContentAlignment = VerticalContentAlignment;
+ var useLayoutRounding = UseLayoutRounding;
+ //Not sure about this part
+ 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));
+ }
- size = LayoutHelper.ApplyLayoutConstraints(Child, size);
+ 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);
- }
+ size = LayoutHelper.ApplyLayoutConstraints(Child, size);
- switch (horizontalContentAlignment)
- {
- case HorizontalAlignment.Center:
- case HorizontalAlignment.Stretch:
- 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:
- case VerticalAlignment.Stretch:
- originY += (availableSizeMinusMargins.Height - size.Height) / 2;
- break;
- case VerticalAlignment.Bottom:
- originY += availableSizeMinusMargins.Height - size.Height;
- break;
- }
+ switch (horizontalContentAlignment)
+ {
+ case HorizontalAlignment.Center:
+ case HorizontalAlignment.Stretch:
+ 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:
+ case VerticalAlignment.Stretch:
+ 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;
}
@@ -447,7 +438,7 @@ namespace Avalonia.Controls.Presenters
{
var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
- if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
+ if (result.Equals(0) || double.IsNaN(result) || double.IsInfinity(result))
{
throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}");
}
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.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
index cb86598a42..ebb14579b4 100644
--- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
+++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml
@@ -1,6 +1,7 @@