From 63af32bc65c0d7bd8eb82b9be07e591ba5e526d7 Mon Sep 17 00:00:00 2001 From: walterlv Date: Tue, 1 May 2018 09:46:35 +0800 Subject: [PATCH] Add some document comments for GridLayout. --- src/Avalonia.Controls/Utils/GridLayout.cs | 210 +++++++++++++++++----- 1 file changed, 163 insertions(+), 47 deletions(-) diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs index 34c3a9f9fa..f2a5d57725 100644 --- a/src/Avalonia.Controls/Utils/GridLayout.cs +++ b/src/Avalonia.Controls/Utils/GridLayout.cs @@ -2,67 +2,100 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Avalonia.Layout; using JetBrains.Annotations; namespace Avalonia.Controls.Utils { - // We have three kind of unit: - // - * means Star unit. It can be affected by min and max pixel length. - // - A means Auto unit. It can be affected by min/max pixel length and desired pixel length. - // - P means Pixel unit. It is fixed and can't be affected by any other values. - // Notice that some child stands not only one column/row and this affects desired length. - // Desired length behaviors like the min pixel length but: - // - This can only be determined after the Measure. - // - // This is an example indicates how this class stores data. - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // | min | min | | | min | | min max | - // |<- desired ->| - // - // During the measuring procedure: - // - * wants as much as possible space in range of min and max. - // - A wants as less as possible space in range of min/desired and max. - // - P wants a fix-size space. - // But during the arranging procedure: - // - * behaviors the same. - // - A wants as much as possible space in range of min/desired and max. - // - P behaviors the same. - // /// /// Contains algorithms that can help to measure and arrange a Grid. /// internal class GridLayout { - internal GridLayout(ColumnDefinitions columns) + /// + /// Initialize a new instance from the column definitions. + /// will forget that the layout data comes from the columns. + /// + internal GridLayout([NotNull] ColumnDefinitions columns) { + if (columns == null) throw new ArgumentNullException(nameof(columns)); _conventions = columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList(); } - internal GridLayout(RowDefinitions rows) + /// + /// Initialize a new instance from the row definitions. + /// will forget that the layout data comes from the rows. + /// + internal GridLayout([NotNull] RowDefinitions rows) { + if (rows == null) throw new ArgumentNullException(nameof(rows)); _conventions = rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList(); } + /// + /// Gets the layout tolerance. If any length offset is less than this value, we will treat them the same. + /// private const double LayoutTolerance = 1.0 / 256.0; + + /// + /// Gets all the length conventions that come from column/row definitions. + /// These conventions provide limitations of each grid cell. + /// + [NotNull] private readonly List _conventions; + + /// + /// Gets all the length conventions that come from the grid children. + /// + [NotNull] private readonly List _additionalConventions = new List(); /// - /// Some elements are not in a single grid cell, they have multiple column/row spans, - /// and these elements may affects the grid layout especially the measure procedure. - /// Append these elements into the convention list can help to layout them correctly through their desired size. - /// Only a small subset of grid children need to be measured before layout starts and they are called via the callback. + /// Some elements are not only in a single grid cell, they have one or more column/row spans, + /// and these elements may affect the grid layout especially the measuring procedure. + /// Append these elements into the convention list can help to layout them correctly through + /// their desired size. Only a small subset of grid children need to be measured before layout + /// starts and they will be called via the callback. /// - /// - /// - /// - internal void AppendMeasureConventions(IDictionary source, - Func getDesiredLength) + /// The grid children type. + /// + /// Contains the safe column/row index and its span. + /// Notice that we will not verify whether the range is in the column/row count, + /// so you should get the safe column/row info first. + /// + /// + /// This callback will be called if the thinks that a child should be + /// measured first. Usually, these are the children that have the * or Auto length. + /// + internal void AppendMeasureConventions([NotNull] IDictionary source, + [NotNull] Func getDesiredLength) { - // M1/6. Find all the Auto length columns/rows. + if (source == null) throw new ArgumentNullException(nameof(source)); + if (getDesiredLength == null) throw new ArgumentNullException(nameof(getDesiredLength)); + + // M1/7. Find all the Auto and * length columns/rows. // Only these columns/rows' layout can be affected by the children desired size. + // + // Find all columns/rows that has the length Auto or *. We'll measure the children in advance. + // Only these kind of columns/rows will affects the Grid layout. + // Please note: + // - The columns/rows of Auto length will definitely be affected by the children size; + // - However, only the Grid.DesiredSize can be affected by the *-length columns/rows unless the Grid has very much space (Infinitely). + + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // _conventions: | min | max | | | min | | min max | max | + // _additionalC: |<- desired ->| |< desired >| + // _additionalC: |< desired >| |<- desired ->| + + // 寻找所有行列范围中包含 Auto 和 * 的元素,使用全部可用尺寸提前测量。 + // 因为只有这部分元素的布局才会被 Grid 的子元素尺寸影响。 + // 请注意: + // - Auto 长度的行列必定会受到子元素布局影响,会影响到行列的布局长度; + // - 而对于 * 长度,一般只有 Grid.DesiredSize 会受到子元素布局影响,只有在 Grid 布局空间充足时行列长度才会被子元素布局影响。 + + // Find all the Auto and * length columns/rows. var found = new Dictionary(); for (var i = 0; i < _conventions.Count; i++) { @@ -91,21 +124,57 @@ namespace Avalonia.Controls.Utils } } + /// + /// Run measure procedure according to the and gets the . + /// + /// + /// The container length. Usually, it is the constraint of the method. + /// + /// + /// The measured result that containing the desired size and all the column/row length. + /// + [NotNull] internal MeasureResult Measure(double containerLength) { - // Initial. + // Prepare all the variables that this method needs to use. var conventions = _conventions.Select(x => x.Clone()).ToList(); var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value); var aggregatedLength = 0.0; double starUnitLength; - // M2/7. Exclude all the pixel lengths, so that we can calculate the star lengths. + // M2/7. Aggregate all the pixel lengths. Then we can get the rest length by `containerLength - aggregatedLength`. + // We mark the aggregated length as "fix" because we can completely determine their values. Same as below. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // |#fix#| |#fix#| + // + // 将全部的固定像素长度的行列长度累加。这样,containerLength - aggregatedLength 便能得到剩余长度。 + // 我们会将所有能够确定下长度的行列标记为 fix。下同。 + // 请注意: + // - 我们并没有直接从 containerLength 一直减下去,而是使用 aggregatedLength 进行累加,是因为无穷大相减得到的是 NaN,不利于后续计算。 + aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value); - // M3/7. Exclude all the * lengths that have reached min value. + // M3/7. Fix all the * lengths that have reached the minimum. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // | fix | |#fix#| fix | + var shouldTestStarMin = true; while (shouldTestStarMin) { + // Calculate the unit * length to estimate the length of each column/row that has * length. + // Under this estimated length, look for if there is a minimum value that has a length less than its constraint. + // If there is such a *, then fix the size of this cell, and then loop it again until there is no * that can be constrained by the minimum value. + // + // 计算单位 * 的长度,以便预估出每一个 * 行列的长度。 + // 在此预估的长度下,从前往后寻找是否存在某个 * 长度已经小于其约束的最小值。 + // 如果发现存在这样的 *,那么将此单元格的尺寸固定下来(Fix),然后循环重来,直至再也没有能被最小值约束的 *。 var @fixed = false; starUnitLength = (containerLength - aggregatedLength) / starCount; foreach (var convention in conventions.Where(x => x.Length.IsStar)) @@ -126,7 +195,14 @@ namespace Avalonia.Controls.Utils shouldTestStarMin = @fixed; } - // M4/7. Exclude all the Auto lengths that have not-zero desired size. + // M3/7. Fix all the Auto lengths that the children on its column/row have a zero or non-zero length. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#fix#| | fix |#fix#| fix | fix | + var shouldTestAuto = true; while (shouldTestAuto) { @@ -151,26 +227,66 @@ namespace Avalonia.Controls.Utils } // M5/7. Expand the stars according to the additional conventions (usually the child desired length). + // We can't fix this kind of length, so we just mark them as desired (des). + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#| + 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. + // M6/7. Determine the desired length of the grid for current container length. Its value stores in desiredLength. + // Assume if the container has infinite length, the grid desired length is stored in greedyDesiredLength. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#| + // + // desiredLength = Math.Max(0.0, des + fix + des + fix + fix + fix + fix + des + des) + // greedyDesiredLength = des + fix + des + fix + fix + fix + fix + des + des + var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength; var greedyDesiredLength = aggregatedLength; - // 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. + // M7/7. Expand all the rest stars. These stars have no conventions or only have + // max value they can be expanded from zero to constraint. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#fix#| fix |#fix#| fix | fix | fix | fix | #fix# |#fix#| + var dynamicConvention = ExpandStars(conventions, containerLength); Clip(dynamicConvention, containerLength); - // Stores the measuring result. + // Returns the measuring result. return new MeasureResult(containerLength, desiredLength, greedyDesiredLength, conventions, dynamicConvention); } - public ArrangeResult Arrange(double finalLength, MeasureResult measure) + /// + /// Run arrange procedure according to the and gets the . + /// + /// + /// The container length. Usually, it is the finalSize of the method. + /// + /// + /// The result that the measuring procedure returns. If it is null, a new measure procedure will run. + /// + /// + /// The measured result that containing the desired size and all the column/row length. + /// + [NotNull] + public ArrangeResult Arrange(double finalLength, [CanBeNull] MeasureResult measure) { + measure = measure ?? Measure(finalLength); + // If the arrange final length does not equal to the measure length, we should measure again. if (finalLength - measure.ContainerLength > LayoutTolerance) { @@ -210,7 +326,7 @@ namespace Avalonia.Controls.Utils } } - return more; + return Math.Min(conventions[index].MaxLength, more); } [Pure]