Skip to content

Commit

Permalink
Add Padding to ILayout; add padding tests for layout managers
Browse files Browse the repository at this point in the history
  • Loading branch information
hartez committed Jul 20, 2021
1 parent ff78dcb commit 1435343
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 108 deletions.
19 changes: 18 additions & 1 deletion src/Controls/src/Core/Layout/Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace Microsoft.Maui.Controls.Layout2
{
[ContentProperty(nameof(Children))]
public abstract class Layout : View, Maui.ILayout, IList<IView>
public abstract class Layout : View, Microsoft.Maui.ILayout, IList<IView>, IPaddingElement
{
ILayoutManager _layoutManager;
ILayoutManager LayoutManager => _layoutManager ??= CreateLayoutManager();
Expand All @@ -29,6 +29,13 @@ public abstract class Layout : View, Maui.ILayout, IList<IView>

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<IView> GetEnumerator() => _children.GetEnumerator();
Expand Down Expand Up @@ -187,5 +194,15 @@ bool ICollection<IView>.Remove(IView child)

return result;
}

void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue)
{
InvalidateMeasure();
}

Thickness IPaddingElement.PaddingDefaultValueCreator()
{
return new Thickness(0);
}
}
}
6 changes: 5 additions & 1 deletion src/Core/src/Core/ILayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace Microsoft.Maui
/// </summary>
public interface ILayout : IView, IContainer
{

/// <summary>
/// Gets the Layout Handler.
/// </summary>
Expand All @@ -25,5 +24,10 @@ public interface ILayout : IView, IContainer
/// </summary>
/// <param name="child">The child View to remove from the Layout.</param>
void Remove(IView child);

/// <summary>
/// The space between the outer edge of the ILayout's content area and its children.
/// </summary>
Thickness Padding { get; }
}
}
76 changes: 47 additions & 29 deletions src/Core/src/Layouts/GridLayoutManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,72 +48,90 @@ 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<IGridRowDefinition> _rowDefinitions;
readonly IReadOnlyList<IGridColumnDefinition> _columnDefinitions;
readonly IReadOnlyList<IView> _gridChildren;

readonly Dictionary<SpanKey, Span> _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];
_rows[0] = new Row(new ImpliedRow());
}
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];
_columns[0] = new Column(new ImpliedColumn());
}
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();

Expand All @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -355,25 +373,25 @@ 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;
}

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;
Expand Down Expand Up @@ -411,7 +429,7 @@ void ResolveStars(Definition[] defs, double availableSpace, Func<Cell, bool> 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));
}
}
}
Expand Down Expand Up @@ -466,7 +484,7 @@ void EnsureFinalMeasure()
width += _columns[n].Size;
}

_children[cell.ViewIndex].Measure(width, height);
_childrenToLayOut[cell.ViewIndex].Measure(width, height);
}
}
}
Expand Down
86 changes: 44 additions & 42 deletions src/Core/src/Layouts/HorizontalStackLayoutManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IView> 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<IView> views)
static void ArrangeLeftToRight(double height, double left, double top, double spacing, IReadOnlyList<IView> 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<IView> views)
static void ArrangeRightToLeft(double height, double left, double top, double spacing, IReadOnlyList<IView> 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;
Expand Down
Loading

0 comments on commit 1435343

Please sign in to comment.