GridLayout with absolute and auto (#513)

* Grid stuff, in progress

* Clean up after rebase; fix arrange call in layout manager

* Adding some notes

* Update ArrangeChildren method calls

* Add missing TypeConverters

* Update test values in light of new Arrange call

* Formatting fix

Co-authored-by: Rui Marinho <me@ruimarinho.net>
This commit is contained in:
E.Z. Hart 2021-03-24 03:06:54 -06:00 коммит произвёл GitHub
Родитель 05ba6f7df0
Коммит fccf7cc07f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
24 изменённых файлов: 1461 добавлений и 35 удалений

Просмотреть файл

@ -32,6 +32,9 @@ namespace Maui.Controls.Sample.Pages
var verticalStack = new VerticalStackLayout() { Spacing = 5, BackgroundColor = Color.AntiqueWhite };
var horizontalStack = new HorizontalStackLayout() { Spacing = 2, BackgroundColor = Color.CornflowerBlue };
verticalStack.Add(CreateSampleGrid());
verticalStack.Add(new Label { Text = " ", Padding = new Thickness(10) });
var label = new Label { Text = "End-aligned text", BackgroundColor = Color.Fuchsia, HorizontalTextAlignment = TextAlignment.End };
label.Margin = new Thickness(15, 10, 20, 15);
@ -47,7 +50,6 @@ namespace Maui.Controls.Sample.Pages
verticalStack.Add(new Label { Text = loremIpsum, MaxLines = 2, LineBreakMode = LineBreakMode.TailTruncation });
verticalStack.Add(new Label { Text = "This should have five times the line height!", LineHeight = 5 });
var paddingButton = new Button
{
Padding = new Thickness(40),
@ -157,6 +159,7 @@ namespace Maui.Controls.Sample.Pages
Content = verticalStack
};
}
void SetupCompatibilityLayout()
{
@ -202,5 +205,36 @@ namespace Maui.Controls.Sample.Pages
}
public IView View { get => (IView)Content; set => Content = (View)value; }
IView CreateSampleGrid()
{
var layout = new Microsoft.Maui.Controls.Layout2.GridLayout() { ColumnSpacing = 5, RowSpacing = 8 };
layout.AddRowDefinition(new RowDefinition() { Height = new GridLength(40) });
layout.AddRowDefinition(new RowDefinition() { Height = GridLength.Auto });
layout.AddColumnDefinition(new ColumnDefinition() { Width = new GridLength(100) });
layout.AddColumnDefinition(new ColumnDefinition() { Width = new GridLength(100) });
var topLeft = new Label { Text = "Top Left", BackgroundColor = Color.LightBlue };
layout.Add(topLeft);
var bottomLeft = new Label { Text = "Bottom Left", BackgroundColor = Color.Lavender };
layout.Add(bottomLeft);
layout.SetRow(bottomLeft, 1);
var topRight = new Label { Text = "Top Right", BackgroundColor = Color.Orange };
layout.Add(topRight);
layout.SetColumn(topRight, 1);
var bottomRight = new Label { Text = "Bottom Right", BackgroundColor = Color.MediumPurple };
layout.Add(bottomRight);
layout.SetRow(bottomRight, 1);
layout.SetColumn(bottomRight, 1);
layout.BackgroundColor = Color.Chartreuse;
return layout;
}
}
}

Просмотреть файл

@ -2,7 +2,7 @@ using System;
namespace Microsoft.Maui.Controls
{
public sealed class ColumnDefinition : BindableObject, IDefinition
public sealed class ColumnDefinition : BindableObject, IDefinition, IGridColumnDefinition
{
public static readonly BindableProperty WidthProperty = BindableProperty.Create("Width", typeof(GridLength), typeof(ColumnDefinition), new GridLength(1, GridUnitType.Star),
propertyChanged: (bindable, oldValue, newValue) => ((ColumnDefinition)bindable).OnSizeChanged());
@ -12,6 +12,7 @@ namespace Microsoft.Maui.Controls
MinimumWidth = -1;
}
[TypeConverter(typeof(GridLengthTypeConverter))]
public GridLength Width
{
get { return (GridLength)GetValue(WidthProperty); }

Просмотреть файл

@ -5,9 +5,10 @@ using System.ComponentModel;
using System.Linq;
using Microsoft.Maui.Controls.Internals;
namespace Microsoft.Maui.Controls
{
public partial class Grid : Layout<View>, IGridController, IElementConfiguration<Grid>
public partial class Grid : Layout<View>, IGridController, IElementConfiguration<Grid>, IGridLayout
{
public static readonly BindableProperty RowProperty = BindableProperty.CreateAttached("Row", typeof(int), typeof(Grid), default(int), validateValue: (bindable, value) => (int)value >= 0);
@ -381,5 +382,48 @@ namespace Microsoft.Maui.Controls
Parent.ColumnDefinitions.Count
);
}
int IGridLayout.GetRow(IView view)
{
if (view is BindableObject bo)
{
return GetRow(bo);
}
throw new InvalidEnumArgumentException($"{nameof(view)} must be a BindableObject");
}
int IGridLayout.GetColumn(IView view)
{
if (view is BindableObject bo)
{
return GetColumn(bo);
}
throw new InvalidEnumArgumentException($"{nameof(view)} must be a BindableObject");
}
int IGridLayout.GetRowSpan(IView view)
{
if (view is BindableObject bo)
{
return GetRowSpan(bo);
}
throw new InvalidEnumArgumentException($"{nameof(view)} must be a BindableObject");
}
int IGridLayout.GetColumnSpan(IView view)
{
if (view is BindableObject bo)
{
return GetColumnSpan(bo);
}
throw new InvalidEnumArgumentException($"{nameof(view)} must be a BindableObject");
}
IReadOnlyList<IGridRowDefinition> IGridLayout.RowDefinitions => RowDefinitions.ToList();
IReadOnlyList<IGridColumnDefinition> IGridLayout.ColumnDefinitions => ColumnDefinitions.ToList();
}
}

Просмотреть файл

@ -0,0 +1,130 @@
using System.Collections.Generic;
using Microsoft.Maui.Layouts;
// This is a temporary namespace until we rename everything and move the legacy layouts
namespace Microsoft.Maui.Controls.Layout2
{
public class GridLayout : Layout, IGridLayout
{
List<IGridRowDefinition> _rowDefinitions = new List<IGridRowDefinition>();
List<IGridColumnDefinition> _columnDefinitions = new List<IGridColumnDefinition>();
public IReadOnlyList<IGridRowDefinition> RowDefinitions => _rowDefinitions;
public IReadOnlyList<IGridColumnDefinition> ColumnDefinitions => _columnDefinitions;
public double RowSpacing { get; set; }
public double ColumnSpacing { get; set; }
Dictionary<IView, GridInfo> _viewInfo = new Dictionary<IView, GridInfo>();
// TODO ezhart This needs to override Remove and clean up any row/column/span info for the removed child
public int GetColumn(IView view)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
return gridInfo.Col;
}
return 0;
}
public int GetColumnSpan(IView view)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
return gridInfo.ColSpan;
}
return 1;
}
public int GetRow(IView view)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
return gridInfo.Row;
}
return 0;
}
public int GetRowSpan(IView view)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
return gridInfo.RowSpan;
}
return 1;
}
protected override ILayoutManager CreateLayoutManager() => new GridLayoutManager(this);
public void AddRowDefinition(IGridRowDefinition gridRowDefinition)
{
_rowDefinitions.Add(gridRowDefinition);
}
public void AddColumnDefinition(IGridColumnDefinition gridColumnDefinition)
{
_columnDefinitions.Add(gridColumnDefinition);
}
public void SetRow(IView view, int row)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
gridInfo.Row = row;
}
else
{
_viewInfo[view] = new GridInfo { Row = row };
}
}
public void SetRowSpan(IView view, int span)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
gridInfo.RowSpan = span;
}
else
{
_viewInfo[view] = new GridInfo { RowSpan = span };
}
}
public void SetColumn(IView view, int col)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
gridInfo.Col = col;
}
else
{
_viewInfo[view] = new GridInfo { Col = col };
}
}
public void SetColumnSpan(IView view, int span)
{
if (_viewInfo.TryGetValue(view, out GridInfo gridInfo))
{
gridInfo.ColSpan = span;
}
else
{
_viewInfo[view] = new GridInfo { ColSpan = span };
}
}
class GridInfo
{
public int Row { get; set; }
public int Col { get; set; }
public int RowSpan { get; set; } = 1;
public int ColSpan { get; set; } = 1;
}
}
}

Просмотреть файл

@ -2,8 +2,6 @@ using System.Collections;
using System.Collections.Generic;
using Microsoft.Maui.Layouts;
// This is a temporary namespace until we rename everything and move the legacy layouts
namespace Microsoft.Maui.Controls.Layout2
{
@ -61,7 +59,7 @@ namespace Microsoft.Maui.Controls.Layout2
Arrange(bounds);
LayoutManager.Arrange(Frame);
LayoutManager.ArrangeChildren(Frame);
IsArrangeValid = true;
Handler?.SetFrame(Frame);
}

Просмотреть файл

@ -1,5 +1,4 @@
using System.Linq;
using System.Linq;
// This is a temporary namespace until we rename everything and move the legacy layouts
namespace Microsoft.Maui.Controls.Layout2

Просмотреть файл

@ -1,4 +1,3 @@
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Layouts;

Просмотреть файл

@ -2,7 +2,7 @@ using System;
namespace Microsoft.Maui.Controls
{
public sealed class RowDefinition : BindableObject, IDefinition
public sealed class RowDefinition : BindableObject, IDefinition, IGridRowDefinition
{
public static readonly BindableProperty HeightProperty = BindableProperty.Create("Height", typeof(GridLength), typeof(RowDefinition), new GridLength(1, GridUnitType.Star),
propertyChanged: (bindable, oldValue, newValue) => ((RowDefinition)bindable).OnSizeChanged());
@ -12,6 +12,7 @@ namespace Microsoft.Maui.Controls
MinimumHeight = -1;
}
[TypeConverter(typeof(GridLengthTypeConverter))]
public GridLength Height
{
get { return (GridLength)GetValue(HeightProperty); }

Просмотреть файл

@ -0,0 +1,7 @@
namespace Microsoft.Maui
{
public interface IGridColumnDefinition
{
GridLength Width { get; }
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Microsoft.Maui
{
public interface IGridLayout : ILayout
{
IReadOnlyList<IGridRowDefinition> RowDefinitions { get; }
IReadOnlyList<IGridColumnDefinition> ColumnDefinitions { get; }
double RowSpacing { get; }
double ColumnSpacing { get; }
int GetRow(IView view);
int GetRowSpan(IView view);
int GetColumn(IView view);
int GetColumnSpan(IView view);
}
}

Просмотреть файл

@ -0,0 +1,7 @@
namespace Microsoft.Maui
{
public interface IGridRowDefinition
{
GridLength Height { get; }
}
}

Просмотреть файл

@ -1,3 +1,6 @@
using System.Collections.Generic;
namespace Microsoft.Maui
{
/// <summary>

Просмотреть файл

@ -0,0 +1,464 @@
using System;
using System.Collections.Generic;
namespace Microsoft.Maui.Layouts
{
public class GridLayoutManager : LayoutManager
{
public GridLayoutManager(IGridLayout layout) : base(layout)
{
Grid = layout;
}
public IGridLayout Grid { get; }
public override Size Measure(double widthConstraint, double heightConstraint)
{
var structure = new GridStructure(Grid, widthConstraint, heightConstraint);
return new Size(structure.GridWidth(), structure.GridHeight());
}
public override void ArrangeChildren(Rectangle childBounds)
{
var structure = new GridStructure(Grid, childBounds.Width, childBounds.Height);
foreach (var view in Grid.Children)
{
var cell = structure.ComputeFrameFor(view);
view.Arrange(cell);
}
}
class GridStructure
{
readonly IGridLayout _grid;
readonly double _gridWidthConstraint;
readonly double _gridHeightConstraint;
Row[] _rows { get; }
Column[] _columns { get; }
Cell[] _cells { get; }
readonly Dictionary<SpanKey, Span> _spans = new Dictionary<SpanKey, Span>();
public GridStructure(IGridLayout grid, double widthConstraint, double heightConstraint)
{
_grid = grid;
_gridWidthConstraint = widthConstraint;
_gridHeightConstraint = heightConstraint;
_rows = new Row[_grid.RowDefinitions.Count];
for (int n = 0; n < _grid.RowDefinitions.Count; n++)
{
_rows[n] = new Row(_grid.RowDefinitions[n]);
}
_columns = new Column[_grid.ColumnDefinitions.Count];
for (int n = 0; n < _grid.ColumnDefinitions.Count; n++)
{
_columns[n] = new Column(_grid.ColumnDefinitions[n]);
}
_cells = new Cell[_grid.Children.Count];
InitializeCells();
MeasureCells();
}
void InitializeCells()
{
for (int n = 0; n < _grid.Children.Count; n++)
{
var view = _grid.Children[n];
var column = _grid.GetColumn(view);
var columnSpan = _grid.GetColumnSpan(view);
var columnGridLengthType = GridLengthType.None;
for (int columnIndex = column; columnIndex < column + columnSpan; columnIndex++)
{
columnGridLengthType |= ToGridLengthType(_columns[columnIndex].ColumnDefinition.Width.GridUnitType);
}
var row = _grid.GetRow(view);
var rowSpan = _grid.GetRowSpan(view);
var rowGridLengthType = GridLengthType.None;
for (int rowIndex = row; rowIndex < row + rowSpan; rowIndex++)
{
rowGridLengthType |= ToGridLengthType(_rows[rowIndex].RowDefinition.Height.GridUnitType);
}
_cells[n] = new Cell(n, row, column, rowSpan, columnSpan, columnGridLengthType, rowGridLengthType);
}
}
public Rectangle ComputeFrameFor(IView view)
{
var firstColumn = _grid.GetColumn(view);
var lastColumn = firstColumn + _grid.GetColumnSpan(view);
var firstRow = _grid.GetRow(view);
var lastRow = firstRow + _grid.GetRowSpan(view);
double top = TopEdgeOfRow(firstRow);
double left = LeftEdgeOfColumn(firstColumn);
double width = 0;
for (int n = firstColumn; n < lastColumn; n++)
{
width += _columns[n].Size;
}
double height = 0;
for (int n = firstRow; n < lastRow; n++)
{
height += _rows[n].Size;
}
// TODO ezhart this isn't correctly accounting for row spacing when spanning multiple rows
// (and column spacing is probably wrong, too)
return new Rectangle(left, top, width, height);
}
public double GridHeight()
{
return SumDefinitions(_rows, _grid.RowSpacing);
}
public double GridWidth()
{
return SumDefinitions(_columns, _grid.ColumnSpacing);
}
double SumDefinitions(Definition[] definitions, double spacing)
{
double sum = 0;
for (int n = 0; n < definitions.Length; n++)
{
var current = definitions[n].Size;
if (current <= 0)
{
continue;
}
sum += current;
if (n > 0)
{
sum += spacing;
}
}
return sum;
}
void MeasureCells()
{
for (int n = 0; n < _cells.Length; n++)
{
var cell = _cells[n];
if (cell.ColumnGridLengthType == GridLengthType.Absolute
&& cell.RowGridLengthType == GridLengthType.Absolute)
{
continue;
}
var availableWidth = _gridWidthConstraint - GridWidth();
var availableHeight = _gridHeightConstraint - GridHeight();
var measure = _grid.Children[cell.ViewIndex].Measure(availableWidth, availableHeight);
if (cell.IsColumnSpanAuto)
{
if (cell.ColumnSpan == 1)
{
_columns[cell.Column].Update(measure.Width);
}
else
{
var span = new Span(cell.Column, cell.ColumnSpan, true, measure.Width);
TrackSpan(span);
}
}
if (cell.IsRowSpanAuto)
{
if (cell.RowSpan == 1)
{
_rows[cell.Row].Update(measure.Height);
}
else
{
var span = new Span(cell.Row, cell.RowSpan, false, measure.Height);
TrackSpan(span);
}
}
}
ResolveSpans();
}
void TrackSpan(Span span)
{
if (_spans.TryGetValue(span.Key, out Span? otherSpan))
{
// This span may replace an equivalent but smaller span
if (span.Requested > otherSpan.Requested)
{
_spans[span.Key] = span;
}
}
else
{
_spans[span.Key] = span;
}
}
void ResolveSpans()
{
foreach (var span in _spans.Values)
{
if (span.IsColumn)
{
ResolveSpan(_columns, span.Start, span.Length, _grid.ColumnSpacing, span.Requested);
}
else
{
ResolveSpan(_rows, span.Start, span.Length, _grid.RowSpacing, span.Requested);
}
}
}
void ResolveSpan(Definition[] definitions, int start, int length, double spacing, double requestedSize)
{
double currentSize = 0;
var end = start + length;
// Determine how large the spanned area currently is
for (int n = start; n < end; n++)
{
currentSize += definitions[n].Size;
if (n > start)
{
currentSize += spacing;
}
}
if (requestedSize <= currentSize)
{
// If our request fits in the current size, we're good
return;
}
// Figure out how much more space we need in this span
double required = requestedSize - currentSize;
// And how many parts of the span to distribute that space over
int autoCount = 0;
for (int n = start; n < end; n++)
{
if (definitions[n].IsAuto)
{
autoCount += 1;
}
}
double distribution = required / autoCount;
// And distribute that over the rows/columns in the span
for (int n = start; n < end; n++)
{
if (definitions[n].IsAuto)
{
definitions[n].Size += distribution;
}
}
}
double LeftEdgeOfColumn(int column)
{
double left = 0;
for (int n = 0; n < column; n++)
{
left += _columns[n].Size;
left += _grid.ColumnSpacing;
}
return left;
}
double TopEdgeOfRow(int row)
{
double top = 0;
for (int n = 0; n < row; n++)
{
top += _rows[n].Size;
top += _grid.RowSpacing;
}
return top;
}
}
class Span
{
public int Start { get; }
public int Length { get; }
public bool IsColumn { get; }
public double Requested { get; }
public SpanKey Key { get; }
public Span(int start, int length, bool isColumn, double value)
{
Start = start;
Length = length;
IsColumn = isColumn;
Requested = value;
Key = new SpanKey(Start, Length, IsColumn);
}
}
class SpanKey
{
public SpanKey(int start, int length, bool isColumn)
{
Start = start;
Length = length;
IsColumn = isColumn;
}
public int Start { get; }
public int Length { get; }
public bool IsColumn { get; }
public override bool Equals(object? obj)
{
return obj is SpanKey key &&
Start == key.Start &&
Length == key.Length &&
IsColumn == key.IsColumn;
}
public override int GetHashCode()
{
return Start.GetHashCode() ^ Length.GetHashCode() ^ IsColumn.GetHashCode();
}
}
class Cell
{
public int ViewIndex { get; }
public int Row { get; }
public int Column { get; }
public int RowSpan { get; }
public int ColumnSpan { get; }
public GridLengthType ColumnGridLengthType { get; }
public GridLengthType RowGridLengthType { get; }
public Cell(int viewIndex, int row, int column, int rowSpan, int columnSpan,
GridLengthType columnGridLengthType, GridLengthType rowGridLengthType)
{
ViewIndex = viewIndex;
Row = row;
Column = column;
RowSpan = rowSpan;
ColumnSpan = columnSpan;
ColumnGridLengthType = columnGridLengthType;
RowGridLengthType = rowGridLengthType;
}
public bool IsColumnSpanAuto => HasFlag(ColumnGridLengthType, GridLengthType.Auto);
public bool IsRowSpanAuto => HasFlag(RowGridLengthType, GridLengthType.Auto);
bool HasFlag(GridLengthType a, GridLengthType b)
{
// Avoiding Enum.HasFlag here for performance reasons; we don't need the type check
return (a & b) == b;
}
}
[Flags]
enum GridLengthType
{
None = 0,
Absolute = 1,
Auto = 2,
Star = 4
}
static GridLengthType ToGridLengthType(GridUnitType gridUnitType)
{
return gridUnitType switch
{
GridUnitType.Absolute => GridLengthType.Absolute,
GridUnitType.Star => GridLengthType.Star,
GridUnitType.Auto => GridLengthType.Auto,
_ => GridLengthType.None,
};
}
abstract class Definition
{
public double Size { get; set; }
public void Update(double size)
{
if (size > Size)
{
Size = size;
}
}
public abstract bool IsAuto { get; }
}
class Column : Definition
{
public IGridColumnDefinition ColumnDefinition { get; set; }
public override bool IsAuto => ColumnDefinition.Width.IsAuto;
public Column(IGridColumnDefinition columnDefinition)
{
ColumnDefinition = columnDefinition;
if (columnDefinition.Width.IsAbsolute)
{
Size = columnDefinition.Width.Value;
}
}
}
class Row : Definition
{
public IGridRowDefinition RowDefinition { get; set; }
public override bool IsAuto => RowDefinition.Height.IsAuto;
public Row(IGridRowDefinition rowDefinition)
{
RowDefinition = rowDefinition;
if (rowDefinition.Height.IsAbsolute)
{
Size = rowDefinition.Height.Value;
}
}
}
}
}

Просмотреть файл

@ -20,7 +20,7 @@ namespace Microsoft.Maui.Layouts
return new Size(finalWidth, measure.Height);
}
public override void Arrange(Rectangle bounds)
public override void ArrangeChildren(Rectangle childBounds)
{
if (Stack.FlowDirection == FlowDirection.LeftToRight)
{

Просмотреть файл

@ -5,6 +5,6 @@ namespace Microsoft.Maui.Layouts
public interface ILayoutManager
{
Size Measure(double widthConstraint, double heightConstraint);
void Arrange(Rectangle bounds);
void ArrangeChildren(Rectangle childBounds);
}
}

Просмотреть файл

@ -13,7 +13,7 @@ namespace Microsoft.Maui.Layouts
public ILayout Layout { get; }
public abstract Size Measure(double widthConstraint, double heightConstraint);
public abstract void Arrange(Rectangle bounds);
public abstract void ArrangeChildren(Rectangle childBounds);
public static double ResolveConstraints(double externalConstraint, double desiredLength)
{

Просмотреть файл

@ -21,7 +21,7 @@ namespace Microsoft.Maui.Layouts
return new Size(measure.Width, finalHeight);
}
public override void Arrange(Rectangle bounds) => Arrange(Stack.Spacing, Stack.Children);
public override void ArrangeChildren(Rectangle childBounds) => Arrange(Stack.Spacing, Stack.Children);
static Size Measure(double widthConstraint, int spacing, IReadOnlyList<IView> views)
{
@ -52,6 +52,5 @@ namespace Microsoft.Maui.Layouts
stackHeight += destination.Height + spacing;
}
}
}
}

Просмотреть файл

@ -1,9 +1,8 @@
using System;
using System.Diagnostics;
namespace Microsoft.Maui.Controls
namespace Microsoft.Maui
{
[TypeConverter(typeof(GridLengthTypeConverter))]
[DebuggerDisplay("{Value}.{GridUnitType}")]
public struct GridLength
{
@ -51,7 +50,7 @@ namespace Microsoft.Maui.Controls
GridUnitType = type;
}
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj != null && obj is GridLength && Equals((GridLength)obj);
}

Просмотреть файл

@ -1,4 +1,4 @@
namespace Microsoft.Maui.Controls
namespace Microsoft.Maui
{
public enum GridUnitType
{

Просмотреть файл

@ -0,0 +1,688 @@
using System.Collections.Generic;
using NSubstitute;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Layouts;
using static Microsoft.Maui.UnitTests.Layouts.LayoutTestHelpers;
using Xunit;
namespace Microsoft.Maui.UnitTests.Layouts
{
[Category(TestCategory.Layout)]
public class GridLayoutManagerTests
{
const string GridSpacing = "GridSpacing";
const string GridAutoSizing = "GridAutoSizing";
const string GridStarSizing = "GridStarSizing";
const string GridAbsoluteSizing = "GridAbsoluteSizing";
const string GridSpan = "GridSpan";
IGridLayout CreateGridLayout(int rowSpacing = 0, int colSpacing = 0,
string rows = null, string columns = null)
{
IEnumerable<IGridRowDefinition> rowDefs = null;
IEnumerable<IGridColumnDefinition> colDefs = null;
if (rows != null)
{
rowDefs = CreateTestRows(rows.Split(","));
}
if (columns != null)
{
colDefs = CreateTestColumns(columns.Split(","));
}
var grid = Substitute.For<IGridLayout>();
grid.RowSpacing.Returns(rowSpacing);
grid.ColumnSpacing.Returns(colSpacing);
SubRowDefs(grid, rowDefs);
SubColDefs(grid, colDefs);
return grid;
}
void SubRowDefs(IGridLayout grid, IEnumerable<IGridRowDefinition> rows = null)
{
if (rows == null)
{
var rowDef = Substitute.For<IGridRowDefinition>();
rowDef.Height.Returns(GridLength.Auto);
var rowDefs = new List<IGridRowDefinition>
{
rowDef
};
grid.RowDefinitions.Returns(rowDefs);
}
else
{
grid.RowDefinitions.Returns(rows);
}
}
void SubColDefs(IGridLayout grid, IEnumerable<IGridColumnDefinition> cols = null)
{
if (cols == null)
{
var colDefs = CreateTestColumns("auto");
grid.ColumnDefinitions.Returns(colDefs);
}
else
{
grid.ColumnDefinitions.Returns(cols);
}
}
List<IGridColumnDefinition> CreateTestColumns(params string[] columnWidths)
{
var converter = new GridLengthTypeConverter();
var colDefs = new List<IGridColumnDefinition>();
foreach (var width in columnWidths)
{
var gridLength = converter.ConvertFromInvariantString(width);
var colDef = Substitute.For<IGridColumnDefinition>();
colDef.Width.Returns(gridLength);
colDefs.Add(colDef);
}
return colDefs;
}
List<IGridRowDefinition> CreateTestRows(params string[] rowHeights)
{
var converter = new GridLengthTypeConverter();
var rowDefs = new List<IGridRowDefinition>();
foreach (var height in rowHeights)
{
var gridLength = converter.ConvertFromInvariantString(height);
var rowDef = Substitute.For<IGridRowDefinition>();
rowDef.Height.Returns(gridLength);
rowDefs.Add(rowDef);
}
return rowDefs;
}
void SetLocation(IGridLayout grid, IView view, int row = 0, int col = 0, int rowSpan = 1, int colSpan = 1)
{
grid.GetRow(view).Returns(row);
grid.GetRowSpan(view).Returns(rowSpan);
grid.GetColumn(view).Returns(col);
grid.GetColumnSpan(view).Returns(colSpan);
}
Size MeasureAndArrange(IGridLayout grid, double widthConstraint = double.PositiveInfinity, double heightConstraint = double.PositiveInfinity)
{
var manager = new GridLayoutManager(grid);
var measuredSize = manager.Measure(widthConstraint, heightConstraint);
manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize));
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()
{
// A one-row, one-column grid
var grid = CreateGridLayout();
// A 100x100 IView
var view = CreateTestView(new Size(100, 100));
// Set up the grid to have a single child
AddChildren(grid, view);
// Set up the row/column values and spans
SetLocation(grid, view);
MeasureAndArrange(grid, double.PositiveInfinity, double.PositiveInfinity);
// We expect that the only child of the grid will be given its full size
AssertArranged(view, 0, 0, 100, 100);
}
[Category(GridAbsoluteSizing)]
[Fact]
public void TwoAbsoluteColumnsOneAbsoluteRow()
{
var grid = CreateGridLayout(columns: "100, 100", rows: "10");
var viewSize = new Size(10, 10);
var view0 = CreateTestView(viewSize);
var view1 = CreateTestView(viewSize);
AddChildren(grid, view0, view1);
SetLocation(grid, view0);
SetLocation(grid, view1, col: 1);
// Assuming no constraints on space
MeasureAndArrange(grid, double.PositiveInfinity, double.NegativeInfinity);
// Column width is 100, viewSize is less than that, so it should be able to layout out at full size
AssertArranged(view0, 0, 0, 100, 10);
// Since the first column is 100 wide, we expect the view in the second column to start at x = 100
AssertArranged(view1, 100, 0, 100, 10);
}
[Category(GridAbsoluteSizing)]
[Fact]
public void TwoAbsoluteRowsAndColumns()
{
var grid = CreateGridLayout(columns: "100, 100", rows: "10, 30");
var viewSize = new Size(10, 10);
var view0 = CreateTestView(viewSize);
var view1 = CreateTestView(viewSize);
var view2 = CreateTestView(viewSize);
var view3 = CreateTestView(viewSize);
AddChildren(grid, view0, view1, view2, view3);
SetLocation(grid, view0);
SetLocation(grid, view1, col: 1);
SetLocation(grid, view2, row: 1);
SetLocation(grid, view3, row: 1, col: 1);
// Assuming no constraints on space
MeasureAndArrange(grid, double.PositiveInfinity, double.NegativeInfinity);
AssertArranged(view0, 0, 0, 100, 10);
// Since the first column is 100 wide, we expect the view in the second column to start at x = 100
AssertArranged(view1, 100, 0, 100, 10);
// First column, second row, so y should be 10
AssertArranged(view2, 0, 10, 100, 30);
// Second column, second row, so 100, 10
AssertArranged(view3, 100, 10, 100, 30);
}
[Category(GridAbsoluteSizing), Category(GridAutoSizing)]
[Fact]
public void TwoAbsoluteColumnsOneAutoRow()
{
var grid = CreateGridLayout(columns: "100, 100");
var viewSize = new Size(10, 10);
var view0 = CreateTestView(viewSize);
var view1 = CreateTestView(viewSize);
AddChildren(grid, view0, view1);
SetLocation(grid, view0);
SetLocation(grid, view1, col: 1);
// Assuming no constraints on space
MeasureAndArrange(grid, double.PositiveInfinity, double.NegativeInfinity);
// Column width is 100, viewSize is less, so it should be able to layout at full size
AssertArranged(view0, 0, 0, 100, viewSize.Height);
// Since the first column is 100 wide, we expect the view in the second column to start at x = 100
AssertArranged(view1, 100, 0, 100, viewSize.Height);
}
[Category(GridAbsoluteSizing), Category(GridAutoSizing)]
[Fact]
public void TwoAbsoluteRowsOneAutoColumn()
{
var grid = CreateGridLayout(rows: "100, 100");
var viewSize = new Size(10, 10);
var view0 = CreateTestView(viewSize);
var view1 = CreateTestView(viewSize);
AddChildren(grid, view0, view1);
SetLocation(grid, view0);
SetLocation(grid, view1, row: 1);
// Assuming no constraints on space
MeasureAndArrange(grid, double.PositiveInfinity, double.NegativeInfinity);
// Row height is 100, so full view should fit
AssertArranged(view0, 0, 0, viewSize.Width, 100);
// Since the first row is 100 tall, we expect the view in the second row to start at y = 100
AssertArranged(view1, 0, 100, viewSize.Width, 100);
}
[Category(GridSpacing)]
[Fact(DisplayName = "Row spacing shouldn't affect a single-row grid")]
public void SingleRowIgnoresRowSpacing()
{
var grid = CreateGridLayout(rowSpacing: 10);
var view = CreateTestView(new Size(100, 100));
AddChildren(grid, view);
SetLocation(grid, view);
MeasureAndArrange(grid, double.PositiveInfinity, double.PositiveInfinity);
AssertArranged(view, 0, 0, 100, 100);
}
[Category(GridSpacing)]
[Fact(DisplayName = "Two rows should include the row spacing once")]
public void TwoRowsWithSpacing()
{
var grid = CreateGridLayout(rows: "100, 100", rowSpacing: 10);
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view1);
SetLocation(grid, view0);
SetLocation(grid, view1, row: 1);
MeasureAndArrange(grid, double.PositiveInfinity, double.PositiveInfinity);
AssertArranged(view0, 0, 0, 100, 100);
// With column width 100 and spacing of 10, we expect the second column to start at 110
AssertArranged(view1, 0, 110, 100, 100);
}
[Category(GridSpacing)]
[Fact(DisplayName = "Measure should include row spacing")]
public void MeasureTwoRowsWithSpacing()
{
var grid = CreateGridLayout(rows: "100, 100", rowSpacing: 10);
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view1);
SetLocation(grid, view0);
SetLocation(grid, view1, row: 1);
var manager = new GridLayoutManager(grid);
var measure = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
Assert.Equal(100 + 100 + 10, measure.Height);
}
[Category(GridAutoSizing)]
[Fact(DisplayName = "Auto rows without content have height zero")]
public void EmptyAutoRowsHaveNoHeight()
{
var grid = CreateGridLayout(rows: "100, auto, 100");
var view0 = CreateTestView(new Size(100, 100));
var view2 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view2);
SetLocation(grid, view0);
SetLocation(grid, view2, row: 2);
var manager = new GridLayoutManager(grid);
var measure = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
manager.ArrangeChildren(new Rectangle(0, 0, measure.Width, measure.Height));
// Because the auto row has no content, we expect it to have height zero
Assert.Equal(100 + 100, measure.Height);
// Verify the offset for the third row
AssertArranged(view2, 0, 100, 100, 100);
}
[Category(GridSpacing)]
[Fact(DisplayName = "Empty rows should not incur additional row spacing")]
public void RowSpacingForEmptyRows()
{
var grid = CreateGridLayout(rows: "100, auto, 100", rowSpacing: 10);
var view0 = CreateTestView(new Size(100, 100));
var view2 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view2);
SetLocation(grid, view0);
SetLocation(grid, view2, row: 2);
var manager = new GridLayoutManager(grid);
var measure = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
// Because the auto row has no content, we expect it to have height zero
// and we expect that it won't add more row spacing
Assert.Equal(100 + 100 + 10, measure.Height);
}
[Fact(DisplayName = "Column spacing shouldn't affect a single-column grid")]
public void SingleColumnIgnoresColumnSpacing()
{
var grid = CreateGridLayout(colSpacing: 10);
var view = CreateTestView(new Size(100, 100));
AddChildren(grid, view);
SetLocation(grid, view);
MeasureAndArrange(grid, double.PositiveInfinity, double.PositiveInfinity);
AssertArranged(view, 0, 0, 100, 100);
}
[Fact(DisplayName = "Two columns should include the column spacing once")]
public void TwoColumnsWithSpacing()
{
var grid = CreateGridLayout(columns: "100, 100", colSpacing: 10);
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view1);
SetLocation(grid, view0);
SetLocation(grid, view1, col: 1);
MeasureAndArrange(grid, double.PositiveInfinity, double.PositiveInfinity);
AssertArranged(view0, 0, 0, 100, 100);
// With column width 100 and spacing of 10, we expect the second column to start at 110
AssertArranged(view1, 110, 0, 100, 100);
}
[Fact(DisplayName = "Measure should include column spacing")]
public void MeasureTwoColumnsWithSpacing()
{
var grid = CreateGridLayout(columns: "100, 100", colSpacing: 10);
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view1);
SetLocation(grid, view0);
SetLocation(grid, view1, col: 1);
var manager = new GridLayoutManager(grid);
var measure = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
Assert.Equal(100 + 100 + 10, measure.Width);
}
[Category(GridAutoSizing)]
[Fact(DisplayName = "Auto columns without content have width zero")]
public void EmptyAutoColumnsHaveNoWidth()
{
var grid = CreateGridLayout(columns: "100, auto, 100");
var view0 = CreateTestView(new Size(100, 100));
var view2 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view2);
SetLocation(grid, view0);
SetLocation(grid, view2, col: 2);
var manager = new GridLayoutManager(grid);
var measure = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
manager.ArrangeChildren(new Rectangle(0, 0, measure.Width, measure.Height));
// Because the auto column has no content, we expect it to have width zero
Assert.Equal(100 + 100, measure.Width);
// Verify the offset for the third column
AssertArranged(view2, 100, 0, 100, 100);
}
[Category(GridSpacing)]
[Fact(DisplayName = "Empty columns should not incur additional column spacing")]
public void ColumnSpacingForEmptyColumns()
{
var grid = CreateGridLayout(columns: "100, auto, 100", colSpacing: 10);
var view0 = CreateTestView(new Size(100, 100));
var view2 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0, view2);
SetLocation(grid, view0);
SetLocation(grid, view2, col: 2);
var manager = new GridLayoutManager(grid);
var measure = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
// Because the auto column has no content, we expect it to have height zero
// and we expect that it won't add more row spacing
Assert.Equal(100 + 100 + 10, measure.Width);
}
[Category(GridSpan)]
[Fact(DisplayName = "Simple row spanning")]
public void ViewSpansRows()
{
var grid = CreateGridLayout(rows: "auto, auto");
var view0 = CreateTestView(new Size(100, 100));
AddChildren(grid, view0);
SetLocation(grid, view0, rowSpan: 2);
var measuredSize = MeasureAndArrange(grid);
AssertArranged(view0, 0, 0, 100, 100);
Assert.Equal(100, measuredSize.Width);
// We expect the rows to each get half the view height
Assert.Equal(100, measuredSize.Height);
}
[Category(GridSpan)]
[Fact(DisplayName = "Simple row spanning with multiple views")]
public void ViewSpansRowsWhenOtherViewsPresent()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, auto");
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(50, 50));
AddChildren(grid, view0, view1);
SetLocation(grid, view0, rowSpan: 2);
SetLocation(grid, view1, row: 1, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(100 + 50, measuredSize.Width);
Assert.Equal(100, measuredSize.Height);
AssertArranged(view0, 0, 0, 100, 100);
AssertArranged(view1, 100, 25, 50, 75);
}
[Category(GridSpan)]
[Category(GridSpacing)]
[Fact(DisplayName = "Row spanning with row spacing")]
public void RowSpanningShouldAccountForSpacing()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, auto", rowSpacing: 5);
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(50, 50));
var view2 = CreateTestView(new Size(50, 50));
AddChildren(grid, view0, view1, view2);
SetLocation(grid, view0, rowSpan: 2);
SetLocation(grid, view1, row: 0, col: 1);
SetLocation(grid, view2, row: 1, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(150, measuredSize.Width);
Assert.Equal(50 + 50 + 5, measuredSize.Height);
AssertArranged(view0, 0, 0, 100, 100);
AssertArranged(view1, 100, 0, 50, 50);
AssertArranged(view2, 100, 55, 50, 50);
}
[Category(GridSpan)]
[Fact(DisplayName = "Simple column spanning with multiple views")]
public void ViewSpansColumnsWhenOtherViewsPresent()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, auto");
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(50, 50));
AddChildren(grid, view0, view1);
SetLocation(grid, view0, colSpan: 2);
SetLocation(grid, view1, row: 1, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(100, measuredSize.Width);
Assert.Equal(100 + 50, measuredSize.Height);
AssertArranged(view0, 0, 0, 100, 100);
AssertArranged(view1, 25, 100, 75, 50);
}
[Category(GridSpan)]
[Category(GridSpacing)]
[Fact(DisplayName = "Column spanning with column spacing")]
public void ColumnSpanningShouldAccountForSpacing()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, auto", colSpacing: 5);
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(50, 50));
var view2 = CreateTestView(new Size(50, 50));
AddChildren(grid, view0, view1, view2);
SetLocation(grid, view0, colSpan: 2);
SetLocation(grid, view1, row: 1, col: 0);
SetLocation(grid, view2, row: 1, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(50 + 50 + 5, measuredSize.Width);
Assert.Equal(100 + 50, measuredSize.Height);
AssertArranged(view0, 0, 0, 100, 100);
AssertArranged(view1, 0, 100, 50, 50);
AssertArranged(view2, 55, 100, 50, 50);
}
[Category(GridSpan)]
[Fact(DisplayName = "Row-spanning views smaller than the views confined to the row should not affect row size")]
public void SmallerSpanningViewsShouldNotAffectRowSize()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, auto");
var view0 = CreateTestView(new Size(30, 30));
var view1 = CreateTestView(new Size(50, 50));
AddChildren(grid, view0, view1);
SetLocation(grid, view0, rowSpan: 2);
SetLocation(grid, view1, row: 0, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(30 + 50, measuredSize.Width);
Assert.Equal(50, measuredSize.Height);
AssertArranged(view0, 0, 0, 30, 50);
AssertArranged(view1, 30, 0, 50, 50);
}
[Category(GridSpan)]
[Fact(DisplayName = "Column-spanning views smaller than the views confined to the column should not affect column size")]
public void SmallerSpanningViewsShouldNotAffectColumnSize()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, auto");
var view0 = CreateTestView(new Size(30, 30));
var view1 = CreateTestView(new Size(50, 50));
AddChildren(grid, view0, view1);
SetLocation(grid, view0, colSpan: 2);
SetLocation(grid, view1, row: 1, col: 0);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(50, measuredSize.Width);
Assert.Equal(30 + 50, measuredSize.Height);
AssertArranged(view0, 0, 0, 50, 30);
AssertArranged(view1, 0, 30, 50, 50);
}
[Category(GridAbsoluteSizing)]
[Fact(DisplayName = "Empty absolute rows/columns still affect Grid size")]
public void EmptyAbsoluteRowsAndColumnsAffectSize()
{
var grid = CreateGridLayout(rows: "10, 40", columns: "15, 85");
var view0 = CreateTestView(new Size(30, 30));
AddChildren(grid, view0);
SetLocation(grid, view0, row: 1, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(15 + 85, measuredSize.Width);
Assert.Equal(10 + 40, measuredSize.Height);
AssertArranged(view0, 15, 10, 85, 40);
}
[Category(GridSpan)]
[Fact(DisplayName = "Row and column spans should be able to mix")]
public void MixedRowAndColumnSpans()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, auto");
var view0 = CreateTestView(new Size(60, 30));
var view1 = CreateTestView(new Size(30, 60));
AddChildren(grid, view0, view1);
SetLocation(grid, view0, row: 0, col: 0, colSpan: 2);
SetLocation(grid, view1, row: 0, col: 1, rowSpan: 2);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(60, measuredSize.Width);
Assert.Equal(60, measuredSize.Height);
AssertArranged(view0, 0, 0, 60, 45);
AssertArranged(view1, 15, 0, 45, 60);
}
[Category(GridSpan)]
[Fact(DisplayName = "Row span including absolute row should not modify absolute size")]
public void RowSpanShouldNotModifyAbsoluteRowSize()
{
var grid = CreateGridLayout(rows: "auto, 20", columns: "auto, auto");
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(50, 10));
AddChildren(grid, view0, view1);
SetLocation(grid, view0, rowSpan: 2);
SetLocation(grid, view1, row: 1, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(100 + 50, measuredSize.Width);
Assert.Equal(100, measuredSize.Height);
AssertArranged(view0, 0, 0, 100, 100);
// The item in the second row starts at y = 80 because the auto row above had to distribute
// all the extra space into row 0; row 1 is absolute, so no tinkering with it to make stuff fit
AssertArranged(view1, 100, 80, 50, 20);
}
[Category(GridSpan)]
[Fact(DisplayName = "Column span including absolute column should not modify absolute size")]
public void ColumnSpanShouldNotModifyAbsoluteColumnSize()
{
var grid = CreateGridLayout(rows: "auto, auto", columns: "auto, 20");
var view0 = CreateTestView(new Size(100, 100));
var view1 = CreateTestView(new Size(50, 10));
AddChildren(grid, view0, view1);
SetLocation(grid, view0, colSpan: 2);
SetLocation(grid, view1, row: 1, col: 1);
var measuredSize = MeasureAndArrange(grid);
Assert.Equal(100, measuredSize.Width);
Assert.Equal(100 + 10, measuredSize.Height);
AssertArranged(view0, 0, 0, 100, 100);
// The item in the second row starts at x = 80 because the auto column before it had to distribute
// all the extra space into column 0; column 1 is absolute, so no tinkering with it to make stuff fit
AssertArranged(view1, 80, 100, 20, 10);
}
}
}

Просмотреть файл

@ -37,7 +37,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
var manager = new HorizontalStackLayoutManager(stack);
var measuredSize = manager.Measure(double.PositiveInfinity, 100);
manager.Arrange(new Rectangle(Point.Zero, measuredSize));
manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize));
var expectedRectangle = new Rectangle(0, 0, 100, 100);
stack.Children[0].Received().Arrange(Arg.Is(expectedRectangle));
@ -53,7 +53,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
var manager = new HorizontalStackLayoutManager(stack);
var measuredSize = manager.Measure(double.PositiveInfinity, 100);
manager.Arrange(new Rectangle(Point.Zero, measuredSize));
manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize));
var expectedRectangle0 = new Rectangle(0, 0, 100, 100);
stack.Children[0].Received().Arrange(Arg.Is(expectedRectangle0));
@ -70,7 +70,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
{
var stack = CreateTestLayout();
var view = CreateTestView(new Size(viewWidth, 100));
var view = LayoutTestHelpers.CreateTestView(new Size(viewWidth, 100));
var children = new List<IView>() { view }.AsReadOnly();
@ -88,14 +88,14 @@ namespace Microsoft.Maui.UnitTests.Layouts
var stack = CreateTestLayout();
var manager = new HorizontalStackLayoutManager(stack);
var view1 = CreateTestView(new Size(100, 200));
var view2 = CreateTestView(new Size(100, 150));
var view1 = LayoutTestHelpers.CreateTestView(new Size(100, 200));
var view2 = LayoutTestHelpers.CreateTestView(new Size(100, 150));
var children = new List<IView>() { view1, view2 }.AsReadOnly();
stack.Children.Returns(children);
var measurement = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
manager.Arrange(new Rectangle(Point.Zero, measurement));
manager.ArrangeChildren(new Rectangle(Point.Zero, measurement));
// The tallest IView is 200, so the stack should be that tall
Assert.Equal(200, measurement.Height);
@ -117,7 +117,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
var manager = new HorizontalStackLayoutManager(stack);
var measuredSize = manager.Measure(double.PositiveInfinity, 100);
manager.Arrange(new Rectangle(Point.Zero, measuredSize));
manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize));
// We expect that the starting view (0) should be arranged on the left,
// and the next rectangle (1) should be on the right
@ -136,7 +136,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
var manager = new HorizontalStackLayoutManager(stack);
var measuredSize = manager.Measure(double.PositiveInfinity, 100);
manager.Arrange(new Rectangle(Point.Zero, measuredSize));
manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize));
// We expect that the starting view (0) should be arranged on the right,
// and the next rectangle (1) should be on the left

Просмотреть файл

@ -0,0 +1,34 @@
using System.Collections.Generic;
using NSubstitute;
namespace Microsoft.Maui.UnitTests.Layouts
{
public static class LayoutTestHelpers
{
public static IView CreateTestView()
{
var view = Substitute.For<IView>();
view.Height.Returns(-1);
view.Width.Returns(-1);
return view;
}
public static IView CreateTestView(Size viewSize)
{
var view = CreateTestView();
view.Measure(Arg.Any<double>(), Arg.Any<double>()).Returns(viewSize);
view.DesiredSize.Returns(viewSize);
return view;
}
public static void AddChildren(ILayout layout, params IView[] views)
{
var children = new List<IView>(views);
layout.Children.Returns(children.AsReadOnly());
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.Maui;
using NSubstitute;
@ -44,7 +44,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
for (int n = 0; n < viewCount; n++)
{
var view = CreateTestView(new Size(viewWidth, viewHeight));
var view = LayoutTestHelpers.CreateTestView(new Size(viewWidth, viewHeight));
children.Add(view);
}

Просмотреть файл

@ -37,7 +37,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
var manager = new VerticalStackLayoutManager(stack);
var measuredSize = manager.Measure(100, double.PositiveInfinity);
manager.Arrange(new Rectangle(Point.Zero, measuredSize));
manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize));
var expectedRectangle = new Rectangle(0, 0, 100, 100);
stack.Children[0].Received().Arrange(Arg.Is(expectedRectangle));
@ -53,7 +53,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
var manager = new VerticalStackLayoutManager(stack);
var measuredSize = manager.Measure(double.PositiveInfinity, 100);
manager.Arrange(new Rectangle(Point.Zero, measuredSize));
manager.ArrangeChildren(new Rectangle(Point.Zero, measuredSize));
var expectedRectangle0 = new Rectangle(0, 0, 100, 100);
stack.Children[0].Received().Arrange(Arg.Is(expectedRectangle0));
@ -70,7 +70,7 @@ namespace Microsoft.Maui.UnitTests.Layouts
{
var stack = CreateTestLayout();
var view = CreateTestView(new Size(100, viewHeight));
var view = LayoutTestHelpers.CreateTestView(new Size(100, viewHeight));
var children = new List<IView>() { view }.AsReadOnly();
@ -88,14 +88,14 @@ namespace Microsoft.Maui.UnitTests.Layouts
var stack = CreateTestLayout();
var manager = new VerticalStackLayoutManager(stack);
var view1 = CreateTestView(new Size(200, 100));
var view2 = CreateTestView(new Size(150, 100));
var view1 = LayoutTestHelpers.CreateTestView(new Size(200, 100));
var view2 = LayoutTestHelpers.CreateTestView(new Size(150, 100));
var children = new List<IView>() { view1, view2 }.AsReadOnly();
stack.Children.Returns(children);
var measurement = manager.Measure(double.PositiveInfinity, double.PositiveInfinity);
manager.Arrange(new Rectangle(Point.Zero, measurement));
manager.ArrangeChildren(new Rectangle(Point.Zero, measurement));
// The widest IView is 200, so the stack should be that wide
Assert.Equal(200, measurement.Width);