Browse Source

Downsize DefinitionBase, removing most of shared size scope code.

pull/2563/head
Jumar Macato 7 years ago
parent
commit
69c056c728
No known key found for this signature in database GPG Key ID: B19884DAC3A5BF3F
  1. 6
      src/Avalonia.Controls/ColumnDefinition.cs
  2. 517
      src/Avalonia.Controls/DefinitionBase.cs
  3. 50
      src/Avalonia.Controls/GridWPF.cs
  4. 6
      src/Avalonia.Controls/RowDefinition.cs

6
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;
}
}

517
src/Avalonia.Controls/DefinitionBase.cs

@ -9,27 +9,16 @@ using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Base class for <see cref="ColumnDefinition"/> and <see cref="RowDefinition"/>.
/// </summary>
public abstract class DefinitionBase : ContentControl
public abstract class DefinitionBase : AvaloniaObject
{
/// <summary>
/// Static ctor. Used for static registration of properties.
/// </summary>
static DefinitionBase()
{
SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
// BoundsProperty.Changed.AddClassHandler<DefinitionBase>(OnUserSizePropertyChanged);
}
/// <summary>
/// Defines the <see cref="SharedSizeGroup"/> property.
/// </summary>
public static readonly StyledProperty<string> SharedSizeGroupProperty =
AvaloniaProperty.Register<DefinitionBase, string>(nameof(SharedSizeGroup), inherits: true, validate: SharedSizeGroupPropertyValueValid);
AvaloniaProperty.Register<DefinitionBase, string>(nameof(SharedSizeGroup), inherits: true);
/// <summary>
/// Gets or sets the name of the shared size group of the column or row.
@ -41,84 +30,30 @@ namespace Avalonia.Controls
}
/// <summary>
/// Performs action preparing definition to enter layout calculation mode.
/// </summary>
internal void OnBeforeLayout(Grid grid)
{
// reset layout state.
_minSize = 0;
LayoutWasUpdated = true;
// defer verification for shared definitions
if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); }
}
/// <summary>
/// Returns <c>true</c> if this definition is a part of shared group.
/// </summary>
internal bool IsShared
{
get { return (_sharedState != null); }
}
/// <summary>
/// Internal accessor to user size field.
/// Internal helper to access up-to-date UserSize property value.
/// </summary>
internal GridLength UserSize
{
get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); }
}
internal abstract GridLength UserSize { get; }
/// <summary>
/// Internal accessor to user min size field.
/// </summary>
internal double UserMinSize
{
get { return (UserMinSizeValueCache); }
}
/// <summary>
/// Internal accessor to user max size field.
/// Internal helper to access up-to-date UserMinSize property value.
/// </summary>
internal double UserMaxSize
{
get { return (UserMaxSizeValueCache); }
}
internal abstract double UserMinSize { get; }
/// <summary>
/// DefinitionBase's index in the parents collection.
/// Internal helper to access up-to-date UserMaxSize property value.
/// </summary>
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
/// <summary>
/// Layout-time user size type.
/// </summary>
internal Grid.LayoutTimeSizeType SizeType
{
get { return (_sizeType); }
set { _sizeType = value; }
}
internal Grid.LayoutTimeSizeType SizeType {get; set;}
/// <summary>
/// Returns or sets measure size for the definition.
/// </summary>
internal double MeasureSize
{
get { return (_measureSize); }
set { _measureSize = value; }
}
internal double MeasureSize { get; set; }
/// <summary>
/// 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
/// <summary>
/// Returns or sets size cache for the definition.
/// </summary>
internal double SizeCache
{
get { return (_sizeCache); }
set { _sizeCache = value; }
}
internal double SizeCache { get; set; }
/// <summary>
/// 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
/// <summary>
/// Offset.
/// </summary>
internal double FinalOffset
{
get { return _offset; }
set { _offset = value; }
}
/// <summary>
/// Internal helper to access up-to-date UserSize property value.
/// </summary>
internal abstract GridLength UserSizeValueCache { get; }
/// <summary>
/// Internal helper to access up-to-date UserMinSize property value.
/// </summary>
internal abstract double UserMinSizeValueCache { get; }
/// <summary>
/// Internal helper to access up-to-date UserMaxSize property value.
/// </summary>
internal abstract double UserMaxSizeValueCache { get; }
/// <summary>
/// Protected. Returns <c>true</c> if this DefinitionBase instance is in parent's logical tree.
/// </summary>
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);
// }
}
}
}
/// <remarks>
/// Verifies that Shared Size Group Property string
/// a) not empty.
/// b) contains only letters, digits and underscore ('_').
/// c) does not start with a digit.
/// </remarks>
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;
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remark>
/// 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.
/// </remark>
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);
}
}
}
}
/// <summary>
/// Convenience accessor to UseSharedMinimum flag
/// </summary>
private bool UseSharedMinimum
{
get { return _useSharedMinimum; }
set { _useSharedMinimum = value; }
}
internal double FinalOffset { get; set; }
/// <summary>
/// Convenience accessor to LayoutWasUpdated flag
/// </summary>
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;
/// <summary>
/// Collection of shared states objects for a single scope
/// </summary>
private class SharedSizeScope
{
/// <summary>
/// Returns SharedSizeState object for a given group.
/// Creates a new StatedState object if necessary.
/// </summary>
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);
}
/// <summary>
/// Removes an entry in the registry by the given key.
/// </summary>
internal void Remove(object key)
{
Debug.Assert(_registry.Contains(key));
_registry.Remove(key);
}
private Hashtable _registry = new Hashtable(); // storage for shared state objects
}
/// <summary>
/// Implementation of per shared group state object
/// Returns <c>true</c> if this definition is a part of shared group.
/// </summary>
private class SharedSizeState
{
/// <summary>
/// Default ctor.
/// </summary>
internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId)
{
Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null);
_sharedSizeScope = sharedSizeScope;
_sharedSizeGroupId = sharedSizeGroupId;
_registry = new List<DefinitionBase>();
// _layoutUpdated = new EventHandler(OnLayoutUpdated);
_broadcastInvalidation = true;
}
/// <summary>
/// Adds / registers a definition instance.
/// </summary>
internal void AddMember(DefinitionBase member)
{
Debug.Assert(!_registry.Contains(member));
_registry.Add(member);
Invalidate();
}
/// <summary>
/// Removes / un-registers a definition instance.
/// </summary>
/// <remarks>
/// If the collection of registered definitions becomes empty
/// instantiates self removal from owner's collection.
/// </remarks>
internal void RemoveMember(DefinitionBase member)
{
Invalidate();
_registry.Remove(member);
if (_registry.Count == 0)
{
_sharedSizeScope.Remove(_sharedSizeGroupId);
}
}
/// <summary>
/// Propogates invalidations for all registered definitions.
/// Resets its own state.
/// </summary>
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;
}
}
/// <summary>
/// Makes sure that one and only one layout updated handler is registered for this shared state.
/// </summary>
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));
}
}
/// <summary>
/// DefinitionBase's specific code.
/// </summary>
internal double MinSize
{
get
{
if (!_userSizeValid) { EnsureUserSizeValid(); }
return (_minSize);
}
}
/// <summary>
/// DefinitionBase's specific code.
/// </summary>
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;
}
/// <summary>
/// OnLayoutUpdated handler. Validates that all participating definitions
/// have updated min size value. Forces another layout update cycle if needed.
/// </summary>
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<DefinitionBase> _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; }
}
}

50
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
}
}
/// <summary>
/// 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)
/// </summary>
// 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
{

6
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;
}
}
Loading…
Cancel
Save