From b9d71860ff3a2711e27293191960441ddfce9c14 Mon Sep 17 00:00:00 2001 From: walterlv Date: Sat, 28 Apr 2018 21:40:55 +0800 Subject: [PATCH] Add a new version of Grid layout. and its performance may be better than the original one. --- src/Avalonia.Controls/Utils/GridLayout.cs | 144 ++++++++++++++++++ .../GridLayoutTests.cs | 99 ++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 src/Avalonia.Controls/Utils/GridLayout.cs create mode 100644 tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs new file mode 100644 index 0000000000..2497a5f46c --- /dev/null +++ b/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; + + /// + /// 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 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, ICloneable + { + private readonly List _lengths; + + private LengthDefinitions(IEnumerable lengths) + { + _lengths = lengths.ToList(); + } + + public IEnumerator 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); + } + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs new file mode 100644 index 0000000000..be6b6ce465 --- /dev/null +++ b/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 }); + } + } +}