| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 |
- using System;
- using System.Collections.Generic;
- using System.Collections.Specialized;
- using System.Diagnostics;
- using System.Linq;
- using Avalonia.Controls.Primitives;
- using Avalonia.Controls.Utils;
- using Avalonia.Input;
- using Avalonia.Interactivity;
- using Avalonia.Layout;
- using Avalonia.Utilities;
- using Avalonia.VisualTree;
- namespace Avalonia.Controls
- {
- /// <summary>
- /// Arranges and virtualizes content on a single line that is oriented either horizontally or vertically.
- /// </summary>
- public class VirtualizingStackPanel : VirtualizingPanel, IScrollSnapPointsInfo
- {
- /// <summary>
- /// Defines the <see cref="Orientation"/> property.
- /// </summary>
- public static readonly StyledProperty<Orientation> OrientationProperty =
- StackPanel.OrientationProperty.AddOwner<VirtualizingStackPanel>();
- /// <summary>
- /// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
- /// </summary>
- public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
- AvaloniaProperty.Register<VirtualizingStackPanel, bool>(nameof(AreHorizontalSnapPointsRegular));
- /// <summary>
- /// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
- /// </summary>
- public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
- AvaloniaProperty.Register<VirtualizingStackPanel, bool>(nameof(AreVerticalSnapPointsRegular));
- /// <summary>
- /// Defines the <see cref="HorizontalSnapPointsChanged"/> event.
- /// </summary>
- public static readonly RoutedEvent<RoutedEventArgs> HorizontalSnapPointsChangedEvent =
- RoutedEvent.Register<VirtualizingStackPanel, RoutedEventArgs>(
- nameof(HorizontalSnapPointsChanged),
- RoutingStrategies.Bubble);
- /// <summary>
- /// Defines the <see cref="VerticalSnapPointsChanged"/> event.
- /// </summary>
- public static readonly RoutedEvent<RoutedEventArgs> VerticalSnapPointsChangedEvent =
- RoutedEvent.Register<VirtualizingStackPanel, RoutedEventArgs>(
- nameof(VerticalSnapPointsChanged),
- RoutingStrategies.Bubble);
- private static readonly AttachedProperty<bool> ItemIsOwnContainerProperty =
- AvaloniaProperty.RegisterAttached<VirtualizingStackPanel, Control, bool>("ItemIsOwnContainer");
- private static readonly Rect s_invalidViewport = new(double.PositiveInfinity, double.PositiveInfinity, 0, 0);
- private readonly Action<Control, int> _recycleElement;
- private readonly Action<Control> _recycleElementOnItemRemoved;
- private readonly Action<Control, int, int> _updateElementIndex;
- private int _scrollToIndex = -1;
- private Control? _scrollToElement;
- private bool _isInLayout;
- private bool _isWaitingForViewportUpdate;
- private double _lastEstimatedElementSizeU = 25;
- private RealizedStackElements? _measureElements;
- private RealizedStackElements? _realizedElements;
- private ScrollViewer? _scrollViewer;
- private Rect _viewport = s_invalidViewport;
- private Stack<Control>? _recyclePool;
- private Control? _unrealizedFocusedElement;
- private int _unrealizedFocusedIndex = -1;
- public VirtualizingStackPanel()
- {
- _recycleElement = RecycleElement;
- _recycleElementOnItemRemoved = RecycleElementOnItemRemoved;
- _updateElementIndex = UpdateElementIndex;
- EffectiveViewportChanged += OnEffectiveViewportChanged;
- }
- /// <summary>
- /// Gets or sets the axis along which items are laid out.
- /// </summary>
- /// <value>
- /// One of the enumeration values that specifies the axis along which items are laid out.
- /// The default is Vertical.
- /// </value>
- public Orientation Orientation
- {
- get => GetValue(OrientationProperty);
- set => SetValue(OrientationProperty, value);
- }
- /// <summary>
- /// Occurs when the measurements for horizontal snap points change.
- /// </summary>
- public event EventHandler<RoutedEventArgs>? HorizontalSnapPointsChanged
- {
- add => AddHandler(HorizontalSnapPointsChangedEvent, value);
- remove => RemoveHandler(HorizontalSnapPointsChangedEvent, value);
- }
- /// <summary>
- /// Occurs when the measurements for vertical snap points change.
- /// </summary>
- public event EventHandler<RoutedEventArgs>? VerticalSnapPointsChanged
- {
- add => AddHandler(VerticalSnapPointsChangedEvent, value);
- remove => RemoveHandler(VerticalSnapPointsChangedEvent, value);
- }
- /// <summary>
- /// Gets or sets whether the horizontal snap points for the <see cref="VirtualizingStackPanel"/> are equidistant from each other.
- /// </summary>
- public bool AreHorizontalSnapPointsRegular
- {
- get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
- set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
- }
- /// <summary>
- /// Gets or sets whether the vertical snap points for the <see cref="VirtualizingStackPanel"/> are equidistant from each other.
- /// </summary>
- public bool AreVerticalSnapPointsRegular
- {
- get { return GetValue(AreVerticalSnapPointsRegularProperty); }
- set { SetValue(AreVerticalSnapPointsRegularProperty, value); }
- }
- /// <summary>
- /// Gets the index of the first realized element, or -1 if no elements are realized.
- /// </summary>
- public int FirstRealizedIndex => _realizedElements?.FirstIndex ?? -1;
- /// <summary>
- /// Gets the index of the last realized element, or -1 if no elements are realized.
- /// </summary>
- public int LastRealizedIndex => _realizedElements?.LastIndex ?? -1;
- protected override Size MeasureOverride(Size availableSize)
- {
- var items = Items;
- if (items.Count == 0)
- return default;
- // If we're bringing an item into view, ignore any layout passes until we receive a new
- // effective viewport.
- if (_isWaitingForViewportUpdate)
- return DesiredSize;
- _isInLayout = true;
- try
- {
- var orientation = Orientation;
- _realizedElements ??= new();
- _measureElements ??= new();
- // We handle horizontal and vertical layouts here so X and Y are abstracted to:
- // - Horizontal layouts: U = horizontal, V = vertical
- // - Vertical layouts: U = vertical, V = horizontal
- var viewport = CalculateMeasureViewport(items);
- // If the viewport is disjunct then we can recycle everything.
- if (viewport.viewportIsDisjunct)
- _realizedElements.RecycleAllElements(_recycleElement);
- // Do the measure, creating/recycling elements as necessary to fill the viewport. Don't
- // write to _realizedElements yet, only _measureElements.
- RealizeElements(items, availableSize, ref viewport);
- // Now swap the measureElements and realizedElements collection.
- (_measureElements, _realizedElements) = (_realizedElements, _measureElements);
- _measureElements.ResetForReuse();
- return CalculateDesiredSize(orientation, items.Count, viewport);
- }
- finally
- {
- _isInLayout = false;
- }
- }
- protected override Size ArrangeOverride(Size finalSize)
- {
- if (_realizedElements is null)
- return default;
- _isInLayout = true;
- try
- {
- var orientation = Orientation;
- var u = _realizedElements!.StartU;
- for (var i = 0; i < _realizedElements.Count; ++i)
- {
- var e = _realizedElements.Elements[i];
- if (e is not null)
- {
- var sizeU = _realizedElements.SizeU[i];
- var rect = orientation == Orientation.Horizontal ?
- new Rect(u, 0, sizeU, finalSize.Height) :
- new Rect(0, u, finalSize.Width, sizeU);
- e.Arrange(rect);
- _scrollViewer?.RegisterAnchorCandidate(e);
- u += orientation == Orientation.Horizontal ? rect.Width : rect.Height;
- }
- }
- return finalSize;
- }
- finally
- {
- _isInLayout = false;
- RaiseEvent(new RoutedEventArgs(Orientation == Orientation.Horizontal ? HorizontalSnapPointsChangedEvent : VerticalSnapPointsChangedEvent));
- }
- }
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnAttachedToVisualTree(e);
- _scrollViewer = this.FindAncestorOfType<ScrollViewer>();
- }
- protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnDetachedFromVisualTree(e);
- _scrollViewer = null;
- }
- protected override void OnItemsChanged(IReadOnlyList<object?> items, NotifyCollectionChangedEventArgs e)
- {
- InvalidateMeasure();
- if (_realizedElements is null)
- return;
- switch (e.Action)
- {
- case NotifyCollectionChangedAction.Add:
- _realizedElements.ItemsInserted(e.NewStartingIndex, e.NewItems!.Count, _updateElementIndex);
- break;
- case NotifyCollectionChangedAction.Remove:
- _realizedElements.ItemsRemoved(e.OldStartingIndex, e.OldItems!.Count, _updateElementIndex, _recycleElementOnItemRemoved);
- break;
- case NotifyCollectionChangedAction.Replace:
- case NotifyCollectionChangedAction.Move:
- _realizedElements.ItemsRemoved(e.OldStartingIndex, e.OldItems!.Count, _updateElementIndex, _recycleElementOnItemRemoved);
- _realizedElements.ItemsInserted(e.NewStartingIndex, e.NewItems!.Count, _updateElementIndex);
- break;
- case NotifyCollectionChangedAction.Reset:
- _realizedElements.ItemsReset(_recycleElementOnItemRemoved);
- break;
- }
- }
- protected override IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
- {
- var count = Items.Count;
- if (count == 0 || from is not Control fromControl)
- return null;
- var horiz = Orientation == Orientation.Horizontal;
- var fromIndex = from != null ? IndexFromContainer(fromControl) : -1;
- var toIndex = fromIndex;
- switch (direction)
- {
- case NavigationDirection.First:
- toIndex = 0;
- break;
- case NavigationDirection.Last:
- toIndex = count - 1;
- break;
- case NavigationDirection.Next:
- ++toIndex;
- break;
- case NavigationDirection.Previous:
- --toIndex;
- break;
- case NavigationDirection.Left:
- if (horiz)
- --toIndex;
- break;
- case NavigationDirection.Right:
- if (horiz)
- ++toIndex;
- break;
- case NavigationDirection.Up:
- if (!horiz)
- --toIndex;
- break;
- case NavigationDirection.Down:
- if (!horiz)
- ++toIndex;
- break;
- default:
- return null;
- }
- if (fromIndex == toIndex)
- return from;
- if (wrap)
- {
- if (toIndex < 0)
- toIndex = count - 1;
- else if (toIndex >= count)
- toIndex = 0;
- }
- return ScrollIntoView(toIndex);
- }
- protected internal override IEnumerable<Control>? GetRealizedContainers()
- {
- return _realizedElements?.Elements.Where(x => x is not null)!;
- }
- protected internal override Control? ContainerFromIndex(int index)
- {
- if (index < 0 || index >= Items.Count)
- return null;
- if (_realizedElements?.GetElement(index) is { } realized)
- return realized;
- if (Items[index] is Control c && c.GetValue(ItemIsOwnContainerProperty))
- return c;
- return null;
- }
- protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1;
- protected internal override Control? ScrollIntoView(int index)
- {
- var items = Items;
- if (_isInLayout || index < 0 || index >= items.Count || _realizedElements is null)
- return null;
- if (GetRealizedElement(index) is Control element)
- {
- element.BringIntoView();
- return element;
- }
- else if (this.GetVisualRoot() is ILayoutRoot root)
- {
- // Create and measure the element to be brought into view. Store it in a field so that
- // it can be re-used in the layout pass.
- _scrollToElement = GetOrCreateElement(items, index);
- _scrollToElement.Measure(Size.Infinity);
- _scrollToIndex = index;
- // Get the expected position of the elment and put it in place.
- var anchorU = _realizedElements.GetOrEstimateElementU(index, ref _lastEstimatedElementSizeU);
- var rect = Orientation == Orientation.Horizontal ?
- new Rect(anchorU, 0, _scrollToElement.DesiredSize.Width, _scrollToElement.DesiredSize.Height) :
- new Rect(0, anchorU, _scrollToElement.DesiredSize.Width, _scrollToElement.DesiredSize.Height);
- _scrollToElement.Arrange(rect);
- // If the item being brought into view was added since the last layout pass then
- // our bounds won't be updated, so any containing scroll viewers will not have an
- // updated extent. Do a layout pass to ensure that the containing scroll viewers
- // will be able to scroll the new item into view.
- if (!Bounds.Contains(rect) && !_viewport.Contains(rect))
- {
- _isWaitingForViewportUpdate = true;
- root.LayoutManager.ExecuteLayoutPass();
- _isWaitingForViewportUpdate = false;
- }
- // Try to bring the item into view.
- _scrollToElement.BringIntoView();
- // If the viewport does not contain the item to scroll to, set _isWaitingForViewportUpdate:
- // this should cause the following chain of events:
- // - Measure is first done with the old viewport (which will be a no-op, see MeasureOverride)
- // - The viewport is then updated by the layout system which invalidates our measure
- // - Measure is then done with the new viewport.
- _isWaitingForViewportUpdate = !_viewport.Contains(rect);
- root.LayoutManager.ExecuteLayoutPass();
- // If for some reason the layout system didn't give us a new viewport during the layout, we
- // need to do another layout pass as the one that took place was a no-op.
- if (_isWaitingForViewportUpdate)
- {
- _isWaitingForViewportUpdate = false;
- InvalidateMeasure();
- root.LayoutManager.ExecuteLayoutPass();
- }
- var result = _scrollToElement;
- _scrollToElement = null;
- _scrollToIndex = -1;
- return result;
- }
- return null;
- }
- internal IReadOnlyList<Control?> GetRealizedElements()
- {
- return _realizedElements?.Elements ?? Array.Empty<Control>();
- }
- private MeasureViewport CalculateMeasureViewport(IReadOnlyList<object?> items)
- {
- Debug.Assert(_realizedElements is not null);
- // If the control has not yet been laid out then the effective viewport won't have been set.
- // Try to work it out from an ancestor control.
- var viewport = _viewport != s_invalidViewport ? _viewport : EstimateViewport();
- // Get the viewport in the orientation direction.
- var viewportStart = Orientation == Orientation.Horizontal ? viewport.X : viewport.Y;
- var viewportEnd = Orientation == Orientation.Horizontal ? viewport.Right : viewport.Bottom;
- // Get or estimate the anchor element from which to start realization.
- var itemCount = items?.Count ?? 0;
- var (anchorIndex, anchorU) = _realizedElements.GetOrEstimateAnchorElementForViewport(
- viewportStart,
- viewportEnd,
- itemCount,
- ref _lastEstimatedElementSizeU);
- // Check if the anchor element is not within the currently realized elements.
- var disjunct = anchorIndex < _realizedElements.FirstIndex ||
- anchorIndex > _realizedElements.LastIndex;
- return new MeasureViewport
- {
- anchorIndex = anchorIndex,
- anchorU = anchorU,
- viewportUStart = viewportStart,
- viewportUEnd = viewportEnd,
- viewportIsDisjunct = disjunct,
- };
- }
- private Size CalculateDesiredSize(Orientation orientation, int itemCount, in MeasureViewport viewport)
- {
- var sizeU = 0.0;
- var sizeV = viewport.measuredV;
- if (viewport.lastIndex >= 0)
- {
- var remaining = itemCount - viewport.lastIndex - 1;
- sizeU = viewport.realizedEndU + (remaining * _lastEstimatedElementSizeU);
- }
- return orientation == Orientation.Horizontal ? new(sizeU, sizeV) : new(sizeV, sizeU);
- }
- private double EstimateElementSizeU()
- {
- if (_realizedElements is null)
- return _lastEstimatedElementSizeU;
- var result = _realizedElements.EstimateElementSizeU();
- if (result >= 0)
- _lastEstimatedElementSizeU = result;
- return _lastEstimatedElementSizeU;
- }
- private Rect EstimateViewport()
- {
- var c = this.GetVisualParent();
- var viewport = new Rect();
- if (c is null)
- {
- return viewport;
- }
- while (c is not null)
- {
- if ((c.Bounds.Width != 0 || c.Bounds.Height != 0) &&
- c.TransformToVisual(this) is Matrix transform)
- {
- viewport = new Rect(0, 0, c.Bounds.Width, c.Bounds.Height)
- .TransformToAABB(transform);
- break;
- }
- c = c?.GetVisualParent();
- }
- return viewport;
- }
- private void RealizeElements(
- IReadOnlyList<object?> items,
- Size availableSize,
- ref MeasureViewport viewport)
- {
- Debug.Assert(_measureElements is not null);
- Debug.Assert(_realizedElements is not null);
- Debug.Assert(items.Count > 0);
- var index = viewport.anchorIndex;
- var horizontal = Orientation == Orientation.Horizontal;
- var u = viewport.anchorU;
- // If the anchor element is at the beginning of, or before, the start of the viewport
- // then we can recycle all elements before it.
- if (u <= viewport.anchorU)
- _realizedElements.RecycleElementsBefore(viewport.anchorIndex, _recycleElement);
- // Start at the anchor element and move forwards, realizing elements.
- do
- {
- var e = GetOrCreateElement(items, index);
- e.Measure(availableSize);
- var sizeU = horizontal ? e.DesiredSize.Width : e.DesiredSize.Height;
- var sizeV = horizontal ? e.DesiredSize.Height : e.DesiredSize.Width;
- _measureElements!.Add(index, e, u, sizeU);
- viewport.measuredV = Math.Max(viewport.measuredV, sizeV);
- u += sizeU;
- ++index;
- } while (u < viewport.viewportUEnd && index < items.Count);
- // Store the last index and end U position for the desired size calculation.
- viewport.lastIndex = index - 1;
- viewport.realizedEndU = u;
- // We can now recycle elements after the last element.
- _realizedElements.RecycleElementsAfter(viewport.lastIndex, _recycleElement);
- // Next move backwards from the anchor element, realizing elements.
- index = viewport.anchorIndex - 1;
- u = viewport.anchorU;
- while (u > viewport.viewportUStart && index >= 0)
- {
- var e = GetOrCreateElement(items, index);
- e.Measure(availableSize);
- var sizeU = horizontal ? e.DesiredSize.Width : e.DesiredSize.Height;
- var sizeV = horizontal ? e.DesiredSize.Height : e.DesiredSize.Width;
- u -= sizeU;
- _measureElements!.Add(index, e, u, sizeU);
- viewport.measuredV = Math.Max(viewport.measuredV, sizeV);
- --index;
- }
- // We can now recycle elements before the first element.
- _realizedElements.RecycleElementsBefore(index + 1, _recycleElement);
- }
- private Control GetOrCreateElement(IReadOnlyList<object?> items, int index)
- {
- var e = GetRealizedElement(index) ??
- GetItemIsOwnContainer(items, index) ??
- GetRecycledElement(items, index) ??
- CreateElement(items, index);
- return e;
- }
- private Control? GetRealizedElement(int index)
- {
- if (_scrollToIndex == index)
- return _scrollToElement;
- return _realizedElements?.GetElement(index);
- }
- private Control? GetItemIsOwnContainer(IReadOnlyList<object?> items, int index)
- {
- var item = items[index];
- if (item is Control controlItem)
- {
- var generator = ItemContainerGenerator!;
- if (controlItem.IsSet(ItemIsOwnContainerProperty))
- {
- controlItem.IsVisible = true;
- return controlItem;
- }
- else if (generator.IsItemItsOwnContainer(controlItem))
- {
- generator.PrepareItemContainer(controlItem, controlItem, index);
- AddInternalChild(controlItem);
- controlItem.SetValue(ItemIsOwnContainerProperty, true);
- generator.ItemContainerPrepared(controlItem, item, index);
- return controlItem;
- }
- }
- return null;
- }
- private Control? GetRecycledElement(IReadOnlyList<object?> items, int index)
- {
- Debug.Assert(ItemContainerGenerator is not null);
- var generator = ItemContainerGenerator!;
- var item = items[index];
- if (_unrealizedFocusedIndex == index && _unrealizedFocusedElement is not null)
- {
- var element = _unrealizedFocusedElement;
- _unrealizedFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus;
- _unrealizedFocusedElement = null;
- _unrealizedFocusedIndex = -1;
- return element;
- }
- if (_recyclePool?.Count > 0)
- {
- var recycled = _recyclePool.Pop();
- recycled.IsVisible = true;
- generator.PrepareItemContainer(recycled, item, index);
- generator.ItemContainerPrepared(recycled, item, index);
- return recycled;
- }
- return null;
- }
- private Control CreateElement(IReadOnlyList<object?> items, int index)
- {
- Debug.Assert(ItemContainerGenerator is not null);
- var generator = ItemContainerGenerator!;
- var item = items[index];
- var container = generator.CreateContainer();
- generator.PrepareItemContainer(container, item, index);
- AddInternalChild(container);
- generator.ItemContainerPrepared(container, item, index);
- return container;
- }
- private void RecycleElement(Control element, int index)
- {
- Debug.Assert(ItemContainerGenerator is not null);
-
- _scrollViewer?.UnregisterAnchorCandidate(element);
- if (element.IsSet(ItemIsOwnContainerProperty))
- {
- element.IsVisible = false;
- }
- else if (element.IsKeyboardFocusWithin)
- {
- _unrealizedFocusedElement = element;
- _unrealizedFocusedIndex = index;
- _unrealizedFocusedElement.LostFocus += OnUnrealizedFocusedElementLostFocus;
- }
- else
- {
- ItemContainerGenerator!.ClearItemContainer(element);
- _recyclePool ??= new();
- _recyclePool.Push(element);
- element.IsVisible = false;
- }
- }
- private void RecycleElementOnItemRemoved(Control element)
- {
- Debug.Assert(ItemContainerGenerator is not null);
- if (element.IsSet(ItemIsOwnContainerProperty))
- {
- RemoveInternalChild(element);
- }
- else
- {
- ItemContainerGenerator!.ClearItemContainer(element);
- _recyclePool ??= new();
- _recyclePool.Push(element);
- element.IsVisible = false;
- }
- }
- private void UpdateElementIndex(Control element, int oldIndex, int newIndex)
- {
- Debug.Assert(ItemContainerGenerator is not null);
- ItemContainerGenerator.ItemContainerIndexChanged(element, oldIndex, newIndex);
- }
- private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e)
- {
- var vertical = Orientation == Orientation.Vertical;
- var oldViewportStart = vertical ? _viewport.Top : _viewport.Left;
- var oldViewportEnd = vertical ? _viewport.Bottom : _viewport.Right;
- _viewport = e.EffectiveViewport.Intersect(new(Bounds.Size));
- _isWaitingForViewportUpdate = false;
- var newViewportStart = vertical ? _viewport.Top : _viewport.Left;
- var newViewportEnd = vertical ? _viewport.Bottom : _viewport.Right;
- if (!MathUtilities.AreClose(oldViewportStart, newViewportStart) ||
- !MathUtilities.AreClose(oldViewportEnd, newViewportEnd))
- {
- InvalidateMeasure();
- }
- }
- private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e)
- {
- if (_unrealizedFocusedElement is null || sender != _unrealizedFocusedElement)
- return;
- _unrealizedFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus;
- RecycleElement(_unrealizedFocusedElement, _unrealizedFocusedIndex);
- _unrealizedFocusedElement = null;
- _unrealizedFocusedIndex = -1;
- }
- /// <inheritdoc/>
- public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
- {
- var snapPoints = new List<double>();
- switch (orientation)
- {
- case Orientation.Horizontal:
- if (AreHorizontalSnapPointsRegular)
- throw new InvalidOperationException();
- if (Orientation == Orientation.Horizontal)
- {
- var averageElementSize = EstimateElementSizeU();
- double snapPoint = 0;
- for (var i = 0; i < Items.Count; i++)
- {
- var container = ContainerFromIndex(i);
- if (container != null)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Near:
- snapPoint = container.Bounds.Left;
- break;
- case SnapPointsAlignment.Center:
- snapPoint = container.Bounds.Center.X;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = container.Bounds.Right;
- break;
- }
- }
- else
- {
- if (snapPoint == 0)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Center:
- snapPoint = averageElementSize / 2;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = averageElementSize;
- break;
- }
- }
- else
- snapPoint += averageElementSize;
- }
- snapPoints.Add(snapPoint);
- }
- }
- break;
- case Orientation.Vertical:
- if (AreVerticalSnapPointsRegular)
- throw new InvalidOperationException();
- if (Orientation == Orientation.Vertical)
- {
- var averageElementSize = EstimateElementSizeU();
- double snapPoint = 0;
- for (var i = 0; i < Items.Count; i++)
- {
- var container = ContainerFromIndex(i);
- if (container != null)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Near:
- snapPoint = container.Bounds.Top;
- break;
- case SnapPointsAlignment.Center:
- snapPoint = container.Bounds.Center.Y;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = container.Bounds.Bottom;
- break;
- }
- }
- else
- {
- if (snapPoint == 0)
- {
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Center:
- snapPoint = averageElementSize / 2;
- break;
- case SnapPointsAlignment.Far:
- snapPoint = averageElementSize;
- break;
- }
- }
- else
- snapPoint += averageElementSize;
- }
- snapPoints.Add(snapPoint);
- }
- }
- break;
- }
- return snapPoints;
- }
- /// <inheritdoc/>
- public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
- {
- offset = 0f;
- var firstRealizedChild = _realizedElements?.Elements.FirstOrDefault();
- if (firstRealizedChild == null)
- {
- return 0;
- }
- double snapPoint = 0;
- switch (Orientation)
- {
- case Orientation.Horizontal:
- if (!AreHorizontalSnapPointsRegular)
- throw new InvalidOperationException();
- snapPoint = firstRealizedChild.Bounds.Width;
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Near:
- offset = 0;
- break;
- case SnapPointsAlignment.Center:
- offset = (firstRealizedChild.Bounds.Right - firstRealizedChild.Bounds.Left) / 2;
- break;
- case SnapPointsAlignment.Far:
- offset = firstRealizedChild.Bounds.Width;
- break;
- }
- break;
- case Orientation.Vertical:
- if (!AreVerticalSnapPointsRegular)
- throw new InvalidOperationException();
- snapPoint = firstRealizedChild.Bounds.Height;
- switch (snapPointsAlignment)
- {
- case SnapPointsAlignment.Near:
- offset = 0;
- break;
- case SnapPointsAlignment.Center:
- offset = (firstRealizedChild.Bounds.Bottom - firstRealizedChild.Bounds.Top) / 2;
- break;
- case SnapPointsAlignment.Far:
- offset = firstRealizedChild.Bounds.Height;
- break;
- }
- break;
- }
- return snapPoint;
- }
- private struct MeasureViewport
- {
- public int anchorIndex;
- public double anchorU;
- public double viewportUStart;
- public double viewportUEnd;
- public double measuredV;
- public double realizedEndU;
- public int lastIndex;
- public bool viewportIsDisjunct;
- }
- }
- }
|