|
@@ -65,8 +65,8 @@ namespace Avalonia.Controls
|
|
|
private bool _isInLayout;
|
|
|
private bool _isWaitingForViewportUpdate;
|
|
|
private double _lastEstimatedElementSizeU = 25;
|
|
|
- private RealizedElementList? _measureElements;
|
|
|
- private RealizedElementList? _realizedElements;
|
|
|
+ private RealizedStackElements? _measureElements;
|
|
|
+ private RealizedStackElements? _realizedElements;
|
|
|
private Rect _viewport = s_invalidViewport;
|
|
|
private Stack<Control>? _recyclePool;
|
|
|
private Control? _unrealizedFocusedElement;
|
|
@@ -853,447 +853,6 @@ namespace Avalonia.Controls
|
|
|
return snapPoint;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Stores the realized element state for a <see cref="VirtualizingStackPanel"/>.
|
|
|
- /// </summary>
|
|
|
- internal class RealizedElementList
|
|
|
- {
|
|
|
- private int _firstIndex;
|
|
|
- private List<Control?>? _elements;
|
|
|
- private List<double>? _sizes;
|
|
|
- private double _startU;
|
|
|
- private bool _startUUnstable;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the number of realized elements.
|
|
|
- /// </summary>
|
|
|
- public int Count => _elements?.Count ?? 0;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the index of the first realized element, or -1 if no elements are realized.
|
|
|
- /// </summary>
|
|
|
- public int FirstIndex => _elements?.Count > 0 ? _firstIndex : -1;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the index of the last realized element, or -1 if no elements are realized.
|
|
|
- /// </summary>
|
|
|
- public int LastIndex => _elements?.Count > 0 ? _firstIndex + _elements.Count - 1 : -1;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the elements.
|
|
|
- /// </summary>
|
|
|
- public IReadOnlyList<Control?> Elements => _elements ??= new List<Control?>();
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the sizes of the elements on the primary axis.
|
|
|
- /// </summary>
|
|
|
- public IReadOnlyList<double> SizeU => _sizes ??= new List<double>();
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the position of the first element on the primary axis.
|
|
|
- /// </summary>
|
|
|
- public double StartU => _startU;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Adds a newly realized element to the collection.
|
|
|
- /// </summary>
|
|
|
- /// <param name="index">The index of the element.</param>
|
|
|
- /// <param name="element">The element.</param>
|
|
|
- /// <param name="u">The position of the elemnt on the primary axis.</param>
|
|
|
- /// <param name="sizeU">The size of the element on the primary axis.</param>
|
|
|
- public void Add(int index, Control element, double u, double sizeU)
|
|
|
- {
|
|
|
- if (index < 0)
|
|
|
- throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
-
|
|
|
- _elements ??= new List<Control?>();
|
|
|
- _sizes ??= new List<double>();
|
|
|
-
|
|
|
- if (Count == 0)
|
|
|
- {
|
|
|
- _elements.Add(element);
|
|
|
- _sizes.Add(sizeU);
|
|
|
- _startU = u;
|
|
|
- _firstIndex = index;
|
|
|
- }
|
|
|
- else if (index == LastIndex + 1)
|
|
|
- {
|
|
|
- _elements.Add(element);
|
|
|
- _sizes.Add(sizeU);
|
|
|
- }
|
|
|
- else if (index == FirstIndex - 1)
|
|
|
- {
|
|
|
- --_firstIndex;
|
|
|
- _elements.Insert(0, element);
|
|
|
- _sizes.Insert(0, sizeU);
|
|
|
- _startU = u;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- throw new NotSupportedException("Can only add items to the beginning or end of realized elements.");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the element at the specified index, if realized.
|
|
|
- /// </summary>
|
|
|
- /// <param name="index">The index in the source collection of the element to get.</param>
|
|
|
- /// <returns>The element if realized; otherwise null.</returns>
|
|
|
- public Control? GetElement(int index)
|
|
|
- {
|
|
|
- var i = index - FirstIndex;
|
|
|
- if (i >= 0 && i < _elements?.Count)
|
|
|
- return _elements[i];
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the index and start U position of the element at the specified U position.
|
|
|
- /// </summary>
|
|
|
- /// <param name="u">The U position.</param>
|
|
|
- /// <returns>
|
|
|
- /// A tuple containing:
|
|
|
- /// - The index of the item at the specified U position, or -1 if the item could not be
|
|
|
- /// determined
|
|
|
- /// - The U position of the start of the item, if determined
|
|
|
- /// </returns>
|
|
|
- public (int index, double position) GetIndexAt(double u)
|
|
|
- {
|
|
|
- if (_elements is null || _sizes is null || _startU > u || _startUUnstable)
|
|
|
- return (-1, 0);
|
|
|
-
|
|
|
- var index = 0;
|
|
|
- var position = _startU;
|
|
|
-
|
|
|
- while (index < _elements.Count)
|
|
|
- {
|
|
|
- var size = _sizes[index];
|
|
|
- if (double.IsNaN(size))
|
|
|
- break;
|
|
|
- if (u >= position && u < position + size)
|
|
|
- return (index + FirstIndex, position);
|
|
|
- position += size;
|
|
|
- ++index;
|
|
|
- }
|
|
|
-
|
|
|
- return (-1, 0);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the element at the specified position on the primary axis, if realized.
|
|
|
- /// </summary>
|
|
|
- /// <param name="position">The position.</param>
|
|
|
- /// <returns>
|
|
|
- /// A tuple containing the index of the element (or -1 if not found) and the position of the element on the
|
|
|
- /// primary axis.
|
|
|
- /// </returns>
|
|
|
- public (int index, double position) GetElementAt(double position)
|
|
|
- {
|
|
|
- if (_sizes is null || position < StartU)
|
|
|
- return (-1, 0);
|
|
|
-
|
|
|
- var u = StartU;
|
|
|
- var i = FirstIndex;
|
|
|
-
|
|
|
- foreach (var size in _sizes)
|
|
|
- {
|
|
|
- var endU = u + size;
|
|
|
- if (position < endU)
|
|
|
- return (i, u);
|
|
|
- u += size;
|
|
|
- ++i;
|
|
|
- }
|
|
|
-
|
|
|
- return (-1, 0);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Estimates the average U size of all elements in the source collection based on the
|
|
|
- /// realized elements.
|
|
|
- /// </summary>
|
|
|
- /// <returns>
|
|
|
- /// The estimated U size of an element, or -1 if not enough information is present to make
|
|
|
- /// an estimate.
|
|
|
- /// </returns>
|
|
|
- public double EstimateElementSizeU()
|
|
|
- {
|
|
|
- var total = 0.0;
|
|
|
- var divisor = 0.0;
|
|
|
-
|
|
|
- // Start by averaging the size of the elements before the first realized element.
|
|
|
- if (FirstIndex >= 0 && !_startUUnstable)
|
|
|
- {
|
|
|
- total += _startU;
|
|
|
- divisor += FirstIndex;
|
|
|
- }
|
|
|
-
|
|
|
- // Average the size of the realized elements.
|
|
|
- if (_sizes is not null)
|
|
|
- {
|
|
|
- foreach (var size in _sizes)
|
|
|
- {
|
|
|
- if (double.IsNaN(size))
|
|
|
- continue;
|
|
|
- total += size;
|
|
|
- ++divisor;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // We don't have any elements on which to base our estimate.
|
|
|
- if (divisor == 0 || total == 0)
|
|
|
- return -1;
|
|
|
-
|
|
|
- return total / divisor;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the index of the specified element.
|
|
|
- /// </summary>
|
|
|
- /// <param name="element">The element.</param>
|
|
|
- /// <returns>The index or -1 if the element is not present in the collection.</returns>
|
|
|
- public int GetIndex(Control element)
|
|
|
- {
|
|
|
- return _elements?.IndexOf(element) is int index && index >= 0 ? index + FirstIndex : -1;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Updates the elements in response to items being inserted into the source collection.
|
|
|
- /// </summary>
|
|
|
- /// <param name="index">The index in the source collection of the insert.</param>
|
|
|
- /// <param name="count">The number of items inserted.</param>
|
|
|
- /// <param name="updateElementIndex">A method used to update the element indexes.</param>
|
|
|
- public void ItemsInserted(int index, int count, Action<Control, int, int> updateElementIndex)
|
|
|
- {
|
|
|
- if (index < 0)
|
|
|
- throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
- if (_elements is null || _elements.Count == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- // Get the index within the realized _elements collection.
|
|
|
- var first = FirstIndex;
|
|
|
- var realizedIndex = index - first;
|
|
|
-
|
|
|
- if (realizedIndex < Count)
|
|
|
- {
|
|
|
- // The insertion point affects the realized elements. Update the index of the
|
|
|
- // elements after the insertion point.
|
|
|
- var elementCount = _elements.Count;
|
|
|
- var start = Math.Max(realizedIndex, 0);
|
|
|
- var newIndex = realizedIndex + count;
|
|
|
-
|
|
|
- for (var i = start; i < elementCount; ++i)
|
|
|
- {
|
|
|
- if (_elements[i] is Control element)
|
|
|
- updateElementIndex(element, newIndex - count, newIndex);
|
|
|
- ++newIndex;
|
|
|
- }
|
|
|
-
|
|
|
- if (realizedIndex < 0)
|
|
|
- {
|
|
|
- // The insertion point was before the first element, update the first index.
|
|
|
- _firstIndex += count;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // The insertion point was within the realized elements, insert an empty space
|
|
|
- // in _elements and _sizes.
|
|
|
- _elements!.InsertMany(realizedIndex, null, count);
|
|
|
- _sizes!.InsertMany(realizedIndex, double.NaN, count);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Updates the elements in response to items being removed from the source collection.
|
|
|
- /// </summary>
|
|
|
- /// <param name="index">The index in the source collection of the remove.</param>
|
|
|
- /// <param name="count">The number of items removed.</param>
|
|
|
- /// <param name="updateElementIndex">A method used to update the element indexes.</param>
|
|
|
- /// <param name="recycleElement">A method used to recycle elements.</param>
|
|
|
- public void ItemsRemoved(
|
|
|
- int index,
|
|
|
- int count,
|
|
|
- Action<Control, int, int> updateElementIndex,
|
|
|
- Action<Control> recycleElement)
|
|
|
- {
|
|
|
- if (index < 0)
|
|
|
- throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
- if (_elements is null || _elements.Count == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- // Get the removal start and end index within the realized _elements collection.
|
|
|
- var first = FirstIndex;
|
|
|
- var last = LastIndex;
|
|
|
- var startIndex = index - first;
|
|
|
- var endIndex = (index + count) - first;
|
|
|
-
|
|
|
- if (endIndex < 0)
|
|
|
- {
|
|
|
- // The removed range was before the realized elements. Update the first index and
|
|
|
- // the indexes of the realized elements.
|
|
|
- _firstIndex -= count;
|
|
|
- _startUUnstable = true;
|
|
|
-
|
|
|
- var newIndex = _firstIndex;
|
|
|
- for (var i = 0; i < _elements.Count; ++i)
|
|
|
- {
|
|
|
- if (_elements[i] is Control element)
|
|
|
- updateElementIndex(element, newIndex - count, newIndex);
|
|
|
- ++newIndex;
|
|
|
- }
|
|
|
- }
|
|
|
- else if (startIndex < _elements.Count)
|
|
|
- {
|
|
|
- // Recycle and remove the affected elements.
|
|
|
- var start = Math.Max(startIndex, 0);
|
|
|
- var end = Math.Min(endIndex, _elements.Count);
|
|
|
-
|
|
|
- for (var i = start; i < end; ++i)
|
|
|
- {
|
|
|
- if (_elements[i] is Control element)
|
|
|
- recycleElement(element);
|
|
|
- }
|
|
|
-
|
|
|
- _elements.RemoveRange(start, end - start);
|
|
|
- _sizes!.RemoveRange(start, end - start);
|
|
|
-
|
|
|
- // If the remove started before and ended within our realized elements, then our new
|
|
|
- // first index will be the index where the remove started. Mark StartU as unstable
|
|
|
- // because we can't rely on it now to estimate element heights.
|
|
|
- if (startIndex <= 0 && end < last)
|
|
|
- {
|
|
|
- _firstIndex = first = index;
|
|
|
- _startUUnstable = true;
|
|
|
- }
|
|
|
-
|
|
|
- // Update the indexes of the elements after the removed range.
|
|
|
- end = _elements.Count;
|
|
|
- var newIndex = first + start;
|
|
|
- for (var i = start; i < end; ++i)
|
|
|
- {
|
|
|
- if (_elements[i] is Control element)
|
|
|
- updateElementIndex(element, newIndex + count, newIndex);
|
|
|
- ++newIndex;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Recycles all elements in response to the source collection being reset.
|
|
|
- /// </summary>
|
|
|
- /// <param name="recycleElement">A method used to recycle elements.</param>
|
|
|
- public void ItemsReset(Action<Control> recycleElement)
|
|
|
- {
|
|
|
- if (_elements is null || _elements.Count == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- foreach (var e in _elements)
|
|
|
- {
|
|
|
- if (e is not null)
|
|
|
- recycleElement(e);
|
|
|
- }
|
|
|
-
|
|
|
- _startU = _firstIndex = 0;
|
|
|
- _elements?.Clear();
|
|
|
- _sizes?.Clear();
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Recycles elements before a specific index.
|
|
|
- /// </summary>
|
|
|
- /// <param name="index">The index in the source collection of new first element.</param>
|
|
|
- /// <param name="recycleElement">A method used to recycle elements.</param>
|
|
|
- public void RecycleElementsBefore(int index, Action<Control, int> recycleElement)
|
|
|
- {
|
|
|
- if (index <= FirstIndex || _elements is null || _elements.Count == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- if (index > LastIndex)
|
|
|
- {
|
|
|
- RecycleAllElements(recycleElement);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var endIndex = index - FirstIndex;
|
|
|
-
|
|
|
- for (var i = 0; i < endIndex; ++i)
|
|
|
- {
|
|
|
- if (_elements[i] is Control e)
|
|
|
- recycleElement(e, i + FirstIndex);
|
|
|
- }
|
|
|
-
|
|
|
- _elements.RemoveRange(0, endIndex);
|
|
|
- _sizes!.RemoveRange(0, endIndex);
|
|
|
- _firstIndex = index;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Recycles elements after a specific index.
|
|
|
- /// </summary>
|
|
|
- /// <param name="index">The index in the source collection of new last element.</param>
|
|
|
- /// <param name="recycleElement">A method used to recycle elements.</param>
|
|
|
- public void RecycleElementsAfter(int index, Action<Control, int> recycleElement)
|
|
|
- {
|
|
|
- if (index >= LastIndex || _elements is null || _elements.Count == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- if (index < FirstIndex)
|
|
|
- {
|
|
|
- RecycleAllElements(recycleElement);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var startIndex = (index + 1) - FirstIndex;
|
|
|
- var count = _elements.Count;
|
|
|
-
|
|
|
- for (var i = startIndex; i < count; ++i)
|
|
|
- {
|
|
|
- if (_elements[i] is Control e)
|
|
|
- recycleElement(e, i + FirstIndex);
|
|
|
- }
|
|
|
-
|
|
|
- _elements.RemoveRange(startIndex, _elements.Count - startIndex);
|
|
|
- _sizes!.RemoveRange(startIndex, _sizes.Count - startIndex);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Recycles all realized elements.
|
|
|
- /// </summary>
|
|
|
- /// <param name="recycleElement">A method used to recycle elements.</param>
|
|
|
- public void RecycleAllElements(Action<Control, int> recycleElement)
|
|
|
- {
|
|
|
- if (_elements is null || _elements.Count == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- var i = FirstIndex;
|
|
|
-
|
|
|
- foreach (var e in _elements)
|
|
|
- {
|
|
|
- if (e is not null)
|
|
|
- recycleElement(e, i);
|
|
|
- ++i;
|
|
|
- }
|
|
|
-
|
|
|
- _startU = _firstIndex = 0;
|
|
|
- _elements?.Clear();
|
|
|
- _sizes?.Clear();
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Resets the element list and prepares it for reuse.
|
|
|
- /// </summary>
|
|
|
- public void ResetForReuse()
|
|
|
- {
|
|
|
- _startU = _firstIndex = 0;
|
|
|
- _startUUnstable = false;
|
|
|
- _elements?.Clear();
|
|
|
- _sizes?.Clear();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private struct MeasureViewport
|
|
|
{
|
|
|
public int firstIndex;
|