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]