diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index 5991d61fb8..a72b5367b6 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -186,6 +186,9 @@ namespace Avalonia.Controls
element.SetValue(RowSpanProperty, value);
}
+ private GridLayout.MeasureResult _columnMeasureCache;
+ private GridLayout.MeasureResult _rowMeasureCache;
+
///
/// Measures the grid.
///
@@ -193,97 +196,45 @@ namespace Avalonia.Controls
/// The desired size of the control.
protected override Size MeasureOverride(Size constraint)
{
- // +------- 1. Prepare the children status -------+
- // + +
- // +------- ------------------------------ -------+
+ var measureCache = new Dictionary();
+ var (safeColumns, safeRows) = GetSafeColumnRows();
- // Normalize the column/columnspan and row/rowspan.
- var columnCount = ColumnDefinitions.Count;
- var rowCount = RowDefinitions.Count;
- var safeColumns = Children.OfType().ToDictionary(child => child,
- child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child)));
- var safeRows = Children.OfType().ToDictionary(child => child,
- child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child)));
-
- // +------- 2. -------+
- // + +
- // +------- ------------------------------ -------+
-
- // Find out the children that should be Measure first (those rows/columns are Auto size.)
var columnLayout = new GridLayout(ColumnDefinitions);
var rowLayout = new GridLayout(RowDefinitions);
- var autoSizeColumns = columnLayout.Prepare();
- var autoSizeRows = rowLayout.Prepare();
-
- foreach (var pair in safeColumns)
- {
- var child = pair.Key;
- var (column, columnSpan) = pair.Value;
- var columnLast = column + columnSpan - 1;
- if (autoSizeColumns.Contains(columnLast))
- {
-
- }
- }
+ columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width);
+ rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height);
- // Calculate row height list and column width list.
- var widthList = columnLayout.Measure(constraint.Width);
- var heightList = rowLayout.Measure(constraint.Height);
+ var columnResult = columnLayout.Measure(constraint.Width);
+ var rowResult = rowLayout.Measure(constraint.Height);
- // Calculate the available width list and height list for every child.
- var childrenAvailableWidths = Children.OfType().ToDictionary(child => child, child =>
- {
- var (column, columnSpan) = GetSafeSpan(widthList.Count, GetColumn(child), GetColumnSpan(child));
- return Span(widthList, column, columnSpan).Sum();
- });
- var childrenAvailableHeights = Children.OfType().ToDictionary(child => child, child =>
+ foreach (var child in Children.OfType())
{
- var (row, rowSpan) = GetSafeSpan(heightList.Count, GetRow(child), GetRowSpan(child));
- return Span(heightList, row, rowSpan).Sum();
- });
+ var (column, columnSpan) = safeColumns[child];
+ var (row, rowSpan) = safeRows[child];
+ var width = Enumerable.Range(column, columnSpan)
+ .Select(x => columnResult.LengthList[x].Length.Value).Sum();
+ var height = Enumerable.Range(row, rowSpan)
+ .Select(x => rowResult.LengthList[x].Length.Value).Sum();
- // Measure the children.
- var availableWidth = constraint.Width;
- var availableHeight = constraint.Height;
- var desiredWidth = 0.0;
- var desiredHeight = 0.0;
- var sortedChildren = Children.OfType()
- .OrderBy(GetColumn).ThenBy(GetRow)
- .ToDictionary(child => child, child => (GetColumn(child), GetRow(child)));
+ MeasureOnce(child, new Size(width, height));
+ }
- var currentDesiredWidth = 0.0;
- var currentDesiredHeight = 0.0;
- var currentColumn = 0;
- var currentRow = 0;
- foreach (var pair in sortedChildren)
- {
- var child = pair.Key;
- var (column, row) = pair.Value;
- child.Measure(new Size(childrenAvailableWidths[child], childrenAvailableHeights[child]));
- var desiredSize = child.DesiredSize;
+ _columnMeasureCache = columnResult;
+ _rowMeasureCache = rowResult;
+ return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
- if (column == currentColumn)
- {
- currentDesiredWidth = Math.Max(desiredSize.Width, currentDesiredWidth);
- }
- else
- {
- currentDesiredWidth = desiredSize.Width;
- currentColumn = column;
- availableWidth -= desiredSize.Width;
- desiredHeight -= desiredSize.Width;
- }
-
- if (availableWidth < desiredSize.Width)
+ Size MeasureOnce(Control child, Size size)
+ {
+ if (measureCache.TryGetValue(child, out var desiredSize))
{
-
+ return desiredSize;
}
- availableHeight -= desiredSize.Height;
- desiredHeight -= desiredSize.Width;
+ child.Measure(size);
+ desiredSize = child.DesiredSize;
+ measureCache[child] = desiredSize;
+ return desiredSize;
}
-
- return constraint;
}
///
@@ -293,25 +244,39 @@ namespace Avalonia.Controls
/// The space taken.
protected override Size ArrangeOverride(Size finalSize)
{
- // Calculate row height list and column width list.
- var rowLayout = new GridLayout(RowDefinitions);
+ var (safeColumns, safeRows) = GetSafeColumnRows();
+
var columnLayout = new GridLayout(ColumnDefinitions);
- var heightList = rowLayout.Measure(finalSize.Height);
- var widthList = columnLayout.Measure(finalSize.Width);
+ var rowLayout = new GridLayout(RowDefinitions);
- var rowMeasure = new Dictionary();
- var columnMeasure = new Dictionary();
- foreach (var child in Children.OfType().OrderBy(GetRow))
- {
- var (row, rowSpan) = GetSafeSpan(heightList.Count, GetRow(child), GetRowSpan(child));
- rowMeasure.Add(child, (row, rowSpan));
- }
- foreach (var child in Children.OfType().OrderBy(GetColumn))
+ var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache);
+ var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache);
+
+ foreach (var child in Children.OfType())
{
- var (column, columnSpan) = GetSafeSpan(widthList.Count, GetColumn(child), GetColumnSpan(child));
- columnMeasure.Add(child, (column, columnSpan));
+ var (column, columnSpan) = safeColumns[child];
+ var (row, rowSpan) = safeRows[child];
+ var width = Enumerable.Range(column, columnSpan)
+ .Select(x => columnResult.LengthList[x].Length.Value).Sum();
+ var height = Enumerable.Range(row, rowSpan)
+ .Select(x => rowResult.LengthList[x].Length.Value).Sum();
+
+ child.Arrange(new Rect(0, 0, width, height));
}
+ return finalSize;
+ }
+
+ private (Dictionary safeColumns,
+ Dictionary safeRows) GetSafeColumnRows()
+ {
+ var columnCount = ColumnDefinitions.Count;
+ var rowCount = RowDefinitions.Count;
+ var safeColumns = Children.OfType().ToDictionary(child => child,
+ child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child)));
+ var safeRows = Children.OfType().ToDictionary(child => child,
+ child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child)));
+ return (safeColumns, safeRows);
}
///
@@ -340,32 +305,6 @@ namespace Avalonia.Controls
return (index, span);
}
- ///
- /// Return part of a list from the specified start index and its span length.
- /// If Avalonia upgrade .NET Core to 2.1 and introduce C# 7.2, we can use Span to do this.
- ///
- [Pure]
- private static IEnumerable Span(IList list, int index, int span)
- {
-#if DEBUG
- // We do not verify arguments in RELEASE because this is a private method,
- // and we must write the correct code before publishing.
- if (index >= list.Count) throw new ArgumentOutOfRangeException(nameof(index));
- if (span <= 1) throw new ArgumentException("Argument span should not be smaller than 1.", nameof(span));
- if (index + span > list.Count) throw new ArgumentOutOfRangeException(nameof(index));
-#endif
- if (span == 1)
- {
- yield return list[index];
- yield break;
- }
-
- for (var i = index; i < index + span; i++)
- {
- yield return list[i];
- }
- }
-
private static double Clamp(double val, double min, double max)
{
if (val < min)
diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs
index 55daf2c9ca..a872c01695 100644
--- a/src/Avalonia.Controls/Utils/GridLayout.cs
+++ b/src/Avalonia.Controls/Utils/GridLayout.cs
@@ -1,263 +1,328 @@
using System;
-using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
namespace Avalonia.Controls.Utils
{
+ // We have three kind of unit:
+ // - * means Star unit. It can be affected by min and max pixel length.
+ // - A means Auto unit. It can be affected by min/max pixel length and desired pixel length.
+ // - P means Pixel unit. It is fixed and can't be affected by any other values.
+ // Notice that some child stands not only one column/row and this affects desired length.
+ // Desired length behaviors like the min pixel length but:
+ // - This can only be determined after the Measure.
+ //
+ // This is an example indicates how this class stores data.
+ // +-----------------------------------------------------------+
+ // | * | A | * | P | A | * | P | * | * |
+ // +-----------------------------------------------------------+
+ // | min | min | | | min | | min max |
+ // |<- desired ->|
+ //
+ // During the measuring procedure:
+ // - * wants as much as possible space in range of min and max.
+ // - A wants as less as possible space in range of min/desired and max.
+ // - P wants a fix-size space.
+ // But during the arranging procedure:
+ // - * behaviors the same.
+ // - A wants as much as possible space in range of min/desired and max.
+ // - P behaviors the same.
+ //
+ ///
+ /// Contains algorithms that can help to measure and arrange a Grid.
+ ///
internal class GridLayout
{
- internal GridLayout(LengthDefinitions lengths)
+ internal GridLayout(ColumnDefinitions columns)
{
- _lengths = lengths;
+ _conventions = columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList();
}
- private readonly LengthDefinitions _lengths;
+ internal GridLayout(RowDefinitions rows)
+ {
+ _conventions = rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList();
+ }
+
+ private const double LayoutTolerance = 1.0 / 256.0;
+ private readonly List _conventions;
+ private readonly List _additionalConventions = new List();
///
- /// Find out which rows/columns should be measured first. These rows/columns are those that marked with "Auto" size.
- /// These "Auto" size rows/columns behavior like fix-size rows/columns but they can only be determined after Measure.
+ /// Some elements are not in a single grid cell, they have multiple column/row spans,
+ /// and these elements may affects the grid layout especially the measure procedure.
+ /// Append these elements into the convention list can help to layout them correctly through their desired size.
+ /// Only a small subset of grid children need to be measured before layout starts and they are called via the callback.
///
- /// The row/column numbers that should be Measure first.
- internal List Prepare()
+ ///
+ ///
+ ///
+ internal void AppendMeasureConventions(IDictionary source,
+ Func getDesiredLength)
{
- var lengths = _lengths;
- return Find().ToList();
-
- IEnumerable Find()
+ // M1/6. Find all the Auto length columns/rows.
+ // Only these columns/rows' layout can be affected by the children desired size.
+ var found = new Dictionary();
+ for (var i = 0; i < _conventions.Count; i++)
{
- for (var i = 0; i < lengths.Count; i++)
+ var index = i;
+ var convention = _conventions[index];
+ if (convention.Length.IsAuto)
{
- var unitType = lengths[i].Length.GridUnitType;
- if (unitType == GridUnitType.Auto)
+ foreach (var pair in source.Where(x =>
+ x.Value.index <= index && index < x.Value.index + x.Value.span))
{
- yield return i;
+ found[pair.Key] = pair.Value;
}
}
}
- }
- ///
- /// Try to calculate the lengths that will be used to measure the children.
- /// If the is not enough, we'll even not compress the measure length.
- /// So you'd better call first to find out the rows/columns that should be excluded first.
- ///
- ///
- /// The container length (width or height) excluding some rows/columns.
- /// Call first to find out the rows/columns that should be excluded.
- ///
- /// The lengths that can be used to measure the children.
- [Pure]
- internal List Measure(double containerLength)
- {
- var lengths = _lengths.Clone();
-
- // Exclude all the pixel lengths, so that we can calculate the star lengths.
- containerLength -= lengths
- .Where(x => x.Length.IsAbsolute)
- .Aggregate(0.0, (sum, add) => sum + add.Length.Value);
-
- // Aggregate the star count, so that we can determine the length of each star unit.
- var starCount = lengths
- .Where(x => x.Length.IsStar)
- .Aggregate(0.0, (sum, add) => sum + add.Length.Value);
- // There is no need to care the (starCount == 0). If this happens, we'll ignore all the stars.
- var starUnitLength = containerLength / starCount;
-
- // If there is no stars, just return all pixels.
- if (Equals(starCount, 0.0))
+ // Append these layout into the additional convention list.
+ foreach (var pair in found)
{
- return lengths.Select(x => x.Length.IsAuto ? double.PositiveInfinity : x.Length.Value).ToList();
+ var t = pair.Key;
+ var (index, span) = pair.Value;
+ var desiredLength = getDesiredLength(t);
+ if (Math.Abs(desiredLength) > LayoutTolerance)
+ {
+ _additionalConventions.Add(new AdditionalLengthConvention(index, span, desiredLength));
+ }
}
+ }
- // ---
- // 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)
+ internal MeasureResult Measure(double containerLength)
+ {
+ // Initial.
+ var conventions = _conventions.Select(x => x.Clone()).ToList();
+ var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
+ var constraint = containerLength;
+ double starUnitLength;
+
+ // M2/6. Exclude all the pixel lengths, so that we can calculate the star lengths.
+ constraint -= conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
+
+ // M3/6. Exclude all the * lengths that have reached min value.
+ var shouldTestStarMin = true;
+ while (shouldTestStarMin)
{
- foreach (var length in intermediateStarLengths)
+ var @fixed = false;
+ starUnitLength = constraint / starCount;
+ foreach (var convention in conventions.Where(x => x.Length.IsStar))
{
- // Find out if there is any length out of min to max.
- var (star, min, max) = (length.Length.Value, length.MinLength, length.MaxLength);
+ var (star, min) = (convention.Length.Value, convention.MinLength);
var starLength = star * starUnitLength;
- if (starLength < min || starLength > max)
+ if (starLength < min)
{
- // 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;
+ convention.Fix(min);
+ starLength = min;
+ constraint -= starLength;
starCount -= star;
- starUnitLength = containerLength / starCount;
+ @fixed = true;
break;
}
}
- // All lengths are in range, so that we have enough lengths to measure children.
- allInRange = true;
- foreach (var length in intermediateStarLengths)
+ shouldTestStarMin = @fixed;
+ }
+
+ // M4/6. Exclude all the Auto lengths that have not-zero desired size.
+ var shouldTestAuto = true;
+ while (shouldTestAuto)
+ {
+ var @fixed = false;
+ starUnitLength = constraint / starCount;
+ for (var i = 0; i < conventions.Count; i++)
{
- length.Update(length.Length.Value * starUnitLength);
+ var convention = conventions[i];
+ if (!convention.Length.IsAuto)
+ {
+ continue;
+ }
+
+ var index = i;
+ var more = 0.0;
+ foreach (var additional in _additionalConventions)
+ {
+ // If the additional conventions contains the Auto column/row, try to determine the Auto column/row length.
+ if (additional.Index <= index && index < additional.Index + additional.Span)
+ {
+ var starUnit = starUnitLength;
+ var min = Enumerable.Range(additional.Index, additional.Span)
+ .Select(x =>
+ {
+ var c = conventions[x];
+ if (c.Length.IsAbsolute) return c.Length.Value;
+ if (c.Length.IsStar) return c.Length.Value * starUnit;
+ return 0.0;
+ }).Sum();
+ more = Math.Max(additional.Min - min, more);
+ }
+ }
+
+ convention.Fix(more);
+ constraint -= more;
+ @fixed = true;
+ break;
}
+
+ shouldTestAuto = @fixed;
}
- // Return the modified lengths as measuring lengths.
- return lengths.Select(x =>
- x.Length.GridUnitType == GridUnitType.Auto
- ? double.PositiveInfinity
- : x.Length.Value).ToList();
+ // M5/6. Determine the desired length of the grid for current contaienr length. Its value stores in desiredLength.
+ // But if the container has infinite length, the grid desired length is stored in greedyDesiredLength.
+ var desiredLength = constraint >= 0.0 ? containerLength - constraint : containerLength;
+ var greedyDesiredLength = containerLength - constraint;
+
+ // M6/6. Expand all the left stars. These stars have no conventions or only have max value so they can be expanded from zero to constrant.
+ var dynamicConvention = ExpandStars(conventions, containerLength);
+
+ // Stores the measure result.
+ return new MeasureResult(containerLength, desiredLength, greedyDesiredLength, conventions, dynamicConvention);
}
- ///
- /// Try to calculate the lengths that will be used to measure the children.
- /// If the is not enough, we'll even not compress the measure length.
- ///
- /// The container length, width or height.
- /// The lengths that can be used to measure the children.
- [Pure]
- internal List Arrange(double containerLength)
+ public ArrangeResult Arrange(double finalLength, MeasureResult measure)
{
- 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))
+ // If the arrange final length does not equal to the measure length, we should measure again.
+ if (finalLength - measure.ContainerLength > LayoutTolerance)
+ {
+ // If the final length is larger, we will rerun the whole measure.
+ measure = Measure(finalLength);
+ }
+ else if (finalLength - measure.ContainerLength < -LayoutTolerance)
{
- return lengths.Select(x => x.Length.IsAuto ? double.PositiveInfinity : x.Length.Value).ToList();
+ // If the final length is smaller, we measure the M6/6 procedure only.
+ var dynamicConvention = ExpandStars(measure.LeanLengthList, measure.ContainerLength);
+ measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength,
+ measure.LeanLengthList, dynamicConvention);
}
- // ---
- // Warning! The code below will start to change the lengths item value.
- // ---
+ return new ArrangeResult(measure.LengthList);
+ }
+
+ [Pure]
+ private static List ExpandStars(IEnumerable conventions, double constraint)
+ {
+ // Initial.
+ var dynamicConvention = conventions.Select(x => x.Clone()).ToList();
+ constraint -= dynamicConvention.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
+ var starUnitLength = 0.0;
- // 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)
+ // M6/6.
+ if (constraint >= 0)
{
- foreach (var length in intermediateStarLengths)
+ var starCount = dynamicConvention.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
+
+ var shouldTestStarMax = true;
+ while (shouldTestStarMax)
{
- // 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)
+ var @fixed = false;
+ starUnitLength = constraint / starCount;
+ foreach (var convention in dynamicConvention.Where(x => x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength)))
{
- // 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)
+ var (star, max) = (convention.Length.Value, convention.MaxLength);
+ var starLength = star * starUnitLength;
+ if (starLength > max)
{
- length.Update(max);
+ convention.Fix(max);
starLength = max;
+ constraint -= starLength;
+ starCount -= star;
+ @fixed = true;
+ break;
}
-
- // 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);
+ shouldTestStarMax = @fixed;
}
}
- // Return the modified lengths as measuring lengths.
- return lengths.Select(x =>
- x.Length.GridUnitType == GridUnitType.Auto
- ? double.PositiveInfinity
- : x.Length.Value).ToList();
+ foreach (var convention in dynamicConvention.Where(x => x.Length.IsStar))
+ {
+ convention.Fix(starUnitLength * convention.Length.Value);
+ }
+
+ Debug.Assert(dynamicConvention.All(x => x.Length.IsAbsolute));
+
+ return dynamicConvention;
}
- internal class LengthDefinitions : IEnumerable, ICloneable
+ internal class LengthConvention : ICloneable
{
- private readonly List _lengths;
-
- private LengthDefinitions(IEnumerable lengths)
+ public LengthConvention(GridLength length, double minLength, double maxLength)
{
- _lengths = lengths.ToList();
+ Length = length;
+ MinLength = minLength;
+ MaxLength = maxLength;
+ if (length.IsAbsolute)
+ {
+ _isFixed = true;
+ }
}
- public LengthDefinition this[int index] => _lengths[index];
+ internal GridLength Length { get; private set; }
+ internal double MinLength { get; }
+ internal double MaxLength { get; }
- public int Count => _lengths.Count;
+ public void Fix(double pixel)
+ {
+ if (_isFixed)
+ {
+ throw new InvalidOperationException("Cannot fix the length convention if it is fixed.");
+ }
- public IEnumerator GetEnumerator() => _lengths.GetEnumerator();
+ Length = new GridLength(pixel);
+ _isFixed = true;
+ }
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ private bool _isFixed;
object ICloneable.Clone() => Clone();
- public LengthDefinitions Clone() => new LengthDefinitions(
- _lengths.Select(x => new LengthDefinition(x.Length, x.MinLength, x.MaxLength)));
+ internal LengthConvention Clone() => new LengthConvention(Length, MinLength, MaxLength);
+ }
- public static implicit operator LengthDefinitions(RowDefinitions rows)
- => new LengthDefinitions(rows.Select(x => (LengthDefinition) x));
+ internal struct AdditionalLengthConvention
+ {
+ public int Index { get; }
+ public int Span { get; }
+ public double Min { get; }
- public static implicit operator LengthDefinitions(ColumnDefinitions rows)
- => new LengthDefinitions(rows.Select(x => (LengthDefinition)x));
+ public AdditionalLengthConvention(int index, int span, double min)
+ {
+ Index = index;
+ Span = span;
+ Min = min;
+ }
}
- internal class LengthDefinition
+ internal class MeasureResult
{
- internal LengthDefinition(GridLength length, double minLength, double maxLength)
+ internal MeasureResult(double containerLength, double desiredLength, double greedyDesiredLength,
+ IReadOnlyList leanConventions, IReadOnlyList expandedConventions)
{
- Length = length;
- MinLength = minLength;
- MaxLength = maxLength;
+ ContainerLength = containerLength;
+ DesiredLength = desiredLength;
+ GreedyDesiredLength = greedyDesiredLength;
+ LeanLengthList = leanConventions;
+ LengthList = expandedConventions.Select(x => x.Length.Value).ToList();
}
- internal GridLength Length { get; private set; }
- internal double MinLength { get; }
- internal double MaxLength { get; }
-
- public static implicit operator LengthDefinition(RowDefinition row)
- => new LengthDefinition(row.Height, row.MinHeight, row.MaxHeight);
-
- public static implicit operator LengthDefinition(ColumnDefinition row)
- => new LengthDefinition(row.Width, row.MinWidth, row.MaxWidth);
+ public double ContainerLength { get; }
+ public double DesiredLength { get; }
+ public double GreedyDesiredLength { get; }
+ public IReadOnlyList LeanLengthList { get; }
+ public IReadOnlyList LengthList { get; }
+ }
- public void Update(double pixel)
+ internal class ArrangeResult
+ {
+ public ArrangeResult(IReadOnlyList lengthList)
{
- Length = new GridLength(pixel);
+ LengthList = lengthList;
}
+
+ public IReadOnlyList LengthList { get; }
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
index 984ec2f6f6..4ffdd24894 100644
--- a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
@@ -1,190 +1,88 @@
-using Avalonia.Controls.Utils;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Controls.Utils;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class GridLayoutTests
{
- [Fact]
- public void Measure_AllPixelLength_Correct()
+ [Theory]
+ [InlineData("100, 200, 300", 800d, 600d, new[] { 100d, 200d, 300d })]
+ public void MeasureArrange_AllPixelLength_Correct(string length, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
{
- // Arrange
- var layout = new GridLayout(new RowDefinitions("100,200,300"));
-
- // Action
- var measure = layout.Measure(800);
-
- // Assert
- Assert.Equal(measure, new [] { 100d, 200d, 300d });
+ TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
- [Fact]
- public void Measure_AllStarLength_Correct()
+ [Theory]
+ [InlineData("*,2*,3*", 600d, 0d, new[] { 100d, 200d, 300d })]
+ public void MeasureArrange_AllStarLength_Correct(string length, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
{
- // Arrange
- var layout = new GridLayout(new RowDefinitions("*,2*,3*"));
-
- // Action
- var measure = layout.Measure(600);
-
- // Assert
- Assert.Equal(measure, new [] { 100d, 200d, 300d });
+ TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
- [Fact]
- public void Measure_MixStarPixelLength_Correct()
+ [Theory]
+ [InlineData("100,2*,3*", 600d, 100d, new[] { 100d, 200d, 300d })]
+ public void MeasureArrange_MixStarPixelLength_Correct(string length, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
{
- // Arrange
- var layout = new GridLayout(new RowDefinitions("100,2*,3*"));
-
- // Action
- var measure = layout.Measure(600);
-
- // Assert
- Assert.Equal(measure, new [] { 100d, 200d, 300d });
+ TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
- [Fact]
- public void Measure_MixAutoPixelLength_Correct()
+ [Theory]
+ [InlineData("100,200,Auto", 600d, 300d, new[] { 100d, 200d, 0d })]
+ public void MeasureArrange_MixAutoPixelLength_Correct(string length, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
{
- // Arrange
- var layout = new GridLayout(new RowDefinitions("100,200,Auto"));
-
- // Action
- var measure = layout.Measure(600);
-
- // Assert
- Assert.Equal(measure, new [] { 100d, 200d, double.PositiveInfinity });
+ TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
- [Fact]
- public void Measure_MixAutoStarLength_Correct()
+ [Theory]
+ [InlineData("*,2*,Auto", 600d, 0d, new[] { 200d, 400d, 0d })]
+ public void MeasureArrange_MixAutoStarLength_Correct(string length, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
{
- // Arrange
- var layout = new GridLayout(new RowDefinitions("*,2*,Auto"));
-
- // Action
- var measure = layout.Measure(600);
-
- // Assert
- Assert.Equal(measure, new[] { 200d, 400d, double.PositiveInfinity });
+ TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
- [Fact]
- public void Measure_MixAutoStarPixelLength_Correct()
+ [Theory]
+ [InlineData("*,200,Auto", 600d, 200d, new[] { 400d, 200d, 0d })]
+ public void MeasureArrange_MixAutoStarPixelLength_Correct(string length, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
{
- // Arrange
- var layout = new GridLayout(new RowDefinitions("*,200,Auto"));
-
- // Action
- var measure = layout.Measure(600);
-
- // Assert
- Assert.Equal(measure, new[] { 400d, 200d, double.PositiveInfinity });
+ TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
[Fact]
- public void Measure_AllPixelLengthButNotEnough_Correct()
+ public void MeasureArrange_AllPixelLengthButNotEnough_Correct()
{
// Arrange
var layout = new GridLayout(new RowDefinitions("100,200,300"));
- // Action
+ // Measure - Action & Assert
var measure = layout.Measure(400);
+ Assert.Equal(new[] { 100d, 200d, 300d }, measure.LengthList);
- // Assert
- Assert.Equal(measure, new[] { 100d, 200d, 300d });
+ // Arrange - Action & Assert
}
- //[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"));
+ [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")]
+ private static void TestRowDefinitionsOnly(string length, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions(length));
- // // Action
- // var arrange = layout.Arrange(400);
+ // Measure - Action & Assert
+ var measure = layout.Measure(containerLength);
+ Assert.Equal(expectedDesiredLength, measure.DesiredLength);
+ Assert.Equal(expectedLengthList, measure.LengthList);
- // // Assert
- // Assert.Equal(arrange, new[] { 100d, 200d, 300d });
- //}
+ // Arrange - Action & Assert
+ var arrange = layout.Arrange(containerLength, measure);
+ Assert.Equal(expectedLengthList, arrange.LengthList);
+ }
}
}