|
|
@@ -3,13 +3,9 @@
|
|
|
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
-using System.Collections.Specialized;
|
|
|
-using System.ComponentModel;
|
|
|
using System.Diagnostics;
|
|
|
using System.Linq;
|
|
|
-using System.Reactive.Disposables;
|
|
|
using System.Reactive.Linq;
|
|
|
-using System.Reactive.Subjects;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Avalonia.Collections;
|
|
|
using Avalonia.Controls.Utils;
|
|
|
@@ -54,393 +50,6 @@ namespace Avalonia.Controls
|
|
|
public static readonly AttachedProperty<bool> IsSharedSizeScopeProperty =
|
|
|
AvaloniaProperty.RegisterAttached<Grid, Control, bool>("IsSharedSizeScope", false);
|
|
|
|
|
|
- private sealed class SharedSizeScopeHost : IDisposable
|
|
|
- {
|
|
|
- private enum MeasurementState
|
|
|
- {
|
|
|
- Invalidated,
|
|
|
- Measuring,
|
|
|
- Cached
|
|
|
- }
|
|
|
-
|
|
|
- private sealed class MeasurementCache : IDisposable
|
|
|
- {
|
|
|
- CompositeDisposable _subscriptions;
|
|
|
-
|
|
|
- Subject<(string, string, MeasurementResult)> _groupChanged = new Subject<(string, string, MeasurementResult)>();
|
|
|
-
|
|
|
- public ISubject<(string oldName, string newName, MeasurementResult result)> GroupChanged => _groupChanged;
|
|
|
-
|
|
|
- public MeasurementCache(Grid grid)
|
|
|
- {
|
|
|
- Grid = grid;
|
|
|
- Results = grid.RowDefinitions.Cast<DefinitionBase>()
|
|
|
- .Concat(grid.ColumnDefinitions)
|
|
|
- .Select(d => new MeasurementResult(d))
|
|
|
- .ToList();
|
|
|
-
|
|
|
- grid.RowDefinitions.CollectionChanged += DefinitionsCollectionChanged;
|
|
|
- grid.ColumnDefinitions.CollectionChanged += DefinitionsCollectionChanged;
|
|
|
-
|
|
|
- _subscriptions = new CompositeDisposable(
|
|
|
- Disposable.Create(() => grid.RowDefinitions.CollectionChanged -= DefinitionsCollectionChanged),
|
|
|
- Disposable.Create(() => grid.ColumnDefinitions.CollectionChanged -= DefinitionsCollectionChanged),
|
|
|
- grid.RowDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged),
|
|
|
- grid.ColumnDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged));
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private void DefinitionPropertyChanged(Tuple<object, PropertyChangedEventArgs> propertyChanged)
|
|
|
- {
|
|
|
- if (propertyChanged.Item2.PropertyName == nameof(DefinitionBase.SharedSizeGroup))
|
|
|
- {
|
|
|
- var oldName = string.Empty;
|
|
|
- var newName = (propertyChanged.Item1 as DefinitionBase).SharedSizeGroup;
|
|
|
- var result = Results.Single(mr => ReferenceEquals(mr.Definition, propertyChanged.Item2));
|
|
|
- _groupChanged.OnNext((oldName, newName, result));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void DefinitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
|
- {
|
|
|
- int offset = 0;
|
|
|
- if (sender is ColumnDefinitions)
|
|
|
- offset = Grid.RowDefinitions.Count;
|
|
|
-
|
|
|
- var newItems = e.NewItems?.OfType<DefinitionBase>().Select(db => new MeasurementResult(db)).ToList() ?? new List<MeasurementResult>();
|
|
|
- var oldItems = Results.GetRange(e.OldStartingIndex + offset, e.OldItems?.Count ?? 0);
|
|
|
-
|
|
|
- void NotifyNewItems()
|
|
|
- {
|
|
|
- foreach (var item in newItems)
|
|
|
- {
|
|
|
- if (string.IsNullOrEmpty(item.Definition.SharedSizeGroup))
|
|
|
- continue;
|
|
|
-
|
|
|
- _groupChanged.OnNext((null, item.Definition.SharedSizeGroup, item));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- void NotifyOldItems()
|
|
|
- {
|
|
|
- foreach (var item in oldItems)
|
|
|
- {
|
|
|
- if (string.IsNullOrEmpty(item.Definition.SharedSizeGroup))
|
|
|
- continue;
|
|
|
-
|
|
|
- _groupChanged.OnNext((item.Definition.SharedSizeGroup, null, item));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- switch (e.Action)
|
|
|
- {
|
|
|
- case NotifyCollectionChangedAction.Add:
|
|
|
- Results.InsertRange(e.NewStartingIndex + offset, newItems);
|
|
|
- NotifyNewItems();
|
|
|
- break;
|
|
|
-
|
|
|
- case NotifyCollectionChangedAction.Remove:
|
|
|
- Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count);
|
|
|
- NotifyOldItems();
|
|
|
- break;
|
|
|
-
|
|
|
- case NotifyCollectionChangedAction.Move:
|
|
|
- Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count);
|
|
|
- Results.InsertRange(e.NewStartingIndex + offset, oldItems);
|
|
|
- break;
|
|
|
-
|
|
|
- case NotifyCollectionChangedAction.Replace:
|
|
|
- Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count);
|
|
|
- Results.InsertRange(e.NewStartingIndex + offset, newItems);
|
|
|
-
|
|
|
- NotifyOldItems();
|
|
|
- NotifyNewItems();
|
|
|
-
|
|
|
- break;
|
|
|
-
|
|
|
- case NotifyCollectionChangedAction.Reset:
|
|
|
- oldItems = Results;
|
|
|
- newItems = Results = Grid.RowDefinitions.Cast<DefinitionBase>()
|
|
|
- .Concat(Grid.ColumnDefinitions)
|
|
|
- .Select(d => new MeasurementResult(d))
|
|
|
- .ToList();
|
|
|
- NotifyOldItems();
|
|
|
- NotifyNewItems();
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void UpdateMeasureResult(GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
|
|
|
- {
|
|
|
- MeasurementState = MeasurementState.Cached;
|
|
|
- for (int i = 0; i < Grid.RowDefinitions.Count; i++)
|
|
|
- {
|
|
|
- Results[i].MeasuredResult = rowResult.LengthList[i];
|
|
|
- }
|
|
|
-
|
|
|
- for (int i = 0; i < Grid.ColumnDefinitions.Count; i++)
|
|
|
- {
|
|
|
- Results[i + rowResult.LengthList.Count].MeasuredResult = columnResult.LengthList[i];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void InvalidateMeasure()
|
|
|
- {
|
|
|
- MeasurementState = MeasurementState.Invalidated;
|
|
|
- Results.ForEach(r => r.MeasuredResult = double.NaN);
|
|
|
- }
|
|
|
-
|
|
|
- public void Dispose()
|
|
|
- {
|
|
|
- _subscriptions.Dispose();
|
|
|
- _groupChanged.OnCompleted();
|
|
|
- }
|
|
|
-
|
|
|
- public Grid Grid { get; }
|
|
|
- public MeasurementState MeasurementState { get; private set; }
|
|
|
-
|
|
|
- public List<MeasurementResult> Results { get; private set; }
|
|
|
- }
|
|
|
-
|
|
|
- private readonly AvaloniaList<MeasurementCache> _measurementCaches;
|
|
|
-
|
|
|
- private class MeasurementResult
|
|
|
- {
|
|
|
- public MeasurementResult(DefinitionBase definition)
|
|
|
- {
|
|
|
- Definition = definition;
|
|
|
- MeasuredResult = double.NaN;
|
|
|
- }
|
|
|
-
|
|
|
- public DefinitionBase Definition { get; }
|
|
|
- public double MeasuredResult { get; set; }
|
|
|
- }
|
|
|
-
|
|
|
- private class Group
|
|
|
- {
|
|
|
- public bool IsFixed { get; set; }
|
|
|
-
|
|
|
- public List<MeasurementResult> Results { get; } = new List<MeasurementResult>();
|
|
|
-
|
|
|
- public double CalculatedLength { get; }
|
|
|
- }
|
|
|
-
|
|
|
- private readonly Dictionary<string, Group> _groups = new Dictionary<string, Group>();
|
|
|
-
|
|
|
-
|
|
|
- public SharedSizeScopeHost(Control scope)
|
|
|
- {
|
|
|
- _measurementCaches = GetParticipatingGrids(scope);
|
|
|
-
|
|
|
- foreach (var cache in _measurementCaches)
|
|
|
- {
|
|
|
- cache.Grid.InvalidateMeasure();
|
|
|
- AddGridToScopes(cache);
|
|
|
-
|
|
|
- cache.GroupChanged.Subscribe(SharedGroupChanged);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- void SharedGroupChanged((string oldName, string newName, MeasurementResult result) change)
|
|
|
- {
|
|
|
- RemoveFromGroup(change.oldName, change.result);
|
|
|
- AddToGroup(change.newName, change.result);
|
|
|
- }
|
|
|
-
|
|
|
- internal void InvalidateMeasure(Grid grid)
|
|
|
- {
|
|
|
- var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
|
|
|
- Debug.Assert(cache != null);
|
|
|
-
|
|
|
- cache.InvalidateMeasure();
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
- private double Gather(IEnumerable<MeasurementResult> measurements)
|
|
|
- {
|
|
|
- var result = 0.0d;
|
|
|
-
|
|
|
- bool onlyFixed = false;
|
|
|
-
|
|
|
- foreach (var measurement in measurements)
|
|
|
- {
|
|
|
- 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;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- (List<GridLayout.LengthConvention>, List<double>, double) Arrange(IReadOnlyList<DefinitionBase> definitions, GridLayout.MeasureResult measureResult)
|
|
|
- {
|
|
|
- var conventions = measureResult.LeanLengthList.ToList();
|
|
|
- var lengths = measureResult.LengthList.ToList();
|
|
|
- var desiredLength = 0.0;
|
|
|
- for (int i = 0; i < definitions.Count; i++)
|
|
|
- {
|
|
|
- var definition = definitions[i];
|
|
|
- if (string.IsNullOrEmpty(definition.SharedSizeGroup))
|
|
|
- {
|
|
|
- desiredLength += measureResult.LengthList[i];
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- var group = _groups[definition.SharedSizeGroup];
|
|
|
-
|
|
|
- var length = Gather(group.Results);
|
|
|
-
|
|
|
- conventions[i] = new GridLayout.LengthConvention(
|
|
|
- new GridLength(length),
|
|
|
- measureResult.LeanLengthList[i].MinLength,
|
|
|
- measureResult.LeanLengthList[i].MaxLength
|
|
|
- );
|
|
|
- lengths[i] = length;
|
|
|
- desiredLength += length;
|
|
|
- }
|
|
|
-
|
|
|
- return (conventions, lengths, desiredLength);
|
|
|
- }
|
|
|
-
|
|
|
- internal (GridLayout.MeasureResult, GridLayout.MeasureResult) HandleArrange(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
|
|
|
- {
|
|
|
- var (rowConventions, rowLengths, rowDesiredLength) = Arrange(grid.RowDefinitions, rowResult);
|
|
|
- var (columnConventions, columnLengths, columnDesiredLength) = Arrange(grid.ColumnDefinitions, columnResult);
|
|
|
-
|
|
|
- return (
|
|
|
- new GridLayout.MeasureResult(
|
|
|
- rowResult.ContainerLength,
|
|
|
- rowDesiredLength,
|
|
|
- rowResult.GreedyDesiredLength,//??
|
|
|
- rowConventions,
|
|
|
- rowLengths),
|
|
|
- new GridLayout.MeasureResult(
|
|
|
- columnResult.ContainerLength,
|
|
|
- columnDesiredLength,
|
|
|
- columnResult.GreedyDesiredLength, //??
|
|
|
- columnConventions,
|
|
|
- columnLengths)
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- private void AddGridToScopes(MeasurementCache cache)
|
|
|
- {
|
|
|
- foreach (var result in cache.Results)
|
|
|
- {
|
|
|
- var scopeName = result.Definition.SharedSizeGroup;
|
|
|
- AddToGroup(scopeName, result);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void AddToGroup(string scopeName, MeasurementResult result)
|
|
|
- {
|
|
|
- if (string.IsNullOrEmpty(scopeName))
|
|
|
- return;
|
|
|
-
|
|
|
- if (!_groups.TryGetValue(scopeName, out var group))
|
|
|
- _groups.Add(scopeName, group = new Group());
|
|
|
-
|
|
|
- group.IsFixed |= IsFixed(result.Definition);
|
|
|
-
|
|
|
- group.Results.Add(result);
|
|
|
- }
|
|
|
-
|
|
|
- private bool IsFixed(DefinitionBase definition)
|
|
|
- {
|
|
|
- return ((definition as ColumnDefinition)?.Width ?? ((RowDefinition)definition).Height).IsAbsolute;
|
|
|
- }
|
|
|
-
|
|
|
- private void RemoveGridFromScopes(MeasurementCache cache)
|
|
|
- {
|
|
|
- foreach (var result in cache.Results)
|
|
|
- {
|
|
|
- var scopeName = result.Definition.SharedSizeGroup;
|
|
|
- RemoveFromGroup(scopeName, result);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void RemoveFromGroup(string scopeName, MeasurementResult result)
|
|
|
- {
|
|
|
- if (string.IsNullOrEmpty(scopeName))
|
|
|
- return;
|
|
|
-
|
|
|
- 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<MeasurementCache> GetParticipatingGrids(Control scope)
|
|
|
- {
|
|
|
- var result = scope.GetVisualDescendants().OfType<Grid>();
|
|
|
-
|
|
|
- return new AvaloniaList<MeasurementCache>(
|
|
|
- result.Where(g => g.HasSharedSizeGroups())
|
|
|
- .Select(g => new MeasurementCache(g)));
|
|
|
- }
|
|
|
-
|
|
|
- public void Dispose()
|
|
|
- {
|
|
|
- foreach (var cache in _measurementCaches)
|
|
|
- {
|
|
|
- cache.Grid.SharedScopeChanged();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- internal void RegisterGrid(Grid 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)
|
|
|
- {
|
|
|
- var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, toRemove));
|
|
|
-
|
|
|
- Debug.Assert(cache != null);
|
|
|
- _measurementCaches.Remove(cache);
|
|
|
- RemoveGridFromScopes(cache);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
protected override void OnMeasureInvalidated()
|
|
|
{
|
|
|
base.OnMeasureInvalidated();
|