From 2052d53d68b6f2a4a6d74128ff721ff7527869fb Mon Sep 17 00:00:00 2001 From: walterlv Date: Tue, 1 May 2018 16:19:49 +0800 Subject: [PATCH] Order the grid codes and make most unit test passed. --- src/Avalonia.Controls/Grid.cs | 443 ++---------------- src/Avalonia.Controls/Utils/GridLayout.cs | 22 +- .../Avalonia.Controls.UnitTests/GridMocks.cs | 1 + .../Avalonia.Controls.UnitTests/GridTests.cs | 25 +- 4 files changed, 80 insertions(+), 411 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index ed484ec378..8360b83fc4 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -48,10 +48,6 @@ namespace Avalonia.Controls private RowDefinitions _rowDefinitions; - private Segment[,] _rowMatrix; - - private Segment[,] _colMatrix; - /// /// Gets or sets the columns definitions for the grid. /// @@ -186,7 +182,16 @@ namespace Avalonia.Controls element.SetValue(RowSpanProperty, value); } + /// + /// Gets the result of last column measuring produce. + /// Use this result to reduce the arrange calculation. + /// private GridLayout.MeasureResult _columnMeasureCache; + + /// + /// Gets the result of last row measuring produce. + /// Use this result to reduce the arrange calculation. + /// private GridLayout.MeasureResult _rowMeasureCache; /// @@ -196,11 +201,32 @@ namespace Avalonia.Controls /// The desired size of the control. 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()) + { + 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(); 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 /// The space taken. 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()) + { + 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; } + /// + /// 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. + /// private (Dictionary safeColumns, Dictionary 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; - } - } - } - - /// - /// Stores the layout values of of of . - /// - private struct Segment - { - /// - /// Gets or sets the base size of this segment. - /// The value is from the user's code or from the stored measuring values. - /// - public double OriginalSize; - - /// - /// Gets the maximum size of this segment. - /// The value is from the user's code. - /// - public readonly double Max; - - /// - /// Gets the minimum size of this segment. - /// The value is from the user's code. - /// - public readonly double Min; - - /// - /// Gets or sets the row/column partial desired size of the . - /// - public double DesiredSize; - - /// - /// Gets or sets the row/column offered size that will be used to measure the children. - /// - public double OfferedSize; - - /// - /// Gets or sets the star unit size if the is . - /// - public double Stars; - - /// - /// Gets the segment size unit type. - /// - 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; } - } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs index eba23a3d8e..7f5051475a 100644 --- a/src/Avalonia.Controls/Utils/GridLayout.cs +++ b/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 { new LengthConvention() } + : columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList(); } /// @@ -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 { new LengthConvention() } + : rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList(); } /// @@ -49,7 +53,8 @@ namespace Avalonia.Controls.Utils /// Gets all the length conventions that come from the grid children. /// [NotNull] - private readonly List _additionalConventions = new List(); + private readonly List _additionalConventions = + new List(); /// /// 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 /// internal class LengthConvention : ICloneable { + /// + /// Initialize a new instance of . + /// + public LengthConvention() + { + Length = new GridLength(1.0, GridUnitType.Star); + MinLength = 0.0; + MaxLength = double.PositiveInfinity; + } + /// /// Initialize a new instance of . /// diff --git a/tests/Avalonia.Controls.UnitTests/GridMocks.cs b/tests/Avalonia.Controls.UnitTests/GridMocks.cs index a982882ad8..23b975207c 100644 --- a/tests/Avalonia.Controls.UnitTests/GridMocks.cs +++ b/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; diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index 7d649a5a07..4c79b7775b 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/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]