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