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