Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Layout padding for new StackLayouts and GridLayout #1749

Merged
merged 2 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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