2 changed files with 243 additions and 0 deletions
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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…
Reference in new issue