|
|
|
@ -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(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|