Browse Source

Add a new version of Grid layout. and its performance may be better than the original one.

pull/1517/head
walterlv 9 years ago
parent
commit
b9d71860ff
  1. 144
      src/Avalonia.Controls/Utils/GridLayout.cs
  2. 99
      tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs

144
src/Avalonia.Controls/Utils/GridLayout.cs

@ -0,0 +1,144 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace Avalonia.Controls.Utils
{
internal class GridLayout
{
internal GridLayout(LengthDefinitions lengths)
{
_lengths = lengths;
}
private readonly LengthDefinitions _lengths;
/// <summary>
/// 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> 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();
}
// ---
// 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)
{
foreach (var length in intermediateStarLengths)
{
// 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)
{
// 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;
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);
}
}
// Return the modified lengths as measuring lengths.
return lengths.Select(x =>
x.Length.GridUnitType == GridUnitType.Auto
? double.PositiveInfinity
: x.Length.Value).ToList();
}
internal class LengthDefinitions : IEnumerable<LengthDefinition>, ICloneable
{
private readonly List<LengthDefinition> _lengths;
private LengthDefinitions(IEnumerable<LengthDefinition> lengths)
{
_lengths = lengths.ToList();
}
public IEnumerator<LengthDefinition> GetEnumerator() => _lengths.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
object ICloneable.Clone() => Clone();
public LengthDefinitions Clone() => new LengthDefinitions(
_lengths.Select(x => new LengthDefinition(x.Length, x.MinLength, x.MaxLength)));
public static implicit operator LengthDefinitions(RowDefinitions rows)
=> new LengthDefinitions(rows.Select(x => (LengthDefinition) x));
}
internal class LengthDefinition
{
internal LengthDefinition(GridLength length, double minLength, double maxLength)
{
Length = length;
MinLength = minLength;
MaxLength = maxLength;
}
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 void Update(double pixel)
{
Length = new GridLength(pixel);
}
}
}
}

99
tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs

@ -0,0 +1,99 @@
using Avalonia.Controls.Utils;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class GridLayoutTests
{
[Fact]
public void Measure_AllPixelLength_Correct()
{
// Arrange
var layout = new GridLayout(new RowDefinitions("100,200,300"));
// Action
var measure = layout.Measure(800);
// Assert
Assert.Equal(measure, new [] { 100d, 200d, 300d });
}
[Fact]
public void Measure_AllStarLength_Correct()
{
// Arrange
var layout = new GridLayout(new RowDefinitions("*,2*,3*"));
// Action
var measure = layout.Measure(600);
// Assert
Assert.Equal(measure, new [] { 100d, 200d, 300d });
}
[Fact]
public void Measure_MixStarPixelLength_Correct()
{
// Arrange
var layout = new GridLayout(new RowDefinitions("100,2*,3*"));
// Action
var measure = layout.Measure(600);
// Assert
Assert.Equal(measure, new [] { 100d, 200d, 300d });
}
[Fact]
public void Measure_MixAutoPixelLength_Correct()
{
// 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 });
}
[Fact]
public void Measure_MixAutoStarLength_Correct()
{
// Arrange
var layout = new GridLayout(new RowDefinitions("*,2*,Auto"));
// Action
var measure = layout.Measure(600);
// Assert
Assert.Equal(measure, new[] { 200d, 400d, double.PositiveInfinity });
}
[Fact]
public void Measure_MixAutoStarPixelLength_Correct()
{
// Arrange
var layout = new GridLayout(new RowDefinitions("*,200,Auto"));
// Action
var measure = layout.Measure(600);
// Assert
Assert.Equal(measure, new[] { 400d, 200d, double.PositiveInfinity });
}
[Fact]
public void Measure_AllPixelLengthButNotEnough_Correct()
{
// Arrange
var layout = new GridLayout(new RowDefinitions("100,200,300"));
// Action
var measure = layout.Measure(400);
// Assert
Assert.Equal(measure, new[] { 100d, 200d, 300d });
}
}
}
Loading…
Cancel
Save