Browse Source

LayoutHelper for controls with single content

BorderRenderHelper refactoring
pull/1503/head
Benedikt Schroeder 8 years ago
parent
commit
da25b2f61f
  1. 28
      src/Avalonia.Controls/Border.cs
  2. 17
      src/Avalonia.Controls/Decorator.cs
  3. 118
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  4. 156
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  5. 29
      src/Avalonia.Layout/LayoutHelper.cs

28
src/Avalonia.Controls/Border.cs

@ -3,6 +3,7 @@
using Avalonia;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Media;
namespace Avalonia.Controls
@ -99,7 +100,7 @@ namespace Avalonia.Controls
/// <returns>The desired size of the control.</returns>
protected override Size MeasureOverride(Size availableSize)
{
return MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness);
return LayoutHelper.MeasureChild(Child, availableSize, Padding, BorderThickness);
}
/// <summary>
@ -109,32 +110,9 @@ namespace Avalonia.Controls
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
if (Child != null)
{
var padding = Padding + BorderThickness;
Child.Arrange(new Rect(finalSize).Deflate(padding));
}
_borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
return finalSize;
}
internal static Size MeasureOverrideImpl(
Size availableSize,
IControl child,
Thickness padding,
Thickness borderThickness)
{
padding += borderThickness;
if (child != null)
{
child.Measure(availableSize.Deflate(padding));
return child.DesiredSize.Inflate(padding);
}
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
}
}
}

17
src/Avalonia.Controls/Decorator.cs

@ -1,6 +1,7 @@
// 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.Layout;
using Avalonia.Metadata;
namespace Avalonia.Controls
@ -53,25 +54,13 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
var content = Child;
var padding = Padding;
if (content != null)
{
content.Measure(availableSize.Deflate(padding));
return content.DesiredSize.Inflate(padding);
}
else
{
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
return LayoutHelper.MeasureChild(Child, availableSize, Padding);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
Child?.Arrange(new Rect(finalSize).Deflate(Padding));
return finalSize;
return LayoutHelper.ArrangeChild(Child, finalSize, Padding);
}
/// <summary>

118
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -35,6 +35,13 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="Child"/> property.
/// </summary>
@ -55,12 +62,6 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
ContentControl.ContentTemplateProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
@ -96,13 +97,6 @@ namespace Avalonia.Controls.Presenters
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentPresenter"/> class.
/// </summary>
public ContentPresenter()
{
}
/// <summary>
/// Gets or sets a brush with which to paint the background.
/// </summary>
@ -130,6 +124,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(BorderThicknessProperty, value); }
}
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
/// <summary>
/// Gets the control displayed by the presenter.
/// </summary>
@ -159,16 +162,7 @@ namespace Avalonia.Controls.Presenters
}
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// Gets or sets the horizontal alignment of the content within the border the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
@ -177,7 +171,7 @@ namespace Avalonia.Controls.Presenters
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// Gets or sets the vertical alignment of the content within the border of the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
@ -186,7 +180,7 @@ namespace Avalonia.Controls.Presenters
}
/// <summary>
/// Gets or sets the padding to place around the <see cref="Child"/> control.
/// Gets or sets the space between the border and the <see cref="Child"/> control.
/// </summary>
public Thickness Padding
{
@ -195,7 +189,7 @@ namespace Avalonia.Controls.Presenters
}
/// <inheritdoc/>
public override sealed void ApplyTemplate()
public sealed override void ApplyTemplate()
{
if (!_createdChild && ((ILogical)this).IsAttachedToLogicalTree)
{
@ -328,67 +322,41 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
return Border.MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness);
return LayoutHelper.MeasureChild(Child, availableSize, Padding, BorderThickness);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
finalSize = ArrangeOverrideImpl(finalSize, new Vector());
_borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
return finalSize;
}
/// <summary>
/// Called when the <see cref="Content"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
_createdChild = false;
if (((ILogical)this).IsAttachedToLogicalTree)
{
UpdateChild();
}
else if (Child != null)
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
Child = null;
_dataTemplate = null;
}
InvalidateMeasure();
return ArrangeOverrideImpl(finalSize, new Vector());
}
internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
{
if (Child == null) return finalSize;
var padding = Padding;
var borderThickness = BorderThickness;
var padding = Padding + 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));
Math.Max(0, finalSize.Width),
Math.Max(0, finalSize.Height));
var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var originX = offset.X + padding.Left + borderThickness.Left;
var originY = offset.Y + padding.Top + borderThickness.Top;
var originX = offset.X;
var originY = offset.Y;
if (horizontalContentAlignment != HorizontalAlignment.Stretch)
{
size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width));
}
if (verticalContentAlignment != VerticalAlignment.Stretch)
{
size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height));
}
if (useLayoutRounding)
@ -427,11 +395,37 @@ namespace Avalonia.Controls.Presenters
originY = Math.Floor(originY * scale) / scale;
}
Child.Arrange(new Rect(originX, originY, Math.Max(0, size.Width), Math.Max(0, size.Height)));
var boundsForChild =
new Rect(originX, originY, Math.Max(0, size.Width), Math.Max(0, size.Height)).Deflate(padding);
Child.Arrange(boundsForChild);
return finalSize;
}
/// <summary>
/// Called when the <see cref="Content"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
_createdChild = false;
if (((ILogical)this).IsAttachedToLogicalTree)
{
UpdateChild();
}
else if (Child != null)
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
Child = null;
_dataTemplate = null;
}
InvalidateMeasure();
}
private double GetLayoutScale()
{
var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;

156
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -26,7 +26,7 @@ namespace Avalonia.Controls.Utils
var boundRect = new Rect(finalSize);
var innerRect = boundRect.Deflate(borderThickness);
var innerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, false);
var innerCoordinates = GeometryCoordinates.CreateBackgroundCoordinates(cornerRadius, borderThickness);
StreamGeometry backgroundGeometry = null;
@ -48,7 +48,7 @@ namespace Avalonia.Controls.Utils
if (boundRect.Width != 0 && innerRect.Height != 0)
{
var outerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, true);
var outerCoordinates = GeometryCoordinates.CreateBorderCoordinates(cornerRadius, borderThickness);
var borderGeometry = new StreamGeometry();
using (var ctx = borderGeometry.Open())
@ -104,42 +104,41 @@ namespace Avalonia.Controls.Utils
}
}
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderCoordinates borderCoordinates)
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, GeometryCoordinates geometryCoordinates)
{
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);
var topLeft = new Point(geometryCoordinates.LeftTop, 0);
var topRight = new Point(boundRect.Width - geometryCoordinates.RightTop, 0);
var rightTop = new Point(boundRect.Width, geometryCoordinates.TopRight);
var rightBottom = new Point(boundRect.Width, boundRect.Height - geometryCoordinates.BottomRight);
var bottomRight = new Point(boundRect.Width - geometryCoordinates.RightBottom, boundRect.Height);
var bottomLeft = new Point(geometryCoordinates.LeftBottom, boundRect.Height);
var leftBottom = new Point(0, boundRect.Height - geometryCoordinates.BottomLeft);
var leftTop = new Point(0, geometryCoordinates.TopLeft);
if (topLeft.X > topRight.X)
{
var scaledX = borderCoordinates.LeftTop / (borderCoordinates.LeftTop + borderCoordinates.RightTop) * boundRect.Width;
var scaledX = geometryCoordinates.LeftTop / (geometryCoordinates.LeftTop + geometryCoordinates.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;
var scaledY = geometryCoordinates.TopRight / (geometryCoordinates.TopRight + geometryCoordinates.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;
var scaledX = geometryCoordinates.LeftBottom / (geometryCoordinates.LeftBottom + geometryCoordinates.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;
var scaledY = geometryCoordinates.TopLeft / (geometryCoordinates.TopLeft + geometryCoordinates.BottomLeft) * boundRect.Height;
leftBottom = new Point(leftBottom.X, scaledY);
leftTop = new Point(leftTop.X, scaledY);
}
@ -204,75 +203,88 @@ namespace Avalonia.Controls.Utils
context.EndFigure(true);
}
private struct BorderCoordinates
private struct GeometryCoordinates
{
internal BorderCoordinates(CornerRadius cornerRadius, Thickness borderThickness, bool isOuter)
internal static GeometryCoordinates CreateBorderCoordinates(CornerRadius cornerRadius, Thickness borderThickness)
{
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)
var leftTop = 0.0;
var topLeft = 0.0;
if (cornerRadius.TopLeft != 0)
{
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;
}
leftTop = cornerRadius.TopLeft + left;
topLeft = cornerRadius.TopLeft + top;
}
else
var topRight = 0.0;
var rightTop = 0.0;
if (cornerRadius.TopRight != 0)
{
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);
topRight = cornerRadius.TopRight + top;
rightTop = cornerRadius.TopRight + right;
}
var rightBottom = 0.0;
var bottomRight = 0.0;
if (cornerRadius.BottomRight != 0)
{
rightBottom = cornerRadius.BottomRight + right;
bottomRight = cornerRadius.BottomRight + bottom;
}
var bottomLeft = 0.0;
var leftBottom = 0.0;
if (cornerRadius.BottomLeft != 0)
{
bottomLeft = cornerRadius.BottomLeft + bottom;
leftBottom = cornerRadius.BottomLeft + left;
}
return new GeometryCoordinates
{
LeftTop = leftTop,
TopLeft = topLeft,
TopRight = topRight,
RightTop = rightTop,
RightBottom = rightBottom,
BottomRight = bottomRight,
BottomLeft = bottomLeft,
LeftBottom = leftBottom,
};
}
internal static GeometryCoordinates CreateBackgroundCoordinates(CornerRadius cornerRadius, Thickness borderThickness)
{
var left = 0.5 * borderThickness.Left;
var top = 0.5 * borderThickness.Top;
var right = 0.5 * borderThickness.Right;
var bottom = 0.5 * borderThickness.Bottom;
return new GeometryCoordinates
{
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;
internal double LeftTop { get; private set; }
internal double TopLeft { get; private set; }
internal double TopRight { get; private set; }
internal double RightTop { get; private set; }
internal double RightBottom { get; private set; }
internal double BottomRight { get; private set; }
internal double BottomLeft { get; private set; }
internal double LeftBottom { get; private set; }
}
}

29
src/Avalonia.Layout/LayoutHelper.cs

@ -29,5 +29,34 @@ namespace Avalonia.Layout
height = Math.Max(height, control.MinHeight);
return new Size(width, height);
}
public static Size MeasureChild(ILayoutable control, Size availableSize, Thickness padding,
Thickness borderThickness)
{
return MeasureChild(control, availableSize, padding + borderThickness);
}
public static Size MeasureChild(ILayoutable control, Size availableSize, Thickness padding)
{
if (control != null)
{
control.Measure(availableSize.Deflate(padding));
return control.DesiredSize.Inflate(padding);
}
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
public static Size ArrangeChild(ILayoutable child, Size availableSize, Thickness padding, Thickness borderThickness)
{
return ArrangeChild(child, availableSize, padding + borderThickness);
}
public static Size ArrangeChild(ILayoutable child, Size availableSize, Thickness padding)
{
child?.Arrange(new Rect(availableSize).Deflate(padding));
return availableSize;
}
}
}

Loading…
Cancel
Save