diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index 015484dbcc..56a6d41b7b 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -88,10 +88,10 @@ namespace Avalonia.Controls set { SetValue(WidthProperty, value); } } - internal override GridLength UserSizeValueCache => this.Width; + internal override GridLength UserSize => this.Width; - internal override double UserMinSizeValueCache => this.MinWidth; + internal override double UserMinSize => this.MinWidth; - internal override double UserMaxSizeValueCache => this.MaxWidth; + internal override double UserMaxSize => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 643d9da751..38fc375631 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -9,27 +9,16 @@ using Avalonia.Utilities; namespace Avalonia.Controls { - /// /// Base class for and . /// - public abstract class DefinitionBase : ContentControl + public abstract class DefinitionBase : AvaloniaObject { - /// - /// Static ctor. Used for static registration of properties. - /// - static DefinitionBase() - { - SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - // BoundsProperty.Changed.AddClassHandler(OnUserSizePropertyChanged); - } - - /// /// Defines the property. /// public static readonly StyledProperty SharedSizeGroupProperty = - AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true, validate: SharedSizeGroupPropertyValueValid); + AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true); /// /// Gets or sets the name of the shared size group of the column or row. @@ -41,84 +30,30 @@ namespace Avalonia.Controls } /// - /// 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); } - } - - - /// - /// 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 helper to access up-to-date UserSize property value. /// - internal GridLength UserSize - { - get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); } - } + internal abstract GridLength UserSize { get; } /// - /// Internal accessor to user min size field. - /// - internal double UserMinSize - { - get { return (UserMinSizeValueCache); } - } - - /// - /// Internal accessor to user max size field. + /// Internal helper to access up-to-date UserMinSize property value. /// - internal double UserMaxSize - { - get { return (UserMaxSizeValueCache); } - } + internal abstract double UserMinSize { get; } /// - /// DefinitionBase's index in the parents collection. + /// Internal helper to access up-to-date UserMaxSize property value. /// - internal int Index - { - get - { - return (_parentIndex); - } - set - { - Debug.Assert(value >= -1 && _parentIndex != value); - _parentIndex = value; - } - } + internal abstract double UserMaxSize { get; } + + private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's /// /// Layout-time user size type. /// - internal Grid.LayoutTimeSizeType SizeType - { - get { return (_sizeType); } - set { _sizeType = value; } - } - + internal Grid.LayoutTimeSizeType SizeType {get; set;} /// /// Returns or sets measure size for the definition. /// - internal double MeasureSize - { - get { return (_measureSize); } - set { _measureSize = value; } - } + internal double MeasureSize { get; set; } /// /// Returns definition's layout time type sensitive preferred size. @@ -131,10 +66,10 @@ namespace Avalonia.Controls get { double preferredSize = MinSize; - if (_sizeType != Grid.LayoutTimeSizeType.Auto - && preferredSize < _measureSize) + if (SizeType != Grid.LayoutTimeSizeType.Auto + && preferredSize < MeasureSize) { - preferredSize = _measureSize; + preferredSize = MeasureSize; } return (preferredSize); } @@ -143,11 +78,7 @@ namespace Avalonia.Controls /// /// Returns or sets size cache for the definition. /// - internal double SizeCache - { - get { return (_sizeCache); } - set { _sizeCache = value; } - } + internal double SizeCache { get; set; } /// /// Returns min size. @@ -157,12 +88,6 @@ namespace Avalonia.Controls get { double minSize = _minSize; - if (UseSharedMinimum - && _sharedState != null - && minSize < _sharedState.MinSize) - { - minSize = _sharedState.MinSize; - } return (minSize); } @@ -194,12 +119,6 @@ namespace Avalonia.Controls get { double minSize = _minSize; - if (_sharedState != null - && (UseSharedMinimum || !LayoutWasUpdated) - && minSize < _sharedState.MinSize) - { - minSize = _sharedState.MinSize; - } return (minSize); } } @@ -207,409 +126,11 @@ namespace Avalonia.Controls /// /// 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; } - - - /// - /// Protected. Returns true if this DefinitionBase instance is in parent's logical tree. - /// - internal bool InParentLogicalTree - { - get { return (_parentIndex != -1); } - } - - private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) - { - 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 string SharedSizeGroupPropertyValueValid(DefinitionBase _, string value) - { - // null is default value - if (value == null) - { - return value; - } - - 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 value; - } - } - - return null; - } - - /// - /// - /// - /// - /// 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(AvaloniaObject d, AvaloniaPropertyChangedEventArgs 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); - } - } - } - } - - /// - /// Convenience accessor to UseSharedMinimum flag - /// - private bool UseSharedMinimum - { - get { return _useSharedMinimum; } - set { _useSharedMinimum = value; } - } + internal double FinalOffset { get; set; } /// - /// Convenience accessor to LayoutWasUpdated flag - /// - private bool LayoutWasUpdated - { - get { return _layoutWasUpdated; } - set { _layoutWasUpdated = value; } - } - - private int _parentIndex = -1; // 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 - private bool _layoutWasUpdated; - private bool _useSharedMinimum; - - - - /// - /// 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 + /// Returns true if this definition is a part of shared group. /// - 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(IControl layoutUpdatedHost) - { - if (_layoutUpdatedHost == null) - { - _layoutUpdatedHost = layoutUpdatedHost; - // PORTING HACK... Remove this when resolved. - _layoutUpdatedHost.GetSubject(Visual.BoundsProperty).Subscribe(p => _layoutUpdated?.Invoke(this, null)); - } - } - - /// - /// 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; - } - - 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 IControl _layoutUpdatedHost; // IControl 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 - } + internal bool IsShared { get; set; } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 334d57ecf8..e18d70c840 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -649,7 +649,7 @@ namespace Avalonia.Controls { for (int i = 0; i < definitions.Length; ++i) { - definitions[i].OnBeforeLayout(this); + // definitions[i].OnBeforeLayout(this); double userMinSize = definitions[i].UserMinSize; double userMaxSize = definitions[i].UserMaxSize; @@ -1215,17 +1215,17 @@ namespace Avalonia.Controls } } - if (Double.IsPositiveInfinity(maxStar)) + 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 + // 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)); + 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 @@ -1277,7 +1277,7 @@ namespace Avalonia.Controls } double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); - if (!Double.IsPositiveInfinity(effectiveMaxSize)) + if (!double.IsPositiveInfinity(effectiveMaxSize)) { // store ratio w/max in SizeCache (for now) tempDefinitions[defCount + maxCount++] = def; @@ -1322,7 +1322,7 @@ namespace Avalonia.Controls remainingStarWeight = totalStarWeight - takenStarWeight; } - double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; + 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") @@ -1532,17 +1532,17 @@ namespace Avalonia.Controls } } - if (Double.IsPositiveInfinity(maxStar)) + 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 + // 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)); + 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 @@ -1590,7 +1590,7 @@ namespace Avalonia.Controls } double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); - if (!Double.IsPositiveInfinity(effectiveMaxSize)) + if (!double.IsPositiveInfinity(effectiveMaxSize)) { // store ratio w/max in SizeCache (for now) definitionIndices[defCount + maxCount++] = i; @@ -1670,7 +1670,7 @@ namespace Avalonia.Controls remainingStarWeight = totalStarWeight - takenStarWeight; } - double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : Double.PositiveInfinity; + 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") @@ -1950,19 +1950,17 @@ namespace Avalonia.Controls } } - /// - /// 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) - /// + // 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) @@ -2092,7 +2090,7 @@ namespace Avalonia.Controls // 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)) + MathUtilities.AreClose(newValue, double.MaxValue)) { newValue = value; } @@ -2249,7 +2247,7 @@ namespace Avalonia.Controls // 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; + return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; } else { diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index 1cb09e16e9..d9120b010e 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -89,10 +89,10 @@ namespace Avalonia.Controls } - internal override GridLength UserSizeValueCache => this.Height; + internal override GridLength UserSize => this.Height; - internal override double UserMinSizeValueCache => this.MinHeight; + internal override double UserMinSize => this.MinHeight; - internal override double UserMaxSizeValueCache => this.MaxHeight; + internal override double UserMaxSize => this.MaxHeight; } } \ No newline at end of file