Browse Source

Merge branch 'master' into fixes/geometryimpl-nre

pull/1485/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
aae2e80cab
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 48
      src/Avalonia.Controls/Border.cs
  2. 147
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  3. 4
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  4. 279
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  5. 2
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  6. 97
      src/Avalonia.Visuals/CornerRadius.cs
  7. 1
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  8. 7
      src/Avalonia.Visuals/Thickness.cs
  9. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  10. 19
      src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs
  11. 1
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
  12. 2
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  13. 2
      tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
  14. 2
      tests/Avalonia.Controls.UnitTests/BorderTests.cs
  15. 66
      tests/Avalonia.RenderTests/Controls/BorderTests.cs
  16. 2
      tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
  17. 2
      tests/Avalonia.RenderTests/Shapes/PathTests.cs
  18. 6
      tests/Avalonia.Styling.UnitTests/StyleTests.cs
  19. 43
      tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs
  20. 4
      tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs
  21. BIN
      tests/TestFiles/Direct2D1/Controls/Border/Border_NonUniform_CornerRadius.expected.png
  22. BIN
      tests/TestFiles/Direct2D1/Controls/Border/Border_Uniform_CornerRadius.expected.png
  23. BIN
      tests/TestFiles/Skia/Controls/Border/Border_NonUniform_CornerRadius.expected.png
  24. BIN
      tests/TestFiles/Skia/Controls/Border/Border_Uniform_CornerRadius.expected.png

48
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
/// <summary>
/// A control which decorates a child with a border and background.
/// </summary>
public class Border : Decorator
public partial class Border : Decorator
{
/// <summary>
/// Defines the <see cref="Background"/> property.
@ -25,14 +27,16 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
AvaloniaProperty.Register<Border, double>(nameof(BorderThickness));
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
AvaloniaProperty.Register<Border, Thickness>(nameof(BorderThickness));
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<float> CornerRadiusProperty =
AvaloniaProperty.Register<Border, float>(nameof(CornerRadius));
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
AvaloniaProperty.Register<Border, CornerRadius>(nameof(CornerRadius));
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
/// <summary>
/// Initializes static members of the <see cref="Border"/> class.
@ -63,7 +67,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@ -72,7 +76,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public float CornerRadius
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@ -84,21 +88,7 @@ namespace Avalonia.Controls
/// <param name="context">The drawing context.</param>
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);
}
/// <summary>
@ -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);
}
}
}

147
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
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<ContentPresenter>();
/// <summary>
@ -57,7 +58,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<float> CornerRadiusProperty =
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
/// <summary>
@ -76,11 +77,12 @@ namespace Avalonia.Controls.Presenters
/// Defines the <see cref="Padding"/> property.
/// </summary>
public static readonly StyledProperty<Thickness> PaddingProperty =
Border.PaddingProperty.AddOwner<ContentPresenter>();
Decorator.PaddingProperty.AddOwner<ContentPresenter>();
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
/// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class.
@ -120,7 +122,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@ -157,7 +159,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public float CornerRadius
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@ -277,21 +279,7 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
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);
}
/// <summary>
@ -344,7 +332,11 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
return ArrangeOverrideImpl(finalSize, new Vector());
finalSize = ArrangeOverrideImpl(finalSize, new Vector());
_borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
return finalSize;
}
/// <summary>
@ -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;
}

4
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<TemplatedControl>();
/// <summary>
@ -132,7 +132,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets or sets the thickness of the control's border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }

279
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;
}
}
}

2
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -20,7 +20,7 @@
<SolidColorBrush x:Key="ErrorBrush">Red</SolidColorBrush>
<SolidColorBrush x:Key="ErrorBrushLight">#10ff0000</SolidColorBrush>
<sys:Double x:Key="ThemeBorderThickness">2</sys:Double>
<Thickness x:Key="ThemeBorderThickness">2</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>
<sys:Double x:Key="FontSizeSmall">10</sys:Double>

97
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);
}
}
}

1
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")]

7
src/Avalonia.Visuals/Thickness.cs

@ -90,7 +90,12 @@ namespace Avalonia
/// <summary>
/// Gets a value indicating whether all sides are set to 0.
/// </summary>
public bool IsEmpty => Left == 0 && Top == 0 && Right == 0 && Bottom == 0;
public bool IsEmpty => Left.Equals(0) && IsUniform;
/// <summary>
/// Gets a value indicating whether all sides are equal.
/// </summary>
public bool IsUniform => Left.Equals(Right) && Top.Equals(Bottom) && Right.Equals(Bottom);
/// <summary>
/// Compares two Thicknesses.

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -31,6 +31,7 @@
</Compile>
<Compile Include="AvaloniaXamlLoaderPortableXaml.cs" />
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\CornerRadiusTypeConverter.cs" />
<Compile Include="Converters\MatrixTypeConverter.cs" />
<Compile Include="Converters\RectTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />

19
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);
}
}
}

1
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) },

2
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
/// <inheritdoc/>
public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
/// <inheritdoc/>
public Geometry Geometry { get; }
/// <inheritdoc/>

2
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.");
}

2
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));

66
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",

2
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

2
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

6
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

43
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);
}
}
}

4
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);
}
}
}
}

BIN
tests/TestFiles/Direct2D1/Controls/Border/Border_NonUniform_CornerRadius.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
tests/TestFiles/Direct2D1/Controls/Border/Border_Uniform_CornerRadius.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
tests/TestFiles/Skia/Controls/Border/Border_NonUniform_CornerRadius.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
tests/TestFiles/Skia/Controls/Border/Border_Uniform_CornerRadius.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Loading…
Cancel
Save