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