Browse Source

Try to use new algorithm to measure and arrange Grid.

walterlv 7 years ago
parent
commit
b135f988e4

+ 128 - 334
src/Avalonia.Controls/Grid.cs

@@ -4,7 +4,10 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using Avalonia.Collections;
 using Avalonia.Collections;
+using Avalonia.Controls.Utils;
+using JetBrains.Annotations;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
 {
 {
@@ -190,299 +193,97 @@ namespace Avalonia.Controls
         /// <returns>The desired size of the control.</returns>
         /// <returns>The desired size of the control.</returns>
         protected override Size MeasureOverride(Size constraint)
         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<Control>().ToDictionary(child => child,
+                child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child)));
+            var safeRows = Children.OfType<Control>().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<Control>().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<Control>().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<GridNode> sizes = new List<GridNode>();
-            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<Control>()
+                .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;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -492,84 +293,77 @@ namespace Avalonia.Controls
         /// <returns>The space taken.</returns>
         /// <returns>The space taken.</returns>
         protected override Size ArrangeOverride(Size finalSize)
         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<Control, (double row, double rowspan)>();
+            var columnMeasure = new Dictionary<Control, (double column, double columnspan)>();
+            foreach (var child in Children.OfType<Control>().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<Control>().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)
+        /// <summary>
+        /// 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.
+        /// </summary>
+        /// <param name="length">The rows count or the columns count.</param>
+        /// <param name="userIndex">The row or column that the user assigned.</param>
+        /// <param name="userSpan">The rowspan or columnspan that the user assigned.</param>
+        /// <returns>The safe row/column and rowspan/columnspan.</returns>
+        [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);
+        }
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        [Pure]
+        private static IEnumerable<double> Span(IList<double> 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)
         private static double Clamp(double val, double min, double max)

+ 114 - 1
src/Avalonia.Controls/Utils/GridLayout.cs

@@ -15,6 +15,115 @@ namespace Avalonia.Controls.Utils
 
 
         private readonly LengthDefinitions _lengths;
         private readonly LengthDefinitions _lengths;
 
 
+        /// <summary>
+        /// Find out which rows/columns should be measured first. These rows/columns are those that marked with "Auto" size.<para/>
+        /// These "Auto" size rows/columns behavior like fix-size rows/columns but they can only be determined after Measure.
+        /// </summary>
+        /// <returns>The row/column numbers that should be Measure first.</returns>
+        internal List<int> Prepare()
+        {
+            var lengths = _lengths;
+            return Find().ToList();
+
+            IEnumerable<int> Find()
+            {
+                for (var i = 0; i < lengths.Count; i++)
+                {
+                    var unitType = lengths[i].Length.GridUnitType;
+                    if (unitType == GridUnitType.Auto)
+                    {
+                        yield return i;
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Try to calculate the lengths that will be used to measure the children.<para/>
+        /// If the <paramref name="containerLength"/> is not enough, we'll even not compress the measure length.
+        /// So you'd better call <see cref="Prepare"/> first to find out the rows/columns that should be excluded first.
+        /// </summary>
+        /// <param name="containerLength">
+        /// The container length (width or height) excluding some rows/columns.
+        /// Call <see cref="Prepare"/> first to find out the rows/columns that should be excluded.
+        /// </param>
+        /// <returns>The lengths that can be used to measure the children.</returns>
+        [Pure]
+        internal List<double> 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();
+        }
+
         /// <summary>
         /// <summary>
         /// Try to calculate the lengths that will be used to measure the children.
         /// Try to calculate the lengths that will be used to measure the children.
         /// If the <paramref name="containerLength"/> is not enough, we'll even not compress the measure length.
         /// If the <paramref name="containerLength"/> is not enough, we'll even not compress the measure length.
@@ -22,7 +131,7 @@ namespace Avalonia.Controls.Utils
         /// <param name="containerLength">The container length, width or height.</param>
         /// <param name="containerLength">The container length, width or height.</param>
         /// <returns>The lengths that can be used to measure the children.</returns>
         /// <returns>The lengths that can be used to measure the children.</returns>
         [Pure]
         [Pure]
-        internal List<double> Measure(double containerLength)
+        internal List<double> Arrange(double containerLength)
         {
         {
             var lengths = _lengths.Clone();
             var lengths = _lengths.Clone();
 
 
@@ -106,6 +215,10 @@ namespace Avalonia.Controls.Utils
                 _lengths = lengths.ToList();
                 _lengths = lengths.ToList();
             }
             }
 
 
+            public LengthDefinition this[int index] => _lengths[index];
+
+            public int Count => _lengths.Count;
+
             public IEnumerator<LengthDefinition> GetEnumerator() => _lengths.GetEnumerator();
             public IEnumerator<LengthDefinition> GetEnumerator() => _lengths.GetEnumerator();
 
 
             IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
             IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

+ 91 - 0
tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs

@@ -95,5 +95,96 @@ namespace Avalonia.Controls.UnitTests
             // Assert
             // Assert
             Assert.Equal(measure, new[] { 100d, 200d, 300d });
             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 });
+        //}
     }
     }
 }
 }

+ 26 - 0
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -1,8 +1,12 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // 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.
 // 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.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
+using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Xunit;
 using Xunit;
 
 
@@ -124,5 +128,27 @@ namespace Avalonia.Controls.UnitTests
             GridAssert.ChildrenHeight(rowGrid, 200, 50, 50);
             GridAssert.ChildrenHeight(rowGrid, 200, 50, 50);
             GridAssert.ChildrenWidth(columnGrid, 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);
+        }
     }
     }
 }
 }