ソースを参照

Order the grid codes and make most unit test passed.

walterlv 7 年 前
コミット
2052d53d68

+ 51 - 392
src/Avalonia.Controls/Grid.cs

@@ -48,10 +48,6 @@ namespace Avalonia.Controls
 
         private RowDefinitions _rowDefinitions;
 
-        private Segment[,] _rowMatrix;
-
-        private Segment[,] _colMatrix;
-
         /// <summary>
         /// Gets or sets the columns definitions for the grid.
         /// </summary>
@@ -186,7 +182,16 @@ namespace Avalonia.Controls
             element.SetValue(RowSpanProperty, value);
         }
 
+        /// <summary>
+        /// Gets the result of last column measuring produce.
+        /// Use this result to reduce the arrange calculation.
+        /// </summary>
         private GridLayout.MeasureResult _columnMeasureCache;
+
+        /// <summary>
+        /// Gets the result of last row measuring produce.
+        /// Use this result to reduce the arrange calculation.
+        /// </summary>
         private GridLayout.MeasureResult _rowMeasureCache;
 
         /// <summary>
@@ -196,11 +201,32 @@ namespace Avalonia.Controls
         /// <returns>The desired size of the control.</returns>
         protected override Size MeasureOverride(Size constraint)
         {
+            // If the grid doesn't have any column/row definitions, it behaviors like a nomal panel.
+
+            if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
+            {
+                var maxWidth = 0.0;
+                var maxHeight = 0.0;
+                foreach (var child in Children.OfType<Control>())
+                {
+                    child.Measure(constraint);
+                    maxWidth = Math.Max(maxWidth, child.DesiredSize.Width);
+                    maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
+                }
+
+                maxWidth = Math.Min(maxWidth, constraint.Width);
+                maxHeight = Math.Min(maxHeight, constraint.Height);
+                return new Size(maxWidth, maxHeight);
+            }
+
+            // If the grid defines some columns or rows.
+
             var measureCache = new Dictionary<Control, Size>();
             var (safeColumns, safeRows) = GetSafeColumnRows();
 
             var columnLayout = new GridLayout(ColumnDefinitions);
             var rowLayout = new GridLayout(RowDefinitions);
+            // Note: If a child stays in a * or Auto column/row, use constraint to measure it.
             columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width);
             rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height);
 
@@ -221,6 +247,8 @@ namespace Avalonia.Controls
             _rowMeasureCache = rowResult;
             return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
 
+            // Measure each child only once.
+            // If a child has been measured, it will just return the desired size.
             Size MeasureOnce(Control child, Size size)
             {
                 if (measureCache.TryGetValue(child, out var desiredSize))
@@ -242,6 +270,20 @@ namespace Avalonia.Controls
         /// <returns>The space taken.</returns>
         protected override Size ArrangeOverride(Size finalSize)
         {
+            // If the grid doesn't have any column/row definitions, it behaviors like a nomal panel.
+
+            if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
+            {
+                foreach (var child in Children.OfType<Control>())
+                {
+                    child.Arrange(new Rect(finalSize));
+                }
+
+                return finalSize;
+            }
+
+            // If the grid defines some columns or rows.
+
             var (safeColumns, safeRows) = GetSafeColumnRows();
 
             var columnLayout = new GridLayout(ColumnDefinitions);
@@ -263,6 +305,10 @@ namespace Avalonia.Controls
             return finalSize;
         }
 
+        /// <summary>
+        /// Get the safe column/columnspan and safe row/rowspan.
+        /// The result of this method ensure that none of the children has a column/row out of the definitions.
+        /// </summary>
         private (Dictionary<Control, (int index, int span)> safeColumns,
             Dictionary<Control, (int index, int span)> safeRows) GetSafeColumnRows()
         {
@@ -301,22 +347,6 @@ namespace Avalonia.Controls
             return (index, span);
         }
 
-        private static double Clamp(double val, double min, double max)
-        {
-            if (val < min)
-            {
-                return min;
-            }
-            else if (val > max)
-            {
-                return max;
-            }
-            else
-            {
-                return val;
-            }
-        }
-
         private static int ValidateColumn(AvaloniaObject o, int value)
         {
             if (value < 0)
@@ -336,376 +366,5 @@ namespace Avalonia.Controls
 
             return value;
         }
-
-        private void CreateMatrices(int rowCount, int colCount)
-        {
-            if (_rowMatrix == null || _colMatrix == null ||
-                _rowMatrix.GetLength(0) != rowCount ||
-                _colMatrix.GetLength(0) != colCount)
-            {
-                _rowMatrix = new Segment[rowCount, rowCount];
-                _colMatrix = new Segment[colCount, colCount];
-            }
-            else
-            {
-                Array.Clear(_rowMatrix, 0, _rowMatrix.Length);
-                Array.Clear(_colMatrix, 0, _colMatrix.Length);
-            }
-        }
-
-        private void ExpandStarCols(Size availableSize)
-        {
-            int matrixCount = _colMatrix.GetLength(0);
-            int columnsCount = ColumnDefinitions.Count;
-            double width = availableSize.Width;
-
-            for (int i = 0; i < matrixCount; i++)
-            {
-                if (_colMatrix[i, i].Type == GridUnitType.Star)
-                {
-                    _colMatrix[i, i].OfferedSize = 0;
-                }
-                else
-                {
-                    width = Math.Max(width - _colMatrix[i, i].OfferedSize, 0);
-                }
-            }
-
-            AssignSize(_colMatrix, 0, matrixCount - 1, ref width, GridUnitType.Star, false);
-            width = Math.Max(0, width);
-
-            if (columnsCount > 0)
-            {
-                for (int i = 0; i < matrixCount; i++)
-                {
-                    if (_colMatrix[i, i].Type == GridUnitType.Star)
-                    {
-                        ColumnDefinitions[i].ActualWidth = _colMatrix[i, i].OfferedSize;
-                    }
-                }
-            }
-        }
-
-        private void ExpandStarRows(Size availableSize)
-        {
-            int matrixCount = _rowMatrix.GetLength(0);
-            int rowCount = RowDefinitions.Count;
-            double height = availableSize.Height;
-
-            // When expanding star rows, we need to zero out their height before
-            // calling AssignSize. AssignSize takes care of distributing the
-            // available size when there are Mins and Maxs applied.
-            for (int i = 0; i < matrixCount; i++)
-            {
-                if (_rowMatrix[i, i].Type == GridUnitType.Star)
-                {
-                    _rowMatrix[i, i].OfferedSize = 0.0;
-                }
-                else
-                {
-                    height = Math.Max(height - _rowMatrix[i, i].OfferedSize, 0);
-                }
-            }
-
-            AssignSize(_rowMatrix, 0, matrixCount - 1, ref height, GridUnitType.Star, false);
-
-            if (rowCount > 0)
-            {
-                for (int i = 0; i < matrixCount; i++)
-                {
-                    if (_rowMatrix[i, i].Type == GridUnitType.Star)
-                    {
-                        RowDefinitions[i].ActualHeight = _rowMatrix[i, i].OfferedSize;
-                    }
-                }
-            }
-        }
-
-        private void AssignSize(
-            Segment[,] matrix,
-            int start,
-            int end,
-            ref double size,
-            GridUnitType type,
-            bool desiredSize)
-        {
-            double count = 0;
-            bool assigned;
-
-            // Count how many segments are of the correct type. If we're measuring Star rows/cols
-            // we need to count the number of stars instead.
-            for (int i = start; i <= end; i++)
-            {
-                double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
-                if (segmentSize < matrix[i, i].Max)
-                {
-                    count += type == GridUnitType.Star ? matrix[i, i].Stars : 1;
-                }
-            }
-
-            do
-            {
-                double contribution = size / count;
-
-                assigned = false;
-
-                for (int i = start; i <= end; i++)
-                {
-                    double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
-
-                    if (!(matrix[i, i].Type == type && segmentSize < matrix[i, i].Max))
-                    {
-                        continue;
-                    }
-
-                    double newsize = segmentSize;
-                    newsize += contribution * (type == GridUnitType.Star ? matrix[i, i].Stars : 1);
-                    double newSizeIgnoringMinMax = newsize;
-                    newsize = Math.Min(newsize, matrix[i, i].Max);
-                    newsize = Math.Max(newsize, matrix[i, i].Min);
-                    assigned |= !Equals(newsize, newSizeIgnoringMinMax);
-                    size -= newsize - segmentSize;
-
-                    if (desiredSize)
-                    {
-                        matrix[i, i].DesiredSize = newsize;
-                    }
-                    else
-                    {
-                        matrix[i, i].OfferedSize = newsize;
-                    }
-                }
-            }
-            while (assigned);
-        }
-
-        private void AllocateDesiredSize(int rowCount, int colCount)
-        {
-            // First allocate the heights of the RowDefinitions, then allocate
-            // the widths of the ColumnDefinitions.
-            for (int i = 0; i < 2; i++)
-            {
-                Segment[,] matrix = i == 0 ? _rowMatrix : _colMatrix;
-                int count = i == 0 ? rowCount : colCount;
-
-                for (int row = count - 1; row >= 0; row--)
-                {
-                    for (int col = row; col >= 0; col--)
-                    {
-                        bool spansStar = false;
-                        for (int j = row; j >= col; j--)
-                        {
-                            spansStar |= matrix[j, j].Type == GridUnitType.Star;
-                        }
-
-                        // This is the amount of pixels which must be available between the grid rows
-                        // at index 'col' and 'row'. i.e. if 'row' == 0 and 'col' == 2, there must
-                        // be at least 'matrix [row][col].size' pixels of height allocated between
-                        // all the rows in the range col -> row.
-                        double current = matrix[row, col].DesiredSize;
-
-                        // Count how many pixels have already been allocated between the grid rows
-                        // in the range col -> row. The amount of pixels allocated to each grid row/column
-                        // is found on the diagonal of the matrix.
-                        double totalAllocated = 0;
-
-                        for (int k = row; k >= col; k--)
-                        {
-                            totalAllocated += matrix[k, k].DesiredSize;
-                        }
-
-                        // If the size requirement has not been met, allocate the additional required
-                        // size between 'pixel' rows, then 'star' rows, finally 'auto' rows, until all
-                        // height has been assigned.
-                        if (totalAllocated < current)
-                        {
-                            double additional = current - totalAllocated;
-
-                            if (spansStar)
-                            {
-                                AssignSize(matrix, col, row, ref additional, GridUnitType.Star, true);
-                            }
-                            else
-                            {
-                                AssignSize(matrix, col, row, ref additional, GridUnitType.Pixel, true);
-                                AssignSize(matrix, col, row, ref additional, GridUnitType.Auto, true);
-                            }
-                        }
-                    }
-                }
-            }
-
-            int rowMatrixDim = _rowMatrix.GetLength(0);
-            int colMatrixDim = _colMatrix.GetLength(0);
-
-            for (int r = 0; r < rowMatrixDim; r++)
-            {
-                _rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize;
-            }
-
-            for (int c = 0; c < colMatrixDim; c++)
-            {
-                _colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize;
-            }
-        }
-
-        private void SaveMeasureResults()
-        {
-            int rowMatrixDim = _rowMatrix.GetLength(0);
-            int colMatrixDim = _colMatrix.GetLength(0);
-
-            for (int i = 0; i < rowMatrixDim; i++)
-            {
-                for (int j = 0; j < rowMatrixDim; j++)
-                {
-                    _rowMatrix[i, j].OriginalSize = _rowMatrix[i, j].OfferedSize;
-                }
-            }
-
-            for (int i = 0; i < colMatrixDim; i++)
-            {
-                for (int j = 0; j < colMatrixDim; j++)
-                {
-                    _colMatrix[i, j].OriginalSize = _colMatrix[i, j].OfferedSize;
-                }
-            }
-        }
-
-        private void RestoreMeasureResults()
-        {
-            int rowMatrixDim = _rowMatrix.GetLength(0);
-            int colMatrixDim = _colMatrix.GetLength(0);
-
-            for (int i = 0; i < rowMatrixDim; i++)
-            {
-                for (int j = 0; j < rowMatrixDim; j++)
-                {
-                    _rowMatrix[i, j].OfferedSize = _rowMatrix[i, j].OriginalSize;
-                }
-            }
-
-            for (int i = 0; i < colMatrixDim; i++)
-            {
-                for (int j = 0; j < colMatrixDim; j++)
-                {
-                    _colMatrix[i, j].OfferedSize = _colMatrix[i, j].OriginalSize;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Stores the layout values of of <see cref="RowDefinitions"/> of <see cref="ColumnDefinitions"/>.
-        /// </summary>
-        private struct Segment
-        {
-            /// <summary>
-            /// Gets or sets the base size of this segment.
-            /// The value is from the user's code or from the stored measuring values.
-            /// </summary>
-            public double OriginalSize;
-
-            /// <summary>
-            /// Gets the maximum size of this segment.
-            /// The value is from the user's code.
-            /// </summary>
-            public readonly double Max;
-
-            /// <summary>
-            /// Gets the minimum size of this segment.
-            /// The value is from the user's code.
-            /// </summary>
-            public readonly double Min;
-
-            /// <summary>
-            /// Gets or sets the row/column partial desired size of the <see cref="Grid"/>.
-            /// </summary>
-            public double DesiredSize;
-
-            /// <summary>
-            /// Gets or sets the row/column offered size that will be used to measure the children.
-            /// </summary>
-            public double OfferedSize;
-
-            /// <summary>
-            /// Gets or sets the star unit size if the <see cref="Type"/> is <see cref="GridUnitType.Star"/>.
-            /// </summary>
-            public double Stars;
-
-            /// <summary>
-            /// Gets the segment size unit type.
-            /// </summary>
-            public readonly GridUnitType Type;
-
-            public Segment(double offeredSize, double min, double max, GridUnitType type)
-            {
-                OriginalSize = 0;
-                Min = min;
-                Max = max;
-                DesiredSize = 0;
-                OfferedSize = offeredSize;
-                Stars = 0;
-                Type = type;
-            }
-        }
-
-        private struct GridNode
-        {
-            public readonly int Row;
-            public readonly int Column;
-            public readonly double Size;
-            public readonly Segment[,] Matrix;
-
-            public GridNode(Segment[,] matrix, int row, int col, double size)
-            {
-                Matrix = matrix;
-                Row = row;
-                Column = col;
-                Size = size;
-            }
-        }
-
-        private class GridWalker
-        {
-            public GridWalker(Grid grid, Segment[,] rowMatrix, Segment[,] colMatrix)
-            {
-                int rowMatrixDim = rowMatrix.GetLength(0);
-                int colMatrixDim = colMatrix.GetLength(0);
-
-                foreach (Control child in grid.Children)
-                {
-                    bool starCol = false;
-                    bool starRow = false;
-                    bool autoCol = false;
-                    bool autoRow = false;
-
-                    int col = Math.Min(GetColumn(child), colMatrixDim - 1);
-                    int row = Math.Min(GetRow(child), rowMatrixDim - 1);
-                    int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - 1);
-                    int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - 1);
-
-                    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;
-                    }
-
-                    HasAutoAuto |= autoRow && autoCol && !starRow && !starCol;
-                    HasStarAuto |= starRow && autoCol;
-                    HasAutoStar |= autoRow && starCol;
-                }
-            }
-
-            public bool HasAutoAuto { get; }
-
-            public bool HasStarAuto { get; }
-
-            public bool HasAutoStar { get; }
-        }
     }
-}
+}

+ 19 - 3
src/Avalonia.Controls/Utils/GridLayout.cs

@@ -20,7 +20,9 @@ namespace Avalonia.Controls.Utils
         internal GridLayout([NotNull] ColumnDefinitions columns)
         {
             if (columns == null) throw new ArgumentNullException(nameof(columns));
-            _conventions = columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList();
+            _conventions = columns.Count == 0
+                ? new List<LengthConvention> { new LengthConvention() }
+                : columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList();
         }
 
         /// <summary>
@@ -30,7 +32,9 @@ namespace Avalonia.Controls.Utils
         internal GridLayout([NotNull] RowDefinitions rows)
         {
             if (rows == null) throw new ArgumentNullException(nameof(rows));
-            _conventions = rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList();
+            _conventions = rows.Count == 0
+                ? new List<LengthConvention> { new LengthConvention() }
+                : rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList();
         }
 
         /// <summary>
@@ -49,7 +53,8 @@ namespace Avalonia.Controls.Utils
         /// Gets all the length conventions that come from the grid children.
         /// </summary>
         [NotNull]
-        private readonly List<AdditionalLengthConvention> _additionalConventions = new List<AdditionalLengthConvention>();
+        private readonly List<AdditionalLengthConvention> _additionalConventions =
+            new List<AdditionalLengthConvention>();
 
         /// <summary>
         /// Some elements are not only in a single grid cell, they have one or more column/row spans,
@@ -451,6 +456,7 @@ namespace Avalonia.Controls.Utils
             {
                 return;
             }
+
             var measureLength = 0.0;
             for (var i = 0; i < lengthList.Count; i++)
             {
@@ -474,6 +480,16 @@ namespace Avalonia.Controls.Utils
         /// </summary>
         internal class LengthConvention : ICloneable
         {
+            /// <summary>
+            /// Initialize a new instance of <see cref="LengthConvention"/>.
+            /// </summary>
+            public LengthConvention()
+            {
+                Length = new GridLength(1.0, GridUnitType.Star);
+                MinLength = 0.0;
+                MaxLength = double.PositiveInfinity;
+            }
+
             /// <summary>
             /// Initialize a new instance of <see cref="LengthConvention"/>.
             /// </summary>

+ 1 - 0
tests/Avalonia.Controls.UnitTests/GridMocks.cs

@@ -16,6 +16,7 @@ namespace Avalonia.Controls.UnitTests
         internal static Grid New(Size measure = default, Size arrange = default)
         {
             var grid = new Grid();
+            grid.Children.Add(new Border());
             grid.Measure(measure == default ? new Size(double.PositiveInfinity, double.PositiveInfinity) : measure);
             grid.Arrange(new Rect(default, arrange == default ? grid.DesiredSize : arrange));
             return grid;

+ 9 - 16
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -1,13 +1,6 @@
 // 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;
 
 namespace Avalonia.Controls.UnitTests
@@ -72,26 +65,26 @@ namespace Avalonia.Controls.UnitTests
         }
 
         [Fact]
-        public void Layout_PixelRowColumn_BoundsCorrect()
+        public void Layout_EmptyColumnRow_LayoutLikeANormalPanel()
         {
             // Arrange & Action
-            var rowGrid = GridMock.New(new RowDefinitions("100,200,300"));
-            var columnGrid = GridMock.New(new ColumnDefinitions("50,100,150"));
+            var grid = GridMock.New(arrange: new Size(600, 200));
 
             // Assert
-            GridAssert.ChildrenHeight(rowGrid, 100, 200, 300);
-            GridAssert.ChildrenWidth(columnGrid, 50, 100, 150);
+            GridAssert.ChildrenWidth(grid, 600);
+            GridAssert.ChildrenHeight(grid, 200);
         }
 
         [Fact]
-        public void Layout_NoRowColumn_BoundsCorrect()
+        public void Layout_PixelRowColumn_BoundsCorrect()
         {
             // Arrange & Action
-            var grid = GridMock.New(arrange: new Size(600, 200));
+            var rowGrid = GridMock.New(new RowDefinitions("100,200,300"));
+            var columnGrid = GridMock.New(new ColumnDefinitions("50,100,150"));
 
             // Assert
-            GridAssert.ChildrenHeight(grid, 600);
-            GridAssert.ChildrenWidth(grid, 200);
+            GridAssert.ChildrenHeight(rowGrid, 100, 200, 300);
+            GridAssert.ChildrenWidth(columnGrid, 50, 100, 150);
         }
 
         [Fact]