diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 8acb3603c9..0382c8d675 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/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 /// The desired size of the control. protected override Size MeasureOverride(Size availableSize) { - return MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness); + return LayoutHelper.MeasureChild(Child, availableSize, Padding, BorderThickness); } /// @@ -109,32 +110,9 @@ namespace Avalonia.Controls /// The space taken. 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); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Decorator.cs b/src/Avalonia.Controls/Decorator.cs index 4040e4b8b7..389cf66d34 100644 --- a/src/Avalonia.Controls/Decorator.cs +++ b/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 /// 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); } /// protected override Size ArrangeOverride(Size finalSize) { - Child?.Arrange(new Rect(finalSize).Deflate(Padding)); - return finalSize; + return LayoutHelper.ArrangeChild(Child, finalSize, Padding); } /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 3cc750e20d..97c85b0c6c 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -35,6 +35,13 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty BorderThicknessProperty = Border.BorderThicknessProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty CornerRadiusProperty = + Border.CornerRadiusProperty.AddOwner(); + + /// /// Defines the property. /// @@ -55,12 +62,6 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty ContentTemplateProperty = ContentControl.ContentTemplateProperty.AddOwner(); - /// - /// Defines the property. - /// - public static readonly StyledProperty CornerRadiusProperty = - Border.CornerRadiusProperty.AddOwner(); - /// /// Defines the property. /// @@ -96,13 +97,6 @@ namespace Avalonia.Controls.Presenters TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); } - /// - /// Initializes a new instance of the class. - /// - public ContentPresenter() - { - } - /// /// Gets or sets a brush with which to paint the background. /// @@ -130,6 +124,15 @@ namespace Avalonia.Controls.Presenters set { SetValue(BorderThicknessProperty, value); } } + /// + /// Gets or sets the radius of the border rounded corners. + /// + public CornerRadius CornerRadius + { + get { return GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + /// /// Gets the control displayed by the presenter. /// @@ -159,16 +162,7 @@ namespace Avalonia.Controls.Presenters } /// - /// Gets or sets the radius of the border rounded corners. - /// - public CornerRadius CornerRadius - { - get { return GetValue(CornerRadiusProperty); } - set { SetValue(CornerRadiusProperty, value); } - } - - /// - /// 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. /// public HorizontalAlignment HorizontalContentAlignment { @@ -177,7 +171,7 @@ namespace Avalonia.Controls.Presenters } /// - /// 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. /// public VerticalAlignment VerticalContentAlignment { @@ -186,7 +180,7 @@ namespace Avalonia.Controls.Presenters } /// - /// Gets or sets the padding to place around the control. + /// Gets or sets the space between the border and the control. /// public Thickness Padding { @@ -195,7 +189,7 @@ namespace Avalonia.Controls.Presenters } /// - public override sealed void ApplyTemplate() + public sealed override void ApplyTemplate() { if (!_createdChild && ((ILogical)this).IsAttachedToLogicalTree) { @@ -328,67 +322,41 @@ namespace Avalonia.Controls.Presenters /// protected override Size MeasureOverride(Size availableSize) { - return Border.MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness); + return LayoutHelper.MeasureChild(Child, availableSize, Padding, BorderThickness); } /// protected override Size ArrangeOverride(Size finalSize) { - finalSize = ArrangeOverrideImpl(finalSize, new Vector()); - _borderRenderer.Update(finalSize, BorderThickness, CornerRadius); - return finalSize; - } - - /// - /// Called when the property changes. - /// - /// The event args. - 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; } + /// + /// Called when the property changes. + /// + /// The event args. + 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; diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index d9169e51f3..e5b1b1961f 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/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; } } } diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs index 8c0a6a579b..d77cc269f9 100644 --- a/src/Avalonia.Layout/LayoutHelper.cs +++ b/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; + } } }