Browse Source

Add support for layout that the children have multi span but also have desired size.

walterlv 7 years ago
parent
commit
163744b89b

+ 1 - 0
src/Avalonia.Controls/Avalonia.Controls.csproj

@@ -1,6 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
+    <LangVersion>latest</LangVersion>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

+ 57 - 26
src/Avalonia.Controls/Utils/GridLayout.cs

@@ -68,7 +68,7 @@ namespace Avalonia.Controls.Utils
             {
                 var index = i;
                 var convention = _conventions[index];
-                if (convention.Length.IsAuto)
+                if (convention.Length.IsAuto || convention.Length.IsStar)
                 {
                     foreach (var pair in source.Where(x =>
                         x.Value.index <= index && index < x.Value.index + x.Value.span))
@@ -99,10 +99,10 @@ namespace Avalonia.Controls.Utils
             var aggregatedLength = 0.0;
             double starUnitLength;
 
-            // M2/6. Exclude all the pixel lengths, so that we can calculate the star lengths.
+            // M2/7. Exclude all the pixel lengths, so that we can calculate the star lengths.
             aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
 
-            // M3/6. Exclude all the * lengths that have reached min value.
+            // M3/7. Exclude all the * lengths that have reached min value.
             var shouldTestStarMin = true;
             while (shouldTestStarMin)
             {
@@ -126,7 +126,7 @@ namespace Avalonia.Controls.Utils
                 shouldTestStarMin = @fixed;
             }
 
-            // M4/6. Exclude all the Auto lengths that have not-zero desired size.
+            // M4/7. Exclude all the Auto lengths that have not-zero desired size.
             var shouldTestAuto = true;
             while (shouldTestAuto)
             {
@@ -140,26 +140,7 @@ namespace Avalonia.Controls.Utils
                         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);
-                        }
-                    }
-
+                    var more = ApplyAdditionalConventionsForAuto(conventions, i, starUnitLength);
                     convention.Fix(more);
                     aggregatedLength += more;
                     @fixed = true;
@@ -169,12 +150,17 @@ namespace Avalonia.Controls.Utils
                 shouldTestAuto = @fixed;
             }
 
-            // M5/6. Determine the desired length of the grid for current contaienr length. Its value stores in desiredLength.
+            // M5/7. Expand the stars according to the additional conventions (usually the child desired length).
+            var desiredStarMin = AggregateAdditionalConventionsForStars(conventions);
+            aggregatedLength += desiredStarMin;
+            
+
+            // M6/7. Determine the desired length of the grid for current contaienr length. Its value stores in desiredLength.
             // But if the container has infinite length, the grid desired length is stored in greedyDesiredLength.
             var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength;
             var greedyDesiredLength = aggregatedLength;
 
-            // 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.
+            // M7/7. 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);
             Clip(dynamicConvention, containerLength);
 
@@ -202,6 +188,51 @@ namespace Avalonia.Controls.Utils
             return new ArrangeResult(measure.LengthList);
         }
 
+        [Pure]
+        private double ApplyAdditionalConventionsForAuto(IReadOnlyList<LengthConvention> conventions,
+            int index, double starUnitLength)
+        {
+            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 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 * starUnitLength;
+                            return 0.0;
+                        }).Sum();
+                    more = Math.Max(additional.Min - min, more);
+                }
+            }
+
+            return more;
+        }
+
+        [Pure]
+        private double AggregateAdditionalConventionsForStars(
+            IReadOnlyList<LengthConvention> conventions)
+        {
+            // +-----------------------------------------------------------+
+            // |  *  |  P  |  *  |  P  |  P  |  *  |  P  |     *     |  *  |
+            // +-----------------------------------------------------------+
+            // |<-      x      ->|                 |<-         z         ->|
+            //       |<-         y         ->|
+            // conveniences 是上面看到的那个列表,所有能够确定的 A、P 和 * 都已经转换成了 P;剩下的 * 只有 Max 是没确定的。
+            // _additionalConventions 是上面的 x、y、z…… 集合,只有最小值是可用的。
+            // 需要返回所有标记为 * 的方格的累加和的最小值。
+
+            var additionalConventions = _additionalConventions;
+
+            // TODO Calculate the min length of all the desired size.
+
+            return 150;
+        }
+
         [Pure]
         private static List<double> ExpandStars(IEnumerable<LengthConvention> conventions, double constraint)
         {

+ 34 - 1
tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs

@@ -115,7 +115,7 @@ namespace Avalonia.Controls.UnitTests
         }
 
         [Theory]
-        [InlineData("Auto,*,*", new[] { 100d, 100d, 100d }, 600d, 100d, new[] { 100d, 250d, 250d })]
+        [InlineData("Auto,*,*", new[] { 100d, 100d, 100d }, 600d, 300d, new[] { 100d, 250d, 250d })]
         public void MeasureArrange_ChildHasSize_Correct(string length,
             IList<double> childLengthList, double containerLength,
             double expectedDesiredLength, IList<double> expectedLengthList)
@@ -136,5 +136,38 @@ namespace Avalonia.Controls.UnitTests
             var arrange = layout.Arrange(containerLength, measure);
             Assert.Equal(expectedLengthList, arrange.LengthList);
         }
+
+        [Theory]
+        [InlineData(Inf, 250d, new[] { 100d, Inf, Inf }, new[] { 100d, 50d, 100d })]
+        [InlineData(400d, 250d, new[] { 100d, 100d, 200d }, new[] { 100d, 100d, 200d })]
+        [InlineData(325d, 250d, new[] { 100d, 75d, 150d }, new[] { 100d, 75d, 150d })]
+        [InlineData(250d, 250d, new[] { 100d, 50d, 100d }, new[] { 100d, 50d, 100d })]
+        [InlineData(160d, 160d, new[] { 100d, 20d, 40d }, new[] { 100d, 20d, 40d })]
+        public void MeasureArrange_ChildHasSizeAndHasMultiSpan_Correct(
+            double containerLength, double expectedDesiredLength,
+            IList<double> expectedMeasureLengthList, IList<double> expectedArrangeLengthList)
+        {
+            var length = "100,*,2*";
+            var childLengthList = new[] { 150d, 150d, 150d };
+            var spans = new[] { 1, 2, 1 };
+
+            // Arrange
+            var lengthList = new ColumnDefinitions(length);
+            var layout = new GridLayout(lengthList);
+            layout.AppendMeasureConventions(
+                Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, spans[x])),
+                x => childLengthList[x]);
+
+            // Measure - Action & Assert
+            var measure = layout.Measure(containerLength);
+            Assert.Equal(expectedDesiredLength, measure.DesiredLength);
+            Assert.Equal(expectedMeasureLengthList, measure.LengthList);
+
+            // Arrange - Action & Assert
+            var arrange = layout.Arrange(
+                double.IsInfinity(containerLength) ? measure.DesiredLength : containerLength,
+                measure);
+            Assert.Equal(expectedArrangeLengthList, arrange.LengthList);
+        }
     }
 }

+ 27 - 2
tests/Avalonia.Controls.UnitTests/GridMocks.cs

@@ -6,6 +6,21 @@ namespace Avalonia.Controls.UnitTests
 {
     internal static class GridMock
     {
+        /// <summary>
+        /// Create a mock grid to test its row layout.
+        /// This method contains Arrange (`new Grid()`) and Action (`Measure()`/`Arrange()`).
+        /// </summary>
+        /// <param name="measure">The measure height of this grid. PositiveInfinity by default.</param>
+        /// <param name="arrange">The arrange height of this grid. DesiredSize.Height by default.</param>
+        /// <returns>The mock grid that its children bounds will be tested.</returns>
+        internal static Grid New(Size measure = default, Size arrange = default)
+        {
+            var grid = new Grid();
+            grid.Measure(measure == default ? new Size(double.PositiveInfinity, double.PositiveInfinity) : measure);
+            grid.Arrange(new Rect(default, arrange == default ? grid.DesiredSize : arrange));
+            return grid;
+        }
+
         /// <summary>
         /// Create a mock grid to test its row layout.
         /// This method contains Arrange (`new Grid()`) and Action (`Measure()`/`Arrange()`).
@@ -25,7 +40,12 @@ namespace Avalonia.Controls.UnitTests
             }
 
             grid.Measure(new Size(double.PositiveInfinity, measure == default ? double.PositiveInfinity : measure));
-            grid.Arrange(new Rect(0, 0, 0, arrange == default ? grid.DesiredSize.Width : arrange));
+            if (arrange == default)
+            {
+                arrange = measure == default ? grid.DesiredSize.Width : measure;
+            }
+
+            grid.Arrange(new Rect(0, 0, 0, arrange));
 
             return grid;
         }
@@ -49,7 +69,12 @@ namespace Avalonia.Controls.UnitTests
             }
 
             grid.Measure(new Size(measure == default ? double.PositiveInfinity : measure, double.PositiveInfinity));
-            grid.Arrange(new Rect(0, 0, arrange == default ? grid.DesiredSize.Width : arrange, 0));
+            if (arrange == default)
+            {
+                arrange = measure == default ? grid.DesiredSize.Width : measure;
+            }
+
+            grid.Arrange(new Rect(0, 0, arrange, 0));
 
             return grid;
         }

+ 19 - 8
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -83,12 +83,23 @@ namespace Avalonia.Controls.UnitTests
             GridAssert.ChildrenWidth(columnGrid, 50, 100, 150);
         }
 
+        [Fact]
+        public void Layout_NoRowColumn_BoundsCorrect()
+        {
+            // Arrange & Action
+            var grid = GridMock.New(arrange: new Size(600, 200));
+
+            // Assert
+            GridAssert.ChildrenHeight(grid, 600);
+            GridAssert.ChildrenWidth(grid, 200);
+        }
+
         [Fact]
         public void Layout_StarRowColumn_BoundsCorrect()
         {
             // Arrange & Action
-            var rowGrid = GridMock.New(new RowDefinitions("1*,2*,3*"), arrange: 600);
-            var columnGrid = GridMock.New(new ColumnDefinitions("*,*,2*"), arrange: 600);
+            var rowGrid = GridMock.New(new RowDefinitions("1*,2*,3*"), 600);
+            var columnGrid = GridMock.New(new ColumnDefinitions("*,*,2*"), 600);
 
             // Assert
             GridAssert.ChildrenHeight(rowGrid, 100, 200, 300);
@@ -99,8 +110,8 @@ namespace Avalonia.Controls.UnitTests
         public void Layout_MixPixelStarRowColumn_BoundsCorrect()
         {
             // Arrange & Action
-            var rowGrid = GridMock.New(new RowDefinitions("1*,2*,150"), arrange: 600);
-            var columnGrid = GridMock.New(new ColumnDefinitions("1*,2*,150"), arrange: 600);
+            var rowGrid = GridMock.New(new RowDefinitions("1*,2*,150"), 600);
+            var columnGrid = GridMock.New(new ColumnDefinitions("1*,2*,150"), 600);
 
             // Assert
             GridAssert.ChildrenHeight(rowGrid, 150, 300, 150);
@@ -116,13 +127,13 @@ namespace Avalonia.Controls.UnitTests
                 new RowDefinition(1, GridUnitType.Star) { MinHeight = 200 },
                 new RowDefinition(1, GridUnitType.Star),
                 new RowDefinition(1, GridUnitType.Star),
-            }, arrange: 300);
+            }, 300);
             var columnGrid = GridMock.New(new ColumnDefinitions
             {
                 new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 200 },
                 new ColumnDefinition(1, GridUnitType.Star),
                 new ColumnDefinition(1, GridUnitType.Star),
-            }, arrange: 300);
+            }, 300);
 
             // Assert
             GridAssert.ChildrenHeight(rowGrid, 200, 50, 50);
@@ -138,13 +149,13 @@ namespace Avalonia.Controls.UnitTests
                 new RowDefinition(1, GridUnitType.Star) { MaxHeight = 200 },
                 new RowDefinition(1, GridUnitType.Star),
                 new RowDefinition(1, GridUnitType.Star),
-            }, arrange: 800);
+            }, 800);
             var columnGrid = GridMock.New(new ColumnDefinitions
             {
                 new ColumnDefinition(1, GridUnitType.Star) { MaxWidth = 200 },
                 new ColumnDefinition(1, GridUnitType.Star),
                 new ColumnDefinition(1, GridUnitType.Star),
-            }, arrange: 800);
+            }, 800);
 
             // Assert
             GridAssert.ChildrenHeight(rowGrid, 200, 300, 300);