Browse Source

Part 4 of n

Removed more legacy stuff
Added Math Functions needed from WPF's DoubleUtil.
pull/2563/head
Jumar Macato 8 years ago
parent
commit
9f1d70e592
No known key found for this signature in database GPG Key ID: B19884DAC3A5BF3F
  1. 123
      src/Avalonia.Base/Utilities/MathUtilities.cs
  2. 894
      src/Avalonia.Controls/DefinitionBase.cs
  3. 468
      src/Avalonia.Controls/GridWPF.cs

123
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -1,6 +1,8 @@
// 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;
namespace Avalonia.Utilities
{
/// <summary>
@ -8,6 +10,127 @@ namespace Avalonia.Utilities
/// </summary>
public static class MathUtilities
{
/// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other. Note that this epsilon is proportional
/// to the numbers themselves to that AreClose survives scalar multiplication.
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the AreClose comparision.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool AreClose(double value1, double value2)
{
//in case they are Infinities (then epsilon check does not work)
if (value1 == value2) return true;
double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon;
double delta = value1 - value2;
return (-eps < delta) && (eps > delta);
}
/// <summary>
/// LessThan - Returns whether or not the first double is less than the second double.
/// That is, whether or not the first is strictly less than *and* not within epsilon of
/// the other number. Note that this epsilon is proportional to the numbers themselves
/// to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the LessThan comparision.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool LessThan(double value1, double value2)
{
return (value1 < value2) && !AreClose(value1, value2);
}
/// <summary>
/// GreaterThan - Returns whether or not the first double is greater than the second double.
/// That is, whether or not the first is strictly greater than *and* not within epsilon of
/// the other number. Note that this epsilon is proportional to the numbers themselves
/// to that AreClose survives scalar multiplication.
/// </summary>
/// <returns> - the result of the GreaterThan comparision.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool GreaterThan(double value1, double value2)
{
return (value1 > value2) && !AreClose(value1, value2);
}
/// <summary>
/// LessThanOrClose - Returns whether or not the first double is less than or close to
/// the second double. That is, whether or not the first is strictly less than or within
/// epsilon of the other number. Note that this epsilon is proportional to the numbers
/// themselves to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the LessThanOrClose comparision.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool LessThanOrClose(double value1, double value2)
{
return (value1 < value2) || AreClose(value1, value2);
}
/// <summary>
/// GreaterThanOrClose - Returns whether or not the first double is greater than or close to
/// the second double. That is, whether or not the first is strictly greater than or within
/// epsilon of the other number.
/// </summary>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
/// <returns>the result of the GreaterThanOrClose comparision.</returns>
public static bool GreaterThanOrClose(double value1, double value2)
{
return (value1 > value2) || AreClose(value1, value2);
}
/// <summary>
/// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1),
/// but this is faster.
/// </summary>
/// <returns>
/// bool - the result of the AreClose comparision.
/// </returns>
/// <param name="value"> The double to compare to 1. </param>
public static bool IsOne(double value)
{
return Math.Abs(value - 1.0) < 10.0 * double.Epsilon;
}
/// <summary>
/// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0),
/// but this is faster.
/// </summary>
/// <returns>
/// bool - the result of the AreClose comparision.
/// </returns>
/// <param name="value"> The double to compare to 0. </param>
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * double.Epsilon;
}
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>

894
src/Avalonia.Controls/DefinitionBase.cs

@ -1,18 +1,24 @@
// 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.Diagnostics;
namespace Avalonia.Controls
{
/// <summary>
/// Base class for <see cref="ColumnDefinition"/> and <see cref="RowDefinition"/>.
/// </summary>
public class DefinitionBase : AvaloniaObject
{
/// <summary>
/// Defines the <see cref="SharedSizeGroup"/> property.
/// </summary>
public static readonly StyledProperty<string> SharedSizeGroupProperty =
AvaloniaProperty.Register<DefinitionBase, string>(nameof(SharedSizeGroup), inherits: true);
AvaloniaProperty.Register<DefinitionBase, string>(nameof(SharedSizeGroup), inherits: true, validate: SharedSizeGroupPropertyValueValid);
/// <summary>
/// Gets or sets the name of the shared size group of the column or row.
@ -22,5 +28,889 @@ namespace Avalonia.Controls
get { return GetValue(SharedSizeGroupProperty); }
set { SetValue(SharedSizeGroupProperty, value); }
}
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
internal DefinitionBase(bool isColumnDefinition)
{
_isColumnDefinition = isColumnDefinition;
_parentIndex = -1;
}
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
/// <summary>
/// Callback to notify about entering model tree.
/// </summary>
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);
}
}
}
}
/// <summary>
/// Callback to notify about exitting model tree.
/// </summary>
internal void OnExitParentTree()
{
_offset = 0;
if (_sharedState != null)
{
_sharedState.RemoveMember(this);
_sharedState = null;
}
}
/// <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>
/// Updates min size.
/// </summary>
/// <param name="minSize">New size.</param>
internal void UpdateMinSize(double minSize)
{
_minSize = Math.Max(_minSize, minSize);
}
/// <summary>
/// Sets min size.
/// </summary>
/// <param name="minSize">New size.</param>
internal void SetMinSize(double minSize)
{
_minSize = minSize;
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
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();
}
}
}
}
/// <summary>
/// <see cref="DependencyProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserSizePropertyValueValid(object value)
{
return (((GridLength)value).Value >= 0);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static void OnUserMinSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase)d;
if (definition.InParentLogicalTree)
{
Grid parentGrid = (Grid)definition.Parent;
parentGrid.InvalidateMeasure();
}
}
/// <summary>
/// <see cref="DependencyProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserMinSizePropertyValueValid(object value)
{
double v = (double)value;
return (!DoubleUtil.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v));
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static void OnUserMaxSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase)d;
if (definition.InParentLogicalTree)
{
Grid parentGrid = (Grid)definition.Parent;
parentGrid.InvalidateMeasure();
}
}
/// <summary>
/// <see cref="DependencyProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserMaxSizePropertyValueValid(object value)
{
double v = (double)value;
return (!DoubleUtil.IsNaN(v) && v >= 0.0d);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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
/// <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.
/// </summary>
internal GridLength UserSize
{
get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); }
}
/// <summary>
/// Internal accessor to user min size field.
/// </summary>
internal double UserMinSize
{
get { return (UserMinSizeValueCache); }
}
/// <summary>
/// Internal accessor to user max size field.
/// </summary>
internal double UserMaxSize
{
get { return (UserMaxSizeValueCache); }
}
/// <summary>
/// DefinitionBase's index in the parents collection.
/// </summary>
internal int Index
{
get
{
return (_parentIndex);
}
set
{
Debug.Assert(value >= -1 && _parentIndex != value);
_parentIndex = value;
}
}
/// <summary>
/// Layout-time user size type.
/// </summary>
internal Grid.LayoutTimeSizeType SizeType
{
get { return (_sizeType); }
set { _sizeType = value; }
}
/// <summary>
/// Returns or sets measure size for the definition.
/// </summary>
internal double MeasureSize
{
get { return (_measureSize); }
set { _measureSize = value; }
}
/// <summary>
/// Returns definition's layout time type sensitive preferred size.
/// </summary>
/// <remarks>
/// Returned value is guaranteed to be true preferred size.
/// </remarks>
internal double PreferredSize
{
get
{
double preferredSize = MinSize;
if (_sizeType != Grid.LayoutTimeSizeType.Auto
&& preferredSize < _measureSize)
{
preferredSize = _measureSize;
}
return (preferredSize);
}
}
/// <summary>
/// Returns or sets size cache for the definition.
/// </summary>
internal double SizeCache
{
get { return (_sizeCache); }
set { _sizeCache = value; }
}
/// <summary>
/// Returns min size.
/// </summary>
internal double MinSize
{
get
{
double minSize = _minSize;
if (UseSharedMinimum
&& _sharedState != null
&& minSize < _sharedState.MinSize)
{
minSize = _sharedState.MinSize;
}
return (minSize);
}
}
/// <summary>
/// Returns min size, always taking into account shared state.
/// </summary>
internal double MinSizeForArrange
{
get
{
double minSize = _minSize;
if (_sharedState != null
&& (UseSharedMinimum || !LayoutWasUpdated)
&& minSize < _sharedState.MinSize)
{
minSize = _sharedState.MinSize;
}
return (minSize);
}
}
/// <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 GridLength UserSizeValueCache
{
get
{
return (GridLength)GetValue(
_isColumnDefinition ?
ColumnDefinition.WidthProperty :
RowDefinition.HeightProperty);
}
}
/// <summary>
/// Internal helper to access up-to-date UserMinSize property value.
/// </summary>
internal double UserMinSizeValueCache
{
get
{
return (double)GetValue(
_isColumnDefinition ?
ColumnDefinition.MinWidthProperty :
RowDefinition.MinHeightProperty);
}
}
/// <summary>
/// Internal helper to access up-to-date UserMaxSize property value.
/// </summary>
internal double UserMaxSizeValueCache
{
get
{
return (double)GetValue(
_isColumnDefinition ?
ColumnDefinition.MaxWidthProperty :
RowDefinition.MaxHeightProperty);
}
}
/// <summary>
/// Protected. Returns <c>true</c> if this DefinitionBase instance is in parent's logical tree.
/// </summary>
internal bool InParentLogicalTree
{
get { return (_parentIndex != -1); }
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
/// <summary>
/// SetFlags is used to set or unset one or multiple
/// flags on the object.
/// </summary>
private void SetFlags(bool value, Flags flags)
{
_flags = value ? (_flags | flags) : (_flags & (~flags));
}
/// <summary>
/// CheckFlagsAnd returns <c>true</c> if all the flags in the
/// given bitmask are set on the object.
/// </summary>
private bool CheckFlagsAnd(Flags flags)
{
return ((_flags & flags) == flags);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
private static void OnSharedSizeGroupPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase)e.Sender;
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 bool SharedSizeGroupPropertyValueValid(string 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);
}
/// <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);
}
}
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
/// <summary>
/// Private getter of shared state collection dynamic property.
/// </summary>
private SharedSizeScope PrivateSharedSizeScope
{
get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); }
}
/// <summary>
/// Convenience accessor to UseSharedMinimum flag
/// </summary>
private bool UseSharedMinimum
{
get { return (CheckFlagsAnd(Flags.UseSharedMinimum)); }
set { SetFlags(value, Flags.UseSharedMinimum); }
}
/// <summary>
/// Convenience accessor to LayoutWasUpdated flag
/// </summary>
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
}
/// <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
/// </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;
_layoutUpdatedHost.LayoutUpdated += _layoutUpdated;
}
}
/// <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 = !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<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
}
/// <summary>
/// Static ctor. Used for static registration of properties.
/// </summary>
static DefinitionBase()
{
SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
}
#endregion Properties
}
}
}

468
src/Avalonia.Controls/GridWPF.cs

@ -868,7 +868,7 @@ namespace Avalonia.Controls
{
for (int i = 0; i < minSizes.Length; i++)
{
if (DoubleUtil.GreaterThanOrClose(minSizes[i], 0))
if (MathUtilities.GreaterThanOrClose(minSizes[i], 0))
{
if (isRows)
{
@ -926,7 +926,7 @@ namespace Avalonia.Controls
MeasureCell(i, forceInfinityV);
hasDesiredSizeUChanged |= !DoubleUtil.AreClose(oldWidth, Children[i].DesiredSize.Width);
hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width);
if (!ignoreDesiredSizeU)
{
@ -1284,10 +1284,10 @@ namespace Avalonia.Controls
// sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers
Debug.Assert(!double.IsInfinity(totalRemainingSize)
&& !DoubleUtil.IsNaN(totalRemainingSize)
&& !MathUtilities.IsNaN(totalRemainingSize)
&& totalRemainingSize > 0
&& !double.IsInfinity(sizeToDistribute)
&& !DoubleUtil.IsNaN(sizeToDistribute)
&& !MathUtilities.IsNaN(sizeToDistribute)
&& sizeToDistribute > 0);
for (int i = 0; i < count; ++i)
@ -1320,107 +1320,6 @@ namespace Avalonia.Controls
/// <remarks>
/// Must initialize LayoutSize for all Star entries in given array of definitions.
/// </remarks>
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
@ -1435,7 +1334,8 @@ namespace Avalonia.Controls
// 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(
private void ResolveStar(
DefinitionBase[] definitions,
double availableSize)
{
@ -1755,272 +1655,7 @@ namespace Avalonia.Controls
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 = IControl.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 = IControl.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 = IControl.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 = IControl.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 = IControl.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;
@ -2359,7 +1994,7 @@ namespace Avalonia.Controls
for (int i = 0; i < definitions.Length; ++i)
{
DefinitionBase def = definitions[i];
double roundedSize = IControl.RoundLayoutValue(def.SizeCache, dpi);
double roundedSize = Control.RoundLayoutValue(def.SizeCache, dpi);
roundingErrors[i] = (roundedSize - def.SizeCache);
def.SizeCache = roundedSize;
roundedTakenSize += roundedSize;
@ -2561,9 +2196,6 @@ namespace Avalonia.Controls
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"
@ -3403,7 +3035,7 @@ namespace Avalonia.Controls
internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions)
{
Invariant.Assert(definitions != null);
Contract.Requires<NullReferenceException>(definitions != null);
this.definitions = definitions;
}
@ -3444,7 +3076,7 @@ namespace Avalonia.Controls
internal DistributionOrderIndexComparer(DefinitionBase[] definitions)
{
Invariant.Assert(definitions != null);
Contract.Requires<NullReferenceException>(definitions != null);
this.definitions = definitions;
}
@ -3487,7 +3119,7 @@ namespace Avalonia.Controls
internal RoundingErrorIndexComparer(double[] errors)
{
Invariant.Assert(errors != null);
Contract.Requires<NullReferenceException>(errors != null);
this.errors = errors;
}
@ -3586,7 +3218,7 @@ namespace Avalonia.Controls
internal MinRatioIndexComparer(DefinitionBase[] definitions)
{
Invariant.Assert(definitions != null);
Contract.Requires<NullReferenceException>(definitions != null);
this.definitions = definitions;
}
@ -3627,7 +3259,7 @@ namespace Avalonia.Controls
internal MaxRatioIndexComparer(DefinitionBase[] definitions)
{
Invariant.Assert(definitions != null);
Contract.Requires<NullReferenceException>(definitions != null);
this.definitions = definitions;
}
@ -3668,7 +3300,7 @@ namespace Avalonia.Controls
internal StarWeightIndexComparer(DefinitionBase[] definitions)
{
Invariant.Assert(definitions != null);
Contract.Requires<NullReferenceException>(definitions != null);
this.definitions = definitions;
}
@ -3699,79 +3331,11 @@ namespace Avalonia.Controls
return result;
}
}
/// <summary>
/// Implementation of a simple enumerator of grid's logical Children
/// </summary>
private class GridChildrenCollectionEnumeratorSimple : IEnumerator
{
internal GridChildrenCollectionEnumeratorSimple(Grid grid, bool includeChildren)
{
Debug.Assert(grid != null);
_currentEnumerator = -1;
_enumerator0 = new ColumnDefinitions.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null);
_enumerator1 = new RowDefinitions.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 void Reset()
{
_currentEnumerator = -1;
_currentChild = null;
_enumerator0.Reset();
_enumerator1.Reset();
_enumerator2Index = 0;
}
private int _currentEnumerator;
private Object _currentChild;
private ColumnDefinitions.Enumerator _enumerator0;
private RowDefinitions.Enumerator _enumerator1;
private Controls _enumerator2Collection;
private int _enumerator2Index;
private int _enumerator2Count;
}
/// <summary>
/// Helper to render grid lines.
/// </summary>
internal class GridLinesRenderer : DrawingVisual
internal class GridLinesRenderer : Visual
{
/// <summary>
/// Static initialization

Loading…
Cancel
Save