From b135f988e41c5d5789629fd06dfc8f7f54995896 Mon Sep 17 00:00:00 2001 From: walterlv Date: Sun, 29 Apr 2018 11:25:30 +0800 Subject: [PATCH] Try to use new algorithm to measure and arrange Grid. --- src/Avalonia.Controls/Grid.cs | 462 +++++------------- src/Avalonia.Controls/Utils/GridLayout.cs | 115 ++++- .../GridLayoutTests.cs | 91 ++++ .../Avalonia.Controls.UnitTests/GridTests.cs | 26 + 4 files changed, 359 insertions(+), 335 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 02c961c179..5991d61fb8 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using Avalonia.Collections; +using Avalonia.Controls.Utils; +using JetBrains.Annotations; namespace Avalonia.Controls { @@ -190,299 +193,97 @@ namespace Avalonia.Controls /// The desired size of the control. protected override Size MeasureOverride(Size constraint) { - Size totalSize = constraint; - int colCount = ColumnDefinitions.Count; - int rowCount = RowDefinitions.Count; - double totalStarsX = 0; - double totalStarsY = 0; - bool emptyRows = rowCount == 0; - bool emptyCols = colCount == 0; - bool hasChildren = Children.Count > 0; - - if (emptyRows) - { - rowCount = 1; - } - - if (emptyCols) - { - colCount = 1; - } - - CreateMatrices(rowCount, colCount); - - if (emptyRows) - { - _rowMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star); - _rowMatrix[0, 0].Stars = 1.0; - totalStarsY += 1.0; - } - else - { - for (int i = 0; i < rowCount; i++) + // +------- 1. Prepare the children status -------+ + // + + + // +------- ------------------------------ -------+ + + // Normalize the column/columnspan and row/rowspan. + var columnCount = ColumnDefinitions.Count; + var rowCount = RowDefinitions.Count; + var safeColumns = Children.OfType().ToDictionary(child => child, + child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child))); + var safeRows = Children.OfType().ToDictionary(child => child, + child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child))); + + // +------- 2. -------+ + // + + + // +------- ------------------------------ -------+ + + // Find out the children that should be Measure first (those rows/columns are Auto size.) + var columnLayout = new GridLayout(ColumnDefinitions); + var rowLayout = new GridLayout(RowDefinitions); + var autoSizeColumns = columnLayout.Prepare(); + var autoSizeRows = rowLayout.Prepare(); + + foreach (var pair in safeColumns) + { + var child = pair.Key; + var (column, columnSpan) = pair.Value; + var columnLast = column + columnSpan - 1; + if (autoSizeColumns.Contains(columnLast)) { - RowDefinition rowdef = RowDefinitions[i]; - GridLength height = rowdef.Height; - - rowdef.ActualHeight = double.PositiveInfinity; - _rowMatrix[i, i] = new Segment(0, rowdef.MinHeight, rowdef.MaxHeight, height.GridUnitType); - - if (height.GridUnitType == GridUnitType.Pixel) - { - _rowMatrix[i, i].OfferedSize = Clamp(height.Value, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max); - _rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize; - rowdef.ActualHeight = _rowMatrix[i, i].OfferedSize; - } - else if (height.GridUnitType == GridUnitType.Star) - { - _rowMatrix[i, i].OfferedSize = Clamp(0, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max); - _rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize; - - _rowMatrix[i, i].Stars = height.Value; - totalStarsY += height.Value; - } - else if (height.GridUnitType == GridUnitType.Auto) - { - _rowMatrix[i, i].OfferedSize = Clamp(0, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max); - _rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize; - } + } } - if (emptyCols) + // Calculate row height list and column width list. + var widthList = columnLayout.Measure(constraint.Width); + var heightList = rowLayout.Measure(constraint.Height); + + // Calculate the available width list and height list for every child. + var childrenAvailableWidths = Children.OfType().ToDictionary(child => child, child => { - _colMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star); - _colMatrix[0, 0].Stars = 1.0; - totalStarsX += 1.0; - } - else + var (column, columnSpan) = GetSafeSpan(widthList.Count, GetColumn(child), GetColumnSpan(child)); + return Span(widthList, column, columnSpan).Sum(); + }); + var childrenAvailableHeights = Children.OfType().ToDictionary(child => child, child => { - for (int i = 0; i < colCount; i++) - { - ColumnDefinition coldef = ColumnDefinitions[i]; - GridLength width = coldef.Width; - - coldef.ActualWidth = double.PositiveInfinity; - _colMatrix[i, i] = new Segment(0, coldef.MinWidth, coldef.MaxWidth, width.GridUnitType); - - if (width.GridUnitType == GridUnitType.Pixel) - { - _colMatrix[i, i].OfferedSize = Clamp(width.Value, _colMatrix[i, i].Min, _colMatrix[i, i].Max); - _colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize; - coldef.ActualWidth = _colMatrix[i, i].OfferedSize; - } - else if (width.GridUnitType == GridUnitType.Star) - { - _colMatrix[i, i].OfferedSize = Clamp(0, _colMatrix[i, i].Min, _colMatrix[i, i].Max); - _colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize; - - _colMatrix[i, i].Stars = width.Value; - totalStarsX += width.Value; - } - else if (width.GridUnitType == GridUnitType.Auto) - { - _colMatrix[i, i].OfferedSize = Clamp(0, _colMatrix[i, i].Min, _colMatrix[i, i].Max); - _colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize; - } - } - } - - List sizes = new List(); - GridNode node; - GridNode separator = new GridNode(null, 0, 0, 0); - int separatorIndex; - - sizes.Add(separator); + var (row, rowSpan) = GetSafeSpan(heightList.Count, GetRow(child), GetRowSpan(child)); + return Span(heightList, row, rowSpan).Sum(); + }); - // Pre-process the grid children so that we know what types of elements we have so - // we can apply our special measuring rules. - GridWalker gridWalker = new GridWalker(this, _rowMatrix, _colMatrix); + // Measure the children. + var availableWidth = constraint.Width; + var availableHeight = constraint.Height; + var desiredWidth = 0.0; + var desiredHeight = 0.0; + var sortedChildren = Children.OfType() + .OrderBy(GetColumn).ThenBy(GetRow) + .ToDictionary(child => child, child => (GetColumn(child), GetRow(child))); - for (int i = 0; i < 6; i++) + var currentDesiredWidth = 0.0; + var currentDesiredHeight = 0.0; + var currentColumn = 0; + var currentRow = 0; + foreach (var pair in sortedChildren) { - // These bools tell us which grid element type we should be measuring. i.e. - // 'star/auto' means we should measure elements with a star row and auto col - bool autoAuto = i == 0; - bool starAuto = i == 1; - bool autoStar = i == 2; - bool starAutoAgain = i == 3; - bool nonStar = i == 4; - bool remainingStar = i == 5; + var child = pair.Key; + var (column, row) = pair.Value; + child.Measure(new Size(childrenAvailableWidths[child], childrenAvailableHeights[child])); + var desiredSize = child.DesiredSize; - if (hasChildren) + if (column == currentColumn) { - ExpandStarCols(totalSize); - ExpandStarRows(totalSize); + currentDesiredWidth = Math.Max(desiredSize.Width, currentDesiredWidth); } - - foreach (Control child in Children) + else { - int col, row; - int colspan, rowspan; - double childSizeX = 0; - double childSizeY = 0; - bool starCol = false; - bool starRow = false; - bool autoCol = false; - bool autoRow = false; - - col = Math.Min(GetColumn(child), colCount - 1); - row = Math.Min(GetRow(child), rowCount - 1); - colspan = Math.Min(GetColumnSpan(child), colCount - col); - rowspan = Math.Min(GetRowSpan(child), rowCount - row); - - for (int r = row; r < row + rowspan; r++) - { - starRow |= _rowMatrix[r, r].Type == GridUnitType.Star; - autoRow |= _rowMatrix[r, r].Type == GridUnitType.Auto; - } - - for (int c = col; c < col + colspan; c++) - { - starCol |= _colMatrix[c, c].Type == GridUnitType.Star; - autoCol |= _colMatrix[c, c].Type == GridUnitType.Auto; - } - - // This series of if statements checks whether or not we should measure - // the current element and also if we need to override the sizes - // passed to the Measure call. - - // If the element has Auto rows and Auto columns and does not span Star - // rows/cols it should only be measured in the auto_auto phase. - // There are similar rules governing auto/star and star/auto elements. - // NOTE: star/auto elements are measured twice. The first time with - // an override for height, the second time without it. - if (autoRow && autoCol && !starRow && !starCol) - { - if (!autoAuto) - { - continue; - } - - childSizeX = double.PositiveInfinity; - childSizeY = double.PositiveInfinity; - } - else if (starRow && autoCol && !starCol) - { - if (!(starAuto || starAutoAgain)) - { - continue; - } - - if (starAuto && gridWalker.HasAutoStar) - { - childSizeY = double.PositiveInfinity; - } - - childSizeX = double.PositiveInfinity; - } - else if (autoRow && starCol && !starRow) - { - if (!autoStar) - { - continue; - } - - childSizeY = double.PositiveInfinity; - } - else if ((autoRow || autoCol) && !(starRow || starCol)) - { - if (!nonStar) - { - continue; - } - - if (autoRow) - { - childSizeY = double.PositiveInfinity; - } - - if (autoCol) - { - childSizeX = double.PositiveInfinity; - } - } - else if (!(starRow || starCol)) - { - if (!nonStar) - { - continue; - } - } - else - { - if (!remainingStar) - { - continue; - } - } - - for (int r = row; r < row + rowspan; r++) - { - childSizeY += _rowMatrix[r, r].OfferedSize; - } - - for (int c = col; c < col + colspan; c++) - { - childSizeX += _colMatrix[c, c].OfferedSize; - } - - child.Measure(new Size(childSizeX, childSizeY)); - Size desired = child.DesiredSize; - - // Elements distribute their height based on two rules: - // 1) Elements with rowspan/colspan == 1 distribute their height first - // 2) Everything else distributes in a LIFO manner. - // As such, add all UIElements with rowspan/colspan == 1 after the separator in - // the list and everything else before it. Then to process, just keep popping - // elements off the end of the list. - if (!starAuto) - { - node = new GridNode(_rowMatrix, row + rowspan - 1, row, desired.Height); - separatorIndex = sizes.IndexOf(separator); - sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node); - } - - node = new GridNode(_colMatrix, col + colspan - 1, col, desired.Width); - - separatorIndex = sizes.IndexOf(separator); - sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node); + currentDesiredWidth = desiredSize.Width; + currentColumn = column; + availableWidth -= desiredSize.Width; + desiredHeight -= desiredSize.Width; } - sizes.Remove(separator); - - while (sizes.Count > 0) + if (availableWidth < desiredSize.Width) { - node = sizes.Last(); - node.Matrix[node.Row, node.Column].DesiredSize = Math.Max(node.Matrix[node.Row, node.Column].DesiredSize, node.Size); - AllocateDesiredSize(rowCount, colCount); - sizes.Remove(node); + } - sizes.Add(separator); + availableHeight -= desiredSize.Height; + desiredHeight -= desiredSize.Width; } - // Once we have measured and distributed all sizes, we have to store - // the results. Every time we want to expand the rows/cols, this will - // be used as the baseline. - SaveMeasureResults(); - - sizes.Remove(separator); - - double gridSizeX = 0; - double gridSizeY = 0; - - for (int c = 0; c < colCount; c++) - { - gridSizeX += _colMatrix[c, c].DesiredSize; - } - - for (int r = 0; r < rowCount; r++) - { - gridSizeY += _rowMatrix[r, r].DesiredSize; - } - - return new Size(gridSizeX, gridSizeY); + return constraint; } /// @@ -492,84 +293,77 @@ namespace Avalonia.Controls /// The space taken. protected override Size ArrangeOverride(Size finalSize) { - int colCount = ColumnDefinitions.Count; - int rowCount = RowDefinitions.Count; - int colMatrixDim = _colMatrix.GetLength(0); - int rowMatrixDim = _rowMatrix.GetLength(0); - - RestoreMeasureResults(); + // Calculate row height list and column width list. + var rowLayout = new GridLayout(RowDefinitions); + var columnLayout = new GridLayout(ColumnDefinitions); + var heightList = rowLayout.Measure(finalSize.Height); + var widthList = columnLayout.Measure(finalSize.Width); - double totalConsumedX = 0; - double totalConsumedY = 0; - - for (int c = 0; c < colMatrixDim; c++) + var rowMeasure = new Dictionary(); + var columnMeasure = new Dictionary(); + foreach (var child in Children.OfType().OrderBy(GetRow)) { - _colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize; - totalConsumedX += _colMatrix[c, c].OfferedSize; + var (row, rowSpan) = GetSafeSpan(heightList.Count, GetRow(child), GetRowSpan(child)); + rowMeasure.Add(child, (row, rowSpan)); } - - for (int r = 0; r < rowMatrixDim; r++) + foreach (var child in Children.OfType().OrderBy(GetColumn)) { - _rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize; - totalConsumedY += _rowMatrix[r, r].OfferedSize; + var (column, columnSpan) = GetSafeSpan(widthList.Count, GetColumn(child), GetColumnSpan(child)); + columnMeasure.Add(child, (column, columnSpan)); } - if (totalConsumedX != finalSize.Width) - { - ExpandStarCols(finalSize); - } + } - if (totalConsumedY != finalSize.Height) + /// + /// Gets the safe row/column and rowspan/columnspan for a specified range. + /// The user may assign the row/column properties out of the row count or column cout, this method helps to keep them in. + /// + /// The rows count or the columns count. + /// The row or column that the user assigned. + /// The rowspan or columnspan that the user assigned. + /// The safe row/column and rowspan/columnspan. + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan) + { + var index = userIndex; + var span = userSpan; + if (userIndex > length) { - ExpandStarRows(finalSize); + index = length; + span = 1; } - - for (int c = 0; c < colCount; c++) + else if (userIndex + userSpan > length) { - ColumnDefinitions[c].ActualWidth = _colMatrix[c, c].OfferedSize; + span = length - userIndex + 1; } - for (int r = 0; r < rowCount; r++) + return (index, span); + } + + /// + /// Return part of a list from the specified start index and its span length. + /// If Avalonia upgrade .NET Core to 2.1 and introduce C# 7.2, we can use Span to do this. + /// + [Pure] + private static IEnumerable Span(IList list, int index, int span) + { +#if DEBUG + // We do not verify arguments in RELEASE because this is a private method, + // and we must write the correct code before publishing. + if (index >= list.Count) throw new ArgumentOutOfRangeException(nameof(index)); + if (span <= 1) throw new ArgumentException("Argument span should not be smaller than 1.", nameof(span)); + if (index + span > list.Count) throw new ArgumentOutOfRangeException(nameof(index)); +#endif + if (span == 1) { - RowDefinitions[r].ActualHeight = _rowMatrix[r, r].OfferedSize; + yield return list[index]; + yield break; } - foreach (Control child in Children) + for (var i = index; i < index + span; i++) { - int col = Math.Min(GetColumn(child), colMatrixDim - 1); - int row = Math.Min(GetRow(child), rowMatrixDim - 1); - int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - col); - int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - row); - - double childFinalX = 0; - double childFinalY = 0; - double childFinalW = 0; - double childFinalH = 0; - - for (int c = 0; c < col; c++) - { - childFinalX += _colMatrix[c, c].OfferedSize; - } - - for (int c = col; c < col + colspan; c++) - { - childFinalW += _colMatrix[c, c].OfferedSize; - } - - for (int r = 0; r < row; r++) - { - childFinalY += _rowMatrix[r, r].OfferedSize; - } - - for (int r = row; r < row + rowspan; r++) - { - childFinalH += _rowMatrix[r, r].OfferedSize; - } - - child.Arrange(new Rect(childFinalX, childFinalY, childFinalW, childFinalH)); + yield return list[i]; } - - return finalSize; } private static double Clamp(double val, double min, double max) diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs index 439b6830b9..55daf2c9ca 100644 --- a/src/Avalonia.Controls/Utils/GridLayout.cs +++ b/src/Avalonia.Controls/Utils/GridLayout.cs @@ -15,6 +15,115 @@ namespace Avalonia.Controls.Utils private readonly LengthDefinitions _lengths; + /// + /// Find out which rows/columns should be measured first. These rows/columns are those that marked with "Auto" size. + /// These "Auto" size rows/columns behavior like fix-size rows/columns but they can only be determined after Measure. + /// + /// The row/column numbers that should be Measure first. + internal List Prepare() + { + var lengths = _lengths; + return Find().ToList(); + + IEnumerable Find() + { + for (var i = 0; i < lengths.Count; i++) + { + var unitType = lengths[i].Length.GridUnitType; + if (unitType == GridUnitType.Auto) + { + yield return i; + } + } + } + } + + /// + /// Try to calculate the lengths that will be used to measure the children. + /// If the is not enough, we'll even not compress the measure length. + /// So you'd better call first to find out the rows/columns that should be excluded first. + /// + /// + /// The container length (width or height) excluding some rows/columns. + /// Call first to find out the rows/columns that should be excluded. + /// + /// The lengths that can be used to measure the children. + [Pure] + internal List Measure(double containerLength) + { + var lengths = _lengths.Clone(); + + // Exclude all the pixel lengths, so that we can calculate the star lengths. + containerLength -= lengths + .Where(x => x.Length.IsAbsolute) + .Aggregate(0.0, (sum, add) => sum + add.Length.Value); + + // Aggregate the star count, so that we can determine the length of each star unit. + var starCount = lengths + .Where(x => x.Length.IsStar) + .Aggregate(0.0, (sum, add) => sum + add.Length.Value); + // There is no need to care the (starCount == 0). If this happens, we'll ignore all the stars. + var starUnitLength = containerLength / starCount; + + // If there is no stars, just return all pixels. + if (Equals(starCount, 0.0)) + { + return lengths.Select(x => x.Length.IsAuto ? double.PositiveInfinity : x.Length.Value).ToList(); + } + + // --- + // Warning! The code below will start to change the lengths item value. + // --- + + // Exclude the star unit if its min/max length range does not contain the calculated star length. + var intermediateStarLengths = lengths.Where(x => x.Length.IsStar).ToList(); + // Indicate whether all star lengths are in range of min and max or not. + var allInRange = false; + while (!allInRange) + { + foreach (var length in intermediateStarLengths) + { + // Find out if there is any length out of min to max. + var (star, min, max) = (length.Length.Value, length.MinLength, length.MaxLength); + var starLength = star * starUnitLength; + if (starLength < min || starLength > max) + { + // If the star length is out of min to max, change it to a pixel unit. + if (starLength < min) + { + length.Update(min); + starLength = min; + } + else if (starLength > max) + { + length.Update(max); + starLength = max; + } + + // Update the rest star length info. + intermediateStarLengths.Remove(length); + containerLength -= starLength; + starCount -= star; + starUnitLength = containerLength / starCount; + break; + } + } + + // All lengths are in range, so that we have enough lengths to measure children. + allInRange = true; + foreach (var length in intermediateStarLengths) + { + length.Update(length.Length.Value * starUnitLength); + } + } + + // Return the modified lengths as measuring lengths. + return lengths.Select(x => + x.Length.GridUnitType == GridUnitType.Auto + ? double.PositiveInfinity + : x.Length.Value).ToList(); + } + /// /// Try to calculate the lengths that will be used to measure the children. /// If the is not enough, we'll even not compress the measure length. @@ -22,7 +131,7 @@ namespace Avalonia.Controls.Utils /// The container length, width or height. /// The lengths that can be used to measure the children. [Pure] - internal List Measure(double containerLength) + internal List Arrange(double containerLength) { var lengths = _lengths.Clone(); @@ -106,6 +215,10 @@ namespace Avalonia.Controls.Utils _lengths = lengths.ToList(); } + public LengthDefinition this[int index] => _lengths[index]; + + public int Count => _lengths.Count; + public IEnumerator GetEnumerator() => _lengths.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs index be6b6ce465..984ec2f6f6 100644 --- a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs @@ -95,5 +95,96 @@ namespace Avalonia.Controls.UnitTests // Assert Assert.Equal(measure, new[] { 100d, 200d, 300d }); } + + //[Fact] + //public void Arrange_AllPixelLength_Correct() + //{ + // // Arrange + // var layout = new GridLayout(new RowDefinitions("100,200,300")); + + // // Action + // var arrange = layout.Arrange(800); + + // // Assert + // Assert.Equal(arrange, new[] { 100d, 200d, 300d }); + //} + + //[Fact] + //public void Arrange_AllStarLength_Correct() + //{ + // // Arrange + // var layout = new GridLayout(new RowDefinitions("*,2*,3*")); + + // // Action + // var arrange = layout.Arrange(600); + + // // Assert + // Assert.Equal(arrange, new[] { 100d, 200d, 300d }); + //} + + //[Fact] + //public void Arrange_MixStarPixelLength_Correct() + //{ + // // Arrange + // var layout = new GridLayout(new RowDefinitions("100,2*,3*")); + + // // Action + // var arrange = layout.Arrange(600); + + // // Assert + // Assert.Equal(arrange, new[] { 100d, 200d, 300d }); + //} + + //[Fact] + //public void Arrange_MixAutoPixelLength_Correct() + //{ + // // Arrange + // var layout = new GridLayout(new RowDefinitions("100,200,Auto")); + + // // Action + // var arrange = layout.Arrange(600); + + // // Assert + // Assert.Equal(arrange, new[] { 100d, 200d, 300d }); + //} + + //[Fact] + //public void Arrange_MixAutoStarLength_Correct() + //{ + // // Arrange + // var layout = new GridLayout(new RowDefinitions("*,2*,Auto")); + + // // Action + // var arrange = layout.Arrange(600); + + // // Assert + // Assert.Equal(arrange, new[] { 200d, 400d, double.PositiveInfinity }); + //} + + //[Fact] + //public void Arrange_MixAutoStarPixelLength_Correct() + //{ + // // Arrange + // var layout = new GridLayout(new RowDefinitions("*,200,Auto")); + + // // Action + // var arrange = layout.Arrange(600); + + // // Assert + // Assert.Equal(arrange, new[] { 400d, 200d, double.PositiveInfinity }); + //} + + //[Fact] + //public void Arrange_AllPixelLengthButNotEnough_Correct() + //{ + // // Arrange + // var layout = new GridLayout(new RowDefinitions("100,200,300")); + + // // Action + // var arrange = layout.Arrange(400); + + // // Assert + // Assert.Equal(arrange, new[] { 100d, 200d, 300d }); + //} } } diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index e739e8edba..93c3c9258f 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1,8 +1,12 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Linq; using Avalonia.Controls; using Xunit; @@ -124,5 +128,27 @@ namespace Avalonia.Controls.UnitTests GridAssert.ChildrenHeight(rowGrid, 200, 50, 50); GridAssert.ChildrenWidth(columnGrid, 200, 50, 50); } + + [Fact] + public void Layout_StarRowColumnWithMaxLength_BoundsCorrect() + { + // Arrange & Action + var rowGrid = GridMock.New(new RowDefinitions + { + new RowDefinition(1, GridUnitType.Star) { MaxHeight = 200 }, + new RowDefinition(1, GridUnitType.Star), + new RowDefinition(1, GridUnitType.Star), + }, arrange: 800); + var columnGrid = GridMock.New(new ColumnDefinitions + { + new ColumnDefinition(1, GridUnitType.Star) { MaxWidth = 200 }, + new ColumnDefinition(1, GridUnitType.Star), + new ColumnDefinition(1, GridUnitType.Star), + }, arrange: 800); + + // Assert + GridAssert.ChildrenHeight(rowGrid, 200, 300, 300); + GridAssert.ChildrenWidth(columnGrid, 200, 300, 300); + } } }