3 changed files with 353 additions and 451 deletions
@ -1,263 +1,328 @@ |
|||||
using System; |
using System; |
||||
using System.Collections; |
|
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
using System.Linq; |
using System.Linq; |
||||
using JetBrains.Annotations; |
using JetBrains.Annotations; |
||||
|
|
||||
namespace Avalonia.Controls.Utils |
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.
|
||||
|
//
|
||||
|
/// <summary>
|
||||
|
/// Contains algorithms that can help to measure and arrange a Grid.
|
||||
|
/// </summary>
|
||||
internal class GridLayout |
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<LengthConvention> _conventions; |
||||
|
private readonly List<AdditionalLengthConvention> _additionalConventions = new List<AdditionalLengthConvention>(); |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Find out which rows/columns should be measured first. These rows/columns are those that marked with "Auto" size.<para/>
|
/// Some elements are not in a single grid cell, they have multiple column/row spans,
|
||||
/// These "Auto" size rows/columns behavior like fix-size rows/columns but they can only be determined after Measure.
|
/// and these elements may affects the grid layout especially the measure procedure.<para/>
|
||||
|
/// 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 <paramref name="getDesiredLength"/> callback.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <returns>The row/column numbers that should be Measure first.</returns>
|
/// <typeparam name="T"></typeparam>
|
||||
internal List<int> Prepare() |
/// <param name="source"></param>
|
||||
|
/// <param name="getDesiredLength"></param>
|
||||
|
internal void AppendMeasureConventions<T>(IDictionary<T, (int index, int span)> source, |
||||
|
Func<T, double> getDesiredLength) |
||||
{ |
{ |
||||
var lengths = _lengths; |
// M1/6. Find all the Auto length columns/rows.
|
||||
return Find().ToList(); |
// Only these columns/rows' layout can be affected by the children desired size.
|
||||
|
var found = new Dictionary<T, (int index, int span)>(); |
||||
IEnumerable<int> Find() |
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; |
foreach (var pair in source.Where(x => |
||||
if (unitType == GridUnitType.Auto) |
x.Value.index <= index && index < x.Value.index + x.Value.span)) |
||||
{ |
{ |
||||
yield return i; |
found[pair.Key] = pair.Value; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
} |
|
||||
|
|
||||
/// <summary>
|
// Append these layout into the additional convention list.
|
||||
/// Try to calculate the lengths that will be used to measure the children.<para/>
|
foreach (var pair in found) |
||||
/// 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(); |
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)); |
||||
|
} |
||||
} |
} |
||||
|
} |
||||
|
|
||||
// ---
|
internal MeasureResult Measure(double containerLength) |
||||
// Warning! The code below will start to change the lengths item value.
|
{ |
||||
// ---
|
// Initial.
|
||||
|
var conventions = _conventions.Select(x => x.Clone()).ToList(); |
||||
// Exclude the star unit if its min/max length range does not contain the calculated star length.
|
var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value); |
||||
var intermediateStarLengths = lengths.Where(x => x.Length.IsStar).ToList(); |
var constraint = containerLength; |
||||
// Indicate whether all star lengths are in range of min and max or not.
|
double starUnitLength; |
||||
var allInRange = false; |
|
||||
while (!allInRange) |
// 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) = (convention.Length.Value, convention.MinLength); |
||||
var (star, min, max) = (length.Length.Value, length.MinLength, length.MaxLength); |
|
||||
var starLength = star * starUnitLength; |
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.
|
convention.Fix(min); |
||||
if (starLength < min) |
starLength = min; |
||||
{ |
constraint -= starLength; |
||||
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; |
starCount -= star; |
||||
starUnitLength = containerLength / starCount; |
@fixed = true; |
||||
break; |
break; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// All lengths are in range, so that we have enough lengths to measure children.
|
shouldTestStarMin = @fixed; |
||||
allInRange = true; |
} |
||||
foreach (var length in intermediateStarLengths) |
|
||||
|
// 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.
|
// M5/6. Determine the desired length of the grid for current contaienr length. Its value stores in desiredLength.
|
||||
return lengths.Select(x => |
// But if the container has infinite length, the grid desired length is stored in greedyDesiredLength.
|
||||
x.Length.GridUnitType == GridUnitType.Auto |
var desiredLength = constraint >= 0.0 ? containerLength - constraint : containerLength; |
||||
? double.PositiveInfinity |
var greedyDesiredLength = containerLength - constraint; |
||||
: x.Length.Value).ToList(); |
|
||||
|
// 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); |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
public ArrangeResult Arrange(double finalLength, MeasureResult measure) |
||||
/// 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.
|
|
||||
/// </summary>
|
|
||||
/// <param name="containerLength">The container length, width or height.</param>
|
|
||||
/// <returns>The lengths that can be used to measure the children.</returns>
|
|
||||
[Pure] |
|
||||
internal List<double> Arrange(double containerLength) |
|
||||
{ |
{ |
||||
var lengths = _lengths.Clone(); |
// If the arrange final length does not equal to the measure length, we should measure again.
|
||||
|
if (finalLength - measure.ContainerLength > LayoutTolerance) |
||||
// Exclude all the pixel lengths, so that we can calculate the star lengths.
|
{ |
||||
containerLength -= lengths |
// If the final length is larger, we will rerun the whole measure.
|
||||
.Where(x => x.Length.IsAbsolute) |
measure = Measure(finalLength); |
||||
.Aggregate(0.0, (sum, add) => sum + add.Length.Value); |
} |
||||
|
else if (finalLength - measure.ContainerLength < -LayoutTolerance) |
||||
// 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(); |
// 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); |
||||
} |
} |
||||
|
|
||||
// ---
|
return new ArrangeResult(measure.LengthList); |
||||
// Warning! The code below will start to change the lengths item value.
|
} |
||||
// ---
|
|
||||
|
[Pure] |
||||
|
private static List<LengthConvention> ExpandStars(IEnumerable<LengthConvention> 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.
|
// M6/6.
|
||||
var intermediateStarLengths = lengths.Where(x => x.Length.IsStar).ToList(); |
if (constraint >= 0) |
||||
// Indicate whether all star lengths are in range of min and max or not.
|
|
||||
var allInRange = false; |
|
||||
while (!allInRange) |
|
||||
{ |
{ |
||||
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 @fixed = false; |
||||
var (star, min, max) = (length.Length.Value, length.MinLength, length.MaxLength); |
starUnitLength = constraint / starCount; |
||||
var starLength = star * starUnitLength; |
foreach (var convention in dynamicConvention.Where(x => x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength))) |
||||
if (starLength < min || starLength > max) |
|
||||
{ |
{ |
||||
// If the star length is out of min to max, change it to a pixel unit.
|
var (star, max) = (convention.Length.Value, convention.MaxLength); |
||||
if (starLength < min) |
var starLength = star * starUnitLength; |
||||
{ |
if (starLength > max) |
||||
length.Update(min); |
|
||||
starLength = min; |
|
||||
} |
|
||||
else if (starLength > max) |
|
||||
{ |
{ |
||||
length.Update(max); |
convention.Fix(max); |
||||
starLength = 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.
|
shouldTestStarMax = @fixed; |
||||
allInRange = true; |
|
||||
foreach (var length in intermediateStarLengths) |
|
||||
{ |
|
||||
length.Update(length.Length.Value * starUnitLength); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// Return the modified lengths as measuring lengths.
|
foreach (var convention in dynamicConvention.Where(x => x.Length.IsStar)) |
||||
return lengths.Select(x => |
{ |
||||
x.Length.GridUnitType == GridUnitType.Auto |
convention.Fix(starUnitLength * convention.Length.Value); |
||||
? double.PositiveInfinity |
} |
||||
: x.Length.Value).ToList(); |
|
||||
|
Debug.Assert(dynamicConvention.All(x => x.Length.IsAbsolute)); |
||||
|
|
||||
|
return dynamicConvention; |
||||
} |
} |
||||
|
|
||||
internal class LengthDefinitions : IEnumerable<LengthDefinition>, ICloneable |
internal class LengthConvention : ICloneable |
||||
{ |
{ |
||||
private readonly List<LengthDefinition> _lengths; |
public LengthConvention(GridLength length, double minLength, double maxLength) |
||||
|
|
||||
private LengthDefinitions(IEnumerable<LengthDefinition> lengths) |
|
||||
{ |
{ |
||||
_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<LengthDefinition> GetEnumerator() => _lengths.GetEnumerator(); |
Length = new GridLength(pixel); |
||||
|
_isFixed = true; |
||||
|
} |
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
private bool _isFixed; |
||||
|
|
||||
object ICloneable.Clone() => Clone(); |
object ICloneable.Clone() => Clone(); |
||||
|
|
||||
public LengthDefinitions Clone() => new LengthDefinitions( |
internal LengthConvention Clone() => new LengthConvention(Length, MinLength, MaxLength); |
||||
_lengths.Select(x => new LengthDefinition(x.Length, x.MinLength, x.MaxLength))); |
} |
||||
|
|
||||
public static implicit operator LengthDefinitions(RowDefinitions rows) |
internal struct AdditionalLengthConvention |
||||
=> new LengthDefinitions(rows.Select(x => (LengthDefinition) x)); |
{ |
||||
|
public int Index { get; } |
||||
|
public int Span { get; } |
||||
|
public double Min { get; } |
||||
|
|
||||
public static implicit operator LengthDefinitions(ColumnDefinitions rows) |
public AdditionalLengthConvention(int index, int span, double min) |
||||
=> new LengthDefinitions(rows.Select(x => (LengthDefinition)x)); |
{ |
||||
|
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<LengthConvention> leanConventions, IReadOnlyList<LengthConvention> expandedConventions) |
||||
{ |
{ |
||||
Length = length; |
ContainerLength = containerLength; |
||||
MinLength = minLength; |
DesiredLength = desiredLength; |
||||
MaxLength = maxLength; |
GreedyDesiredLength = greedyDesiredLength; |
||||
|
LeanLengthList = leanConventions; |
||||
|
LengthList = expandedConventions.Select(x => x.Length.Value).ToList(); |
||||
} |
} |
||||
|
|
||||
internal GridLength Length { get; private set; } |
public double ContainerLength { get; } |
||||
internal double MinLength { get; } |
public double DesiredLength { get; } |
||||
internal double MaxLength { get; } |
public double GreedyDesiredLength { get; } |
||||
|
public IReadOnlyList<LengthConvention> LeanLengthList { get; } |
||||
public static implicit operator LengthDefinition(RowDefinition row) |
public IReadOnlyList<double> LengthList { get; } |
||||
=> new LengthDefinition(row.Height, row.MinHeight, row.MaxHeight); |
} |
||||
|
|
||||
public static implicit operator LengthDefinition(ColumnDefinition row) |
|
||||
=> new LengthDefinition(row.Width, row.MinWidth, row.MaxWidth); |
|
||||
|
|
||||
public void Update(double pixel) |
internal class ArrangeResult |
||||
|
{ |
||||
|
public ArrangeResult(IReadOnlyList<double> lengthList) |
||||
{ |
{ |
||||
Length = new GridLength(pixel); |
LengthList = lengthList; |
||||
} |
} |
||||
|
|
||||
|
public IReadOnlyList<double> LengthList { get; } |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,190 +1,88 @@ |
|||||
using Avalonia.Controls.Utils; |
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Controls.Utils; |
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
namespace Avalonia.Controls.UnitTests |
namespace Avalonia.Controls.UnitTests |
||||
{ |
{ |
||||
public class GridLayoutTests |
public class GridLayoutTests |
||||
{ |
{ |
||||
[Fact] |
[Theory] |
||||
public void Measure_AllPixelLength_Correct() |
[InlineData("100, 200, 300", 800d, 600d, new[] { 100d, 200d, 300d })] |
||||
|
public void MeasureArrange_AllPixelLength_Correct(string length, double containerLength, |
||||
|
double expectedDesiredLength, IList<double> expectedLengthList) |
||||
{ |
{ |
||||
// Arrange
|
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); |
||||
var layout = new GridLayout(new RowDefinitions("100,200,300")); |
|
||||
|
|
||||
// Action
|
|
||||
var measure = layout.Measure(800); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(measure, new [] { 100d, 200d, 300d }); |
|
||||
} |
} |
||||
|
|
||||
[Fact] |
[Theory] |
||||
public void Measure_AllStarLength_Correct() |
[InlineData("*,2*,3*", 600d, 0d, new[] { 100d, 200d, 300d })] |
||||
|
public void MeasureArrange_AllStarLength_Correct(string length, double containerLength, |
||||
|
double expectedDesiredLength, IList<double> expectedLengthList) |
||||
{ |
{ |
||||
// Arrange
|
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); |
||||
var layout = new GridLayout(new RowDefinitions("*,2*,3*")); |
|
||||
|
|
||||
// Action
|
|
||||
var measure = layout.Measure(600); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(measure, new [] { 100d, 200d, 300d }); |
|
||||
} |
} |
||||
|
|
||||
[Fact] |
[Theory] |
||||
public void Measure_MixStarPixelLength_Correct() |
[InlineData("100,2*,3*", 600d, 100d, new[] { 100d, 200d, 300d })] |
||||
|
public void MeasureArrange_MixStarPixelLength_Correct(string length, double containerLength, |
||||
|
double expectedDesiredLength, IList<double> expectedLengthList) |
||||
{ |
{ |
||||
// Arrange
|
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); |
||||
var layout = new GridLayout(new RowDefinitions("100,2*,3*")); |
|
||||
|
|
||||
// Action
|
|
||||
var measure = layout.Measure(600); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(measure, new [] { 100d, 200d, 300d }); |
|
||||
} |
} |
||||
|
|
||||
[Fact] |
[Theory] |
||||
public void Measure_MixAutoPixelLength_Correct() |
[InlineData("100,200,Auto", 600d, 300d, new[] { 100d, 200d, 0d })] |
||||
|
public void MeasureArrange_MixAutoPixelLength_Correct(string length, double containerLength, |
||||
|
double expectedDesiredLength, IList<double> expectedLengthList) |
||||
{ |
{ |
||||
// Arrange
|
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); |
||||
var layout = new GridLayout(new RowDefinitions("100,200,Auto")); |
|
||||
|
|
||||
// Action
|
|
||||
var measure = layout.Measure(600); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(measure, new [] { 100d, 200d, double.PositiveInfinity }); |
|
||||
} |
} |
||||
|
|
||||
[Fact] |
[Theory] |
||||
public void Measure_MixAutoStarLength_Correct() |
[InlineData("*,2*,Auto", 600d, 0d, new[] { 200d, 400d, 0d })] |
||||
|
public void MeasureArrange_MixAutoStarLength_Correct(string length, double containerLength, |
||||
|
double expectedDesiredLength, IList<double> expectedLengthList) |
||||
{ |
{ |
||||
// Arrange
|
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); |
||||
var layout = new GridLayout(new RowDefinitions("*,2*,Auto")); |
|
||||
|
|
||||
// Action
|
|
||||
var measure = layout.Measure(600); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(measure, new[] { 200d, 400d, double.PositiveInfinity }); |
|
||||
} |
} |
||||
|
|
||||
[Fact] |
[Theory] |
||||
public void Measure_MixAutoStarPixelLength_Correct() |
[InlineData("*,200,Auto", 600d, 200d, new[] { 400d, 200d, 0d })] |
||||
|
public void MeasureArrange_MixAutoStarPixelLength_Correct(string length, double containerLength, |
||||
|
double expectedDesiredLength, IList<double> expectedLengthList) |
||||
{ |
{ |
||||
// Arrange
|
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); |
||||
var layout = new GridLayout(new RowDefinitions("*,200,Auto")); |
|
||||
|
|
||||
// Action
|
|
||||
var measure = layout.Measure(600); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(measure, new[] { 400d, 200d, double.PositiveInfinity }); |
|
||||
} |
} |
||||
|
|
||||
[Fact] |
[Fact] |
||||
public void Measure_AllPixelLengthButNotEnough_Correct() |
public void MeasureArrange_AllPixelLengthButNotEnough_Correct() |
||||
{ |
{ |
||||
// Arrange
|
// Arrange
|
||||
var layout = new GridLayout(new RowDefinitions("100,200,300")); |
var layout = new GridLayout(new RowDefinitions("100,200,300")); |
||||
|
|
||||
// Action
|
// Measure - Action & Assert
|
||||
var measure = layout.Measure(400); |
var measure = layout.Measure(400); |
||||
|
Assert.Equal(new[] { 100d, 200d, 300d }, measure.LengthList); |
||||
|
|
||||
// Assert
|
// Arrange - Action & Assert
|
||||
Assert.Equal(measure, new[] { 100d, 200d, 300d }); |
|
||||
} |
} |
||||
|
|
||||
//[Fact]
|
[SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")] |
||||
//public void Arrange_AllPixelLength_Correct()
|
private static void TestRowDefinitionsOnly(string length, double containerLength, |
||||
//{
|
double expectedDesiredLength, IList<double> expectedLengthList) |
||||
// // Arrange
|
{ |
||||
// var layout = new GridLayout(new RowDefinitions("100,200,300"));
|
// Arrange
|
||||
|
var layout = new GridLayout(new RowDefinitions(length)); |
||||
// // 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
|
// Measure - Action & Assert
|
||||
// var arrange = layout.Arrange(400);
|
var measure = layout.Measure(containerLength); |
||||
|
Assert.Equal(expectedDesiredLength, measure.DesiredLength); |
||||
|
Assert.Equal(expectedLengthList, measure.LengthList); |
||||
|
|
||||
// // Assert
|
// Arrange - Action & Assert
|
||||
// Assert.Equal(arrange, new[] { 100d, 200d, 300d });
|
var arrange = layout.Arrange(containerLength, measure); |
||||
//}
|
Assert.Equal(expectedLengthList, arrange.LengthList); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue