diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj
index c321d3a2f1..32331d29ab 100644
--- a/src/Avalonia.Controls/Avalonia.Controls.csproj
+++ b/src/Avalonia.Controls/Avalonia.Controls.csproj
@@ -11,7 +11,6 @@
-
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index ad5e96376d..236b478f95 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -1,5 +1,5 @@
-// 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.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
@@ -10,15 +10,81 @@ using System.Runtime.CompilerServices;
using Avalonia.Collections;
using Avalonia.Controls.Utils;
using Avalonia.VisualTree;
+using System.Threading;
using JetBrains.Annotations;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia;
+using System.Collections;
+using Avalonia.Utilities;
+using Avalonia.Layout;
namespace Avalonia.Controls
{
///
- /// Lays out child controls according to a grid.
+ /// Grid
///
public class Grid : Panel
{
+ internal bool CellsStructureDirty = true;
+ internal bool SizeToContentU;
+ internal bool SizeToContentV;
+ internal bool HasStarCellsU;
+ internal bool HasStarCellsV;
+ internal bool HasGroup3CellsInAutoRows;
+ internal bool ColumnDefinitionsDirty = true;
+ internal bool RowDefinitionsDirty = true;
+
+ // index of the first cell in first cell group
+ internal int CellGroup1;
+
+ // index of the first cell in second cell group
+ internal int CellGroup2;
+
+ // index of the first cell in third cell group
+ internal int CellGroup3;
+
+ // index of the first cell in fourth cell group
+ internal int CellGroup4;
+
+ // temporary array used during layout for various purposes
+ // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length)
+ private DefinitionBase[] _tempDefinitions;
+ private GridLinesRenderer _gridLinesRenderer;
+
+ // Keeps track of definition indices.
+ private int[] _definitionIndices;
+
+ private CellCache[] _cellCache;
+
+
+ // Stores unrounded values and rounding errors during layout rounding.
+ private double[] _roundingErrors;
+ private DefinitionBase[] _definitionsU;
+ private DefinitionBase[] _definitionsV;
+
+ // 5 is an arbitrary constant chosen to end the measure loop
+ private const int _layoutLoopMaxCount = 5;
+ private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot;
+ private static readonly IComparer _spanPreferredDistributionOrderComparer;
+ private static readonly IComparer _spanMaxDistributionOrderComparer;
+ private static readonly IComparer _minRatioComparer;
+ private static readonly IComparer _maxRatioComparer;
+ private static readonly IComparer _starWeightComparer;
+
+ static Grid()
+ {
+ ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged);
+ AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
+
+ _tempDefinitionsDataSlot = Thread.AllocateDataSlot();
+ _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer();
+ _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer();
+ _minRatioComparer = new MinRatioComparer();
+ _maxRatioComparer = new MaxRatioComparer();
+ _starWeightComparer = new StarWeightComparer();
+ }
+
///
/// Defines the Column attached property.
///
@@ -50,91 +116,21 @@ namespace Avalonia.Controls
public static readonly AttachedProperty IsSharedSizeScopeProperty =
AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false);
- protected override void OnMeasureInvalidated()
- {
- base.OnMeasureInvalidated();
- _sharedSizeHost?.InvalidateMeasure(this);
- }
-
- private SharedSizeScopeHost _sharedSizeHost;
-
- ///
- /// Defines the SharedSizeScopeHost private property.
- /// The ampersands are used to make accessing the property via xaml inconvenient.
- ///
- internal static readonly AttachedProperty s_sharedSizeScopeHostProperty =
- AvaloniaProperty.RegisterAttached("&&SharedSizeScopeHost");
-
- private ColumnDefinitions _columnDefinitions;
-
- private RowDefinitions _rowDefinitions;
-
- static Grid()
- {
- AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
- IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopeChanged);
- }
-
- public Grid()
- {
- this.AttachedToVisualTree += Grid_AttachedToVisualTree;
- this.DetachedFromVisualTree += Grid_DetachedFromVisualTree;
- }
-
///
- /// Gets or sets the columns definitions for the grid.
+ /// Defines the property.
///
- public ColumnDefinitions ColumnDefinitions
- {
- get
- {
- if (_columnDefinitions == null)
- {
- ColumnDefinitions = new ColumnDefinitions();
- }
-
- return _columnDefinitions;
- }
-
- set
- {
- if (_columnDefinitions != null)
- {
- throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented.");
- }
-
- _columnDefinitions = value;
- _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure());
- _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure();
- }
- }
+ public static readonly StyledProperty ShowGridLinesProperty =
+ AvaloniaProperty.Register(
+ nameof(ShowGridLines),
+ defaultValue: true);
///
- /// Gets or sets the row definitions for the grid.
+ /// ShowGridLines property.
///
- public RowDefinitions RowDefinitions
+ public bool ShowGridLines
{
- get
- {
- if (_rowDefinitions == null)
- {
- RowDefinitions = new RowDefinitions();
- }
-
- return _rowDefinitions;
- }
-
- set
- {
- if (_rowDefinitions != null)
- {
- throw new NotSupportedException("Reassigning RowDefinitions not yet implemented.");
- }
-
- _rowDefinitions = value;
- _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure());
- _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure();
- }
+ get { return GetValue(ShowGridLinesProperty); }
+ set { SetValue(ShowGridLinesProperty, value); }
}
///
@@ -177,7 +173,6 @@ namespace Avalonia.Controls
return element.GetValue(RowSpanProperty);
}
-
///
/// Gets the value of the IsSharedSizeScope attached property for a control.
///
@@ -228,373 +223,2602 @@ namespace Avalonia.Controls
element.SetValue(RowSpanProperty, value);
}
+ private ColumnDefinitions _columnDefinitions;
+ private RowDefinitions _rowDefinitions;
+
///
- /// Sets the value of IsSharedSizeScope property for a control.
+ /// Gets or sets the columns definitions for the grid.
///
- /// The control.
- /// The IsSharedSizeScope value.
- public static void SetIsSharedSizeScope(AvaloniaObject element, bool value)
+ public ColumnDefinitions ColumnDefinitions
{
- element.SetValue(IsSharedSizeScopeProperty, value);
+ get
+ {
+ if (_columnDefinitions == null)
+ {
+ ColumnDefinitions = new ColumnDefinitions();
+ }
+
+ return _columnDefinitions;
+ }
+ set
+ {
+ _columnDefinitions = value;
+ _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate());
+ ColumnDefinitionsDirty = true;
+
+ if (_columnDefinitions.Count > 0)
+ _definitionsU = _columnDefinitions.Cast().ToArray();
+ else
+ _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
+
+ _columnDefinitions.CollectionChanged += (_, e) =>
+ {
+ if (_columnDefinitions.Count == 0)
+ {
+ _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
+ }
+ else
+ {
+ _definitionsU = _columnDefinitions.Cast().ToArray();
+ ColumnDefinitionsDirty = true;
+ }
+ Invalidate();
+ };
+ }
}
///
- /// Gets the result of the last column measurement.
- /// Use this result to reduce the arrange calculation.
+ /// Gets or sets the row definitions for the grid.
///
- private GridLayout.MeasureResult _columnMeasureCache;
+ public RowDefinitions RowDefinitions
+ {
+ get
+ {
+ if (_rowDefinitions == null)
+ {
+ RowDefinitions = new RowDefinitions();
+ }
- ///
- /// Gets the result of the last row measurement.
- /// Use this result to reduce the arrange calculation.
- ///
- private GridLayout.MeasureResult _rowMeasureCache;
+ return _rowDefinitions;
+ }
+ set
+ {
+ _rowDefinitions = value;
+ _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate());
- ///
- /// Gets the row layout as of the last measure.
- ///
- private GridLayout _rowLayoutCache;
+ RowDefinitionsDirty = true;
- ///
- /// Gets the column layout as of the last measure.
- ///
- private GridLayout _columnLayoutCache;
+ if (_rowDefinitions.Count > 0)
+ _definitionsV = _rowDefinitions.Cast().ToArray();
+ else
+ _definitionsV = new DefinitionBase[1] { new RowDefinition() };
+
+ _rowDefinitions.CollectionChanged += (_, e) =>
+ {
+ if (_rowDefinitions.Count == 0)
+ {
+ _definitionsV = new DefinitionBase[1] { new RowDefinition() };
+ }
+ else
+ {
+ _definitionsV = _rowDefinitions.Cast().ToArray();
+ RowDefinitionsDirty = true;
+ }
+ Invalidate();
+ };
+ }
+ }
+
+ private bool IsTrivialGrid => (_definitionsU?.Length <= 1) &&
+ (_definitionsV?.Length <= 1);
///
- /// Measures the grid.
+ /// Content measurement.
///
- /// The available size.
- /// The desired size of the control.
+ /// Constraint
+ /// Desired size
protected override Size MeasureOverride(Size constraint)
{
- // Situation 1/2:
- // If the grid doesn't have any column/row definitions, it behaves like a normal panel.
- // GridLayout supports this situation but we handle this separately for performance.
+ Size gridDesiredSize;
- if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
+ try
{
- var maxWidth = 0.0;
- var maxHeight = 0.0;
- foreach (var child in Children.OfType())
+ if (IsTrivialGrid)
{
- child.Measure(constraint);
- maxWidth = Math.Max(maxWidth, child.DesiredSize.Width);
- maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
+ gridDesiredSize = new Size();
+
+ for (int i = 0, count = Children.Count; i < count; ++i)
+ {
+ var child = Children[i];
+ if (child != null)
+ {
+ child.Measure(constraint);
+ gridDesiredSize = new Size(
+ Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),
+ Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));
+ }
+ }
+ }
+ else
+ {
+ {
+ bool sizeToContentU = double.IsPositiveInfinity(constraint.Width);
+ bool sizeToContentV = double.IsPositiveInfinity(constraint.Height);
+
+ // Clear index information and rounding errors
+ if (RowDefinitionsDirty || ColumnDefinitionsDirty)
+ {
+ if (_definitionIndices != null)
+ {
+ Array.Clear(_definitionIndices, 0, _definitionIndices.Length);
+ _definitionIndices = null;
+ }
+
+ if (UseLayoutRounding)
+ {
+ if (_roundingErrors != null)
+ {
+ Array.Clear(_roundingErrors, 0, _roundingErrors.Length);
+ _roundingErrors = null;
+ }
+ }
+ }
+
+ ValidateColumnDefinitionsStructure();
+ ValidateDefinitionsLayout(_definitionsU, sizeToContentU);
+
+ ValidateRowDefinitionsStructure();
+ ValidateDefinitionsLayout(_definitionsV, sizeToContentV);
+
+ CellsStructureDirty |= (SizeToContentU != sizeToContentU)
+ || (SizeToContentV != sizeToContentV);
+
+ SizeToContentU = sizeToContentU;
+ SizeToContentV = sizeToContentV;
+ }
+
+ ValidateCells();
+
+ Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0);
+
+ MeasureCellsGroup(CellGroup1, constraint, false, false);
+
+ {
+ // after Group1 is measured, only Group3 may have cells belonging to Auto rows.
+ bool canResolveStarsV = !HasGroup3CellsInAutoRows;
+
+ if (canResolveStarsV)
+ {
+ if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
+ MeasureCellsGroup(CellGroup2, constraint, false, false);
+ if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
+ MeasureCellsGroup(CellGroup3, constraint, false, false);
+ }
+ else
+ {
+ // if at least one cell exists in Group2, it must be measured before
+ // StarsU can be resolved.
+ bool canResolveStarsU = CellGroup2 > _cellCache.Length;
+ if (canResolveStarsU)
+ {
+ if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
+ MeasureCellsGroup(CellGroup3, constraint, false, false);
+ if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
+ }
+ else
+ {
+ // This is a revision to the algorithm employed for the cyclic
+ // dependency case described above. We now repeatedly
+ // measure Group3 and Group2 until their sizes settle. We
+ // also use a count heuristic to break a loop in case of one.
+
+ bool hasDesiredSizeUChanged = false;
+ int cnt = 0;
+
+ // Cache Group2MinWidths & Group3MinHeights
+ double[] group2MinSizes = CacheMinSizes(CellGroup2, false);
+ double[] group3MinSizes = CacheMinSizes(CellGroup3, true);
+
+ MeasureCellsGroup(CellGroup2, constraint, false, true);
+
+ do
+ {
+ if (hasDesiredSizeUChanged)
+ {
+ // Reset cached Group3Heights
+ ApplyCachedMinSizes(group3MinSizes, true);
+ }
+
+ if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
+ MeasureCellsGroup(CellGroup3, constraint, false, false);
+
+ // Reset cached Group2Widths
+ ApplyCachedMinSizes(group2MinSizes, false);
+
+ if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
+ MeasureCellsGroup(CellGroup2, constraint,
+ cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged);
+ }
+ while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount);
+ }
+ }
+ }
+
+ MeasureCellsGroup(CellGroup4, constraint, false, false);
+
+ gridDesiredSize = new Size(
+ CalculateDesiredSize(_definitionsU),
+ CalculateDesiredSize(_definitionsV));
}
-
- maxWidth = Math.Min(maxWidth, constraint.Width);
- maxHeight = Math.Min(maxHeight, constraint.Height);
- return new Size(maxWidth, maxHeight);
}
-
- // Situation 2/2:
- // If the grid defines some columns or rows.
- // Debug Tip:
- // - GridLayout doesn't hold any state, so you can drag the debugger execution
- // arrow back to any statements and re-run them without any side-effect.
-
- var measureCache = new Dictionary();
- var (safeColumns, safeRows) = GetSafeColumnRows();
- var columnLayout = new GridLayout(ColumnDefinitions);
- var rowLayout = new GridLayout(RowDefinitions);
- // Note: If a child stays in a * or Auto column/row, use constraint to measure it.
- columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width);
- rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height);
-
- // Calculate measurement.
- var columnResult = columnLayout.Measure(constraint.Width);
- var rowResult = rowLayout.Measure(constraint.Height);
-
- // Use the results of the measurement to measure the rest of the children.
- foreach (var child in Children.OfType())
+ finally
{
- var (column, columnSpan) = safeColumns[child];
- var (row, rowSpan) = safeRows[child];
- var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum();
- var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum();
-
- MeasureOnce(child, new Size(width, height));
}
- // Cache the measure result and return the desired size.
- _columnMeasureCache = columnResult;
- _rowMeasureCache = rowResult;
- _rowLayoutCache = rowLayout;
- _columnLayoutCache = columnLayout;
+ return (gridDesiredSize);
+ }
- if (_sharedSizeHost?.ParticipatesInScope(this) ?? false)
+ private void ValidateColumnDefinitionsStructure()
+ {
+ if (ColumnDefinitionsDirty)
{
- _sharedSizeHost.UpdateMeasureStatus(this, rowResult, columnResult);
+ if (_definitionsU == null)
+ _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
+ ColumnDefinitionsDirty = false;
}
+ }
- return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
-
- // Measure each child only once.
- // If a child has been measured, it will just return the desired size.
- Size MeasureOnce(Control child, Size size)
+ private void ValidateRowDefinitionsStructure()
+ {
+ if (RowDefinitionsDirty)
{
- if (measureCache.TryGetValue(child, out var desiredSize))
- {
- return desiredSize;
- }
+ if (_definitionsV == null)
+ _definitionsV = new DefinitionBase[1] { new RowDefinition() };
- child.Measure(size);
- desiredSize = child.DesiredSize;
- measureCache[child] = desiredSize;
- return desiredSize;
+ RowDefinitionsDirty = false;
}
}
///
- /// Arranges the grid's children.
+ /// Content arrangement.
///
- /// The size allocated to the control.
- /// The space taken.
- protected override Size ArrangeOverride(Size finalSize)
+ /// Arrange size
+ protected override Size ArrangeOverride(Size arrangeSize)
{
- // Situation 1/2:
- // If the grid doesn't have any column/row definitions, it behaves like a normal panel.
- // GridLayout supports this situation but we handle this separately for performance.
-
- if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
+ try
{
- foreach (var child in Children.OfType())
+ if (IsTrivialGrid)
{
- child.Arrange(new Rect(finalSize));
+ for (int i = 0, count = Children.Count; i < count; ++i)
+ {
+ var child = Children[i];
+ if (child != null)
+ {
+ child.Arrange(new Rect(arrangeSize));
+ }
+ }
+ }
+ else
+ {
+ Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0);
+
+ SetFinalSize(_definitionsU, arrangeSize.Width, true);
+ SetFinalSize(_definitionsV, arrangeSize.Height, false);
+
+ for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell)
+ {
+ IControl cell = Children[currentCell];
+ if (cell == null)
+ {
+ continue;
+ }
+
+ int columnIndex = _cellCache[currentCell].ColumnIndex;
+ int rowIndex = _cellCache[currentCell].RowIndex;
+ int columnSpan = _cellCache[currentCell].ColumnSpan;
+ int rowSpan = _cellCache[currentCell].RowSpan;
+
+ Rect cellRect = new Rect(
+ columnIndex == 0 ? 0.0 : _definitionsU[columnIndex].FinalOffset,
+ rowIndex == 0 ? 0.0 : _definitionsV[rowIndex].FinalOffset,
+ GetFinalSizeForRange(_definitionsU, columnIndex, columnSpan),
+ GetFinalSizeForRange(_definitionsV, rowIndex, rowSpan));
+
+ cell.Arrange(cellRect);
+ }
+
+ // update render bound on grid lines renderer visual
+ var gridLinesRenderer = EnsureGridLinesRenderer();
+ if (gridLinesRenderer != null)
+ {
+ gridLinesRenderer.UpdateRenderBounds(arrangeSize);
+ }
}
-
- return finalSize;
- }
-
- // Situation 2/2:
- // If the grid defines some columns or rows.
- // Debug Tip:
- // - GridLayout doesn't hold any state, so you can drag the debugger execution
- // arrow back to any statements and re-run them without any side-effect.
-
- var (safeColumns, safeRows) = GetSafeColumnRows();
- var columnLayout = _columnLayoutCache;
- var rowLayout = _rowLayoutCache;
-
- var rowCache = _rowMeasureCache;
- var columnCache = _columnMeasureCache;
-
- if (_sharedSizeHost?.ParticipatesInScope(this) ?? false)
- {
- (rowCache, columnCache) = _sharedSizeHost.HandleArrange(this, _rowMeasureCache, _columnMeasureCache);
-
- rowCache = rowLayout.Measure(finalSize.Height, rowCache.LeanLengthList);
- columnCache = columnLayout.Measure(finalSize.Width, columnCache.LeanLengthList);
}
-
- // Calculate for arrange result.
- var columnResult = columnLayout.Arrange(finalSize.Width, columnCache);
- var rowResult = rowLayout.Arrange(finalSize.Height, rowCache);
- // Arrange the children.
- foreach (var child in Children.OfType())
+ finally
{
- var (column, columnSpan) = safeColumns[child];
- var (row, rowSpan) = safeRows[child];
- var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]);
- var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]);
- var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]);
- var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]);
- child.Arrange(new Rect(x, y, width, height));
+ SetValid();
}
- // Assign the actual width.
for (var i = 0; i < ColumnDefinitions.Count; i++)
{
- ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i];
+ ColumnDefinitions[i].ActualWidth = GetFinalColumnDefinitionWidth(i);
}
- // Assign the actual height.
for (var i = 0; i < RowDefinitions.Count; i++)
{
- RowDefinitions[i].ActualHeight = rowResult.LengthList[i];
+ RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i);
}
- // Return the render size.
- return finalSize;
+ return (arrangeSize);
}
///
- /// Tests whether this grid belongs to a shared size scope.
+ /// Returns final width for a column.
///
- /// True if the grid is registered in a shared size scope.
- internal bool HasSharedSizeScope()
+ ///
+ /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data.
+ ///
+ internal double GetFinalColumnDefinitionWidth(int columnIndex)
{
- return _sharedSizeHost != null;
+ double value = 0.0;
+
+ // actual value calculations require structure to be up-to-date
+ if (!ColumnDefinitionsDirty)
+ {
+ value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset;
+ if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; }
+ }
+ return (value);
}
///
- /// Called when the SharedSizeScope for a given grid has changed.
- /// Unregisters the grid from it's current scope and finds a new one (if any)
+ /// Returns final height for a row.
///
///
- /// This method, while not efficient, correctly handles nested scopes, with any order of scope changes.
+ /// Used from public RowDefinition ActualHeight. Calculates final height using offset data.
///
- internal void SharedScopeChanged()
+ internal double GetFinalRowDefinitionHeight(int rowIndex)
{
- _sharedSizeHost?.UnegisterGrid(this);
-
- _sharedSizeHost = null;
- var scope = this.GetVisualAncestors().OfType()
- .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty));
+ double value = 0.0;
- if (scope != null)
+ // actual value calculations require structure to be up-to-date
+ if (!RowDefinitionsDirty)
{
- _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty);
- _sharedSizeHost.RegisterGrid(this);
+ value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset;
+ if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; }
}
+ return (value);
+ }
+ ///
+ /// Invalidates grid caches and makes the grid dirty for measure.
+ ///
+ internal void Invalidate()
+ {
+ CellsStructureDirty = true;
InvalidateMeasure();
}
///
- /// Callback when a grid is attached to the visual tree. Finds the innermost SharedSizeScope and registers the grid
- /// in it.
+ /// Lays out cells according to rows and columns, and creates lookup grids.
///
- /// The source of the event.
- /// The event arguments.
- private void Grid_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+ private void ValidateCells()
{
- var scope =
- new Control[] { this }.Concat(this.GetVisualAncestors().OfType())
- .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty));
+ if (!CellsStructureDirty) return;
- if (_sharedSizeHost != null)
- throw new AvaloniaInternalException("Shared size scope already present when attaching to visual tree!");
+ _cellCache = new CellCache[Children.Count];
+ CellGroup1 = int.MaxValue;
+ CellGroup2 = int.MaxValue;
+ CellGroup3 = int.MaxValue;
+ CellGroup4 = int.MaxValue;
- if (scope != null)
+ bool hasStarCellsU = false;
+ bool hasStarCellsV = false;
+ bool hasGroup3CellsInAutoRows = false;
+
+ for (int i = _cellCache.Length - 1; i >= 0; --i)
{
- _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty);
- _sharedSizeHost.RegisterGrid(this);
+ var child = Children[i] as Control;
+
+ if (child == null)
+ {
+ continue;
+ }
+
+ var cell = new CellCache();
+
+ // read indices from the corresponding properties
+ // clamp to value < number_of_columns
+ // column >= 0 is guaranteed by property value validation callback
+ cell.ColumnIndex = Math.Min(GetColumn(child), _definitionsU.Length - 1);
+
+ // clamp to value < number_of_rows
+ // row >= 0 is guaranteed by property value validation callback
+ cell.RowIndex = Math.Min(GetRow(child), _definitionsV.Length - 1);
+
+ // read span properties
+ // clamp to not exceed beyond right side of the grid
+ // column_span > 0 is guaranteed by property value validation callback
+ cell.ColumnSpan = Math.Min(GetColumnSpan(child), _definitionsU.Length - cell.ColumnIndex);
+
+ // clamp to not exceed beyond bottom side of the grid
+ // row_span > 0 is guaranteed by property value validation callback
+ cell.RowSpan = Math.Min(GetRowSpan(child), _definitionsV.Length - cell.RowIndex);
+
+ Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < _definitionsU.Length);
+ Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < _definitionsV.Length);
+
+ //
+ // calculate and cache length types for the child
+ //
+ cell.SizeTypeU = GetLengthTypeForRange(_definitionsU, cell.ColumnIndex, cell.ColumnSpan);
+ cell.SizeTypeV = GetLengthTypeForRange(_definitionsV, cell.RowIndex, cell.RowSpan);
+
+ hasStarCellsU |= cell.IsStarU;
+ hasStarCellsV |= cell.IsStarV;
+
+ //
+ // distribute cells into four groups.
+ //
+ if (!cell.IsStarV)
+ {
+ if (!cell.IsStarU)
+ {
+ cell.Next = CellGroup1;
+ CellGroup1 = i;
+ }
+ else
+ {
+ cell.Next = CellGroup3;
+ CellGroup3 = i;
+
+ // remember if this cell belongs to auto row
+ hasGroup3CellsInAutoRows |= cell.IsAutoV;
+ }
+ }
+ else
+ {
+ if (cell.IsAutoU
+ // note below: if spans through Star column it is NOT Auto
+ && !cell.IsStarU)
+ {
+ cell.Next = CellGroup2;
+ CellGroup2 = i;
+ }
+ else
+ {
+ cell.Next = CellGroup4;
+ CellGroup4 = i;
+ }
+ }
+
+ _cellCache[i] = cell;
}
+
+ HasStarCellsU = hasStarCellsU;
+ HasStarCellsV = hasStarCellsV;
+ HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows;
+
+ CellsStructureDirty = false;
}
///
- /// Callback when a grid is detached from the visual tree. Unregisters the grid from its SharedSizeScope if any.
+ /// Validates layout time size type information on given array of definitions.
+ /// Sets MinSize and MeasureSizes.
///
- /// The source of the event.
- /// The event arguments.
- private void Grid_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+ /// Array of definitions to update.
+ /// if "true" then star definitions are treated as Auto.
+ private void ValidateDefinitionsLayout(
+ DefinitionBase[] definitions,
+ bool treatStarAsAuto)
{
- _sharedSizeHost?.UnegisterGrid(this);
- _sharedSizeHost = null;
- }
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ // Reset minimum size.
+ definitions[i].MinSize = 0;
+ double userMinSize = definitions[i].UserMinSize;
+ double userMaxSize = definitions[i].UserMaxSize;
+ double userSize = 0;
- ///
- /// Get the safe column/columnspan and safe row/rowspan.
- /// This method ensures that none of the children has a column/row outside the bounds of the definitions.
- ///
- [Pure]
- private (Dictionary safeColumns,
- Dictionary safeRows) GetSafeColumnRows()
- {
- var columnCount = ColumnDefinitions.Count;
- var rowCount = RowDefinitions.Count;
- columnCount = columnCount == 0 ? 1 : columnCount;
- rowCount = rowCount == 0 ? 1 : rowCount;
- var safeColumns = Children.OfType().ToDictionary(child => child,
- child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child)));
- var safeRows = Children.OfType().ToDictionary(child => child,
- child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child)));
- return (safeColumns, safeRows);
+ switch (definitions[i].UserSize.GridUnitType)
+ {
+ case (GridUnitType.Pixel):
+ definitions[i].SizeType = LayoutTimeSizeType.Pixel;
+ userSize = definitions[i].UserSize.Value;
+
+ // this was brought with NewLayout and defeats squishy behavior
+ userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
+ break;
+ case (GridUnitType.Auto):
+ definitions[i].SizeType = LayoutTimeSizeType.Auto;
+ userSize = double.PositiveInfinity;
+ break;
+ case (GridUnitType.Star):
+ if (treatStarAsAuto)
+ {
+ definitions[i].SizeType = LayoutTimeSizeType.Auto;
+ userSize = double.PositiveInfinity;
+ }
+ else
+ {
+ definitions[i].SizeType = LayoutTimeSizeType.Star;
+ userSize = double.PositiveInfinity;
+ }
+ break;
+ default:
+ Debug.Assert(false);
+ break;
+ }
+
+ definitions[i].UpdateMinSize(userMinSize);
+ definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
+ }
}
- ///
- /// Gets the safe row/column and rowspan/columnspan for a specified range.
- /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside.
- ///
- /// The row or column count.
- /// The row or column that the user assigned.
- /// The rowspan or columnspan that the user assigned.
- /// The safe row/column and rowspan/columnspan.
- [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan)
+ private double[] CacheMinSizes(int cellsHead, bool isRows)
{
- var index = userIndex;
- var span = userSpan;
+ double[] minSizes = isRows ? new double[_definitionsV.Length]
+ : new double[_definitionsU.Length];
- if (index < 0)
+ for (int j = 0; j < minSizes.Length; j++)
{
- span = index + span;
- index = 0;
+ minSizes[j] = -1;
}
- if (span <= 0)
+ int i = cellsHead;
+ do
{
- span = 1;
- }
+ if (isRows)
+ {
+ minSizes[_cellCache[i].RowIndex] = _definitionsV[_cellCache[i].RowIndex].MinSize;
+ }
+ else
+ {
+ minSizes[_cellCache[i].ColumnIndex] = _definitionsU[_cellCache[i].ColumnIndex].MinSize;
+ }
- if (userIndex >= length)
- {
- index = length - 1;
- span = 1;
- }
- else if (userIndex + userSpan > length)
+ i = _cellCache[i].Next;
+ } while (i < _cellCache.Length);
+
+ return minSizes;
+ }
+
+ private void ApplyCachedMinSizes(double[] minSizes, bool isRows)
+ {
+ for (int i = 0; i < minSizes.Length; i++)
{
- span = length - userIndex;
+ if (MathUtilities.GreaterThanOrClose(minSizes[i], 0))
+ {
+ if (isRows)
+ {
+ _definitionsV[i].MinSize = minSizes[i];
+ }
+ else
+ {
+ _definitionsU[i].MinSize = minSizes[i];
+ }
+ }
}
+ }
- return (index, span);
+ private void MeasureCellsGroup(
+ int cellsHead,
+ Size referenceSize,
+ bool ignoreDesiredSizeU,
+ bool forceInfinityV)
+ {
+ bool unusedHasDesiredSizeUChanged;
+ MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU,
+ forceInfinityV, out unusedHasDesiredSizeUChanged);
}
- private static int ValidateColumn(AvaloniaObject o, int value)
+ ///
+ /// Measures one group of cells.
+ ///
+ /// Head index of the cells chain.
+ /// Reference size for spanned cells
+ /// calculations.
+ /// When "true" cells' desired
+ /// width is not registered in columns.
+ /// Passed through to MeasureCell.
+ /// When "true" cells' desired height is not registered in rows.
+ private void MeasureCellsGroup(
+ int cellsHead,
+ Size referenceSize,
+ bool ignoreDesiredSizeU,
+ bool forceInfinityV,
+ out bool hasDesiredSizeUChanged)
{
- if (value < 0)
+ hasDesiredSizeUChanged = false;
+
+ if (cellsHead >= _cellCache.Length)
{
- throw new ArgumentException("Invalid Grid.Column value.");
+ return;
}
- return value;
+ Hashtable spanStore = null;
+ bool ignoreDesiredSizeV = forceInfinityV;
+
+ int i = cellsHead;
+ do
+ {
+ double oldWidth = Children[i].DesiredSize.Width;
+
+ MeasureCell(i, forceInfinityV);
+
+ hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width);
+
+ if (!ignoreDesiredSizeU)
+ {
+ if (_cellCache[i].ColumnSpan == 1)
+ {
+ _definitionsU[_cellCache[i].ColumnIndex]
+ .UpdateMinSize(Math.Min(Children[i].DesiredSize.Width,
+ _definitionsU[_cellCache[i].ColumnIndex].UserMaxSize));
+ }
+ else
+ {
+ RegisterSpan(
+ ref spanStore,
+ _cellCache[i].ColumnIndex,
+ _cellCache[i].ColumnSpan,
+ true,
+ Children[i].DesiredSize.Width);
+ }
+ }
+
+ if (!ignoreDesiredSizeV)
+ {
+ if (_cellCache[i].RowSpan == 1)
+ {
+ _definitionsV[_cellCache[i].RowIndex]
+ .UpdateMinSize(Math.Min(Children[i].DesiredSize.Height,
+ _definitionsV[_cellCache[i].RowIndex].UserMaxSize));
+ }
+ else
+ {
+ RegisterSpan(
+ ref spanStore,
+ _cellCache[i].RowIndex,
+ _cellCache[i].RowSpan,
+ false,
+ Children[i].DesiredSize.Height);
+ }
+ }
+
+ i = _cellCache[i].Next;
+ } while (i < _cellCache.Length);
+
+ if (spanStore != null)
+ {
+ foreach (DictionaryEntry e in spanStore)
+ {
+ SpanKey key = (SpanKey)e.Key;
+ double requestedSize = (double)e.Value;
+
+ EnsureMinSizeInDefinitionRange(
+ key.U ? _definitionsU : _definitionsV,
+ key.Start,
+ key.Count,
+ requestedSize,
+ key.U ? referenceSize.Width : referenceSize.Height);
+ }
+ }
}
- private static int ValidateRow(AvaloniaObject o, int value)
+ ///
+ /// Helper method to register a span information for delayed processing.
+ ///
+ /// Reference to a hashtable object used as storage.
+ /// Span starting index.
+ /// Span count.
+ /// true if this is a column span. false if this is a row span.
+ /// Value to store. If an entry already exists the biggest value is stored.
+ private static void RegisterSpan(
+ ref Hashtable store,
+ int start,
+ int count,
+ bool u,
+ double value)
{
- if (value < 0)
+ if (store == null)
{
- throw new ArgumentException("Invalid Grid.Row value.");
+ store = new Hashtable();
}
- return value;
+ SpanKey key = new SpanKey(start, count, u);
+ object o = store[key];
+
+ if (o == null
+ || value > (double)o)
+ {
+ store[key] = value;
+ }
}
///
- /// Called when the value of changes for a control.
+ /// Takes care of measuring a single cell.
///
- /// The control that triggered the change.
- /// Change arguments.
- private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2)
+ /// Index of the cell to measure.
+ /// If "true" then cell is always
+ /// calculated to infinite height.
+ private void MeasureCell(
+ int cell,
+ bool forceInfinityV)
{
- var shouldDispose = (arg2.OldValue is bool d) && d;
- if (shouldDispose)
+ double cellMeasureWidth;
+ double cellMeasureHeight;
+
+ if (_cellCache[cell].IsAutoU
+ && !_cellCache[cell].IsStarU)
{
- var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost;
- if (host == null)
- throw new AvaloniaInternalException("SharedScopeHost wasn't set when IsSharedSizeScope was true!");
- host.Dispose();
- source.ClearValue(s_sharedSizeScopeHostProperty);
+ // if cell belongs to at least one Auto column and not a single Star column
+ // then it should be calculated "to content", thus it is possible to "shortcut"
+ // calculations and simply assign PositiveInfinity here.
+ cellMeasureWidth = double.PositiveInfinity;
}
-
- var shouldAssign = (arg2.NewValue is bool a) && a;
- if (shouldAssign)
+ else
{
- if (source.GetValue(s_sharedSizeScopeHostProperty) != null)
- throw new AvaloniaInternalException("SharedScopeHost was already set when IsSharedSizeScope is only now being set to true!");
- source.SetValue(s_sharedSizeScopeHostProperty, new SharedSizeScopeHost());
+ // otherwise...
+ cellMeasureWidth = GetMeasureSizeForRange(
+ _definitionsU,
+ _cellCache[cell].ColumnIndex,
+ _cellCache[cell].ColumnSpan);
}
- // if the scope has changed, notify the descendant grids that they need to update.
- if (source.GetVisualRoot() != null && shouldAssign || shouldDispose)
+ if (forceInfinityV)
{
- var participatingGrids = new[] { source }.Concat(source.GetVisualDescendants()).OfType();
+ cellMeasureHeight = double.PositiveInfinity;
+ }
+ else if (_cellCache[cell].IsAutoV
+ && !_cellCache[cell].IsStarV)
+ {
+ // if cell belongs to at least one Auto row and not a single Star row
+ // then it should be calculated "to content", thus it is possible to "shortcut"
+ // calculations and simply assign PositiveInfinity here.
+ cellMeasureHeight = double.PositiveInfinity;
+ }
+ else
+ {
+ cellMeasureHeight = GetMeasureSizeForRange(
+ _definitionsV,
+ _cellCache[cell].RowIndex,
+ _cellCache[cell].RowSpan);
+ }
+
+ var child = Children[cell];
- foreach (var grid in participatingGrids)
- grid.SharedScopeChanged();
+ if (child != null)
+ {
+ Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
+ child.Measure(childConstraint);
}
}
+
+ ///
+ /// Calculates one dimensional measure size for given definitions' range.
+ ///
+ /// Source array of definitions to read values from.
+ /// Starting index of the range.
+ /// Number of definitions included in the range.
+ /// Calculated measure size.
+ ///
+ /// For "Auto" definitions MinWidth is used in place of PreferredSize.
+ ///
+ private double GetMeasureSizeForRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count)
+ {
+ Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
+
+ double measureSize = 0;
+ int i = start + count - 1;
+
+ do
+ {
+ measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto)
+ ? definitions[i].MinSize
+ : definitions[i].MeasureSize;
+ } while (--i >= start);
+
+ return (measureSize);
+ }
+
+ ///
+ /// Accumulates length type information for given definition's range.
+ ///
+ /// Source array of definitions to read values from.
+ /// Starting index of the range.
+ /// Number of definitions included in the range.
+ /// Length type for given range.
+ private LayoutTimeSizeType GetLengthTypeForRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count)
+ {
+ Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
+
+ LayoutTimeSizeType lengthType = LayoutTimeSizeType.None;
+ int i = start + count - 1;
+
+ do
+ {
+ lengthType |= definitions[i].SizeType;
+ } while (--i >= start);
+
+ return (lengthType);
+ }
+
+ ///
+ /// Distributes min size back to definition array's range.
+ ///
+ /// Start of the range.
+ /// Number of items in the range.
+ /// Minimum size that should "fit" into the definitions range.
+ /// Definition array receiving distribution.
+ /// Size used to resolve percentages.
+ private void EnsureMinSizeInDefinitionRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count,
+ double requestedSize,
+ double percentReferenceSize)
+ {
+ Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length);
+
+ // avoid processing when asked to distribute "0"
+ if (!MathUtilities.IsZero(requestedSize))
+ {
+ DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
+ int end = start + count;
+ int autoDefinitionsCount = 0;
+ double rangeMinSize = 0;
+ double rangePreferredSize = 0;
+ double rangeMaxSize = 0;
+ double maxMaxSize = 0; // maximum of maximum sizes
+
+ // first accumulate the necessary information:
+ // a) sum up the sizes in the range;
+ // b) count the number of auto definitions in the range;
+ // c) initialize temp array
+ // d) cache the maximum size into SizeCache
+ // e) accumulate max of max sizes
+ for (int i = start; i < end; ++i)
+ {
+ double minSize = definitions[i].MinSize;
+ double preferredSize = definitions[i].PreferredSize;
+ double maxSize = Math.Max(definitions[i].UserMaxSize, minSize);
+
+ rangeMinSize += minSize;
+ rangePreferredSize += preferredSize;
+ rangeMaxSize += maxSize;
+
+ definitions[i].SizeCache = maxSize;
+
+ // sanity check: no matter what, but min size must always be the smaller;
+ // max size must be the biggest; and preferred should be in between
+ Debug.Assert(minSize <= preferredSize
+ && preferredSize <= maxSize
+ && rangeMinSize <= rangePreferredSize
+ && rangePreferredSize <= rangeMaxSize);
+
+ if (maxMaxSize < maxSize) maxMaxSize = maxSize;
+ if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++;
+ tempDefinitions[i - start] = definitions[i];
+ }
+
+ // avoid processing if the range already big enough
+ if (requestedSize > rangeMinSize)
+ {
+ if (requestedSize <= rangePreferredSize)
+ {
+ //
+ // requestedSize fits into preferred size of the range.
+ // distribute according to the following logic:
+ // * do not distribute into auto definitions - they should continue to stay "tight";
+ // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size.
+ //
+ // in order to achieve that, definitions are sorted in a way that all auto definitions
+ // are first, then definitions follow ascending order with PreferredSize as the key of sorting.
+ //
+ double sizeToDistribute;
+ int i;
+
+ Array.Sort(tempDefinitions, 0, count, _spanPreferredDistributionOrderComparer);
+ for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i)
+ {
+ // sanity check: only auto definitions allowed in this loop
+ Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
+
+ // adjust sizeToDistribute value by subtracting auto definition min size
+ sizeToDistribute -= (tempDefinitions[i].MinSize);
+ }
+
+ for (; i < count; ++i)
+ {
+ // sanity check: no auto definitions allowed in this loop
+ Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
+
+ double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize);
+ if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); }
+ sizeToDistribute -= newMinSize;
+ }
+
+ // sanity check: requested size must all be distributed
+ Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
+ }
+ else if (requestedSize <= rangeMaxSize)
+ {
+ //
+ // requestedSize bigger than preferred size, but fit into max size of the range.
+ // distribute according to the following logic:
+ // * do not distribute into auto definitions, if possible - they should continue to stay "tight";
+ // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size.
+ //
+ // in order to achieve that, definitions are sorted in a way that all non-auto definitions
+ // are last, then definitions follow ascending order with MaxSize as the key of sorting.
+ //
+ double sizeToDistribute;
+ int i;
+
+ Array.Sort(tempDefinitions, 0, count, _spanMaxDistributionOrderComparer);
+ for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i)
+ {
+ // sanity check: no auto definitions allowed in this loop
+ Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
+
+ double preferredSize = tempDefinitions[i].PreferredSize;
+ double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i);
+ tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
+ sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
+ }
+
+ for (; i < count; ++i)
+ {
+ // sanity check: only auto definitions allowed in this loop
+ Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
+
+ double preferredSize = tempDefinitions[i].MinSize;
+ double newMinSize = preferredSize + sizeToDistribute / (count - i);
+ tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
+ sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
+ }
+
+ // sanity check: requested size must all be distributed
+ Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
+ }
+ else
+ {
+ //
+ // requestedSize bigger than max size of the range.
+ // distribute according to the following logic:
+ // * for all definitions distribute to equi-size min sizes.
+ //
+ double equalSize = requestedSize / count;
+
+ if (equalSize < maxMaxSize
+ && !MathUtilities.AreClose(equalSize, maxMaxSize))
+ {
+ // equi-size is less than maximum of maxSizes.
+ // in this case distribute so that smaller definitions grow faster than
+ // bigger ones.
+ double totalRemainingSize = maxMaxSize * count - rangeMaxSize;
+ double sizeToDistribute = requestedSize - rangeMaxSize;
+
+ // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers
+ Debug.Assert(!double.IsInfinity(totalRemainingSize)
+ && !double.IsNaN(totalRemainingSize)
+ && totalRemainingSize > 0
+ && !double.IsInfinity(sizeToDistribute)
+ && !double.IsNaN(sizeToDistribute)
+ && sizeToDistribute > 0);
+
+ for (int i = 0; i < count; ++i)
+ {
+ double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize;
+ tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize);
+ }
+ }
+ else
+ {
+ //
+ // equi-size is greater or equal to maximum of max sizes.
+ // all definitions receive equalSize as their mim sizes.
+ //
+ for (int i = 0; i < count; ++i)
+ {
+ tempDefinitions[i].UpdateMinSize(equalSize);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // new implementation as of 4.7. Several improvements:
+ // 1. Allocate to *-defs hitting their min or max constraints, before allocating
+ // to other *-defs. A def that hits its min uses more space than its
+ // proportional share, reducing the space available to everyone else.
+ // The legacy algorithm deducted this space only from defs processed
+ // after the min; the new algorithm deducts it proportionally from all
+ // defs. This avoids the "*-defs exceed available space" problem,
+ // and other related problems where *-defs don't receive proportional
+ // allocations even though no constraints are preventing it.
+ // 2. When multiple defs hit min or max, resolve the one with maximum
+ // discrepancy (defined below). This avoids discontinuities - small
+ // change in available space resulting in large change to one def's allocation.
+ // 3. Correct handling of large *-values, including Infinity.
+
+ ///
+ /// Resolves Star's for given array of definitions.
+ ///
+ /// Array of definitions to resolve stars.
+ /// All available size.
+ ///
+ /// Must initialize LayoutSize for all Star entries in given array of definitions.
+ ///
+ private void ResolveStar(
+ DefinitionBase[] definitions,
+ double availableSize)
+ {
+ int defCount = definitions.Length;
+ DefinitionBase[] tempDefinitions = TempDefinitions;
+ int minCount = 0, maxCount = 0;
+ double takenSize = 0;
+ double totalStarWeight = 0.0;
+ int starCount = 0; // number of unresolved *-definitions
+ double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
+
+ // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
+ double maxStar = 0.0;
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+
+ if (def.SizeType == LayoutTimeSizeType.Star)
+ {
+ ++starCount;
+ def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3"
+ if (def.UserSize.Value > maxStar)
+ {
+ maxStar = def.UserSize.Value;
+ }
+ }
+ }
+
+ if (double.IsPositiveInfinity(maxStar))
+ {
+ // negative scale means one or more of the weights was Infinity
+ scale = -1.0;
+ }
+ else if (starCount > 0)
+ {
+ // if maxStar * starCount > double.Max, summing all the weights could cause
+ // floating-point overflow. To avoid that, scale the weights by a factor to keep
+ // the sum within limits. Choose a power of 2, to preserve precision.
+ double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0));
+ if (power < 0.0)
+ {
+ scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
+ }
+ }
+
+ // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
+ // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
+ // More explanation below...
+ for (bool runPhase2and3 = true; runPhase2and3;)
+ {
+ // Phase 2. Compute total *-weight W and available space S.
+ // For *-items that have Min or Max constraints, compute the ratios used to decide
+ // whether proportional space is too big or too small and add the item to the
+ // corresponding list. (The "min" list is in the first half of tempDefinitions,
+ // the "max" list in the second half. TempDefinitions has capacity at least
+ // 2*defCount, so there's room for both lists.)
+ totalStarWeight = 0.0;
+ takenSize = 0.0;
+ minCount = maxCount = 0;
+
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+
+ switch (def.SizeType)
+ {
+ case (LayoutTimeSizeType.Auto):
+ takenSize += definitions[i].MinSize;
+ break;
+ case (LayoutTimeSizeType.Pixel):
+ takenSize += def.MeasureSize;
+ break;
+ case (LayoutTimeSizeType.Star):
+ if (def.MeasureSize < 0.0)
+ {
+ takenSize += -def.MeasureSize; // already resolved
+ }
+ else
+ {
+ double starWeight = StarWeight(def, scale);
+ totalStarWeight += starWeight;
+
+ if (def.MinSize > 0.0)
+ {
+ // store ratio w/min in MeasureSize (for now)
+ tempDefinitions[minCount++] = def;
+ def.MeasureSize = starWeight / def.MinSize;
+ }
+
+ double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize);
+ if (!double.IsPositiveInfinity(effectiveMaxSize))
+ {
+ // store ratio w/max in SizeCache (for now)
+ tempDefinitions[defCount + maxCount++] = def;
+ def.SizeCache = starWeight / effectiveMaxSize;
+ }
+ }
+ break;
+ }
+ }
+
+ // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
+ int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
+ double takenStarWeight = 0.0;
+ double remainingAvailableSize = availableSize - takenSize;
+ double remainingStarWeight = totalStarWeight - takenStarWeight;
+ Array.Sort(tempDefinitions, 0, minCount, _minRatioComparer);
+ Array.Sort(tempDefinitions, defCount, maxCount, _maxRatioComparer);
+
+ while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
+ {
+ // the calculation
+ // remainingStarWeight = totalStarWeight - takenStarWeight
+ // is subject to catastrophic cancellation if the two terms are nearly equal,
+ // which leads to meaningless results. Check for that, and recompute from
+ // the remaining definitions. [This leads to quadratic behavior in really
+ // pathological cases - but they'd never arise in practice.]
+ const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
+ if (remainingStarWeight < totalStarWeight * starFactor)
+ {
+ takenStarWeight = 0.0;
+ totalStarWeight = 0.0;
+
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+ if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0)
+ {
+ totalStarWeight += StarWeight(def, scale);
+ }
+ }
+
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+ }
+
+ double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : double.PositiveInfinity;
+ double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0;
+
+ // choose the def with larger ratio to the current proportion ("max discrepancy")
+ double proportion = remainingStarWeight / remainingAvailableSize;
+ bool? chooseMin = Choose(minRatio, maxRatio, proportion);
+
+ // if no def was chosen, advance to phase 4; the current proportion doesn't
+ // conflict with any min or max values.
+ if (!(chooseMin.HasValue))
+ {
+ break;
+ }
+
+ // get the chosen definition and its resolved size
+ DefinitionBase resolvedDef;
+ double resolvedSize;
+ if (chooseMin == true)
+ {
+ resolvedDef = tempDefinitions[minCount - 1];
+ resolvedSize = resolvedDef.MinSize;
+ --minCount;
+ }
+ else
+ {
+ resolvedDef = tempDefinitions[defCount + maxCount - 1];
+ resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
+ --maxCount;
+ }
+
+ // resolve the chosen def, deduct its contributions from W and S.
+ // Defs resolved in phase 3 are marked by storing the negative of their resolved
+ // size in MeasureSize, to distinguish them from a pending def.
+ takenSize += resolvedSize;
+ resolvedDef.MeasureSize = -resolvedSize;
+ takenStarWeight += StarWeight(resolvedDef, scale);
+ --starCount;
+
+ remainingAvailableSize = availableSize - takenSize;
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+
+ // advance to the next candidate defs, removing ones that have been resolved.
+ // Both counts are advanced, as a def might appear in both lists.
+ while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0)
+ {
+ --minCount;
+ tempDefinitions[minCount] = null;
+ }
+ while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0)
+ {
+ --maxCount;
+ tempDefinitions[defCount + maxCount] = null;
+ }
+ }
+
+ // decide whether to run Phase2 and Phase3 again. There are 3 cases:
+ // 1. There is space available, and *-defs remaining. This is the
+ // normal case - move on to Phase 4 to allocate the remaining
+ // space proportionally to the remaining *-defs.
+ // 2. There is space available, but no *-defs. This implies at least one
+ // def was resolved as 'max', taking less space than its proportion.
+ // If there are also 'min' defs, reconsider them - we can give
+ // them more space. If not, all the *-defs are 'max', so there's
+ // no way to use all the available space.
+ // 3. We allocated too much space. This implies at least one def was
+ // resolved as 'min'. If there are also 'max' defs, reconsider
+ // them, otherwise the over-allocation is an inevitable consequence
+ // of the given min constraints.
+ // Note that if we return to Phase2, at least one *-def will have been
+ // resolved. This guarantees we don't run Phase2+3 infinitely often.
+ runPhase2and3 = false;
+ if (starCount == 0 && takenSize < availableSize)
+ {
+ // if no *-defs remain and we haven't allocated all the space, reconsider the defs
+ // resolved as 'min'. Their allocation can be increased to make up the gap.
+ for (int i = minCount; i < minCountPhase2; ++i)
+ {
+ DefinitionBase def = tempDefinitions[i];
+ if (def != null)
+ {
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+
+ if (takenSize > availableSize)
+ {
+ // if we've allocated too much space, reconsider the defs
+ // resolved as 'max'. Their allocation can be decreased to make up the gap.
+ for (int i = maxCount; i < maxCountPhase2; ++i)
+ {
+ DefinitionBase def = tempDefinitions[defCount + i];
+ if (def != null)
+ {
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+ }
+
+ // Phase 4. Resolve the remaining defs proportionally.
+ starCount = 0;
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+
+ if (def.SizeType == LayoutTimeSizeType.Star)
+ {
+ if (def.MeasureSize < 0.0)
+ {
+ // this def was resolved in phase 3 - fix up its measure size
+ def.MeasureSize = -def.MeasureSize;
+ }
+ else
+ {
+ // this def needs resolution, add it to the list, sorted by *-weight
+ tempDefinitions[starCount++] = def;
+ def.MeasureSize = StarWeight(def, scale);
+ }
+ }
+ }
+
+ if (starCount > 0)
+ {
+ Array.Sort(tempDefinitions, 0, starCount, _starWeightComparer);
+
+ // compute the partial sums of *-weight, in increasing order of weight
+ // for minimal loss of precision.
+ totalStarWeight = 0.0;
+ for (int i = 0; i < starCount; ++i)
+ {
+ DefinitionBase def = tempDefinitions[i];
+ totalStarWeight += def.MeasureSize;
+ def.SizeCache = totalStarWeight;
+ }
+
+ // resolve the defs, in decreasing order of weight
+ for (int i = starCount - 1; i >= 0; --i)
+ {
+ DefinitionBase def = tempDefinitions[i];
+ double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
+
+ // min and max should have no effect by now, but just in case...
+ resolvedSize = Math.Min(resolvedSize, def.UserMaxSize);
+ resolvedSize = Math.Max(def.MinSize, resolvedSize);
+
+ def.MeasureSize = resolvedSize;
+ takenSize += resolvedSize;
+ }
+ }
+ }
+
+ ///
+ /// Calculates desired size for given array of definitions.
+ ///
+ /// Array of definitions to use for calculations.
+ /// Desired size.
+ private double CalculateDesiredSize(
+ DefinitionBase[] definitions)
+ {
+ double desiredSize = 0;
+
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ desiredSize += definitions[i].MinSize;
+ }
+
+ return (desiredSize);
+ }
+
+ ///
+ /// Calculates and sets final size for all definitions in the given array.
+ ///
+ /// Array of definitions to process.
+ /// Final size to lay out to.
+ /// True if sizing row definitions, false for columns
+ private void SetFinalSize(
+ DefinitionBase[] definitions,
+ double finalSize,
+ bool columns)
+ {
+ int defCount = definitions.Length;
+ int[] definitionIndices = DefinitionIndices;
+ int minCount = 0, maxCount = 0;
+ double takenSize = 0.0;
+ double totalStarWeight = 0.0;
+ int starCount = 0; // number of unresolved *-definitions
+ double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
+
+ // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
+ double maxStar = 0.0;
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+
+ if (def.UserSize.IsStar)
+ {
+ ++starCount;
+ def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3"
+ if (def.UserSize.Value > maxStar)
+ {
+ maxStar = def.UserSize.Value;
+ }
+ }
+ }
+
+ if (double.IsPositiveInfinity(maxStar))
+ {
+ // negative scale means one or more of the weights was Infinity
+ scale = -1.0;
+ }
+ else if (starCount > 0)
+ {
+ // if maxStar * starCount > double.Max, summing all the weights could cause
+ // floating-point overflow. To avoid that, scale the weights by a factor to keep
+ // the sum within limits. Choose a power of 2, to preserve precision.
+ double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0));
+ if (power < 0.0)
+ {
+ scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
+ }
+ }
+
+
+ // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
+ // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
+ // More explanation below...
+ for (bool runPhase2and3 = true; runPhase2and3;)
+ {
+ // Phase 2. Compute total *-weight W and available space S.
+ // For *-items that have Min or Max constraints, compute the ratios used to decide
+ // whether proportional space is too big or too small and add the item to the
+ // corresponding list. (The "min" list is in the first half of definitionIndices,
+ // the "max" list in the second half. DefinitionIndices has capacity at least
+ // 2*defCount, so there's room for both lists.)
+ totalStarWeight = 0.0;
+ takenSize = 0.0;
+ minCount = maxCount = 0;
+
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+
+ if (def.UserSize.IsStar)
+ {
+ // Debug.Assert(!def.IsShared, "*-defs cannot be shared");
+
+ if (def.MeasureSize < 0.0)
+ {
+ takenSize += -def.MeasureSize; // already resolved
+ }
+ else
+ {
+ double starWeight = StarWeight(def, scale);
+ totalStarWeight += starWeight;
+
+ if (def.MinSize > 0.0)
+ {
+ // store ratio w/min in MeasureSize (for now)
+ definitionIndices[minCount++] = i;
+ def.MeasureSize = starWeight / def.MinSize;
+ }
+
+ double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize);
+ if (!double.IsPositiveInfinity(effectiveMaxSize))
+ {
+ // store ratio w/max in SizeCache (for now)
+ definitionIndices[defCount + maxCount++] = i;
+ def.SizeCache = starWeight / effectiveMaxSize;
+ }
+ }
+ }
+ else
+ {
+ double userSize = 0;
+
+ switch (def.UserSize.GridUnitType)
+ {
+ case (GridUnitType.Pixel):
+ userSize = def.UserSize.Value;
+ break;
+
+ case (GridUnitType.Auto):
+ userSize = def.MinSize;
+ break;
+ }
+
+ double userMaxSize;
+
+ // if (def.IsShared)
+ // {
+ // // overriding userMaxSize effectively prevents squishy-ness.
+ // // this is a "solution" to avoid shared definitions from been sized to
+ // // different final size at arrange time, if / when different grids receive
+ // // different final sizes.
+ // userMaxSize = userSize;
+ // }
+ // else
+ // {
+ userMaxSize = def.UserMaxSize;
+ // }
+
+ def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize));
+ takenSize += def.SizeCache;
+ }
+ }
+
+ // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
+ int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
+ double takenStarWeight = 0.0;
+ double remainingAvailableSize = finalSize - takenSize;
+ double remainingStarWeight = totalStarWeight - takenStarWeight;
+
+ MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions);
+ Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer);
+ MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions);
+ Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer);
+
+ while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
+ {
+ // the calculation
+ // remainingStarWeight = totalStarWeight - takenStarWeight
+ // is subject to catastrophic cancellation if the two terms are nearly equal,
+ // which leads to meaningless results. Check for that, and recompute from
+ // the remaining definitions. [This leads to quadratic behavior in really
+ // pathological cases - but they'd never arise in practice.]
+ const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
+ if (remainingStarWeight < totalStarWeight * starFactor)
+ {
+ takenStarWeight = 0.0;
+ totalStarWeight = 0.0;
+
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+ if (def.UserSize.IsStar && def.MeasureSize > 0.0)
+ {
+ totalStarWeight += StarWeight(def, scale);
+ }
+ }
+
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+ }
+
+ double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : double.PositiveInfinity;
+ double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0;
+
+ // choose the def with larger ratio to the current proportion ("max discrepancy")
+ double proportion = remainingStarWeight / remainingAvailableSize;
+ bool? chooseMin = Choose(minRatio, maxRatio, proportion);
+
+ // if no def was chosen, advance to phase 4; the current proportion doesn't
+ // conflict with any min or max values.
+ if (!(chooseMin.HasValue))
+ {
+ break;
+ }
+
+ // get the chosen definition and its resolved size
+ int resolvedIndex;
+ DefinitionBase resolvedDef;
+ double resolvedSize;
+ if (chooseMin == true)
+ {
+ resolvedIndex = definitionIndices[minCount - 1];
+ resolvedDef = definitions[resolvedIndex];
+ resolvedSize = resolvedDef.MinSize;
+ --minCount;
+ }
+ else
+ {
+ resolvedIndex = definitionIndices[defCount + maxCount - 1];
+ resolvedDef = definitions[resolvedIndex];
+ resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
+ --maxCount;
+ }
+
+ // resolve the chosen def, deduct its contributions from W and S.
+ // Defs resolved in phase 3 are marked by storing the negative of their resolved
+ // size in MeasureSize, to distinguish them from a pending def.
+ takenSize += resolvedSize;
+ resolvedDef.MeasureSize = -resolvedSize;
+ takenStarWeight += StarWeight(resolvedDef, scale);
+ --starCount;
+
+ remainingAvailableSize = finalSize - takenSize;
+ remainingStarWeight = totalStarWeight - takenStarWeight;
+
+ // advance to the next candidate defs, removing ones that have been resolved.
+ // Both counts are advanced, as a def might appear in both lists.
+ while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0)
+ {
+ --minCount;
+ definitionIndices[minCount] = -1;
+ }
+ while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0)
+ {
+ --maxCount;
+ definitionIndices[defCount + maxCount] = -1;
+ }
+ }
+
+ // decide whether to run Phase2 and Phase3 again. There are 3 cases:
+ // 1. There is space available, and *-defs remaining. This is the
+ // normal case - move on to Phase 4 to allocate the remaining
+ // space proportionally to the remaining *-defs.
+ // 2. There is space available, but no *-defs. This implies at least one
+ // def was resolved as 'max', taking less space than its proportion.
+ // If there are also 'min' defs, reconsider them - we can give
+ // them more space. If not, all the *-defs are 'max', so there's
+ // no way to use all the available space.
+ // 3. We allocated too much space. This implies at least one def was
+ // resolved as 'min'. If there are also 'max' defs, reconsider
+ // them, otherwise the over-allocation is an inevitable consequence
+ // of the given min constraints.
+ // Note that if we return to Phase2, at least one *-def will have been
+ // resolved. This guarantees we don't run Phase2+3 infinitely often.
+ runPhase2and3 = false;
+ if (starCount == 0 && takenSize < finalSize)
+ {
+ // if no *-defs remain and we haven't allocated all the space, reconsider the defs
+ // resolved as 'min'. Their allocation can be increased to make up the gap.
+ for (int i = minCount; i < minCountPhase2; ++i)
+ {
+ if (definitionIndices[i] >= 0)
+ {
+ DefinitionBase def = definitions[definitionIndices[i]];
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+
+ if (takenSize > finalSize)
+ {
+ // if we've allocated too much space, reconsider the defs
+ // resolved as 'max'. Their allocation can be decreased to make up the gap.
+ for (int i = maxCount; i < maxCountPhase2; ++i)
+ {
+ if (definitionIndices[defCount + i] >= 0)
+ {
+ DefinitionBase def = definitions[definitionIndices[defCount + i]];
+ def.MeasureSize = 1.0; // mark as 'not yet resolved'
+ ++starCount;
+ runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
+ }
+ }
+ }
+ }
+
+ // Phase 4. Resolve the remaining defs proportionally.
+ starCount = 0;
+ for (int i = 0; i < defCount; ++i)
+ {
+ DefinitionBase def = definitions[i];
+
+ if (def.UserSize.IsStar)
+ {
+ if (def.MeasureSize < 0.0)
+ {
+ // this def was resolved in phase 3 - fix up its size
+ def.SizeCache = -def.MeasureSize;
+ }
+ else
+ {
+ // this def needs resolution, add it to the list, sorted by *-weight
+ definitionIndices[starCount++] = i;
+ def.MeasureSize = StarWeight(def, scale);
+ }
+ }
+ }
+
+ if (starCount > 0)
+ {
+ StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions);
+ Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer);
+
+ // compute the partial sums of *-weight, in increasing order of weight
+ // for minimal loss of precision.
+ totalStarWeight = 0.0;
+ for (int i = 0; i < starCount; ++i)
+ {
+ DefinitionBase def = definitions[definitionIndices[i]];
+ totalStarWeight += def.MeasureSize;
+ def.SizeCache = totalStarWeight;
+ }
+
+ // resolve the defs, in decreasing order of weight.
+ for (int i = starCount - 1; i >= 0; --i)
+ {
+ DefinitionBase def = definitions[definitionIndices[i]];
+ double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
+
+ // min and max should have no effect by now, but just in case...
+ resolvedSize = Math.Min(resolvedSize, def.UserMaxSize);
+ resolvedSize = Math.Max(def.MinSize, resolvedSize);
+
+ // Use the raw (unrounded) sizes to update takenSize, so that
+ // proportions are computed in the same terms as in phase 3;
+ // this avoids errors arising from min/max constraints.
+ takenSize += resolvedSize;
+ def.SizeCache = resolvedSize;
+ }
+ }
+
+ // Phase 5. Apply layout rounding. We do this after fully allocating
+ // unrounded sizes, to avoid breaking assumptions in the previous phases
+ if (UseLayoutRounding)
+ {
+ var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
+
+ double[] roundingErrors = RoundingErrors;
+ double roundedTakenSize = 0.0;
+
+ // round each of the allocated sizes, keeping track of the deltas
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ DefinitionBase def = definitions[i];
+ double roundedSize = RoundLayoutValue(def.SizeCache, dpi);
+ roundingErrors[i] = (roundedSize - def.SizeCache);
+ def.SizeCache = roundedSize;
+ roundedTakenSize += roundedSize;
+ }
+
+ // The total allocation might differ from finalSize due to rounding
+ // effects. Tweak the allocations accordingly.
+
+ // Theoretical and historical note. The problem at hand - allocating
+ // space to columns (or rows) with *-weights, min and max constraints,
+ // and layout rounding - has a long history. Especially the special
+ // case of 50 columns with min=1 and available space=435 - allocating
+ // seats in the U.S. House of Representatives to the 50 states in
+ // proportion to their population. There are numerous algorithms
+ // and papers dating back to the 1700's, including the book:
+ // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982.
+ //
+ // One surprising result of all this research is that *any* algorithm
+ // will suffer from one or more undesirable features such as the
+ // "population paradox" or the "Alabama paradox", where (to use our terminology)
+ // increasing the available space by one pixel might actually decrease
+ // the space allocated to a given column, or increasing the weight of
+ // a column might decrease its allocation. This is worth knowing
+ // in case someone complains about this behavior; it's not a bug so
+ // much as something inherent to the problem. Cite the book mentioned
+ // above or one of the 100s of references, and resolve as WontFix.
+ //
+ // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer)
+ // each being allocated a large number of pixels (~50 or greater), and
+ // people don't even notice the kind of 1-pixel anomolies that are
+ // theoretically inevitable, or don't care if they do. At least they shouldn't
+ // care - no one should be using the results WPF's grid layout to make
+ // quantitative decisions; its job is to produce a reasonable display, not
+ // to allocate seats in Congress.
+ //
+ // Our algorithm is more susceptible to paradox than the one currently
+ // used for Congressional allocation ("Huntington-Hill" algorithm), but
+ // it is faster to run: O(N log N) vs. O(S * N), where N=number of
+ // definitions, S = number of available pixels. And it produces
+ // adequate results in practice, as mentioned above.
+ //
+ // To reiterate one point: all this only applies when layout rounding
+ // is in effect. When fractional sizes are allowed, the algorithm
+ // behaves as well as possible, subject to the min/max constraints
+ // and precision of floating-point computation. (However, the resulting
+ // display is subject to anti-aliasing problems. TANSTAAFL.)
+
+ if (!MathUtilities.AreClose(roundedTakenSize, finalSize))
+ {
+ // Compute deltas
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ definitionIndices[i] = i;
+ }
+
+ // Sort rounding errors
+ RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors);
+ Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer);
+ double adjustedSize = roundedTakenSize;
+ double dpiIncrement = 1.0 / dpi;
+
+ if (roundedTakenSize > finalSize)
+ {
+ int i = definitions.Length - 1;
+ while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0)
+ {
+ DefinitionBase definition = definitions[definitionIndices[i]];
+ double final = definition.SizeCache - dpiIncrement;
+ final = Math.Max(final, definition.MinSize);
+ if (final < definition.SizeCache)
+ {
+ adjustedSize -= dpiIncrement;
+ }
+ definition.SizeCache = final;
+ i--;
+ }
+ }
+ else if (roundedTakenSize < finalSize)
+ {
+ int i = 0;
+ while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Length)
+ {
+ DefinitionBase definition = definitions[definitionIndices[i]];
+ double final = definition.SizeCache + dpiIncrement;
+ final = Math.Max(final, definition.MinSize);
+ if (final > definition.SizeCache)
+ {
+ adjustedSize += dpiIncrement;
+ }
+ definition.SizeCache = final;
+ i++;
+ }
+ }
+ }
+ }
+
+ // Phase 6. Compute final offsets
+ definitions[0].FinalOffset = 0.0;
+ for (int i = 0; i < definitions.Length; ++i)
+ {
+ definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache;
+ }
+ }
+
+ // Choose the ratio with maximum discrepancy from the current proportion.
+ // Returns:
+ // true if proportion fails a min constraint but not a max, or
+ // if the min constraint has higher discrepancy
+ // false if proportion fails a max constraint but not a min, or
+ // if the max constraint has higher discrepancy
+ // null if proportion doesn't fail a min or max constraint
+ // The discrepancy is the ratio of the proportion to the max- or min-ratio.
+ // When both ratios hit the constraint, minRatio < proportion < maxRatio,
+ // and the minRatio has higher discrepancy if
+ // (proportion / minRatio) > (maxRatio / proportion)
+ private static bool? Choose(double minRatio, double maxRatio, double proportion)
+ {
+ if (minRatio < proportion)
+ {
+ if (maxRatio > proportion)
+ {
+ // compare proportion/minRatio : maxRatio/proportion, but
+ // do it carefully to avoid floating-point overflow or underflow
+ // and divide-by-0.
+ double minPower = Math.Floor(Math.Log(minRatio, 2.0));
+ double maxPower = Math.Floor(Math.Log(maxRatio, 2.0));
+ double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0));
+ if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else if (maxRatio > proportion)
+ {
+ return false;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Sorts row/column indices by rounding error if layout rounding is applied.
+ ///
+ /// Index, rounding error pair
+ /// Index, rounding error pair
+ /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise
+ private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y)
+ {
+ if (x.Value < y.Value)
+ {
+ return -1;
+ }
+ else if (x.Value > y.Value)
+ {
+ return 1;
+ }
+ return 0;
+ }
+
+ ///
+ /// Calculates final (aka arrange) size for given range.
+ ///
+ /// Array of definitions to process.
+ /// Start of the range.
+ /// Number of items in the range.
+ /// Final size.
+ private double GetFinalSizeForRange(
+ DefinitionBase[] definitions,
+ int start,
+ int count)
+ {
+ double size = 0;
+ int i = start + count - 1;
+
+ do
+ {
+ size += definitions[i].SizeCache;
+ } while (--i >= start);
+
+ return (size);
+ }
+
+ ///
+ /// Clears dirty state for the grid and its columns / rows
+ ///
+ private void SetValid()
+ {
+ if (IsTrivialGrid)
+ {
+ if (_tempDefinitions != null)
+ {
+ // TempDefinitions has to be cleared to avoid "memory leaks"
+ Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length));
+ _tempDefinitions = null;
+ }
+ }
+ }
+
+
+ ///
+ /// Synchronized ShowGridLines property with the state of the grid's visual collection
+ /// by adding / removing GridLinesRenderer visual.
+ /// Returns a reference to GridLinesRenderer visual or null.
+ ///
+ private GridLinesRenderer EnsureGridLinesRenderer()
+ {
+ //
+ // synchronize the state
+ //
+ if (ShowGridLines && (_gridLinesRenderer == null))
+ {
+ _gridLinesRenderer = new GridLinesRenderer();
+ this.VisualChildren.Add(_gridLinesRenderer);
+ }
+
+ if ((!ShowGridLines) && (_gridLinesRenderer != null))
+ {
+ this.VisualChildren.Remove(_gridLinesRenderer);
+ _gridLinesRenderer = null;
+ }
+
+ return (_gridLinesRenderer);
+ }
+
+ private double RoundLayoutValue(double value, double dpiScale)
+ {
+ double newValue;
+
+ // If DPI == 1, don't use DPI-aware rounding.
+ if (!MathUtilities.AreClose(dpiScale, 1.0))
+ {
+ newValue = Math.Round(value * dpiScale) / dpiScale;
+ // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
+ if (double.IsNaN(newValue) ||
+ double.IsInfinity(newValue) ||
+ MathUtilities.AreClose(newValue, double.MaxValue))
+ {
+ newValue = value;
+ }
+ }
+ else
+ {
+ newValue = Math.Round(value);
+ }
+
+ return newValue;
+ }
+
+
+ private static int ValidateColumn(AvaloniaObject o, int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentException("Invalid Grid.Column value.");
+ }
+
+ return value;
+ }
+
+ private static int ValidateRow(AvaloniaObject o, int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentException("Invalid Grid.Row value.");
+ }
+
+ return value;
+ }
+
+ private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (!grid.IsTrivialGrid) // trivial grid is 1 by 1. there is no grid lines anyway
+ {
+ grid.Invalidate();
+ }
+ }
+
+ ///
+ /// Helper for Comparer methods.
+ ///
+ ///
+ /// true if one or both of x and y are null, in which case result holds
+ /// the relative sort order.
+ ///
+ private static bool CompareNullRefs(object x, object y, out int result)
+ {
+ result = 2;
+
+ if (x == null)
+ {
+ if (y == null)
+ {
+ result = 0;
+ }
+ else
+ {
+ result = -1;
+ }
+ }
+ else
+ {
+ if (y == null)
+ {
+ result = 1;
+ }
+ }
+
+ return (result != 2);
+ }
+
+ ///
+ /// Helper accessor to layout time array of definitions.
+ ///
+ private DefinitionBase[] TempDefinitions
+ {
+ get
+ {
+ int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2;
+
+ if (_tempDefinitions == null
+ || _tempDefinitions.Length < requiredLength)
+ {
+ WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot);
+ if (tempDefinitionsWeakRef == null)
+ {
+ _tempDefinitions = new DefinitionBase[requiredLength];
+ Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions));
+ }
+ else
+ {
+ _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target;
+ if (_tempDefinitions == null
+ || _tempDefinitions.Length < requiredLength)
+ {
+ _tempDefinitions = new DefinitionBase[requiredLength];
+ tempDefinitionsWeakRef.Target = _tempDefinitions;
+ }
+ }
+ }
+ return (_tempDefinitions);
+ }
+ }
+
+ ///
+ /// Helper accessor to definition indices.
+ ///
+ private int[] DefinitionIndices
+ {
+ get
+ {
+ int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2;
+
+ if (_definitionIndices == null || _definitionIndices.Length < requiredLength)
+ {
+ _definitionIndices = new int[requiredLength];
+ }
+
+ return _definitionIndices;
+ }
+ }
+
+ ///
+ /// Helper accessor to rounding errors.
+ ///
+ private double[] RoundingErrors
+ {
+ get
+ {
+ int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length);
+
+ if (_roundingErrors == null && requiredLength == 0)
+ {
+ _roundingErrors = new double[1];
+ }
+ else if (_roundingErrors == null || _roundingErrors.Length < requiredLength)
+ {
+ _roundingErrors = new double[requiredLength];
+ }
+ return _roundingErrors;
+ }
+ }
+
+ ///
+ /// Returns *-weight, adjusted for scale computed during Phase 1
+ ///
+ static double StarWeight(DefinitionBase def, double scale)
+ {
+ if (scale < 0.0)
+ {
+ // if one of the *-weights is Infinity, adjust the weights by mapping
+ // Infinty to 1.0 and everything else to 0.0: the infinite items share the
+ // available space equally, everyone else gets nothing.
+ return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0;
+ }
+ else
+ {
+ return def.UserSize.Value * scale;
+ }
+ }
+
+ ///
+ /// LayoutTimeSizeType is used internally and reflects layout-time size type.
+ ///
+ [System.Flags]
+ internal enum LayoutTimeSizeType : byte
+ {
+ None = 0x00,
+ Pixel = 0x01,
+ Auto = 0x02,
+ Star = 0x04,
+ }
+
+ ///
+ /// CellCache stored calculated values of
+ /// 1. attached cell positioning properties;
+ /// 2. size type;
+ /// 3. index of a next cell in the group;
+ ///
+ private struct CellCache
+ {
+ internal int ColumnIndex;
+ internal int RowIndex;
+ internal int ColumnSpan;
+ internal int RowSpan;
+ internal LayoutTimeSizeType SizeTypeU;
+ internal LayoutTimeSizeType SizeTypeV;
+ internal int Next;
+ internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } }
+ internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } }
+ internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } }
+ internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
+ }
+
+ ///
+ /// Helper class for representing a key for a span in hashtable.
+ ///
+ private class SpanKey
+ {
+ ///
+ /// Constructor.
+ ///
+ /// Starting index of the span.
+ /// Span count.
+ /// true for columns; false for rows.
+ internal SpanKey(int start, int count, bool u)
+ {
+ _start = start;
+ _count = count;
+ _u = u;
+ }
+
+ ///
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ int hash = (_start ^ (_count << 2));
+
+ if (_u) hash &= 0x7ffffff;
+ else hash |= 0x8000000;
+
+ return (hash);
+ }
+
+ ///
+ ///
+ ///
+ public override bool Equals(object obj)
+ {
+ SpanKey sk = obj as SpanKey;
+ return (sk != null
+ && sk._start == _start
+ && sk._count == _count
+ && sk._u == _u);
+ }
+
+ ///
+ /// Returns start index of the span.
+ ///
+ internal int Start { get { return (_start); } }
+
+ ///
+ /// Returns span count.
+ ///
+ internal int Count { get { return (_count); } }
+
+ ///
+ /// Returns true if this is a column span.
+ /// false if this is a row span.
+ ///
+ internal bool U { get { return (_u); } }
+
+ private int _start;
+ private int _count;
+ private bool _u;
+ }
+
+ ///
+ /// SpanPreferredDistributionOrderComparer.
+ ///
+ private class SpanPreferredDistributionOrderComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ if (definitionX.UserSize.IsAuto)
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = definitionX.MinSize.CompareTo(definitionY.MinSize);
+ }
+ else
+ {
+ result = -1;
+ }
+ }
+ else
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = +1;
+ }
+ else
+ {
+ result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize);
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// SpanMaxDistributionOrderComparer.
+ ///
+ private class SpanMaxDistributionOrderComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ if (definitionX.UserSize.IsAuto)
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+ else
+ {
+ result = +1;
+ }
+ }
+ else
+ {
+ if (definitionY.UserSize.IsAuto)
+ {
+ result = -1;
+ }
+ else
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// RoundingErrorIndexComparer.
+ ///
+ private class RoundingErrorIndexComparer : IComparer
+ {
+ private readonly double[] errors;
+
+ internal RoundingErrorIndexComparer(double[] errors)
+ {
+ Contract.Requires(errors != null);
+ this.errors = errors;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ int result;
+
+ if (!CompareNullRefs(indexX, indexY, out result))
+ {
+ double errorX = errors[indexX.Value];
+ double errorY = errors[indexY.Value];
+ result = errorX.CompareTo(errorY);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MinRatioComparer.
+ /// Sort by w/min (stored in MeasureSize), descending.
+ /// We query the list from the back, i.e. in ascending order of w/min.
+ ///
+ private class MinRatioComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionY, definitionX, out result))
+ {
+ result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MaxRatioComparer.
+ /// Sort by w/max (stored in SizeCache), ascending.
+ /// We query the list from the back, i.e. in descending order of w/max.
+ ///
+ private class MaxRatioComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// StarWeightComparer.
+ /// Sort by *-weight (stored in MeasureSize), ascending.
+ ///
+ private class StarWeightComparer : IComparer
+ {
+ public int Compare(object x, object y)
+ {
+ DefinitionBase definitionX = x as DefinitionBase;
+ DefinitionBase definitionY = y as DefinitionBase;
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MinRatioIndexComparer.
+ ///
+ private class MinRatioIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal MinRatioIndexComparer(DefinitionBase[] definitions)
+ {
+ Contract.Requires(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionY, definitionX, out result))
+ {
+ result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MaxRatioIndexComparer.
+ ///
+ private class MaxRatioIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal MaxRatioIndexComparer(DefinitionBase[] definitions)
+ {
+ Contract.Requires(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// MaxRatioIndexComparer.
+ ///
+ private class StarWeightIndexComparer : IComparer
+ {
+ private readonly DefinitionBase[] definitions;
+
+ internal StarWeightIndexComparer(DefinitionBase[] definitions)
+ {
+ Contract.Requires(definitions != null);
+ this.definitions = definitions;
+ }
+
+ public int Compare(object x, object y)
+ {
+ int? indexX = x as int?;
+ int? indexY = y as int?;
+
+ DefinitionBase definitionX = null;
+ DefinitionBase definitionY = null;
+
+ if (indexX != null)
+ {
+ definitionX = definitions[indexX.Value];
+ }
+ if (indexY != null)
+ {
+ definitionY = definitions[indexY.Value];
+ }
+
+ int result;
+
+ if (!CompareNullRefs(definitionX, definitionY, out result))
+ {
+ result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// Helper to render grid lines.
+ ///
+ private class GridLinesRenderer : Control
+ {
+ ///
+ /// Static initialization
+ ///
+ static GridLinesRenderer()
+ {
+ var oddDashArray = new List();
+ oddDashArray.Add(_dashLength);
+ oddDashArray.Add(_dashLength);
+ var ds1 = new DashStyle(oddDashArray, 0);
+ _oddDashPen = new Pen(Brushes.Blue,
+ _penWidth,
+ lineCap: PenLineCap.Flat,
+ dashStyle: ds1);
+
+ var evenDashArray = new List();
+ evenDashArray.Add(_dashLength);
+ evenDashArray.Add(_dashLength);
+ var ds2 = new DashStyle(evenDashArray, 0);
+ _evenDashPen = new Pen(Brushes.Yellow,
+ _penWidth,
+ lineCap: PenLineCap.Flat,
+ dashStyle: ds2);
+ }
+
+ ///
+ /// UpdateRenderBounds.
+ ///
+ public override void Render(DrawingContext drawingContext)
+ {
+ var grid = this.GetVisualParent();
+
+ if (grid == null
+ || !grid.ShowGridLines
+ || grid.IsTrivialGrid)
+ {
+ return;
+ }
+
+ for (int i = 1; i < grid.ColumnDefinitions.Count; ++i)
+ {
+ DrawGridLine(
+ drawingContext,
+ grid.ColumnDefinitions[i].ActualWidth, 0.0,
+ grid.ColumnDefinitions[i].ActualWidth, _lastArrangeSize.Height);
+ }
+
+ for (int i = 1; i < grid.RowDefinitions.Count; ++i)
+ {
+ DrawGridLine(
+ drawingContext,
+ 0.0, grid.RowDefinitions[i].ActualHeight,
+ _lastArrangeSize.Width, grid.RowDefinitions[i].ActualHeight);
+ }
+ }
+
+ ///
+ /// Draw single hi-contrast line.
+ ///
+ private static void DrawGridLine(
+ DrawingContext drawingContext,
+ double startX,
+ double startY,
+ double endX,
+ double endY)
+ {
+ var start = new Point(startX, startY);
+ var end = new Point(endX, endY);
+ drawingContext.DrawLine(_oddDashPen, start, end);
+ drawingContext.DrawLine(_evenDashPen, start, end);
+ }
+
+ internal void UpdateRenderBounds(Size arrangeSize)
+ {
+ _lastArrangeSize = arrangeSize;
+ this.InvalidateVisual();
+ }
+
+ private static Size _lastArrangeSize;
+ private const double _dashLength = 4.0; //
+ private const double _penWidth = 1.0; //
+ private static readonly Pen _oddDashPen; // first pen to draw dash
+ private static readonly Pen _evenDashPen; // second pen to draw dash
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs
deleted file mode 100644
index 35a38a6423..0000000000
--- a/src/Avalonia.Controls/GridWPF.cs
+++ /dev/null
@@ -1,2824 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Reactive.Linq;
-using System.Runtime.CompilerServices;
-using Avalonia.Collections;
-using Avalonia.Controls.Utils;
-using Avalonia.VisualTree;
-using System.Threading;
-using JetBrains.Annotations;
-using Avalonia.Controls;
-using Avalonia.Media;
-using Avalonia;
-using System.Collections;
-using Avalonia.Utilities;
-using Avalonia.Layout;
-
-namespace Avalonia.Controls
-{
- ///
- /// Grid
- ///
- public class Grid : Panel
- {
- internal bool CellsStructureDirty = true;
- internal bool SizeToContentU;
- internal bool SizeToContentV;
- internal bool HasStarCellsU;
- internal bool HasStarCellsV;
- internal bool HasGroup3CellsInAutoRows;
- internal bool ColumnDefinitionsDirty = true;
- internal bool RowDefinitionsDirty = true;
-
- // index of the first cell in first cell group
- internal int CellGroup1;
-
- // index of the first cell in second cell group
- internal int CellGroup2;
-
- // index of the first cell in third cell group
- internal int CellGroup3;
-
- // index of the first cell in fourth cell group
- internal int CellGroup4;
-
- // temporary array used during layout for various purposes
- // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length)
- internal DefinitionBase[] _tempDefinitions;
- private GridLinesRenderer _gridLinesRenderer;
-
- // Keeps track of definition indices.
- private int[] _definitionIndices;
-
- private CellCache[] _cellCache;
-
-
- // Stores unrounded values and rounding errors during layout rounding.
- private double[] _roundingErrors;
- private DefinitionBase[] _definitionsU;
- private DefinitionBase[] _definitionsV;
-
- // 5 is an arbitrary constant chosen to end the measure loop
- private const int _layoutLoopMaxCount = 5;
- private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot;
- private static readonly IComparer _spanPreferredDistributionOrderComparer;
- private static readonly IComparer _spanMaxDistributionOrderComparer;
- private static readonly IComparer _minRatioComparer;
- private static readonly IComparer _maxRatioComparer;
- private static readonly IComparer _starWeightComparer;
-
- static Grid()
- {
- ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged);
- AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
-
- _tempDefinitionsDataSlot = Thread.AllocateDataSlot();
- _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer();
- _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer();
- _minRatioComparer = new MinRatioComparer();
- _maxRatioComparer = new MaxRatioComparer();
- _starWeightComparer = new StarWeightComparer();
- }
-
- ///
- /// Defines the Column attached property.
- ///
- public static readonly AttachedProperty ColumnProperty =
- AvaloniaProperty.RegisterAttached(
- "Column",
- validate: ValidateColumn);
-
- ///
- /// Defines the ColumnSpan attached property.
- ///
- public static readonly AttachedProperty ColumnSpanProperty =
- AvaloniaProperty.RegisterAttached("ColumnSpan", 1);
-
- ///
- /// Defines the Row attached property.
- ///
- public static readonly AttachedProperty RowProperty =
- AvaloniaProperty.RegisterAttached(
- "Row",
- validate: ValidateRow);
-
- ///
- /// Defines the RowSpan attached property.
- ///
- public static readonly AttachedProperty RowSpanProperty =
- AvaloniaProperty.RegisterAttached("RowSpan", 1);
-
- public static readonly AttachedProperty IsSharedSizeScopeProperty =
- AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false);
-
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty ShowGridLinesProperty =
- AvaloniaProperty.Register(
- nameof(ShowGridLines),
- defaultValue: false);
-
- ///
- /// ShowGridLines property.
- ///
- public bool ShowGridLines
- {
- get { return GetValue(ShowGridLinesProperty); }
- set { SetValue(ShowGridLinesProperty, value); }
- }
-
- ///
- /// Gets the value of the Column attached property for a control.
- ///
- /// The control.
- /// The control's column.
- public static int GetColumn(AvaloniaObject element)
- {
- return element.GetValue(ColumnProperty);
- }
-
- ///
- /// Gets the value of the ColumnSpan attached property for a control.
- ///
- /// The control.
- /// The control's column span.
- public static int GetColumnSpan(AvaloniaObject element)
- {
- return element.GetValue(ColumnSpanProperty);
- }
-
- ///
- /// Gets the value of the Row attached property for a control.
- ///
- /// The control.
- /// The control's row.
- public static int GetRow(AvaloniaObject element)
- {
- return element.GetValue(RowProperty);
- }
-
- ///
- /// Gets the value of the RowSpan attached property for a control.
- ///
- /// The control.
- /// The control's row span.
- public static int GetRowSpan(AvaloniaObject element)
- {
- return element.GetValue(RowSpanProperty);
- }
-
- ///
- /// Gets the value of the IsSharedSizeScope attached property for a control.
- ///
- /// The control.
- /// The control's IsSharedSizeScope value.
- public static bool GetIsSharedSizeScope(AvaloniaObject element)
- {
- return element.GetValue(IsSharedSizeScopeProperty);
- }
-
- ///
- /// Sets the value of the Column attached property for a control.
- ///
- /// The control.
- /// The column value.
- public static void SetColumn(AvaloniaObject element, int value)
- {
- element.SetValue(ColumnProperty, value);
- }
-
- ///
- /// Sets the value of the ColumnSpan attached property for a control.
- ///
- /// The control.
- /// The column span value.
- public static void SetColumnSpan(AvaloniaObject element, int value)
- {
- element.SetValue(ColumnSpanProperty, value);
- }
-
- ///
- /// Sets the value of the Row attached property for a control.
- ///
- /// The control.
- /// The row value.
- public static void SetRow(AvaloniaObject element, int value)
- {
- element.SetValue(RowProperty, value);
- }
-
- ///
- /// Sets the value of the RowSpan attached property for a control.
- ///
- /// The control.
- /// The row span value.
- public static void SetRowSpan(AvaloniaObject element, int value)
- {
- element.SetValue(RowSpanProperty, value);
- }
-
- private ColumnDefinitions _columnDefinitions;
- private RowDefinitions _rowDefinitions;
-
- ///
- /// Gets or sets the columns definitions for the grid.
- ///
- public ColumnDefinitions ColumnDefinitions
- {
- get
- {
- if (_columnDefinitions == null)
- {
- ColumnDefinitions = new ColumnDefinitions();
- }
-
- return _columnDefinitions;
- }
- set
- {
- _columnDefinitions = value;
- _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate());
- ColumnDefinitionsDirty = true;
-
- if (_columnDefinitions.Count > 0)
- _definitionsU = _columnDefinitions.Cast().ToArray();
- else
- _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
-
- _columnDefinitions.CollectionChanged += (_, e) =>
- {
- if (_columnDefinitions.Count == 0)
- {
- _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
- }
- else
- {
- _definitionsU = _columnDefinitions.Cast().ToArray();
- ColumnDefinitionsDirty = true;
- }
- Invalidate();
- };
- }
- }
-
- ///
- /// Gets or sets the row definitions for the grid.
- ///
- public RowDefinitions RowDefinitions
- {
- get
- {
- if (_rowDefinitions == null)
- {
- RowDefinitions = new RowDefinitions();
- }
-
- return _rowDefinitions;
- }
- set
- {
- _rowDefinitions = value;
- _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate());
-
- RowDefinitionsDirty = true;
-
- if (_rowDefinitions.Count > 0)
- _definitionsV = _rowDefinitions.Cast().ToArray();
- else
- _definitionsV = new DefinitionBase[1] { new RowDefinition() };
-
- _rowDefinitions.CollectionChanged += (_, e) =>
- {
- if (_rowDefinitions.Count == 0)
- {
- _definitionsV = new DefinitionBase[1] { new RowDefinition() };
- }
- else
- {
- _definitionsV = _rowDefinitions.Cast().ToArray();
- RowDefinitionsDirty = true;
- }
- Invalidate();
- };
- }
- }
-
- private bool IsTrivialGrid => (_definitionsU?.Length <= 1) &&
- (_definitionsV?.Length <= 1);
-
- ///
- /// Content measurement.
- ///
- /// Constraint
- /// Desired size
- protected override Size MeasureOverride(Size constraint)
- {
- Size gridDesiredSize;
-
- try
- {
- if (IsTrivialGrid)
- {
- gridDesiredSize = new Size();
-
- for (int i = 0, count = Children.Count; i < count; ++i)
- {
- var child = Children[i];
- if (child != null)
- {
- child.Measure(constraint);
- gridDesiredSize = new Size(
- Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),
- Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));
- }
- }
- }
- else
- {
- {
- bool sizeToContentU = double.IsPositiveInfinity(constraint.Width);
- bool sizeToContentV = double.IsPositiveInfinity(constraint.Height);
-
- // Clear index information and rounding errors
- if (RowDefinitionsDirty || ColumnDefinitionsDirty)
- {
- if (_definitionIndices != null)
- {
- Array.Clear(_definitionIndices, 0, _definitionIndices.Length);
- _definitionIndices = null;
- }
-
- if (UseLayoutRounding)
- {
- if (_roundingErrors != null)
- {
- Array.Clear(_roundingErrors, 0, _roundingErrors.Length);
- _roundingErrors = null;
- }
- }
- }
-
- ValidateColumnDefinitionsStructure();
- ValidateDefinitionsLayout(_definitionsU, sizeToContentU);
-
- ValidateRowDefinitionsStructure();
- ValidateDefinitionsLayout(_definitionsV, sizeToContentV);
-
- CellsStructureDirty |= (SizeToContentU != sizeToContentU)
- || (SizeToContentV != sizeToContentV);
-
- SizeToContentU = sizeToContentU;
- SizeToContentV = sizeToContentV;
- }
-
- ValidateCells();
-
- Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0);
-
- MeasureCellsGroup(CellGroup1, constraint, false, false);
-
- {
- // after Group1 is measured, only Group3 may have cells belonging to Auto rows.
- bool canResolveStarsV = !HasGroup3CellsInAutoRows;
-
- if (canResolveStarsV)
- {
- if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
- MeasureCellsGroup(CellGroup2, constraint, false, false);
- if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
- MeasureCellsGroup(CellGroup3, constraint, false, false);
- }
- else
- {
- // if at least one cell exists in Group2, it must be measured before
- // StarsU can be resolved.
- bool canResolveStarsU = CellGroup2 > _cellCache.Length;
- if (canResolveStarsU)
- {
- if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
- MeasureCellsGroup(CellGroup3, constraint, false, false);
- if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
- }
- else
- {
- // This is a revision to the algorithm employed for the cyclic
- // dependency case described above. We now repeatedly
- // measure Group3 and Group2 until their sizes settle. We
- // also use a count heuristic to break a loop in case of one.
-
- bool hasDesiredSizeUChanged = false;
- int cnt = 0;
-
- // Cache Group2MinWidths & Group3MinHeights
- double[] group2MinSizes = CacheMinSizes(CellGroup2, false);
- double[] group3MinSizes = CacheMinSizes(CellGroup3, true);
-
- MeasureCellsGroup(CellGroup2, constraint, false, true);
-
- do
- {
- if (hasDesiredSizeUChanged)
- {
- // Reset cached Group3Heights
- ApplyCachedMinSizes(group3MinSizes, true);
- }
-
- if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); }
- MeasureCellsGroup(CellGroup3, constraint, false, false);
-
- // Reset cached Group2Widths
- ApplyCachedMinSizes(group2MinSizes, false);
-
- if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); }
- MeasureCellsGroup(CellGroup2, constraint,
- cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged);
- }
- while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount);
- }
- }
- }
-
- MeasureCellsGroup(CellGroup4, constraint, false, false);
-
- gridDesiredSize = new Size(
- CalculateDesiredSize(_definitionsU),
- CalculateDesiredSize(_definitionsV));
- }
- }
- finally
- {
- }
-
- return (gridDesiredSize);
- }
-
- private void ValidateColumnDefinitionsStructure()
- {
- if (ColumnDefinitionsDirty)
- {
- if (_definitionsU == null)
- _definitionsU = new DefinitionBase[1] { new ColumnDefinition() };
- ColumnDefinitionsDirty = false;
- }
- }
-
- private void ValidateRowDefinitionsStructure()
- {
- if (RowDefinitionsDirty)
- {
- if (_definitionsV == null)
- _definitionsV = new DefinitionBase[1] { new RowDefinition() };
-
- RowDefinitionsDirty = false;
- }
- }
-
- ///
- /// Content arrangement.
- ///
- /// Arrange size
- protected override Size ArrangeOverride(Size arrangeSize)
- {
- try
- {
- if (IsTrivialGrid)
- {
- for (int i = 0, count = Children.Count; i < count; ++i)
- {
- var child = Children[i];
- if (child != null)
- {
- child.Arrange(new Rect(arrangeSize));
- }
- }
- }
- else
- {
- Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0);
-
- SetFinalSize(_definitionsU, arrangeSize.Width, true);
- SetFinalSize(_definitionsV, arrangeSize.Height, false);
-
- for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell)
- {
- IControl cell = Children[currentCell];
- if (cell == null)
- {
- continue;
- }
-
- int columnIndex = _cellCache[currentCell].ColumnIndex;
- int rowIndex = _cellCache[currentCell].RowIndex;
- int columnSpan = _cellCache[currentCell].ColumnSpan;
- int rowSpan = _cellCache[currentCell].RowSpan;
-
- Rect cellRect = new Rect(
- columnIndex == 0 ? 0.0 : _definitionsU[columnIndex].FinalOffset,
- rowIndex == 0 ? 0.0 : _definitionsV[rowIndex].FinalOffset,
- GetFinalSizeForRange(_definitionsU, columnIndex, columnSpan),
- GetFinalSizeForRange(_definitionsV, rowIndex, rowSpan));
-
- cell.Arrange(cellRect);
- }
-
- // update render bound on grid lines renderer visual
- var gridLinesRenderer = EnsureGridLinesRenderer();
- if (gridLinesRenderer != null)
- {
- gridLinesRenderer.UpdateRenderBounds(arrangeSize);
- }
- }
- }
- finally
- {
- SetValid();
- }
-
- for (var i = 0; i < ColumnDefinitions.Count; i++)
- {
- ColumnDefinitions[i].ActualWidth = GetFinalColumnDefinitionWidth(i);
- }
-
- for (var i = 0; i < RowDefinitions.Count; i++)
- {
- RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i);
- }
-
- return (arrangeSize);
- }
-
- ///
- /// Returns final width for a column.
- ///
- ///
- /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data.
- ///
- internal double GetFinalColumnDefinitionWidth(int columnIndex)
- {
- double value = 0.0;
-
- // actual value calculations require structure to be up-to-date
- if (!ColumnDefinitionsDirty)
- {
- value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset;
- if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; }
- }
- return (value);
- }
-
- ///
- /// Returns final height for a row.
- ///
- ///
- /// Used from public RowDefinition ActualHeight. Calculates final height using offset data.
- ///
- internal double GetFinalRowDefinitionHeight(int rowIndex)
- {
- double value = 0.0;
-
- // actual value calculations require structure to be up-to-date
- if (!RowDefinitionsDirty)
- {
- value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset;
- if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; }
- }
- return (value);
- }
-
- ///
- /// Invalidates grid caches and makes the grid dirty for measure.
- ///
- internal void Invalidate()
- {
- CellsStructureDirty = true;
- InvalidateMeasure();
- }
-
- ///
- /// Lays out cells according to rows and columns, and creates lookup grids.
- ///
- private void ValidateCells()
- {
- if (!CellsStructureDirty) return;
-
- _cellCache = new CellCache[Children.Count];
- CellGroup1 = int.MaxValue;
- CellGroup2 = int.MaxValue;
- CellGroup3 = int.MaxValue;
- CellGroup4 = int.MaxValue;
-
- bool hasStarCellsU = false;
- bool hasStarCellsV = false;
- bool hasGroup3CellsInAutoRows = false;
-
- for (int i = _cellCache.Length - 1; i >= 0; --i)
- {
- var child = Children[i] as Control;
-
- if (child == null)
- {
- continue;
- }
-
- var cell = new CellCache();
-
- // read indices from the corresponding properties
- // clamp to value < number_of_columns
- // column >= 0 is guaranteed by property value validation callback
- cell.ColumnIndex = Math.Min(GetColumn(child), _definitionsU.Length - 1);
-
- // clamp to value < number_of_rows
- // row >= 0 is guaranteed by property value validation callback
- cell.RowIndex = Math.Min(GetRow(child), _definitionsV.Length - 1);
-
- // read span properties
- // clamp to not exceed beyond right side of the grid
- // column_span > 0 is guaranteed by property value validation callback
- cell.ColumnSpan = Math.Min(GetColumnSpan(child), _definitionsU.Length - cell.ColumnIndex);
-
- // clamp to not exceed beyond bottom side of the grid
- // row_span > 0 is guaranteed by property value validation callback
- cell.RowSpan = Math.Min(GetRowSpan(child), _definitionsV.Length - cell.RowIndex);
-
- Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < _definitionsU.Length);
- Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < _definitionsV.Length);
-
- //
- // calculate and cache length types for the child
- //
- cell.SizeTypeU = GetLengthTypeForRange(_definitionsU, cell.ColumnIndex, cell.ColumnSpan);
- cell.SizeTypeV = GetLengthTypeForRange(_definitionsV, cell.RowIndex, cell.RowSpan);
-
- hasStarCellsU |= cell.IsStarU;
- hasStarCellsV |= cell.IsStarV;
-
- //
- // distribute cells into four groups.
- //
- if (!cell.IsStarV)
- {
- if (!cell.IsStarU)
- {
- cell.Next = CellGroup1;
- CellGroup1 = i;
- }
- else
- {
- cell.Next = CellGroup3;
- CellGroup3 = i;
-
- // remember if this cell belongs to auto row
- hasGroup3CellsInAutoRows |= cell.IsAutoV;
- }
- }
- else
- {
- if (cell.IsAutoU
- // note below: if spans through Star column it is NOT Auto
- && !cell.IsStarU)
- {
- cell.Next = CellGroup2;
- CellGroup2 = i;
- }
- else
- {
- cell.Next = CellGroup4;
- CellGroup4 = i;
- }
- }
-
- _cellCache[i] = cell;
- }
-
- HasStarCellsU = hasStarCellsU;
- HasStarCellsV = hasStarCellsV;
- HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows;
-
- CellsStructureDirty = false;
- }
-
- ///
- /// Validates layout time size type information on given array of definitions.
- /// Sets MinSize and MeasureSizes.
- ///
- /// Array of definitions to update.
- /// if "true" then star definitions are treated as Auto.
- private void ValidateDefinitionsLayout(
- DefinitionBase[] definitions,
- bool treatStarAsAuto)
- {
- for (int i = 0; i < definitions.Length; ++i)
- {
- // Reset minimum size.
- definitions[i].MinSize = 0;
-
- double userMinSize = definitions[i].UserMinSize;
- double userMaxSize = definitions[i].UserMaxSize;
- double userSize = 0;
-
- switch (definitions[i].UserSize.GridUnitType)
- {
- case (GridUnitType.Pixel):
- definitions[i].SizeType = LayoutTimeSizeType.Pixel;
- userSize = definitions[i].UserSize.Value;
-
- // this was brought with NewLayout and defeats squishy behavior
- userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
- break;
- case (GridUnitType.Auto):
- definitions[i].SizeType = LayoutTimeSizeType.Auto;
- userSize = double.PositiveInfinity;
- break;
- case (GridUnitType.Star):
- if (treatStarAsAuto)
- {
- definitions[i].SizeType = LayoutTimeSizeType.Auto;
- userSize = double.PositiveInfinity;
- }
- else
- {
- definitions[i].SizeType = LayoutTimeSizeType.Star;
- userSize = double.PositiveInfinity;
- }
- break;
- default:
- Debug.Assert(false);
- break;
- }
-
- definitions[i].UpdateMinSize(userMinSize);
- definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize));
- }
- }
-
- private double[] CacheMinSizes(int cellsHead, bool isRows)
- {
- double[] minSizes = isRows ? new double[_definitionsV.Length]
- : new double[_definitionsU.Length];
-
- for (int j = 0; j < minSizes.Length; j++)
- {
- minSizes[j] = -1;
- }
-
- int i = cellsHead;
- do
- {
- if (isRows)
- {
- minSizes[_cellCache[i].RowIndex] = _definitionsV[_cellCache[i].RowIndex].MinSize;
- }
- else
- {
- minSizes[_cellCache[i].ColumnIndex] = _definitionsU[_cellCache[i].ColumnIndex].MinSize;
- }
-
- i = _cellCache[i].Next;
- } while (i < _cellCache.Length);
-
- return minSizes;
- }
-
- private void ApplyCachedMinSizes(double[] minSizes, bool isRows)
- {
- for (int i = 0; i < minSizes.Length; i++)
- {
- if (MathUtilities.GreaterThanOrClose(minSizes[i], 0))
- {
- if (isRows)
- {
- _definitionsV[i].MinSize = minSizes[i];
- }
- else
- {
- _definitionsU[i].MinSize = minSizes[i];
- }
- }
- }
- }
-
- private void MeasureCellsGroup(
- int cellsHead,
- Size referenceSize,
- bool ignoreDesiredSizeU,
- bool forceInfinityV)
- {
- bool unusedHasDesiredSizeUChanged;
- MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU,
- forceInfinityV, out unusedHasDesiredSizeUChanged);
- }
-
- ///
- /// Measures one group of cells.
- ///
- /// Head index of the cells chain.
- /// Reference size for spanned cells
- /// calculations.
- /// When "true" cells' desired
- /// width is not registered in columns.
- /// Passed through to MeasureCell.
- /// When "true" cells' desired height is not registered in rows.
- private void MeasureCellsGroup(
- int cellsHead,
- Size referenceSize,
- bool ignoreDesiredSizeU,
- bool forceInfinityV,
- out bool hasDesiredSizeUChanged)
- {
- hasDesiredSizeUChanged = false;
-
- if (cellsHead >= _cellCache.Length)
- {
- return;
- }
-
- Hashtable spanStore = null;
- bool ignoreDesiredSizeV = forceInfinityV;
-
- int i = cellsHead;
- do
- {
- double oldWidth = Children[i].DesiredSize.Width;
-
- MeasureCell(i, forceInfinityV);
-
- hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width);
-
- if (!ignoreDesiredSizeU)
- {
- if (_cellCache[i].ColumnSpan == 1)
- {
- _definitionsU[_cellCache[i].ColumnIndex]
- .UpdateMinSize(Math.Min(Children[i].DesiredSize.Width,
- _definitionsU[_cellCache[i].ColumnIndex].UserMaxSize));
- }
- else
- {
- RegisterSpan(
- ref spanStore,
- _cellCache[i].ColumnIndex,
- _cellCache[i].ColumnSpan,
- true,
- Children[i].DesiredSize.Width);
- }
- }
-
- if (!ignoreDesiredSizeV)
- {
- if (_cellCache[i].RowSpan == 1)
- {
- _definitionsV[_cellCache[i].RowIndex]
- .UpdateMinSize(Math.Min(Children[i].DesiredSize.Height,
- _definitionsV[_cellCache[i].RowIndex].UserMaxSize));
- }
- else
- {
- RegisterSpan(
- ref spanStore,
- _cellCache[i].RowIndex,
- _cellCache[i].RowSpan,
- false,
- Children[i].DesiredSize.Height);
- }
- }
-
- i = _cellCache[i].Next;
- } while (i < _cellCache.Length);
-
- if (spanStore != null)
- {
- foreach (DictionaryEntry e in spanStore)
- {
- SpanKey key = (SpanKey)e.Key;
- double requestedSize = (double)e.Value;
-
- EnsureMinSizeInDefinitionRange(
- key.U ? _definitionsU : _definitionsV,
- key.Start,
- key.Count,
- requestedSize,
- key.U ? referenceSize.Width : referenceSize.Height);
- }
- }
- }
-
- ///
- /// Helper method to register a span information for delayed processing.
- ///
- /// Reference to a hashtable object used as storage.
- /// Span starting index.
- /// Span count.
- /// true if this is a column span. false if this is a row span.
- /// Value to store. If an entry already exists the biggest value is stored.
- private static void RegisterSpan(
- ref Hashtable store,
- int start,
- int count,
- bool u,
- double value)
- {
- if (store == null)
- {
- store = new Hashtable();
- }
-
- SpanKey key = new SpanKey(start, count, u);
- object o = store[key];
-
- if (o == null
- || value > (double)o)
- {
- store[key] = value;
- }
- }
-
- ///
- /// Takes care of measuring a single cell.
- ///
- /// Index of the cell to measure.
- /// If "true" then cell is always
- /// calculated to infinite height.
- private void MeasureCell(
- int cell,
- bool forceInfinityV)
- {
- double cellMeasureWidth;
- double cellMeasureHeight;
-
- if (_cellCache[cell].IsAutoU
- && !_cellCache[cell].IsStarU)
- {
- // if cell belongs to at least one Auto column and not a single Star column
- // then it should be calculated "to content", thus it is possible to "shortcut"
- // calculations and simply assign PositiveInfinity here.
- cellMeasureWidth = double.PositiveInfinity;
- }
- else
- {
- // otherwise...
- cellMeasureWidth = GetMeasureSizeForRange(
- _definitionsU,
- _cellCache[cell].ColumnIndex,
- _cellCache[cell].ColumnSpan);
- }
-
- if (forceInfinityV)
- {
- cellMeasureHeight = double.PositiveInfinity;
- }
- else if (_cellCache[cell].IsAutoV
- && !_cellCache[cell].IsStarV)
- {
- // if cell belongs to at least one Auto row and not a single Star row
- // then it should be calculated "to content", thus it is possible to "shortcut"
- // calculations and simply assign PositiveInfinity here.
- cellMeasureHeight = double.PositiveInfinity;
- }
- else
- {
- cellMeasureHeight = GetMeasureSizeForRange(
- _definitionsV,
- _cellCache[cell].RowIndex,
- _cellCache[cell].RowSpan);
- }
-
- var child = Children[cell];
-
- if (child != null)
- {
- Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
- child.Measure(childConstraint);
- }
- }
-
- ///
- /// Calculates one dimensional measure size for given definitions' range.
- ///
- /// Source array of definitions to read values from.
- /// Starting index of the range.
- /// Number of definitions included in the range.
- /// Calculated measure size.
- ///
- /// For "Auto" definitions MinWidth is used in place of PreferredSize.
- ///
- private double GetMeasureSizeForRange(
- DefinitionBase[] definitions,
- int start,
- int count)
- {
- Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
-
- double measureSize = 0;
- int i = start + count - 1;
-
- do
- {
- measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto)
- ? definitions[i].MinSize
- : definitions[i].MeasureSize;
- } while (--i >= start);
-
- return (measureSize);
- }
-
- ///
- /// Accumulates length type information for given definition's range.
- ///
- /// Source array of definitions to read values from.
- /// Starting index of the range.
- /// Number of definitions included in the range.
- /// Length type for given range.
- private LayoutTimeSizeType GetLengthTypeForRange(
- DefinitionBase[] definitions,
- int start,
- int count)
- {
- Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length);
-
- LayoutTimeSizeType lengthType = LayoutTimeSizeType.None;
- int i = start + count - 1;
-
- do
- {
- lengthType |= definitions[i].SizeType;
- } while (--i >= start);
-
- return (lengthType);
- }
-
- ///
- /// Distributes min size back to definition array's range.
- ///
- /// Start of the range.
- /// Number of items in the range.
- /// Minimum size that should "fit" into the definitions range.
- /// Definition array receiving distribution.
- /// Size used to resolve percentages.
- private void EnsureMinSizeInDefinitionRange(
- DefinitionBase[] definitions,
- int start,
- int count,
- double requestedSize,
- double percentReferenceSize)
- {
- Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length);
-
- // avoid processing when asked to distribute "0"
- if (!MathUtilities.IsZero(requestedSize))
- {
- DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
- int end = start + count;
- int autoDefinitionsCount = 0;
- double rangeMinSize = 0;
- double rangePreferredSize = 0;
- double rangeMaxSize = 0;
- double maxMaxSize = 0; // maximum of maximum sizes
-
- // first accumulate the necessary information:
- // a) sum up the sizes in the range;
- // b) count the number of auto definitions in the range;
- // c) initialize temp array
- // d) cache the maximum size into SizeCache
- // e) accumulate max of max sizes
- for (int i = start; i < end; ++i)
- {
- double minSize = definitions[i].MinSize;
- double preferredSize = definitions[i].PreferredSize;
- double maxSize = Math.Max(definitions[i].UserMaxSize, minSize);
-
- rangeMinSize += minSize;
- rangePreferredSize += preferredSize;
- rangeMaxSize += maxSize;
-
- definitions[i].SizeCache = maxSize;
-
- // sanity check: no matter what, but min size must always be the smaller;
- // max size must be the biggest; and preferred should be in between
- Debug.Assert(minSize <= preferredSize
- && preferredSize <= maxSize
- && rangeMinSize <= rangePreferredSize
- && rangePreferredSize <= rangeMaxSize);
-
- if (maxMaxSize < maxSize) maxMaxSize = maxSize;
- if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++;
- tempDefinitions[i - start] = definitions[i];
- }
-
- // avoid processing if the range already big enough
- if (requestedSize > rangeMinSize)
- {
- if (requestedSize <= rangePreferredSize)
- {
- //
- // requestedSize fits into preferred size of the range.
- // distribute according to the following logic:
- // * do not distribute into auto definitions - they should continue to stay "tight";
- // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size.
- //
- // in order to achieve that, definitions are sorted in a way that all auto definitions
- // are first, then definitions follow ascending order with PreferredSize as the key of sorting.
- //
- double sizeToDistribute;
- int i;
-
- Array.Sort(tempDefinitions, 0, count, _spanPreferredDistributionOrderComparer);
- for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i)
- {
- // sanity check: only auto definitions allowed in this loop
- Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
-
- // adjust sizeToDistribute value by subtracting auto definition min size
- sizeToDistribute -= (tempDefinitions[i].MinSize);
- }
-
- for (; i < count; ++i)
- {
- // sanity check: no auto definitions allowed in this loop
- Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
-
- double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize);
- if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); }
- sizeToDistribute -= newMinSize;
- }
-
- // sanity check: requested size must all be distributed
- Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
- }
- else if (requestedSize <= rangeMaxSize)
- {
- //
- // requestedSize bigger than preferred size, but fit into max size of the range.
- // distribute according to the following logic:
- // * do not distribute into auto definitions, if possible - they should continue to stay "tight";
- // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size.
- //
- // in order to achieve that, definitions are sorted in a way that all non-auto definitions
- // are last, then definitions follow ascending order with MaxSize as the key of sorting.
- //
- double sizeToDistribute;
- int i;
-
- Array.Sort(tempDefinitions, 0, count, _spanMaxDistributionOrderComparer);
- for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i)
- {
- // sanity check: no auto definitions allowed in this loop
- Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
-
- double preferredSize = tempDefinitions[i].PreferredSize;
- double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i);
- tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
- sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
- }
-
- for (; i < count; ++i)
- {
- // sanity check: only auto definitions allowed in this loop
- Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
-
- double preferredSize = tempDefinitions[i].MinSize;
- double newMinSize = preferredSize + sizeToDistribute / (count - i);
- tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
- sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
- }
-
- // sanity check: requested size must all be distributed
- Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
- }
- else
- {
- //
- // requestedSize bigger than max size of the range.
- // distribute according to the following logic:
- // * for all definitions distribute to equi-size min sizes.
- //
- double equalSize = requestedSize / count;
-
- if (equalSize < maxMaxSize
- && !MathUtilities.AreClose(equalSize, maxMaxSize))
- {
- // equi-size is less than maximum of maxSizes.
- // in this case distribute so that smaller definitions grow faster than
- // bigger ones.
- double totalRemainingSize = maxMaxSize * count - rangeMaxSize;
- double sizeToDistribute = requestedSize - rangeMaxSize;
-
- // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers
- Debug.Assert(!double.IsInfinity(totalRemainingSize)
- && !double.IsNaN(totalRemainingSize)
- && totalRemainingSize > 0
- && !double.IsInfinity(sizeToDistribute)
- && !double.IsNaN(sizeToDistribute)
- && sizeToDistribute > 0);
-
- for (int i = 0; i < count; ++i)
- {
- double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize;
- tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize);
- }
- }
- else
- {
- //
- // equi-size is greater or equal to maximum of max sizes.
- // all definitions receive equalSize as their mim sizes.
- //
- for (int i = 0; i < count; ++i)
- {
- tempDefinitions[i].UpdateMinSize(equalSize);
- }
- }
- }
- }
- }
- }
-
- // new implementation as of 4.7. Several improvements:
- // 1. Allocate to *-defs hitting their min or max constraints, before allocating
- // to other *-defs. A def that hits its min uses more space than its
- // proportional share, reducing the space available to everyone else.
- // The legacy algorithm deducted this space only from defs processed
- // after the min; the new algorithm deducts it proportionally from all
- // defs. This avoids the "*-defs exceed available space" problem,
- // and other related problems where *-defs don't receive proportional
- // allocations even though no constraints are preventing it.
- // 2. When multiple defs hit min or max, resolve the one with maximum
- // discrepancy (defined below). This avoids discontinuities - small
- // change in available space resulting in large change to one def's allocation.
- // 3. Correct handling of large *-values, including Infinity.
-
- ///
- /// Resolves Star's for given array of definitions.
- ///
- /// Array of definitions to resolve stars.
- /// All available size.
- ///
- /// Must initialize LayoutSize for all Star entries in given array of definitions.
- ///
- private void ResolveStar(
- DefinitionBase[] definitions,
- double availableSize)
- {
- int defCount = definitions.Length;
- DefinitionBase[] tempDefinitions = TempDefinitions;
- int minCount = 0, maxCount = 0;
- double takenSize = 0;
- double totalStarWeight = 0.0;
- int starCount = 0; // number of unresolved *-definitions
- double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
-
- // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
- double maxStar = 0.0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.SizeType == LayoutTimeSizeType.Star)
- {
- ++starCount;
- def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3"
- if (def.UserSize.Value > maxStar)
- {
- maxStar = def.UserSize.Value;
- }
- }
- }
-
- if (double.IsPositiveInfinity(maxStar))
- {
- // negative scale means one or more of the weights was Infinity
- scale = -1.0;
- }
- else if (starCount > 0)
- {
- // if maxStar * starCount > double.Max, summing all the weights could cause
- // floating-point overflow. To avoid that, scale the weights by a factor to keep
- // the sum within limits. Choose a power of 2, to preserve precision.
- double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0));
- if (power < 0.0)
- {
- scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
- }
- }
-
- // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
- // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
- // More explanation below...
- for (bool runPhase2and3 = true; runPhase2and3;)
- {
- // Phase 2. Compute total *-weight W and available space S.
- // For *-items that have Min or Max constraints, compute the ratios used to decide
- // whether proportional space is too big or too small and add the item to the
- // corresponding list. (The "min" list is in the first half of tempDefinitions,
- // the "max" list in the second half. TempDefinitions has capacity at least
- // 2*defCount, so there's room for both lists.)
- totalStarWeight = 0.0;
- takenSize = 0.0;
- minCount = maxCount = 0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- switch (def.SizeType)
- {
- case (LayoutTimeSizeType.Auto):
- takenSize += definitions[i].MinSize;
- break;
- case (LayoutTimeSizeType.Pixel):
- takenSize += def.MeasureSize;
- break;
- case (LayoutTimeSizeType.Star):
- if (def.MeasureSize < 0.0)
- {
- takenSize += -def.MeasureSize; // already resolved
- }
- else
- {
- double starWeight = StarWeight(def, scale);
- totalStarWeight += starWeight;
-
- if (def.MinSize > 0.0)
- {
- // store ratio w/min in MeasureSize (for now)
- tempDefinitions[minCount++] = def;
- def.MeasureSize = starWeight / def.MinSize;
- }
-
- double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize);
- if (!double.IsPositiveInfinity(effectiveMaxSize))
- {
- // store ratio w/max in SizeCache (for now)
- tempDefinitions[defCount + maxCount++] = def;
- def.SizeCache = starWeight / effectiveMaxSize;
- }
- }
- break;
- }
- }
-
- // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
- int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
- double takenStarWeight = 0.0;
- double remainingAvailableSize = availableSize - takenSize;
- double remainingStarWeight = totalStarWeight - takenStarWeight;
- Array.Sort(tempDefinitions, 0, minCount, _minRatioComparer);
- Array.Sort(tempDefinitions, defCount, maxCount, _maxRatioComparer);
-
- while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
- {
- // the calculation
- // remainingStarWeight = totalStarWeight - takenStarWeight
- // is subject to catastrophic cancellation if the two terms are nearly equal,
- // which leads to meaningless results. Check for that, and recompute from
- // the remaining definitions. [This leads to quadratic behavior in really
- // pathological cases - but they'd never arise in practice.]
- const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
- if (remainingStarWeight < totalStarWeight * starFactor)
- {
- takenStarWeight = 0.0;
- totalStarWeight = 0.0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
- if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0)
- {
- totalStarWeight += StarWeight(def, scale);
- }
- }
-
- remainingStarWeight = totalStarWeight - takenStarWeight;
- }
-
- double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : double.PositiveInfinity;
- double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0;
-
- // choose the def with larger ratio to the current proportion ("max discrepancy")
- double proportion = remainingStarWeight / remainingAvailableSize;
- bool? chooseMin = Choose(minRatio, maxRatio, proportion);
-
- // if no def was chosen, advance to phase 4; the current proportion doesn't
- // conflict with any min or max values.
- if (!(chooseMin.HasValue))
- {
- break;
- }
-
- // get the chosen definition and its resolved size
- DefinitionBase resolvedDef;
- double resolvedSize;
- if (chooseMin == true)
- {
- resolvedDef = tempDefinitions[minCount - 1];
- resolvedSize = resolvedDef.MinSize;
- --minCount;
- }
- else
- {
- resolvedDef = tempDefinitions[defCount + maxCount - 1];
- resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
- --maxCount;
- }
-
- // resolve the chosen def, deduct its contributions from W and S.
- // Defs resolved in phase 3 are marked by storing the negative of their resolved
- // size in MeasureSize, to distinguish them from a pending def.
- takenSize += resolvedSize;
- resolvedDef.MeasureSize = -resolvedSize;
- takenStarWeight += StarWeight(resolvedDef, scale);
- --starCount;
-
- remainingAvailableSize = availableSize - takenSize;
- remainingStarWeight = totalStarWeight - takenStarWeight;
-
- // advance to the next candidate defs, removing ones that have been resolved.
- // Both counts are advanced, as a def might appear in both lists.
- while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0)
- {
- --minCount;
- tempDefinitions[minCount] = null;
- }
- while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0)
- {
- --maxCount;
- tempDefinitions[defCount + maxCount] = null;
- }
- }
-
- // decide whether to run Phase2 and Phase3 again. There are 3 cases:
- // 1. There is space available, and *-defs remaining. This is the
- // normal case - move on to Phase 4 to allocate the remaining
- // space proportionally to the remaining *-defs.
- // 2. There is space available, but no *-defs. This implies at least one
- // def was resolved as 'max', taking less space than its proportion.
- // If there are also 'min' defs, reconsider them - we can give
- // them more space. If not, all the *-defs are 'max', so there's
- // no way to use all the available space.
- // 3. We allocated too much space. This implies at least one def was
- // resolved as 'min'. If there are also 'max' defs, reconsider
- // them, otherwise the over-allocation is an inevitable consequence
- // of the given min constraints.
- // Note that if we return to Phase2, at least one *-def will have been
- // resolved. This guarantees we don't run Phase2+3 infinitely often.
- runPhase2and3 = false;
- if (starCount == 0 && takenSize < availableSize)
- {
- // if no *-defs remain and we haven't allocated all the space, reconsider the defs
- // resolved as 'min'. Their allocation can be increased to make up the gap.
- for (int i = minCount; i < minCountPhase2; ++i)
- {
- DefinitionBase def = tempDefinitions[i];
- if (def != null)
- {
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
-
- if (takenSize > availableSize)
- {
- // if we've allocated too much space, reconsider the defs
- // resolved as 'max'. Their allocation can be decreased to make up the gap.
- for (int i = maxCount; i < maxCountPhase2; ++i)
- {
- DefinitionBase def = tempDefinitions[defCount + i];
- if (def != null)
- {
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
- }
-
- // Phase 4. Resolve the remaining defs proportionally.
- starCount = 0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.SizeType == LayoutTimeSizeType.Star)
- {
- if (def.MeasureSize < 0.0)
- {
- // this def was resolved in phase 3 - fix up its measure size
- def.MeasureSize = -def.MeasureSize;
- }
- else
- {
- // this def needs resolution, add it to the list, sorted by *-weight
- tempDefinitions[starCount++] = def;
- def.MeasureSize = StarWeight(def, scale);
- }
- }
- }
-
- if (starCount > 0)
- {
- Array.Sort(tempDefinitions, 0, starCount, _starWeightComparer);
-
- // compute the partial sums of *-weight, in increasing order of weight
- // for minimal loss of precision.
- totalStarWeight = 0.0;
- for (int i = 0; i < starCount; ++i)
- {
- DefinitionBase def = tempDefinitions[i];
- totalStarWeight += def.MeasureSize;
- def.SizeCache = totalStarWeight;
- }
-
- // resolve the defs, in decreasing order of weight
- for (int i = starCount - 1; i >= 0; --i)
- {
- DefinitionBase def = tempDefinitions[i];
- double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
-
- // min and max should have no effect by now, but just in case...
- resolvedSize = Math.Min(resolvedSize, def.UserMaxSize);
- resolvedSize = Math.Max(def.MinSize, resolvedSize);
-
- def.MeasureSize = resolvedSize;
- takenSize += resolvedSize;
- }
- }
- }
-
- ///
- /// Calculates desired size for given array of definitions.
- ///
- /// Array of definitions to use for calculations.
- /// Desired size.
- private double CalculateDesiredSize(
- DefinitionBase[] definitions)
- {
- double desiredSize = 0;
-
- for (int i = 0; i < definitions.Length; ++i)
- {
- desiredSize += definitions[i].MinSize;
- }
-
- return (desiredSize);
- }
-
- ///
- /// Calculates and sets final size for all definitions in the given array.
- ///
- /// Array of definitions to process.
- /// Final size to lay out to.
- /// True if sizing row definitions, false for columns
- private void SetFinalSize(
- DefinitionBase[] definitions,
- double finalSize,
- bool columns)
- {
- int defCount = definitions.Length;
- int[] definitionIndices = DefinitionIndices;
- int minCount = 0, maxCount = 0;
- double takenSize = 0.0;
- double totalStarWeight = 0.0;
- int starCount = 0; // number of unresolved *-definitions
- double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present"
-
- // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights
- double maxStar = 0.0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.UserSize.IsStar)
- {
- ++starCount;
- def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3"
- if (def.UserSize.Value > maxStar)
- {
- maxStar = def.UserSize.Value;
- }
- }
- }
-
- if (double.IsPositiveInfinity(maxStar))
- {
- // negative scale means one or more of the weights was Infinity
- scale = -1.0;
- }
- else if (starCount > 0)
- {
- // if maxStar * starCount > double.Max, summing all the weights could cause
- // floating-point overflow. To avoid that, scale the weights by a factor to keep
- // the sum within limits. Choose a power of 2, to preserve precision.
- double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0));
- if (power < 0.0)
- {
- scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia
- }
- }
-
-
- // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights
- // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3.
- // More explanation below...
- for (bool runPhase2and3 = true; runPhase2and3;)
- {
- // Phase 2. Compute total *-weight W and available space S.
- // For *-items that have Min or Max constraints, compute the ratios used to decide
- // whether proportional space is too big or too small and add the item to the
- // corresponding list. (The "min" list is in the first half of definitionIndices,
- // the "max" list in the second half. DefinitionIndices has capacity at least
- // 2*defCount, so there's room for both lists.)
- totalStarWeight = 0.0;
- takenSize = 0.0;
- minCount = maxCount = 0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.UserSize.IsStar)
- {
- // Debug.Assert(!def.IsShared, "*-defs cannot be shared");
-
- if (def.MeasureSize < 0.0)
- {
- takenSize += -def.MeasureSize; // already resolved
- }
- else
- {
- double starWeight = StarWeight(def, scale);
- totalStarWeight += starWeight;
-
- if (def.MinSize > 0.0)
- {
- // store ratio w/min in MeasureSize (for now)
- definitionIndices[minCount++] = i;
- def.MeasureSize = starWeight / def.MinSize;
- }
-
- double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize);
- if (!double.IsPositiveInfinity(effectiveMaxSize))
- {
- // store ratio w/max in SizeCache (for now)
- definitionIndices[defCount + maxCount++] = i;
- def.SizeCache = starWeight / effectiveMaxSize;
- }
- }
- }
- else
- {
- double userSize = 0;
-
- switch (def.UserSize.GridUnitType)
- {
- case (GridUnitType.Pixel):
- userSize = def.UserSize.Value;
- break;
-
- case (GridUnitType.Auto):
- userSize = def.MinSize;
- break;
- }
-
- double userMaxSize;
-
- // if (def.IsShared)
- // {
- // // overriding userMaxSize effectively prevents squishy-ness.
- // // this is a "solution" to avoid shared definitions from been sized to
- // // different final size at arrange time, if / when different grids receive
- // // different final sizes.
- // userMaxSize = userSize;
- // }
- // else
- // {
- userMaxSize = def.UserMaxSize;
- // }
-
- def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize));
- takenSize += def.SizeCache;
- }
- }
-
- // Phase 3. Resolve *-items whose proportional sizes are too big or too small.
- int minCountPhase2 = minCount, maxCountPhase2 = maxCount;
- double takenStarWeight = 0.0;
- double remainingAvailableSize = finalSize - takenSize;
- double remainingStarWeight = totalStarWeight - takenStarWeight;
-
- MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions);
- Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer);
- MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions);
- Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer);
-
- while (minCount + maxCount > 0 && remainingAvailableSize > 0.0)
- {
- // the calculation
- // remainingStarWeight = totalStarWeight - takenStarWeight
- // is subject to catastrophic cancellation if the two terms are nearly equal,
- // which leads to meaningless results. Check for that, and recompute from
- // the remaining definitions. [This leads to quadratic behavior in really
- // pathological cases - but they'd never arise in practice.]
- const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate
- if (remainingStarWeight < totalStarWeight * starFactor)
- {
- takenStarWeight = 0.0;
- totalStarWeight = 0.0;
-
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
- if (def.UserSize.IsStar && def.MeasureSize > 0.0)
- {
- totalStarWeight += StarWeight(def, scale);
- }
- }
-
- remainingStarWeight = totalStarWeight - takenStarWeight;
- }
-
- double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : double.PositiveInfinity;
- double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0;
-
- // choose the def with larger ratio to the current proportion ("max discrepancy")
- double proportion = remainingStarWeight / remainingAvailableSize;
- bool? chooseMin = Choose(minRatio, maxRatio, proportion);
-
- // if no def was chosen, advance to phase 4; the current proportion doesn't
- // conflict with any min or max values.
- if (!(chooseMin.HasValue))
- {
- break;
- }
-
- // get the chosen definition and its resolved size
- int resolvedIndex;
- DefinitionBase resolvedDef;
- double resolvedSize;
- if (chooseMin == true)
- {
- resolvedIndex = definitionIndices[minCount - 1];
- resolvedDef = definitions[resolvedIndex];
- resolvedSize = resolvedDef.MinSize;
- --minCount;
- }
- else
- {
- resolvedIndex = definitionIndices[defCount + maxCount - 1];
- resolvedDef = definitions[resolvedIndex];
- resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
- --maxCount;
- }
-
- // resolve the chosen def, deduct its contributions from W and S.
- // Defs resolved in phase 3 are marked by storing the negative of their resolved
- // size in MeasureSize, to distinguish them from a pending def.
- takenSize += resolvedSize;
- resolvedDef.MeasureSize = -resolvedSize;
- takenStarWeight += StarWeight(resolvedDef, scale);
- --starCount;
-
- remainingAvailableSize = finalSize - takenSize;
- remainingStarWeight = totalStarWeight - takenStarWeight;
-
- // advance to the next candidate defs, removing ones that have been resolved.
- // Both counts are advanced, as a def might appear in both lists.
- while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0)
- {
- --minCount;
- definitionIndices[minCount] = -1;
- }
- while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0)
- {
- --maxCount;
- definitionIndices[defCount + maxCount] = -1;
- }
- }
-
- // decide whether to run Phase2 and Phase3 again. There are 3 cases:
- // 1. There is space available, and *-defs remaining. This is the
- // normal case - move on to Phase 4 to allocate the remaining
- // space proportionally to the remaining *-defs.
- // 2. There is space available, but no *-defs. This implies at least one
- // def was resolved as 'max', taking less space than its proportion.
- // If there are also 'min' defs, reconsider them - we can give
- // them more space. If not, all the *-defs are 'max', so there's
- // no way to use all the available space.
- // 3. We allocated too much space. This implies at least one def was
- // resolved as 'min'. If there are also 'max' defs, reconsider
- // them, otherwise the over-allocation is an inevitable consequence
- // of the given min constraints.
- // Note that if we return to Phase2, at least one *-def will have been
- // resolved. This guarantees we don't run Phase2+3 infinitely often.
- runPhase2and3 = false;
- if (starCount == 0 && takenSize < finalSize)
- {
- // if no *-defs remain and we haven't allocated all the space, reconsider the defs
- // resolved as 'min'. Their allocation can be increased to make up the gap.
- for (int i = minCount; i < minCountPhase2; ++i)
- {
- if (definitionIndices[i] >= 0)
- {
- DefinitionBase def = definitions[definitionIndices[i]];
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
-
- if (takenSize > finalSize)
- {
- // if we've allocated too much space, reconsider the defs
- // resolved as 'max'. Their allocation can be decreased to make up the gap.
- for (int i = maxCount; i < maxCountPhase2; ++i)
- {
- if (definitionIndices[defCount + i] >= 0)
- {
- DefinitionBase def = definitions[definitionIndices[defCount + i]];
- def.MeasureSize = 1.0; // mark as 'not yet resolved'
- ++starCount;
- runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3
- }
- }
- }
- }
-
- // Phase 4. Resolve the remaining defs proportionally.
- starCount = 0;
- for (int i = 0; i < defCount; ++i)
- {
- DefinitionBase def = definitions[i];
-
- if (def.UserSize.IsStar)
- {
- if (def.MeasureSize < 0.0)
- {
- // this def was resolved in phase 3 - fix up its size
- def.SizeCache = -def.MeasureSize;
- }
- else
- {
- // this def needs resolution, add it to the list, sorted by *-weight
- definitionIndices[starCount++] = i;
- def.MeasureSize = StarWeight(def, scale);
- }
- }
- }
-
- if (starCount > 0)
- {
- StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions);
- Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer);
-
- // compute the partial sums of *-weight, in increasing order of weight
- // for minimal loss of precision.
- totalStarWeight = 0.0;
- for (int i = 0; i < starCount; ++i)
- {
- DefinitionBase def = definitions[definitionIndices[i]];
- totalStarWeight += def.MeasureSize;
- def.SizeCache = totalStarWeight;
- }
-
- // resolve the defs, in decreasing order of weight.
- for (int i = starCount - 1; i >= 0; --i)
- {
- DefinitionBase def = definitions[definitionIndices[i]];
- double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
-
- // min and max should have no effect by now, but just in case...
- resolvedSize = Math.Min(resolvedSize, def.UserMaxSize);
- resolvedSize = Math.Max(def.MinSize, resolvedSize);
-
- // Use the raw (unrounded) sizes to update takenSize, so that
- // proportions are computed in the same terms as in phase 3;
- // this avoids errors arising from min/max constraints.
- takenSize += resolvedSize;
- def.SizeCache = resolvedSize;
- }
- }
-
- // Phase 5. Apply layout rounding. We do this after fully allocating
- // unrounded sizes, to avoid breaking assumptions in the previous phases
- if (UseLayoutRounding)
- {
- var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
-
- double[] roundingErrors = RoundingErrors;
- double roundedTakenSize = 0.0;
-
- // round each of the allocated sizes, keeping track of the deltas
- for (int i = 0; i < definitions.Length; ++i)
- {
- DefinitionBase def = definitions[i];
- double roundedSize = RoundLayoutValue(def.SizeCache, dpi);
- roundingErrors[i] = (roundedSize - def.SizeCache);
- def.SizeCache = roundedSize;
- roundedTakenSize += roundedSize;
- }
-
- // The total allocation might differ from finalSize due to rounding
- // effects. Tweak the allocations accordingly.
-
- // Theoretical and historical note. The problem at hand - allocating
- // space to columns (or rows) with *-weights, min and max constraints,
- // and layout rounding - has a long history. Especially the special
- // case of 50 columns with min=1 and available space=435 - allocating
- // seats in the U.S. House of Representatives to the 50 states in
- // proportion to their population. There are numerous algorithms
- // and papers dating back to the 1700's, including the book:
- // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982.
- //
- // One surprising result of all this research is that *any* algorithm
- // will suffer from one or more undesirable features such as the
- // "population paradox" or the "Alabama paradox", where (to use our terminology)
- // increasing the available space by one pixel might actually decrease
- // the space allocated to a given column, or increasing the weight of
- // a column might decrease its allocation. This is worth knowing
- // in case someone complains about this behavior; it's not a bug so
- // much as something inherent to the problem. Cite the book mentioned
- // above or one of the 100s of references, and resolve as WontFix.
- //
- // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer)
- // each being allocated a large number of pixels (~50 or greater), and
- // people don't even notice the kind of 1-pixel anomolies that are
- // theoretically inevitable, or don't care if they do. At least they shouldn't
- // care - no one should be using the results WPF's grid layout to make
- // quantitative decisions; its job is to produce a reasonable display, not
- // to allocate seats in Congress.
- //
- // Our algorithm is more susceptible to paradox than the one currently
- // used for Congressional allocation ("Huntington-Hill" algorithm), but
- // it is faster to run: O(N log N) vs. O(S * N), where N=number of
- // definitions, S = number of available pixels. And it produces
- // adequate results in practice, as mentioned above.
- //
- // To reiterate one point: all this only applies when layout rounding
- // is in effect. When fractional sizes are allowed, the algorithm
- // behaves as well as possible, subject to the min/max constraints
- // and precision of floating-point computation. (However, the resulting
- // display is subject to anti-aliasing problems. TANSTAAFL.)
-
- if (!MathUtilities.AreClose(roundedTakenSize, finalSize))
- {
- // Compute deltas
- for (int i = 0; i < definitions.Length; ++i)
- {
- definitionIndices[i] = i;
- }
-
- // Sort rounding errors
- RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors);
- Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer);
- double adjustedSize = roundedTakenSize;
- double dpiIncrement = 1.0 / dpi;
-
- if (roundedTakenSize > finalSize)
- {
- int i = definitions.Length - 1;
- while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0)
- {
- DefinitionBase definition = definitions[definitionIndices[i]];
- double final = definition.SizeCache - dpiIncrement;
- final = Math.Max(final, definition.MinSize);
- if (final < definition.SizeCache)
- {
- adjustedSize -= dpiIncrement;
- }
- definition.SizeCache = final;
- i--;
- }
- }
- else if (roundedTakenSize < finalSize)
- {
- int i = 0;
- while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Length)
- {
- DefinitionBase definition = definitions[definitionIndices[i]];
- double final = definition.SizeCache + dpiIncrement;
- final = Math.Max(final, definition.MinSize);
- if (final > definition.SizeCache)
- {
- adjustedSize += dpiIncrement;
- }
- definition.SizeCache = final;
- i++;
- }
- }
- }
- }
-
- // Phase 6. Compute final offsets
- definitions[0].FinalOffset = 0.0;
- for (int i = 0; i < definitions.Length; ++i)
- {
- definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache;
- }
- }
-
- // Choose the ratio with maximum discrepancy from the current proportion.
- // Returns:
- // true if proportion fails a min constraint but not a max, or
- // if the min constraint has higher discrepancy
- // false if proportion fails a max constraint but not a min, or
- // if the max constraint has higher discrepancy
- // null if proportion doesn't fail a min or max constraint
- // The discrepancy is the ratio of the proportion to the max- or min-ratio.
- // When both ratios hit the constraint, minRatio < proportion < maxRatio,
- // and the minRatio has higher discrepancy if
- // (proportion / minRatio) > (maxRatio / proportion)
- private static bool? Choose(double minRatio, double maxRatio, double proportion)
- {
- if (minRatio < proportion)
- {
- if (maxRatio > proportion)
- {
- // compare proportion/minRatio : maxRatio/proportion, but
- // do it carefully to avoid floating-point overflow or underflow
- // and divide-by-0.
- double minPower = Math.Floor(Math.Log(minRatio, 2.0));
- double maxPower = Math.Floor(Math.Log(maxRatio, 2.0));
- double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0));
- if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f))
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- else
- {
- return true;
- }
- }
- else if (maxRatio > proportion)
- {
- return false;
- }
-
- return null;
- }
-
- ///
- /// Sorts row/column indices by rounding error if layout rounding is applied.
- ///
- /// Index, rounding error pair
- /// Index, rounding error pair
- /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise
- private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y)
- {
- if (x.Value < y.Value)
- {
- return -1;
- }
- else if (x.Value > y.Value)
- {
- return 1;
- }
- return 0;
- }
-
- ///
- /// Calculates final (aka arrange) size for given range.
- ///
- /// Array of definitions to process.
- /// Start of the range.
- /// Number of items in the range.
- /// Final size.
- private double GetFinalSizeForRange(
- DefinitionBase[] definitions,
- int start,
- int count)
- {
- double size = 0;
- int i = start + count - 1;
-
- do
- {
- size += definitions[i].SizeCache;
- } while (--i >= start);
-
- return (size);
- }
-
- ///
- /// Clears dirty state for the grid and its columns / rows
- ///
- private void SetValid()
- {
- if (IsTrivialGrid)
- {
- if (_tempDefinitions != null)
- {
- // TempDefinitions has to be cleared to avoid "memory leaks"
- Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length));
- _tempDefinitions = null;
- }
- }
- }
-
-
- ///
- /// Synchronized ShowGridLines property with the state of the grid's visual collection
- /// by adding / removing GridLinesRenderer visual.
- /// Returns a reference to GridLinesRenderer visual or null.
- ///
- private GridLinesRenderer EnsureGridLinesRenderer()
- {
- //
- // synchronize the state
- //
- if (ShowGridLines && (_gridLinesRenderer == null))
- {
- _gridLinesRenderer = new GridLinesRenderer();
- this.VisualChildren.Add(_gridLinesRenderer);
- }
-
- if ((!ShowGridLines) && (_gridLinesRenderer != null))
- {
- this.VisualChildren.Remove(_gridLinesRenderer);
- _gridLinesRenderer = null;
- }
-
- return (_gridLinesRenderer);
- }
-
- private double RoundLayoutValue(double value, double dpiScale)
- {
- double newValue;
-
- // If DPI == 1, don't use DPI-aware rounding.
- if (!MathUtilities.AreClose(dpiScale, 1.0))
- {
- newValue = Math.Round(value * dpiScale) / dpiScale;
- // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
- if (double.IsNaN(newValue) ||
- double.IsInfinity(newValue) ||
- MathUtilities.AreClose(newValue, double.MaxValue))
- {
- newValue = value;
- }
- }
- else
- {
- newValue = Math.Round(value);
- }
-
- return newValue;
- }
-
-
- private static int ValidateColumn(AvaloniaObject o, int value)
- {
- if (value < 0)
- {
- throw new ArgumentException("Invalid Grid.Column value.");
- }
-
- return value;
- }
-
- private static int ValidateRow(AvaloniaObject o, int value)
- {
- if (value < 0)
- {
- throw new ArgumentException("Invalid Grid.Row value.");
- }
-
- return value;
- }
-
- private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e)
- {
- if (!grid.IsTrivialGrid) // trivial grid is 1 by 1. there is no grid lines anyway
- {
- grid.Invalidate();
- }
- }
-
- ///
- /// Helper for Comparer methods.
- ///
- ///
- /// true if one or both of x and y are null, in which case result holds
- /// the relative sort order.
- ///
- private static bool CompareNullRefs(object x, object y, out int result)
- {
- result = 2;
-
- if (x == null)
- {
- if (y == null)
- {
- result = 0;
- }
- else
- {
- result = -1;
- }
- }
- else
- {
- if (y == null)
- {
- result = 1;
- }
- }
-
- return (result != 2);
- }
-
- ///
- /// Helper accessor to layout time array of definitions.
- ///
- private DefinitionBase[] TempDefinitions
- {
- get
- {
- int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2;
-
- if (_tempDefinitions == null
- || _tempDefinitions.Length < requiredLength)
- {
- WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot);
- if (tempDefinitionsWeakRef == null)
- {
- _tempDefinitions = new DefinitionBase[requiredLength];
- Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions));
- }
- else
- {
- _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target;
- if (_tempDefinitions == null
- || _tempDefinitions.Length < requiredLength)
- {
- _tempDefinitions = new DefinitionBase[requiredLength];
- tempDefinitionsWeakRef.Target = _tempDefinitions;
- }
- }
- }
- return (_tempDefinitions);
- }
- }
-
- ///
- /// Helper accessor to definition indices.
- ///
- private int[] DefinitionIndices
- {
- get
- {
- int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2;
-
- if (_definitionIndices == null || _definitionIndices.Length < requiredLength)
- {
- _definitionIndices = new int[requiredLength];
- }
-
- return _definitionIndices;
- }
- }
-
- ///
- /// Helper accessor to rounding errors.
- ///
- private double[] RoundingErrors
- {
- get
- {
- int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length);
-
- if (_roundingErrors == null && requiredLength == 0)
- {
- _roundingErrors = new double[1];
- }
- else if (_roundingErrors == null || _roundingErrors.Length < requiredLength)
- {
- _roundingErrors = new double[requiredLength];
- }
- return _roundingErrors;
- }
- }
-
- ///
- /// Returns *-weight, adjusted for scale computed during Phase 1
- ///
- static double StarWeight(DefinitionBase def, double scale)
- {
- if (scale < 0.0)
- {
- // if one of the *-weights is Infinity, adjust the weights by mapping
- // Infinty to 1.0 and everything else to 0.0: the infinite items share the
- // available space equally, everyone else gets nothing.
- return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0;
- }
- else
- {
- return def.UserSize.Value * scale;
- }
- }
-
- ///
- /// LayoutTimeSizeType is used internally and reflects layout-time size type.
- ///
- [System.Flags]
- internal enum LayoutTimeSizeType : byte
- {
- None = 0x00,
- Pixel = 0x01,
- Auto = 0x02,
- Star = 0x04,
- }
-
- ///
- /// CellCache stored calculated values of
- /// 1. attached cell positioning properties;
- /// 2. size type;
- /// 3. index of a next cell in the group;
- ///
- private struct CellCache
- {
- internal int ColumnIndex;
- internal int RowIndex;
- internal int ColumnSpan;
- internal int RowSpan;
- internal LayoutTimeSizeType SizeTypeU;
- internal LayoutTimeSizeType SizeTypeV;
- internal int Next;
- internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } }
- internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } }
- internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } }
- internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
- }
-
- ///
- /// Helper class for representing a key for a span in hashtable.
- ///
- private class SpanKey
- {
- ///
- /// Constructor.
- ///
- /// Starting index of the span.
- /// Span count.
- /// true for columns; false for rows.
- internal SpanKey(int start, int count, bool u)
- {
- _start = start;
- _count = count;
- _u = u;
- }
-
- ///
- ///
- ///
- public override int GetHashCode()
- {
- int hash = (_start ^ (_count << 2));
-
- if (_u) hash &= 0x7ffffff;
- else hash |= 0x8000000;
-
- return (hash);
- }
-
- ///
- ///
- ///
- public override bool Equals(object obj)
- {
- SpanKey sk = obj as SpanKey;
- return (sk != null
- && sk._start == _start
- && sk._count == _count
- && sk._u == _u);
- }
-
- ///
- /// Returns start index of the span.
- ///
- internal int Start { get { return (_start); } }
-
- ///
- /// Returns span count.
- ///
- internal int Count { get { return (_count); } }
-
- ///
- /// Returns true if this is a column span.
- /// false if this is a row span.
- ///
- internal bool U { get { return (_u); } }
-
- private int _start;
- private int _count;
- private bool _u;
- }
-
- ///
- /// SpanPreferredDistributionOrderComparer.
- ///
- private class SpanPreferredDistributionOrderComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- if (definitionX.UserSize.IsAuto)
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = definitionX.MinSize.CompareTo(definitionY.MinSize);
- }
- else
- {
- result = -1;
- }
- }
- else
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = +1;
- }
- else
- {
- result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize);
- }
- }
- }
-
- return result;
- }
- }
-
- ///
- /// SpanMaxDistributionOrderComparer.
- ///
- private class SpanMaxDistributionOrderComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- if (definitionX.UserSize.IsAuto)
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
- else
- {
- result = +1;
- }
- }
- else
- {
- if (definitionY.UserSize.IsAuto)
- {
- result = -1;
- }
- else
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
- }
- }
-
- return result;
- }
- }
-
- ///
- /// RoundingErrorIndexComparer.
- ///
- private class RoundingErrorIndexComparer : IComparer
- {
- private readonly double[] errors;
-
- internal RoundingErrorIndexComparer(double[] errors)
- {
- Contract.Requires(errors != null);
- this.errors = errors;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- int result;
-
- if (!CompareNullRefs(indexX, indexY, out result))
- {
- double errorX = errors[indexX.Value];
- double errorY = errors[indexY.Value];
- result = errorX.CompareTo(errorY);
- }
-
- return result;
- }
- }
-
- ///
- /// MinRatioComparer.
- /// Sort by w/min (stored in MeasureSize), descending.
- /// We query the list from the back, i.e. in ascending order of w/min.
- ///
- private class MinRatioComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!CompareNullRefs(definitionY, definitionX, out result))
- {
- result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
- }
-
- return result;
- }
- }
-
- ///
- /// MaxRatioComparer.
- /// Sort by w/max (stored in SizeCache), ascending.
- /// We query the list from the back, i.e. in descending order of w/max.
- ///
- private class MaxRatioComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
-
- return result;
- }
- }
-
- ///
- /// StarWeightComparer.
- /// Sort by *-weight (stored in MeasureSize), ascending.
- ///
- private class StarWeightComparer : IComparer
- {
- public int Compare(object x, object y)
- {
- DefinitionBase definitionX = x as DefinitionBase;
- DefinitionBase definitionY = y as DefinitionBase;
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
- }
-
- return result;
- }
- }
-
- ///
- /// MinRatioIndexComparer.
- ///
- private class MinRatioIndexComparer : IComparer
- {
- private readonly DefinitionBase[] definitions;
-
- internal MinRatioIndexComparer(DefinitionBase[] definitions)
- {
- Contract.Requires(definitions != null);
- this.definitions = definitions;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase definitionX = null;
- DefinitionBase definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!CompareNullRefs(definitionY, definitionX, out result))
- {
- result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize);
- }
-
- return result;
- }
- }
-
- ///
- /// MaxRatioIndexComparer.
- ///
- private class MaxRatioIndexComparer : IComparer
- {
- private readonly DefinitionBase[] definitions;
-
- internal MaxRatioIndexComparer(DefinitionBase[] definitions)
- {
- Contract.Requires(definitions != null);
- this.definitions = definitions;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase definitionX = null;
- DefinitionBase definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.SizeCache.CompareTo(definitionY.SizeCache);
- }
-
- return result;
- }
- }
-
- ///
- /// MaxRatioIndexComparer.
- ///
- private class StarWeightIndexComparer : IComparer
- {
- private readonly DefinitionBase[] definitions;
-
- internal StarWeightIndexComparer(DefinitionBase[] definitions)
- {
- Contract.Requires(definitions != null);
- this.definitions = definitions;
- }
-
- public int Compare(object x, object y)
- {
- int? indexX = x as int?;
- int? indexY = y as int?;
-
- DefinitionBase definitionX = null;
- DefinitionBase definitionY = null;
-
- if (indexX != null)
- {
- definitionX = definitions[indexX.Value];
- }
- if (indexY != null)
- {
- definitionY = definitions[indexY.Value];
- }
-
- int result;
-
- if (!CompareNullRefs(definitionX, definitionY, out result))
- {
- result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize);
- }
-
- return result;
- }
- }
-
- ///
- /// Helper to render grid lines.
- ///
- private class GridLinesRenderer : Control
- {
- ///
- /// Static initialization
- ///
- static GridLinesRenderer()
- {
- var oddDashArray = new List();
- oddDashArray.Add(c_dashLength);
- oddDashArray.Add(c_dashLength);
- var ds1 = new DashStyle(oddDashArray, 0);
- s_oddDashPen = new Pen(Brushes.Blue,
- c_penWidth,
- lineCap: PenLineCap.Flat,
- dashStyle: ds1);
-
- var evenDashArray = new List();
- evenDashArray.Add(c_dashLength);
- evenDashArray.Add(c_dashLength);
- var ds2 = new DashStyle(evenDashArray, 0);
- s_evenDashPen = new Pen(Brushes.Yellow,
- c_penWidth,
- lineCap: PenLineCap.Flat,
- dashStyle: ds2);
- }
-
- ///
- /// UpdateRenderBounds.
- ///
- public override void Render(DrawingContext drawingContext)
- {
- var grid = this.GetVisualParent();
-
- if (grid == null
- || !grid.ShowGridLines
- || grid.IsTrivialGrid)
- {
- return;
- }
-
- for (int i = 1; i < grid.ColumnDefinitions.Count; ++i)
- {
- DrawGridLine(
- drawingContext,
- grid.ColumnDefinitions[i].FinalOffset, 0.0,
- grid.ColumnDefinitions[i].FinalOffset, lastArrangeSize.Height);
- }
-
- for (int i = 1; i < grid.RowDefinitions.Count; ++i)
- {
- DrawGridLine(
- drawingContext,
- 0.0, grid.RowDefinitions[i].FinalOffset,
- lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset);
- }
- }
-
- ///
- /// Draw single hi-contrast line.
- ///
- private static void DrawGridLine(
- DrawingContext drawingContext,
- double startX,
- double startY,
- double endX,
- double endY)
- {
- var start = new Point(startX, startY);
- var end = new Point(endX, endY);
- drawingContext.DrawLine(s_oddDashPen, start, end);
- drawingContext.DrawLine(s_evenDashPen, start, end);
- }
-
- internal void UpdateRenderBounds(Size arrangeSize)
- {
- lastArrangeSize = arrangeSize;
- this.InvalidateVisual();
- }
-
- private static Size lastArrangeSize;
- private const double c_dashLength = 4.0; //
- private const double c_penWidth = 1.0; //
- private static readonly Pen s_oddDashPen; // first pen to draw dash
- private static readonly Pen s_evenDashPen; // second pen to draw dash
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs
deleted file mode 100644
index 7704228a4e..0000000000
--- a/src/Avalonia.Controls/Utils/GridLayout.cs
+++ /dev/null
@@ -1,705 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using Avalonia.Layout;
-using JetBrains.Annotations;
-
-namespace Avalonia.Controls.Utils
-{
- ///
- /// Contains algorithms that can help to measure and arrange a Grid.
- ///
- internal class GridLayout
- {
- ///
- /// Initialize a new instance from the column definitions.
- /// The instance doesn't care about whether the definitions are rows or columns.
- /// It will not calculate the column or row differently.
- ///
- internal GridLayout([NotNull] ColumnDefinitions columns)
- {
- if (columns == null) throw new ArgumentNullException(nameof(columns));
- _conventions = columns.Count == 0
- ? new List { new LengthConvention() }
- : columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList();
- }
-
- ///
- /// Initialize a new instance from the row definitions.
- /// The instance doesn't care about whether the definitions are rows or columns.
- /// It will not calculate the column or row differently.
- ///
- internal GridLayout([NotNull] RowDefinitions rows)
- {
- if (rows == null) throw new ArgumentNullException(nameof(rows));
- _conventions = rows.Count == 0
- ? new List { new LengthConvention() }
- : rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList();
- }
-
- ///
- /// Gets the layout tolerance. If any length offset is less than this value, we will treat them the same.
- ///
- private const double LayoutTolerance = 1.0 / 256.0;
-
- ///
- /// Gets all the length conventions that come from column/row definitions.
- /// These conventions provide cell limitations, such as the expected pixel length, the min/max pixel length and the * count.
- ///
- [NotNull]
- private readonly List _conventions;
-
- ///
- /// Gets all the length conventions that come from the grid children.
- ///
- [NotNull]
- private readonly List _additionalConventions =
- new List();
-
- ///
- /// Appending these elements into the convention list helps lay them out according to their desired sizes.
- ///
- /// Some elements are not only in a single grid cell, they have one or more column/row spans,
- /// and these elements may affect the grid layout especially the measuring procedure.
- /// Append these elements into the convention list can help to layout them correctly through
- /// their desired size. Only a small subset of children need to be measured before layout starts
- /// and they will be called via the callback.
- ///
- /// The grid children type.
- ///
- /// Contains the safe column/row index and its span.
- /// Notice that we will not verify whether the range is in the column/row count,
- /// so you should get the safe column/row info first.
- ///
- ///
- /// This callback will be called if the thinks that a child should be
- /// measured first. Usually, these are the children that have the * or Auto length.
- ///
- internal void AppendMeasureConventions([NotNull] IDictionary source,
- [NotNull] Func getDesiredLength)
- {
- if (source == null) throw new ArgumentNullException(nameof(source));
- if (getDesiredLength == null) throw new ArgumentNullException(nameof(getDesiredLength));
-
- // M1/7. Find all the Auto and * length columns/rows. (M1/7 means the 1st procedure of measurement.)
- // Only these columns/rows' layout can be affected by the child desired size.
- //
- // Find all columns/rows that have Auto or * length. We'll measure the children in advance.
- // Only these kind of columns/rows will affect the Grid layout.
- // Please note:
- // - If the column / row has Auto length, the Grid.DesiredSize and the column width
- // will be affected by the child's desired size.
- // - If the column / row has* length, the Grid.DesiredSize will be affected by the
- // child's desired size but the column width not.
-
- // +-----------------------------------------------------------+
- // | * | A | * | P | A | * | P | * | * |
- // +-----------------------------------------------------------+
- // _conventions: | min | max | | | min | | min max | max |
- // _additionalC: |<- desired ->| |< desired >|
- // _additionalC: |< desired >| |<- desired ->|
-
- // 寻找所有行列范围中包含 Auto 和 * 的元素,使用全部可用尺寸提前测量。
- // 因为只有这部分元素的布局才会被 Grid 的子元素尺寸影响。
- // 请注意:
- // - Auto 长度的行列必定会受到子元素布局影响,会影响到行列的布局长度和 Grid 本身的 DesiredSize;
- // - 而对于 * 长度,只有 Grid.DesiredSize 会受到子元素布局影响,而行列长度不会受影响。
-
- // Find all the Auto and * length columns/rows.
- var found = new Dictionary();
- for (var i = 0; i < _conventions.Count; i++)
- {
- var index = i;
- var convention = _conventions[index];
- if (convention.Length.IsAuto || convention.Length.IsStar)
- {
- foreach (var pair in source.Where(x =>
- x.Value.index <= index && index < x.Value.index + x.Value.span))
- {
- found[pair.Key] = pair.Value;
- }
- }
- }
-
- // Append these layout into the additional convention list.
- foreach (var pair in found)
- {
- var t = pair.Key;
- var (index, span) = pair.Value;
- var desiredLength = getDesiredLength(t);
- if (Math.Abs(desiredLength) > LayoutTolerance)
- {
- _additionalConventions.Add(new AdditionalLengthConvention(index, span, desiredLength));
- }
- }
- }
-
- ///
- /// Run measure procedure according to the and gets the .
- ///
- ///
- /// The container length. Usually, it is the constraint of the method.
- ///
- ///
- /// Overriding conventions that allows the algorithm to handle external inputa
- ///
- ///
- /// The measured result that containing the desired size and all the column/row lengths.
- ///
- [NotNull, Pure]
- internal MeasureResult Measure(double containerLength, IReadOnlyList conventions = null)
- {
- // Prepare all the variables that this method needs to use.
- conventions = conventions ?? _conventions.Select(x => x.Clone()).ToList();
- var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
- var aggregatedLength = 0.0;
- double starUnitLength;
-
- // M2/7. Aggregate all the pixel lengths. Then we can get the remaining length by `containerLength - aggregatedLength`.
- // We mark the aggregated length as "fix" because we can completely determine their values. Same as below.
- //
- // +-----------------------------------------------------------+
- // | * | A | * | P | A | * | P | * | * |
- // +-----------------------------------------------------------+
- // |#fix#| |#fix#|
- //
- // 将全部的固定像素长度的行列长度累加。这样,containerLength - aggregatedLength 便能得到剩余长度。
- // 我们会将所有能够确定下长度的行列标记为 fix。下同。
- // 请注意:
- // - 我们并没有直接从 containerLength 一直减下去,而是使用 aggregatedLength 进行累加,是因为无穷大相减得到的是 NaN,不利于后续计算。
-
- aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
-
- // M3/7. Fix all the * lengths that have reached the minimum.
- //
- // +-----------------------------------------------------------+
- // | * | A | * | P | A | * | P | * | * |
- // +-----------------------------------------------------------+
- // | min | max | | | min | | min max | max |
- // | fix | |#fix#| fix |
-
- var shouldTestStarMin = true;
- while (shouldTestStarMin)
- {
- // Calculate the unit * length to estimate the length of each column/row that has * length.
- // Under this estimated length, check if there is a minimum value that has a length less than its constraint.
- // If there is such a *, then fix the size of this cell, and then loop it again until there is no * that can be constrained by the minimum value.
- //
- // 计算单位 * 的长度,以便预估出每一个 * 行列的长度。
- // 在此预估的长度下,从前往后寻找是否存在某个 * 长度已经小于其约束的最小值。
- // 如果发现存在这样的 *,那么将此单元格的尺寸固定下来(Fix),然后循环重来,直至再也没有能被最小值约束的 *。
- var @fixed = false;
- starUnitLength = (containerLength - aggregatedLength) / starCount;
- foreach (var convention in conventions.Where(x => x.Length.IsStar))
- {
- var (star, min) = (convention.Length.Value, convention.MinLength);
- var starLength = star * starUnitLength;
- if (starLength < min)
- {
- convention.Fix(min);
- starLength = min;
- aggregatedLength += starLength;
- starCount -= star;
- @fixed = true;
- break;
- }
- }
-
- shouldTestStarMin = @fixed;
- }
-
- // M4/7. Determine the absolute pixel size of all columns/rows that have an Auto length.
- //
- // +-----------------------------------------------------------+
- // | * | A | * | P | A | * | P | * | * |
- // +-----------------------------------------------------------+
- // | min | max | | | min | | min max | max |
- // |#fix#| | fix |#fix#| fix | fix |
-
- var shouldTestAuto = true;
- while (shouldTestAuto)
- {
- var @fixed = false;
- starUnitLength = (containerLength - aggregatedLength) / starCount;
- for (var i = 0; i < conventions.Count; i++)
- {
- var convention = conventions[i];
- if (!convention.Length.IsAuto)
- {
- continue;
- }
-
- var more = ApplyAdditionalConventionsForAuto(conventions, i, starUnitLength);
- convention.Fix(more);
- aggregatedLength += more;
- @fixed = true;
- break;
- }
-
- shouldTestAuto = @fixed;
- }
-
- // M5/7. Expand the stars according to the additional conventions (usually the child desired length).
- // We can't fix this kind of length, so we just mark them as desired (des).
- //
- // +-----------------------------------------------------------+
- // | * | A | * | P | A | * | P | * | * |
- // +-----------------------------------------------------------+
- // | min | max | | | min | | min max | max |
- // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#|
-
- var (minLengths, desiredStarMin) = AggregateAdditionalConventionsForStars(conventions);
- aggregatedLength += desiredStarMin;
-
- // M6/7. Determine the desired length of the grid for current container length. Its value is stored in desiredLength.
- // Assume if the container has infinite length, the grid desired length is stored in greedyDesiredLength.
- //
- // +-----------------------------------------------------------+
- // | * | A | * | P | A | * | P | * | * |
- // +-----------------------------------------------------------+
- // | min | max | | | min | | min max | max |
- // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#|
- // Note: This table will be stored as the intermediate result into the MeasureResult and it will be reused by Arrange procedure.
- //
- // desiredLength = Math.Max(0.0, des + fix + des + fix + fix + fix + fix + des + des)
- // greedyDesiredLength = des + fix + des + fix + fix + fix + fix + des + des
-
- var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength;
- var greedyDesiredLength = aggregatedLength;
-
- // M7/7. Expand all the rest stars. These stars have no conventions or only have
- // max value they can be expanded from zero to constraint.
- //
- // +-----------------------------------------------------------+
- // | * | A | * | P | A | * | P | * | * |
- // +-----------------------------------------------------------+
- // | min | max | | | min | | min max | max |
- // |#fix#| fix |#fix#| fix | fix | fix | fix | #fix# |#fix#|
- // Note: This table will be stored as the final result into the MeasureResult.
-
- var dynamicConvention = ExpandStars(conventions, containerLength);
- Clip(dynamicConvention, containerLength);
-
- // Returns the measuring result.
- return new MeasureResult(containerLength, desiredLength, greedyDesiredLength,
- conventions, dynamicConvention, minLengths);
- }
-
- ///
- /// Run arrange procedure according to the and gets the .
- ///
- ///
- /// The container length. Usually, it is the finalSize of the method.
- ///
- ///
- /// The result that the measuring procedure returns. If it is null, a new measure procedure will run.
- ///
- ///
- /// The measured result that containing the desired size and all the column/row length.
- ///
- [NotNull, Pure]
- public ArrangeResult Arrange(double finalLength, [CanBeNull] MeasureResult measure)
- {
- measure = measure ?? Measure(finalLength);
-
- // If the arrange final length does not equal to the measure length, we should measure again.
- if (finalLength - measure.ContainerLength > LayoutTolerance)
- {
- // If the final length is larger, we will rerun the whole measure.
- measure = Measure(finalLength, measure.LeanLengthList);
- }
- else if (finalLength - measure.ContainerLength < -LayoutTolerance)
- {
- // If the final length is smaller, we measure the M6/6 procedure only.
- var dynamicConvention = ExpandStars(measure.LeanLengthList, finalLength);
- measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength,
- measure.LeanLengthList, dynamicConvention, measure.MinLengths);
- }
-
- return new ArrangeResult(measure.LengthList);
- }
-
- ///
- /// Use the to calculate the fixed length of the Auto column/row.
- ///
- /// The convention list that all the * with minimum length are fixed.
- /// The column/row index that should be fixed.
- /// The unit * length for the current rest length.
- /// The final length of the Auto length column/row.
- [Pure]
- private double ApplyAdditionalConventionsForAuto(IReadOnlyList conventions,
- int index, double starUnitLength)
- {
- // 1. Calculate all the * length with starUnitLength.
- // 2. Exclude all the fixed length and all the * length.
- // 3. Compare the rest of the desired length and the convention.
- // +-----------------+
- // | * | A | * |
- // +-----------------+
- // | exl | | exl |
- // |< desired >|
- // |< desired >|
-
- var more = 0.0;
- foreach (var additional in _additionalConventions)
- {
- // If the additional convention's last column/row contains the Auto column/row, try to determine the Auto column/row length.
- if (index == additional.Index + additional.Span - 1)
- {
- var min = Enumerable.Range(additional.Index, additional.Span)
- .Select(x =>
- {
- var c = conventions[x];
- if (c.Length.IsAbsolute) return c.Length.Value;
- if (c.Length.IsStar) return c.Length.Value * starUnitLength;
- return 0.0;
- }).Sum();
- more = Math.Max(additional.Min - min, more);
- }
- }
-
- return Math.Min(conventions[index].MaxLength, more);
- }
-
- ///
- /// Calculate the total desired length of all the * length.
- /// Bug Warning:
- /// - The behavior of this method is undefined! Different UI Frameworks have different behaviors.
- /// - We ignore all the span columns/rows and just take single cells into consideration.
- ///
- /// All the conventions that have almost been fixed except the rest *.
- /// The total desired length of all the * length.
- [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
- private (List, double) AggregateAdditionalConventionsForStars(
- IReadOnlyList conventions)
- {
- // 1. Determine all one-span column's desired widths or row's desired heights.
- // 2. Order the multi-span conventions by its last index
- // (Notice that the sorted data is much smaller than the source.)
- // 3. Determine each multi-span last index by calculating the maximum desired size.
-
- // Before we determine the behavior of this method, we just aggregate the one-span * columns.
-
- var fixedLength = conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
-
- // Prepare a lengthList variable indicating the fixed length of each column/row.
- var lengthList = conventions.Select(x => x.Length.IsAbsolute ? x.Length.Value : 0.0).ToList();
- foreach (var group in _additionalConventions
- .Where(x => x.Span == 1 && conventions[x.Index].Length.IsStar)
- .ToLookup(x => x.Index))
- {
- lengthList[group.Key] = Math.Max(lengthList[group.Key], group.Max(x => x.Min));
- }
-
- // Now the lengthList is fixed by every one-span columns/rows.
- // Then we should determine the multi-span column's/row's length.
- foreach (var group in _additionalConventions
- .Where(x => x.Span > 1)
- .ToLookup(x => x.Index + x.Span - 1)
- // Order the multi-span columns/rows by last index.
- .OrderBy(x => x.Key))
- {
- var length = group.Max(x => x.Min - Enumerable.Range(x.Index, x.Span - 1).Sum(r => lengthList[r]));
- lengthList[group.Key] = Math.Max(lengthList[group.Key], length > 0 ? length : 0);
- }
-
- return (lengthList, lengthList.Sum() - fixedLength);
- }
-
- ///
- /// This method implements the last procedure (M7/7) of measure.
- /// It expands all the * length to the fixed length according to the .
- ///
- /// All the conventions that have almost been fixed except the remaining *.
- /// The container length.
- /// The final pixel length list.
- [Pure]
- private static List ExpandStars(IEnumerable conventions, double constraint)
- {
- // Initial.
- var dynamicConvention = conventions.Select(x => x.Clone()).ToList();
- constraint -= dynamicConvention.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
- var starUnitLength = 0.0;
-
- // M6/6.
- if (constraint >= 0)
- {
- var starCount = dynamicConvention.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
-
- var shouldTestStarMax = true;
- while (shouldTestStarMax)
- {
- var @fixed = false;
- starUnitLength = constraint / starCount;
- foreach (var convention in dynamicConvention.Where(x =>
- x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength)))
- {
- var (star, max) = (convention.Length.Value, convention.MaxLength);
- var starLength = star * starUnitLength;
- if (starLength > max)
- {
- convention.Fix(max);
- starLength = max;
- constraint -= starLength;
- starCount -= star;
- @fixed = true;
- break;
- }
- }
-
- shouldTestStarMax = @fixed;
- }
- }
-
- Debug.Assert(dynamicConvention.All(x => !x.Length.IsAuto));
-
- var starUnit = starUnitLength;
- var result = dynamicConvention.Select(x =>
- {
- if (x.Length.IsStar)
- {
- return double.IsInfinity(starUnit) ? double.PositiveInfinity : starUnit * x.Length.Value;
- }
-
- return x.Length.Value;
- }).ToList();
-
- return result;
- }
-
- ///
- /// If the container length is not infinity. It may be not enough to contain all the columns/rows.
- /// We should clip the columns/rows that have been out of the container bounds.
- /// Note: This method may change the items value of .
- ///
- /// A list of all the column widths and row heights with a fixed pixel length
- /// the container length. It can be positive infinity.
- private static void Clip([NotNull] IList lengthList, double constraint)
- {
- if (double.IsInfinity(constraint))
- {
- return;
- }
-
- var measureLength = 0.0;
- for (var i = 0; i < lengthList.Count; i++)
- {
- var length = lengthList[i];
- if (constraint - measureLength > length)
- {
- measureLength += length;
- }
- else
- {
- lengthList[i] = constraint - measureLength;
- measureLength = constraint;
- }
- }
- }
-
- ///
- /// Contains the convention of each column/row.
- /// This is mostly the same as or .
- /// We use this because we can treat the column and the row the same.
- ///
- [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
- internal class LengthConvention : ICloneable
- {
- ///
- /// Initialize a new instance of .
- ///
- public LengthConvention()
- {
- Length = new GridLength(1.0, GridUnitType.Star);
- MinLength = 0.0;
- MaxLength = double.PositiveInfinity;
- }
-
- ///
- /// Initialize a new instance of .
- ///
- public LengthConvention(GridLength length, double minLength, double maxLength)
- {
- Length = length;
- MinLength = minLength;
- MaxLength = maxLength;
- if (length.IsAbsolute)
- {
- _isFixed = true;
- }
- }
-
- ///
- /// Gets the of a column or a row.
- ///
- internal GridLength Length { get; private set; }
-
- ///
- /// Gets the minimum convention for a column or a row.
- ///
- internal double MinLength { get; }
-
- ///
- /// Gets the maximum convention for a column or a row.
- ///
- internal double MaxLength { get; }
-
- ///
- /// Fix the .
- /// If all columns/rows are fixed, we can get the size of all columns/rows in pixels.
- ///
- ///
- /// The pixel length that should be used to fix the convention.
- ///
- ///
- /// If the convention is pixel length, this exception will throw.
- ///
- public void Fix(double pixel)
- {
- if (_isFixed)
- {
- throw new InvalidOperationException("Cannot fix the length convention if it is fixed.");
- }
-
- Length = new GridLength(pixel);
- _isFixed = true;
- }
-
- ///
- /// Gets a value that indicates whether this convention is fixed.
- ///
- private bool _isFixed;
-
- ///
- /// Helps the debugger to display the intermediate column/row calculation result.
- ///
- private string DebuggerDisplay =>
- $"{(_isFixed ? Length.Value.ToString(CultureInfo.InvariantCulture) : (Length.GridUnitType == GridUnitType.Auto ? "Auto" : $"{Length.Value}*"))}, ∈[{MinLength}, {MaxLength}]";
-
- ///
- object ICloneable.Clone() => Clone();
-
- ///
- /// Get a deep copy of this convention list.
- /// We need this because we want to store some intermediate states.
- ///
- internal LengthConvention Clone() => new LengthConvention(Length, MinLength, MaxLength);
- }
-
- ///
- /// Contains the convention that comes from the grid children.
- /// Some children span multiple columns or rows, so even a simple column/row can have multiple conventions.
- ///
- [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
- internal struct AdditionalLengthConvention
- {
- ///
- /// Initialize a new instance of .
- ///
- public AdditionalLengthConvention(int index, int span, double min)
- {
- Index = index;
- Span = span;
- Min = min;
- }
-
- ///
- /// Gets the start index of this additional convention.
- ///
- public int Index { get; }
-
- ///
- /// Gets the span of this additional convention.
- ///
- public int Span { get; }
-
- ///
- /// Gets the minimum length of this additional convention.
- /// This value is usually provided by the child's desired length.
- ///
- public double Min { get; }
-
- ///
- /// Helps the debugger to display the intermediate column/row calculation result.
- ///
- private string DebuggerDisplay =>
- $"{{{string.Join(",", Enumerable.Range(Index, Span))}}}, ∈[{Min},∞)";
- }
-
- ///
- /// Stores the result of the measuring procedure.
- /// This result can be used to measure children and assign the desired size.
- /// Passing this result to can reduce calculation.
- ///
- [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")]
- internal class MeasureResult
- {
- ///
- /// Initialize a new instance of .
- ///
- internal MeasureResult(double containerLength, double desiredLength, double greedyDesiredLength,
- IReadOnlyList leanConventions, IReadOnlyList expandedConventions, IReadOnlyList minLengths)
- {
- ContainerLength = containerLength;
- DesiredLength = desiredLength;
- GreedyDesiredLength = greedyDesiredLength;
- LeanLengthList = leanConventions;
- LengthList = expandedConventions;
- MinLengths = minLengths;
- }
-
- ///
- /// Gets the container length for this result.
- /// This property will be used by to determine whether to measure again or not.
- ///
- public double ContainerLength { get; }
-
- ///
- /// Gets the desired length of this result.
- /// Just return this value as the desired size in .
- ///
- public double DesiredLength { get; }
-
- ///
- /// Gets the desired length if the container has infinite length.
- ///
- public double GreedyDesiredLength { get; }
-
- ///
- /// Contains the column/row calculation intermediate result.
- /// This value is used by for reducing repeat calculation.
- ///
- public IReadOnlyList LeanLengthList { get; }
-
- ///
- /// Gets the length list for each column/row.
- ///
- public IReadOnlyList LengthList { get; }
- public IReadOnlyList MinLengths { get; }
- }
-
- ///
- /// Stores the result of the measuring procedure.
- /// This result can be used to arrange children and assign the render size.
- ///
- [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")]
- internal class ArrangeResult
- {
- ///
- /// Initialize a new instance of .
- ///
- internal ArrangeResult(IReadOnlyList lengthList)
- {
- LengthList = lengthList;
- }
-
- ///
- /// Gets the length list for each column/row.
- ///
- public IReadOnlyList LengthList { get; }
- }
- }
-}
diff --git a/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs b/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs
deleted file mode 100644
index 5f70557385..0000000000
--- a/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs
+++ /dev/null
@@ -1,651 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Reactive.Disposables;
-using System.Reactive.Subjects;
-using Avalonia.Collections;
-using Avalonia.Controls.Utils;
-using Avalonia.Layout;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Controls
-{
- ///
- /// Shared size scope implementation.
- /// Shares the size information between participating grids.
- /// An instance of this class is attached to every that has its
- /// IsSharedSizeScope property set to true.
- ///
- internal sealed class SharedSizeScopeHost : IDisposable
- {
- private enum MeasurementState
- {
- Invalidated,
- Measuring,
- Cached
- }
-
- ///
- /// Class containing the measured rows/columns for a single grid.
- /// Monitors changes to the row/column collections as well as the SharedSizeGroup changes
- /// for the individual items in those collections.
- /// Notifies the of SharedSizeGroup changes.
- ///
- private sealed class MeasurementCache : IDisposable
- {
- readonly CompositeDisposable _subscriptions;
- readonly Subject<(string, string, MeasurementResult)> _groupChanged = new Subject<(string, string, MeasurementResult)>();
-
- public ISubject<(string oldName, string newName, MeasurementResult result)> GroupChanged => _groupChanged;
-
- public MeasurementCache(Grid grid)
- {
- Grid = grid;
- Results = grid.RowDefinitions.Cast()
- .Concat(grid.ColumnDefinitions)
- .Select(d => new MeasurementResult(grid, d))
- .ToList();
-
- grid.RowDefinitions.CollectionChanged += DefinitionsCollectionChanged;
- grid.ColumnDefinitions.CollectionChanged += DefinitionsCollectionChanged;
-
-
- _subscriptions = new CompositeDisposable(
- Disposable.Create(() => grid.RowDefinitions.CollectionChanged -= DefinitionsCollectionChanged),
- Disposable.Create(() => grid.ColumnDefinitions.CollectionChanged -= DefinitionsCollectionChanged),
- grid.RowDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged),
- grid.ColumnDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged));
-
- }
-
- // method to be hooked up once RowDefinitions/ColumnDefinitions collections can be replaced on a grid
- private void DefinitionsChanged(object sender, AvaloniaPropertyChangedEventArgs e)
- {
- // route to collection changed as a Reset.
- DefinitionsCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- }
-
- private void DefinitionPropertyChanged(Tuple