Browse Source

Merge pull request #1503 from Gillibald/feature/layoutHelper

Layout helper for single child controls
Steven Kirk 7 years ago
parent
commit
0e8e8610f9

+ 3 - 25
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);
         }
     }
 }

+ 3 - 14
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>

+ 67 - 75
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>
@@ -90,19 +91,12 @@ namespace Avalonia.Controls.Presenters
         static ContentPresenter()
         {
             AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
-            AffectsMeasure(BorderThicknessProperty);
+            AffectsMeasure(BorderThicknessProperty, PaddingProperty);
             ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
             ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
             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,96 +322,68 @@ 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));
-            var size = availableSizeMinusMargins;
+            var availableSize = finalSize;
+            var sizeForChild = availableSize;
             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));
+                sizeForChild = sizeForChild.WithWidth(Math.Min(sizeForChild.Width, DesiredSize.Width));
             }
 
             if (verticalContentAlignment != VerticalAlignment.Stretch)
             {
-                size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
+                sizeForChild = sizeForChild.WithHeight(Math.Min(sizeForChild.Height, DesiredSize.Height));
             }
 
             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);
+                sizeForChild = new Size(
+                    Math.Ceiling(sizeForChild.Width * scale) / scale,
+                    Math.Ceiling(sizeForChild.Height * scale) / scale);
+                availableSize = new Size(
+                    Math.Ceiling(availableSize.Width * scale) / scale,
+                    Math.Ceiling(availableSize.Height * scale) / scale);
             }
 
             switch (horizontalContentAlignment)
             {
                 case HorizontalAlignment.Center:
-                    originX += (availableSizeMinusMargins.Width - size.Width) / 2;
+                    originX += (availableSize.Width - sizeForChild.Width) / 2;
                     break;
                 case HorizontalAlignment.Right:
-                    originX += availableSizeMinusMargins.Width - size.Width;
+                    originX += availableSize.Width - sizeForChild.Width;
                     break;
             }
 
             switch (verticalContentAlignment)
             {
                 case VerticalAlignment.Center:
-                    originY += (availableSizeMinusMargins.Height - size.Height) / 2;
+                    originY += (availableSize.Height - sizeForChild.Height) / 2;
                     break;
                 case VerticalAlignment.Bottom:
-                    originY += availableSizeMinusMargins.Height - size.Height;
+                    originY += availableSize.Height - sizeForChild.Height;
                     break;
             }
 
@@ -427,11 +393,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, sizeForChild.Width, sizeForChild.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;

+ 117 - 137
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@@ -26,17 +26,17 @@ namespace Avalonia.Controls.Utils
 
                 var boundRect = new Rect(finalSize);
                 var innerRect = boundRect.Deflate(borderThickness);
-                var innerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, false);
-
+                BorderGeometryKeypoints backgroundKeypoints = null;
                 StreamGeometry backgroundGeometry = null;
 
                 if (innerRect.Width != 0 && innerRect.Height != 0)
                 {
                     backgroundGeometry = new StreamGeometry();
+                    backgroundKeypoints = new BorderGeometryKeypoints(innerRect, borderThickness, cornerRadius, true);
 
                     using (var ctx = backgroundGeometry.Open())
                     {
-                        CreateGeometry(ctx, innerRect, innerCoordinates);
+                        CreateGeometry(ctx, innerRect, backgroundKeypoints);
                     }
 
                     _backgroundGeometryCache = backgroundGeometry;
@@ -48,16 +48,16 @@ namespace Avalonia.Controls.Utils
 
                 if (boundRect.Width != 0 && innerRect.Height != 0)
                 {
-                    var outerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, true);
+                    var borderGeometryKeypoints = new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
                     var borderGeometry = new StreamGeometry();
 
                     using (var ctx = borderGeometry.Open())
                     {
-                        CreateGeometry(ctx, boundRect, outerCoordinates);
+                        CreateGeometry(ctx, boundRect, borderGeometryKeypoints);
 
                         if (backgroundGeometry != null)
                         {
-                            CreateGeometry(ctx, innerRect, innerCoordinates);
+                            CreateGeometry(ctx, innerRect, backgroundKeypoints);
                         }
                     }
 
@@ -104,176 +104,156 @@ namespace Avalonia.Controls.Utils
             }
         }
 
-        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);
+        private class BorderGeometryKeypoints
+        {           
+            internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, bool inner)
+            {
+                var left = 0.5 * borderThickness.Left;
+                var top = 0.5 * borderThickness.Top;
+                var right = 0.5 * borderThickness.Right;
+                var bottom = 0.5 * borderThickness.Bottom;
 
+                double leftTopY;
+                double topLeftX;
+                double topRightX;
+                double rightTopY;
+                double rightBottomY;
+                double bottomRightX;
+                double bottomLeftX;
+                double leftBottomY;
 
-            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 (inner)
+                {
+                    leftTopY = Math.Max(0, cornerRadius.TopLeft - top) + boundRect.TopLeft.Y;
+                    topLeftX = Math.Max(0, cornerRadius.TopLeft - left) + boundRect.TopLeft.X;
+                    topRightX = boundRect.Width - Math.Max(0, cornerRadius.TopRight - top) + boundRect.TopLeft.X;
+                    rightTopY = Math.Max(0, cornerRadius.TopRight - right) + boundRect.TopLeft.Y;
+                    rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) + boundRect.TopLeft.Y;
+                    bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) + boundRect.TopLeft.X;
+                    bottomLeftX = Math.Max(0, cornerRadius.BottomLeft - left) + boundRect.TopLeft.X;
+                    leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) + boundRect.TopLeft.Y;
+                }
+                else
+                {
+                   
+                    leftTopY = cornerRadius.TopLeft + top + boundRect.TopLeft.Y;
+                    topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;                   
+                    topRightX = boundRect.Width - (cornerRadius.TopRight + right) + boundRect.TopLeft.X;
+                    rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;                                   
+                    rightBottomY = boundRect.Height - (cornerRadius.BottomRight + bottom) + boundRect.TopLeft.Y;
+                    bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;                
+                    bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;               
+                    leftBottomY = boundRect.Height - (cornerRadius.BottomLeft + bottom) + boundRect.TopLeft.Y;
+                }              
+
+                var leftTopX = boundRect.TopLeft.X;               
+                var topLeftY = boundRect.TopLeft.Y;              
+                var topRightY = boundRect.TopLeft.Y;
+                var rightTopX = boundRect.Width + boundRect.TopLeft.X;              
+                var rightBottomX = boundRect.Width + boundRect.TopLeft.X;                             
+                var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;            
+                var bottomLeftY = boundRect.Height + boundRect.TopLeft.Y;
+                var leftBottomX = boundRect.TopLeft.X;           
+
+                LeftTop = new Point(leftTopX, leftTopY);
+                TopLeft = new Point(topLeftX, topLeftY);
+                TopRight = new Point(topRightX, topRightY);
+                RightTop = new Point(rightTopX, rightTopY);
+                RightBottom = new Point(rightBottomX, rightBottomY);
+                BottomRight = new Point(bottomRightX, bottomRightY);
+                BottomLeft = new Point(bottomLeftX, bottomLeftY);
+                LeftBottom = new Point(leftBottomX, leftBottomY);
+
+                //Fix overlap
+                if (TopLeft.X > TopRight.X)
+                {
+                    var scaledX = topLeftX / (topLeftX + topRightX) * 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 (RightTop.Y > RightBottom.Y)
+                {
+                    var scaledY = rightBottomY / (rightTopY + rightBottomY) * 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 (BottomRight.X < BottomLeft.X)
+                {
+                    var scaledX = bottomLeftX / (bottomLeftX + bottomRightX) * 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);
+                if (LeftBottom.Y < LeftTop.Y)
+                {
+                    var scaledY = leftTopY / (leftTopY + leftBottomY) * 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;
+            internal Point LeftTop { get; private set; }
+            internal Point TopLeft { get; private set; }
+            internal Point TopRight { get; private set; }
+            internal Point RightTop { get; private set; }
+            internal Point RightBottom { get; private set; }
+            internal Point BottomRight { get; private set; }
+            internal Point BottomLeft { get; private set; }
+            internal Point LeftBottom { get; private set; }
+        }
 
-            context.BeginFigure(topLeft, true);
+        private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints)
+        {
+            context.BeginFigure(keypoints.TopLeft, true);
 
             //Top
-            context.LineTo(topRight);
+            context.LineTo(keypoints.TopRight);
 
             //TopRight corner
-            var radiusX = boundRect.TopRight.X - topRight.X;
-            var radiusY = rightTop.Y - boundRect.TopRight.Y;
+            var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
+            var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
             if (radiusX != 0 || radiusY != 0)
             {
-                context.ArcTo(rightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
+                context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
             }
 
             //Right
-            context.LineTo(rightBottom);
+            context.LineTo(keypoints.RightBottom);
 
             //BottomRight corner
-            radiusX = boundRect.BottomRight.X - bottomRight.X;
-            radiusY = boundRect.BottomRight.Y - rightBottom.Y;
+            radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
+            radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
             if (radiusX != 0 || radiusY != 0)
             {
-                context.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+                context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
             }
 
             //Bottom
-            context.LineTo(bottomLeft);
+            context.LineTo(keypoints.BottomLeft);
 
             //BottomLeft corner
-            radiusX = bottomLeft.X - boundRect.BottomLeft.X;
-            radiusY = boundRect.BottomLeft.Y - leftBottom.Y;
+            radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
+            radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
             if (radiusX != 0 || radiusY != 0)
             {
-                context.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+                context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
             }
 
             //Left
-            context.LineTo(leftTop);
+            context.LineTo(keypoints.LeftTop);
 
             //TopLeft corner
-            radiusX = topLeft.X - boundRect.TopLeft.X;
-            radiusY = leftTop.Y - boundRect.TopLeft.Y;
+            radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
+            radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;
 
             if (radiusX != 0 || radiusY != 0)
             {
-                context.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+                context.ArcTo(keypoints.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;
-        }
-
     }
 }

+ 29 - 0
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;
+        }
     }
 }

+ 12 - 6
src/Avalonia.Themes.Default/ButtonSpinner.xaml

@@ -46,12 +46,15 @@
                 BorderBrush="{TemplateBinding BorderBrush}"
                 BorderThickness="{TemplateBinding BorderThickness}"
                 Margin="{TemplateBinding Padding}"
-                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
-                VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
+                HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
+                VerticalAlignment="{TemplateBinding VerticalAlignment}">
           <Grid ColumnDefinitions="*,Auto">
             <ContentPresenter Name="PART_ContentPresenter" Grid.Column="0"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
-                              Content="{TemplateBinding Content}"/>
+                              Content="{TemplateBinding Content}"
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Padding="{TemplateBinding Padding}"/>
             <Grid Grid.Column="1" RowDefinitions="*,*" IsVisible="{TemplateBinding ShowButtonSpinner}">
               <RepeatButton Grid.Row="0" Name="PART_IncreaseButton"/>
               <RepeatButton Grid.Row="1" Name="PART_DecreaseButton"/>
@@ -68,8 +71,8 @@
                 BorderBrush="{TemplateBinding BorderBrush}"
                 BorderThickness="{TemplateBinding BorderThickness}"
                 Margin="{TemplateBinding Padding}"
-                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
-                VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
+                HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
+                VerticalAlignment="{TemplateBinding VerticalAlignment}">
           <Grid ColumnDefinitions="Auto,*">
             <Grid Grid.Column="0" RowDefinitions="*,*" IsVisible="{TemplateBinding ShowButtonSpinner}">
               <RepeatButton Grid.Row="0" Name="PART_IncreaseButton"/>
@@ -77,7 +80,10 @@
             </Grid>
             <ContentPresenter Name="PART_ContentPresenter" Grid.Column="1"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
-                              Content="{TemplateBinding Content}"/>
+                              Content="{TemplateBinding Content}"
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Padding="{TemplateBinding Padding}"/>
           </Grid>
         </Border>
       </ControlTemplate>

+ 3 - 1
src/Avalonia.Themes.Default/ContentControl.xaml

@@ -7,7 +7,9 @@
                         BorderThickness="{TemplateBinding BorderThickness}"
                         ContentTemplate="{TemplateBinding ContentTemplate}"
                         Content="{TemplateBinding Content}"
-                        Padding="{TemplateBinding Padding}"/>
+                        Padding="{TemplateBinding Padding}"
+                        VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/>
     </ControlTemplate>
   </Setter>
 </Style>

+ 16 - 9
src/Avalonia.Themes.Default/Expander.xaml

@@ -18,8 +18,9 @@
                               IsVisible="{TemplateBinding IsExpanded}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               Content="{TemplateBinding Content}"
-                              HorizontalAlignment="Stretch"
-                              VerticalAlignment="Stretch" />
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Padding="{TemplateBinding Padding}" />
           </Grid>
         </Border>
       </ControlTemplate>
@@ -36,8 +37,9 @@
                               IsVisible="{TemplateBinding IsExpanded}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               Content="{TemplateBinding Content}"
-                              HorizontalAlignment="Stretch"
-                              VerticalAlignment="Stretch" />
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Padding="{TemplateBinding Padding}" />
           </Grid>
         </Border>
       </ControlTemplate>
@@ -54,8 +56,9 @@
                               IsVisible="{TemplateBinding IsExpanded}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               Content="{TemplateBinding Content}"
-                              HorizontalAlignment="Stretch"
-                              VerticalAlignment="Stretch" />
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Padding="{TemplateBinding Padding}" />
           </Grid>
         </Border>
       </ControlTemplate>
@@ -72,8 +75,9 @@
                               IsVisible="{TemplateBinding IsExpanded}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               Content="{TemplateBinding Content}"
-                              HorizontalAlignment="Stretch"
-                              VerticalAlignment="Stretch" />
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Padding="{TemplateBinding Padding}" />
           </Grid>
         </Border>
       </ControlTemplate>
@@ -94,7 +98,10 @@
                               Grid.Column="1" 
                               Background="Transparent" 
                               Content="{TemplateBinding Content}" 
-                              VerticalAlignment="Center" />
+                              VerticalAlignment="Center" 
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                              Padding="{TemplateBinding Padding}"/>
           </Grid>
         </Border>
       </ControlTemplate>

+ 3 - 1
src/Avalonia.Themes.Default/ListBoxItem.xaml

@@ -9,7 +9,9 @@
                           BorderThickness="{TemplateBinding BorderThickness}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           Content="{TemplateBinding Content}"
-                          Padding="{TemplateBinding Padding}"/>
+                          Padding="{TemplateBinding Padding}"
+                          VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                          HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/>
       </ControlTemplate>
     </Setter>
   </Style>

+ 1 - 1
src/Avalonia.Themes.Default/RepeatButton.xaml

@@ -25,7 +25,7 @@
                                   Padding="{TemplateBinding Padding}"
                                   TextBlock.Foreground="{TemplateBinding Foreground}"
                                   HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
-                                  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
+                                  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
             </ControlTemplate>
         </Setter>
     </Style>

+ 2 - 0
src/Avalonia.Themes.Default/TabStripItem.xaml

@@ -11,6 +11,8 @@
                           BorderThickness="{TemplateBinding BorderThickness}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           Content="{TemplateBinding Content}"
+                          HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                          VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                           Padding="{TemplateBinding Padding}"/>
       </ControlTemplate>
     </Setter>

+ 2 - 0
src/Avalonia.Themes.Default/TreeViewItem.xaml

@@ -13,6 +13,8 @@
                               BorderBrush="{TemplateBinding BorderBrush}"
                               BorderThickness="{TemplateBinding BorderThickness}"
                               Content="{TemplateBinding Header}"
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                               Padding="{TemplateBinding Padding}"
                               TemplatedControl.IsTemplateFocusTarget="True"
                               Grid.Column="1"/>

+ 5 - 3
src/Avalonia.Themes.Default/Window.xaml

@@ -6,10 +6,12 @@
     <ControlTemplate>
       <Border Background="{TemplateBinding Background}">
         <AdornerDecorator>
-          <ContentPresenter Name="PART_ContentPresenter" 
+          <ContentPresenter Name="PART_ContentPresenter"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
-                            Content="{TemplateBinding Content}" 
-                            Margin="{TemplateBinding Padding}"/>
+                            Content="{TemplateBinding Content}"
+                            Margin="{TemplateBinding Padding}"
+                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>           
         </AdornerDecorator>
       </Border>
     </ControlTemplate>

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs

@@ -233,7 +233,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
             target.Arrange(new Rect(0, 0, 100, 100));
 
-            Assert.Equal(new Rect(48, 48, 0, 0), content.Bounds);
+            Assert.Equal(new Rect(32, 32, 0, 0), content.Bounds);
         }
     }
 }

+ 1 - 1
tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs

@@ -296,7 +296,7 @@ namespace Avalonia.Markup.UnitTests.Data
             var data = new Class1 { DoubleValue = 5.6 };
             var converter = new Mock<IValueConverter>();
             var target = new BindingExpression(
-                new ExpressionObserver(data, "DoubleValue"), 
+                new ExpressionObserver(data, "DoubleValue"),
                 typeof(string),
                 converter.Object,
                 converterParameter: "foo");