wojciech krysiak 7 年 前
コミット
2754649edd
2 ファイル変更190 行追加83 行削除
  1. 187 80
      src/Avalonia.Controls/Grid.cs
  2. 3 3
      src/Avalonia.Controls/Utils/GridLayout.cs

+ 187 - 80
src/Avalonia.Controls/Grid.cs

@@ -51,147 +51,249 @@ namespace Avalonia.Controls
 
         private sealed class SharedSizeScopeHost : IDisposable
         {
-            private class GridMeasureCache
+            private enum MeasurementState
             {
-                public Grid Grid { get; }
-                public DefinitionBase Definition { get; }
-                public double CachedLength { get; set; }
+                Invalidated,
+                Measuring,
+                Cached
             }
 
-            private readonly AvaloniaList<Grid> _participatingGrids;
+            private class MeasurementCache
+            {
+                public MeasurementCache(Grid grid)
+                {
+                    Grid = grid;
+                    Results = grid.RowDefinitions.Cast<DefinitionBase>()
+                                 .Concat(grid.ColumnDefinitions)
+                                 .Select(d => new MeasurementResult(d))
+                                 .ToList();
+                }
 
-            private Dictionary<string, double> _cachedSize = new Dictionary<string, double>();
+                public void UpdateMeasureResult(GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
+                {
+                    RowResult = rowResult;
+                    ColumnResult = columnResult;
+                    MeasurementState = MeasurementState.Cached;
+                    for (int i = 0; i < rowResult.LengthList.Count; i++)
+                    {
+                        Results[i].MeasuredResult = rowResult.LengthList[i];
+                    }
+
+                    for (int i = 0; i < columnResult.LengthList.Count; i++)
+                    {
+                        Results[i + rowResult.LengthList.Count].MeasuredResult = columnResult.LengthList[i];
+                    }
+                }
 
-            private Dictionary<string, List<Grid>> _gridsInScopes = new Dictionary<string, List<Grid>>(); 
+                public void InvalidateMeasure()
+                {
+                    MeasurementState = MeasurementState.Invalidated;
+                    Results.ForEach(r => r.MeasuredResult = double.NaN);
+                }
+
+                public Grid Grid { get; }
+                public GridLayout.MeasureResult RowResult { get; private set; }
+                public GridLayout.MeasureResult ColumnResult { get; private set; }
+                public MeasurementState MeasurementState { get; private set; }
 
-            private Dictionary<string, List<GridMeasureCache>> _scopeCache;
-            private int _leftToMeasure;
+                public List<MeasurementResult> Results { get; }
+            }
 
-            public SharedSizeScopeHost(Control scope)
+            private readonly AvaloniaList<MeasurementCache> _measurementCaches;
+
+            private class MeasurementResult
             {
-                _participatingGrids = GetParticipatingGrids(scope);
-                
-                foreach (var grid in _participatingGrids)
+                public MeasurementResult(DefinitionBase @base)
                 {
-                    grid.InvalidateMeasure();
-                    AddGridToScopes(grid);
+                    Definition = @base;
+                    MeasuredResult = double.NaN;
                 }
+
+                public DefinitionBase Definition { get; }
+                public double MeasuredResult { get; set; }
             }
 
-            private bool _invalidating = false;
+            private enum ScopeType
+            {
+                Auto,
+                Fixed
+            }
 
-            internal void InvalidateMeasure(Grid grid)
+            private class Group
             {
-                if (_invalidating)
-                    return;
-                _invalidating = true;
+                public bool IsFixed { get; set; }
 
-                List<Grid> candidates = new List<Grid> {grid};
-                while (candidates.Any())
-                {
-                    var scopes = candidates.SelectMany(c => c.RowDefinitions.Select(rd => rd.SharedSizeGroup))
-                         .Concat(candidates.SelectMany(c => c.ColumnDefinitions.Select(rd => rd.SharedSizeGroup))).Distinct();
-                    
-                    candidates = scopes.SelectMany(r => _scopeCache[r].Select(gmc => gmc.Grid))
-                                 .Distinct().Where(c => c.IsMeasureValid).ToList();
-                    candidates.ForEach(c => c.InvalidateMeasure());
-                }
+                public List<MeasurementResult> Results { get; }
 
-                _invalidating = false;
+                public double CalculatedLength { get; }
             }
 
-            private void AddGridToScopes(Grid grid)
+            private Dictionary<string, Group> _groups = new Dictionary<string, Group>();
+
+
+            public SharedSizeScopeHost(Control scope)
             {
-                var scopeNames = grid.ColumnDefinitions.Select(g => g.SharedSizeGroup)
-                                 .Concat(grid.RowDefinitions.Select(g => g.SharedSizeGroup)).Distinct();
-                foreach (var scopeName in scopeNames)
+                _measurementCaches = GetParticipatingGrids(scope);
+                
+                foreach (var cache in _measurementCaches)
                 {
-                    if (!_gridsInScopes.TryGetValue(scopeName, out var list))
-                        _gridsInScopes.Add(scopeName, list = new List<Grid>() );
-                    list.Add(grid);
+                    cache.Grid.InvalidateMeasure();
+                    AddGridToScopes(cache);
                 }
             }
 
-            private void RemoveGridFromScopes(Grid grid)
+            internal void InvalidateMeasure(Grid grid)
             {
-                var scopeNames = grid.ColumnDefinitions.Select(g => g.SharedSizeGroup)
-                    .Concat(grid.RowDefinitions.Select(g => g.SharedSizeGroup)).Distinct();
-                foreach (var scopeName in scopeNames)
-                {
-                    Debug.Assert(_gridsInScopes.TryGetValue(scopeName, out var list));
-                    list.Remove(grid);
-                    if (!list.Any())
-                        _gridsInScopes.Remove(scopeName);
-                }
+                var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
+                Debug.Assert(cache != null);
+
+                cache.InvalidateMeasure();
             }
 
-            internal void UpdateMeasureResult(GridLayout.MeasureResult result, ColumnDefinitions columnDefinitions)
+            internal void UpdateMeasureStatus(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
             {
-                for (var i = 0; i < columnDefinitions.Count; i++)
+                var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
+                Debug.Assert(cache != null);
+
+                cache.UpdateMeasureResult(rowResult, columnResult);
+            }
+
+            internal (GridLayout.MeasureResult, GridLayout.MeasureResult) HandleArrange(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
+            {
+                var rowConventions = rowResult.LeanLengthList.ToList();
+                var rowLengths = rowResult.LengthList.ToList();
+                var rowDesiredLength = 0.0;
+                for (int i = 0; i < grid.RowDefinitions.Count; i++)
                 {
-                    if (string.IsNullOrEmpty(columnDefinitions[i].SharedSizeGroup))
+                    var definition = grid.RowDefinitions[i];
+                    if (string.IsNullOrEmpty(definition.SharedSizeGroup))
+                    {
+                        rowDesiredLength += rowResult.LengthList[i];
                         continue;
-                    // if any in this group is Absolute we don't care about measured values.
-                    
+                    }
+
+                    var group = _groups[definition.SharedSizeGroup];
+
+                    var length = group.Results.Max(g => g.MeasuredResult);
+                    rowConventions[i] = new GridLayout.LengthConvention(
+                        new GridLength(length),
+                        rowResult.LeanLengthList[i].MinLength,
+                        rowResult.LeanLengthList[i].MaxLength
+                        );
+                    rowLengths[i] = length;
+                    rowDesiredLength += length;
+
                 }
-            }
 
-            internal void UpdateMeasureResult(GridLayout.MeasureResult result, RowDefinitions rowDefinitions)
-            {
+                var columnConventions = columnResult.LeanLengthList.ToList();
+                var columnLengths = columnResult.LengthList.ToList();
+                var columnDesiredLength = 0.0;
+                for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
+                {
+                    var definition = grid.ColumnDefinitions[i];
+                    if (string.IsNullOrEmpty(definition.SharedSizeGroup))
+                    {
+                        columnDesiredLength += rowResult.LengthList[i];
+                        continue;
+                    }
+
+                    var group = _groups[definition.SharedSizeGroup];
+
+                    var length = group.Results.Max(g => g.MeasuredResult);
+                    columnConventions[i] = new GridLayout.LengthConvention(
+                        new GridLength(length),
+                        columnResult.LeanLengthList[i].MinLength,
+                        columnResult.LeanLengthList[i].MaxLength
+                        );
+                    columnLengths[i] = length;
+                    columnDesiredLength += length;
+                }
 
+                return (
+                    new GridLayout.MeasureResult(
+                        rowResult.ContainerLength,
+                        rowDesiredLength,
+                        rowResult.GreedyDesiredLength,//??
+                        rowConventions,
+                        rowLengths),
+                    new GridLayout.MeasureResult(
+                        columnResult.ContainerLength,
+                        columnDesiredLength,
+                        columnResult.GreedyDesiredLength, //??
+                        columnConventions,
+                        columnLengths)
+                    );
             }
 
-            internal double GetExistingLimit(DefinitionBase definition)
+
+            private void AddGridToScopes(MeasurementCache cache)
             {
-                List<GridMeasureCache> cache = _scopeCache[definition.SharedSizeGroup];
+                foreach (var result in cache.Results)
+                {
+                    var scopeName = result.Definition.SharedSizeGroup;
+                    if (!_groups.TryGetValue(scopeName, out var group))
+                        _groups.Add(scopeName, group = new Group());
+
+                    group.IsFixed |= IsFixed(result.Definition);
 
-                return cache.Where(gmc => gmc.Grid.IsMeasureValid)
-                    .Aggregate(double.NaN, (a, gmc) => Math.Max(a, gmc.CachedLength));
+                    group.Results.Add(result);
+                }
             }
 
-            internal void UpdateExistingLimit(DefinitionBase definition, double limit)
+            private bool IsFixed(DefinitionBase definition)
             {
-                List<GridMeasureCache> cache = _scopeCache[definition.SharedSizeGroup];
-
-                cache.Single(gmc => ReferenceEquals(gmc.Definition, definition)).CachedLength = limit;
-                // if any other are lower - invalidate the grid.
+                return ((definition as ColumnDefinition)?.Width ?? ((RowDefinition)definition).Height).IsAbsolute;
             }
 
-            internal void BeginMeasurePass()
+            private void RemoveGridFromScopes(MeasurementCache cache)
             {
-                if (_leftToMeasure == 0)
+                foreach (var result in cache.Results)
                 {
-                    _leftToMeasure = _participatingGrids.Count(g => !g.IsMeasureValid);
+                    var scopeName = result.Definition.SharedSizeGroup;
+                    Debug.Assert(_groups.TryGetValue(scopeName, out var group));
+
+                    group.Results.Remove(result);
+                    if (!group.Results.Any())
+                        _groups.Remove(scopeName);
+                    else
+                    {
+                        group.IsFixed = group.Results.Select(r => r.Definition).Any(IsFixed);
+                    }
                 }
             }
 
-            private static AvaloniaList<Grid> GetParticipatingGrids(Control scope)
+            private static AvaloniaList<MeasurementCache> GetParticipatingGrids(Control scope)
             {
                 var result = scope.GetVisualDescendants().OfType<Grid>();
 
-                return new AvaloniaList<Grid>(result.Where(g => g.HasSharedSizeGroups()));
+                return new AvaloniaList<MeasurementCache>(
+                    result.Where(g => g.HasSharedSizeGroups())
+                          .Select(g => new MeasurementCache(g)));
             }
 
             public void Dispose()
             {
-                foreach (var grid in _participatingGrids)
+                foreach (var cache in _measurementCaches)
                 {
-                    grid.SharedScopeChanged();
+                    cache.Grid.SharedScopeChanged();
                 }
             }
 
             internal void RegisterGrid(Grid toAdd)
             {
-                Debug.Assert(!_participatingGrids.Contains(toAdd));
-                _participatingGrids.Add(toAdd);
-                AddGridToScopes(toAdd);
+                Debug.Assert(!_measurementCaches.Any(mc => ReferenceEquals(mc.Grid,toAdd)));
+                var cache = new MeasurementCache(toAdd);
+                _measurementCaches.Add(cache);
+                AddGridToScopes(cache);
             }
 
             internal void UnegisterGrid(Grid toRemove)
             {
-                Debug.Assert(_participatingGrids.Contains(toRemove));
-                _participatingGrids.Remove(toRemove);
-                RemoveGridFromScopes(toRemove);
+                var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, toRemove));
+
+                Debug.Assert(cache != null);
+                _measurementCaches.Remove(cache);
+                RemoveGridFromScopes(cache);
             }
         }
 
@@ -473,6 +575,8 @@ namespace Avalonia.Controls
             _rowLayoutCache = rowLayout;
             _columnLayoutCache = columnLayout;
 
+            _sharedSizeHost?.UpdateMeasureStatus(this, rowResult, columnResult);
+
             return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
 
             // Measure each child only once.
@@ -521,9 +625,12 @@ namespace Avalonia.Controls
             var (safeColumns, safeRows) = GetSafeColumnRows();
             var columnLayout = _columnLayoutCache;
             var rowLayout = _rowLayoutCache;
+
+            var (rowCache, columnCache) = _sharedSizeHost?.HandleArrange(this, _rowMeasureCache, _columnMeasureCache) ?? (_rowMeasureCache, _columnMeasureCache);
+
             // Calculate for arrange result.
-            var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache);
-            var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache);
+            var columnResult = columnLayout.Arrange(finalSize.Width, rowCache);
+            var rowResult = rowLayout.Arrange(finalSize.Height, columnCache);
             // Arrange the children.
             foreach (var child in Children.OfType<Control>())
             {

+ 3 - 3
src/Avalonia.Controls/Utils/GridLayout.cs

@@ -147,10 +147,10 @@ namespace Avalonia.Controls.Utils
         /// The measured result that containing the desired size and all the column/row lengths.
         /// </returns>
         [NotNull, Pure]
-        internal MeasureResult Measure(double containerLength)
+        internal MeasureResult Measure(double containerLength, IReadOnlyList<LengthConvention> conventions = null)
         {
             // Prepare all the variables that this method needs to use.
-            var conventions = _conventions.Select(x => x.Clone()).ToList();
+            conventions = 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;
@@ -306,7 +306,7 @@ namespace Avalonia.Controls.Utils
             if (finalLength - measure.ContainerLength > LayoutTolerance)
             {
                 // If the final length is larger, we will rerun the whole measure.
-                measure = Measure(finalLength);
+                measure = Measure(finalLength, measure.LeanLengthList);
             }
             else if (finalLength - measure.ContainerLength < -LayoutTolerance)
             {