{"action":"created","comment":{"url":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/comments/186278447","pull_request_review_id":117830869,"id":186278447,"diff_hunk":"@@ -0,0 +1,694 @@\n+﻿using System;\n+using System.Collections.Generic;\n+using System.Diagnostics;\n+using System.Globalization;\n+using System.Linq;\n+using System.Runtime.CompilerServices;\n+using Avalonia.Layout;\n+using JetBrains.Annotations;\n+\n+namespace Avalonia.Controls.Utils\n+{\n+    /// <summary>\n+    /// Contains algorithms that can help to measure and arrange a Grid.\n+    /// </summary>\n+    internal class GridLayout\n+    {\n+        /// <summary>\n+        /// Initialize a new <see cref=\"GridLayout\"/> instance from the column definitions.\n+        /// <see cref=\"GridLayout\"/> will forget that the layout data comes from the columns.\n+        /// </summary>\n+        internal GridLayout([NotNull] ColumnDefinitions columns)\n+        {\n+            if (columns == null) throw new ArgumentNullException(nameof(columns));\n+            _conventions = columns.Count == 0\n+                ? new List<LengthConvention> { new LengthConvention() }\n+                : columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList();\n+        }\n+\n+        /// <summary>\n+        /// Initialize a new <see cref=\"GridLayout\"/> instance from the row definitions.\n+        /// <see cref=\"GridLayout\"/> will forget that the layout data comes from the rows.\n+        /// </summary>\n+        internal GridLayout([NotNull] RowDefinitions rows)\n+        {\n+            if (rows == null) throw new ArgumentNullException(nameof(rows));\n+            _conventions = rows.Count == 0\n+                ? new List<LengthConvention> { new LengthConvention() }\n+                : rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList();\n+        }\n+\n+        /// <summary>\n+        /// Gets the layout tolerance. If any length offset is less than this value, we will treat them the same.\n+        /// </summary>\n+        private const double LayoutTolerance = 1.0 / 256.0;\n+\n+        /// <summary>\n+        /// Gets all the length conventions that come from column/row definitions.\n+        /// These conventions provide limitations of each grid cell.\n+        /// </summary>\n+        [NotNull]\n+        private readonly List<LengthConvention> _conventions;\n+\n+        /// <summary>\n+        /// Gets all the length conventions that come from the grid children.\n+        /// </summary>\n+        [NotNull]\n+        private readonly List<AdditionalLengthConvention> _additionalConventions =\n+            new List<AdditionalLengthConvention>();\n+\n+        /// <summary>\n+        /// Some elements are not only in a single grid cell, they have one or more column/row spans,\n+        /// and these elements may affect the grid layout especially the measuring procedure.<para/>\n+        /// Append these elements into the convention list can help to layout them correctly through\n+        /// their desired size. Only a small subset of grid children need to be measured before layout\n+        /// starts and they will be called via the<paramref name=\"getDesiredLength\"/> callback.\n+        /// </summary>\n+        /// <typeparam name=\"T\">The grid children type.</typeparam>\n+        /// <param name=\"source\">\n+        /// Contains the safe column/row index and its span.\n+        /// Notice that we will not verify whether the range is in the column/row count,\n+        /// so you should get the safe column/row info first.\n+        /// </param>\n+        /// <param name=\"getDesiredLength\">\n+        /// This callback will be called if the <see cref=\"GridLayout\"/> thinks that a child should be\n+        /// measured first. Usually, these are the children that have the * or Auto length.\n+        /// </param>\n+        internal void AppendMeasureConventions<T>([NotNull] IDictionary<T, (int index, int span)> source,\n+            [NotNull] Func<T, double> getDesiredLength)\n+        {\n+            if (source == null) throw new ArgumentNullException(nameof(source));\n+            if (getDesiredLength == null) throw new ArgumentNullException(nameof(getDesiredLength));\n+\n+            // M1/7. Find all the Auto and * length columns/rows.\n+            // Only these columns/rows' layout can be affected by the children desired size.\n+            // \n+            // Find all columns/rows that has the length Auto or *. We'll measure the children in advance.\n+            // Only these kind of columns/rows will affects the Grid layout.\n+            // Please note:\n+            // - The columns/rows of Auto length will definitely be affected by the children size;\n+            // - However, only the Grid.DesiredSize can be affected by the *-length columns/rows unless the Grid has very much space (Infinitely).\n+\n+            //               +-----------------------------------------------------------+\n+            //               |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |\n+            //               +-----------------------------------------------------------+\n+            // _conventions: | min | max |     |           | min |     |  min max  | max |\n+            // _additionalC:                   |<-   desired   ->|     |< desired >|\n+            // _additionalC:       |< desired >|           |<-        desired          ->|\n+\n+            // 寻找所有行列范围中包含 Auto 和 * 的元素，使用全部可用尺寸提前测量。\n+            // 因为只有这部分元素的布局才会被 Grid 的子元素尺寸影响。\n+            // 请注意：\n+            // - Auto 长度的行列必定会受到子元素布局影响，会影响到行列的布局长度；\n+            // - 而对于 * 长度，一般只有 Grid.DesiredSize 会受到子元素布局影响，只有在 Grid 布局空间充足时行列长度才会被子元素布局影响。\n+\n+            // Find all the Auto and * length columns/rows.\n+            var found = new Dictionary<T, (int index, int span)>();\n+            for (var i = 0; i < _conventions.Count; i++)\n+            {\n+                var index = i;\n+                var convention = _conventions[index];\n+                if (convention.Length.IsAuto || convention.Length.IsStar)\n+                {\n+                    foreach (var pair in source.Where(x =>\n+                        x.Value.index <= index && index < x.Value.index + x.Value.span))\n+                    {\n+                        found[pair.Key] = pair.Value;\n+                    }\n+                }\n+            }\n+\n+            // Append these layout into the additional convention list.\n+            foreach (var pair in found)\n+            {\n+                var t = pair.Key;\n+                var (index, span) = pair.Value;\n+                var desiredLength = getDesiredLength(t);\n+                if (Math.Abs(desiredLength) > LayoutTolerance)\n+                {\n+                    _additionalConventions.Add(new AdditionalLengthConvention(index, span, desiredLength));\n+                }\n+            }\n+        }\n+\n+        /// <summary>\n+        /// Run measure procedure according to the <paramref name=\"containerLength\"/> and gets the <see cref=\"MeasureResult\"/>.\n+        /// </summary>\n+        /// <param name=\"containerLength\">\n+        /// The container length. Usually, it is the constraint of the <see cref=\"Layoutable.MeasureOverride\"/> method.\n+        /// </param>\n+        /// <returns>\n+        /// The measured result that containing the desired size and all the column/row length.\n+        /// </returns>\n+        [NotNull, Pure]\n+        internal MeasureResult Measure(double containerLength)\n+        {\n+            // Prepare all the variables that this method needs to use.\n+            var conventions = _conventions.Select(x => x.Clone()).ToList();\n+            var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);\n+            var aggregatedLength = 0.0;\n+            double starUnitLength;\n+\n+            // M2/7. Aggregate all the pixel lengths. Then we can get the rest length by `containerLength - aggregatedLength`.\n+            // We mark the aggregated length as \"fix\" because we can completely determine their values. Same as below.\n+            //\n+            // +-----------------------------------------------------------+\n+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |\n+            // +-----------------------------------------------------------+\n+            //                   |#fix#|           |#fix#|\n+            //\n+            // 将全部的固定像素长度的行列长度累加。这样，containerLength - aggregatedLength 便能得到剩余长度。\n+            // 我们会将所有能够确定下长度的行列标记为 fix。下同。\n+            // 请注意：\n+            // - 我们并没有直接从 containerLength 一直减下去，而是使用 aggregatedLength 进行累加，是因为无穷大相减得到的是 NaN，不利于后续计算。\n+\n+            aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);\n+\n+            // M3/7. Fix all the * lengths that have reached the minimum.\n+            //\n+            // +-----------------------------------------------------------+\n+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |\n+            // +-----------------------------------------------------------+\n+            // | min | max |     |           | min |     |  min max  | max |\n+            //                   | fix |     |#fix#| fix |\n+\n+            var shouldTestStarMin = true;\n+            while (shouldTestStarMin)\n+            {\n+                // Calculate the unit * length to estimate the length of each column/row that has * length.\n+                // Under this estimated length, look for if there is a minimum value that has a length less than its constraint.\n+                // 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.\n+                //\n+                // 计算单位 * 的长度，以便预估出每一个 * 行列的长度。\n+                // 在此预估的长度下，从前往后寻找是否存在某个 * 长度已经小于其约束的最小值。\n+                // 如果发现存在这样的 *，那么将此单元格的尺寸固定下来（Fix），然后循环重来，直至再也没有能被最小值约束的 *。\n+                var @fixed = false;\n+                starUnitLength = (containerLength - aggregatedLength) / starCount;\n+                foreach (var convention in conventions.Where(x => x.Length.IsStar))\n+                {\n+                    var (star, min) = (convention.Length.Value, convention.MinLength);\n+                    var starLength = star * starUnitLength;\n+                    if (starLength < min)\n+                    {\n+                        convention.Fix(min);\n+                        starLength = min;\n+                        aggregatedLength += starLength;\n+                        starCount -= star;\n+                        @fixed = true;\n+                        break;\n+                    }\n+                }\n+\n+                shouldTestStarMin = @fixed;\n+            }\n+\n+            // M4/7. Fix all the Auto lengths that the children on its column/row have a zero or non-zero length.\n+            //\n+            // +-----------------------------------------------------------+\n+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |\n+            // +-----------------------------------------------------------+\n+            // | min | max |     |           | min |     |  min max  | max |\n+            //       |#fix#|     | fix |#fix#| fix | fix |\n+\n+            var shouldTestAuto = true;\n+            while (shouldTestAuto)\n+            {\n+                var @fixed = false;\n+                starUnitLength = (containerLength - aggregatedLength) / starCount;\n+                for (var i = 0; i < conventions.Count; i++)\n+                {\n+                    var convention = conventions[i];\n+                    if (!convention.Length.IsAuto)\n+                    {\n+                        continue;\n+                    }\n+\n+                    var more = ApplyAdditionalConventionsForAuto(conventions, i, starUnitLength);\n+                    convention.Fix(more);\n+                    aggregatedLength += more;\n+                    @fixed = true;\n+                    break;\n+                }\n+\n+                shouldTestAuto = @fixed;\n+            }\n+\n+            // M5/7. Expand the stars according to the additional conventions (usually the child desired length).\n+            // We can't fix this kind of length, so we just mark them as desired (des).\n+            //\n+            // +-----------------------------------------------------------+\n+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |\n+            // +-----------------------------------------------------------+\n+            // | min | max |     |           | min |     |  min max  | max |\n+            // |#des#| fix |#des#| fix | fix | fix | fix |   #des#   |#des#|\n+\n+            var desiredStarMin = AggregateAdditionalConventionsForStars(conventions);\n+            aggregatedLength += desiredStarMin;\n+\n+            // M6/7. Determine the desired length of the grid for current container length. Its value stores in desiredLength.\n+            // Assume if the container has infinite length, the grid desired length is stored in greedyDesiredLength.\n+            //\n+            // +-----------------------------------------------------------+\n+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |\n+            // +-----------------------------------------------------------+\n+            // | min | max |     |           | min |     |  min max  | max |\n+            // |#des#| fix |#des#| fix | fix | fix | fix |   #des#   |#des#|\n+            // Note: This table will be stored as the intermediate result into the MeasureResult and it will be reused by Arrange procedure.\n+            // \n+            // desiredLength = Math.Max(0.0, des + fix + des + fix + fix + fix + fix + des + des)\n+            // greedyDesiredLength = des + fix + des + fix + fix + fix + fix + des + des\n+\n+            var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength;\n+            var greedyDesiredLength = aggregatedLength;\n+\n+            // M7/7. Expand all the rest stars. These stars have no conventions or only have\n+            // max value they can be expanded from zero to constraint.\n+            //\n+            // +-----------------------------------------------------------+\n+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |\n+            // +-----------------------------------------------------------+\n+            // | min | max |     |           | min |     |  min max  | max |\n+            // |#fix#| fix |#fix#| fix | fix | fix | fix |   #fix#   |#fix#|\n+            // Note: This table will be stored as the final result into the MeasureResult.\n+\n+            var dynamicConvention = ExpandStars(conventions, containerLength);\n+            Clip(dynamicConvention, containerLength);\n+\n+            // Returns the measuring result.\n+            return new MeasureResult(containerLength, desiredLength, greedyDesiredLength,\n+                conventions, dynamicConvention);\n+        }\n+\n+        /// <summary>\n+        /// Run arrange procedure according to the <paramref name=\"measure\"/> and gets the <see cref=\"ArrangeResult\"/>.\n+        /// </summary>\n+        /// <param name=\"finalLength\">\n+        /// The container length. Usually, it is the finalSize of the <see cref=\"Layoutable.ArrangeOverride\"/> method.\n+        /// </param>\n+        /// <param name=\"measure\">\n+        /// The result that the measuring procedure returns. If it is null, a new measure procedure will run.\n+        /// </param>\n+        /// <returns>\n+        /// The measured result that containing the desired size and all the column/row length.\n+        /// </returns>\n+        [NotNull, Pure]\n+        public ArrangeResult Arrange(double finalLength, [CanBeNull] MeasureResult measure)\n+        {\n+            measure = measure ?? Measure(finalLength);\n+\n+            // If the arrange final length does not equal to the measure length, we should measure again.\n+            if (finalLength - measure.ContainerLength > LayoutTolerance)\n+            {\n+                // If the final length is larger, we will rerun the whole measure.\n+                measure = Measure(finalLength);\n+            }\n+            else if (finalLength - measure.ContainerLength < -LayoutTolerance)\n+            {\n+                // If the final length is smaller, we measure the M6/6 procedure only.\n+                var dynamicConvention = ExpandStars(measure.LeanLengthList, finalLength);\n+                measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength,\n+                    measure.LeanLengthList, dynamicConvention);\n+            }\n+\n+            return new ArrangeResult(measure.LengthList);\n+        }\n+\n+        /// <summary>\n+        /// Use the <see cref=\"_additionalConventions\"/> to calculate the fixed length of the Auto column/row.\n+        /// </summary>\n+        /// <param name=\"conventions\">The convention list that has same length fixed.</param>\n+        /// <param name=\"index\">The column/row index that should be fixed.</param>\n+        /// <param name=\"starUnitLength\">The unit * length for the current rest length.</param>\n+        /// <returns>The final length of the Auto length column/row.</returns>\n+        [Pure]\n+        private double ApplyAdditionalConventionsForAuto(IReadOnlyList<LengthConvention> conventions,\n+            int index, double starUnitLength)\n+        {\n+            // 1. Calculate all the * length with starUnitLength.\n+            // 2. Exclude all the fixed length and all the * length.\n+            // 3. Compare the rest of the desired length and the convention.\n+            // +-----------------+\n+            // |  *  |  A  |  *  |\n+            // +-----------------+\n+            // | exl |     | exl |\n+            // |< desired >|\n+            //       |< desired >|\n+\n+            var more = 0.0;\n+            foreach (var additional in _additionalConventions)\n+            {\n+                // If the additional convention's last column/row contains the Auto column/row, try to determine the Auto column/row length.\n+                if (index == additional.Index + additional.Span - 1)\n+                {\n+                    var min = Enumerable.Range(additional.Index, additional.Span)\n+                        .Select(x =>\n+                        {\n+                            var c = conventions[x];\n+                            if (c.Length.IsAbsolute) return c.Length.Value;\n+                            if (c.Length.IsStar) return c.Length.Value * starUnitLength;\n+                            return 0.0;\n+                        }).Sum();\n+                    more = Math.Max(additional.Min - min, more);\n+                }\n+            }\n+\n+            return Math.Min(conventions[index].MaxLength, more);\n+        }\n+\n+        /// <summary>\n+        /// Calculate the total desired length of all the * length.\n+        /// Bug Warning:\n+        /// - The behavior of this method is undefined! Different UI Frameworks have different behaviors.\n+        /// - We ignore all the span columns/rows and just take single cells into consideration.\n+        /// </summary>\n+        /// <param name=\"conventions\">All the conventions that have almost been fixed except the rest *.</param>\n+        /// <returns>The total desired length of all the * length.</returns>\n+        [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]\n+        private double AggregateAdditionalConventionsForStars(\n+            IReadOnlyList<LengthConvention> conventions)\n+        {\n+            // 1. Determine all one-span column's desired widths or row's desired heights.\n+            // 2. Order the multi-span conventions by its last index\n+            //    (Notice that the sorting data source is much smaller than the original children source.)\n+            // 3. Determin each multi-span last index by calculating the maximun desired size.\n+\n+            // Before we determine the behavior of this method, we just aggregate the one-span * columns.\n+\n+            var fixedLength = conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);\n+\n+            // Prepare a lengthList variable indicating the fixed length of each column/row.\n+            var lengthList = conventions.Select(x => x.Length.IsAbsolute ? x.Length.Value : 0.0).ToList();\n+            foreach (var group in _additionalConventions\n+                .Where(x => x.Span == 1 && conventions[x.Index].Length.IsStar)\n+                .ToLookup(x => x.Index))\n+            {\n+                lengthList[group.Key] = Math.Max(lengthList[group.Key], group.Max(x => x.Min));\n+            }\n+\n+            // Now the lengthList is fixed by every one-span columns/rows.\n+            // Then we should determine the multi-span column's/row's length.\n+            foreach (var group in _additionalConventions\n+                .Where(x => x.Span > 1)\n+                .ToLookup(x => x.Index + x.Span - 1)\n+                // Order the multi-span columns/rows by last index.\n+                .OrderBy(x => x.Key))\n+            {\n+                var length = group.Max(x => x.Min - Enumerable.Range(x.Index, x.Span - 1).Sum(r => lengthList[r]));\n+                lengthList[group.Key] = Math.Max(lengthList[group.Key], length > 0 ? length : 0);\n+            }\n+\n+            return lengthList.Sum() - fixedLength;\n+        }\n+\n+        /// <summary>\n+        /// This method implement the last procedure (M7/7) of measure.\n+        /// It expand all the * length to the fixed length according to the <paramref name=\"constraint\"/>.\n+        /// </summary>\n+        /// <param name=\"conventions\">All the conventions that have almost been fixed except the rest *.</param>\n+        /// <param name=\"constraint\">The container length.</param>\n+        /// <returns>The final pixel length list.</returns>\n+        [Pure]\n+        private static List<double> ExpandStars(IEnumerable<LengthConvention> conventions, double constraint)\n+        {\n+            // Initial.\n+            var dynamicConvention = conventions.Select(x => x.Clone()).ToList();\n+            constraint -= dynamicConvention.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);\n+            var starUnitLength = 0.0;\n+\n+            // M6/6.\n+            if (constraint >= 0)\n+            {\n+                var starCount = dynamicConvention.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);\n+\n+                var shouldTestStarMax = true;\n+                while (shouldTestStarMax)\n+                {\n+                    var @fixed = false;\n+                    starUnitLength = constraint / starCount;\n+                    foreach (var convention in dynamicConvention.Where(x =>\n+                        x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength)))\n+                    {\n+                        var (star, max) = (convention.Length.Value, convention.MaxLength);\n+                        var starLength = star * starUnitLength;\n+                        if (starLength > max)\n+                        {\n+                            convention.Fix(max);\n+                            starLength = max;\n+                            constraint -= starLength;\n+                            starCount -= star;\n+                            @fixed = true;\n+                            break;\n+                        }\n+                    }\n+\n+                    shouldTestStarMax = @fixed;\n+                }\n+            }\n+\n+            Debug.Assert(dynamicConvention.All(x => !x.Length.IsAuto));\n+\n+            var starUnit = starUnitLength;\n+            var result = dynamicConvention.Select(x =>\n+            {\n+                if (x.Length.IsStar)\n+                {\n+                    return double.IsInfinity(starUnit) ? double.PositiveInfinity : starUnit * x.Length.Value;\n+                }\n+\n+                return x.Length.Value;\n+            }).ToList();\n+\n+            return result;\n+        }\n+\n+        /// <summary>\n+        /// If the container length is not infinity. It may be not enough to contain all the columns/rows.\n+        /// We should clip the columns/rows that have been out of the container bounds.\n+        /// Note: This method may change the items value of <paramref name=\"lengthList\"/>.\n+        /// </summary>\n+        /// <param name=\"lengthList\">All the column width list or the row height list with fixed pixel length.</param>","path":"src/Avalonia.Controls/Utils/GridLayout.cs","position":469,"original_position":469,"commit_id":"ad3e6e3496adcc3cb7354e467c39a3d26e4fadb3","original_commit_id":"ad3e6e3496adcc3cb7354e467c39a3d26e4fadb3","user":{"login":"grokys","id":1775141,"avatar_url":"https://avatars1.githubusercontent.com/u/1775141?v=4","gravatar_id":"","url":"https://api.github.com/users/grokys","html_url":"https://github.com/grokys","followers_url":"https://api.github.com/users/grokys/followers","following_url":"https://api.github.com/users/grokys/following{/other_user}","gists_url":"https://api.github.com/users/grokys/gists{/gist_id}","starred_url":"https://api.github.com/users/grokys/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grokys/subscriptions","organizations_url":"https://api.github.com/users/grokys/orgs","repos_url":"https://api.github.com/users/grokys/repos","events_url":"https://api.github.com/users/grokys/events{/privacy}","received_events_url":"https://api.github.com/users/grokys/received_events","type":"User","site_admin":true},"body":"Does this mean \"A list of all the column widths and row heights with a fixed pixel length\"?","created_at":"2018-05-06T04:51:45Z","updated_at":"2018-05-06T05:00:05Z","html_url":"https://github.com/AvaloniaUI/Avalonia/pull/1517#discussion_r186278447","pull_request_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/comments/186278447"},"html":{"href":"https://github.com/AvaloniaUI/Avalonia/pull/1517#discussion_r186278447"},"pull_request":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517"}}},"pull_request":{"url":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517","id":183263843,"html_url":"https://github.com/AvaloniaUI/Avalonia/pull/1517","diff_url":"https://github.com/AvaloniaUI/Avalonia/pull/1517.diff","patch_url":"https://github.com/AvaloniaUI/Avalonia/pull/1517.patch","issue_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/issues/1517","number":1517,"state":"open","locked":false,"title":"A new grid layout algorithm to improve performance and fix some bugs","user":{"login":"walterlv","id":9959623,"avatar_url":"https://avatars1.githubusercontent.com/u/9959623?v=4","gravatar_id":"","url":"https://api.github.com/users/walterlv","html_url":"https://github.com/walterlv","followers_url":"https://api.github.com/users/walterlv/followers","following_url":"https://api.github.com/users/walterlv/following{/other_user}","gists_url":"https://api.github.com/users/walterlv/gists{/gist_id}","starred_url":"https://api.github.com/users/walterlv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/walterlv/subscriptions","organizations_url":"https://api.github.com/users/walterlv/orgs","repos_url":"https://api.github.com/users/walterlv/repos","events_url":"https://api.github.com/users/walterlv/events{/privacy}","received_events_url":"https://api.github.com/users/walterlv/received_events","type":"User","site_admin":false},"body":"### New algorithm\r\n\r\n- Fix the bugs, see issue #1516 \r\n- Easier to read (Most methods are Pure, which means it doesn't modify states.)\r\n- Better performance (See the performace test below.)\r\n- The whole layout algorithm is totally different from the existed ones (such as the WPF one).\r\n\r\n### Performance test\r\n\r\nMy algorithmic time complexity is O(n), which means I enumerate the columns and rows separately only several times. Notice that the children count is different for each test case, so the elapsed difference between these test cases does not directly reflect the elapsed difference required by the algorithm.\r\n\r\nCreate 1 grid then measure and arrange children. *Each cell has a `TextBlock`.*\r\n\r\nTest case|Original `Grid`(ms)|`SimpleGrid`(ms)|WPF `Grid`(ms)|New `Grid`(ms)\r\n-|-|-|-|-\r\n300 × 3|199040|55|140|102\r\n300 × 300|200384|3489|11724|6370\r\n3000 × 3|**Too Long** (> 180 min)|336|1141|1602\r\n\r\nCreate 10,000 grid then measure and arrange children.\r\n\r\nTest case|Original elapsed(s)|New elapsed(s)\r\n-|-|-\r\n0 rows and 0 columns|0.5049|0.3067\r\n3 rows and 4 columns|1.4546|1.4930\r\n\r\n### Remarks\r\n\r\n- `MaxWidth`/`MaxHeight` behaviors different to the WPF version.\r\n- I can't implement all WPF's behaviors and I think that some of them are undefined. (I've written a post for these undefined behaviors. Read it for more details: [The undefined behaviors of WPF Grid (the so-called bugs) - walterlv](https://walterlv.github.io/post/the-bugs-of-grid-en.html))\r\n\r\n### Unit tests coverage\r\n\r\n![image](https://user-images.githubusercontent.com/9959623/39470768-27fea5f8-4d72-11e8-9c93-b8601d0eba27.png)\r\n\r\n![image](https://user-images.githubusercontent.com/9959623/39470808-55424466-4d72-11e8-9e34-a2cff7bf9f9f.png)\r\n\r\n---\r\n\r\n### Attached code\r\n\r\nThe performance test code:\r\n\r\n```csharp\r\nvar grid = new Grid();\r\nfor (var i = 0; i < 3000; i++) grid.RowDefinitions.Add(new RowDefinition(0.0, GridUnitType.Auto));\r\nfor (var j = 0; j < 3; j++) grid.ColumnDefinitions.Add(new ColumnDefinition(0.0, GridUnitType.Auto));\r\nfor (var i = 0; i < 3000; i++) for (var j = 0; j < 3; j++) grid.Children.Add(new TextBlock { Text = $\"{i},{i}\", [Grid.RowProperty] = i, [Grid.ColumnProperty] = j });\r\ngrid.Measure(Size.Infinity);\r\ngrid.Arrange(new Rect(grid.DesiredSize));\r\n```","created_at":"2018-04-22T09:23:15Z","updated_at":"2018-05-06T05:00:05Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2c00635a38939ecdee921272c683d0435295af70","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517/commits","review_comments_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517/comments","review_comment_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/comments{/number}","comments_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/issues/1517/comments","statuses_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/statuses/ad3e6e3496adcc3cb7354e467c39a3d26e4fadb3","head":{"label":"dotnet-campus:t/lvyi/fix-1516","ref":"t/lvyi/fix-1516","sha":"ad3e6e3496adcc3cb7354e467c39a3d26e4fadb3","user":{"login":"dotnet-campus","id":25194078,"avatar_url":"https://avatars3.githubusercontent.com/u/25194078?v=4","gravatar_id":"","url":"https://api.github.com/users/dotnet-campus","html_url":"https://github.com/dotnet-campus","followers_url":"https://api.github.com/users/dotnet-campus/followers","following_url":"https://api.github.com/users/dotnet-campus/following{/other_user}","gists_url":"https://api.github.com/users/dotnet-campus/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet-campus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet-campus/subscriptions","organizations_url":"https://api.github.com/users/dotnet-campus/orgs","repos_url":"https://api.github.com/users/dotnet-campus/repos","events_url":"https://api.github.com/users/dotnet-campus/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet-campus/received_events","type":"Organization","site_admin":false},"repo":{"id":115480478,"name":"Avalonia","full_name":"dotnet-campus/Avalonia","owner":{"login":"dotnet-campus","id":25194078,"avatar_url":"https://avatars3.githubusercontent.com/u/25194078?v=4","gravatar_id":"","url":"https://api.github.com/users/dotnet-campus","html_url":"https://github.com/dotnet-campus","followers_url":"https://api.github.com/users/dotnet-campus/followers","following_url":"https://api.github.com/users/dotnet-campus/following{/other_user}","gists_url":"https://api.github.com/users/dotnet-campus/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet-campus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet-campus/subscriptions","organizations_url":"https://api.github.com/users/dotnet-campus/orgs","repos_url":"https://api.github.com/users/dotnet-campus/repos","events_url":"https://api.github.com/users/dotnet-campus/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet-campus/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dotnet-campus/Avalonia","description":"A multi-platform .NET UI framework (formerly known as Perspex)","fork":true,"url":"https://api.github.com/repos/dotnet-campus/Avalonia","forks_url":"https://api.github.com/repos/dotnet-campus/Avalonia/forks","keys_url":"https://api.github.com/repos/dotnet-campus/Avalonia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dotnet-campus/Avalonia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dotnet-campus/Avalonia/teams","hooks_url":"https://api.github.com/repos/dotnet-campus/Avalonia/hooks","issue_events_url":"https://api.github.com/repos/dotnet-campus/Avalonia/issues/events{/number}","events_url":"https://api.github.com/repos/dotnet-campus/Avalonia/events","assignees_url":"https://api.github.com/repos/dotnet-campus/Avalonia/assignees{/user}","branches_url":"https://api.github.com/repos/dotnet-campus/Avalonia/branches{/branch}","tags_url":"https://api.github.com/repos/dotnet-campus/Avalonia/tags","blobs_url":"https://api.github.com/repos/dotnet-campus/Avalonia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dotnet-campus/Avalonia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dotnet-campus/Avalonia/git/refs{/sha}","trees_url":"https://api.github.com/repos/dotnet-campus/Avalonia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dotnet-campus/Avalonia/statuses/{sha}","languages_url":"https://api.github.com/repos/dotnet-campus/Avalonia/languages","stargazers_url":"https://api.github.com/repos/dotnet-campus/Avalonia/stargazers","contributors_url":"https://api.github.com/repos/dotnet-campus/Avalonia/contributors","subscribers_url":"https://api.github.com/repos/dotnet-campus/Avalonia/subscribers","subscription_url":"https://api.github.com/repos/dotnet-campus/Avalonia/subscription","commits_url":"https://api.github.com/repos/dotnet-campus/Avalonia/commits{/sha}","git_commits_url":"https://api.github.com/repos/dotnet-campus/Avalonia/git/commits{/sha}","comments_url":"https://api.github.com/repos/dotnet-campus/Avalonia/comments{/number}","issue_comment_url":"https://api.github.com/repos/dotnet-campus/Avalonia/issues/comments{/number}","contents_url":"https://api.github.com/repos/dotnet-campus/Avalonia/contents/{+path}","compare_url":"https://api.github.com/repos/dotnet-campus/Avalonia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dotnet-campus/Avalonia/merges","archive_url":"https://api.github.com/repos/dotnet-campus/Avalonia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dotnet-campus/Avalonia/downloads","issues_url":"https://api.github.com/repos/dotnet-campus/Avalonia/issues{/number}","pulls_url":"https://api.github.com/repos/dotnet-campus/Avalonia/pulls{/number}","milestones_url":"https://api.github.com/repos/dotnet-campus/Avalonia/milestones{/number}","notifications_url":"https://api.github.com/repos/dotnet-campus/Avalonia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dotnet-campus/Avalonia/labels{/name}","releases_url":"https://api.github.com/repos/dotnet-campus/Avalonia/releases{/id}","deployments_url":"https://api.github.com/repos/dotnet-campus/Avalonia/deployments","created_at":"2017-12-27T04:13:50Z","updated_at":"2018-04-28T13:41:22Z","pushed_at":"2018-05-06T04:15:52Z","git_url":"git://github.com/dotnet-campus/Avalonia.git","ssh_url":"git@github.com:dotnet-campus/Avalonia.git","clone_url":"https://github.com/dotnet-campus/Avalonia.git","svn_url":"https://github.com/dotnet-campus/Avalonia","homepage":"http://avaloniaui.net","size":49533,"stargazers_count":2,"watchers_count":2,"language":"C#","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":0,"open_issues":0,"watchers":2,"default_branch":"master"}},"base":{"label":"AvaloniaUI:master","ref":"master","sha":"c5090ee275c5faa01fcbb4d2cbce4ad4b7433537","user":{"login":"AvaloniaUI","id":14075148,"avatar_url":"https://avatars3.githubusercontent.com/u/14075148?v=4","gravatar_id":"","url":"https://api.github.com/users/AvaloniaUI","html_url":"https://github.com/AvaloniaUI","followers_url":"https://api.github.com/users/AvaloniaUI/followers","following_url":"https://api.github.com/users/AvaloniaUI/following{/other_user}","gists_url":"https://api.github.com/users/AvaloniaUI/gists{/gist_id}","starred_url":"https://api.github.com/users/AvaloniaUI/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AvaloniaUI/subscriptions","organizations_url":"https://api.github.com/users/AvaloniaUI/orgs","repos_url":"https://api.github.com/users/AvaloniaUI/repos","events_url":"https://api.github.com/users/AvaloniaUI/events{/privacy}","received_events_url":"https://api.github.com/users/AvaloniaUI/received_events","type":"Organization","site_admin":false},"repo":{"id":14964475,"name":"Avalonia","full_name":"AvaloniaUI/Avalonia","owner":{"login":"AvaloniaUI","id":14075148,"avatar_url":"https://avatars3.githubusercontent.com/u/14075148?v=4","gravatar_id":"","url":"https://api.github.com/users/AvaloniaUI","html_url":"https://github.com/AvaloniaUI","followers_url":"https://api.github.com/users/AvaloniaUI/followers","following_url":"https://api.github.com/users/AvaloniaUI/following{/other_user}","gists_url":"https://api.github.com/users/AvaloniaUI/gists{/gist_id}","starred_url":"https://api.github.com/users/AvaloniaUI/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AvaloniaUI/subscriptions","organizations_url":"https://api.github.com/users/AvaloniaUI/orgs","repos_url":"https://api.github.com/users/AvaloniaUI/repos","events_url":"https://api.github.com/users/AvaloniaUI/events{/privacy}","received_events_url":"https://api.github.com/users/AvaloniaUI/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/AvaloniaUI/Avalonia","description":"A multi-platform .NET UI framework","fork":false,"url":"https://api.github.com/repos/AvaloniaUI/Avalonia","forks_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/forks","keys_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/teams","hooks_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/hooks","issue_events_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/issues/events{/number}","events_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/events","assignees_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/assignees{/user}","branches_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/branches{/branch}","tags_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/tags","blobs_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/git/refs{/sha}","trees_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/statuses/{sha}","languages_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/languages","stargazers_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/stargazers","contributors_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/contributors","subscribers_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/subscribers","subscription_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/subscription","commits_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/commits{/sha}","git_commits_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/git/commits{/sha}","comments_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/comments{/number}","issue_comment_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/issues/comments{/number}","contents_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/contents/{+path}","compare_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/merges","archive_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/downloads","issues_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/issues{/number}","pulls_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls{/number}","milestones_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/milestones{/number}","notifications_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/labels{/name}","releases_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/releases{/id}","deployments_url":"https://api.github.com/repos/AvaloniaUI/Avalonia/deployments","created_at":"2013-12-05T20:13:21Z","updated_at":"2018-05-06T03:48:13Z","pushed_at":"2018-05-06T04:49:30Z","git_url":"git://github.com/AvaloniaUI/Avalonia.git","ssh_url":"git@github.com:AvaloniaUI/Avalonia.git","clone_url":"https://github.com/AvaloniaUI/Avalonia.git","svn_url":"https://github.com/AvaloniaUI/Avalonia","homepage":"http://avaloniaui.net","size":49557,"stargazers_count":4305,"watchers_count":4305,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":388,"mirror_url":null,"archived":false,"open_issues_count":232,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":388,"open_issues":232,"watchers":4305,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517"},"html":{"href":"https://github.com/AvaloniaUI/Avalonia/pull/1517"},"issue":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/issues/1517"},"comments":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/issues/1517/comments"},"review_comments":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517/comments"},"review_comment":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/pulls/1517/commits"},"statuses":{"href":"https://api.github.com/repos/AvaloniaUI/Avalonia/statuses/ad3e6e3496adcc3cb7354e467c39a3d26e4fadb3"}},"author_association":"NONE"}}
{"actor":{"display_login":"grokys"}}