diff --git a/src/Avalonia.Controls/Grid/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs
similarity index 93%
rename from src/Avalonia.Controls/Grid/ColumnDefinition.cs
rename to src/Avalonia.Controls/ColumnDefinition.cs
index 015484dbcc..d316881a05 100644
--- a/src/Avalonia.Controls/Grid/ColumnDefinition.cs
+++ b/src/Avalonia.Controls/ColumnDefinition.cs
@@ -87,11 +87,5 @@ namespace Avalonia.Controls
get { return GetValue(WidthProperty); }
set { SetValue(WidthProperty, value); }
}
-
- internal override GridLength UserSizeValueCache => this.Width;
-
- internal override double UserMinSizeValueCache => this.MinWidth;
-
- internal override double UserMaxSizeValueCache => this.MaxWidth;
}
}
diff --git a/src/Avalonia.Controls/Grid/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs
similarity index 100%
rename from src/Avalonia.Controls/Grid/ColumnDefinitions.cs
rename to src/Avalonia.Controls/ColumnDefinitions.cs
diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs
new file mode 100644
index 0000000000..4878523a70
--- /dev/null
+++ b/src/Avalonia.Controls/DefinitionBase.cs
@@ -0,0 +1,992 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// Specs
+// Grid : Grid.mht
+// Size Sharing: Size Information Sharing.doc
+//
+// Misc
+// Grid Tutorial: Grid Tutorial.mht
+//
+// Description: Implementation of base abstract class for ColumnDefinition
+// and RowDefinition.
+//
+
+using MS.Internal;
+using MS.Internal.KnownBoxes;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Security.Permissions;
+using System.Windows;
+using System.Windows.Threading;
+
+namespace System.Windows.Controls
+{
+ ///
+ /// DefinitionBase provides core functionality used internally by Grid
+ /// and ColumnDefinitionCollection / RowDefinitionCollection
+ ///
+ [Localizability(LocalizationCategory.Ignore)]
+ public abstract class DefinitionBase : FrameworkContentElement
+ {
+ //------------------------------------------------------
+ //
+ // Constructors
+ //
+ //------------------------------------------------------
+
+ #region Constructors
+
+ internal DefinitionBase(bool isColumnDefinition)
+ {
+ _isColumnDefinition = isColumnDefinition;
+ _parentIndex = -1;
+ }
+
+ #endregion Constructors
+
+ //------------------------------------------------------
+ //
+ // Public Properties
+ //
+ //------------------------------------------------------
+
+ #region Public Properties
+
+ ///
+ /// SharedSizeGroup property.
+ ///
+ public string SharedSizeGroup
+ {
+ get { return (string) GetValue(SharedSizeGroupProperty); }
+ set { SetValue(SharedSizeGroupProperty, value); }
+ }
+
+ #endregion Public Properties
+
+ //------------------------------------------------------
+ //
+ // Internal Methods
+ //
+ //------------------------------------------------------
+
+ #region Internal Methods
+
+ ///
+ /// Callback to notify about entering model tree.
+ ///
+ internal void OnEnterParentTree()
+ {
+ if (_sharedState == null)
+ {
+ // start with getting SharedSizeGroup value.
+ // this property is NOT inhereted which should result in better overall perf.
+ string sharedSizeGroupId = SharedSizeGroup;
+ if (sharedSizeGroupId != null)
+ {
+ SharedSizeScope privateSharedSizeScope = PrivateSharedSizeScope;
+ if (privateSharedSizeScope != null)
+ {
+ _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
+ _sharedState.AddMember(this);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Callback to notify about exitting model tree.
+ ///
+ internal void OnExitParentTree()
+ {
+ _offset = 0;
+ if (_sharedState != null)
+ {
+ _sharedState.RemoveMember(this);
+ _sharedState = null;
+ }
+ }
+
+ ///
+ /// Performs action preparing definition to enter layout calculation mode.
+ ///
+ internal void OnBeforeLayout(Grid grid)
+ {
+ // reset layout state.
+ _minSize = 0;
+ LayoutWasUpdated = true;
+
+ // defer verification for shared definitions
+ if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); }
+ }
+
+ ///
+ /// 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 static void OnUserSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ DefinitionBase definition = (DefinitionBase) d;
+
+ if (definition.InParentLogicalTree)
+ {
+ if (definition._sharedState != null)
+ {
+ definition._sharedState.Invalidate();
+ }
+ else
+ {
+ Grid parentGrid = (Grid) definition.Parent;
+
+ if (((GridLength) e.OldValue).GridUnitType != ((GridLength) e.NewValue).GridUnitType)
+ {
+ parentGrid.Invalidate();
+ }
+ else
+ {
+ parentGrid.InvalidateMeasure();
+ }
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// This method needs to be internal to be accessable from derived classes.
+ ///
+ internal static bool IsUserSizePropertyValueValid(object value)
+ {
+ return (((GridLength)value).Value >= 0);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// This method needs to be internal to be accessable from derived classes.
+ ///
+ internal static void OnUserMinSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ DefinitionBase definition = (DefinitionBase) d;
+
+ if (definition.InParentLogicalTree)
+ {
+ Grid parentGrid = (Grid) definition.Parent;
+ parentGrid.InvalidateMeasure();
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// This method needs to be internal to be accessable from derived classes.
+ ///
+ internal static bool IsUserMinSizePropertyValueValid(object value)
+ {
+ double v = (double)value;
+ return (!DoubleUtil.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v));
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// This method needs to be internal to be accessable from derived classes.
+ ///
+ internal static void OnUserMaxSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ DefinitionBase definition = (DefinitionBase) d;
+
+ if (definition.InParentLogicalTree)
+ {
+ 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 (!DoubleUtil.IsNaN(v) && v >= 0.0d);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// This method reflects Grid.SharedScopeProperty state by setting / clearing
+ /// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty
+ /// is a collection of SharedSizeState objects for the scope.
+ /// Also PrivateSharedSizeScopeProperty is FrameworkPropertyMetadataOptions.Inherits property. So that all children
+ /// elements belonging to a certain scope can easily access SharedSizeState collection. As well
+ /// as been norified about enter / exit a scope.
+ ///
+ internal static void OnIsSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ // is it possible to optimize here something like this:
+ // if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null)
+ // { /* do nothing */ }
+ if ((bool) e.NewValue)
+ {
+ SharedSizeScope sharedStatesCollection = new SharedSizeScope();
+ d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection);
+ }
+ else
+ {
+ d.ClearValue(PrivateSharedSizeScopeProperty);
+ }
+ }
+
+ #endregion Internal Methods
+
+ //------------------------------------------------------
+ //
+ // Internal Properties
+ //
+ //------------------------------------------------------
+
+ #region Internal Properties
+
+ ///
+ /// 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 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 Grid.LayoutTimeSizeType SizeType
+ {
+ get { return (_sizeType); }
+ set { _sizeType = value; }
+ }
+
+ ///
+ /// Returns or sets measure size for the definition.
+ ///
+ internal double MeasureSize
+ {
+ get { return (_measureSize); }
+ set { _measureSize = value; }
+ }
+
+ ///
+ /// Returns definition's layout time type sensitive preferred size.
+ ///
+ ///
+ /// Returned value is guaranteed to be true preferred size.
+ ///
+ internal double PreferredSize
+ {
+ get
+ {
+ double preferredSize = MinSize;
+ if ( _sizeType != Grid.LayoutTimeSizeType.Auto
+ && preferredSize < _measureSize )
+ {
+ preferredSize = _measureSize;
+ }
+ return (preferredSize);
+ }
+ }
+
+ ///
+ /// Returns or sets size cache for the definition.
+ ///
+ internal double SizeCache
+ {
+ get { return (_sizeCache); }
+ set { _sizeCache = value; }
+ }
+
+ ///
+ /// Returns min size.
+ ///
+ internal double MinSize
+ {
+ get
+ {
+ double minSize = _minSize;
+ if ( UseSharedMinimum
+ && _sharedState != null
+ && minSize < _sharedState.MinSize )
+ {
+ minSize = _sharedState.MinSize;
+ }
+ return (minSize);
+ }
+ }
+
+ ///
+ /// Returns min size, always taking into account shared state.
+ ///
+ internal double MinSizeForArrange
+ {
+ get
+ {
+ double minSize = _minSize;
+ if ( _sharedState != null
+ && (UseSharedMinimum || !LayoutWasUpdated)
+ && minSize < _sharedState.MinSize )
+ {
+ minSize = _sharedState.MinSize;
+ }
+ return (minSize);
+ }
+ }
+
+ ///
+ /// Offset.
+ ///
+ internal double FinalOffset
+ {
+ get { return _offset; }
+ set { _offset = value; }
+ }
+
+ ///
+ /// Internal helper to access up-to-date UserSize property value.
+ ///
+ internal GridLength UserSizeValueCache
+ {
+ get
+ {
+ return (GridLength) GetValue(
+ _isColumnDefinition ?
+ ColumnDefinition.WidthProperty :
+ RowDefinition.HeightProperty);
+ }
+ }
+
+ ///
+ /// Internal helper to access up-to-date UserMinSize property value.
+ ///
+ internal double UserMinSizeValueCache
+ {
+ get
+ {
+ return (double) GetValue(
+ _isColumnDefinition ?
+ ColumnDefinition.MinWidthProperty :
+ RowDefinition.MinHeightProperty);
+ }
+ }
+
+ ///
+ /// Internal helper to access up-to-date UserMaxSize property value.
+ ///
+ internal double UserMaxSizeValueCache
+ {
+ get
+ {
+ return (double) GetValue(
+ _isColumnDefinition ?
+ ColumnDefinition.MaxWidthProperty :
+ RowDefinition.MaxHeightProperty);
+ }
+ }
+
+ ///
+ /// Protected. Returns true if this DefinitionBase instance is in parent's logical tree.
+ ///
+ internal bool InParentLogicalTree
+ {
+ get { return (_parentIndex != -1); }
+ }
+
+ #endregion Internal Properties
+
+ //------------------------------------------------------
+ //
+ // Private Methods
+ //
+ //------------------------------------------------------
+
+ #region Private Methods
+
+ ///
+ /// SetFlags is used to set or unset one or multiple
+ /// flags on the object.
+ ///
+ private void SetFlags(bool value, Flags flags)
+ {
+ _flags = value ? (_flags | flags) : (_flags & (~flags));
+ }
+
+ ///
+ /// CheckFlagsAnd returns true if all the flags in the
+ /// given bitmask are set on the object.
+ ///
+ private bool CheckFlagsAnd(Flags flags)
+ {
+ return ((_flags & flags) == flags);
+ }
+
+ ///
+ ///
+ ///
+ private static void OnSharedSizeGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ DefinitionBase definition = (DefinitionBase) d;
+
+ if (definition.InParentLogicalTree)
+ {
+ 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))
+ {
+ SharedSizeScope 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);
+ }
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// Verifies that Shared Size Group Property string
+ /// a) not empty.
+ /// b) contains only letters, digits and underscore ('_').
+ /// c) does not start with a digit.
+ ///
+ private static bool SharedSizeGroupPropertyValueValid(object value)
+ {
+ // null is default value
+ if (value == null)
+ {
+ return (true);
+ }
+
+ string id = (string)value;
+
+ if (id != string.Empty)
+ {
+ int i = -1;
+ while (++i < id.Length)
+ {
+ bool isDigit = Char.IsDigit(id[i]);
+
+ if ( (i == 0 && isDigit)
+ || !( isDigit
+ || Char.IsLetter(id[i])
+ || '_' == id[i] ) )
+ {
+ break;
+ }
+ }
+
+ if (i == id.Length)
+ {
+ return (true);
+ }
+ }
+
+ return (false);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or
+ /// existing scope just left. In both cases if the DefinitionBase object is already registered
+ /// in SharedSizeState, it should un-register and register itself in a new one.
+ ///
+ private static void OnPrivateSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ DefinitionBase definition = (DefinitionBase)d;
+
+ if (definition.InParentLogicalTree)
+ {
+ SharedSizeScope privateSharedSizeScope = (SharedSizeScope) e.NewValue;
+
+ if (definition._sharedState != null)
+ {
+ // if definition is already registered And shared size scope 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) && (privateSharedSizeScope != null))
+ {
+ string sharedSizeGroup = definition.SharedSizeGroup;
+ if (sharedSizeGroup != 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(definition.SharedSizeGroup);
+ definition._sharedState.AddMember(definition);
+ }
+ }
+ }
+ }
+
+ #endregion Private Methods
+
+ //------------------------------------------------------
+ //
+ // Private Properties
+ //
+ //------------------------------------------------------
+
+ #region Private Properties
+
+ ///
+ /// Private getter of shared state collection dynamic property.
+ ///
+ private SharedSizeScope PrivateSharedSizeScope
+ {
+ get { return (SharedSizeScope) GetValue(PrivateSharedSizeScopeProperty); }
+ }
+
+ ///
+ /// Convenience accessor to UseSharedMinimum flag
+ ///
+ private bool UseSharedMinimum
+ {
+ get { return (CheckFlagsAnd(Flags.UseSharedMinimum)); }
+ set { SetFlags(value, Flags.UseSharedMinimum); }
+ }
+
+ ///
+ /// Convenience accessor to LayoutWasUpdated flag
+ ///
+ private bool LayoutWasUpdated
+ {
+ get { return (CheckFlagsAnd(Flags.LayoutWasUpdated)); }
+ set { SetFlags(value, Flags.LayoutWasUpdated); }
+ }
+
+ #endregion Private Properties
+
+ //------------------------------------------------------
+ //
+ // Private Fields
+ //
+ //------------------------------------------------------
+
+ #region Private Fields
+ private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check)
+ private Flags _flags; // flags reflecting various aspects of internal state
+ private int _parentIndex; // this instance's index in parent's children collection
+
+ private Grid.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)
+
+ private SharedSizeState _sharedState; // reference to shared state object this instance is registered with
+
+ internal const bool ThisIsColumnDefinition = true;
+ internal const bool ThisIsRowDefinition = false;
+
+ #endregion Private Fields
+
+ //------------------------------------------------------
+ //
+ // Private Structures / Classes
+ //
+ //------------------------------------------------------
+
+ #region Private Structures Classes
+
+ [System.Flags]
+ private enum Flags : byte
+ {
+ //
+ // bool flags
+ //
+ UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum
+ LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured
+ }
+
+ ///
+ /// Collection of shared states objects for a single scope
+ ///
+ private 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
+ }
+
+ ///
+ /// Implementation of per shared group state object
+ ///
+ private class SharedSizeState
+ {
+ ///
+ /// 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(UIElement 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 = !DoubleUtil.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 (!DoubleUtil.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 (!DoubleUtil.AreClose(sharedMinSize, definitionBase.SizeCache))
+ {
+ Grid parentGrid = (Grid)definitionBase.Parent;
+ parentGrid.InvalidateArrange();
+ }
+ }
+
+ definitionBase.LayoutWasUpdated = false;
+ }
+ }
+
+ _minSize = sharedMinSize;
+
+ _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated;
+ _layoutUpdatedHost = null;
+
+ _broadcastInvalidation = true;
+ }
+
+ 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 UIElement _layoutUpdatedHost; // UIElement 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
+ }
+
+ #endregion Private Structures Classes
+
+ //------------------------------------------------------
+ //
+ // Properties
+ //
+ //------------------------------------------------------
+
+ #region Properties
+
+ ///
+ /// Private shared size scope property holds a collection of shared state objects for the a given shared size scope.
+ ///
+ ///
+ internal static readonly DependencyProperty PrivateSharedSizeScopeProperty =
+ DependencyProperty.RegisterAttached(
+ "PrivateSharedSizeScope",
+ typeof(SharedSizeScope),
+ typeof(DefinitionBase),
+ new FrameworkPropertyMetadata(
+ null,
+ FrameworkPropertyMetadataOptions.Inherits));
+
+ ///
+ /// Shared size group property marks column / row definition as belonging to a group "Foo" or "Bar".
+ ///
+ ///
+ /// Value of the Shared Size Group Property must satisfy the following rules:
+ ///
+ /// -
+ /// String must not be empty.
+ ///
+ /// -
+ /// String must consist of letters, digits and underscore ('_') only.
+ ///
+ /// -
+ /// String must not start with a digit.
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty SharedSizeGroupProperty =
+ DependencyProperty.Register(
+ "SharedSizeGroup",
+ typeof(string),
+ typeof(DefinitionBase),
+ new FrameworkPropertyMetadata(new PropertyChangedCallback(OnSharedSizeGroupPropertyChanged)),
+ new ValidateValueCallback(SharedSizeGroupPropertyValueValid));
+
+ ///
+ /// Static ctor. Used for static registration of properties.
+ ///
+ static DefinitionBase()
+ {
+ PrivateSharedSizeScopeProperty.OverrideMetadata(
+ typeof(DefinitionBase),
+ new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPrivateSharedSizeScopePropertyChanged)));
+ }
+
+ #endregion Properties
+ }
+}
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
new file mode 100644
index 0000000000..fa310b73ba
--- /dev/null
+++ b/src/Avalonia.Controls/Grid.cs
@@ -0,0 +1,4424 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// Description: Grid implementation.
+//
+// Specs
+// Grid : Grid.mht
+// Size Sharing: Size Information Sharing.doc
+//
+// Misc
+// Grid Tutorial: Grid Tutorial.mht
+//
+
+using MS.Internal;
+using MS.Internal.Controls;
+using MS.Internal.PresentationFramework;
+using MS.Internal.Telemetry.PresentationFramework;
+using MS.Utility;
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Windows.Threading;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Media;
+using System.Windows.Markup;
+
+#pragma warning disable 1634, 1691 // suppressing PreSharp warnings
+
+namespace System.Windows.Controls
+{
+ ///
+ /// Grid
+ ///
+ public class Grid : Panel, IAddChild
+ {
+ //------------------------------------------------------
+ //
+ // Constructors
+ //
+ //------------------------------------------------------
+
+ #region Constructors
+
+ static Grid()
+ {
+ ControlsTraceLogger.AddControl(TelemetryControls.Grid);
+ }
+
+ ///
+ /// Default constructor.
+ ///
+ public Grid()
+ {
+ SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(DependencyObjectType), Flags.ShowGridLinesPropertyValue);
+ }
+
+ #endregion Constructors
+
+ //------------------------------------------------------
+ //
+ // Public Methods
+ //
+ //------------------------------------------------------
+
+ #region Public Methods
+
+ ///
+ ///
+ ///
+ void IAddChild.AddChild(object value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+
+ UIElement cell = value as UIElement;
+ if (cell != null)
+ {
+ Children.Add(cell);
+ return;
+ }
+
+ throw (new ArgumentException(SR.Get(SRID.Grid_UnexpectedParameterType, value.GetType(), typeof(UIElement)), "value"));
+ }
+
+ ///
+ ///
+ ///
+ void IAddChild.AddText(string text)
+ {
+ XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this);
+ }
+
+ ///
+ ///
+ ///
+ protected internal override IEnumerator LogicalChildren
+ {
+ get
+ {
+ // empty panel or a panel being used as the items
+ // host has *no* logical children; give empty enumerator
+ bool noChildren = (base.VisualChildrenCount == 0) || IsItemsHost;
+
+ if (noChildren)
+ {
+ ExtendedData extData = ExtData;
+
+ if ( extData == null
+ || ( (extData.ColumnDefinitions == null || extData.ColumnDefinitions.Count == 0)
+ && (extData.RowDefinitions == null || extData.RowDefinitions.Count == 0) )
+ )
+ {
+ // grid is empty
+ return EmptyEnumerator.Instance;
+ }
+ }
+
+ return (new GridChildrenCollectionEnumeratorSimple(this, !noChildren));
+ }
+ }
+
+ ///
+ /// Helper for setting Column property on a UIElement.
+ ///
+ /// UIElement to set Column property on.
+ /// Column property value.
+ public static void SetColumn(UIElement element, int value)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ element.SetValue(ColumnProperty, value);
+ }
+
+ ///
+ /// Helper for reading Column property from a UIElement.
+ ///
+ /// UIElement to read Column property from.
+ /// Column property value.
+ [AttachedPropertyBrowsableForChildren()]
+ public static int GetColumn(UIElement element)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ return ((int)element.GetValue(ColumnProperty));
+ }
+
+ ///
+ /// Helper for setting Row property on a UIElement.
+ ///
+ /// UIElement to set Row property on.
+ /// Row property value.
+ public static void SetRow(UIElement element, int value)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ element.SetValue(RowProperty, value);
+ }
+
+ ///
+ /// Helper for reading Row property from a UIElement.
+ ///
+ /// UIElement to read Row property from.
+ /// Row property value.
+ [AttachedPropertyBrowsableForChildren()]
+ public static int GetRow(UIElement element)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ return ((int)element.GetValue(RowProperty));
+ }
+
+ ///
+ /// Helper for setting ColumnSpan property on a UIElement.
+ ///
+ /// UIElement to set ColumnSpan property on.
+ /// ColumnSpan property value.
+ public static void SetColumnSpan(UIElement element, int value)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ element.SetValue(ColumnSpanProperty, value);
+ }
+
+ ///
+ /// Helper for reading ColumnSpan property from a UIElement.
+ ///
+ /// UIElement to read ColumnSpan property from.
+ /// ColumnSpan property value.
+ [AttachedPropertyBrowsableForChildren()]
+ public static int GetColumnSpan(UIElement element)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ return ((int)element.GetValue(ColumnSpanProperty));
+ }
+
+ ///
+ /// Helper for setting RowSpan property on a UIElement.
+ ///
+ /// UIElement to set RowSpan property on.
+ /// RowSpan property value.
+ public static void SetRowSpan(UIElement element, int value)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ element.SetValue(RowSpanProperty, value);
+ }
+
+ ///
+ /// Helper for reading RowSpan property from a UIElement.
+ ///
+ /// UIElement to read RowSpan property from.
+ /// RowSpan property value.
+ [AttachedPropertyBrowsableForChildren()]
+ public static int GetRowSpan(UIElement element)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ return ((int)element.GetValue(RowSpanProperty));
+ }
+
+ ///
+ /// Helper for setting IsSharedSizeScope property on a UIElement.
+ ///
+ /// UIElement to set IsSharedSizeScope property on.
+ /// IsSharedSizeScope property value.
+ public static void SetIsSharedSizeScope(UIElement element, bool value)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ element.SetValue(IsSharedSizeScopeProperty, value);
+ }
+
+ ///
+ /// Helper for reading IsSharedSizeScope property from a UIElement.
+ ///
+ /// UIElement to read IsSharedSizeScope property from.
+ /// IsSharedSizeScope property value.
+ public static bool GetIsSharedSizeScope(UIElement element)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+
+ return ((bool)element.GetValue(IsSharedSizeScopeProperty));
+ }
+
+ #endregion Public Methods
+
+ //------------------------------------------------------
+ //
+ // Public Properties
+ //
+ //------------------------------------------------------
+
+ #region Public Properties
+
+ ///
+ /// ShowGridLines property.
+ ///
+ public bool ShowGridLines
+ {
+ get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); }
+ set { SetValue(ShowGridLinesProperty, value); }
+ }
+
+ ///
+ /// Returns a ColumnDefinitionCollection of column definitions.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
+ public ColumnDefinitionCollection ColumnDefinitions
+ {
+ get
+ {
+ if (_data == null) { _data = new ExtendedData(); }
+ if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitionCollection(this); }
+
+ return (_data.ColumnDefinitions);
+ }
+ }
+
+ ///
+ /// Returns a RowDefinitionCollection of row definitions.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
+ public RowDefinitionCollection RowDefinitions
+ {
+ get
+ {
+ if (_data == null) { _data = new ExtendedData(); }
+ if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitionCollection(this); }
+
+ return (_data.RowDefinitions);
+ }
+ }
+
+ #endregion Public Properties
+
+ //------------------------------------------------------
+ //
+ // Protected Methods
+ //
+ //------------------------------------------------------
+
+ #region Protected Methods
+
+ ///
+ /// Derived class must implement to support Visual children. The method must return
+ /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
+ ///
+ /// By default a Visual does not have any children.
+ ///
+ /// Remark:
+ /// During this virtual call it is not valid to modify the Visual tree.
+ ///
+ protected override Visual GetVisualChild(int index)
+ {
+ // because "base.Count + 1" for GridLinesRenderer
+ // argument checking done at the base class
+ if(index == base.VisualChildrenCount)
+ {
+ if (_gridLinesRenderer == null)
+ {
+ throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange));
+ }
+ return _gridLinesRenderer;
+ }
+ else return base.GetVisualChild(index);
+ }
+
+ ///
+ /// Derived classes override this property to enable the Visual code to enumerate
+ /// the Visual children. Derived classes need to return the number of children
+ /// from this method.
+ ///
+ /// By default a Visual does not have any children.
+ ///
+ /// Remark: During this virtual method the Visual tree must not be modified.
+ ///
+ protected override int VisualChildrenCount
+ {
+ //since GridLinesRenderer has not been added as a child, so we do not subtract
+ get { return base.VisualChildrenCount + (_gridLinesRenderer != null ? 1 : 0); }
+ }
+
+
+ ///
+ /// Content measurement.
+ ///
+ /// Constraint
+ /// Desired size
+ protected override Size MeasureOverride(Size constraint)
+ {
+ Size gridDesiredSize;
+ ExtendedData extData = ExtData;
+
+ try
+ {
+ EnterCounterScope(Counters.MeasureOverride);
+
+ ListenToNotifications = true;
+ MeasureOverrideInProgress = true;
+
+ if (extData == null)
+ {
+ gridDesiredSize = new Size();
+ UIElementCollection children = InternalChildren;
+
+ for (int i = 0, count = children.Count; i < count; ++i)
+ {
+ UIElement child = children[i];
+ if (child != null)
+ {
+ child.Measure(constraint);
+ gridDesiredSize.Width = Math.Max(gridDesiredSize.Width, child.DesiredSize.Width);
+ gridDesiredSize.Height = Math.Max(gridDesiredSize.Height, child.DesiredSize.Height);
+ }
+ }
+ }
+ else
+ {
+ {
+ bool sizeToContentU = double.IsPositiveInfinity(constraint.Width);
+ bool sizeToContentV = double.IsPositiveInfinity(constraint.Height);
+
+ // Clear index information and rounding errors
+ if (RowDefinitionCollectionDirty || ColumnDefinitionCollectionDirty)
+ {
+ if (_definitionIndices != null)
+ {
+ Array.Clear(_definitionIndices, 0, _definitionIndices.Length);
+ _definitionIndices = null;
+ }
+
+ if (UseLayoutRounding)
+ {
+ if (_roundingErrors != null)
+ {
+ Array.Clear(_roundingErrors, 0, _roundingErrors.Length);
+ _roundingErrors = null;
+ }
+ }
+ }
+
+ ValidateDefinitionsUStructure();
+ ValidateDefinitionsLayout(DefinitionsU, sizeToContentU);
+
+ ValidateDefinitionsVStructure();
+ ValidateDefinitionsLayout(DefinitionsV, sizeToContentV);
+
+ CellsStructureDirty |= (SizeToContentU != sizeToContentU) || (SizeToContentV != sizeToContentV);
+
+ SizeToContentU = sizeToContentU;
+ SizeToContentV = sizeToContentV;
+ }
+
+ ValidateCells();
+
+ Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0);
+
+ // Grid classifies cells into four groups depending on
+ // the column / row type a cell belongs to (number corresponds to
+ // group number):
+ //
+ // Px Auto Star
+ // +--------+--------+--------+
+ // | | | |
+ // Px | 1 | 1 | 3 |
+ // | | | |
+ // +--------+--------+--------+
+ // | | | |
+ // Auto | 1 | 1 | 3 |
+ // | | | |
+ // +--------+--------+--------+
+ // | | | |
+ // Star | 4 | 2 | 4 |
+ // | | | |
+ // +--------+--------+--------+
+ //
+ // The group number indicates the order in which cells are measured.
+ // Certain order is necessary to be able to dynamically resolve star
+ // columns / rows sizes which are used as input for measuring of
+ // the cells belonging to them.
+ //
+ // However, there are cases when topology of a grid causes cyclical
+ // size dependences. For example:
+ //
+ //
+ // column width="Auto" column width="*"
+ // +----------------------+----------------------+
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // row height="Auto" | | cell 1 2 |
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // +----------------------+----------------------+
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // row height="*" | cell 2 1 | |
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // +----------------------+----------------------+
+ //
+ // In order to accurately calculate constraint width for "cell 1 2"
+ // (which is the remaining of grid's available width and calculated
+ // value of Auto column), "cell 2 1" needs to be calculated first,
+ // as it contributes to the Auto column's calculated value.
+ // At the same time in order to accurately calculate constraint
+ // height for "cell 2 1", "cell 1 2" needs to be calcualted first,
+ // as it contributes to Auto row height, which is used in the
+ // computation of Star row resolved height.
+ //
+ // to "break" this cyclical dependency we are making (arbitrary)
+ // decision to treat cells like "cell 2 1" as if they appear in Auto
+ // rows. And then recalculate them one more time when star row
+ // heights are resolved.
+ //
+ // (Or more strictly) the code below implement the following logic:
+ //
+ // +---------+
+ // | enter |
+ // +---------+
+ // |
+ // V
+ // +----------------+
+ // | Measure Group1 |
+ // +----------------+
+ // |
+ // V
+ // / - \
+ // / \
+ // Y / Can \ N
+ // +--------| Resolve |-----------+
+ // | \ StarsV? / |
+ // | \ / |
+ // | \ - / |
+ // V V
+ // +----------------+ / - \
+ // | Resolve StarsV | / \
+ // +----------------+ Y / Can \ N
+ // | +----| Resolve |------+
+ // V | \ StarsU? / |
+ // +----------------+ | \ / |
+ // | Measure Group2 | | \ - / |
+ // +----------------+ | V
+ // | | +-----------------+
+ // V | | Measure Group2' |
+ // +----------------+ | +-----------------+
+ // | Resolve StarsU | | |
+ // +----------------+ V V
+ // | +----------------+ +----------------+
+ // V | Resolve StarsU | | Resolve StarsU |
+ // +----------------+ +----------------+ +----------------+
+ // | Measure Group3 | | |
+ // +----------------+ V V
+ // | +----------------+ +----------------+
+ // | | Measure Group3 | | Measure Group3 |
+ // | +----------------+ +----------------+
+ // | | |
+ // | V V
+ // | +----------------+ +----------------+
+ // | | Resolve StarsV | | Resolve StarsV |
+ // | +----------------+ +----------------+
+ // | | |
+ // | | V
+ // | | +------------------+
+ // | | | Measure Group2'' |
+ // | | +------------------+
+ // | | |
+ // +----------------------+-------------------------+
+ // |
+ // V
+ // +----------------+
+ // | Measure Group4 |
+ // +----------------+
+ // |
+ // V
+ // +--------+
+ // | exit |
+ // +--------+
+ //
+ // where:
+ // * all [Measure GroupN] - regular children measure process -
+ // each cell is measured given contraint size as an input
+ // and each cell's desired size is accumulated on the
+ // corresponding column / row;
+ // * [Measure Group2'] - is when each cell is measured with
+ // infinit height as a constraint and a cell's desired
+ // height is ignored;
+ // * [Measure Groups''] - is when each cell is measured (second
+ // time during single Grid.MeasureOverride) regularly but its
+ // returned width is ignored;
+ //
+ // This algorithm is believed to be as close to ideal as possible.
+ // It has the following drawbacks:
+ // * cells belonging to Group2 can be called to measure twice;
+ // * iff during second measure a cell belonging to Group2 returns
+ // desired width greater than desired width returned the first
+ // time, such a cell is going to be clipped, even though it
+ // appears in Auto column.
+ //
+
+ MeasureCellsGroup(extData.CellGroup1, constraint, false, false);
+
+ {
+ // after Group1 is measured, only Group3 may have cells belonging to Auto rows.
+ bool canResolveStarsV = !HasGroup3CellsInAutoRows;
+
+ if (canResolveStarsV)
+ {
+ if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); }
+ MeasureCellsGroup(extData.CellGroup2, constraint, false, false);
+ if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); }
+ MeasureCellsGroup(extData.CellGroup3, constraint, false, false);
+ }
+ else
+ {
+ // if at least one cell exists in Group2, it must be measured before
+ // StarsU can be resolved.
+ bool canResolveStarsU = extData.CellGroup2 > PrivateCells.Length;
+ if (canResolveStarsU)
+ {
+ if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); }
+ MeasureCellsGroup(extData.CellGroup3, constraint, false, false);
+ if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); }
+ }
+ else
+ {
+ // This is a revision to the algorithm employed for the cyclic
+ // dependency case described above. We now repeatedly
+ // measure Group3 and Group2 until their sizes settle. We
+ // also use a count heuristic to break a loop in case of one.
+
+ bool hasDesiredSizeUChanged = false;
+ int cnt=0;
+
+ // Cache Group2MinWidths & Group3MinHeights
+ double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false);
+ double[] group3MinSizes = CacheMinSizes(extData.CellGroup3, true);
+
+ MeasureCellsGroup(extData.CellGroup2, constraint, false, true);
+
+ do
+ {
+ if (hasDesiredSizeUChanged)
+ {
+ // Reset cached Group3Heights
+ ApplyCachedMinSizes(group3MinSizes, true);
+ }
+
+ if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); }
+ MeasureCellsGroup(extData.CellGroup3, constraint, false, false);
+
+ // Reset cached Group2Widths
+ ApplyCachedMinSizes(group2MinSizes, false);
+
+ if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); }
+ MeasureCellsGroup(extData.CellGroup2, constraint, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged);
+ }
+ while (hasDesiredSizeUChanged && ++cnt <= c_layoutLoopMaxCount);
+ }
+ }
+ }
+
+ MeasureCellsGroup(extData.CellGroup4, constraint, false, false);
+
+ EnterCounter(Counters._CalculateDesiredSize);
+ gridDesiredSize = new Size(
+ CalculateDesiredSize(DefinitionsU),
+ CalculateDesiredSize(DefinitionsV));
+ ExitCounter(Counters._CalculateDesiredSize);
+ }
+ }
+ finally
+ {
+ MeasureOverrideInProgress = false;
+ ExitCounterScope(Counters.MeasureOverride);
+ }
+
+ return (gridDesiredSize);
+ }
+
+ ///
+ /// Content arrangement.
+ ///
+ /// Arrange size
+ protected override Size ArrangeOverride(Size arrangeSize)
+ {
+ try
+ {
+ EnterCounterScope(Counters.ArrangeOverride);
+
+ ArrangeOverrideInProgress = true;
+
+ if (_data == null)
+ {
+ UIElementCollection children = InternalChildren;
+
+ for (int i = 0, count = children.Count; i < count; ++i)
+ {
+ UIElement child = children[i];
+ if (child != null)
+ {
+ child.Arrange(new Rect(arrangeSize));
+ }
+ }
+ }
+ else
+ {
+ Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0);
+
+ EnterCounter(Counters._SetFinalSize);
+
+ SetFinalSize(DefinitionsU, arrangeSize.Width, true);
+ SetFinalSize(DefinitionsV, arrangeSize.Height, false);
+
+ ExitCounter(Counters._SetFinalSize);
+
+ UIElementCollection children = InternalChildren;
+
+ for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell)
+ {
+ UIElement cell = children[currentCell];
+ if (cell == null)
+ {
+ continue;
+ }
+
+ int columnIndex = PrivateCells[currentCell].ColumnIndex;
+ int rowIndex = PrivateCells[currentCell].RowIndex;
+ int columnSpan = PrivateCells[currentCell].ColumnSpan;
+ int rowSpan = PrivateCells[currentCell].RowSpan;
+
+ Rect cellRect = new Rect(
+ columnIndex == 0 ? 0.0 : DefinitionsU[columnIndex].FinalOffset,
+ rowIndex == 0 ? 0.0 : DefinitionsV[rowIndex].FinalOffset,
+ GetFinalSizeForRange(DefinitionsU, columnIndex, columnSpan),
+ GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan) );
+
+ EnterCounter(Counters._ArrangeChildHelper2);
+ cell.Arrange(cellRect);
+ ExitCounter(Counters._ArrangeChildHelper2);
+ }
+
+ // update render bound on grid lines renderer visual
+ GridLinesRenderer gridLinesRenderer = EnsureGridLinesRenderer();
+ if (gridLinesRenderer != null)
+ {
+ gridLinesRenderer.UpdateRenderBounds(arrangeSize);
+ }
+ }
+ }
+ finally
+ {
+ SetValid();
+ ArrangeOverrideInProgress = false;
+ ExitCounterScope(Counters.ArrangeOverride);
+ }
+ return (arrangeSize);
+ }
+
+ ///
+ ///
+ ///
+ protected internal override void OnVisualChildrenChanged(
+ DependencyObject visualAdded,
+ DependencyObject visualRemoved)
+ {
+ CellsStructureDirty = true;
+
+ base.OnVisualChildrenChanged(visualAdded, visualRemoved);
+ }
+
+ #endregion Protected Methods
+
+ //------------------------------------------------------
+ //
+ // Internal Methods
+ //
+ //------------------------------------------------------
+
+ #region Internal Methods
+
+ ///
+ /// Invalidates grid caches and makes the grid dirty for measure.
+ ///
+ internal void Invalidate()
+ {
+ CellsStructureDirty = true;
+ InvalidateMeasure();
+ }
+
+ ///
+ /// Returns final width for a column.
+ ///
+ ///
+ /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data.
+ ///
+ internal double GetFinalColumnDefinitionWidth(int columnIndex)
+ {
+ double value = 0.0;
+
+ Invariant.Assert(_data != null);
+
+ // actual value calculations require structure to be up-to-date
+ if (!ColumnDefinitionCollectionDirty)
+ {
+ DefinitionBase[] definitions = DefinitionsU;
+ value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset;
+ if (columnIndex != 0) { value -= definitions[columnIndex].FinalOffset; }
+ }
+ return (value);
+ }
+
+ ///
+ /// Returns final height for a row.
+ ///
+ ///
+ /// Used from public RowDefinition ActualHeight. Calculates final height using offset data.
+ ///
+ internal double GetFinalRowDefinitionHeight(int rowIndex)
+ {
+ double value = 0.0;
+
+ Invariant.Assert(_data != null);
+
+ // actual value calculations require structure to be up-to-date
+ if (!RowDefinitionCollectionDirty)
+ {
+ DefinitionBase[] definitions = DefinitionsV;
+ value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset;
+ if (rowIndex != 0) { value -= definitions[rowIndex].FinalOffset; }
+ }
+ return (value);
+ }
+
+ #endregion Internal Methods
+
+ //------------------------------------------------------
+ //
+ // Internal Properties
+ //
+ //------------------------------------------------------
+
+ #region Internal Properties
+
+ ///
+ /// Convenience accessor to MeasureOverrideInProgress bit flag.
+ ///
+ internal bool MeasureOverrideInProgress
+ {
+ get { return (CheckFlagsAnd(Flags.MeasureOverrideInProgress)); }
+ set { SetFlags(value, Flags.MeasureOverrideInProgress); }
+ }
+
+ ///
+ /// Convenience accessor to ArrangeOverrideInProgress bit flag.
+ ///
+ internal bool ArrangeOverrideInProgress
+ {
+ get { return (CheckFlagsAnd(Flags.ArrangeOverrideInProgress)); }
+ set { SetFlags(value, Flags.ArrangeOverrideInProgress); }
+ }
+
+ ///
+ /// Convenience accessor to ValidDefinitionsUStructure bit flag.
+ ///
+ internal bool ColumnDefinitionCollectionDirty
+ {
+ get { return (!CheckFlagsAnd(Flags.ValidDefinitionsUStructure)); }
+ set { SetFlags(!value, Flags.ValidDefinitionsUStructure); }
+ }
+
+ ///
+ /// Convenience accessor to ValidDefinitionsVStructure bit flag.
+ ///
+ internal bool RowDefinitionCollectionDirty
+ {
+ get { return (!CheckFlagsAnd(Flags.ValidDefinitionsVStructure)); }
+ set { SetFlags(!value, Flags.ValidDefinitionsVStructure); }
+ }
+
+ #endregion Internal Properties
+
+ //------------------------------------------------------
+ //
+ // Private Methods
+ //
+ //------------------------------------------------------
+
+ #region Private Methods
+
+ ///
+ /// Lays out cells according to rows and columns, and creates lookup grids.
+ ///
+ private void ValidateCells()
+ {
+ EnterCounter(Counters._ValidateCells);
+
+ if (CellsStructureDirty)
+ {
+ ValidateCellsCore();
+ CellsStructureDirty = false;
+ }
+
+ ExitCounter(Counters._ValidateCells);
+ }
+
+ ///
+ /// ValidateCellsCore
+ ///
+ private void ValidateCellsCore()
+ {
+ UIElementCollection children = InternalChildren;
+ ExtendedData extData = ExtData;
+
+ extData.CellCachesCollection = new CellCache[children.Count];
+ extData.CellGroup1 = int.MaxValue;
+ extData.CellGroup2 = int.MaxValue;
+ extData.CellGroup3 = int.MaxValue;
+ extData.CellGroup4 = int.MaxValue;
+
+ bool hasStarCellsU = false;
+ bool hasStarCellsV = false;
+ bool hasGroup3CellsInAutoRows = false;
+
+ for (int i = PrivateCells.Length - 1; i >= 0; --i)
+ {
+ UIElement child = children[i];
+ if (child == null)
+ {
+ continue;
+ }
+
+ CellCache cell = new CellCache();
+
+ //
+ // read and cache child positioning properties
+ //
+
+ // read indices from the corresponding properties
+ // clamp to value < number_of_columns
+ // column >= 0 is guaranteed by property value validation callback
+ cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Length - 1);
+ // clamp to value < number_of_rows
+ // row >= 0 is guaranteed by property value validation callback
+ cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1);
+
+ // read span properties
+ // clamp to not exceed beyond right side of the grid
+ // column_span > 0 is guaranteed by property value validation callback
+ cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex);
+
+ // clamp to not exceed beyond bottom side of the grid
+ // row_span > 0 is guaranteed by property value validation callback
+ cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex);
+
+ Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length);
+ Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length);
+
+ //
+ // calculate and cache length types for the child
+ //
+
+ cell.SizeTypeU = GetLengthTypeForRange(DefinitionsU, cell.ColumnIndex, cell.ColumnSpan);
+ cell.SizeTypeV = GetLengthTypeForRange(DefinitionsV, cell.RowIndex, cell.RowSpan);
+
+ hasStarCellsU |= cell.IsStarU;
+ hasStarCellsV |= cell.IsStarV;
+
+ //
+ // distribute cells into four groups.
+ //
+
+ if (!cell.IsStarV)
+ {
+ if (!cell.IsStarU)
+ {
+ cell.Next = extData.CellGroup1;
+ extData.CellGroup1 = i;
+ }
+ else
+ {
+ cell.Next = extData.CellGroup3;
+ extData.CellGroup3 = i;
+
+ // remember if this cell belongs to auto row
+ hasGroup3CellsInAutoRows |= cell.IsAutoV;
+ }
+ }
+ else
+ {
+ if ( cell.IsAutoU
+ // note below: if spans through Star column it is NOT Auto
+ && !cell.IsStarU )
+ {
+ cell.Next = extData.CellGroup2;
+ extData.CellGroup2 = i;
+ }
+ else
+ {
+ cell.Next = extData.CellGroup4;
+ extData.CellGroup4 = i;
+ }
+ }
+
+ PrivateCells[i] = cell;
+ }
+
+ HasStarCellsU = hasStarCellsU;
+ HasStarCellsV = hasStarCellsV;
+ HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows;
+ }
+
+ ///
+ /// Initializes DefinitionsU memeber either to user supplied ColumnDefinitions collection
+ /// or to a default single element collection. DefinitionsU gets trimmed to size.
+ ///
+ ///
+ /// This is one of two methods, where ColumnDefinitions and DefinitionsU are directly accessed.
+ /// All the rest measure / arrange / render code must use DefinitionsU.
+ ///
+ private void ValidateDefinitionsUStructure()
+ {
+ EnterCounter(Counters._ValidateColsStructure);
+
+ if (ColumnDefinitionCollectionDirty)
+ {
+ ExtendedData extData = ExtData;
+
+ if (extData.ColumnDefinitions == null)
+ {
+ if (extData.DefinitionsU == null)
+ {
+ extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() };
+ }
+ }
+ else
+ {
+ extData.ColumnDefinitions.InternalTrimToSize();
+
+ if (extData.ColumnDefinitions.InternalCount == 0)
+ {
+ // if column definitions collection is empty
+ // mockup array with one column
+ extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() };
+ }
+ else
+ {
+ extData.DefinitionsU = extData.ColumnDefinitions.InternalItems;
+ }
+ }
+
+ ColumnDefinitionCollectionDirty = false;
+ }
+
+ Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0);
+
+ ExitCounter(Counters._ValidateColsStructure);
+ }
+
+ ///
+ /// Initializes DefinitionsV memeber either to user supplied RowDefinitions collection
+ /// or to a default single element collection. DefinitionsV gets trimmed to size.
+ ///
+ ///
+ /// This is one of two methods, where RowDefinitions and DefinitionsV are directly accessed.
+ /// All the rest measure / arrange / render code must use DefinitionsV.
+ ///
+ private void ValidateDefinitionsVStructure()
+ {
+ EnterCounter(Counters._ValidateRowsStructure);
+
+ if (RowDefinitionCollectionDirty)
+ {
+ ExtendedData extData = ExtData;
+
+ if (extData.RowDefinitions == null)
+ {
+ if (extData.DefinitionsV == null)
+ {
+ extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() };
+ }
+ }
+ else
+ {
+ extData.RowDefinitions.InternalTrimToSize();
+
+ if (extData.RowDefinitions.InternalCount == 0)
+ {
+ // if row definitions collection is empty
+ // mockup array with one row
+ extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() };
+ }
+ else
+ {
+ extData.DefinitionsV = extData.RowDefinitions.InternalItems;
+ }
+ }
+
+ RowDefinitionCollectionDirty = false;
+ }
+
+ Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0);
+
+ ExitCounter(Counters._ValidateRowsStructure);
+ }
+
+ ///
+ /// Validates layout time size type information on given array of definitions.
+ /// Sets MinSize and MeasureSizes.
+ ///
+ /// Array of definitions to update.
+ /// if "true" then star definitions are treated as Auto.
+ private void ValidateDefinitionsLayout(
+ DefinitionBase[] definitions,
+ bool treatStarAsAuto)
+ {
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ definitions[i].OnBeforeLayout(this);
+
+ double userMinSize = definitions[i].UserMinSize;
+ double userMaxSize = definitions[i].UserMaxSize;
+ double userSize = 0;
+
+ switch (definitions[i].UserSize.GridUnitType)
+ {
+ case (GridUnitType.Pixel):
+ definitions[i].SizeType = LayoutTimeSizeType.Pixel;
+ userSize = definitions[i].UserSize.Value;
+ // this was brought with NewLayout and defeats squishy behavior
+ userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
+ break;
+ case (GridUnitType.Auto):
+ definitions[i].SizeType = LayoutTimeSizeType.Auto;
+ userSize = double.PositiveInfinity;
+ break;
+ case (GridUnitType.Star):
+ if (treatStarAsAuto)
+ {
+ definitions[i].SizeType = LayoutTimeSizeType.Auto;
+ userSize = double.PositiveInfinity;
+ }
+ else
+ {
+ definitions[i].SizeType = LayoutTimeSizeType.Star;
+ userSize = double.PositiveInfinity;
+ }
+ break;
+ default:
+ Debug.Assert(false);
+ break;
+ }
+
+ definitions[i].UpdateMinSize(userMinSize);
+ definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
+ }
+ }
+
+ private double[] CacheMinSizes(int cellsHead, bool isRows)
+ {
+ double[] minSizes = isRows ? new double[DefinitionsV.Length] : new double[DefinitionsU.Length];
+
+ for (int j=0; j
+ /// Measures one group of cells.
+ ///
+ /// Head index of the cells chain.
+ /// Reference size for spanned cells
+ /// calculations.
+ /// When "true" cells' desired
+ /// width is not registered in columns.
+ /// Passed through to MeasureCell.
+ /// When "true" cells' desired height is not registered in rows.
+ private void MeasureCellsGroup(
+ int cellsHead,
+ Size referenceSize,
+ bool ignoreDesiredSizeU,
+ bool forceInfinityV,
+ out bool hasDesiredSizeUChanged)
+ {
+ hasDesiredSizeUChanged = false;
+
+ if (cellsHead >= PrivateCells.Length)
+ {
+ return;
+ }
+
+ UIElementCollection children = InternalChildren;
+ Hashtable spanStore = null;
+ bool ignoreDesiredSizeV = forceInfinityV;
+
+ int i = cellsHead;
+ do
+ {
+ double oldWidth = children[i].DesiredSize.Width;
+
+ MeasureCell(i, forceInfinityV);
+
+ hasDesiredSizeUChanged |= !DoubleUtil.AreClose(oldWidth, children[i].DesiredSize.Width);
+
+ if (!ignoreDesiredSizeU)
+ {
+ if (PrivateCells[i].ColumnSpan == 1)
+ {
+ DefinitionsU[PrivateCells[i].ColumnIndex].UpdateMinSize(Math.Min(children[i].DesiredSize.Width, DefinitionsU[PrivateCells[i].ColumnIndex].UserMaxSize));
+ }
+ else
+ {
+ RegisterSpan(
+ ref spanStore,
+ PrivateCells[i].ColumnIndex,
+ PrivateCells[i].ColumnSpan,
+ true,
+ children[i].DesiredSize.Width);
+ }
+ }
+
+ if (!ignoreDesiredSizeV)
+ {
+ if (PrivateCells[i].RowSpan == 1)
+ {
+ DefinitionsV[PrivateCells[i].RowIndex].UpdateMinSize(Math.Min(children[i].DesiredSize.Height, DefinitionsV[PrivateCells[i].RowIndex].UserMaxSize));
+ }
+ else
+ {
+ RegisterSpan(
+ ref spanStore,
+ PrivateCells[i].RowIndex,
+ PrivateCells[i].RowSpan,
+ false,
+ children[i].DesiredSize.Height);
+ }
+ }
+
+ i = PrivateCells[i].Next;
+ } while (i < PrivateCells.Length);
+
+ if (spanStore != null)
+ {
+ foreach (DictionaryEntry e in spanStore)
+ {
+ SpanKey key = (SpanKey)e.Key;
+ double requestedSize = (double)e.Value;
+
+ EnsureMinSizeInDefinitionRange(
+ key.U ? DefinitionsU : DefinitionsV,
+ key.Start,
+ key.Count,
+ requestedSize,
+ key.U ? referenceSize.Width : referenceSize.Height);
+ }
+ }
+ }
+
+ ///
+ /// Helper method to register a span information for delayed processing.
+ ///
+ /// Reference to a hashtable object used as storage.
+ /// Span starting index.
+ /// Span count.
+ /// true if this is a column span. false if this is a row span.
+ /// Value to store. If an entry already exists the biggest value is stored.
+ private static void RegisterSpan(
+ ref Hashtable store,
+ int start,
+ int count,
+ bool u,
+ double value)
+ {
+ if (store == null)
+ {
+ store = new Hashtable();
+ }
+
+ SpanKey key = new SpanKey(start, count, u);
+ object o = store[key];
+
+ if ( o == null
+ || value > (double)o )
+ {
+ store[key] = value;
+ }
+ }
+
+ ///
+ /// Takes care of measuring a single cell.
+ ///
+ /// Index of the cell to measure.
+ /// If "true" then cell is always
+ /// calculated to infinite height.
+ private void MeasureCell(
+ int cell,
+ bool forceInfinityV)
+ {
+ EnterCounter(Counters._MeasureCell);
+
+ double cellMeasureWidth;
+ double cellMeasureHeight;
+
+ if ( PrivateCells[cell].IsAutoU
+ && !PrivateCells[cell].IsStarU )
+ {
+ // if cell belongs to at least one Auto column and not a single Star column
+ // then it should be calculated "to content", thus it is possible to "shortcut"
+ // calculations and simply assign PositiveInfinity here.
+ cellMeasureWidth = double.PositiveInfinity;
+ }
+ else
+ {
+ // otherwise...
+ cellMeasureWidth = GetMeasureSizeForRange(
+ DefinitionsU,
+ PrivateCells[cell].ColumnIndex,
+ PrivateCells[cell].ColumnSpan);
+ }
+
+ if (forceInfinityV)
+ {
+ cellMeasureHeight = double.PositiveInfinity;
+ }
+ else if ( PrivateCells[cell].IsAutoV
+ && !PrivateCells[cell].IsStarV )
+ {
+ // if cell belongs to at least one Auto row and not a single Star row
+ // then it should be calculated "to content", thus it is possible to "shortcut"
+ // calculations and simply assign PositiveInfinity here.
+ cellMeasureHeight = double.PositiveInfinity;
+ }
+ else
+ {
+ cellMeasureHeight = GetMeasureSizeForRange(
+ DefinitionsV,
+ PrivateCells[cell].RowIndex,
+ PrivateCells[cell].RowSpan);
+ }
+
+ EnterCounter(Counters.__MeasureChild);
+ UIElement child = InternalChildren[cell];
+ if (child != null)
+ {
+ Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
+ child.Measure(childConstraint);
+ }
+ ExitCounter(Counters.__MeasureChild);
+
+ ExitCounter(Counters._MeasureCell);
+ }
+
+
+ ///
+ /// Calculates one dimensional measure size for given definitions' range.
+ ///
+ /// Source array of definitions to read values from.
+ /// Starting index of the range.
+ /// Number of definitions included in the range.
+ /// Calculated measure size.
+ ///
+ /// For "Auto" definitions MinWidth is used in place of PreferredSize.
+ ///
+ private double GetMeasureSizeForRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count)
+ {
+ Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
+
+ double measureSize = 0;
+ int i = start + count - 1;
+
+ do
+ {
+ measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto)
+ ? definitions[i].MinSize
+ : definitions[i].MeasureSize;
+ } while (--i >= start);
+
+ return (measureSize);
+ }
+
+ ///
+ /// Accumulates length type information for given definition's range.
+ ///
+ /// Source array of definitions to read values from.
+ /// Starting index of the range.
+ /// Number of definitions included in the range.
+ /// Length type for given range.
+ private LayoutTimeSizeType GetLengthTypeForRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count)
+ {
+ Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
+
+ LayoutTimeSizeType lengthType = LayoutTimeSizeType.None;
+ int i = start + count - 1;
+
+ do
+ {
+ lengthType |= definitions[i].SizeType;
+ } while (--i >= start);
+
+ return (lengthType);
+ }
+
+ ///
+ /// Distributes min size back to definition array's range.
+ ///
+ /// Start of the range.
+ /// Number of items in the range.
+ /// Minimum size that should "fit" into the definitions range.
+ /// Definition array receiving distribution.
+ /// Size used to resolve percentages.
+ private void EnsureMinSizeInDefinitionRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count,
+ double requestedSize,
+ double percentReferenceSize)
+ {
+ Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length);
+
+ // avoid processing when asked to distribute "0"
+ if (!_IsZero(requestedSize))
+ {
+ DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
+ int end = start + count;
+ int autoDefinitionsCount = 0;
+ double rangeMinSize = 0;
+ double rangePreferredSize = 0;
+ double rangeMaxSize = 0;
+ double maxMaxSize = 0; // maximum of maximum sizes
+
+ // first accumulate the necessary information:
+ // a) sum up the sizes in the range;
+ // b) count the number of auto definitions in the range;
+ // c) initialize temp array
+ // d) cache the maximum size into SizeCache
+ // e) accumulate max of max sizes
+ for (int i = start; i < end; ++i)
+ {
+ double minSize = definitions[i].MinSize;
+ double preferredSize = definitions[i].PreferredSize;
+ double maxSize = Math.Max(definitions[i].UserMaxSize, minSize);
+
+ rangeMinSize += minSize;
+ rangePreferredSize += preferredSize;
+ rangeMaxSize += maxSize;
+
+ definitions[i].SizeCache = maxSize;
+
+ // sanity check: no matter what, but min size must always be the smaller;
+ // max size must be the biggest; and preferred should be in between
+ Debug.Assert( minSize <= preferredSize
+ && preferredSize <= maxSize
+ && rangeMinSize <= rangePreferredSize
+ && rangePreferredSize <= rangeMaxSize );
+
+ if (maxMaxSize < maxSize) maxMaxSize = maxSize;
+ if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++;
+ tempDefinitions[i - start] = definitions[i];
+ }
+
+ // avoid processing if the range already big enough
+ if (requestedSize > rangeMinSize)
+ {
+ if (requestedSize <= rangePreferredSize)
+ {
+ //
+ // requestedSize fits into preferred size of the range.
+ // distribute according to the following logic:
+ // * do not distribute into auto definitions - they should continue to stay "tight";
+ // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size.
+ //
+ // in order to achieve that, definitions are sorted in a way that all auto definitions
+ // are first, then definitions follow ascending order with PreferredSize as the key of sorting.
+ //
+ double sizeToDistribute;
+ int i;
+
+ Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer);
+ for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i)
+ {
+ // sanity check: only auto definitions allowed in this loop
+ Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
+
+ // adjust sizeToDistribute value by subtracting auto definition min size
+ sizeToDistribute -= (tempDefinitions[i].MinSize);
+ }
+
+ for (; i < count; ++i)
+ {
+ // sanity check: no auto definitions allowed in this loop
+ Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
+
+ double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize);
+ if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); }
+ sizeToDistribute -= newMinSize;
+ }
+
+ // sanity check: requested size must all be distributed
+ Debug.Assert(_IsZero(sizeToDistribute));
+ }
+ else if (requestedSize <= rangeMaxSize)
+ {
+ //
+ // requestedSize bigger than preferred size, but fit into max size of the range.
+ // distribute according to the following logic:
+ // * do not distribute into auto definitions, if possible - they should continue to stay "tight";
+ // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size.
+ //
+ // in order to achieve that, definitions are sorted in a way that all non-auto definitions
+ // are last, then definitions follow ascending order with MaxSize as the key of sorting.
+ //
+ double sizeToDistribute;
+ int i;
+
+ Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer);
+ for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i)
+ {
+ // sanity check: no auto definitions allowed in this loop
+ Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
+
+ double preferredSize = tempDefinitions[i].PreferredSize;
+ double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i);
+ tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
+ sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
+ }
+
+ for (; i < count; ++i)
+ {
+ // sanity check: only auto definitions allowed in this loop
+ Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
+
+ double preferredSize = tempDefinitions[i].MinSize;
+ double newMinSize = preferredSize + sizeToDistribute / (count - i);
+ tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
+ sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
+ }
+
+ // sanity check: requested size must all be distributed
+ Debug.Assert(_IsZero(sizeToDistribute));
+ }
+ else
+ {
+ //
+ // requestedSize bigger than max size of the range.
+ // distribute according to the following logic:
+ // * for all definitions distribute to equi-size min sizes.
+ //
+ double equalSize = requestedSize / count;
+
+ if ( equalSize < maxMaxSize
+ && !_AreClose(equalSize, maxMaxSize) )
+ {
+ // equi-size is less than maximum of maxSizes.
+ // in this case distribute so that smaller definitions grow faster than
+ // bigger ones.
+ double totalRemainingSize = maxMaxSize * count - rangeMaxSize;
+ double sizeToDistribute = requestedSize - rangeMaxSize;
+
+ // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers
+ Debug.Assert( !double.IsInfinity(totalRemainingSize)
+ && !DoubleUtil.IsNaN(totalRemainingSize)
+ && totalRemainingSize > 0
+ && !double.IsInfinity(sizeToDistribute)
+ && !DoubleUtil.IsNaN(sizeToDistribute)
+ && sizeToDistribute > 0 );
+
+ for (int i = 0; i < count; ++i)
+ {
+ double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize;
+ tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize);
+ }
+ }
+ else
+ {
+ //
+ // equi-size is greater or equal to maximum of max sizes.
+ // all definitions receive equalSize as their mim sizes.
+ //
+ for (int i = 0; i < count; ++i)
+ {
+ tempDefinitions[i].UpdateMinSize(equalSize);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Resolves Star's for given array of definitions.
+ ///
+ /// Array of definitions to resolve stars.
+ /// All available size.
+ ///
+ /// Must initialize LayoutSize for all Star entries in given array of definitions.
+ ///
+ private void ResolveStar(
+ DefinitionBase[] definitions,
+ double availableSize)
+ {
+ if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace)
+ {
+ ResolveStarLegacy(definitions, availableSize);
+ }
+ else
+ {
+ ResolveStarMaxDiscrepancy(definitions, availableSize);
+ }
+ }
+
+ // original implementation, used from 3.0 through 4.6.2
+ private void ResolveStarLegacy(
+ DefinitionBase[] definitions,
+ double availableSize)
+ {
+ DefinitionBase[] tempDefinitions = TempDefinitions;
+ int starDefinitionsCount = 0;
+ double takenSize = 0;
+
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ switch (definitions[i].SizeType)
+ {
+ case (LayoutTimeSizeType.Auto):
+ takenSize += definitions[i].MinSize;
+ break;
+ case (LayoutTimeSizeType.Pixel):
+ takenSize += definitions[i].MeasureSize;
+ break;
+ case (LayoutTimeSizeType.Star):
+ {
+ tempDefinitions[starDefinitionsCount++] = definitions[i];
+
+ double starValue = definitions[i].UserSize.Value;
+
+ if (_IsZero(starValue))
+ {
+ definitions[i].MeasureSize = 0;
+ definitions[i].SizeCache = 0;
+ }
+ else
+ {
+ // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values
+ // can be summed up without overflow
+ starValue = Math.Min(starValue, c_starClip);
+
+ // Note: normalized star value is temporary cached into MeasureSize
+ definitions[i].MeasureSize = starValue;
+ double maxSize = Math.Max(definitions[i].MinSize, definitions[i].UserMaxSize);
+ maxSize = Math.Min(maxSize, c_starClip);
+ definitions[i].SizeCache = maxSize / starValue;
+ }
+ }
+ break;
+ }
+ }
+
+ if (starDefinitionsCount > 0)
+ {
+ Array.Sort(tempDefinitions, 0, starDefinitionsCount, s_starDistributionOrderComparer);
+
+ // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow...
+ // partial sum value is stored in each definition's SizeCache member.
+ // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus
+ // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero.
+ // this is an important change from previous implementation where the following was possible:
+ // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0...
+ double allStarWeights = 0;
+ int i = starDefinitionsCount - 1;
+ do
+ {
+ allStarWeights += tempDefinitions[i].MeasureSize;
+ tempDefinitions[i].SizeCache = allStarWeights;
+ } while (--i >= 0);
+
+ i = 0;
+ do
+ {
+ double resolvedSize;
+ double starValue = tempDefinitions[i].MeasureSize;
+
+ if (_IsZero(starValue))
+ {
+ resolvedSize = tempDefinitions[i].MinSize;
+ }
+ else
+ {
+ double userSize = Math.Max(availableSize - takenSize, 0.0) * (starValue / tempDefinitions[i].SizeCache);
+ resolvedSize = Math.Min(userSize, tempDefinitions[i].UserMaxSize);
+ resolvedSize = Math.Max(tempDefinitions[i].MinSize, resolvedSize);
+ }
+
+ tempDefinitions[i].MeasureSize = resolvedSize;
+ takenSize += resolvedSize;
+ } while (++i < starDefinitionsCount);
+ }
+ }
+
+ // new implementation as of 4.7. Several improvements:
+ // 1. Allocate to *-defs hitting their min or max constraints, before allocating
+ // to other *-defs. A def that hits its min uses more space than its
+ // proportional share, reducing the space available to everyone else.
+ // The legacy algorithm deducted this space only from defs processed
+ // after the min; the new algorithm deducts it proportionally from all
+ // defs. This avoids the "*-defs exceed available space" problem,
+ // and other related problems where *-defs don't receive proportional
+ // allocations even though no constraints are preventing it.
+ // 2. When multiple defs hit min or max, resolve the one with maximum
+ // discrepancy (defined below). This avoids discontinuities - small
+ // change in available space resulting in large change to one def's allocation.
+ // 3. Correct handling of large *-values, including Infinity.
+ private void ResolveStarMaxDiscrepancy(
+ DefinitionBase[] definitions,
+ double availableSize)
+ {
+ int defCount = definitions.Length;
+ DefinitionBase[] tempDefinitions = TempDefinitions;
+ int minCount = 0, maxCount = 0;
+ double takenSize = 0;
+ double totalStarWeight = 0.0;
+ int starCount = 0; // number of unresolved *-definitions
+ double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
+
+ // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
+ double maxStar = 0.0;
+ for (int i=0; i maxStar)
+ {
+ maxStar = def.UserSize.Value;
+ }
+ }
+ }
+
+ if (Double.IsPositiveInfinity(maxStar))
+ {
+ // negative scale means one or more of the weights was Infinity
+ scale = -1.0;
+ }
+ else if (starCount > 0)
+ {
+ // if maxStar * starCount > Double.Max, summing all the weights could cause
+ // floating-point overflow. To avoid that, scale the weights by a factor to keep
+ // the sum within limits. Choose a power of 2, to preserve precision.
+ double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0));
+ if (power < 0.0)
+ {
+ scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
+ }
+ }
+
+ // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
+ // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
+ // More explanation below...
+ for (bool runPhase2and3=true; runPhase2and3; )
+ {
+ // Phase 2. Compute total *-weight W and available space S.
+ // For *-items that have Min or Max constraints, compute the ratios used to decide
+ // whether proportional space is too big or too small and add the item to the
+ // corresponding list. (The "min" list is in the first half of tempDefinitions,
+ // the "max" list in the second half. TempDefinitions has capacity at least
+ // 2*defCount, so there's room for both lists.)
+ totalStarWeight = 0.0;
+ takenSize = 0.0;
+ minCount = maxCount = 0;
+
+ for (int i=0; i 0.0)
+ {
+ // store ratio w/min in MeasureSize (for now)
+ tempDefinitions[minCount++] = def;
+ def.MeasureSize = starWeight / def.MinSize;
+ }
+
+ double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize);
+ if (!Double.IsPositiveInfinity(effectiveMaxSize))
+ {
+ // store ratio w/max in SizeCache (for now)
+ tempDefinitions[defCount + maxCount++] = def;
+ def.SizeCache = starWeight / effectiveMaxSize;
+ }
+ }
+ break;
+ }
+ }
+
+ // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
+ int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
+ double takenStarWeight = 0.0;
+ double remainingAvailableSize = availableSize - takenSize;
+ double remainingStarWeight = totalStarWeight - takenStarWeight;
+ Array.Sort(tempDefinitions, 0, minCount, s_minRatioComparer);
+ Array.Sort(tempDefinitions, defCount, maxCount, s_maxRatioComparer);
+
+ while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
+ {
+ // the calculation
+ // remainingStarWeight = totalStarWeight - takenStarWeight
+ // is subject to catastrophic cancellation if the two terms are nearly equal,
+ // which leads to meaningless results. Check for that, and recompute from
+ // the remaining definitions. [This leads to quadratic behavior in really
+ // pathological cases - but they'd never arise in practice.]
+ const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
+ if (remainingStarWeight < totalStarWeight * starFactor)
+ {
+ takenStarWeight = 0.0;
+ totalStarWeight = 0.0;
+
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+ if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0)
+ {
+ totalStarWeight += StarWeight(def, scale);
+ }
+ }
+
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+ }
+
+ double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity;
+ double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0;
+
+ // choose the def with larger ratio to the current proportion ("max discrepancy")
+ double proportion = remainingStarWeight / remainingAvailableSize;
+ bool? chooseMin = Choose(minRatio, maxRatio, proportion);
+
+ // if no def was chosen, advance to phase 4; the current proportion doesn't
+ // conflict with any min or max values.
+ if (!(chooseMin.HasValue))
+ {
+ break;
+ }
+
+ // get the chosen definition and its resolved size
+ DefinitionBase resolvedDef;
+ double resolvedSize;
+ if (chooseMin == true)
+ {
+ resolvedDef = tempDefinitions[minCount - 1];
+ resolvedSize = resolvedDef.MinSize;
+ --minCount;
+ }
+ else
+ {
+ resolvedDef = tempDefinitions[defCount + maxCount - 1];
+ resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
+ --maxCount;
+ }
+
+ // resolve the chosen def, deduct its contributions from W and S.
+ // Defs resolved in phase 3 are marked by storing the negative of their resolved
+ // size in MeasureSize, to distinguish them from a pending def.
+ takenSize += resolvedSize;
+ resolvedDef.MeasureSize = -resolvedSize;
+ takenStarWeight += StarWeight(resolvedDef, scale);
+ --starCount;
+
+ remainingAvailableSize = availableSize - takenSize;
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+
+ // advance to the next candidate defs, removing ones that have been resolved.
+ // Both counts are advanced, as a def might appear in both lists.
+ while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0)
+ {
+ --minCount;
+ tempDefinitions[minCount] = null;
+ }
+ while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0)
+ {
+ --maxCount;
+ tempDefinitions[defCount + maxCount] = null;
+ }
+ }
+
+ // decide whether to run Phase2 and Phase3 again. There are 3 cases:
+ // 1. There is space available, and *-defs remaining. This is the
+ // normal case - move on to Phase 4 to allocate the remaining
+ // space proportionally to the remaining *-defs.
+ // 2. There is space available, but no *-defs. This implies at least one
+ // def was resolved as 'max', taking less space than its proportion.
+ // If there are also 'min' defs, reconsider them - we can give
+ // them more space. If not, all the *-defs are 'max', so there's
+ // no way to use all the available space.
+ // 3. We allocated too much space. This implies at least one def was
+ // resolved as 'min'. If there are also 'max' defs, reconsider
+ // them, otherwise the over-allocation is an inevitable consequence
+ // of the given min constraints.
+ // Note that if we return to Phase2, at least one *-def will have been
+ // resolved. This guarantees we don't run Phase2+3 infinitely often.
+ runPhase2and3 = false;
+ if (starCount == 0 && takenSize < availableSize)
+ {
+ // if no *-defs remain and we haven't allocated all the space, reconsider the defs
+ // resolved as 'min'. Their allocation can be increased to make up the gap.
+ for (int i = minCount; i < minCountPhase2; ++i)
+ {
+ DefinitionBase def = tempDefinitions[i];
+ if (def != null)
+ {
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+
+ if (takenSize > availableSize)
+ {
+ // if we've allocated too much space, reconsider the defs
+ // resolved as 'max'. Their allocation can be decreased to make up the gap.
+ for (int i = maxCount; i < maxCountPhase2; ++i)
+ {
+ DefinitionBase def = tempDefinitions[defCount + i];
+ if (def != null)
+ {
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+ }
+
+ // Phase 4. Resolve the remaining defs proportionally.
+ starCount = 0;
+ for (int i=0; i 0)
+ {
+ Array.Sort(tempDefinitions, 0, starCount, s_starWeightComparer);
+
+ // compute the partial sums of *-weight, in increasing order of weight
+ // for minimal loss of precision.
+ totalStarWeight = 0.0;
+ for (int i = 0; i < starCount; ++i)
+ {
+ DefinitionBase def = tempDefinitions[i];
+ totalStarWeight += def.MeasureSize;
+ def.SizeCache = totalStarWeight;
+ }
+
+ // resolve the defs, in decreasing order of weight
+ for (int i = starCount - 1; i >= 0; --i)
+ {
+ DefinitionBase def = tempDefinitions[i];
+ double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
+
+ // 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);
+
+ def.MeasureSize = resolvedSize;
+ takenSize += resolvedSize;
+ }
+ }
+ }
+
+ ///
+ /// Calculates desired size for given array of definitions.
+ ///
+ /// Array of definitions to use for calculations.
+ /// Desired size.
+ private double CalculateDesiredSize(
+ DefinitionBase[] definitions)
+ {
+ double desiredSize = 0;
+
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ desiredSize += definitions[i].MinSize;
+ }
+
+ return (desiredSize);
+ }
+
+ ///
+ /// Calculates and sets final size for all definitions in the given array.
+ ///
+ /// Array of definitions to process.
+ /// Final size to lay out to.
+ /// True if sizing row definitions, false for columns
+ private void SetFinalSize(
+ DefinitionBase[] definitions,
+ double finalSize,
+ bool columns)
+ {
+ if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace)
+ {
+ SetFinalSizeLegacy(definitions, finalSize, columns);
+ }
+ else
+ {
+ SetFinalSizeMaxDiscrepancy(definitions, finalSize, columns);
+ }
+ }
+
+ // original implementation, used from 3.0 through 4.6.2
+ private void SetFinalSizeLegacy(
+ DefinitionBase[] definitions,
+ double finalSize,
+ bool columns)
+ {
+ int starDefinitionsCount = 0; // traverses form the first entry up
+ int nonStarIndex = definitions.Length; // traverses from the last entry down
+ double allPreferredArrangeSize = 0;
+ bool useLayoutRounding = this.UseLayoutRounding;
+ int[] definitionIndices = DefinitionIndices;
+ double[] roundingErrors = null;
+
+ // If using layout rounding, check whether rounding needs to compensate for high DPI
+ double dpi = 1.0;
+
+ if (useLayoutRounding)
+ {
+ DpiScale dpiScale = GetDpi();
+ dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY;
+ roundingErrors = RoundingErrors;
+ }
+
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ // if definition is shared then is cannot be star
+ Debug.Assert(!definitions[i].IsShared || !definitions[i].UserSize.IsStar);
+
+ if (definitions[i].UserSize.IsStar)
+ {
+ double starValue = definitions[i].UserSize.Value;
+
+ if (_IsZero(starValue))
+ {
+ // cach normilized star value temporary into MeasureSize
+ definitions[i].MeasureSize = 0;
+ definitions[i].SizeCache = 0;
+ }
+ else
+ {
+ // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values
+ // can be summed up without overflow
+ starValue = Math.Min(starValue, c_starClip);
+
+ // Note: normalized star value is temporary cached into MeasureSize
+ definitions[i].MeasureSize = starValue;
+ double maxSize = Math.Max(definitions[i].MinSizeForArrange, definitions[i].UserMaxSize);
+ maxSize = Math.Min(maxSize, c_starClip);
+ definitions[i].SizeCache = maxSize / starValue;
+ if (useLayoutRounding)
+ {
+ roundingErrors[i] = definitions[i].SizeCache;
+ definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi);
+ }
+ }
+ definitionIndices[starDefinitionsCount++] = i;
+ }
+ else
+ {
+ double userSize = 0;
+
+ switch (definitions[i].UserSize.GridUnitType)
+ {
+ case (GridUnitType.Pixel):
+ userSize = definitions[i].UserSize.Value;
+ break;
+
+ case (GridUnitType.Auto):
+ userSize = definitions[i].MinSizeForArrange;
+ break;
+ }
+
+ double userMaxSize;
+
+ if (definitions[i].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 = definitions[i].UserMaxSize;
+ }
+
+ definitions[i].SizeCache = Math.Max(definitions[i].MinSizeForArrange, Math.Min(userSize, userMaxSize));
+ if (useLayoutRounding)
+ {
+ roundingErrors[i] = definitions[i].SizeCache;
+ definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi);
+ }
+
+ allPreferredArrangeSize += definitions[i].SizeCache;
+ definitionIndices[--nonStarIndex] = i;
+ }
+ }
+
+ // indices should meet
+ Debug.Assert(nonStarIndex == starDefinitionsCount);
+
+ if (starDefinitionsCount > 0)
+ {
+ StarDistributionOrderIndexComparer starDistributionOrderIndexComparer = new StarDistributionOrderIndexComparer(definitions);
+ Array.Sort(definitionIndices, 0, starDefinitionsCount, starDistributionOrderIndexComparer);
+
+ // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow...
+ // partial sum value is stored in each definition's SizeCache member.
+ // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus
+ // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero.
+ // this is an important change from previous implementation where the following was possible:
+ // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0...
+ double allStarWeights = 0;
+ int i = starDefinitionsCount - 1;
+ do
+ {
+ allStarWeights += definitions[definitionIndices[i]].MeasureSize;
+ definitions[definitionIndices[i]].SizeCache = allStarWeights;
+ } while (--i >= 0);
+
+ i = 0;
+ do
+ {
+ double resolvedSize;
+ double starValue = definitions[definitionIndices[i]].MeasureSize;
+
+ if (_IsZero(starValue))
+ {
+ resolvedSize = definitions[definitionIndices[i]].MinSizeForArrange;
+ }
+ else
+ {
+ double userSize = Math.Max(finalSize - allPreferredArrangeSize, 0.0) * (starValue / definitions[definitionIndices[i]].SizeCache);
+ resolvedSize = Math.Min(userSize, definitions[definitionIndices[i]].UserMaxSize);
+ resolvedSize = Math.Max(definitions[definitionIndices[i]].MinSizeForArrange, resolvedSize);
+ }
+
+ definitions[definitionIndices[i]].SizeCache = resolvedSize;
+ if (useLayoutRounding)
+ {
+ roundingErrors[definitionIndices[i]] = definitions[definitionIndices[i]].SizeCache;
+ definitions[definitionIndices[i]].SizeCache = UIElement.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi);
+ }
+
+ allPreferredArrangeSize += definitions[definitionIndices[i]].SizeCache;
+ } while (++i < starDefinitionsCount);
+ }
+
+ if ( allPreferredArrangeSize > finalSize
+ && !_AreClose(allPreferredArrangeSize, finalSize) )
+ {
+ DistributionOrderIndexComparer distributionOrderIndexComparer = new DistributionOrderIndexComparer(definitions);
+ Array.Sort(definitionIndices, 0, definitions.Length, distributionOrderIndexComparer);
+ double sizeToDistribute = finalSize - allPreferredArrangeSize;
+
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ int definitionIndex = definitionIndices[i];
+ double final = definitions[definitionIndex].SizeCache + (sizeToDistribute / (definitions.Length - i));
+ double finalOld = final;
+ final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange);
+ final = Math.Min(final, definitions[definitionIndex].SizeCache);
+
+ if (useLayoutRounding)
+ {
+ roundingErrors[definitionIndex] = final;
+ final = UIElement.RoundLayoutValue(finalOld, dpi);
+ final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange);
+ final = Math.Min(final, definitions[definitionIndex].SizeCache);
+ }
+
+ sizeToDistribute -= (final - definitions[definitionIndex].SizeCache);
+ definitions[definitionIndex].SizeCache = final;
+ }
+
+ allPreferredArrangeSize = finalSize - sizeToDistribute;
+ }
+
+ if (useLayoutRounding)
+ {
+ if (!_AreClose(allPreferredArrangeSize, finalSize))
+ {
+ // Compute deltas
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ roundingErrors[i] = roundingErrors[i] - definitions[i].SizeCache;
+ definitionIndices[i] = i;
+ }
+
+ // Sort rounding errors
+ RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors);
+ Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer);
+ double adjustedSize = allPreferredArrangeSize;
+ double dpiIncrement = UIElement.RoundLayoutValue(1.0, dpi);
+
+ if (allPreferredArrangeSize > finalSize)
+ {
+ int i = definitions.Length - 1;
+ while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0)
+ {
+ DefinitionBase definition = definitions[definitionIndices[i]];
+ double final = definition.SizeCache - dpiIncrement;
+ final = Math.Max(final, definition.MinSizeForArrange);
+ if (final < definition.SizeCache)
+ {
+ adjustedSize -= dpiIncrement;
+ }
+ definition.SizeCache = final;
+ i--;
+ }
+ }
+ else if (allPreferredArrangeSize < finalSize)
+ {
+ int i = 0;
+ while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length)
+ {
+ DefinitionBase definition = definitions[definitionIndices[i]];
+ double final = definition.SizeCache + dpiIncrement;
+ final = Math.Max(final, definition.MinSizeForArrange);
+ if (final > definition.SizeCache)
+ {
+ adjustedSize += dpiIncrement;
+ }
+ definition.SizeCache = final;
+ i++;
+ }
+ }
+ }
+ }
+
+ definitions[0].FinalOffset = 0.0;
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache;
+ }
+ }
+
+ // new implementation, as of 4.7. This incorporates the same algorithm
+ // as in ResolveStarMaxDiscrepancy. It differs in the same way that SetFinalSizeLegacy
+ // differs from ResolveStarLegacy, namely (a) leaves results in def.SizeCache
+ // instead of def.MeasureSize, (b) implements LayoutRounding if requested,
+ // (c) stores intermediate results differently.
+ // The LayoutRounding logic is improved:
+ // 1. Use pre-rounded values during proportional allocation. This avoids the
+ // same kind of problems arising from interaction with min/max that
+ // motivated the new algorithm in the first place.
+ // 2. Use correct "nudge" amount when distributing roundoff space. This
+ // comes into play at high DPI - greater than 134.
+ // 3. Applies rounding only to real pixel values (not to ratios)
+ private void SetFinalSizeMaxDiscrepancy(
+ DefinitionBase[] definitions,
+ double finalSize,
+ bool columns)
+ {
+ int defCount = definitions.Length;
+ int[] definitionIndices = DefinitionIndices;
+ int minCount = 0, maxCount = 0;
+ double takenSize = 0.0;
+ double totalStarWeight = 0.0;
+ int starCount = 0; // number of unresolved *-definitions
+ double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
+
+ // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
+ double maxStar = 0.0;
+ for (int i=0; i maxStar)
+ {
+ maxStar = def.UserSize.Value;
+ }
+ }
+ }
+
+ if (Double.IsPositiveInfinity(maxStar))
+ {
+ // negative scale means one or more of the weights was Infinity
+ scale = -1.0;
+ }
+ else if (starCount > 0)
+ {
+ // if maxStar * starCount > Double.Max, summing all the weights could cause
+ // floating-point overflow. To avoid that, scale the weights by a factor to keep
+ // the sum within limits. Choose a power of 2, to preserve precision.
+ double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0));
+ if (power < 0.0)
+ {
+ scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
+ }
+ }
+
+
+ // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
+ // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
+ // More explanation below...
+ for (bool runPhase2and3=true; runPhase2and3; )
+ {
+ // Phase 2. Compute total *-weight W and available space S.
+ // For *-items that have Min or Max constraints, compute the ratios used to decide
+ // whether proportional space is too big or too small and add the item to the
+ // corresponding list. (The "min" list is in the first half of definitionIndices,
+ // the "max" list in the second half. DefinitionIndices has capacity at least
+ // 2*defCount, so there's room for both lists.)
+ totalStarWeight = 0.0;
+ takenSize = 0.0;
+ minCount = maxCount = 0;
+
+ for (int i=0; i 0.0)
+ {
+ // store ratio w/min in MeasureSize (for now)
+ definitionIndices[minCount++] = i;
+ def.MeasureSize = starWeight / def.MinSizeForArrange;
+ }
+
+ double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize);
+ if (!Double.IsPositiveInfinity(effectiveMaxSize))
+ {
+ // store ratio w/max in SizeCache (for now)
+ definitionIndices[defCount + maxCount++] = i;
+ def.SizeCache = starWeight / effectiveMaxSize;
+ }
+ }
+ }
+ else
+ {
+ double userSize = 0;
+
+ switch (def.UserSize.GridUnitType)
+ {
+ case (GridUnitType.Pixel):
+ userSize = def.UserSize.Value;
+ break;
+
+ case (GridUnitType.Auto):
+ 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.MinSizeForArrange, Math.Min(userSize, userMaxSize));
+ takenSize += def.SizeCache;
+ }
+ }
+
+ // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
+ int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
+ double takenStarWeight = 0.0;
+ double remainingAvailableSize = finalSize - takenSize;
+ double remainingStarWeight = totalStarWeight - takenStarWeight;
+
+ MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions);
+ Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer);
+ MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions);
+ Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer);
+
+ while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
+ {
+ // the calculation
+ // remainingStarWeight = totalStarWeight - takenStarWeight
+ // is subject to catastrophic cancellation if the two terms are nearly equal,
+ // which leads to meaningless results. Check for that, and recompute from
+ // the remaining definitions. [This leads to quadratic behavior in really
+ // pathological cases - but they'd never arise in practice.]
+ const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
+ if (remainingStarWeight < totalStarWeight * starFactor)
+ {
+ takenStarWeight = 0.0;
+ totalStarWeight = 0.0;
+
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+ if (def.UserSize.IsStar && def.MeasureSize > 0.0)
+ {
+ totalStarWeight += StarWeight(def, scale);
+ }
+ }
+
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+ }
+
+ double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : Double.PositiveInfinity;
+ double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0;
+
+ // choose the def with larger ratio to the current proportion ("max discrepancy")
+ double proportion = remainingStarWeight / remainingAvailableSize;
+ bool? chooseMin = Choose(minRatio, maxRatio, proportion);
+
+ // if no def was chosen, advance to phase 4; the current proportion doesn't
+ // conflict with any min or max values.
+ if (!chooseMin.HasValue)
+ {
+ break;
+ }
+
+ // get the chosen definition and its resolved size
+ int resolvedIndex;
+ DefinitionBase resolvedDef;
+ double resolvedSize;
+ if (chooseMin == true)
+ {
+ resolvedIndex = definitionIndices[minCount - 1];
+ resolvedDef = definitions[resolvedIndex];
+ resolvedSize = resolvedDef.MinSizeForArrange;
+ --minCount;
+ }
+ else
+ {
+ resolvedIndex = definitionIndices[defCount + maxCount - 1];
+ resolvedDef = definitions[resolvedIndex];
+ resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize);
+ --maxCount;
+ }
+
+ // resolve the chosen def, deduct its contributions from W and S.
+ // Defs resolved in phase 3 are marked by storing the negative of their resolved
+ // size in MeasureSize, to distinguish them from a pending def.
+ takenSize += resolvedSize;
+ resolvedDef.MeasureSize = -resolvedSize;
+ takenStarWeight += StarWeight(resolvedDef, scale);
+ --starCount;
+
+ remainingAvailableSize = finalSize - takenSize;
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+
+ // advance to the next candidate defs, removing ones that have been resolved.
+ // Both counts are advanced, as a def might appear in both lists.
+ while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0)
+ {
+ --minCount;
+ definitionIndices[minCount] = -1;
+ }
+ while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0)
+ {
+ --maxCount;
+ definitionIndices[defCount + maxCount] = -1;
+ }
+ }
+
+ // decide whether to run Phase2 and Phase3 again. There are 3 cases:
+ // 1. There is space available, and *-defs remaining. This is the
+ // normal case - move on to Phase 4 to allocate the remaining
+ // space proportionally to the remaining *-defs.
+ // 2. There is space available, but no *-defs. This implies at least one
+ // def was resolved as 'max', taking less space than its proportion.
+ // If there are also 'min' defs, reconsider them - we can give
+ // them more space. If not, all the *-defs are 'max', so there's
+ // no way to use all the available space.
+ // 3. We allocated too much space. This implies at least one def was
+ // resolved as 'min'. If there are also 'max' defs, reconsider
+ // them, otherwise the over-allocation is an inevitable consequence
+ // of the given min constraints.
+ // Note that if we return to Phase2, at least one *-def will have been
+ // resolved. This guarantees we don't run Phase2+3 infinitely often.
+ runPhase2and3 = false;
+ if (starCount == 0 && takenSize < finalSize)
+ {
+ // if no *-defs remain and we haven't allocated all the space, reconsider the defs
+ // resolved as 'min'. Their allocation can be increased to make up the gap.
+ for (int i = minCount; i < minCountPhase2; ++i)
+ {
+ if (definitionIndices[i] >= 0)
+ {
+ DefinitionBase def = definitions[definitionIndices[i]];
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+
+ if (takenSize > finalSize)
+ {
+ // if we've allocated too much space, reconsider the defs
+ // resolved as 'max'. Their allocation can be decreased to make up the gap.
+ for (int i = maxCount; i < maxCountPhase2; ++i)
+ {
+ if (definitionIndices[defCount + i] >= 0)
+ {
+ DefinitionBase def = definitions[definitionIndices[defCount + i]];
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+ }
+
+ // Phase 4. Resolve the remaining defs proportionally.
+ starCount = 0;
+ for (int i=0; i 0)
+ {
+ StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions);
+ Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer);
+
+ // compute the partial sums of *-weight, in increasing order of weight
+ // for minimal loss of precision.
+ totalStarWeight = 0.0;
+ for (int i = 0; i < starCount; ++i)
+ {
+ DefinitionBase def = definitions[definitionIndices[i]];
+ totalStarWeight += def.MeasureSize;
+ def.SizeCache = totalStarWeight;
+ }
+
+ // resolve the defs, in decreasing order of weight.
+ for (int i = starCount - 1; i >= 0; --i)
+ {
+ DefinitionBase def = definitions[definitionIndices[i]];
+ double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
+
+ // min and max should have no effect by now, but just in case...
+ resolvedSize = Math.Min(resolvedSize, def.UserMaxSize);
+ 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;
+ // this avoids errors arising from min/max constraints.
+ takenSize += resolvedSize;
+ def.SizeCache = resolvedSize;
+ }
+ }
+
+ // Phase 5. Apply layout rounding. We do this after fully allocating
+ // unrounded sizes, to avoid breaking assumptions in the previous phases
+ if (UseLayoutRounding)
+ {
+ DpiScale dpiScale = GetDpi();
+ double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY;
+ double[] roundingErrors = RoundingErrors;
+ double roundedTakenSize = 0.0;
+
+ // round each of the allocated sizes, keeping track of the deltas
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ DefinitionBase def = definitions[i];
+ double roundedSize = UIElement.RoundLayoutValue(def.SizeCache, dpi);
+ roundingErrors[i] = (roundedSize - def.SizeCache);
+ def.SizeCache = roundedSize;
+ roundedTakenSize += roundedSize;
+ }
+
+ // The total allocation might differ from finalSize due to rounding
+ // effects. Tweak the allocations accordingly.
+
+ // Theoretical and historical note. The problem at hand - allocating
+ // space to columns (or rows) with *-weights, min and max constraints,
+ // and layout rounding - has a long history. Especially the special
+ // case of 50 columns with min=1 and available space=435 - allocating
+ // seats in the U.S. House of Representatives to the 50 states in
+ // proportion to their population. There are numerous algorithms
+ // and papers dating back to the 1700's, including the book:
+ // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982.
+ //
+ // One surprising result of all this research is that *any* algorithm
+ // will suffer from one or more undesirable features such as the
+ // "population paradox" or the "Alabama paradox", where (to use our terminology)
+ // increasing the available space by one pixel might actually decrease
+ // the space allocated to a given column, or increasing the weight of
+ // a column might decrease its allocation. This is worth knowing
+ // in case someone complains about this behavior; it's not a bug so
+ // much as something inherent to the problem. Cite the book mentioned
+ // above or one of the 100s of references, and resolve as WontFix.
+ //
+ // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer)
+ // each being allocated a large number of pixels (~50 or greater), and
+ // people don't even notice the kind of 1-pixel anomolies that are
+ // theoretically inevitable, or don't care if they do. At least they shouldn't
+ // care - no one should be using the results WPF's grid layout to make
+ // quantitative decisions; its job is to produce a reasonable display, not
+ // to allocate seats in Congress.
+ //
+ // Our algorithm is more susceptible to paradox than the one currently
+ // used for Congressional allocation ("Huntington-Hill" algorithm), but
+ // it is faster to run: O(N log N) vs. O(S * N), where N=number of
+ // definitions, S = number of available pixels. And it produces
+ // adequate results in practice, as mentioned above.
+ //
+ // To reiterate one point: all this only applies when layout rounding
+ // is in effect. When fractional sizes are allowed, the algorithm
+ // behaves as well as possible, subject to the min/max constraints
+ // and precision of floating-point computation. (However, the resulting
+ // display is subject to anti-aliasing problems. TANSTAAFL.)
+
+ if (!_AreClose(roundedTakenSize, finalSize))
+ {
+ // Compute deltas
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ definitionIndices[i] = i;
+ }
+
+ // Sort rounding errors
+ RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors);
+ Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer);
+ double adjustedSize = roundedTakenSize;
+ double dpiIncrement = 1.0/dpi;
+
+ if (roundedTakenSize > finalSize)
+ {
+ int i = definitions.Length - 1;
+ while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0)
+ {
+ DefinitionBase definition = definitions[definitionIndices[i]];
+ double final = definition.SizeCache - dpiIncrement;
+ final = Math.Max(final, definition.MinSizeForArrange);
+ if (final < definition.SizeCache)
+ {
+ adjustedSize -= dpiIncrement;
+ }
+ definition.SizeCache = final;
+ i--;
+ }
+ }
+ else if (roundedTakenSize < finalSize)
+ {
+ int i = 0;
+ while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length)
+ {
+ DefinitionBase definition = definitions[definitionIndices[i]];
+ double final = definition.SizeCache + dpiIncrement;
+ final = Math.Max(final, definition.MinSizeForArrange);
+ if (final > definition.SizeCache)
+ {
+ adjustedSize += dpiIncrement;
+ }
+ definition.SizeCache = final;
+ i++;
+ }
+ }
+ }
+ }
+
+ // Phase 6. Compute final offsets
+ definitions[0].FinalOffset = 0.0;
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache;
+ }
+ }
+
+ ///
+ /// Choose the ratio with maximum discrepancy from the current proportion.
+ /// Returns:
+ /// true if proportion fails a min constraint but not a max, or
+ /// if the min constraint has higher discrepancy
+ /// false if proportion fails a max constraint but not a min, or
+ /// if the max constraint has higher discrepancy
+ /// null if proportion doesn't fail a min or max constraint
+ /// The discrepancy is the ratio of the proportion to the max- or min-ratio.
+ /// When both ratios hit the constraint, minRatio < proportion < maxRatio,
+ /// and the minRatio has higher discrepancy if
+ /// (proportion / minRatio) > (maxRatio / proportion)
+ ///
+ private static bool? Choose(double minRatio, double maxRatio, double proportion)
+ {
+ if (minRatio < proportion)
+ {
+ if (maxRatio > proportion)
+ {
+ // compare proportion/minRatio : maxRatio/proportion, but
+ // do it carefully to avoid floating-point overflow or underflow
+ // and divide-by-0.
+ double minPower = Math.Floor(Math.Log(minRatio, 2.0));
+ double maxPower = Math.Floor(Math.Log(maxRatio, 2.0));
+ double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0));
+ if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else if (maxRatio > proportion)
+ {
+ return false;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Sorts row/column indices by rounding error if layout rounding is applied.
+ ///
+ /// Index, rounding error pair
+ /// Index, rounding error pair
+ /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise
+ private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y)
+ {
+ if (x.Value < y.Value)
+ {
+ return -1;
+ }
+ else if (x.Value > y.Value)
+ {
+ return 1;
+ }
+ return 0;
+ }
+
+ ///
+ /// Calculates final (aka arrange) size for given range.
+ ///
+ /// Array of definitions to process.
+ /// Start of the range.
+ /// Number of items in the range.
+ /// Final size.
+ private double GetFinalSizeForRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count)
+ {
+ double size = 0;
+ int i = start + count - 1;
+
+ do
+ {
+ size += definitions[i].SizeCache;
+ } while (--i >= start);
+
+ return (size);
+ }
+
+ ///
+ /// Clears dirty state for the grid and its columns / rows
+ ///
+ private void SetValid()
+ {
+ ExtendedData extData = ExtData;
+ if (extData != null)
+ {
+// for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid ();
+// for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid ();
+
+ if (extData.TempDefinitions != null)
+ {
+ // TempDefinitions has to be cleared to avoid "memory leaks"
+ Array.Clear(extData.TempDefinitions, 0, Math.Max(DefinitionsU.Length, DefinitionsV.Length));
+ extData.TempDefinitions = null;
+ }
+ }
+ }
+
+ ///
+ /// Returns true if ColumnDefinitions collection is not empty
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeColumnDefinitions()
+ {
+ ExtendedData extData = ExtData;
+ return ( extData != null
+ && extData.ColumnDefinitions != null
+ && extData.ColumnDefinitions.Count > 0 );
+ }
+
+ ///
+ /// Returns true if RowDefinitions collection is not empty
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ShouldSerializeRowDefinitions()
+ {
+ ExtendedData extData = ExtData;
+ return ( extData != null
+ && extData.RowDefinitions != null
+ && extData.RowDefinitions.Count > 0 );
+ }
+
+ ///
+ /// Synchronized ShowGridLines property with the state of the grid's visual collection
+ /// by adding / removing GridLinesRenderer visual.
+ /// Returns a reference to GridLinesRenderer visual or null.
+ ///
+ private GridLinesRenderer EnsureGridLinesRenderer()
+ {
+ //
+ // synchronize the state
+ //
+ if (ShowGridLines && (_gridLinesRenderer == null))
+ {
+ _gridLinesRenderer = new GridLinesRenderer();
+ this.AddVisualChild(_gridLinesRenderer);
+ }
+
+ if ((!ShowGridLines) && (_gridLinesRenderer != null))
+ {
+ this.RemoveVisualChild(_gridLinesRenderer);
+ _gridLinesRenderer = null;
+ }
+
+ return (_gridLinesRenderer);
+ }
+
+ ///
+ /// SetFlags is used to set or unset one or multiple
+ /// flags on the object.
+ ///
+ private void SetFlags(bool value, Flags flags)
+ {
+ _flags = value ? (_flags | flags) : (_flags & (~flags));
+ }
+
+ ///
+ /// CheckFlagsAnd returns true if all the flags in the
+ /// given bitmask are set on the object.
+ ///
+ private bool CheckFlagsAnd(Flags flags)
+ {
+ return ((_flags & flags) == flags);
+ }
+
+ ///
+ /// CheckFlagsOr returns true if at least one flag in the
+ /// given bitmask is set.
+ ///
+ ///
+ /// If no bits are set in the given bitmask, the method returns
+ /// true.
+ ///
+ private bool CheckFlagsOr(Flags flags)
+ {
+ return (flags == 0 || (_flags & flags) != 0);
+ }
+
+ ///
+ ///
+ ///
+ private static void OnShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ Grid grid = (Grid)d;
+
+ if ( grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway
+ && grid.ListenToNotifications)
+ {
+ grid.InvalidateVisual();
+ }
+
+ grid.SetFlags((bool) e.NewValue, Flags.ShowGridLinesPropertyValue);
+ }
+
+ ///
+ ///
+ ///
+ private static void OnCellAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ Visual child = d as Visual;
+
+ if (child != null)
+ {
+ Grid grid = VisualTreeHelper.GetParent(child) as Grid;
+ if ( grid != null
+ && grid.ExtData != null
+ && grid.ListenToNotifications )
+ {
+ grid.CellsStructureDirty = true;
+ grid.InvalidateMeasure();
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ private static bool IsIntValueNotNegative(object value)
+ {
+ return ((int)value >= 0);
+ }
+
+ ///
+ ///
+ ///
+ private static bool IsIntValueGreaterThanZero(object value)
+ {
+ return ((int)value > 0);
+ }
+
+ ///
+ /// Helper for Comparer methods.
+ ///
+ ///
+ /// true iff one or both of x and y are null, in which case result holds
+ /// the relative sort order.
+ ///
+ private static bool CompareNullRefs(object x, object y, out int result)
+ {
+ result = 2;
+
+ if (x == null)
+ {
+ if (y == null)
+ {
+ result = 0;
+ }
+ else
+ {
+ result = -1;
+ }
+ }
+ else
+ {
+ if (y == null)
+ {
+ result = 1;
+ }
+ }
+
+ return (result != 2);
+ }
+
+ #endregion Private Methods
+
+ //------------------------------------------------------
+ //
+ // Private Properties
+ //
+ //------------------------------------------------------
+
+ #region Private Properties
+
+ ///
+ /// Private version returning array of column definitions.
+ ///
+ private DefinitionBase[] DefinitionsU
+ {
+ get { return (ExtData.DefinitionsU); }
+ }
+
+ ///
+ /// Private version returning array of row definitions.
+ ///
+ private DefinitionBase[] DefinitionsV
+ {
+ get { return (ExtData.DefinitionsV); }
+ }
+
+ ///
+ /// Helper accessor to layout time array of definitions.
+ ///
+ private DefinitionBase[] TempDefinitions
+ {
+ get
+ {
+ ExtendedData extData = ExtData;
+ int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length) * 2;
+
+ if ( extData.TempDefinitions == null
+ || extData.TempDefinitions.Length < requiredLength )
+ {
+ WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(s_tempDefinitionsDataSlot);
+ if (tempDefinitionsWeakRef == null)
+ {
+ extData.TempDefinitions = new DefinitionBase[requiredLength];
+ Thread.SetData(s_tempDefinitionsDataSlot, new WeakReference(extData.TempDefinitions));
+ }
+ else
+ {
+ extData.TempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target;
+ if ( extData.TempDefinitions == null
+ || extData.TempDefinitions.Length < requiredLength )
+ {
+ extData.TempDefinitions = new DefinitionBase[requiredLength];
+ tempDefinitionsWeakRef.Target = extData.TempDefinitions;
+ }
+ }
+ }
+ return (extData.TempDefinitions);
+ }
+ }
+
+ ///
+ /// Helper accessor to definition indices.
+ ///
+ private int[] DefinitionIndices
+ {
+ get
+ {
+ int requiredLength = Math.Max(Math.Max(DefinitionsU.Length, DefinitionsV.Length), 1) * 2;
+
+ if (_definitionIndices == null || _definitionIndices.Length < requiredLength)
+ {
+ _definitionIndices = new int[requiredLength];
+ }
+
+ return _definitionIndices;
+ }
+ }
+
+ ///
+ /// Helper accessor to rounding errors.
+ ///
+ private double[] RoundingErrors
+ {
+ get
+ {
+ int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length);
+
+ if (_roundingErrors == null && requiredLength == 0)
+ {
+ _roundingErrors = new double[1];
+ }
+ else if (_roundingErrors == null || _roundingErrors.Length < requiredLength)
+ {
+ _roundingErrors = new double[requiredLength];
+ }
+ return _roundingErrors;
+ }
+ }
+
+ ///
+ /// Private version returning array of cells.
+ ///
+ private CellCache[] PrivateCells
+ {
+ get { return (ExtData.CellCachesCollection); }
+ }
+
+ ///
+ /// Convenience accessor to ValidCellsStructure bit flag.
+ ///
+ private bool CellsStructureDirty
+ {
+ get { return (!CheckFlagsAnd(Flags.ValidCellsStructure)); }
+ set { SetFlags(!value, Flags.ValidCellsStructure); }
+ }
+
+ ///
+ /// Convenience accessor to ListenToNotifications bit flag.
+ ///
+ private bool ListenToNotifications
+ {
+ get { return (CheckFlagsAnd(Flags.ListenToNotifications)); }
+ set { SetFlags(value, Flags.ListenToNotifications); }
+ }
+
+ ///
+ /// Convenience accessor to SizeToContentU bit flag.
+ ///
+ private bool SizeToContentU
+ {
+ get { return (CheckFlagsAnd(Flags.SizeToContentU)); }
+ set { SetFlags(value, Flags.SizeToContentU); }
+ }
+
+ ///
+ /// Convenience accessor to SizeToContentV bit flag.
+ ///
+ private bool SizeToContentV
+ {
+ get { return (CheckFlagsAnd(Flags.SizeToContentV)); }
+ set { SetFlags(value, Flags.SizeToContentV); }
+ }
+
+ ///
+ /// Convenience accessor to HasStarCellsU bit flag.
+ ///
+ private bool HasStarCellsU
+ {
+ get { return (CheckFlagsAnd(Flags.HasStarCellsU)); }
+ set { SetFlags(value, Flags.HasStarCellsU); }
+ }
+
+ ///
+ /// Convenience accessor to HasStarCellsV bit flag.
+ ///
+ private bool HasStarCellsV
+ {
+ get { return (CheckFlagsAnd(Flags.HasStarCellsV)); }
+ set { SetFlags(value, Flags.HasStarCellsV); }
+ }
+
+ ///
+ /// Convenience accessor to HasGroup3CellsInAutoRows bit flag.
+ ///
+ private bool HasGroup3CellsInAutoRows
+ {
+ get { return (CheckFlagsAnd(Flags.HasGroup3CellsInAutoRows)); }
+ set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); }
+ }
+
+ ///
+ /// fp version of d == 0.
+ ///
+ /// Value to check.
+ /// true if d == 0.
+ private static bool _IsZero(double d)
+ {
+ return (Math.Abs(d) < c_epsilon);
+ }
+
+ ///
+ /// fp version of d1 == d2
+ ///
+ /// First value to compare
+ /// Second value to compare
+ /// true if d1 == d2
+ private static bool _AreClose(double d1, double d2)
+ {
+ return (Math.Abs(d1 - d2) < c_epsilon);
+ }
+
+ ///
+ /// Returns reference to extended data bag.
+ ///
+ private ExtendedData ExtData
+ {
+ get { return (_data); }
+ }
+
+ ///
+ /// Returns *-weight, adjusted for scale computed during Phase 1
+ ///
+ static double StarWeight(DefinitionBase def, double scale)
+ {
+ if (scale < 0.0)
+ {
+ // if one of the *-weights is Infinity, adjust the weights by mapping
+ // Infinty to 1.0 and everything else to 0.0: the infinite items share the
+ // available space equally, everyone else gets nothing.
+ return (Double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0;
+ }
+ else
+ {
+ return def.UserSize.Value * scale;
+ }
+ }
+
+ #endregion Private Properties
+
+ //------------------------------------------------------
+ //
+ // Private Fields
+ //
+ //------------------------------------------------------
+
+ #region Private Fields
+ private ExtendedData _data; // extended data instantiated on demand, for non-trivial case handling only
+ private Flags _flags; // grid validity / property caches dirtiness flags
+ private GridLinesRenderer _gridLinesRenderer;
+
+ // Keeps track of definition indices.
+ int[] _definitionIndices;
+
+ // Stores unrounded values and rounding errors during layout rounding.
+ double[] _roundingErrors;
+
+ #endregion Private Fields
+
+ //------------------------------------------------------
+ //
+ // Static Fields
+ //
+ //------------------------------------------------------
+
+ #region Static Fields
+ private const double c_epsilon = 1e-5; // used in fp calculations
+ private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization
+ private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop
+ private static readonly LocalDataStoreSlot s_tempDefinitionsDataSlot = Thread.AllocateDataSlot();
+ private static readonly IComparer s_spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer();
+ private static readonly IComparer s_spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer();
+ private static readonly IComparer s_starDistributionOrderComparer = new StarDistributionOrderComparer();
+ private static readonly IComparer s_distributionOrderComparer = new DistributionOrderComparer();
+ private static readonly IComparer s_minRatioComparer = new MinRatioComparer();
+ private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer();
+ private static readonly IComparer s_starWeightComparer = new StarWeightComparer();
+
+ #endregion Static Fields
+
+ //------------------------------------------------------
+ //
+ // Private Structures / Classes
+ //
+ //------------------------------------------------------
+
+ #region Private Structures Classes
+
+ ///
+ /// Extended data instantiated on demand, when grid handles non-trivial case.
+ ///
+ private class ExtendedData
+ {
+ internal ColumnDefinitionCollection ColumnDefinitions; // collection of column definitions (logical tree support)
+ internal RowDefinitionCollection RowDefinitions; // collection of row definitions (logical tree support)
+ internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc
+ internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc
+ internal CellCache[] CellCachesCollection; // backing store for logical children
+ internal int CellGroup1; // index of the first cell in first cell group
+ internal int CellGroup2; // index of the first cell in second cell group
+ internal int CellGroup3; // index of the first cell in third cell group
+ internal int CellGroup4; // index of the first cell in forth cell group
+ internal DefinitionBase[] TempDefinitions; // temporary array used during layout for various purposes
+ // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length)
+ }
+
+ ///
+ /// Grid validity / property caches dirtiness flags
+ ///
+ [System.Flags]
+ private enum Flags
+ {
+ //
+ // the foolowing flags let grid tracking dirtiness in more granular manner:
+ // * Valid???Structure flags indicate that elements were added or removed.
+ // * Valid???Layout flags indicate that layout time portion of the information
+ // stored on the objects should be updated.
+ //
+ ValidDefinitionsUStructure = 0x00000001,
+ ValidDefinitionsVStructure = 0x00000002,
+ ValidCellsStructure = 0x00000004,
+
+ //
+ // boolean properties state
+ //
+ ShowGridLinesPropertyValue = 0x00000100, // show grid lines ?
+
+ //
+ // boolean flags
+ //
+ ListenToNotifications = 0x00001000, // "0" when all notifications are ignored
+ SizeToContentU = 0x00002000, // "1" if calculating to content in U direction
+ SizeToContentV = 0x00004000, // "1" if calculating to content in V direction
+ HasStarCellsU = 0x00008000, // "1" if at least one cell belongs to a Star column
+ HasStarCellsV = 0x00010000, // "1" if at least one cell belongs to a Star row
+ HasGroup3CellsInAutoRows = 0x00020000, // "1" if at least one cell of group 3 belongs to an Auto row
+ MeasureOverrideInProgress = 0x00040000, // "1" while in the context of Grid.MeasureOverride
+ ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride
+ }
+
+ #endregion Private Structures Classes
+
+ //------------------------------------------------------
+ //
+ // Properties
+ //
+ //------------------------------------------------------
+
+ #region Properties
+
+ ///
+ /// ShowGridLines property. This property is used mostly
+ /// for simplification of visual debuggig. When it is set
+ /// to true grid lines are drawn to visualize location
+ /// of grid lines.
+ ///
+ public static readonly DependencyProperty ShowGridLinesProperty =
+ DependencyProperty.Register(
+ "ShowGridLines",
+ typeof(bool),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(
+ false,
+ new PropertyChangedCallback(OnShowGridLinesPropertyChanged)));
+
+ ///
+ /// Column property. This is an attached property.
+ /// Grid defines Column property, so that it can be set
+ /// on any element treated as a cell. Column property
+ /// specifies child's position with respect to columns.
+ ///
+ ///
+ /// Columns are 0 - based. In order to appear in first column, element
+ /// should have Column property set to 0.
+ /// Default value for the property is 0.
+ ///
+ [CommonDependencyProperty]
+ public static readonly DependencyProperty ColumnProperty =
+ DependencyProperty.RegisterAttached(
+ "Column",
+ typeof(int),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(
+ 0,
+ new PropertyChangedCallback(OnCellAttachedPropertyChanged)),
+ new ValidateValueCallback(IsIntValueNotNegative));
+
+ ///
+ /// Row property. This is an attached property.
+ /// Grid defines Row, so that it can be set
+ /// on any element treated as a cell. Row property
+ /// specifies child's position with respect to rows.
+ ///
+ /// Rows are 0 - based. In order to appear in first row, element
+ /// should have Row property set to 0.
+ /// Default value for the property is 0.
+ ///
+ ///
+ [CommonDependencyProperty]
+ public static readonly DependencyProperty RowProperty =
+ DependencyProperty.RegisterAttached(
+ "Row",
+ typeof(int),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(
+ 0,
+ new PropertyChangedCallback(OnCellAttachedPropertyChanged)),
+ new ValidateValueCallback(IsIntValueNotNegative));
+
+ ///
+ /// ColumnSpan property. This is an attached property.
+ /// Grid defines ColumnSpan, so that it can be set
+ /// on any element treated as a cell. ColumnSpan property
+ /// specifies child's width with respect to columns.
+ /// Example, ColumnSpan == 2 means that child will span across two columns.
+ ///
+ ///
+ /// Default value for the property is 1.
+ ///
+ [CommonDependencyProperty]
+ public static readonly DependencyProperty ColumnSpanProperty =
+ DependencyProperty.RegisterAttached(
+ "ColumnSpan",
+ typeof(int),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(
+ 1,
+ new PropertyChangedCallback(OnCellAttachedPropertyChanged)),
+ new ValidateValueCallback(IsIntValueGreaterThanZero));
+
+ ///
+ /// RowSpan property. This is an attached property.
+ /// Grid defines RowSpan, so that it can be set
+ /// on any element treated as a cell. RowSpan property
+ /// specifies child's height with respect to row grid lines.
+ /// Example, RowSpan == 3 means that child will span across three rows.
+ ///
+ ///
+ /// Default value for the property is 1.
+ ///
+ [CommonDependencyProperty]
+ public static readonly DependencyProperty RowSpanProperty =
+ DependencyProperty.RegisterAttached(
+ "RowSpan",
+ typeof(int),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(
+ 1,
+ new PropertyChangedCallback(OnCellAttachedPropertyChanged)),
+ new ValidateValueCallback(IsIntValueGreaterThanZero));
+
+
+ ///
+ /// IsSharedSizeScope property marks scoping element for shared size.
+ ///
+ public static readonly DependencyProperty IsSharedSizeScopeProperty =
+ DependencyProperty.RegisterAttached(
+ "IsSharedSizeScope",
+ typeof(bool),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(
+ false,
+ new PropertyChangedCallback(DefinitionBase.OnIsSharedSizeScopePropertyChanged)));
+
+ #endregion Properties
+
+ //------------------------------------------------------
+ //
+ // Internal Structures / Classes
+ //
+ //------------------------------------------------------
+
+ #region Internal Structures Classes
+
+ ///
+ /// LayoutTimeSizeType is used internally and reflects layout-time size type.
+ ///
+ [System.Flags]
+ internal enum LayoutTimeSizeType : byte
+ {
+ None = 0x00,
+ Pixel = 0x01,
+ Auto = 0x02,
+ Star = 0x04,
+ }
+
+ #endregion Internal Structures Classes
+
+ //------------------------------------------------------
+ //
+ // Private Structures / Classes
+ //
+ //------------------------------------------------------
+
+ #region Private Structures Classes
+
+ ///
+ /// CellCache stored calculated values of
+ /// 1. attached cell positioning properties;
+ /// 2. size type;
+ /// 3. index of a next cell in the group;
+ ///
+ private struct CellCache
+ {
+ internal int ColumnIndex;
+ internal int RowIndex;
+ internal int ColumnSpan;
+ internal int RowSpan;
+ internal LayoutTimeSizeType SizeTypeU;
+ internal LayoutTimeSizeType SizeTypeV;
+ internal int Next;
+ internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } }
+ internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } }
+ internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } }
+ internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
+ }
+
+ ///
+ /// Helper class for representing a key for a span in hashtable.
+ ///
+ private class SpanKey
+ {
+ ///
+ /// Constructor.
+ ///
+ /// Starting index of the span.
+ /// Span count.
+ /// true for columns; false for rows.
+ internal SpanKey(int start, int count, bool u)
+ {
+ _start = start;
+ _count = count;
+ _u = u;
+ }
+
+ ///
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ int hash = (_start ^ (_count << 2));
+
+ if (_u) hash &= 0x7ffffff;
+ else hash |= 0x8000000;
+
+ return (hash);
+ }
+
+ ///
+ ///
+ ///
+ public override bool Equals(object obj)
+ {
+ SpanKey sk = obj as SpanKey;
+ return ( sk != null
+ && sk._start == _start
+ && sk._count == _count
+ && sk._u == _u );
+ }
+
+ ///
+ /// Returns start index of the span.
+ ///
+ internal int Start { get { return (_start); } }
+
+ ///
+ /// Returns span count.
+ ///
+ internal int Count { get { return (_count); } }
+
+ ///
+ /// Returns true if this is a column span.
+ /// false if this is a row span.
+ ///
+ internal bool U { get { return (_u); } }
+
+ private int _start;
+ private int _count;
+ private bool _u;
+ }
+
+ ///
+ /// SpanPreferredDistributionOrderComparer.
+ ///
+ private class SpanPreferredDistributionOrderComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ if (definitionX.UserSize.IsAuto)
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = definitionX.MinSize.CompareTo(definitionY.MinSize);
+ }
+ else
+ {
+ result = -1;
+ }
+ }
+ else
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = +1;
+ }
+ else
+ {
+ result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize);
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// SpanMaxDistributionOrderComparer.
+ ///
+ private class SpanMaxDistributionOrderComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ if (definitionX.UserSize.IsAuto)
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+ else
+ {
+ result = +1;
+ }
+ }
+ else
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = -1;
+ }
+ else
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// StarDistributionOrderComparer.
+ ///
+ private class StarDistributionOrderComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// DistributionOrderComparer.
+ ///
+ private class DistributionOrderComparer: IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange;
+ double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange;
+ result = xprime.CompareTo(yprime);
+ }
+
+ return result;
+ }
+ }
+
+
+ ///
+ /// StarDistributionOrderIndexComparer.
+ ///
+ private class StarDistributionOrderIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions)
+ {
+ Invariant.Assert(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// DistributionOrderComparer.
+ ///
+ private class DistributionOrderIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal DistributionOrderIndexComparer(DefinitionBase[] definitions)
+ {
+ Invariant.Assert(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange;
+ double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange;
+ result = xprime.CompareTo(yprime);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// RoundingErrorIndexComparer.
+ ///
+ private class RoundingErrorIndexComparer : IComparer
+ {
+ private readonly double[] errors;
+
+ internal RoundingErrorIndexComparer(double[] errors)
+ {
+ Invariant.Assert(errors != null);
+ this.errors = errors;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ int result;
+
+ if (!CompareNullRefs(indexX, indexY, out result))
+ {
+ double errorX = errors[indexX.Value];
+ double errorY = errors[indexY.Value];
+ result = errorX.CompareTo(errorY);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MinRatioComparer.
+ /// Sort by w/min (stored in MeasureSize), descending.
+ /// We query the list from the back, i.e. in ascending order of w/min.
+ ///
+ private class MinRatioComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionY, definitionX, out result))
+ {
+ result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MaxRatioComparer.
+ /// Sort by w/max (stored in SizeCache), ascending.
+ /// We query the list from the back, i.e. in descending order of w/max.
+ ///
+ private class MaxRatioComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// StarWeightComparer.
+ /// Sort by *-weight (stored in MeasureSize), ascending.
+ ///
+ private class StarWeightComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MinRatioIndexComparer.
+ ///
+ private class MinRatioIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal MinRatioIndexComparer(DefinitionBase[] definitions)
+ {
+ Invariant.Assert(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionY, definitionX, out result))
+ {
+ result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MaxRatioIndexComparer.
+ ///
+ private class MaxRatioIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal MaxRatioIndexComparer(DefinitionBase[] definitions)
+ {
+ Invariant.Assert(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MaxRatioIndexComparer.
+ ///
+ private class StarWeightIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal StarWeightIndexComparer(DefinitionBase[] definitions)
+ {
+ Invariant.Assert(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// Implementation of a simple enumerator of grid's logical children
+ ///
+ private class GridChildrenCollectionEnumeratorSimple : IEnumerator
+ {
+ internal GridChildrenCollectionEnumeratorSimple(Grid grid, bool includeChildren)
+ {
+ Debug.Assert(grid != null);
+ _currentEnumerator = -1;
+ _enumerator0 = new ColumnDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null);
+ _enumerator1 = new RowDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null);
+ // GridLineRenderer is NOT included into this enumerator.
+ _enumerator2Index = 0;
+ if (includeChildren)
+ {
+ _enumerator2Collection = grid.Children;
+ _enumerator2Count = _enumerator2Collection.Count;
+ }
+ else
+ {
+ _enumerator2Collection = null;
+ _enumerator2Count = 0;
+ }
+ }
+
+ public bool MoveNext()
+ {
+ while (_currentEnumerator < 3)
+ {
+ if (_currentEnumerator >= 0)
+ {
+ switch (_currentEnumerator)
+ {
+ case (0): if (_enumerator0.MoveNext()) { _currentChild = _enumerator0.Current; return (true); } break;
+ case (1): if (_enumerator1.MoveNext()) { _currentChild = _enumerator1.Current; return (true); } break;
+ case (2): if (_enumerator2Index < _enumerator2Count)
+ {
+ _currentChild = _enumerator2Collection[_enumerator2Index];
+ _enumerator2Index++;
+ return (true);
+ }
+ break;
+ }
+ }
+ _currentEnumerator++;
+ }
+ return (false);
+ }
+
+ public Object Current
+ {
+ get
+ {
+ if (_currentEnumerator == -1)
+ {
+ #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception
+ throw new InvalidOperationException(SR.Get(SRID.EnumeratorNotStarted));
+ }
+ if (_currentEnumerator >= 3)
+ {
+ #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception
+ throw new InvalidOperationException(SR.Get(SRID.EnumeratorReachedEnd));
+ }
+
+ // assert below is not true anymore since UIElementCollection allowes for null children
+ //Debug.Assert(_currentChild != null);
+ return (_currentChild);
+ }
+ }
+
+ public void Reset()
+ {
+ _currentEnumerator = -1;
+ _currentChild = null;
+ _enumerator0.Reset();
+ _enumerator1.Reset();
+ _enumerator2Index = 0;
+ }
+
+ private int _currentEnumerator;
+ private Object _currentChild;
+ private ColumnDefinitionCollection.Enumerator _enumerator0;
+ private RowDefinitionCollection.Enumerator _enumerator1;
+ private UIElementCollection _enumerator2Collection;
+ private int _enumerator2Index;
+ private int _enumerator2Count;
+ }
+
+ ///
+ /// Helper to render grid lines.
+ ///
+ internal class GridLinesRenderer : DrawingVisual
+ {
+ ///
+ /// Static initialization
+ ///
+ static GridLinesRenderer()
+ {
+ s_oddDashPen = new Pen(Brushes.Blue, c_penWidth);
+ DoubleCollection oddDashArray = new DoubleCollection();
+ oddDashArray.Add(c_dashLength);
+ oddDashArray.Add(c_dashLength);
+ s_oddDashPen.DashStyle = new DashStyle(oddDashArray, 0);
+ s_oddDashPen.DashCap = PenLineCap.Flat;
+ s_oddDashPen.Freeze();
+
+ s_evenDashPen = new Pen(Brushes.Yellow, c_penWidth);
+ DoubleCollection evenDashArray = new DoubleCollection();
+ evenDashArray.Add(c_dashLength);
+ evenDashArray.Add(c_dashLength);
+ s_evenDashPen.DashStyle = new DashStyle(evenDashArray, c_dashLength);
+ s_evenDashPen.DashCap = PenLineCap.Flat;
+ s_evenDashPen.Freeze();
+ }
+
+ ///
+ /// UpdateRenderBounds.
+ ///
+ /// Size of render bounds
+ internal void UpdateRenderBounds(Size boundsSize)
+ {
+ using (DrawingContext drawingContext = RenderOpen())
+ {
+ Grid grid = VisualTreeHelper.GetParent(this) as Grid;
+ if ( grid == null
+ || grid.ShowGridLines == false )
+ {
+ return;
+ }
+
+ for (int i = 1; i < grid.DefinitionsU.Length; ++i)
+ {
+ DrawGridLine(
+ drawingContext,
+ grid.DefinitionsU[i].FinalOffset, 0.0,
+ grid.DefinitionsU[i].FinalOffset, boundsSize.Height);
+ }
+
+ for (int i = 1; i < grid.DefinitionsV.Length; ++i)
+ {
+ DrawGridLine(
+ drawingContext,
+ 0.0, grid.DefinitionsV[i].FinalOffset,
+ boundsSize.Width, grid.DefinitionsV[i].FinalOffset);
+ }
+ }
+ }
+
+ ///
+ /// Draw single hi-contrast line.
+ ///
+ private static void DrawGridLine(
+ DrawingContext drawingContext,
+ double startX,
+ double startY,
+ double endX,
+ double endY)
+ {
+ Point start = new Point(startX, startY);
+ Point end = new Point(endX, endY);
+ drawingContext.DrawLine(s_oddDashPen, start, end);
+ drawingContext.DrawLine(s_evenDashPen, start, end);
+ }
+
+ private const double c_dashLength = 4.0; //
+ private const double c_penWidth = 1.0; //
+ private static readonly Pen s_oddDashPen; // first pen to draw dash
+ private static readonly Pen s_evenDashPen; // second pen to draw dash
+ private static readonly Point c_zeroPoint = new Point(0, 0);
+ }
+
+ #endregion Private Structures Classes
+
+ //------------------------------------------------------
+ //
+ // Extended debugging for grid
+ //
+ //------------------------------------------------------
+
+#if GRIDPARANOIA
+ private static double _performanceFrequency;
+ private static readonly bool _performanceFrequencyInitialized = InitializePerformanceFrequency();
+
+ //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")]
+ private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
+
+ //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")]
+ private static extern bool QueryPerformanceFrequency(out long lpFrequency);
+
+ private static double CostInMilliseconds(long count)
+ {
+ return ((double)count / _performanceFrequency);
+ }
+
+ private static long Cost(long startCount, long endCount)
+ {
+ long l = endCount - startCount;
+ if (l < 0) { l += long.MaxValue; }
+ return (l);
+ }
+
+ private static bool InitializePerformanceFrequency()
+ {
+ long l;
+ QueryPerformanceFrequency(out l);
+ _performanceFrequency = (double)l * 0.001;
+ return (true);
+ }
+
+ private struct Counter
+ {
+ internal long Start;
+ internal long Total;
+ internal int Calls;
+ }
+
+ private Counter[] _counters;
+ private bool _hasNewCounterInfo;
+#endif // GRIDPARANOIA
+
+ //
+ // This property
+ // 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject
+ // 2. This is a performance optimization
+ //
+ internal override int EffectiveValuesInitialSize
+ {
+ get { return 9; }
+ }
+
+ [Conditional("GRIDPARANOIA")]
+ internal void EnterCounterScope(Counters scopeCounter)
+ {
+ #if GRIDPARANOIA
+ if (ID == "CountThis")
+ {
+ if (_counters == null)
+ {
+ _counters = new Counter[(int)Counters.Count];
+ }
+ ExitCounterScope(Counters.Default);
+ EnterCounter(scopeCounter);
+ }
+ else
+ {
+ _counters = null;
+ }
+ #endif // GRIDPARANOIA
+ }
+
+ [Conditional("GRIDPARANOIA")]
+ internal void ExitCounterScope(Counters scopeCounter)
+ {
+ #if GRIDPARANOIA
+ if (_counters != null)
+ {
+ if (scopeCounter != Counters.Default)
+ {
+ ExitCounter(scopeCounter);
+ }
+
+ if (_hasNewCounterInfo)
+ {
+ string NFormat = "F6";
+ Console.WriteLine(
+ "\ncounter name | total t (ms) | # of calls | per call t (ms)"
+ + "\n----------------------+---------------+---------------+----------------------" );
+
+ for (int i = 0; i < _counters.Length; ++i)
+ {
+ if (_counters[i].Calls > 0)
+ {
+ Counters counter = (Counters)i;
+ double total = CostInMilliseconds(_counters[i].Total);
+ double single = total / _counters[i].Calls;
+ string counterName = counter.ToString();
+ string separator;
+
+ if (counterName.Length < 8) { separator = "\t\t\t"; }
+ else if (counterName.Length < 16) { separator = "\t\t"; }
+ else { separator = "\t"; }
+
+ Console.WriteLine(
+ counter.ToString() + separator
+ + total.ToString(NFormat) + "\t"
+ + _counters[i].Calls + "\t\t"
+ + single.ToString(NFormat));
+
+ _counters[i] = new Counter();
+ }
+ }
+ }
+ _hasNewCounterInfo = false;
+ }
+ #endif // GRIDPARANOIA
+ }
+
+ [Conditional("GRIDPARANOIA")]
+ internal void EnterCounter(Counters counter)
+ {
+ #if GRIDPARANOIA
+ if (_counters != null)
+ {
+ Debug.Assert((int)counter < _counters.Length);
+
+ int i = (int)counter;
+ QueryPerformanceCounter(out _counters[i].Start);
+ }
+ #endif // GRIDPARANOIA
+ }
+
+ [Conditional("GRIDPARANOIA")]
+ internal void ExitCounter(Counters counter)
+ {
+ #if GRIDPARANOIA
+ if (_counters != null)
+ {
+ Debug.Assert((int)counter < _counters.Length);
+
+ int i = (int)counter;
+ long l;
+ QueryPerformanceCounter(out l);
+ l = Cost(_counters[i].Start, l);
+ _counters[i].Total += l;
+ _counters[i].Calls++;
+ _hasNewCounterInfo = true;
+ }
+ #endif // GRIDPARANOIA
+ }
+
+ internal enum Counters : int
+ {
+ Default = -1,
+
+ MeasureOverride,
+ _ValidateColsStructure,
+ _ValidateRowsStructure,
+ _ValidateCells,
+ _MeasureCell,
+ __MeasureChild,
+ _CalculateDesiredSize,
+
+ ArrangeOverride,
+ _SetFinalSize,
+ _ArrangeChildHelper2,
+ _PositionCell,
+
+ Count,
+ }
+ }
+}
+
+
+
+
diff --git a/src/Avalonia.Controls/Grid/DefinitionBase.cs b/src/Avalonia.Controls/Grid/DefinitionBase.cs
deleted file mode 100644
index 051ed49289..0000000000
--- a/src/Avalonia.Controls/Grid/DefinitionBase.cs
+++ /dev/null
@@ -1,339 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections;
-using System.Diagnostics;
-
-namespace Avalonia.Controls
-{
- ///
- /// Base class for and .
- ///
- 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
-
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty SharedSizeGroupProperty =
- AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true);
-
- ///
- /// Gets or sets the name of the shared size group of the column or row.
- ///
- public string SharedSizeGroup
- {
- 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?.PrivateSharedSizeScope != null )
- {
- _privateSharedSizeScope = Parent.PrivateSharedSizeScope;
- _sharedState = _privateSharedSizeScope.EnsureSharedState(SharedSizeGroup);
- _sharedState.AddMember(this);
- }
- }
-
- internal Grid Parent { get; set; }
-
- ///
- /// Callback to notify about exitting model tree.
- ///
- internal void OnExitParentTree()
- {
- _offset = 0;
- if (_sharedState != null)
- {
- _sharedState.RemoveMember(this);
- _sharedState = null;
- }
- }
-
- ///
- /// Performs action preparing definition to enter layout calculation mode.
- ///
- 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);
- }
- }
-
- ///
- /// 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 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 { return (_sizeType); }
- set { _sizeType = value; }
- }
-
- ///
- /// Returns or sets measure size for the definition.
- ///
- internal double MeasureSize
- {
- get { return (_measureSize); }
- set { _measureSize = value; }
- }
-
- ///
- /// Returns definition's layout time type sensitive preferred size.
- ///
- ///
- /// Returned value is guaranteed to be true preferred size.
- ///
- internal double PreferredSize
- {
- get
- {
- double preferredSize = MinSize;
- if (_sizeType != LayoutTimeSizeType.Auto
- && preferredSize < _measureSize)
- {
- preferredSize = _measureSize;
- }
- return (preferredSize);
- }
- }
-
- ///
- /// Returns or sets size cache for the definition.
- ///
- internal double SizeCache
- {
- get { return (_sizeCache); }
- set { _sizeCache = value; }
- }
-
- ///
- /// Returns min size.
- ///
- internal double MinSize
- {
- get
- {
- double minSize = _minSize;
- if (UseSharedMinimum
- && _sharedState != null
- && minSize < _sharedState.MinSize)
- {
- minSize = _sharedState.MinSize;
- }
- return (minSize);
- }
- }
-
- ///
- /// Returns min size, always taking into account shared state.
- ///
- internal double MinSizeForArrange
- {
- get
- {
- double minSize = _minSize;
- if (_sharedState != null
- && (UseSharedMinimum || !LayoutWasUpdated)
- && minSize < _sharedState.MinSize)
- {
- minSize = _sharedState.MinSize;
- }
- return (minSize);
- }
- }
-
- ///
- /// Offset.
- ///
- 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
deleted file mode 100644
index 511853a982..0000000000
--- a/src/Avalonia.Controls/Grid/Grid.cs
+++ /dev/null
@@ -1,2378 +0,0 @@
-// 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 System.Linq;
-using System.Reactive.Linq;
-using System.Runtime.CompilerServices;
-using Avalonia.Collections;
-using Avalonia.Controls.Utils;
-using System.Threading;
-using JetBrains.Annotations;
-using Avalonia.Controls;
-using Avalonia;
-using System.Collections;
-using Avalonia.Utilities;
-using Avalonia.Layout;
-
-namespace Avalonia.Controls
-{
- public class Grid : Panel
- {
- internal bool CellsStructureDirty = true;
- internal bool SizeToContentU;
- internal bool SizeToContentV;
- internal bool HasStarCellsU;
- internal bool HasStarCellsV;
- internal bool HasGroup3CellsInAutoRows;
- internal bool DefinitionsDirty;
- internal bool IsTrivialGrid => (_definitionsU?.Length <= 1) &&
- (_definitionsV?.Length <= 1);
- internal int CellGroup1;
- internal int CellGroup2;
- internal int CellGroup3;
- internal int CellGroup4;
-
- internal bool HasSharedSizeScope()
- {
- return this.GetValue(Grid.PrivateSharedSizeScopeProperty) != null;
- }
-
- ///
- /// Helper for Comparer methods.
- ///
- ///
- /// true if one or both of x and y are null, in which case result holds
- /// the relative sort order.
- ///
- internal static bool CompareNullRefs(object x, object y, out int result)
- {
- result = 2;
-
- if (x == null)
- {
- if (y == null)
- {
- result = 0;
- }
- else
- {
- result = -1;
- }
- }
- else
- {
- if (y == null)
- {
- result = 1;
- }
- }
-
- return (result != 2);
- }
-
- // temporary array used during layout for various purposes
- // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length)
- private DefinitionBase[] _tempDefinitions;
-
-
- private GridLinesRenderer _gridLinesRenderer;
-
- // Keeps track of definition indices.
- private int[] _definitionIndices;
-
- private GridCellCache[] _cellCache;
-
- // Stores unrounded values and rounding errors during layout rounding.
- private double[] _roundingErrors;
- private ColumnDefinitions _columnDefinitions;
- private RowDefinitions _rowDefinitions;
- private DefinitionBase[] _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
- private DefinitionBase[] _definitionsV = new DefinitionBase[1] { new RowDefinition() };
-
- internal SharedSizeScope PrivateSharedSizeScope
- {
- get { return GetPrivateSharedSizeScope(this); }
- set { SetPrivateSharedSizeScope(this, value); }
- }
-
- // 5 is an arbitrary constant chosen to end the measure loop
- private const int _layoutLoopMaxCount = 5;
- private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot;
- private static readonly IComparer _spanPreferredDistributionOrderComparer;
- private static readonly IComparer _spanMaxDistributionOrderComparer;
- private static readonly IComparer _minRatioComparer;
- private static readonly IComparer _maxRatioComparer;
- private static readonly IComparer _starWeightComparer;
-
- ///
- /// Helper accessor to layout time array of definitions.
- ///
- private DefinitionBase[] TempDefinitions
- {
- get
- {
- int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2;
-
- if (_tempDefinitions == null
- || _tempDefinitions.Length < requiredLength)
- {
- WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot);
- if (tempDefinitionsWeakRef == null)
- {
- _tempDefinitions = new DefinitionBase[requiredLength];
- Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions));
- }
- else
- {
- _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target;
- if (_tempDefinitions == null
- || _tempDefinitions.Length < requiredLength)
- {
- _tempDefinitions = new DefinitionBase[requiredLength];
- tempDefinitionsWeakRef.Target = _tempDefinitions;
- }
- }
- }
- return (_tempDefinitions);
- }
- }
-
- ///
- /// Helper accessor to definition indices.
- ///
- private int[] DefinitionIndices
- {
- get
- {
- int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2;
-
- if (_definitionIndices == null || _definitionIndices.Length < requiredLength)
- {
- _definitionIndices = new int[requiredLength];
- }
-
- return _definitionIndices;
- }
- }
-
- ///
- /// Helper accessor to rounding errors.
- ///
- private double[] RoundingErrors
- {
- get
- {
- int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length);
-
- if (_roundingErrors == null && requiredLength == 0)
- {
- _roundingErrors = new double[1];
- }
- else if (_roundingErrors == null || _roundingErrors.Length < requiredLength)
- {
- _roundingErrors = new double[requiredLength];
- }
- return _roundingErrors;
- }
- }
-
- static Grid()
- {
- ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged);
- IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopePropertyChanged);
- BoundsProperty.Changed.AddClassHandler(BoundsPropertyChanged);
-
- AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
-
- _tempDefinitionsDataSlot = Thread.AllocateDataSlot();
- _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer();
- _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer();
- _minRatioComparer = new MinRatioComparer();
- _maxRatioComparer = new MaxRatioComparer();
- _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);
-
- UpdateSharedSizeScopes(grid);
- }
-
- private static void IsSharedSizeScopePropertyChanged(Control control, AvaloniaPropertyChangedEventArgs e)
- {
- if ((bool)e.NewValue)
- {
- control.SetValue(Grid.PrivateSharedSizeScopeProperty, new SharedSizeScope());
- }
- else
- {
- control.SetValue(Grid.PrivateSharedSizeScopeProperty, null);
- }
- }
-
- static void UpdateSharedSizeScopes(Grid grid)
- {
- for (int i = 0; i < grid._definitionsU.Length; i++)
- if (grid._definitionsU[i].SharedSizeGroup != null)
- grid._definitionsU[i].UpdateSharedScope();
- for (int i = 0; i < grid._definitionsV.Length; i++)
- if (grid._definitionsV[i].SharedSizeGroup != null)
- grid._definitionsV[i].UpdateSharedScope();
- }
-
- ///
- /// Defines the Column attached property.
- ///
- public static readonly AttachedProperty ColumnProperty =
- AvaloniaProperty.RegisterAttached(
- "Column",
- validate: ValidateColumn);
-
- ///
- /// Defines the ColumnSpan attached property.
- ///
- public static readonly AttachedProperty ColumnSpanProperty =
- AvaloniaProperty.RegisterAttached("ColumnSpan", 1);
-
- ///
- /// Defines the Row attached property.
- ///
- public static readonly AttachedProperty RowProperty =
- AvaloniaProperty.RegisterAttached(
- "Row",
- validate: ValidateRow);
-
- ///
- /// Defines the RowSpan attached property.
- ///
- public static readonly AttachedProperty RowSpanProperty =
- AvaloniaProperty.RegisterAttached("RowSpan", 1);
-
- public static readonly AttachedProperty IsSharedSizeScopeProperty =
- AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false);
-
- internal static readonly AttachedProperty PrivateSharedSizeScopeProperty =
- AvaloniaProperty.RegisterAttached("&&PrivateSharedSizeScope", null, inherits: true);
-
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty ShowGridLinesProperty =
- AvaloniaProperty.Register(
- nameof(ShowGridLines),
- defaultValue: false);
-
- ///
- /// ShowGridLines property.
- ///
- public bool ShowGridLines
- {
- get { return GetValue(ShowGridLinesProperty); }
- set { SetValue(ShowGridLinesProperty, value); }
- }
-
- ///
- /// Gets or sets the columns definitions for the grid.
- ///
- public ColumnDefinitions ColumnDefinitions
- {
- get
- {
- if (_columnDefinitions == null)
- {
- ColumnDefinitions = new ColumnDefinitions();
- }
-
- return _columnDefinitions;
- }
- set
- {
- _columnDefinitions = value;
- _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate());
- DefinitionsDirty = true;
-
- 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() };
- }
- else
- {
- _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.
- ///
- public RowDefinitions RowDefinitions
- {
- get
- {
- if (_rowDefinitions == null)
- {
- RowDefinitions = new RowDefinitions();
- }
-
- return _rowDefinitions;
- }
- set
- {
- _rowDefinitions = value;
- _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate());
-
- DefinitionsDirty = true;
-
- if (_rowDefinitions.Count > 0)
- _definitionsV = _rowDefinitions.Cast().ToArray();
-
- _rowDefinitions.CollectionChanged += delegate
- {
- CallExitParentTree(_definitionsU);
-
- if (_rowDefinitions.Count == 0)
- {
- _definitionsV = new DefinitionBase[1] { new RowDefinition() };
- }
- else
- {
- _definitionsV = _rowDefinitions.Cast().ToArray();
- DefinitionsDirty = true;
- }
- CallEnterParentTree(_definitionsU);
-
- Invalidate();
- };
- }
- }
-
-
- ///
- /// Gets the value of the Column attached property for a control.
- ///
- /// The control.
- /// The control's column.
- public static int GetColumn(AvaloniaObject element)
- {
- return element.GetValue(ColumnProperty);
- }
-
- ///
- /// Gets the value of the ColumnSpan attached property for a control.
- ///
- /// The control.
- /// The control's column span.
- public static int GetColumnSpan(AvaloniaObject element)
- {
- return element.GetValue(ColumnSpanProperty);
- }
-
- ///
- /// Gets the value of the Row attached property for a control.
- ///
- /// The control.
- /// The control's row.
- public static int GetRow(AvaloniaObject element)
- {
- return element.GetValue(RowProperty);
- }
-
- ///
- /// Gets the value of the RowSpan attached property for a control.
- ///
- /// The control.
- /// The control's row span.
- public static int GetRowSpan(AvaloniaObject element)
- {
- return element.GetValue(RowSpanProperty);
- }
-
- ///
- /// Gets the value of the IsSharedSizeScope attached property for a control.
- ///
- /// The control.
- /// The control's IsSharedSizeScope value.
- public static bool GetIsSharedSizeScope(AvaloniaObject element)
- {
- return element.GetValue(IsSharedSizeScopeProperty);
- }
-
- ///
- /// Sets the value of the IsSharedSizeScope attached property for a control.
- ///
- public static void SetIsSharedSizeScope(AvaloniaObject element, bool value)
- {
- element.SetValue(IsSharedSizeScopeProperty, value);
- }
-
- internal static SharedSizeScope GetPrivateSharedSizeScope(AvaloniaObject element)
- {
- return element.GetValue(PrivateSharedSizeScopeProperty);
- }
-
- internal static void SetPrivateSharedSizeScope(AvaloniaObject element, SharedSizeScope value)
- {
- element.SetValue(PrivateSharedSizeScopeProperty, value);
- }
-
- ///
- /// Sets the value of the Column attached property for a control.
- ///
- /// The control.
- /// The column value.
- public static void SetColumn(AvaloniaObject element, int value)
- {
- element.SetValue(ColumnProperty, value);
- }
-
- ///
- /// Sets the value of the ColumnSpan attached property for a control.
- ///
- /// The control.
- /// The column span value.
- public static void SetColumnSpan(AvaloniaObject element, int value)
- {
- element.SetValue(ColumnSpanProperty, value);
- }
-
- ///
- /// Sets the value of the Row attached property for a control.
- ///
- /// The control.
- /// The row value.
- public static void SetRow(AvaloniaObject element, int value)
- {
- element.SetValue(RowProperty, value);
- }
-
- ///
- /// Sets the value of the RowSpan attached property for a control.
- ///
- /// The control.
- /// The row span value.
- public static void SetRowSpan(AvaloniaObject element, int value)
- {
- element.SetValue(RowSpanProperty, value);
- }
-
- ///
- /// Content measurement.
- ///
- /// Constraint
- /// Desired size
- protected override Size MeasureOverride(Size constraint)
- {
- Size gridDesiredSize;
-
- try
- {
- if (IsTrivialGrid)
- {
- gridDesiredSize = new Size();
-
- for (int i = 0, count = Children.Count; i < count; ++i)
- {
- var child = Children[i];
- if (child != null)
- {
- child.Measure(constraint);
- gridDesiredSize = new Size(
- Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),
- Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));
- }
- }
- }
- else
- {
- {
- bool sizeToContentU = double.IsPositiveInfinity(constraint.Width);
- bool sizeToContentV = double.IsPositiveInfinity(constraint.Height);
-
- // Clear index information and rounding errors
- if (DefinitionsDirty)
- {
- if (_definitionIndices != null)
- {
- Array.Clear(_definitionIndices, 0, _definitionIndices.Length);
- _definitionIndices = null;
- }
-
- if (UseLayoutRounding)
- {
- if (_roundingErrors != null)
- {
- Array.Clear(_roundingErrors, 0, _roundingErrors.Length);
- _roundingErrors = null;
- }
- }
-
- DefinitionsDirty = false;
- }
-
- ValidateDefinitionsLayout(_definitionsU, sizeToContentU);
- ValidateDefinitionsLayout(_definitionsV, sizeToContentV);
-
- CellsStructureDirty |= (SizeToContentU != sizeToContentU)
- || (SizeToContentV != sizeToContentV);
-
- SizeToContentU = sizeToContentU;
- SizeToContentV = sizeToContentV;
- }
-
- ValidateCells();
-
- Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0);
-
- MeasureCellsGroup(CellGroup1, constraint, false, false);
-
- {
- // after Group1 is measured, only Group3 may have cells belonging to Auto rows.
- bool canResolveStarsV = !HasGroup3CellsInAutoRows;
-
- if (canResolveStarsV)
- {
- if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
- MeasureCellsGroup(CellGroup2, constraint, false, false);
- if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
- MeasureCellsGroup(CellGroup3, constraint, false, false);
- }
- else
- {
- // if at least one cell exists in Group2, it must be measured before
- // StarsU can be resolved.
- bool canResolveStarsU = CellGroup2 > _cellCache.Length;
- if (canResolveStarsU)
- {
- if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
- MeasureCellsGroup(CellGroup3, constraint, false, false);
- if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
- }
- else
- {
- // This is a revision to the algorithm employed for the cyclic
- // dependency case described above. We now repeatedly
- // measure Group3 and Group2 until their sizes settle. We
- // also use a count heuristic to break a loop in case of one.
-
- bool hasDesiredSizeUChanged = false;
- int cnt = 0;
-
- // Cache Group2MinWidths & Group3MinHeights
- double[] group2MinSizes = CacheMinSizes(CellGroup2, false);
- double[] group3MinSizes = CacheMinSizes(CellGroup3, true);
-
- MeasureCellsGroup(CellGroup2, constraint, false, true);
-
- do
- {
- if (hasDesiredSizeUChanged)
- {
- // Reset cached Group3Heights
- ApplyCachedMinSizes(group3MinSizes, true);
- }
-
- if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
- MeasureCellsGroup(CellGroup3, constraint, false, false);
-
- // Reset cached Group2Widths
- ApplyCachedMinSizes(group2MinSizes, false);
-
- if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
- MeasureCellsGroup(CellGroup2, constraint,
- cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged);
- }
- while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount);
- }
- }
- }
-
- MeasureCellsGroup(CellGroup4, constraint, false, false);
-
- gridDesiredSize = new Size(
- CalculateDesiredSize(_definitionsU),
- CalculateDesiredSize(_definitionsV));
- }
- }
- finally
- {
- }
-
- UpdateSharedSizeScopes(this);
- return (gridDesiredSize);
- }
-
- ///
- /// Content arrangement.
- ///
- /// Arrange size
- protected override Size ArrangeOverride(Size arrangeSize)
- {
- try
- {
- if (IsTrivialGrid)
- {
- for (int i = 0, count = Children.Count; i < count; ++i)
- {
- var child = Children[i];
- if (child != null)
- {
- child.Arrange(new Rect(arrangeSize));
- }
- }
- }
- else
- {
- Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0);
-
- SetFinalSize(_definitionsU, arrangeSize.Width, true);
- SetFinalSize(_definitionsV, arrangeSize.Height, false);
-
- for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell)
- {
- IControl cell = Children[currentCell];
- if (cell == null)
- {
- continue;
- }
-
- int columnIndex = _cellCache[currentCell].ColumnIndex;
- int rowIndex = _cellCache[currentCell].RowIndex;
- int columnSpan = _cellCache[currentCell].ColumnSpan;
- int rowSpan = _cellCache[currentCell].RowSpan;
-
- Rect cellRect = new Rect(
- columnIndex == 0 ? 0.0 : _definitionsU[columnIndex].FinalOffset,
- rowIndex == 0 ? 0.0 : _definitionsV[rowIndex].FinalOffset,
- GetFinalSizeForRange(_definitionsU, columnIndex, columnSpan),
- GetFinalSizeForRange(_definitionsV, rowIndex, rowSpan));
-
- cell.Arrange(cellRect);
- }
-
- // update render bound on grid lines renderer visual
- var gridLinesRenderer = EnsureGridLinesRenderer();
- if (gridLinesRenderer != null)
- {
- gridLinesRenderer.UpdateRenderBounds(arrangeSize);
- }
- }
- }
- finally
- {
- SetValid();
- }
-
- for (var i = 0; i < ColumnDefinitions.Count; i++)
- {
- ColumnDefinitions[i].ActualWidth = GetFinalColumnDefinitionWidth(i);
- }
-
- for (var i = 0; i < RowDefinitions.Count; i++)
- {
- RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i);
- }
-
- UpdateSharedSizeScopes(this);
- return (arrangeSize);
- }
-
- ///
- /// Returns final width for a column.
- ///
- ///
- /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data.
- ///
- private double GetFinalColumnDefinitionWidth(int columnIndex)
- {
- double value = 0.0;
-
- // actual value calculations require structure to be up-to-date
- if (!DefinitionsDirty)
- {
- value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset;
- if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; }
- }
- return (value);
- }
-
- ///
- /// Returns final height for a row.
- ///
- ///
- /// Used from public RowDefinition ActualHeight. Calculates final height using offset data.
- ///
- private double GetFinalRowDefinitionHeight(int rowIndex)
- {
- double value = 0.0;
-
- // actual value calculations require structure to be up-to-date
- if (!DefinitionsDirty)
- {
- value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset;
- if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; }
- }
- return (value);
- }
-
- ///
- /// Invalidates grid caches and makes the grid dirty for measure.
- ///
- internal void Invalidate()
- {
- CellsStructureDirty = true;
- InvalidateMeasure();
- }
-
- ///
- /// Lays out cells according to rows and columns, and creates lookup grids.
- ///
- private void ValidateCells()
- {
- if (!CellsStructureDirty) return;
-
- _cellCache = new GridCellCache[Children.Count];
- CellGroup1 = int.MaxValue;
- CellGroup2 = int.MaxValue;
- CellGroup3 = int.MaxValue;
- CellGroup4 = int.MaxValue;
-
- bool hasStarCellsU = false;
- bool hasStarCellsV = false;
- bool hasGroup3CellsInAutoRows = false;
-
- for (int i = _cellCache.Length - 1; i >= 0; --i)
- {
- var child = Children[i] as Control;
-
- if (child == null)
- {
- continue;
- }
-
- var cell = new GridCellCache();
-
- // read indices from the corresponding properties
- // clamp to value < number_of_columns
- // column >= 0 is guaranteed by property value validation callback
- cell.ColumnIndex = Math.Min(GetColumn(child), _definitionsU.Length - 1);
-
- // clamp to value < number_of_rows
- // row >= 0 is guaranteed by property value validation callback
- cell.RowIndex = Math.Min(GetRow(child), _definitionsV.Length - 1);
-
- // read span properties
- // clamp to not exceed beyond right side of the grid
- // column_span > 0 is guaranteed by property value validation callback
- cell.ColumnSpan = Math.Min(GetColumnSpan(child), _definitionsU.Length - cell.ColumnIndex);
-
- // clamp to not exceed beyond bottom side of the grid
- // row_span > 0 is guaranteed by property value validation callback
- cell.RowSpan = Math.Min(GetRowSpan(child), _definitionsV.Length - cell.RowIndex);
-
- Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < _definitionsU.Length);
- Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < _definitionsV.Length);
-
- //
- // calculate and cache length types for the child
- //
- cell.SizeTypeU = GetLengthTypeForRange(_definitionsU, cell.ColumnIndex, cell.ColumnSpan);
- cell.SizeTypeV = GetLengthTypeForRange(_definitionsV, cell.RowIndex, cell.RowSpan);
-
- hasStarCellsU |= cell.IsStarU;
- hasStarCellsV |= cell.IsStarV;
-
- //
- // distribute cells into four groups.
- //
- if (!cell.IsStarV)
- {
- if (!cell.IsStarU)
- {
- cell.Next = CellGroup1;
- CellGroup1 = i;
- }
- else
- {
- cell.Next = CellGroup3;
- CellGroup3 = i;
-
- // remember if this cell belongs to auto row
- hasGroup3CellsInAutoRows |= cell.IsAutoV;
- }
- }
- else
- {
- if (cell.IsAutoU
- // note below: if spans through Star column it is NOT Auto
- && !cell.IsStarU)
- {
- cell.Next = CellGroup2;
- CellGroup2 = i;
- }
- else
- {
- cell.Next = CellGroup4;
- CellGroup4 = i;
- }
- }
-
- _cellCache[i] = cell;
- }
-
- HasStarCellsU = hasStarCellsU;
- HasStarCellsV = hasStarCellsV;
- HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows;
-
- CellsStructureDirty = false;
- }
-
- ///
- /// Validates layout time size type information on given array of definitions.
- /// Sets MinSize and MeasureSizes.
- ///
- /// Array of definitions to update.
- /// if "true" then star definitions are treated as Auto.
- private void ValidateDefinitionsLayout(
- DefinitionBase[] definitions,
- bool treatStarAsAuto)
- {
- for (int i = 0; i < definitions.Length; ++i)
- {
- definitions[i].OnBeforeLayout(this);
-
- double userMinSize = definitions[i].UserMinSize;
- double userMaxSize = definitions[i].UserMaxSize;
- double userSize = 0;
-
- switch (definitions[i].UserSize.GridUnitType)
- {
- case (GridUnitType.Pixel):
- definitions[i].SizeType = LayoutTimeSizeType.Pixel;
- userSize = definitions[i].UserSize.Value;
-
- // this was brought with NewLayout and defeats squishy behavior
- userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
- break;
- case (GridUnitType.Auto):
- definitions[i].SizeType = LayoutTimeSizeType.Auto;
- userSize = double.PositiveInfinity;
- break;
- case (GridUnitType.Star):
- if (treatStarAsAuto)
- {
- definitions[i].SizeType = LayoutTimeSizeType.Auto;
- userSize = double.PositiveInfinity;
- }
- else
- {
- definitions[i].SizeType = LayoutTimeSizeType.Star;
- userSize = double.PositiveInfinity;
- }
- break;
- default:
- Debug.Assert(false);
- break;
- }
-
- definitions[i].UpdateMinSize(userMinSize);
- definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
- }
- }
-
- private double[] CacheMinSizes(int cellsHead, bool isRows)
- {
- double[] minSizes = isRows ? new double[_definitionsV.Length]
- : new double[_definitionsU.Length];
-
- for (int j = 0; j < minSizes.Length; j++)
- {
- minSizes[j] = -1;
- }
-
- int i = cellsHead;
- do
- {
- if (isRows)
- {
- minSizes[_cellCache[i].RowIndex] = _definitionsV[_cellCache[i].RowIndex].MinSize;
- }
- else
- {
- minSizes[_cellCache[i].ColumnIndex] = _definitionsU[_cellCache[i].ColumnIndex].MinSize;
- }
-
- i = _cellCache[i].Next;
- } while (i < _cellCache.Length);
-
- return minSizes;
- }
-
- private void ApplyCachedMinSizes(double[] minSizes, bool isRows)
- {
- for (int i = 0; i < minSizes.Length; i++)
- {
- if (MathUtilities.GreaterThanOrClose(minSizes[i], 0))
- {
- if (isRows)
- {
- _definitionsV[i].SetMinSize(minSizes[i]);
- }
- else
- {
- _definitionsU[i].SetMinSize(minSizes[i]);
- }
- }
- }
- }
-
- private void MeasureCellsGroup(
- int cellsHead,
- Size referenceSize,
- bool ignoreDesiredSizeU,
- bool forceInfinityV)
- {
- bool unusedHasDesiredSizeUChanged;
- MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU,
- forceInfinityV, out unusedHasDesiredSizeUChanged);
- }
-
- ///
- /// Measures one group of cells.
- ///
- /// Head index of the cells chain.
- /// Reference size for spanned cells
- /// calculations.
- /// When "true" cells' desired
- /// width is not registered in columns.
- /// Passed through to MeasureCell.
- /// When "true" cells' desired height is not registered in rows.
- private void MeasureCellsGroup(
- int cellsHead,
- Size referenceSize,
- bool ignoreDesiredSizeU,
- bool forceInfinityV,
- out bool hasDesiredSizeUChanged)
- {
- hasDesiredSizeUChanged = false;
-
- if (cellsHead >= _cellCache.Length)
- {
- return;
- }
-
- Hashtable spanStore = null;
- bool ignoreDesiredSizeV = forceInfinityV;
-
- int i = cellsHead;
- do
- {
- double oldWidth = Children[i].DesiredSize.Width;
-
- MeasureCell(i, forceInfinityV);
-
- hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width);
-
- if (!ignoreDesiredSizeU)
- {
- if (_cellCache[i].ColumnSpan == 1)
- {
- _definitionsU[_cellCache[i].ColumnIndex]
- .UpdateMinSize(Math.Min(Children[i].DesiredSize.Width,
- _definitionsU[_cellCache[i].ColumnIndex].UserMaxSize));
- }
- else
- {
- RegisterSpan(
- ref spanStore,
- _cellCache[i].ColumnIndex,
- _cellCache[i].ColumnSpan,
- true,
- Children[i].DesiredSize.Width);
- }
- }
-
- if (!ignoreDesiredSizeV)
- {
- if (_cellCache[i].RowSpan == 1)
- {
- _definitionsV[_cellCache[i].RowIndex]
- .UpdateMinSize(Math.Min(Children[i].DesiredSize.Height,
- _definitionsV[_cellCache[i].RowIndex].UserMaxSize));
- }
- else
- {
- RegisterSpan(
- ref spanStore,
- _cellCache[i].RowIndex,
- _cellCache[i].RowSpan,
- false,
- Children[i].DesiredSize.Height);
- }
- }
-
- i = _cellCache[i].Next;
- } while (i < _cellCache.Length);
-
- if (spanStore != null)
- {
- foreach (DictionaryEntry e in spanStore)
- {
- GridSpanKey key = (GridSpanKey)e.Key;
- double requestedSize = (double)e.Value;
-
- EnsureMinSizeInDefinitionRange(
- key.U ? _definitionsU : _definitionsV,
- key.Start,
- key.Count,
- requestedSize,
- key.U ? referenceSize.Width : referenceSize.Height);
- }
- }
- }
-
- ///
- /// Helper method to register a span information for delayed processing.
- ///
- /// Reference to a hashtable object used as storage.
- /// Span starting index.
- /// Span count.
- /// true if this is a column span. false if this is a row span.
- /// Value to store. If an entry already exists the biggest value is stored.
- private static void RegisterSpan(
- ref Hashtable store,
- int start,
- int count,
- bool u,
- double value)
- {
- if (store == null)
- {
- store = new Hashtable();
- }
-
- GridSpanKey key = new GridSpanKey(start, count, u);
- object o = store[key];
-
- if (o == null
- || value > (double)o)
- {
- store[key] = value;
- }
- }
-
- ///
- /// Takes care of measuring a single cell.
- ///
- /// Index of the cell to measure.
- /// If "true" then cell is always
- /// calculated to infinite height.
- private void MeasureCell(
- int cell,
- bool forceInfinityV)
- {
- double cellMeasureWidth;
- double cellMeasureHeight;
-
- if (_cellCache[cell].IsAutoU
- && !_cellCache[cell].IsStarU)
- {
- // if cell belongs to at least one Auto column and not a single Star column
- // then it should be calculated "to content", thus it is possible to "shortcut"
- // calculations and simply assign PositiveInfinity here.
- cellMeasureWidth = double.PositiveInfinity;
- }
- else
- {
- // otherwise...
- cellMeasureWidth = GetMeasureSizeForRange(
- _definitionsU,
- _cellCache[cell].ColumnIndex,
- _cellCache[cell].ColumnSpan);
- }
-
- if (forceInfinityV)
- {
- cellMeasureHeight = double.PositiveInfinity;
- }
- else if (_cellCache[cell].IsAutoV
- && !_cellCache[cell].IsStarV)
- {
- // if cell belongs to at least one Auto row and not a single Star row
- // then it should be calculated "to content", thus it is possible to "shortcut"
- // calculations and simply assign PositiveInfinity here.
- cellMeasureHeight = double.PositiveInfinity;
- }
- else
- {
- cellMeasureHeight = GetMeasureSizeForRange(
- _definitionsV,
- _cellCache[cell].RowIndex,
- _cellCache[cell].RowSpan);
- }
-
- var child = Children[cell];
-
- if (child != null)
- {
- Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
- child.Measure(childConstraint);
- }
- }
-
- ///
- /// Calculates one dimensional measure size for given definitions' range.
- ///
- /// Source array of definitions to read values from.
- /// Starting index of the range.
- /// Number of definitions included in the range.
- /// Calculated measure size.
- ///
- /// For "Auto" definitions MinWidth is used in place of PreferredSize.
- ///
- private double GetMeasureSizeForRange(
- DefinitionBase[] definitions,
- int start,
- int count)
- {
- Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
-
- double measureSize = 0;
- int i = start + count - 1;
-
- do
- {
- measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto)
- ? definitions[i].MinSize
- : definitions[i].MeasureSize;
- } while (--i >= start);
-
- return (measureSize);
- }
-
- ///
- /// Accumulates length type information for given definition's range.
- ///
- /// Source array of definitions to read values from.
- /// Starting index of the range.
- /// Number of definitions included in the range.
- /// Length type for given range.
- private LayoutTimeSizeType GetLengthTypeForRange(
- DefinitionBase[] definitions,
- int start,
- int count)
- {
- Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
-
- LayoutTimeSizeType lengthType = LayoutTimeSizeType.None;
- int i = start + count - 1;
-
- do
- {
- lengthType |= definitions[i].SizeType;
- } while (--i >= start);
-
- return (lengthType);
- }
-
- ///
- /// Distributes min size back to definition array's range.
- ///
- /// Start of the range.
- /// Number of items in the range.
- /// Minimum size that should "fit" into the definitions range.
- /// Definition array receiving distribution.
- /// Size used to resolve percentages.
- private void EnsureMinSizeInDefinitionRange(
- DefinitionBase[] definitions,
- int start,
- int count,
- double requestedSize,
- double percentReferenceSize)
- {
- Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length);
-
- // avoid processing when asked to distribute "0"
- if (!MathUtilities.IsZero(requestedSize))
- {
- DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
- int end = start + count;
- int autoDefinitionsCount = 0;
- double rangeMinSize = 0;
- double rangePreferredSize = 0;
- double rangeMaxSize = 0;
- double maxMaxSize = 0; // maximum of maximum sizes
-
- // first accumulate the necessary information:
- // a) sum up the sizes in the range;
- // b) count the number of auto definitions in the range;
- // c) initialize temp array
- // d) cache the maximum size into SizeCache
- // e) accumulate max of max sizes
- for (int i = start; i < end; ++i)
- {
- double minSize = definitions[i].MinSize;
- double preferredSize = definitions[i].PreferredSize;
- double maxSize = Math.Max(definitions[i].UserMaxSize, minSize);
-
- rangeMinSize += minSize;
- rangePreferredSize += preferredSize;
- rangeMaxSize += maxSize;
-
- definitions[i].SizeCache = maxSize;
-
- // sanity check: no matter what, but min size must always be the smaller;
- // max size must be the biggest; and preferred should be in between
- Debug.Assert(minSize <= preferredSize
- && preferredSize <= maxSize
- && rangeMinSize <= rangePreferredSize
- && rangePreferredSize <= rangeMaxSize);
-
- if (maxMaxSize < maxSize) maxMaxSize = maxSize;
- if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++;
- tempDefinitions[i - start] = definitions[i];
- }
-
- // avoid processing if the range already big enough
- if (requestedSize > rangeMinSize)
- {
- if (requestedSize <= rangePreferredSize)
- {
- //
- // requestedSize fits into preferred size of the range.
- // distribute according to the following logic:
- // * do not distribute into auto definitions - they should continue to stay "tight";
- // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size.
- //
- // in order to achieve that, definitions are sorted in a way that all auto definitions
- // are first, then definitions follow ascending order with PreferredSize as the key of sorting.
- //
- double sizeToDistribute;
- int i;
-
- Array.Sort(tempDefinitions, 0, count, _spanPreferredDistributionOrderComparer);
- for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i)
- {
- // sanity check: only auto definitions allowed in this loop
- Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
-
- // adjust sizeToDistribute value by subtracting auto definition min size
- sizeToDistribute -= (tempDefinitions[i].MinSize);
- }
-
- for (; i < count; ++i)
- {
- // sanity check: no auto definitions allowed in this loop
- Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
-
- double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize);
- if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); }
- sizeToDistribute -= newMinSize;
- }
-
- // sanity check: requested size must all be distributed
- Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
- }
- else if (requestedSize <= rangeMaxSize)
- {
- //
- // requestedSize bigger than preferred size, but fit into max size of the range.
- // distribute according to the following logic:
- // * do not distribute into auto definitions, if possible - they should continue to stay "tight";
- // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size.
- //
- // in order to achieve that, definitions are sorted in a way that all non-auto definitions
- // are last, then definitions follow ascending order with MaxSize as the key of sorting.
- //
- double sizeToDistribute;
- int i;
-
- Array.Sort(tempDefinitions, 0, count, _spanMaxDistributionOrderComparer);
- for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i)
- {
- // sanity check: no auto definitions allowed in this loop
- Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
-
- double preferredSize = tempDefinitions[i].PreferredSize;
- double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i);
- tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
- sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
- }
-
- for (; i < count; ++i)
- {
- // sanity check: only auto definitions allowed in this loop
- Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
-
- double preferredSize = tempDefinitions[i].MinSize;
- double newMinSize = preferredSize + sizeToDistribute / (count - i);
- tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
- sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
- }
-
- // sanity check: requested size must all be distributed
- Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
- }
- else
- {
- //
- // requestedSize bigger than max size of the range.
- // distribute according to the following logic:
- // * for all definitions distribute to equi-size min sizes.
- //
- double equalSize = requestedSize / count;
-
- if (equalSize < maxMaxSize
- && !MathUtilities.AreClose(equalSize, maxMaxSize))
- {
- // equi-size is less than maximum of maxSizes.
- // in this case distribute so that smaller definitions grow faster than
- // bigger ones.
- double totalRemainingSize = maxMaxSize * count - rangeMaxSize;
- double sizeToDistribute = requestedSize - rangeMaxSize;
-
- // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers
- Debug.Assert(!double.IsInfinity(totalRemainingSize)
- && !double.IsNaN(totalRemainingSize)
- && totalRemainingSize > 0
- && !double.IsInfinity(sizeToDistribute)
- && !double.IsNaN(sizeToDistribute)
- && sizeToDistribute > 0);
-
- for (int i = 0; i < count; ++i)
- {
- double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize;
- tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize);
- }
- }
- else
- {
- //
- // equi-size is greater or equal to maximum of max sizes.
- // all definitions receive equalSize as their mim sizes.
- //
- for (int i = 0; i < count; ++i)
- {
- tempDefinitions[i].UpdateMinSize(equalSize);
- }
- }
- }
- }
- }
- }
-
- // new implementation as of 4.7. Several improvements:
- // 1. Allocate to *-defs hitting their min or max constraints, before allocating
- // to other *-defs. A def that hits its min uses more space than its
- // proportional share, reducing the space available to everyone else.
- // The legacy algorithm deducted this space only from defs processed
- // after the min; the new algorithm deducts it proportionally from all
- // defs. This avoids the "*-defs exceed available space" problem,
- // and other related problems where *-defs don't receive proportional
- // allocations even though no constraints are preventing it.
- // 2. When multiple defs hit min or max, resolve the one with maximum
- // discrepancy (defined below). This avoids discontinuities - small
- // change in available space resulting in large change to one def's allocation.
- // 3. Correct handling of large *-values, including Infinity.
-
- ///
- /// Resolves Star's for given array of definitions.
- ///
- /// Array of definitions to resolve stars.
- /// All available size.
- ///
- /// Must initialize LayoutSize for all Star entries in given array of definitions.
- ///
- private void ResolveStar(
- DefinitionBase[] definitions,
- double availableSize)
- {
- int defCount = definitions.Length;
- DefinitionBase[] tempDefinitions = TempDefinitions;
- int minCount = 0, maxCount = 0;
- double takenSize = 0;
- double totalStarWeight = 0.0;
- int starCount = 0; // number of unresolved *-definitions
- double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
-
- // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
- double maxStar = 0.0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.SizeType == LayoutTimeSizeType.Star)
- {
- ++starCount;
- def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3"
- if (def.UserSize.Value > maxStar)
- {
- maxStar = def.UserSize.Value;
- }
- }
- }
-
- if (double.IsPositiveInfinity(maxStar))
- {
- // negative scale means one or more of the weights was Infinity
- scale = -1.0;
- }
- else if (starCount > 0)
- {
- // if maxStar * starCount > double.Max, summing all the weights could cause
- // floating-point overflow. To avoid that, scale the weights by a factor to keep
- // the sum within limits. Choose a power of 2, to preserve precision.
- double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0));
- if (power < 0.0)
- {
- scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
- }
- }
-
- // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
- // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
- // More explanation below...
- for (bool runPhase2and3 = true; runPhase2and3;)
- {
- // Phase 2. Compute total *-weight W and available space S.
- // For *-items that have Min or Max constraints, compute the ratios used to decide
- // whether proportional space is too big or too small and add the item to the
- // corresponding list. (The "min" list is in the first half of tempDefinitions,
- // the "max" list in the second half. TempDefinitions has capacity at least
- // 2*defCount, so there's room for both lists.)
- totalStarWeight = 0.0;
- takenSize = 0.0;
- minCount = maxCount = 0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- switch (def.SizeType)
- {
- case (LayoutTimeSizeType.Auto):
- takenSize += definitions[i].MinSize;
- break;
- case (LayoutTimeSizeType.Pixel):
- takenSize += def.MeasureSize;
- break;
- case (LayoutTimeSizeType.Star):
- if (def.MeasureSize < 0.0)
- {
- takenSize += -def.MeasureSize; // already resolved
- }
- else
- {
- double starWeight = StarWeight(def, scale);
- totalStarWeight += starWeight;
-
- if (def.MinSize > 0.0)
- {
- // store ratio w/min in MeasureSize (for now)
- tempDefinitions[minCount++] = def;
- def.MeasureSize = starWeight / def.MinSize;
- }
-
- double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize);
- if (!double.IsPositiveInfinity(effectiveMaxSize))
- {
- // store ratio w/max in SizeCache (for now)
- tempDefinitions[defCount + maxCount++] = def;
- def.SizeCache = starWeight / effectiveMaxSize;
- }
- }
- break;
- }
- }
-
- // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
- int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
- double takenStarWeight = 0.0;
- double remainingAvailableSize = availableSize - takenSize;
- double remainingStarWeight = totalStarWeight - takenStarWeight;
- Array.Sort(tempDefinitions, 0, minCount, _minRatioComparer);
- Array.Sort(tempDefinitions, defCount, maxCount, _maxRatioComparer);
-
- while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
- {
- // the calculation
- // remainingStarWeight = totalStarWeight - takenStarWeight
- // is subject to catastrophic cancellation if the two terms are nearly equal,
- // which leads to meaningless results. Check for that, and recompute from
- // the remaining definitions. [This leads to quadratic behavior in really
- // pathological cases - but they'd never arise in practice.]
- const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
- if (remainingStarWeight < totalStarWeight * starFactor)
- {
- takenStarWeight = 0.0;
- totalStarWeight = 0.0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
- if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0)
- {
- totalStarWeight += StarWeight(def, scale);
- }
- }
-
- remainingStarWeight = totalStarWeight - takenStarWeight;
- }
-
- double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : double.PositiveInfinity;
- double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0;
-
- // choose the def with larger ratio to the current proportion ("max discrepancy")
- double proportion = remainingStarWeight / remainingAvailableSize;
- bool? chooseMin = Choose(minRatio, maxRatio, proportion);
-
- // if no def was chosen, advance to phase 4; the current proportion doesn't
- // conflict with any min or max values.
- if (!(chooseMin.HasValue))
- {
- break;
- }
-
- // get the chosen definition and its resolved size
- DefinitionBase resolvedDef;
- double resolvedSize;
- if (chooseMin == true)
- {
- resolvedDef = tempDefinitions[minCount - 1];
- resolvedSize = resolvedDef.MinSize;
- --minCount;
- }
- else
- {
- resolvedDef = tempDefinitions[defCount + maxCount - 1];
- resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
- --maxCount;
- }
-
- // resolve the chosen def, deduct its contributions from W and S.
- // Defs resolved in phase 3 are marked by storing the negative of their resolved
- // size in MeasureSize, to distinguish them from a pending def.
- takenSize += resolvedSize;
- resolvedDef.MeasureSize = -resolvedSize;
- takenStarWeight += StarWeight(resolvedDef, scale);
- --starCount;
-
- remainingAvailableSize = availableSize - takenSize;
- remainingStarWeight = totalStarWeight - takenStarWeight;
-
- // advance to the next candidate defs, removing ones that have been resolved.
- // Both counts are advanced, as a def might appear in both lists.
- while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0)
- {
- --minCount;
- tempDefinitions[minCount] = null;
- }
- while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0)
- {
- --maxCount;
- tempDefinitions[defCount + maxCount] = null;
- }
- }
-
- // decide whether to run Phase2 and Phase3 again. There are 3 cases:
- // 1. There is space available, and *-defs remaining. This is the
- // normal case - move on to Phase 4 to allocate the remaining
- // space proportionally to the remaining *-defs.
- // 2. There is space available, but no *-defs. This implies at least one
- // def was resolved as 'max', taking less space than its proportion.
- // If there are also 'min' defs, reconsider them - we can give
- // them more space. If not, all the *-defs are 'max', so there's
- // no way to use all the available space.
- // 3. We allocated too much space. This implies at least one def was
- // resolved as 'min'. If there are also 'max' defs, reconsider
- // them, otherwise the over-allocation is an inevitable consequence
- // of the given min constraints.
- // Note that if we return to Phase2, at least one *-def will have been
- // resolved. This guarantees we don't run Phase2+3 infinitely often.
- runPhase2and3 = false;
- if (starCount == 0 && takenSize < availableSize)
- {
- // if no *-defs remain and we haven't allocated all the space, reconsider the defs
- // resolved as 'min'. Their allocation can be increased to make up the gap.
- for (int i = minCount; i < minCountPhase2; ++i)
- {
- DefinitionBase def = tempDefinitions[i];
- if (def != null)
- {
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
-
- if (takenSize > availableSize)
- {
- // if we've allocated too much space, reconsider the defs
- // resolved as 'max'. Their allocation can be decreased to make up the gap.
- for (int i = maxCount; i < maxCountPhase2; ++i)
- {
- DefinitionBase def = tempDefinitions[defCount + i];
- if (def != null)
- {
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
- }
-
- // Phase 4. Resolve the remaining defs proportionally.
- starCount = 0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.SizeType == LayoutTimeSizeType.Star)
- {
- if (def.MeasureSize < 0.0)
- {
- // this def was resolved in phase 3 - fix up its measure size
- def.MeasureSize = -def.MeasureSize;
- }
- else
- {
- // this def needs resolution, add it to the list, sorted by *-weight
- tempDefinitions[starCount++] = def;
- def.MeasureSize = StarWeight(def, scale);
- }
- }
- }
-
- if (starCount > 0)
- {
- Array.Sort(tempDefinitions, 0, starCount, _starWeightComparer);
-
- // compute the partial sums of *-weight, in increasing order of weight
- // for minimal loss of precision.
- totalStarWeight = 0.0;
- for (int i = 0; i < starCount; ++i)
- {
- DefinitionBase def = tempDefinitions[i];
- totalStarWeight += def.MeasureSize;
- def.SizeCache = totalStarWeight;
- }
-
- // resolve the defs, in decreasing order of weight
- for (int i = starCount - 1; i >= 0; --i)
- {
- DefinitionBase def = tempDefinitions[i];
- double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
-
- // 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);
-
- def.MeasureSize = resolvedSize;
- takenSize += resolvedSize;
- }
- }
- }
-
- ///
- /// Calculates desired size for given array of definitions.
- ///
- /// Array of definitions to use for calculations.
- /// Desired size.
- private double CalculateDesiredSize(
- DefinitionBase[] definitions)
- {
- double desiredSize = 0;
-
- for (int i = 0; i < definitions.Length; ++i)
- {
- desiredSize += definitions[i].MinSize;
- }
-
- return (desiredSize);
- }
-
- ///
- /// Calculates and sets final size for all definitions in the given array.
- ///
- /// Array of definitions to process.
- /// Final size to lay out to.
- /// True if sizing row definitions, false for columns
- private void SetFinalSize(
- DefinitionBase[] definitions,
- double finalSize,
- bool columns)
- {
- int defCount = definitions.Length;
- int[] definitionIndices = DefinitionIndices;
- int minCount = 0, maxCount = 0;
- double takenSize = 0.0;
- double totalStarWeight = 0.0;
- int starCount = 0; // number of unresolved *-definitions
- double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
-
- // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
- double maxStar = 0.0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.UserSize.IsStar)
- {
- ++starCount;
- def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3"
- if (def.UserSize.Value > maxStar)
- {
- maxStar = def.UserSize.Value;
- }
- }
- }
-
- if (double.IsPositiveInfinity(maxStar))
- {
- // negative scale means one or more of the weights was Infinity
- scale = -1.0;
- }
- else if (starCount > 0)
- {
- // if maxStar * starCount > double.Max, summing all the weights could cause
- // floating-point overflow. To avoid that, scale the weights by a factor to keep
- // the sum within limits. Choose a power of 2, to preserve precision.
- double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0));
- if (power < 0.0)
- {
- scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
- }
- }
-
-
- // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
- // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
- // More explanation below...
- for (bool runPhase2and3 = true; runPhase2and3;)
- {
- // Phase 2. Compute total *-weight W and available space S.
- // For *-items that have Min or Max constraints, compute the ratios used to decide
- // whether proportional space is too big or too small and add the item to the
- // corresponding list. (The "min" list is in the first half of definitionIndices,
- // the "max" list in the second half. DefinitionIndices has capacity at least
- // 2*defCount, so there's room for both lists.)
- totalStarWeight = 0.0;
- takenSize = 0.0;
- minCount = maxCount = 0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.UserSize.IsStar)
- {
- Debug.Assert(!def.IsShared, "*-defs cannot be shared");
-
- if (def.MeasureSize < 0.0)
- {
- takenSize += -def.MeasureSize; // already resolved
- }
- else
- {
- double starWeight = StarWeight(def, scale);
- totalStarWeight += starWeight;
-
- if (def.MinSizeForArrange > 0.0)
- {
- // store ratio w/min in MeasureSize (for now)
- definitionIndices[minCount++] = i;
- def.MeasureSize = starWeight / def.MinSizeForArrange;
- }
-
- double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize);
- if (!double.IsPositiveInfinity(effectiveMaxSize))
- {
- // store ratio w/max in SizeCache (for now)
- definitionIndices[defCount + maxCount++] = i;
- def.SizeCache = starWeight / effectiveMaxSize;
- }
- }
- }
- else
- {
- double userSize = 0;
-
- switch (def.UserSize.GridUnitType)
- {
- case (GridUnitType.Pixel):
- userSize = def.UserSize.Value;
- break;
-
- case (GridUnitType.Auto):
- 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.MinSizeForArrange, Math.Min(userSize, userMaxSize));
- takenSize += def.SizeCache;
- }
- }
-
- // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
- int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
- double takenStarWeight = 0.0;
- double remainingAvailableSize = finalSize - takenSize;
- double remainingStarWeight = totalStarWeight - takenStarWeight;
-
- MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions);
- Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer);
- MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions);
- Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer);
-
- while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
- {
- // the calculation
- // remainingStarWeight = totalStarWeight - takenStarWeight
- // is subject to catastrophic cancellation if the two terms are nearly equal,
- // which leads to meaningless results. Check for that, and recompute from
- // the remaining definitions. [This leads to quadratic behavior in really
- // pathological cases - but they'd never arise in practice.]
- const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
- if (remainingStarWeight < totalStarWeight * starFactor)
- {
- takenStarWeight = 0.0;
- totalStarWeight = 0.0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
- if (def.UserSize.IsStar && def.MeasureSize > 0.0)
- {
- totalStarWeight += StarWeight(def, scale);
- }
- }
-
- remainingStarWeight = totalStarWeight - takenStarWeight;
- }
-
- double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : double.PositiveInfinity;
- double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0;
-
- // choose the def with larger ratio to the current proportion ("max discrepancy")
- double proportion = remainingStarWeight / remainingAvailableSize;
- bool? chooseMin = Choose(minRatio, maxRatio, proportion);
-
- // if no def was chosen, advance to phase 4; the current proportion doesn't
- // conflict with any min or max values.
- if (!(chooseMin.HasValue))
- {
- break;
- }
-
- // get the chosen definition and its resolved size
- int resolvedIndex;
- DefinitionBase resolvedDef;
- double resolvedSize;
- if (chooseMin == true)
- {
- resolvedIndex = definitionIndices[minCount - 1];
- resolvedDef = definitions[resolvedIndex];
- resolvedSize = resolvedDef.MinSizeForArrange;
- --minCount;
- }
- else
- {
- resolvedIndex = definitionIndices[defCount + maxCount - 1];
- resolvedDef = definitions[resolvedIndex];
- resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize);
- --maxCount;
- }
-
- // resolve the chosen def, deduct its contributions from W and S.
- // Defs resolved in phase 3 are marked by storing the negative of their resolved
- // size in MeasureSize, to distinguish them from a pending def.
- takenSize += resolvedSize;
- resolvedDef.MeasureSize = -resolvedSize;
- takenStarWeight += StarWeight(resolvedDef, scale);
- --starCount;
-
- remainingAvailableSize = finalSize - takenSize;
- remainingStarWeight = totalStarWeight - takenStarWeight;
-
- // advance to the next candidate defs, removing ones that have been resolved.
- // Both counts are advanced, as a def might appear in both lists.
- while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0)
- {
- --minCount;
- definitionIndices[minCount] = -1;
- }
- while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0)
- {
- --maxCount;
- definitionIndices[defCount + maxCount] = -1;
- }
- }
-
- // decide whether to run Phase2 and Phase3 again. There are 3 cases:
- // 1. There is space available, and *-defs remaining. This is the
- // normal case - move on to Phase 4 to allocate the remaining
- // space proportionally to the remaining *-defs.
- // 2. There is space available, but no *-defs. This implies at least one
- // def was resolved as 'max', taking less space than its proportion.
- // If there are also 'min' defs, reconsider them - we can give
- // them more space. If not, all the *-defs are 'max', so there's
- // no way to use all the available space.
- // 3. We allocated too much space. This implies at least one def was
- // resolved as 'min'. If there are also 'max' defs, reconsider
- // them, otherwise the over-allocation is an inevitable consequence
- // of the given min constraints.
- // Note that if we return to Phase2, at least one *-def will have been
- // resolved. This guarantees we don't run Phase2+3 infinitely often.
- runPhase2and3 = false;
- if (starCount == 0 && takenSize < finalSize)
- {
- // if no *-defs remain and we haven't allocated all the space, reconsider the defs
- // resolved as 'min'. Their allocation can be increased to make up the gap.
- for (int i = minCount; i < minCountPhase2; ++i)
- {
- if (definitionIndices[i] >= 0)
- {
- DefinitionBase def = definitions[definitionIndices[i]];
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
-
- if (takenSize > finalSize)
- {
- // if we've allocated too much space, reconsider the defs
- // resolved as 'max'. Their allocation can be decreased to make up the gap.
- for (int i = maxCount; i < maxCountPhase2; ++i)
- {
- if (definitionIndices[defCount + i] >= 0)
- {
- DefinitionBase def = definitions[definitionIndices[defCount + i]];
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
- }
-
- // Phase 4. Resolve the remaining defs proportionally.
- starCount = 0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.UserSize.IsStar)
- {
- if (def.MeasureSize < 0.0)
- {
- // this def was resolved in phase 3 - fix up its size
- def.SizeCache = -def.MeasureSize;
- }
- else
- {
- // this def needs resolution, add it to the list, sorted by *-weight
- definitionIndices[starCount++] = i;
- def.MeasureSize = StarWeight(def, scale);
- }
- }
- }
-
- if (starCount > 0)
- {
- StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions);
- Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer);
-
- // compute the partial sums of *-weight, in increasing order of weight
- // for minimal loss of precision.
- totalStarWeight = 0.0;
- for (int i = 0; i < starCount; ++i)
- {
- DefinitionBase def = definitions[definitionIndices[i]];
- totalStarWeight += def.MeasureSize;
- def.SizeCache = totalStarWeight;
- }
-
- // resolve the defs, in decreasing order of weight.
- for (int i = starCount - 1; i >= 0; --i)
- {
- DefinitionBase def = definitions[definitionIndices[i]];
- double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
-
- // min and max should have no effect by now, but just in case...
- resolvedSize = Math.Min(resolvedSize, def.UserMaxSize);
- 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;
- // this avoids errors arising from min/max constraints.
- takenSize += resolvedSize;
- def.SizeCache = resolvedSize;
- }
- }
-
- // Phase 5. Apply layout rounding. We do this after fully allocating
- // unrounded sizes, to avoid breaking assumptions in the previous phases
- if (UseLayoutRounding)
- {
- var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
-
- double[] roundingErrors = RoundingErrors;
- double roundedTakenSize = 0.0;
-
- // round each of the allocated sizes, keeping track of the deltas
- for (int i = 0; i < definitions.Length; ++i)
- {
- DefinitionBase def = definitions[i];
- double roundedSize = RoundLayoutValue(def.SizeCache, dpi);
- roundingErrors[i] = (roundedSize - def.SizeCache);
- def.SizeCache = roundedSize;
- roundedTakenSize += roundedSize;
- }
-
- // The total allocation might differ from finalSize due to rounding
- // effects. Tweak the allocations accordingly.
-
- // Theoretical and historical note. The problem at hand - allocating
- // space to columns (or rows) with *-weights, min and max constraints,
- // and layout rounding - has a long history. Especially the special
- // case of 50 columns with min=1 and available space=435 - allocating
- // seats in the U.S. House of Representatives to the 50 states in
- // proportion to their population. There are numerous algorithms
- // and papers dating back to the 1700's, including the book:
- // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982.
- //
- // One surprising result of all this research is that *any* algorithm
- // will suffer from one or more undesirable features such as the
- // "population paradox" or the "Alabama paradox", where (to use our terminology)
- // increasing the available space by one pixel might actually decrease
- // the space allocated to a given column, or increasing the weight of
- // a column might decrease its allocation. This is worth knowing
- // in case someone complains about this behavior; it's not a bug so
- // much as something inherent to the problem. Cite the book mentioned
- // above or one of the 100s of references, and resolve as WontFix.
- //
- // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer)
- // each being allocated a large number of pixels (~50 or greater), and
- // people don't even notice the kind of 1-pixel anomolies that are
- // theoretically inevitable, or don't care if they do. At least they shouldn't
- // care - no one should be using the results WPF's grid layout to make
- // quantitative decisions; its job is to produce a reasonable display, not
- // to allocate seats in Congress.
- //
- // Our algorithm is more susceptible to paradox than the one currently
- // used for Congressional allocation ("Huntington-Hill" algorithm), but
- // it is faster to run: O(N log N) vs. O(S * N), where N=number of
- // definitions, S = number of available pixels. And it produces
- // adequate results in practice, as mentioned above.
- //
- // To reiterate one point: all this only applies when layout rounding
- // is in effect. When fractional sizes are allowed, the algorithm
- // behaves as well as possible, subject to the min/max constraints
- // and precision of floating-point computation. (However, the resulting
- // display is subject to anti-aliasing problems. TANSTAAFL.)
-
- if (!MathUtilities.AreClose(roundedTakenSize, finalSize))
- {
- // Compute deltas
- for (int i = 0; i < definitions.Length; ++i)
- {
- definitionIndices[i] = i;
- }
-
- // Sort rounding errors
- RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors);
- Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer);
- double adjustedSize = roundedTakenSize;
- double dpiIncrement = 1.0 / dpi;
-
- if (roundedTakenSize > finalSize)
- {
- int i = definitions.Length - 1;
- while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0)
- {
- DefinitionBase definition = definitions[definitionIndices[i]];
- double final = definition.SizeCache - dpiIncrement;
- final = Math.Max(final, definition.MinSizeForArrange);
- if (final < definition.SizeCache)
- {
- adjustedSize -= dpiIncrement;
- }
- definition.SizeCache = final;
- i--;
- }
- }
- else if (roundedTakenSize < finalSize)
- {
- int i = 0;
- while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Length)
- {
- DefinitionBase definition = definitions[definitionIndices[i]];
- double final = definition.SizeCache + dpiIncrement;
- final = Math.Max(final, definition.MinSizeForArrange);
- if (final > definition.SizeCache)
- {
- adjustedSize += dpiIncrement;
- }
- definition.SizeCache = final;
- i++;
- }
- }
- }
- }
-
- // Phase 6. Compute final offsets
- definitions[0].FinalOffset = 0.0;
- for (int i = 0; i < definitions.Length; ++i)
- {
- definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache;
- }
- }
-
- // Choose the ratio with maximum discrepancy from the current proportion.
- // Returns:
- // true if proportion fails a min constraint but not a max, or
- // if the min constraint has higher discrepancy
- // false if proportion fails a max constraint but not a min, or
- // if the max constraint has higher discrepancy
- // null if proportion doesn't fail a min or max constraint
- // The discrepancy is the ratio of the proportion to the max- or min-ratio.
- // When both ratios hit the constraint, minRatio < proportion < maxRatio,
- // and the minRatio has higher discrepancy if
- // (proportion / minRatio) > (maxRatio / proportion)
- private static bool? Choose(double minRatio, double maxRatio, double proportion)
- {
- if (minRatio < proportion)
- {
- if (maxRatio > proportion)
- {
- // compare proportion/minRatio : maxRatio/proportion, but
- // do it carefully to avoid floating-point overflow or underflow
- // and divide-by-0.
- double minPower = Math.Floor(Math.Log(minRatio, 2.0));
- double maxPower = Math.Floor(Math.Log(maxRatio, 2.0));
- double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0));
- if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f))
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- else
- {
- return true;
- }
- }
- else if (maxRatio > proportion)
- {
- return false;
- }
-
- return null;
- }
-
- ///
- /// Sorts row/column indices by rounding error if layout rounding is applied.
- ///
- /// Index, rounding error pair
- /// Index, rounding error pair
- /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise
- private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y)
- {
- if (x.Value < y.Value)
- {
- return -1;
- }
- else if (x.Value > y.Value)
- {
- return 1;
- }
- return 0;
- }
-
- ///
- /// Calculates final (aka arrange) size for given range.
- ///
- /// Array of definitions to process.
- /// Start of the range.
- /// Number of items in the range.
- /// Final size.
- private double GetFinalSizeForRange(
- DefinitionBase[] definitions,
- int start,
- int count)
- {
- double size = 0;
- int i = start + count - 1;
-
- do
- {
- size += definitions[i].SizeCache;
- } while (--i >= start);
-
- return (size);
- }
-
- ///
- /// Clears dirty state for the grid and its columns / rows
- ///
- private void SetValid()
- {
- if (IsTrivialGrid && _tempDefinitions != null)
- {
- // TempDefinitions has to be cleared to avoid "memory leaks"
- Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length));
- _tempDefinitions = null;
- }
- }
-
- ///
- /// Synchronized ShowGridLines property with the state of the grid's visual collection
- /// by adding / removing GridLinesRenderer visual.
- /// Returns a reference to GridLinesRenderer visual or null.
- ///
- private GridLinesRenderer EnsureGridLinesRenderer()
- {
- //
- // synchronize the state
- //
- if (ShowGridLines && (_gridLinesRenderer == null))
- {
- _gridLinesRenderer = new GridLinesRenderer();
- this.VisualChildren.Add(_gridLinesRenderer);
- }
-
- if ((!ShowGridLines) && (_gridLinesRenderer != null))
- {
- this.VisualChildren.Remove(_gridLinesRenderer);
- _gridLinesRenderer = null;
- }
-
- return (_gridLinesRenderer);
- }
-
- private double RoundLayoutValue(double value, double dpiScale)
- {
- double newValue;
-
- // If DPI == 1, don't use DPI-aware rounding.
- if (!MathUtilities.AreClose(dpiScale, 1.0))
- {
- newValue = Math.Round(value * dpiScale) / dpiScale;
- // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
- if (double.IsNaN(newValue) ||
- double.IsInfinity(newValue) ||
- MathUtilities.AreClose(newValue, double.MaxValue))
- {
- newValue = value;
- }
- }
- else
- {
- newValue = Math.Round(value);
- }
-
- return newValue;
- }
-
- private static int ValidateColumn(AvaloniaObject o, int value)
- {
- if (value < 0)
- {
- throw new ArgumentException("Invalid Grid.Column value.");
- }
-
- return value;
- }
-
- private static int ValidateRow(AvaloniaObject o, int value)
- {
- if (value < 0)
- {
- throw new ArgumentException("Invalid Grid.Row value.");
- }
-
- return value;
- }
-
- private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e)
- {
- if (!grid.IsTrivialGrid) // trivial grid is 1 by 1. there is no grid lines anyway
- {
- grid.Invalidate();
- }
- }
-
- ///
- /// Returns *-weight, adjusted for scale computed during Phase 1
- ///
- private static double StarWeight(DefinitionBase def, double scale)
- {
- if (scale < 0.0)
- {
- // if one of the *-weights is Infinity, adjust the weights by mapping
- // Infinty to 1.0 and everything else to 0.0: the infinite items share the
- // available space equally, everyone else gets nothing.
- return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0;
- }
- else
- {
- return def.UserSize.Value * scale;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/GridCellCache.cs b/src/Avalonia.Controls/Grid/GridCellCache.cs
deleted file mode 100644
index 81edf72ca5..0000000000
--- a/src/Avalonia.Controls/Grid/GridCellCache.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Avalonia.Controls
-{
- ///
- /// CellCache stored calculated values of
- /// 1. attached cell positioning properties;
- /// 2. size type;
- /// 3. index of a next cell in the group;
- ///
- internal struct GridCellCache
- {
- internal int ColumnIndex;
- internal int RowIndex;
- internal int ColumnSpan;
- internal int RowSpan;
- internal LayoutTimeSizeType SizeTypeU;
- internal LayoutTimeSizeType SizeTypeV;
- internal int Next;
- internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } }
- internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } }
- internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } }
- internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/GridLength.cs b/src/Avalonia.Controls/Grid/GridLength.cs
deleted file mode 100644
index 02be95b647..0000000000
--- a/src/Avalonia.Controls/Grid/GridLength.cs
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using Avalonia.Utilities;
-
-namespace Avalonia.Controls
-{
- ///
- /// Defines the valid units for a .
- ///
- public enum GridUnitType
- {
- ///
- /// The row or column is auto-sized to fit its content.
- ///
- Auto = 0,
-
- ///
- /// The row or column is sized in device independent pixels.
- ///
- Pixel = 1,
-
- ///
- /// The row or column is sized as a weighted proportion of available space.
- ///
- Star = 2,
- }
-
- ///
- /// Holds the width or height of a 's column and row definitions.
- ///
- public struct GridLength : IEquatable
- {
- private readonly GridUnitType _type;
-
- private readonly double _value;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The size of the GridLength in device independent pixels.
- public GridLength(double value)
- : this(value, GridUnitType.Pixel)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The size of the GridLength.
- /// The unit of the GridLength.
- public GridLength(double value, GridUnitType type)
- {
- if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
- {
- throw new ArgumentException("Invalid value", nameof(value));
- }
-
- if (type < GridUnitType.Auto || type > GridUnitType.Star)
- {
- throw new ArgumentException("Invalid value", nameof(type));
- }
-
- _type = type;
- _value = value;
- }
-
- ///
- /// Gets an instance of that indicates that a row or column should
- /// auto-size to fit its content.
- ///
- public static GridLength Auto => new GridLength(0, GridUnitType.Auto);
-
- ///
- /// Gets the unit of the .
- ///
- public GridUnitType GridUnitType => _type;
-
- ///
- /// Gets a value that indicates whether the has a of Pixel.
- ///
- public bool IsAbsolute => _type == GridUnitType.Pixel;
-
- ///
- /// Gets a value that indicates whether the has a of Auto.
- ///
- public bool IsAuto => _type == GridUnitType.Auto;
-
- ///
- /// Gets a value that indicates whether the has a of Star.
- ///
- public bool IsStar => _type == GridUnitType.Star;
-
- ///
- /// Gets the length.
- ///
- public double Value => _value;
-
- ///
- /// Compares two GridLength structures for equality.
- ///
- /// The first GridLength.
- /// The second GridLength.
- /// True if the structures are equal, otherwise false.
- public static bool operator ==(GridLength a, GridLength b)
- {
- return (a.IsAuto && b.IsAuto) || (a._value == b._value && a._type == b._type);
- }
-
- ///
- /// Compares two GridLength structures for inequality.
- ///
- /// The first GridLength.
- /// The first GridLength.
- /// True if the structures are unequal, otherwise false.
- public static bool operator !=(GridLength gl1, GridLength gl2)
- {
- return !(gl1 == gl2);
- }
-
- ///
- /// Determines whether the is equal to the specified object.
- ///
- /// The object with which to test equality.
- /// True if the objects are equal, otherwise false.
- public override bool Equals(object o)
- {
- if (o == null)
- {
- return false;
- }
-
- if (!(o is GridLength))
- {
- return false;
- }
-
- return this == (GridLength)o;
- }
-
- ///
- /// Compares two GridLength structures for equality.
- ///
- /// The structure with which to test equality.
- /// True if the structures are equal, otherwise false.
- public bool Equals(GridLength gridLength)
- {
- return this == gridLength;
- }
-
- ///
- /// Gets a hash code for the GridLength.
- ///
- /// The hash code.
- public override int GetHashCode()
- {
- return _value.GetHashCode() ^ _type.GetHashCode();
- }
-
- ///
- /// Gets a string representation of the .
- ///
- /// The string representation.
- public override string ToString()
- {
- if (IsAuto)
- {
- return "Auto";
- }
-
- string s = _value.ToString();
- return IsStar ? s + "*" : s;
- }
-
- ///
- /// Parses a string to return a .
- ///
- /// The string.
- /// The .
- public static GridLength Parse(string s)
- {
- s = s.ToUpperInvariant();
-
- if (s == "AUTO")
- {
- return Auto;
- }
- else if (s.EndsWith("*"))
- {
- var valueString = s.Substring(0, s.Length - 1).Trim();
- var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1;
- return new GridLength(value, GridUnitType.Star);
- }
- else
- {
- var value = double.Parse(s, CultureInfo.InvariantCulture);
- return new GridLength(value, GridUnitType.Pixel);
- }
- }
-
- ///
- /// Parses a string to return a collection of s.
- ///
- /// The string.
- /// The .
- public static IEnumerable ParseLengths(string s)
- {
- using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture))
- {
- while (tokenizer.TryReadString(out var item))
- {
- yield return Parse(item);
- }
- }
- }
- }
-}
diff --git a/src/Avalonia.Controls/Grid/GridLinesRenderer.cs b/src/Avalonia.Controls/Grid/GridLinesRenderer.cs
deleted file mode 100644
index 0f7f5963d2..0000000000
--- a/src/Avalonia.Controls/Grid/GridLinesRenderer.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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.Generic;
-using Avalonia.VisualTree;
-using Avalonia.Media;
-
-namespace Avalonia.Controls
-{
- internal class GridLinesRenderer : Control
- {
- ///
- /// Static initialization
- ///
- static GridLinesRenderer()
- {
- var dashArray = new List() { _dashLength, _dashLength };
-
- var ds1 = new DashStyle(dashArray, 0);
- _oddDashPen = new Pen(Brushes.Blue,
- _penWidth,
- lineCap: PenLineCap.Flat,
- dashStyle: ds1);
-
- var ds2 = new DashStyle(dashArray, _dashLength);
- _evenDashPen = new Pen(Brushes.Yellow,
- _penWidth,
- lineCap: PenLineCap.Flat,
- dashStyle: ds2);
- }
-
- ///
- /// UpdateRenderBounds.
- ///
- public override void Render(DrawingContext drawingContext)
- {
- var grid = this.GetVisualParent();
-
- if (grid == null
- || !grid.ShowGridLines
- || grid.IsTrivialGrid)
- {
- return;
- }
-
- for (int i = 1; i < grid.ColumnDefinitions.Count; ++i)
- {
- DrawGridLine(
- drawingContext,
- grid.ColumnDefinitions[i].FinalOffset, 0.0,
- grid.ColumnDefinitions[i].FinalOffset, _lastArrangeSize.Height);
- }
-
- for (int i = 1; i < grid.RowDefinitions.Count; ++i)
- {
- DrawGridLine(
- drawingContext,
- 0.0, grid.RowDefinitions[i].FinalOffset,
- _lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset);
- }
- }
-
- ///
- /// Draw single hi-contrast line.
- ///
- private static void DrawGridLine(
- DrawingContext drawingContext,
- double startX,
- double startY,
- double endX,
- double endY)
- {
- var start = new Point(startX, startY);
- var end = new Point(endX, endY);
- drawingContext.DrawLine(_oddDashPen, start, end);
- drawingContext.DrawLine(_evenDashPen, start, end);
- }
-
- internal void UpdateRenderBounds(Size arrangeSize)
- {
- _lastArrangeSize = arrangeSize;
- this.InvalidateVisual();
- }
-
- private static Size _lastArrangeSize;
- private const double _dashLength = 4.0; //
- private const double _penWidth = 1.0; //
- private static readonly Pen _oddDashPen; // first pen to draw dash
- private static readonly Pen _evenDashPen; // second pen to draw dash
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/GridSpanKey.cs b/src/Avalonia.Controls/Grid/GridSpanKey.cs
deleted file mode 100644
index cd48bc1265..0000000000
--- a/src/Avalonia.Controls/Grid/GridSpanKey.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Avalonia.Controls
-{
- ///
- /// Helper class for representing a key for a span in hashtable.
- ///
- internal class GridSpanKey
- {
- ///
- /// Constructor.
- ///
- /// Starting index of the span.
- /// Span count.
- /// true for columns; false for rows.
- internal GridSpanKey(int start, int count, bool u)
- {
- _start = start;
- _count = count;
- _u = u;
- }
-
- ///
- ///
- ///
- public override int GetHashCode()
- {
- int hash = (_start ^ (_count << 2));
-
- if (_u) hash &= 0x7ffffff;
- else hash |= 0x8000000;
-
- return (hash);
- }
-
- ///
- ///
- ///
- public override bool Equals(object obj)
- {
- GridSpanKey sk = obj as GridSpanKey;
- return (sk != null
- && sk._start == _start
- && sk._count == _count
- && sk._u == _u);
- }
-
- ///
- /// Returns start index of the span.
- ///
- internal int Start { get { return (_start); } }
-
- ///
- /// Returns span count.
- ///
- internal int Count { get { return (_count); } }
-
- ///
- /// Returns true if this is a column span.
- /// false if this is a row span.
- ///
- internal bool U { get { return (_u); } }
-
- private int _start;
- private int _count;
- private bool _u;
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/GridSplitter.cs b/src/Avalonia.Controls/Grid/GridSplitter.cs
deleted file mode 100644
index 304a760216..0000000000
--- a/src/Avalonia.Controls/Grid/GridSplitter.cs
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Avalonia.Controls.Primitives;
-using Avalonia.Input;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Controls
-{
- ///
- /// Represents the control that redistributes space between columns or rows of a Grid control.
- ///
- ///
- /// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
- ///
- public class GridSplitter : Thumb
- {
- private List _definitions;
-
- private Grid _grid;
-
- private DefinitionBase _nextDefinition;
-
- private Orientation _orientation;
-
- private DefinitionBase _prevDefinition;
-
- private void GetDeltaConstraints(out double min, out double max)
- {
- var prevDefinitionLen = GetActualLength(_prevDefinition);
- var prevDefinitionMin = GetMinLength(_prevDefinition);
- var prevDefinitionMax = GetMaxLength(_prevDefinition);
-
- var nextDefinitionLen = GetActualLength(_nextDefinition);
- var nextDefinitionMin = GetMinLength(_nextDefinition);
- var nextDefinitionMax = GetMaxLength(_nextDefinition);
- // Determine the minimum and maximum the columns can be resized
- min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen);
- max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin);
- }
-
- protected override void OnDragDelta(VectorEventArgs e)
- {
- // WPF doesn't change anything when spliter is in the last row/column
- // but resizes the splitter row/column when it's the first one.
- // this is different, but more internally consistent.
- if (_prevDefinition == null || _nextDefinition == null)
- return;
-
- var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y;
- double max;
- double min;
- GetDeltaConstraints(out min, out max);
- delta = Math.Min(Math.Max(delta, min), max);
-
- var prevIsStar = IsStar(_prevDefinition);
- var nextIsStar = IsStar(_nextDefinition);
-
- if (prevIsStar && nextIsStar)
- {
- foreach (var definition in _definitions)
- {
- if (definition == _prevDefinition)
- {
- SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta);
- }
- else if (definition == _nextDefinition)
- {
- SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta);
- }
- else if (IsStar(definition))
- {
- SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars.
- }
- }
- }
- else if (prevIsStar)
- {
- SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta);
- }
- else if (nextIsStar)
- {
- SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta);
- }
- else
- {
- SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta);
- SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta);
- }
- }
-
- private double GetActualLength(DefinitionBase definition)
- {
- if (definition == null)
- return 0;
- var columnDefinition = definition as ColumnDefinition;
- return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
- }
-
- private double GetMinLength(DefinitionBase definition)
- {
- if (definition == null)
- return 0;
- var columnDefinition = definition as ColumnDefinition;
- return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight;
- }
-
- private double GetMaxLength(DefinitionBase definition)
- {
- if (definition == null)
- return 0;
- var columnDefinition = definition as ColumnDefinition;
- return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight;
- }
-
- private bool IsStar(DefinitionBase definition)
- {
- var columnDefinition = definition as ColumnDefinition;
- return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar;
- }
-
- private void SetLengthInStars(DefinitionBase definition, double value)
- {
- var columnDefinition = definition as ColumnDefinition;
- if (columnDefinition != null)
- {
- columnDefinition.Width = new GridLength(value, GridUnitType.Star);
- }
- else
- {
- ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star);
- }
- }
-
- private void SetLength(DefinitionBase definition, double value)
- {
- var columnDefinition = definition as ColumnDefinition;
- if (columnDefinition != null)
- {
- columnDefinition.Width = new GridLength(value);
- }
- else
- {
- ((RowDefinition)definition).Height = new GridLength(value);
- }
- }
-
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnAttachedToVisualTree(e);
- _grid = this.GetVisualParent();
-
- _orientation = DetectOrientation();
-
- int definitionIndex; //row or col
- if (_orientation == Orientation.Vertical)
- {
- Cursor = new Cursor(StandardCursorType.SizeWestEast);
- _definitions = _grid.ColumnDefinitions.Cast().ToList();
- definitionIndex = GetValue(Grid.ColumnProperty);
- PseudoClasses.Add(":vertical");
- }
- else
- {
- Cursor = new Cursor(StandardCursorType.SizeNorthSouth);
- definitionIndex = GetValue(Grid.RowProperty);
- _definitions = _grid.RowDefinitions.Cast().ToList();
- PseudoClasses.Add(":horizontal");
- }
-
- if (definitionIndex > 0)
- _prevDefinition = _definitions[definitionIndex - 1];
-
- if (definitionIndex < _definitions.Count - 1)
- _nextDefinition = _definitions[definitionIndex + 1];
- }
-
- private Orientation DetectOrientation()
- {
- if (!_grid.ColumnDefinitions.Any())
- return Orientation.Horizontal;
- if (!_grid.RowDefinitions.Any())
- return Orientation.Vertical;
-
- var col = GetValue(Grid.ColumnProperty);
- var row = GetValue(Grid.RowProperty);
- var width = _grid.ColumnDefinitions[col].Width;
- var height = _grid.RowDefinitions[row].Height;
- if (width.IsAuto && !height.IsAuto)
- {
- return Orientation.Vertical;
- }
- if (!width.IsAuto && height.IsAuto)
- {
- return Orientation.Horizontal;
- }
- if (_grid.Children.OfType() // Decision based on other controls in the same column
- .Where(c => Grid.GetColumn(c) == col)
- .Any(c => c.GetType() != typeof(GridSplitter)))
- {
- return Orientation.Horizontal;
- }
- return Orientation.Vertical;
- }
- }
-}
diff --git a/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs b/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs
deleted file mode 100644
index 1432c29ae5..0000000000
--- a/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Avalonia.Controls
-{
- ///
- /// LayoutTimeSizeType is used internally and reflects layout-time size type.
- ///
- [System.Flags]
- internal enum LayoutTimeSizeType : byte
- {
- None = 0x00,
- Pixel = 0x01,
- Auto = 0x02,
- Star = 0x04,
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/MaxRatioComparer.cs b/src/Avalonia.Controls/Grid/MaxRatioComparer.cs
deleted file mode 100644
index fe7eb356ec..0000000000
--- a/src/Avalonia.Controls/Grid/MaxRatioComparer.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
-
- ///
- /// MaxRatioComparer.
- /// Sort by w/max (stored in SizeCache), ascending.
- /// We query the list from the back, i.e. in descending order of w/max.
- ///
- internal class MaxRatioComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs b/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs
deleted file mode 100644
index 01bcf85b27..0000000000
--- a/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
- internal class MaxRatioIndexComparer : IComparer
- {
- private readonly DefinitionBase[] definitions;
-
- internal MaxRatioIndexComparer(DefinitionBase[] definitions)
- {
- Contract.Requires(definitions != null);
- this.definitions = definitions;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase definitionX = null;
- DefinitionBase definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/MinRatioComparer.cs b/src/Avalonia.Controls/Grid/MinRatioComparer.cs
deleted file mode 100644
index 8e0fa0a282..0000000000
--- a/src/Avalonia.Controls/Grid/MinRatioComparer.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
- ///
- /// MinRatioComparer.
- /// Sort by w/min (stored in MeasureSize), descending.
- /// We query the list from the back, i.e. in ascending order of w/min.
- ///
- internal class MinRatioComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionY, definitionX, out result))
- {
- result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs b/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs
deleted file mode 100644
index 01add324c1..0000000000
--- a/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
- internal class MinRatioIndexComparer : IComparer
- {
- private readonly DefinitionBase[] definitions;
-
- internal MinRatioIndexComparer(DefinitionBase[] definitions)
- {
- Contract.Requires(definitions != null);
- this.definitions = definitions;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase definitionX = null;
- DefinitionBase definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionY, definitionX, out result))
- {
- result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs b/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs
deleted file mode 100644
index a0a9035384..0000000000
--- a/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
- internal class RoundingErrorIndexComparer : IComparer
- {
- private readonly double[] errors;
-
- internal RoundingErrorIndexComparer(double[] errors)
- {
- Contract.Requires(errors != null);
- this.errors = errors;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- int result;
-
- if (!Grid.CompareNullRefs(indexX, indexY, out result))
- {
- double errorX = errors[indexX.Value];
- double errorY = errors[indexY.Value];
- result = errorX.CompareTo(errorY);
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/SharedSizeScope.cs b/src/Avalonia.Controls/Grid/SharedSizeScope.cs
deleted file mode 100644
index 6835d13132..0000000000
--- a/src/Avalonia.Controls/Grid/SharedSizeScope.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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
deleted file mode 100644
index 2b99c09861..0000000000
--- a/src/Avalonia.Controls/Grid/SharedSizeState.cs
+++ /dev/null
@@ -1,209 +0,0 @@
-// 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
diff --git a/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs b/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs
deleted file mode 100644
index f6fbf4d2bb..0000000000
--- a/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
- internal class SpanMaxDistributionOrderComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionX, definitionY, out result))
- {
- if (definitionX.UserSize.IsAuto)
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
- else
- {
- result = +1;
- }
- }
- else
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = -1;
- }
- else
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
- }
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs b/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs
deleted file mode 100644
index 1adb62590c..0000000000
--- a/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
- internal class SpanPreferredDistributionOrderComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionX, definitionY, out result))
- {
- if (definitionX.UserSize.IsAuto)
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = definitionX.MinSize.CompareTo(definitionY.MinSize);
- }
- else
- {
- result = -1;
- }
- }
- else
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = +1;
- }
- else
- {
- result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize);
- }
- }
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/StarWeightComparer.cs b/src/Avalonia.Controls/Grid/StarWeightComparer.cs
deleted file mode 100644
index 216f97f2c1..0000000000
--- a/src/Avalonia.Controls/Grid/StarWeightComparer.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
- ///
- /// StarWeightComparer.
- /// Sort by *-weight (stored in MeasureSize), ascending.
- ///
- internal class StarWeightComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs b/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs
deleted file mode 100644
index da5148e9a5..0000000000
--- a/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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;
-
-namespace Avalonia.Controls
-{
-
- internal class StarWeightIndexComparer : IComparer
- {
- private readonly DefinitionBase[] definitions;
-
- internal StarWeightIndexComparer(DefinitionBase[] definitions)
- {
- Contract.Requires(definitions != null);
- this.definitions = definitions;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase definitionX = null;
- DefinitionBase definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!Grid.CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
- }
-
- return result;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs
similarity index 93%
rename from src/Avalonia.Controls/Grid/RowDefinition.cs
rename to src/Avalonia.Controls/RowDefinition.cs
index 1cb09e16e9..8e6ab3ae36 100644
--- a/src/Avalonia.Controls/Grid/RowDefinition.cs
+++ b/src/Avalonia.Controls/RowDefinition.cs
@@ -87,12 +87,5 @@ namespace Avalonia.Controls
get { return GetValue(HeightProperty); }
set { SetValue(HeightProperty, value); }
}
-
-
- internal override GridLength UserSizeValueCache => this.Height;
-
- internal override double UserMinSizeValueCache => this.MinHeight;
-
- internal override double UserMaxSizeValueCache => this.MaxHeight;
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Grid/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs
similarity index 100%
rename from src/Avalonia.Controls/Grid/RowDefinitions.cs
rename to src/Avalonia.Controls/RowDefinitions.cs