Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Thickness support for BorderThickness and CornerRadius #1462

Merged
merged 9 commits into from Apr 10, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
218 changes: 192 additions & 26 deletions 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
Expand All @@ -25,14 +27,16 @@ public class Border : Decorator
/// <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 BorderRenderer _borderRenderer = new BorderRenderer();

/// <summary>
/// Initializes static members of the <see cref="Border"/> class.
Expand Down Expand Up @@ -63,7 +67,7 @@ public IBrush BorderBrush
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
Expand All @@ -72,7 +76,7 @@ public double BorderThickness
/// <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); }
Expand All @@ -84,21 +88,7 @@ public float CornerRadius
/// <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);
}
_borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
}

/// <summary>
Expand All @@ -120,29 +110,205 @@ protected override Size ArrangeOverride(Size finalSize)
{
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;
}

internal static Size MeasureOverrideImpl(
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather have this class moved out of Border. Maybe put it as an internal class under Utils?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will have a look at present render tests and create new ones to represent my additions. That way we can make sure everything looks as it should be.

{
private bool _useComplexRendering;
private StreamGeometry _backgroundGeometryCache;
private StreamGeometry _borderGeometryCache;

public void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
{
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
if (borderThickness.IsUniform && cornerRadius.IsUniform)
{
_backgroundGeometryCache = null;
_borderGeometryCache = null;
_useComplexRendering = false;
}
else
{
_useComplexRendering = true;

var boundRect = new Rect(finalSize);
var innerRect = new Rect(borderThickness.Left, borderThickness.Top,
boundRect.Width - borderThickness.Right, boundRect.Height - borderThickness.Bottom);

StreamGeometry backgroundGeometry = null;

if (!boundRect.Width.Equals(0) && !boundRect.Height.Equals(0))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it supposed to be innerRect inside if?

{
backgroundGeometry = new StreamGeometry();

if (cornerRadius.IsEmpty)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why there are two identical if-else branches?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Those checks are not needed anymore.

{
using (var ctx = backgroundGeometry.Open())
{
CreateGeometry(ctx, innerRect, cornerRadius);
}
}
else
{
using (var ctx = backgroundGeometry.Open())
{
CreateGeometry(ctx, innerRect, cornerRadius);
}
}

_backgroundGeometryCache = backgroundGeometry;
}
else
{
_backgroundGeometryCache = null;
}

if (!boundRect.Width.Equals(0) && !boundRect.Height.Equals(0))
{
var borderGeometry = new StreamGeometry();

if (cornerRadius.IsEmpty)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And two more identical if-else braches

{
using (var ctx = borderGeometry.Open())
{
CreateGeometry(ctx, boundRect, cornerRadius);

if (backgroundGeometry != null)
{
CreateGeometry(ctx, innerRect, cornerRadius);
}
}
}
else
{
using (var ctx = borderGeometry.Open())
{
CreateGeometry(ctx, boundRect, cornerRadius);

if (backgroundGeometry != null)
{
CreateGeometry(ctx, innerRect, cornerRadius);
}

}
}

_borderGeometryCache = borderGeometry;
}
else
{
_borderGeometryCache = null;
}
}
}

private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, CornerRadius cornerRadius)
{
var topLeft = new Point(boundRect.X + cornerRadius.TopLeft, boundRect.Y);
var topRight = new Point(boundRect.Width - cornerRadius.TopRight, boundRect.Y);
var rightTop = new Point(boundRect.Width, boundRect.Y + cornerRadius.TopRight);
var rightBottom = new Point(boundRect.Width, boundRect.Height - cornerRadius.BottomRight);
var bottomRight = new Point(boundRect.Width - cornerRadius.BottomRight, boundRect.Height);
var bottomLeft = new Point(boundRect.X + cornerRadius.BottomLeft, boundRect.Height);
var leftBottom = new Point(boundRect.X, boundRect.Height - cornerRadius.BottomLeft);
var leftTop = new Point(boundRect.X, boundRect.Y + cornerRadius.TopLeft);

context.BeginFigure(topLeft, true);

//Top
context.LineTo(topRight);

//TopRight corner
if (topRight != rightTop)
{
context.ArcTo(rightTop, new Size(cornerRadius.TopRight, cornerRadius.TopRight), 0, false, SweepDirection.Clockwise);
}

//Right
context.LineTo(rightBottom);

//BottomRight corner
if (rightBottom != bottomRight)
{
context.ArcTo(bottomRight, new Size(cornerRadius.BottomRight, cornerRadius.BottomRight), 0, false, SweepDirection.Clockwise);
}

//Bottom
context.LineTo(bottomLeft);

//BottomLeft corner
if (bottomLeft != leftBottom)
{
context.ArcTo(leftBottom, new Size(cornerRadius.BottomLeft, cornerRadius.BottomLeft), 0, false, SweepDirection.Clockwise);
}

//Left
context.LineTo(leftTop);

//TopLeft corner
if (leftTop != topLeft)
{
context.ArcTo(topLeft, new Size(cornerRadius.TopLeft, cornerRadius.TopLeft), 0, false, SweepDirection.Clockwise);
}

context.EndFigure(true);
}

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);
}
}
}
}
}
Expand Down