| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- using System;
- using System.Collections.Generic;
- namespace Avalonia.Controls.Utils
- {
- /// <summary>
- /// Stores the realized element state for a virtualizing panel that arranges its children
- /// in a stack layout, such as <see cref="VirtualizingStackPanel"/>.
- /// </summary>
- internal class RealizedStackElements
- {
- 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();
- }
- }
- }
|