diff --git a/src/Avalonia.Controls/Grid/ColumnDefinition.cs b/src/Avalonia.Controls/Grid/ColumnDefinition.cs index 56a6d41b7b..015484dbcc 100644 --- a/src/Avalonia.Controls/Grid/ColumnDefinition.cs +++ b/src/Avalonia.Controls/Grid/ColumnDefinition.cs @@ -88,10 +88,10 @@ namespace Avalonia.Controls set { SetValue(WidthProperty, value); } } - internal override GridLength UserSize => this.Width; + internal override GridLength UserSizeValueCache => this.Width; - internal override double UserMinSize => this.MinWidth; + internal override double UserMinSizeValueCache => this.MinWidth; - internal override double UserMaxSize => this.MaxWidth; + internal override double UserMaxSizeValueCache => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/Grid/DefinitionBase.cs b/src/Avalonia.Controls/Grid/DefinitionBase.cs index ad2f33a18a..e21d55c1f6 100644 --- a/src/Avalonia.Controls/Grid/DefinitionBase.cs +++ b/src/Avalonia.Controls/Grid/DefinitionBase.cs @@ -3,9 +3,7 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using Avalonia.Utilities; namespace Avalonia.Controls { @@ -14,6 +12,26 @@ namespace Avalonia.Controls /// public abstract class DefinitionBase : AvaloniaObject { + /// + /// Static ctor. Used for static registration of properties. + /// + static DefinitionBase() + { + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + } + internal bool UseSharedMinimum { get; set; } + internal bool LayoutWasUpdated { get; set; } + + private int _parentIndex = -1; // this instance's index in parent's children collection + private LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" + private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's + private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure + private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations + private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) + internal SharedSizeScope _privateSharedSizeScope; + private SharedSizeState _sharedState; // reference to shared state object this instance is registered with + private bool _successUpdateSharedScope; + /// /// Defines the property. /// @@ -28,31 +46,182 @@ namespace Avalonia.Controls get { return GetValue(SharedSizeGroupProperty); } set { SetValue(SharedSizeGroupProperty, value); } } + /// + /// Callback to notify about entering model tree. + /// + internal void OnEnterParentTree(Grid grid, int index) + { + Parent = grid; + _parentIndex = index; + } + + internal void UpdateSharedScope() + { + if (_sharedState == null & + SharedSizeGroup != null & + Parent?.sharedSizeScope != null & + !_successUpdateSharedScope) + { + _privateSharedSizeScope = Parent.sharedSizeScope; + _sharedState = _privateSharedSizeScope.EnsureSharedState(SharedSizeGroup); + _sharedState.AddMember(this); + _successUpdateSharedScope = true; + } + } + + internal Grid Parent { get; set; } /// - /// Internal helper to access up-to-date UserSize property value. + /// Callback to notify about exitting model tree. /// - internal abstract GridLength UserSize { get; } + internal void OnExitParentTree() + { + _offset = 0; + if (_sharedState != null) + { + _sharedState.RemoveMember(this); + _sharedState = null; + } + } /// - /// Internal helper to access up-to-date UserMinSize property value. + /// Performs action preparing definition to enter layout calculation mode. /// - internal abstract double UserMinSize { get; } + internal void OnBeforeLayout(Grid grid) + { + if (SharedSizeGroup != null) + UpdateSharedScope(); + + // reset layout state. + _minSize = 0; + LayoutWasUpdated = true; + + // defer verification for shared definitions + if (_sharedState != null) + { + _sharedState.EnsureDeferredValidation(grid); + } + } /// - /// Internal helper to access up-to-date UserMaxSize property value. + /// Updates min size. + /// + /// New size. + internal void UpdateMinSize(double minSize) + { + _minSize = Math.Max(_minSize, minSize); + } + + /// + /// Sets min size. + /// + /// New size. + internal void SetMinSize(double minSize) + { + _minSize = minSize; + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal void OnUserSizePropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + _sharedState?.Invalidate(); + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMinSizePropertyValueValid(object value) + { + double v = (double)value; + return (!double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserMaxSizePropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) + { + Grid parentGrid = (Grid)definition.Parent; + parentGrid.InvalidateMeasure(); + + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMaxSizePropertyValueValid(object value) + { + double v = (double)value; + return (!double.IsNaN(v) && v >= 0.0d); + } + + /// + /// Returns true if this definition is a part of shared group. + /// + internal bool IsShared + { + get { return (_sharedState != null); } + } + + /// + /// Internal accessor to user size field. + /// + internal GridLength UserSize + { + get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); } + } + + /// + /// Internal accessor to user min size field. + /// + internal double UserMinSize + { + get { return (UserMinSizeValueCache); } + } + + /// + /// Internal accessor to user max size field. /// - internal abstract double UserMaxSize { get; } + internal double UserMaxSize + { + get { return (UserMaxSizeValueCache); } + } + + /// + /// DefinitionBase's index in the parents collection. + /// + internal int Index + { + get + { + return (_parentIndex); + } + set + { + Debug.Assert(value >= -1 && _parentIndex != value); + _parentIndex = value; + } + } /// /// Layout-time user size type. /// - internal LayoutTimeSizeType SizeType { get; set; } - + internal LayoutTimeSizeType SizeType + { + get { return (_sizeType); } + set { _sizeType = value; } + } + /// /// Returns or sets measure size for the definition. /// - internal double MeasureSize { get; set; } + internal double MeasureSize + { + get { return (_measureSize); } + set { _measureSize = value; } + } /// /// Returns definition's layout time type sensitive preferred size. @@ -65,37 +234,108 @@ namespace Avalonia.Controls get { double preferredSize = MinSize; - if (SizeType != LayoutTimeSizeType.Auto - && preferredSize < MeasureSize) + if (_sizeType != LayoutTimeSizeType.Auto + && preferredSize < _measureSize) { - preferredSize = MeasureSize; + preferredSize = _measureSize; } - return (preferredSize); + return (preferredSize); } } /// /// Returns or sets size cache for the definition. /// - internal double SizeCache { get; set; } + internal double SizeCache + { + get { return (_sizeCache); } + set { _sizeCache = value; } + } /// - /// Used during measure and arrange to accumulate size for "Auto" and "Star" DefinitionBase's + /// Returns min size. /// - internal double MinSize { get; set; } + internal double MinSize + { + get + { + double minSize = _minSize; + if (UseSharedMinimum + && _sharedState != null + && minSize < _sharedState.MinSize) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } + } /// - /// Updates min size. + /// Returns min size, always taking into account shared state. /// - /// New size. - internal void UpdateMinSize(double minSize) + internal double MinSizeForArrange { - MinSize = Math.Max(MinSize, minSize); + get + { + double minSize = _minSize; + if (_sharedState != null + && (UseSharedMinimum || !LayoutWasUpdated) + && minSize < _sharedState.MinSize) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } } /// /// Offset. /// - internal double FinalOffset { get; set; } + internal double FinalOffset + { + get { return _offset; } + set { _offset = value; } + } + + /// + /// Internal helper to access up-to-date UserSize property value. + /// + internal abstract GridLength UserSizeValueCache { get; } + + /// + /// Internal helper to access up-to-date UserMinSize property value. + /// + internal abstract double UserMinSizeValueCache { get; } + + /// + /// Internal helper to access up-to-date UserMaxSize property value. + /// + internal abstract double UserMaxSizeValueCache { get; } + + private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) + { + string sharedSizeGroupId = (string)e.NewValue; + + if (definition._sharedState != null) + { + // if definition is already registered AND shared size group id is changing, + // then un-register the definition from the current shared size state object. + definition._sharedState.RemoveMember(definition); + definition._sharedState = null; + } + + if ((definition._sharedState == null) && (sharedSizeGroupId != null)) + { + var privateSharedSizeScope = definition._privateSharedSizeScope; + if (privateSharedSizeScope != null) + { + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + definition._sharedState.AddMember(definition); + + } + } + } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index 449b92ac52..c08c4396c5 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -86,6 +86,7 @@ namespace Avalonia.Controls private RowDefinitions _rowDefinitions; private DefinitionBase[] _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; private DefinitionBase[] _definitionsV = new DefinitionBase[1] { new RowDefinition() }; + internal SharedSizeScope sharedSizeScope; // 5 is an arbitrary constant chosen to end the measure loop private const int _layoutLoopMaxCount = 5; @@ -171,6 +172,9 @@ namespace Avalonia.Controls static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); + IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopePropertyChanged); + BoundsProperty.Changed.AddClassHandler(BoundsPropertyChanged); + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); @@ -181,6 +185,26 @@ namespace Avalonia.Controls _starWeightComparer = new StarWeightComparer(); } + private static void BoundsPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs arg2) + { + for (int i = 0; i < grid._definitionsU.Length; i++) + grid._definitionsU[i].OnUserSizePropertyChanged(arg2); + for (int i = 0; i < grid._definitionsV.Length; i++) + grid._definitionsV[i].OnUserSizePropertyChanged(arg2); + } + + private static void IsSharedSizeScopePropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) + { + if ((bool)e.NewValue) + { + grid.sharedSizeScope = new SharedSizeScope(); + } + else + { + grid.sharedSizeScope = null; + } + } + /// /// Defines the Column attached property. /// @@ -252,8 +276,12 @@ namespace Avalonia.Controls if (_columnDefinitions.Count > 0) _definitionsU = _columnDefinitions.Cast().ToArray(); + CallEnterParentTree(_definitionsU); + _columnDefinitions.CollectionChanged += delegate { + CallExitParentTree(_definitionsU); + if (_columnDefinitions.Count == 0) { _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; @@ -263,11 +291,26 @@ namespace Avalonia.Controls _definitionsU = _columnDefinitions.Cast().ToArray(); DefinitionsDirty = true; } + + CallEnterParentTree(_definitionsU); + Invalidate(); }; } } + private void CallEnterParentTree(DefinitionBase[] definitionsU) + { + for (int i = 0; i < definitionsU.Length; i++) + definitionsU[i].OnEnterParentTree(this, i); + } + + private void CallExitParentTree(DefinitionBase[] definitionsU) + { + for (int i = 0; i < definitionsU.Length; i++) + definitionsU[i].OnExitParentTree(); + } + /// /// Gets or sets the row definitions for the grid. /// @@ -294,6 +337,8 @@ namespace Avalonia.Controls _rowDefinitions.CollectionChanged += delegate { + CallExitParentTree(_definitionsU); + if (_rowDefinitions.Count == 0) { _definitionsV = new DefinitionBase[1] { new RowDefinition() }; @@ -303,6 +348,8 @@ namespace Avalonia.Controls _definitionsV = _rowDefinitions.Cast().ToArray(); DefinitionsDirty = true; } + CallEnterParentTree(_definitionsU); + Invalidate(); }; } @@ -359,6 +406,16 @@ namespace Avalonia.Controls return element.GetValue(IsSharedSizeScopeProperty); } + /// + /// Sets the value of the IsSharedSizeScope attached property for a control. + /// + /// The control. + /// The control's IsSharedSizeScope value. + public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) + { + element.SetValue(IsSharedSizeScopeProperty, value); + } + /// /// Sets the value of the Column attached property for a control. /// @@ -659,7 +716,7 @@ namespace Avalonia.Controls /// /// Invalidates grid caches and makes the grid dirty for measure. /// - private void Invalidate() + internal void Invalidate() { CellsStructureDirty = true; InvalidateMeasure(); @@ -780,8 +837,7 @@ namespace Avalonia.Controls { for (int i = 0; i < definitions.Length; ++i) { - // Reset minimum size. - definitions[i].MinSize = 0; + definitions[i].OnBeforeLayout(this); double userMinSize = definitions[i].UserMinSize; double userMaxSize = definitions[i].UserMaxSize; @@ -858,11 +914,11 @@ namespace Avalonia.Controls { if (isRows) { - _definitionsV[i].MinSize = minSizes[i]; + _definitionsV[i].SetMinSize(minSizes[i]); } else { - _definitionsU[i].MinSize = minSizes[i]; + _definitionsU[i].SetMinSize(minSizes[i]); } } } @@ -1710,7 +1766,7 @@ namespace Avalonia.Controls if (def.UserSize.IsStar) { - // Debug.Assert(!def.IsShared, "*-defs cannot be shared"); + Debug.Assert(!def.IsShared, "*-defs cannot be shared"); if (def.MeasureSize < 0.0) { @@ -1721,14 +1777,14 @@ namespace Avalonia.Controls double starWeight = StarWeight(def, scale); totalStarWeight += starWeight; - if (def.MinSize > 0.0) + if (def.MinSizeForArrange > 0.0) { // store ratio w/min in MeasureSize (for now) definitionIndices[minCount++] = i; - def.MeasureSize = starWeight / def.MinSize; + def.MeasureSize = starWeight / def.MinSizeForArrange; } - double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); + double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); if (!double.IsPositiveInfinity(effectiveMaxSize)) { // store ratio w/max in SizeCache (for now) @@ -1748,26 +1804,26 @@ namespace Avalonia.Controls break; case (GridUnitType.Auto): - userSize = def.MinSize; + userSize = def.MinSizeForArrange; break; } double userMaxSize; - // if (def.IsShared) - // { - // // overriding userMaxSize effectively prevents squishy-ness. - // // this is a "solution" to avoid shared definitions from been sized to - // // different final size at arrange time, if / when different grids receive - // // different final sizes. - // userMaxSize = userSize; - // } - // else - // { - userMaxSize = def.UserMaxSize; - // } - - def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize)); + if (def.IsShared) + { + // overriding userMaxSize effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + userMaxSize = userSize; + } + else + { + userMaxSize = def.UserMaxSize; + } + + def.SizeCache = Math.Max(def.MinSizeForArrange, Math.Min(userSize, userMaxSize)); takenSize += def.SizeCache; } } @@ -1831,14 +1887,14 @@ namespace Avalonia.Controls { resolvedIndex = definitionIndices[minCount - 1]; resolvedDef = definitions[resolvedIndex]; - resolvedSize = resolvedDef.MinSize; + resolvedSize = resolvedDef.MinSizeForArrange; --minCount; } else { resolvedIndex = definitionIndices[defCount + maxCount - 1]; resolvedDef = definitions[resolvedIndex]; - resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); + resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize); --maxCount; } @@ -1961,7 +2017,7 @@ namespace Avalonia.Controls // min and max should have no effect by now, but just in case... resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); - resolvedSize = Math.Max(def.MinSize, resolvedSize); + resolvedSize = Math.Max(def.MinSizeForArrange, resolvedSize); // Use the raw (unrounded) sizes to update takenSize, so that // proportions are computed in the same terms as in phase 3; @@ -2053,7 +2109,7 @@ namespace Avalonia.Controls { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache - dpiIncrement; - final = Math.Max(final, definition.MinSize); + final = Math.Max(final, definition.MinSizeForArrange); if (final < definition.SizeCache) { adjustedSize -= dpiIncrement; @@ -2069,7 +2125,7 @@ namespace Avalonia.Controls { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; - final = Math.Max(final, definition.MinSize); + final = Math.Max(final, definition.MinSizeForArrange); if (final > definition.SizeCache) { adjustedSize += dpiIncrement; diff --git a/src/Avalonia.Controls/Grid/RowDefinition.cs b/src/Avalonia.Controls/Grid/RowDefinition.cs index d9120b010e..1cb09e16e9 100644 --- a/src/Avalonia.Controls/Grid/RowDefinition.cs +++ b/src/Avalonia.Controls/Grid/RowDefinition.cs @@ -89,10 +89,10 @@ namespace Avalonia.Controls } - internal override GridLength UserSize => this.Height; + internal override GridLength UserSizeValueCache => this.Height; - internal override double UserMinSize => this.MinHeight; + internal override double UserMinSizeValueCache => this.MinHeight; - internal override double UserMaxSize => this.MaxHeight; + internal override double UserMaxSizeValueCache => this.MaxHeight; } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SharedSizeScope.cs b/src/Avalonia.Controls/Grid/SharedSizeScope.cs new file mode 100644 index 0000000000..6835d13132 --- /dev/null +++ b/src/Avalonia.Controls/Grid/SharedSizeScope.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics; + +namespace Avalonia.Controls +{ + /// + /// Collection of shared states objects for a single scope + /// + internal class SharedSizeScope + { + /// + /// Returns SharedSizeState object for a given group. + /// Creates a new StatedState object if necessary. + /// + internal SharedSizeState EnsureSharedState(string sharedSizeGroup) + { + // check that sharedSizeGroup is not default + Debug.Assert(sharedSizeGroup != null); + + SharedSizeState sharedState = _registry[sharedSizeGroup] as SharedSizeState; + if (sharedState == null) + { + sharedState = new SharedSizeState(this, sharedSizeGroup); + _registry[sharedSizeGroup] = sharedState; + } + return (sharedState); + } + + /// + /// Removes an entry in the registry by the given key. + /// + internal void Remove(object key) + { + Debug.Assert(_registry.Contains(key)); + _registry.Remove(key); + } + + private Hashtable _registry = new Hashtable(); // storage for shared state objects + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SharedSizeState.cs b/src/Avalonia.Controls/Grid/SharedSizeState.cs new file mode 100644 index 0000000000..2b99c09861 --- /dev/null +++ b/src/Avalonia.Controls/Grid/SharedSizeState.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Implementation of per shared group state object + /// + internal class SharedSizeState + { + private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to + private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing + private readonly List _registry; // registry of participating definitions + private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event + private Control _layoutUpdatedHost; // Control for which layout updated event handler is registered + private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed + private bool _userSizeValid; // "true" when _userSize is up to date + private GridLength _userSize; // shared state + private double _minSize; // shared state + + /// + /// Default ctor. + /// + internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) + { + Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); + _sharedSizeScope = sharedSizeScope; + _sharedSizeGroupId = sharedSizeGroupId; + _registry = new List(); + _layoutUpdated = new EventHandler(OnLayoutUpdated); + _broadcastInvalidation = true; + } + + /// + /// Adds / registers a definition instance. + /// + internal void AddMember(DefinitionBase member) + { + Debug.Assert(!_registry.Contains(member)); + _registry.Add(member); + Invalidate(); + } + + /// + /// Removes / un-registers a definition instance. + /// + /// + /// If the collection of registered definitions becomes empty + /// instantiates self removal from owner's collection. + /// + internal void RemoveMember(DefinitionBase member) + { + Invalidate(); + _registry.Remove(member); + + if (_registry.Count == 0) + { + _sharedSizeScope.Remove(_sharedSizeGroupId); + } + } + + /// + /// Propogates invalidations for all registered definitions. + /// Resets its own state. + /// + internal void Invalidate() + { + _userSizeValid = false; + + if (_broadcastInvalidation) + { + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Grid parentGrid = (Grid)(_registry[i].Parent); + parentGrid.Invalidate(); + } + _broadcastInvalidation = false; + } + } + + /// + /// Makes sure that one and only one layout updated handler is registered for this shared state. + /// + internal void EnsureDeferredValidation(Control layoutUpdatedHost) + { + if (_layoutUpdatedHost == null) + { + _layoutUpdatedHost = layoutUpdatedHost; + _layoutUpdatedHost.LayoutUpdated += _layoutUpdated; + } + } + + /// + /// DefinitionBase's specific code. + /// + internal double MinSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_minSize); + } + } + + /// + /// DefinitionBase's specific code. + /// + internal GridLength UserSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_userSize); + } + } + + private void EnsureUserSizeValid() + { + _userSize = new GridLength(1, GridUnitType.Auto); + + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto + || _userSize.GridUnitType == GridUnitType.Pixel); + + GridLength currentGridLength = _registry[i].UserSizeValueCache; + if (currentGridLength.GridUnitType == GridUnitType.Pixel) + { + if (_userSize.GridUnitType == GridUnitType.Auto) + { + _userSize = currentGridLength; + } + else if (_userSize.Value < currentGridLength.Value) + { + _userSize = currentGridLength; + } + } + } + // taking maximum with user size effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + _minSize = _userSize.IsAbsolute ? _userSize.Value : 0.0; + + _userSizeValid = true; + } + + /// + /// OnLayoutUpdated handler. Validates that all participating definitions + /// have updated min size value. Forces another layout update cycle if needed. + /// + private void OnLayoutUpdated(object sender, EventArgs e) + { + double sharedMinSize = 0; + + // accumulate min size of all participating definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); + } + + bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); + + // compare accumulated min size with min sizes of the individual definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + DefinitionBase definitionBase = _registry[i]; + + if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) + { + // if definition's min size is different, then need to re-measure + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateMeasure(); + definitionBase.UseSharedMinimum = true; + } + else + { + definitionBase.UseSharedMinimum = false; + + // if measure is valid then also need to check arrange. + // Note: definitionBase.SizeCache is volatile but at this point + // it contains up-to-date final size + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateArrange(); + } + } + + definitionBase.LayoutWasUpdated = false; + } + } + + _minSize = sharedMinSize; + + _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; + _layoutUpdatedHost = null; + + _broadcastInvalidation = true; + } + } +} \ No newline at end of file