From 9bb89479995fa2e0574a119fc3ac2d54e1c499f4 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Tue, 20 Jul 2021 09:51:05 -0600 Subject: [PATCH 1/2] Add Padding to ILayout; add padding tests for layout managers --- src/Controls/src/Core/Layout/Layout.cs | 19 +++- src/Core/src/Core/ILayout.cs | 6 +- src/Core/src/Layouts/GridLayoutManager.cs | 76 +++++++++------- .../Layouts/HorizontalStackLayoutManager.cs | 86 ++++++++++--------- .../src/Layouts/VerticalStackLayoutManager.cs | 48 ++++++----- .../Layouts/GridLayoutManagerTests.cs | 63 ++++++++++++-- .../HorizontalStackLayoutManagerTests.cs | 54 +++++++++++- .../UnitTests/Layouts/LayoutTestHelpers.cs | 6 ++ .../VerticalStackLayoutManagerTests.cs | 60 +++++++++++-- 9 files changed, 310 insertions(+), 108 deletions(-) diff --git a/src/Controls/src/Core/Layout/Layout.cs b/src/Controls/src/Core/Layout/Layout.cs index 3f4a840f431c..c7bab8ed7e20 100644 --- a/src/Controls/src/Core/Layout/Layout.cs +++ b/src/Controls/src/Core/Layout/Layout.cs @@ -8,7 +8,7 @@ namespace Microsoft.Maui.Controls.Layout2 { [ContentProperty(nameof(Children))] - public abstract class Layout : View, Maui.ILayout, IList + public abstract class Layout : View, Microsoft.Maui.ILayout, IList, IPaddingElement { ILayoutManager _layoutManager; ILayoutManager LayoutManager => _layoutManager ??= CreateLayoutManager(); @@ -29,6 +29,13 @@ public abstract class Layout : View, Maui.ILayout, IList public IView this[int index] { get => _children[index]; set => _children[index] = value; } + + public Thickness Padding + { + get => (Thickness)GetValue(PaddingElement.PaddingProperty); + set => SetValue(PaddingElement.PaddingProperty, value); + } + protected abstract ILayoutManager CreateLayoutManager(); public IEnumerator GetEnumerator() => _children.GetEnumerator(); @@ -187,5 +194,15 @@ bool ICollection.Remove(IView child) return result; } + + void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue) + { + InvalidateMeasure(); + } + + Thickness IPaddingElement.PaddingDefaultValueCreator() + { + return new Thickness(0); + } } } diff --git a/src/Core/src/Core/ILayout.cs b/src/Core/src/Core/ILayout.cs index 3a725e845b50..b2c373379663 100644 --- a/src/Core/src/Core/ILayout.cs +++ b/src/Core/src/Core/ILayout.cs @@ -8,7 +8,6 @@ namespace Microsoft.Maui /// public interface ILayout : IView, IContainer { - /// /// Gets the Layout Handler. /// @@ -25,5 +24,10 @@ public interface ILayout : IView, IContainer /// /// The child View to remove from the Layout. void Remove(IView child); + + /// + /// The space between the outer edge of the ILayout's content area and its children. + /// + Thickness Padding { get; } } } \ No newline at end of file diff --git a/src/Core/src/Layouts/GridLayoutManager.cs b/src/Core/src/Layouts/GridLayoutManager.cs index 608354005b5b..c7af1e4dfcd5 100644 --- a/src/Core/src/Layouts/GridLayoutManager.cs +++ b/src/Core/src/Layouts/GridLayoutManager.cs @@ -48,18 +48,36 @@ class GridStructure Row[] _rows { get; } Column[] _columns { get; } - IView[] _children; + IView[] _childrenToLayOut; Cell[] _cells { get; } + readonly Thickness _padding; + readonly double _rowSpacing; + readonly double _columnSpacing; + readonly IReadOnlyList _rowDefinitions; + readonly IReadOnlyList _columnDefinitions; + readonly IReadOnlyList _gridChildren; + readonly Dictionary _spans = new(); public GridStructure(IGridLayout grid, double widthConstraint, double heightConstraint) { _grid = grid; + _gridWidthConstraint = widthConstraint; _gridHeightConstraint = heightConstraint; - if (_grid.RowDefinitions.Count == 0) + // Cache these GridLayout properties so we don't have to keep looking them up via _grid + // (Property access via _grid may have performance implications for some SDKs.) + + _padding = grid.Padding; + _columnSpacing = grid.ColumnSpacing; + _rowSpacing = grid.RowSpacing; + _rowDefinitions = grid.RowDefinitions; + _columnDefinitions = grid.ColumnDefinitions; + _gridChildren = grid.Children; + + if (_rowDefinitions.Count == 0) { // Since no rows are specified, we'll create an implied row 0 _rows = new Row[1]; @@ -67,15 +85,15 @@ public GridStructure(IGridLayout grid, double widthConstraint, double heightCons } else { - _rows = new Row[_grid.RowDefinitions.Count]; + _rows = new Row[_rowDefinitions.Count]; - for (int n = 0; n < _grid.RowDefinitions.Count; n++) + for (int n = 0; n < _rowDefinitions.Count; n++) { - _rows[n] = new Row(_grid.RowDefinitions[n]); + _rows[n] = new Row(_rowDefinitions[n]); } } - if (_grid.ColumnDefinitions.Count == 0) + if (_columnDefinitions.Count == 0) { // Since no columns are specified, we'll create an implied column 0 _columns = new Column[1]; @@ -83,37 +101,37 @@ public GridStructure(IGridLayout grid, double widthConstraint, double heightCons } else { - _columns = new Column[_grid.ColumnDefinitions.Count]; + _columns = new Column[_columnDefinitions.Count]; - for (int n = 0; n < _grid.ColumnDefinitions.Count; n++) + for (int n = 0; n < _columnDefinitions.Count; n++) { - _columns[n] = new Column(_grid.ColumnDefinitions[n]); + _columns[n] = new Column(_columnDefinitions[n]); } } - // We could work out the _children array (with the Collapsed items filtered out) with a Linq 1-liner + // We could work out the _childrenToLayOut array (with the Collapsed items filtered out) with a Linq 1-liner // but doing it the hard way means we don't allocate extra enumerators, especially if we're in the // happy path where _none_ of the children are Collapsed. - var gridChildCount = _grid.Children.Count; + var gridChildCount = _gridChildren.Count; - _children = new IView[gridChildCount]; + _childrenToLayOut = new IView[gridChildCount]; int currentChild = 0; for (int n = 0; n < gridChildCount; n++) { - if (_grid.Children[n].Visibility != Visibility.Collapsed) + if (_gridChildren[n].Visibility != Visibility.Collapsed) { - _children[currentChild] = _grid.Children[n]; + _childrenToLayOut[currentChild] = _gridChildren[n]; currentChild += 1; } } if (currentChild < gridChildCount) { - Array.Resize(ref _children, currentChild); + Array.Resize(ref _childrenToLayOut, currentChild); } // We'll ignore any collapsed child views during layout - _cells = new Cell[_children.Length]; + _cells = new Cell[_childrenToLayOut.Length]; InitializeCells(); @@ -122,9 +140,9 @@ public GridStructure(IGridLayout grid, double widthConstraint, double heightCons void InitializeCells() { - for (int n = 0; n < _children.Length; n++) + for (int n = 0; n < _childrenToLayOut.Length; n++) { - var view = _children[n]; + var view = _childrenToLayOut[n]; if (view.Visibility == Visibility.Collapsed) { @@ -188,12 +206,12 @@ public Rectangle GetCellBoundsFor(IView view) public double GridHeight() { - return SumDefinitions(_rows, _grid.RowSpacing); + return SumDefinitions(_rows, _rowSpacing) + _padding.VerticalThickness; } public double GridWidth() { - return SumDefinitions(_columns, _grid.ColumnSpacing); + return SumDefinitions(_columns, _columnSpacing) + _padding.HorizontalThickness; } double SumDefinitions(Definition[] definitions, double spacing) @@ -237,7 +255,7 @@ void MeasureCells() if (cell.IsColumnSpanAuto || cell.IsRowSpanAuto) { - var measure = _children[cell.ViewIndex].Measure(availableWidth, availableHeight); + var measure = _childrenToLayOut[cell.ViewIndex].Measure(availableWidth, availableHeight); if (cell.IsColumnSpanAuto) { @@ -297,11 +315,11 @@ void ResolveSpans() { if (span.IsColumn) { - ResolveSpan(_columns, span.Start, span.Length, _grid.ColumnSpacing, span.Requested); + ResolveSpan(_columns, span.Start, span.Length, _columnSpacing, span.Requested); } else { - ResolveSpan(_rows, span.Start, span.Length, _grid.RowSpacing, span.Requested); + ResolveSpan(_rows, span.Start, span.Length, _rowSpacing, span.Requested); } } } @@ -355,12 +373,12 @@ void ResolveSpan(Definition[] definitions, int start, int length, double spacing double LeftEdgeOfColumn(int column) { - double left = 0; + double left = _padding.Left; for (int n = 0; n < column; n++) { left += _columns[n].Size; - left += _grid.ColumnSpacing; + left += _columnSpacing; } return left; @@ -368,12 +386,12 @@ double LeftEdgeOfColumn(int column) double TopEdgeOfRow(int row) { - double top = 0; + double top = _padding.Top; for (int n = 0; n < row; n++) { top += _rows[n].Size; - top += _grid.RowSpacing; + top += _rowSpacing; } return top; @@ -411,7 +429,7 @@ void ResolveStars(Definition[] defs, double availableSpace, Func cel if (cellCheck(cell)) // Check whether this cell should count toward the type of star value were measuring { // Update the star width if the view in this cell is bigger - starSize = Math.Max(starSize, dimension(_grid.Children[cell.ViewIndex].DesiredSize)); + starSize = Math.Max(starSize, dimension(_gridChildren[cell.ViewIndex].DesiredSize)); } } } @@ -466,7 +484,7 @@ void EnsureFinalMeasure() width += _columns[n].Size; } - _children[cell.ViewIndex].Measure(width, height); + _childrenToLayOut[cell.ViewIndex].Measure(width, height); } } } diff --git a/src/Core/src/Layouts/HorizontalStackLayoutManager.cs b/src/Core/src/Layouts/HorizontalStackLayoutManager.cs index b36bf3739adb..6d156c6d5da4 100644 --- a/src/Core/src/Layouts/HorizontalStackLayoutManager.cs +++ b/src/Core/src/Layouts/HorizontalStackLayoutManager.cs @@ -12,89 +12,91 @@ public HorizontalStackLayoutManager(IStackLayout layout) : base(layout) public override Size Measure(double widthConstraint, double heightConstraint) { - var measure = Measure(heightConstraint, Stack.Spacing, Stack.Children); + var children = Stack.Children; + var padding = Stack.Padding; + + double measuredWidth = 0; + double measuredHeight = 0; - var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, measure.Width); + for (int n = 0; n < children.Count; n++) + { + var child = children[n]; + + if (child.Visibility == Visibility.Collapsed) + { + continue; + } + + var measure = child.Measure(double.PositiveInfinity, heightConstraint); + measuredWidth += measure.Width; + measuredHeight = Math.Max(measuredHeight, measure.Height); + } + + measuredWidth += MeasureSpacing(Stack.Spacing, children.Count); + measuredWidth += padding.HorizontalThickness; + measuredHeight += padding.VerticalThickness; + + var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, measuredWidth); + var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, measuredHeight); - return new Size(finalWidth, measure.Height); + return new Size(finalWidth, finalHeight); } public override void ArrangeChildren(Rectangle bounds) { + var children = Stack.Children; + var padding = Stack.Padding; + var height = bounds.Height - padding.VerticalThickness; + if (Stack.FlowDirection == FlowDirection.LeftToRight) { - ArrangeLeftToRight(bounds.Height, Stack.Spacing, Stack.Children); + ArrangeLeftToRight(height, padding.Left, padding.Top, Stack.Spacing, children); } else { // We _could_ simply reverse the list of child views when arranging from right to left, // but this way we avoid extra list and enumerator allocations - ArrangeRightToLeft(bounds.Height, Stack.Spacing, Stack.Children); + ArrangeRightToLeft(height, padding.Left, padding.Top, Stack.Spacing, children); } } - static Size Measure(double heightConstraint, double spacing, IReadOnlyList views) - { - double totalRequestedWidth = 0; - double requestedHeight = 0; - - for (int n = 0; n < views.Count; n++) - { - var child = views[n]; - - if (child.Visibility == Visibility.Collapsed) - { - continue; - } - - var measure = child.Measure(double.PositiveInfinity, heightConstraint); - totalRequestedWidth += measure.Width; - requestedHeight = Math.Max(requestedHeight, measure.Height); - } - - var accountForSpacing = MeasureSpacing(spacing, views.Count); - totalRequestedWidth += accountForSpacing; - - return new Size(totalRequestedWidth, requestedHeight); - } - - static void ArrangeLeftToRight(double height, double spacing, IReadOnlyList views) + static void ArrangeLeftToRight(double height, double left, double top, double spacing, IReadOnlyList children) { - double xPosition = 0; + double xPosition = left; - for (int n = 0; n < views.Count; n++) + for (int n = 0; n < children.Count; n++) { - var child = views[n]; + var child = children[n]; if (child.Visibility == Visibility.Collapsed) { continue; } - xPosition += ArrangeChild(child, height, spacing, xPosition); + xPosition += ArrangeChild(child, height, top, spacing, xPosition); } } - static void ArrangeRightToLeft(double height, double spacing, IReadOnlyList views) + static void ArrangeRightToLeft(double height, double left, double top, double spacing, IReadOnlyList children) { - double xPostition = 0; + double xPostition = left; - for (int n = views.Count - 1; n >= 0; n--) + for (int n = children.Count - 1; n >= 0; n--) { - var child = views[n]; + var child = children[n]; if (child.Visibility == Visibility.Collapsed) { continue; } - xPostition += ArrangeChild(child, height, spacing, xPostition); + xPostition += ArrangeChild(child, height, top, spacing, xPostition); } } - static double ArrangeChild(IView child, double height, double spacing, double x) + static double ArrangeChild(IView child, double height, double top, double spacing, double x) { - var destination = new Rectangle(x, 0, child.DesiredSize.Width, height); + var destination = new Rectangle(x, top, child.DesiredSize.Width, height); child.Frame = child.ComputeFrame(destination); child.Arrange(child.Frame); return destination.Width + spacing; diff --git a/src/Core/src/Layouts/VerticalStackLayoutManager.cs b/src/Core/src/Layouts/VerticalStackLayoutManager.cs index 3810ef7f1c5f..bcc23ce180d4 100644 --- a/src/Core/src/Layouts/VerticalStackLayoutManager.cs +++ b/src/Core/src/Layouts/VerticalStackLayoutManager.cs @@ -12,53 +12,55 @@ public VerticalStackLayoutManager(IStackLayout stackLayout) : base(stackLayout) public override Size Measure(double widthConstraint, double heightConstraint) { - var measure = Measure(widthConstraint, Stack.Spacing, Stack.Children); + var children = Stack.Children; + var padding = Stack.Padding; - var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, measure.Height); + double measuredHeight = 0; + double measuredWidth = 0; - return new Size(measure.Width, finalHeight); - } - - public override void ArrangeChildren(Rectangle bounds) => Arrange(bounds.Width, Stack.Spacing, Stack.Children); - - static Size Measure(double widthConstraint, double spacing, IReadOnlyList views) - { - double totalRequestedHeight = 0; - double requestedWidth = 0; - - foreach (var child in views) + for (int n = 0; n < children.Count; n++) { + var child = children[n]; + if (child.Visibility == Visibility.Collapsed) { continue; } var measure = child.Measure(widthConstraint, double.PositiveInfinity); - totalRequestedHeight += measure.Height; - requestedWidth = Math.Max(requestedWidth, measure.Width); + measuredHeight += measure.Height; + measuredWidth = Math.Max(measuredWidth, measure.Width); } - var accountForSpacing = MeasureSpacing(spacing, views.Count); - totalRequestedHeight += accountForSpacing; + measuredHeight += MeasureSpacing(Stack.Spacing, children.Count); + measuredHeight += padding.VerticalThickness; + measuredWidth += padding.HorizontalThickness; - return new Size(requestedWidth, totalRequestedHeight); + var finalHeight = ResolveConstraints(heightConstraint, Stack.Height, measuredHeight); + var finalWidth = ResolveConstraints(widthConstraint, Stack.Width, measuredWidth); + + return new Size(finalWidth, finalHeight); } - static void Arrange(double width, double spacing, IEnumerable views) + public override void ArrangeChildren(Rectangle bounds) { - double stackHeight = 0; + var padding = Stack.Padding; + + double stackHeight = padding.Top; + double left = padding.Left; + double width = bounds.Width - padding.HorizontalThickness; - foreach (var child in views) + foreach (var child in Stack.Children) { if (child.Visibility == Visibility.Collapsed) { continue; } - var destination = new Rectangle(0, stackHeight, width, child.DesiredSize.Height); + var destination = new Rectangle(left, stackHeight, width, child.DesiredSize.Height); child.Frame = child.ComputeFrame(destination); child.Arrange(child.Frame); - stackHeight += destination.Height + spacing; + stackHeight += destination.Height + Stack.Spacing; } } } diff --git a/src/Core/tests/UnitTests/Layouts/GridLayoutManagerTests.cs b/src/Core/tests/UnitTests/Layouts/GridLayoutManagerTests.cs index 0f6437105e70..3b8d86927030 100644 --- a/src/Core/tests/UnitTests/Layouts/GridLayoutManagerTests.cs +++ b/src/Core/tests/UnitTests/Layouts/GridLayoutManagerTests.cs @@ -123,12 +123,6 @@ Size MeasureAndArrange(IGridLayout grid, double widthConstraint = double.Positiv return measuredSize; } - void AssertArranged(IView view, double x, double y, double width, double height) - { - var expected = new Rectangle(x, y, width, height); - view.Received().Arrange(Arg.Is(expected)); - } - [Category(GridAutoSizing)] [Fact] public void OneAutoRowOneAutoColumn() @@ -1131,5 +1125,62 @@ public void DoesNotIgnoreHiddenViews() hiddenView.Received().Measure(Arg.Any(), Arg.Any()); hiddenView.Received().Arrange(Arg.Any()); } + + IGridLayout BuildPaddedGrid(Thickness padding, double viewWidth, double viewHeight) + { + var grid = CreateGridLayout(); + var view = CreateTestView(new Size(viewWidth, viewHeight)); + AddChildren(grid, view); + SetLocation(grid, view); + + grid.Padding.Returns(padding); + + return grid; + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(10, 10, 10, 10)] + [InlineData(10, 0, 10, 0)] + [InlineData(0, 10, 0, 10)] + [InlineData(23, 5, 3, 15)] + public void MeasureAccountsForPadding(double left, double top, double right, double bottom) + { + var viewWidth = 100d; + var viewHeight = 100d; + var padding = new Thickness(left, top, right, bottom); + + var expectedHeight = padding.VerticalThickness + viewHeight; + var expectedWidth = padding.HorizontalThickness + viewWidth; + + var grid = BuildPaddedGrid(padding, viewWidth, viewHeight); + + var manager = new GridLayoutManager(grid); + var measuredSize = manager.Measure(double.PositiveInfinity, double.PositiveInfinity); + + Assert.Equal(expectedHeight, measuredSize.Height); + Assert.Equal(expectedWidth, measuredSize.Width); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(10, 10, 10, 10)] + [InlineData(10, 0, 10, 0)] + [InlineData(0, 10, 0, 10)] + [InlineData(23, 5, 3, 15)] + public void ArrangeAccountsForPadding(double left, double top, double right, double bottom) + { + var viewWidth = 100d; + var viewHeight = 100d; + var padding = new Thickness(left, top, right, bottom); + + var grid = BuildPaddedGrid(padding, viewWidth, viewHeight); + + var manager = new GridLayoutManager(grid); + var measuredSize = manager.Measure(double.PositiveInfinity, double.PositiveInfinity); + manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize)); + + AssertArranged(grid.Children[0], padding.Left, padding.Top, viewWidth, viewHeight); + } } } diff --git a/src/Core/tests/UnitTests/Layouts/HorizontalStackLayoutManagerTests.cs b/src/Core/tests/UnitTests/Layouts/HorizontalStackLayoutManagerTests.cs index 80f93542bd7f..260f2704b3b5 100644 --- a/src/Core/tests/UnitTests/Layouts/HorizontalStackLayoutManagerTests.cs +++ b/src/Core/tests/UnitTests/Layouts/HorizontalStackLayoutManagerTests.cs @@ -5,6 +5,7 @@ using Microsoft.Maui.Primitives; using NSubstitute; using Xunit; +using static Microsoft.Maui.UnitTests.Layouts.LayoutTestHelpers; namespace Microsoft.Maui.UnitTests.Layouts { @@ -122,7 +123,6 @@ public void RtlShouldHaveFirstItemOnTheRight() stack.Children[1].Received().Arrange(Arg.Is(expectedRectangle1)); } - [Fact] public void IgnoresCollapsedViews() { @@ -172,5 +172,57 @@ public void DoesNotIgnoreHiddenViews() hiddenView.Received().Measure(Arg.Any(), Arg.Any()); hiddenView.Received().Arrange(Arg.Any()); } + + IStackLayout BuildPaddedStack(Thickness padding, double viewWidth, double viewHeight) + { + var stack = BuildStack(1, viewWidth, viewHeight); + stack.Padding.Returns(padding); + return stack; + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(10, 10, 10, 10)] + [InlineData(10, 0, 10, 0)] + [InlineData(0, 10, 0, 10)] + [InlineData(23, 5, 3, 15)] + public void MeasureAccountsForPadding(double left, double top, double right, double bottom) + { + var viewWidth = 100d; + var viewHeight = 100d; + var padding = new Thickness(left, top, right, bottom); + + var expectedHeight = padding.VerticalThickness + viewHeight; + var expectedWidth = padding.HorizontalThickness + viewWidth; + + var stack = BuildPaddedStack(padding, viewWidth, viewHeight); + + var manager = new HorizontalStackLayoutManager(stack); + var measuredSize = manager.Measure(double.PositiveInfinity, double.PositiveInfinity); + + Assert.Equal(expectedHeight, measuredSize.Height); + Assert.Equal(expectedWidth, measuredSize.Width); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(10, 10, 10, 10)] + [InlineData(10, 0, 10, 0)] + [InlineData(0, 10, 0, 10)] + [InlineData(23, 5, 3, 15)] + public void ArrangeAccountsForPadding(double left, double top, double right, double bottom) + { + var viewWidth = 100d; + var viewHeight = 100d; + var padding = new Thickness(left, top, right, bottom); + + var stack = BuildPaddedStack(padding, viewWidth, viewHeight); + + var manager = new HorizontalStackLayoutManager(stack); + var measuredSize = manager.Measure(double.PositiveInfinity, double.PositiveInfinity); + manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize)); + + AssertArranged(stack.Children[0], padding.Left, padding.Top, viewWidth, viewHeight); + } } } diff --git a/src/Core/tests/UnitTests/Layouts/LayoutTestHelpers.cs b/src/Core/tests/UnitTests/Layouts/LayoutTestHelpers.cs index 2224e4d0cd50..5971e5dad54c 100644 --- a/src/Core/tests/UnitTests/Layouts/LayoutTestHelpers.cs +++ b/src/Core/tests/UnitTests/Layouts/LayoutTestHelpers.cs @@ -31,5 +31,11 @@ public static void AddChildren(ILayout layout, params IView[] views) var children = new List(views); layout.Children.Returns(children.AsReadOnly()); } + + public static void AssertArranged(IView view, double x, double y, double width, double height) + { + var expected = new Rectangle(x, y, width, height); + view.Received().Arrange(Arg.Is(expected)); + } } } diff --git a/src/Core/tests/UnitTests/Layouts/VerticalStackLayoutManagerTests.cs b/src/Core/tests/UnitTests/Layouts/VerticalStackLayoutManagerTests.cs index 91166bcbb1be..cbc1dc061001 100644 --- a/src/Core/tests/UnitTests/Layouts/VerticalStackLayoutManagerTests.cs +++ b/src/Core/tests/UnitTests/Layouts/VerticalStackLayoutManagerTests.cs @@ -3,6 +3,7 @@ using Microsoft.Maui.Layouts; using NSubstitute; using Xunit; +using static Microsoft.Maui.UnitTests.Layouts.LayoutTestHelpers; namespace Microsoft.Maui.UnitTests.Layouts { @@ -55,11 +56,8 @@ public void SpacingArrangementTwoItems(int spacing) var measuredSize = manager.Measure(double.PositiveInfinity, 100); manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize)); - var expectedRectangle0 = new Rectangle(0, 0, 100, 100); - stack.Children[0].Received().Arrange(Arg.Is(expectedRectangle0)); - - var expectedRectangle1 = new Rectangle(0, 100 + spacing, 100, 100); - stack.Children[1].Received().Arrange(Arg.Is(expectedRectangle1)); + AssertArranged(stack.Children[0], 0, 0, 100, 100); + AssertArranged(stack.Children[1], 0, 100 + spacing, 100, 100); } [Theory] @@ -131,5 +129,57 @@ public void DoesNotIgnoreHiddenViews() hiddenView.Received().Measure(Arg.Any(), Arg.Any()); hiddenView.Received().Arrange(Arg.Any()); } + + IStackLayout BuildPaddedStack(Thickness padding, double viewWidth, double viewHeight) + { + var stack = BuildStack(1, viewWidth, viewHeight); + stack.Padding.Returns(padding); + return stack; + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(10, 10, 10, 10)] + [InlineData(10, 0, 10, 0)] + [InlineData(0, 10, 0, 10)] + [InlineData(23, 5, 3, 15)] + public void MeasureAccountsForPadding(double left, double top, double right, double bottom) + { + var viewWidth = 100d; + var viewHeight = 100d; + var padding = new Thickness(left, top, right, bottom); + + var expectedHeight = padding.VerticalThickness + viewHeight; + var expectedWidth = padding.HorizontalThickness + viewWidth; + + var stack = BuildPaddedStack(padding, viewWidth, viewHeight); + + var manager = new VerticalStackLayoutManager(stack); + var measuredSize = manager.Measure(double.PositiveInfinity, double.PositiveInfinity); + + Assert.Equal(expectedHeight, measuredSize.Height); + Assert.Equal(expectedWidth, measuredSize.Width); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(10, 10, 10, 10)] + [InlineData(10, 0, 10, 0)] + [InlineData(0, 10, 0, 10)] + [InlineData(23, 5, 3, 15)] + public void ArrangeAccountsForPadding(double left, double top, double right, double bottom) + { + var viewWidth = 100d; + var viewHeight = 100d; + var padding = new Thickness(left, top, right, bottom); + + var stack = BuildPaddedStack(padding, viewWidth, viewHeight); + + var manager = new VerticalStackLayoutManager(stack); + var measuredSize = manager.Measure(double.PositiveInfinity, double.PositiveInfinity); + manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize)); + + AssertArranged(stack.Children[0], padding.Left, padding.Top, viewWidth, viewHeight); + } } } From ad8d581a25d2fbad624f8e19e275ace263d53be2 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Tue, 20 Jul 2021 16:03:57 -0600 Subject: [PATCH 2/2] Add Padding to LayoutStub --- src/Core/tests/DeviceTests/Stubs/LayoutStub.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Core/tests/DeviceTests/Stubs/LayoutStub.cs b/src/Core/tests/DeviceTests/Stubs/LayoutStub.cs index d4e62204c76a..1ebd50b179ee 100644 --- a/src/Core/tests/DeviceTests/Stubs/LayoutStub.cs +++ b/src/Core/tests/DeviceTests/Stubs/LayoutStub.cs @@ -20,5 +20,7 @@ public void Remove(IView child) { _children.Remove(child); } + + public Thickness Padding { get; set; } } }