浏览代码

MessCommit

wojciech krysiak 7 年之前
父节点
当前提交
269fc2a5d2
共有 1 个文件被更改,包括 223 次插入0 次删除
  1. 223 0
      src/Avalonia.Controls/Grid.cs

+ 223 - 0
src/Avalonia.Controls/Grid.cs

@@ -3,10 +3,12 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Runtime.CompilerServices;
 using Avalonia.Collections;
 using Avalonia.Controls.Utils;
+using Avalonia.VisualTree;
 using JetBrains.Annotations;
 
 namespace Avalonia.Controls
@@ -44,6 +46,189 @@ namespace Avalonia.Controls
         public static readonly AttachedProperty<int> RowSpanProperty =
             AvaloniaProperty.RegisterAttached<Grid, Control, int>("RowSpan", 1);
 
+        public static readonly AttachedProperty<bool> IsSharedSizeScopeProperty =
+            AvaloniaProperty.RegisterAttached<Grid, Control, bool>("IsSharedSizeScope", false);
+
+        private sealed class SharedSizeScopeHost : IDisposable
+        {
+            private class GridMeasureCache
+            {
+                public Grid Grid { get; }
+                public DefinitionBase Definition { get; }
+                public double CachedLength { get; set; }
+            }
+
+            private readonly AvaloniaList<Grid> _participatingGrids;
+
+            private Dictionary<string, double> _cachedSize = new Dictionary<string, double>();
+
+            private Dictionary<string, List<Grid>> _gridsInScopes = new Dictionary<string, List<Grid>>(); 
+
+            private Dictionary<string, List<GridMeasureCache>> _scopeCache;
+            private int _leftToMeasure;
+
+            public SharedSizeScopeHost(Control scope)
+            {
+                _participatingGrids = GetParticipatingGrids(scope);
+                
+                foreach (var grid in _participatingGrids)
+                {
+                    grid.InvalidateMeasure();
+                    AddGridToScopes(grid);
+                }
+            }
+
+            private bool _invalidating = false;
+
+            internal void InvalidateMeasure(Grid grid)
+            {
+                if (_invalidating)
+                    return;
+                _invalidating = true;
+
+                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());
+                }
+
+                _invalidating = false;
+            }
+
+            private void AddGridToScopes(Grid grid)
+            {
+                var scopeNames = grid.ColumnDefinitions.Select(g => g.SharedSizeGroup)
+                                 .Concat(grid.RowDefinitions.Select(g => g.SharedSizeGroup)).Distinct();
+                foreach (var scopeName in scopeNames)
+                {
+                    if (!_gridsInScopes.TryGetValue(scopeName, out var list))
+                        _gridsInScopes.Add(scopeName, list = new List<Grid>() );
+                    list.Add(grid);
+                }
+            }
+
+            private void RemoveGridFromScopes(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);
+                }
+            }
+
+            internal void UpdateMeasureResult(GridLayout.MeasureResult result, ColumnDefinitions columnDefinitions)
+            {
+                for (var i = 0; i < columnDefinitions.Count; i++)
+                {
+                    if (string.IsNullOrEmpty(columnDefinitions[i].SharedSizeGroup))
+                        continue;
+                    // if any in this group is Absolute we don't care about measured values.
+                    
+                }
+            }
+
+            internal void UpdateMeasureResult(GridLayout.MeasureResult result, RowDefinitions rowDefinitions)
+            {
+
+            }
+
+            internal double GetExistingLimit(DefinitionBase definition)
+            {
+                List<GridMeasureCache> cache = _scopeCache[definition.SharedSizeGroup];
+
+                return cache.Where(gmc => gmc.Grid.IsMeasureValid)
+                    .Aggregate(double.NaN, (a, gmc) => Math.Max(a, gmc.CachedLength));
+            }
+
+            internal void UpdateExistingLimit(DefinitionBase definition, double limit)
+            {
+                List<GridMeasureCache> cache = _scopeCache[definition.SharedSizeGroup];
+
+                cache.Single(gmc => ReferenceEquals(gmc.Definition, definition)).CachedLength = limit;
+                // if any other are lower - invalidate the grid.
+            }
+
+            internal void BeginMeasurePass()
+            {
+                if (_leftToMeasure == 0)
+                {
+                    _leftToMeasure = _participatingGrids.Count(g => !g.IsMeasureValid);
+                }
+            }
+
+            private static AvaloniaList<Grid> GetParticipatingGrids(Control scope)
+            {
+                var result = scope.GetVisualDescendants().OfType<Grid>();
+
+                return new AvaloniaList<Grid>(result.Where(g => g.HasSharedSizeGroups()));
+            }
+
+            public void Dispose()
+            {
+                foreach (var grid in _participatingGrids)
+                {
+                    grid.SharedScopeChanged();
+                }
+            }
+
+            internal void RegisterGrid(Grid toAdd)
+            {
+                Debug.Assert(!_participatingGrids.Contains(toAdd));
+                _participatingGrids.Add(toAdd);
+                AddGridToScopes(toAdd);
+            }
+
+            internal void UnegisterGrid(Grid toRemove)
+            {
+                Debug.Assert(_participatingGrids.Contains(toRemove));
+                _participatingGrids.Remove(toRemove);
+                RemoveGridFromScopes(toRemove);
+            }
+        }
+
+        protected override void OnMeasureInvalidated()
+        {
+            base.OnMeasureInvalidated();
+            _sharedSizeHost?.InvalidateMeasure(this);
+        }
+
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+            var scope = this.GetVisualAncestors().OfType<Control>()
+                .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty));
+
+            Debug.Assert(_sharedSizeHost == null);
+
+            if (scope != null)
+            {
+                _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty);
+                _sharedSizeHost.RegisterGrid(this);
+            }
+        }
+
+        protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromVisualTree(e);
+
+            _sharedSizeHost?.UnegisterGrid(this);
+            _sharedSizeHost = null;
+        }
+
+        private SharedSizeScopeHost _sharedSizeHost;
+
+        private static readonly AttachedProperty<SharedSizeScopeHost> s_sharedSizeScopeHostProperty =
+            AvaloniaProperty.RegisterAttached<Grid, Control, SharedSizeScopeHost>("&&SharedSizeScopeHost", null);
+
         private ColumnDefinitions _columnDefinitions;
 
         private RowDefinitions _rowDefinitions;
@@ -51,6 +236,23 @@ namespace Avalonia.Controls
         static Grid()
         {
             AffectsParentMeasure<Grid>(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
+            IsSharedSizeScopeProperty.Changed.AddClassHandler<Control>(IsSharedSizeScopeChanged);
+        }
+
+        private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2) 
+        {
+            if ((bool)arg2.NewValue)
+            {
+                Debug.Assert(source.GetValue(s_sharedSizeScopeHostProperty) == null);
+                source.SetValue(IsSharedSizeScopeProperty, new SharedSizeScopeHost(source));
+            }
+            else
+            {
+                var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost;
+                Debug.Assert(host != null);
+                host.Dispose();
+                source.SetValue(IsSharedSizeScopeProperty, null);
+            }
         }
 
         /// <summary>
@@ -426,5 +628,26 @@ namespace Avalonia.Controls
 
             return value;
         }
+
+        internal bool HasSharedSizeGroups()
+        {
+            return ColumnDefinitions.Any(cd => !string.IsNullOrEmpty(cd.SharedSizeGroup)) ||
+                   RowDefinitions.Any(rd => !string.IsNullOrEmpty(rd.SharedSizeGroup));
+        }
+
+        internal void SharedScopeChanged()
+        {
+            _sharedSizeHost = null;
+            var scope = this.GetVisualAncestors().OfType<Control>()
+                .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty));
+
+            if (scope != null)
+            {
+                _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty);
+                _sharedSizeHost.RegisterGrid(this);
+            }
+
+            InvalidateMeasure();
+        }
     }
 }