Browse Source

Corrected found issues

wojciech krysiak 7 years ago
parent
commit
4b02fb375f
1 changed files with 140 additions and 54 deletions
  1. 140 54
      src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs

+ 140 - 54
src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs

@@ -8,6 +8,7 @@ using System.Reactive.Disposables;
 using System.Reactive.Subjects;
 using Avalonia.Collections;
 using Avalonia.Controls.Utils;
+using Avalonia.Layout;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
@@ -33,7 +34,7 @@ namespace Avalonia.Controls
                 Grid = grid;
                 Results = grid.RowDefinitions.Cast<DefinitionBase>()
                     .Concat(grid.ColumnDefinitions)
-                    .Select(d => new MeasurementResult(d))
+                    .Select(d => new MeasurementResult(grid, d))
                     .ToList();
 
                 grid.RowDefinitions.CollectionChanged += DefinitionsCollectionChanged;
@@ -51,9 +52,9 @@ namespace Avalonia.Controls
             {
                 if (propertyChanged.Item2.PropertyName == nameof(DefinitionBase.SharedSizeGroup))
                 {
-                    var oldName = string.Empty; // TODO: find how to determine the old name
-                    var newName = (propertyChanged.Item1 as DefinitionBase).SharedSizeGroup;
                     var result = Results.Single(mr => ReferenceEquals(mr.Definition, propertyChanged.Item1));
+                    var oldName = result.SizeGroup?.Name;
+                    var newName = (propertyChanged.Item1 as DefinitionBase).SharedSizeGroup;
                     _groupChanged.OnNext((oldName, newName, result));
                 }
             }
@@ -64,7 +65,7 @@ namespace Avalonia.Controls
                 if (sender is ColumnDefinitions)
                     offset = Grid.RowDefinitions.Count;
 
-                var newItems = e.NewItems?.OfType<DefinitionBase>().Select(db => new MeasurementResult(db)).ToList() ?? new List<MeasurementResult>();
+                var newItems = e.NewItems?.OfType<DefinitionBase>().Select(db => new MeasurementResult(Grid, db)).ToList() ?? new List<MeasurementResult>();
                 var oldItems = Results.GetRange(e.OldStartingIndex + offset, e.OldItems?.Count ?? 0);
 
                 void NotifyNewItems()
@@ -119,7 +120,7 @@ namespace Avalonia.Controls
                         oldItems = Results;
                         newItems = Results = Grid.RowDefinitions.Cast<DefinitionBase>()
                             .Concat(Grid.ColumnDefinitions)
-                            .Select(d => new MeasurementResult(d))
+                            .Select(d => new MeasurementResult(Grid, d))
                             .ToList();
                         NotifyOldItems();
                         NotifyNewItems();
@@ -145,7 +146,11 @@ namespace Avalonia.Controls
             public void InvalidateMeasure()
             {
                 MeasurementState = MeasurementState.Invalidated;
-                Results.ForEach(r => r.MeasuredResult = double.NaN);
+                Results.ForEach(r =>
+                {
+                    r.MeasuredResult = double.NaN;
+                    r.SizeGroup?.Reset();
+                });
             }
 
             public void Dispose()
@@ -160,31 +165,107 @@ namespace Avalonia.Controls
             public List<MeasurementResult> Results { get; private set; }
         }
 
-        private readonly AvaloniaList<MeasurementCache> _measurementCaches;
-
         private class MeasurementResult
         {
-            public MeasurementResult(DefinitionBase definition)
+            public MeasurementResult(Grid owningGrid, DefinitionBase definition)
             {
+                OwningGrid = owningGrid;
                 Definition = definition;
                 MeasuredResult = double.NaN;
             }
 
             public DefinitionBase Definition { get; }
             public double MeasuredResult { get; set; }
+            public Group SizeGroup { get; set; }
+            public Grid OwningGrid { get; }
         }
 
+
         private class Group
         {
+            private double? cachedResult;
+            private List<MeasurementResult> _results = new List<MeasurementResult>(); 
+
+            public string Name { get; }
+
+            public Group(string name)
+            {
+                Name = name;
+            }
+
             public bool IsFixed { get; set; }
 
-            public List<MeasurementResult> Results { get; } = new List<MeasurementResult>();
+            public IReadOnlyList<MeasurementResult> Results => _results;
+
+            public double CalculatedLength => (cachedResult ?? (cachedResult = Gather())).Value;
+
+            public void Reset()
+            {
+                cachedResult = null;
+            }
+
+            public void Add(MeasurementResult result)
+            {
+                if (!_results.Contains(result))
+                    throw new AvaloniaInternalException(
+                        $"Invalid call to Group.Add - The SharedSizeGroup {Name} already contains the passed result");
+
+                result.SizeGroup = this;
+                _results.Add(result);
+            }
+
+            public void Remove(MeasurementResult result)
+            {
+                if (!_results.Contains(result))
+                    throw new AvaloniaInternalException(
+                        $"Invalid call to Group.Remove - The SharedSizeGroup {Name} does not contain the passed result");
+                result.SizeGroup = null;
+                _results.Remove(result);
+            }
+
+
+            private double Gather()
+            {
+                var result = 0.0d;
+
+                bool onlyFixed = false;
+
+                foreach (var measurement in Results)
+                {
+                    if (measurement.Definition is ColumnDefinition column)
+                    {
+                        if (!onlyFixed && column.Width.IsAbsolute)
+                        {
+                            onlyFixed = true;
+                            result = measurement.MeasuredResult;
+                        }
+                        else if (onlyFixed == column.Width.IsAbsolute)
+                            result = Math.Max(result, measurement.MeasuredResult);
+
+                        result = Math.Max(result, column.MinWidth);
+                    }
+                    if (measurement.Definition is RowDefinition row)
+                    {
+                        if (!onlyFixed && row.Height.IsAbsolute)
+                        {
+                            onlyFixed = true;
+                            result = measurement.MeasuredResult;
+                        }
+                        else if (onlyFixed == row.Height.IsAbsolute)
+                            result = Math.Max(result, measurement.MeasuredResult);
+
+                        result = Math.Max(result, row.MinHeight);
+                    }
+                }
+
+                return result;
+            }
 
-            public double CalculatedLength { get; }
         }
 
-        private readonly Dictionary<string, Group> _groups = new Dictionary<string, Group>();
+        private readonly AvaloniaList<MeasurementCache> _measurementCaches;
 
+        private readonly Dictionary<string, Group> _groups = new Dictionary<string, Group>();
 
         public SharedSizeScopeHost(Control scope)
         {
@@ -205,59 +286,62 @@ namespace Avalonia.Controls
             AddToGroup(change.newName, change.result);
         }
 
+        private bool _invalidating;
+
         internal void InvalidateMeasure(Grid grid)
         {
-            var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
-            Debug.Assert(cache != null);
+            // prevent stack overflow
+            if (_invalidating)
+                return;
+            _invalidating = true;
 
-            cache.InvalidateMeasure();
+            InvalidateMeasureImpl(grid);
+
+            _invalidating = false;
         }
 
-        internal void UpdateMeasureStatus(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
+        private void InvalidateMeasureImpl(Grid grid)
         {
             var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
-            Debug.Assert(cache != null);
 
-            cache.UpdateMeasureResult(rowResult, columnResult);
-        }
+            if (cache == null)
+                throw new AvaloniaInternalException(
+                    $"InvalidateMeasureImpl - called with a grid not present in the internal cache");
 
-        private double Gather(IEnumerable<MeasurementResult> measurements)
-        {
-            var result = 0.0d;
+            // already invalidated the cache, early out.
+            if (cache.MeasurementState == MeasurementState.Invalidated)
+                return;
 
-            bool onlyFixed = false;
+            cache.InvalidateMeasure();
 
-            foreach (var measurement in measurements)
+            // maybe there is a condition to only call arrange on some of the calls?
+            grid.InvalidateMeasure();
+
+            // find all the scopes within the invalidated grid
+            var scopeNames = cache.Results
+                                  .Where(mr => mr.SizeGroup != null)
+                                  .Select(mr => mr.SizeGroup.Name)
+                                  .Distinct();
+            // find all grids related to those scopes
+            var otherGrids = scopeNames.SelectMany(sn => _groups[sn].Results)
+                                       .Select(r => r.OwningGrid)
+                                       .Where(g => g.IsMeasureValid)
+                                       .Distinct();
+
+            // invalidate them as well
+            foreach (var otherGrid in otherGrids)
             {
-                if (measurement.Definition is ColumnDefinition column)
-                {
-                    if (!onlyFixed && column.Width.IsAbsolute)
-                    {
-                        onlyFixed = true;
-                        result = measurement.MeasuredResult;
-                    }
-                    else if (onlyFixed == column.Width.IsAbsolute)
-                        result = Math.Max(result, measurement.MeasuredResult);
-
-                    result = Math.Max(result, column.MinWidth);
-                }
-                if (measurement.Definition is RowDefinition row)
-                {
-                    if (!onlyFixed && row.Height.IsAbsolute)
-                    {
-                        onlyFixed = true;
-                        result = measurement.MeasuredResult;
-                    }
-                    else if (onlyFixed == row.Height.IsAbsolute)
-                        result = Math.Max(result, measurement.MeasuredResult);
-
-                    result = Math.Max(result, row.MinHeight);
-                }
+                InvalidateMeasureImpl(otherGrid);
             }
-
-            return result;
         }
 
+        internal void UpdateMeasureStatus(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
+        {
+            var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
+            Debug.Assert(cache != null);
+
+            cache.UpdateMeasureResult(rowResult, columnResult);
+        }
 
         (List<GridLayout.LengthConvention>, List<double>, double) Arrange(IReadOnlyList<DefinitionBase> definitions, GridLayout.MeasureResult measureResult)
         {
@@ -275,7 +359,7 @@ namespace Avalonia.Controls
 
                 var group = _groups[definition.SharedSizeGroup];
 
-                var length = Gather(group.Results);
+                var length = group.CalculatedLength;
 
                 conventions[i] = new GridLayout.LengthConvention(
                     new GridLength(length),
@@ -326,11 +410,11 @@ namespace Avalonia.Controls
                 return;
 
             if (!_groups.TryGetValue(scopeName, out var group))
-                _groups.Add(scopeName, group = new Group());
+                _groups.Add(scopeName, group = new Group(scopeName));
 
             group.IsFixed |= IsFixed(result.Definition);
 
-            group.Results.Add(result);
+            group.Add(result);
         }
 
         private bool IsFixed(DefinitionBase definition)
@@ -354,7 +438,7 @@ namespace Avalonia.Controls
 
             Debug.Assert(_groups.TryGetValue(scopeName, out var group));
 
-            group.Results.Remove(result);
+            group.Remove(result);
             if (!group.Results.Any())
                 _groups.Remove(scopeName);
             else
@@ -377,6 +461,7 @@ namespace Avalonia.Controls
             foreach (var cache in _measurementCaches)
             {
                 cache.Grid.SharedScopeChanged();
+                cache.Dispose();
             }
         }
 
@@ -395,6 +480,7 @@ namespace Avalonia.Controls
             Debug.Assert(cache != null);
             _measurementCaches.Remove(cache);
             RemoveGridFromScopes(cache);
+            cache.Dispose();
         }
     }
 }