|
|
|
@ -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>()) |
|
|
|
{ |
|
|
|
|