|
|
@ -70,8 +70,8 @@ namespace Avalonia.Controls |
|
|
private ScrollViewer? _scrollViewer; |
|
|
private ScrollViewer? _scrollViewer; |
|
|
private Rect _viewport = s_invalidViewport; |
|
|
private Rect _viewport = s_invalidViewport; |
|
|
private Dictionary<object, Stack<Control>>? _recyclePool; |
|
|
private Dictionary<object, Stack<Control>>? _recyclePool; |
|
|
private Control? _unrealizedFocusedElement; |
|
|
private Control? _focusedElement; |
|
|
private int _unrealizedFocusedIndex = -1; |
|
|
private int _focusedIndex = -1; |
|
|
|
|
|
|
|
|
public VirtualizingStackPanel() |
|
|
public VirtualizingStackPanel() |
|
|
{ |
|
|
{ |
|
|
@ -265,12 +265,14 @@ namespace Avalonia.Controls |
|
|
protected override IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap) |
|
|
protected override IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap) |
|
|
{ |
|
|
{ |
|
|
var count = Items.Count; |
|
|
var count = Items.Count; |
|
|
|
|
|
var fromControl = from as Control; |
|
|
|
|
|
|
|
|
if (count == 0 || from is not Control fromControl) |
|
|
if (count == 0 || |
|
|
|
|
|
fromControl is null && direction is not NavigationDirection.First or NavigationDirection.Last) |
|
|
return null; |
|
|
return null; |
|
|
|
|
|
|
|
|
var horiz = Orientation == Orientation.Horizontal; |
|
|
var horiz = Orientation == Orientation.Horizontal; |
|
|
var fromIndex = from != null ? IndexFromContainer(fromControl) : -1; |
|
|
var fromIndex = fromControl != null ? IndexFromContainer(fromControl) : -1; |
|
|
var toIndex = fromIndex; |
|
|
var toIndex = fromIndex; |
|
|
|
|
|
|
|
|
switch (direction) |
|
|
switch (direction) |
|
|
@ -330,14 +332,25 @@ namespace Avalonia.Controls |
|
|
{ |
|
|
{ |
|
|
if (index < 0 || index >= Items.Count) |
|
|
if (index < 0 || index >= Items.Count) |
|
|
return null; |
|
|
return null; |
|
|
if (_realizedElements?.GetElement(index) is { } realized) |
|
|
if (_scrollToIndex == index) |
|
|
|
|
|
return _scrollToElement; |
|
|
|
|
|
if (_focusedIndex == index) |
|
|
|
|
|
return _focusedElement; |
|
|
|
|
|
if (GetRealizedElement(index) is { } realized) |
|
|
return realized; |
|
|
return realized; |
|
|
if (Items[index] is Control c && c.GetValue(RecycleKeyProperty) == s_itemIsItsOwnContainer) |
|
|
if (Items[index] is Control c && c.GetValue(RecycleKeyProperty) == s_itemIsItsOwnContainer) |
|
|
return c; |
|
|
return c; |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1; |
|
|
protected internal override int IndexFromContainer(Control container) |
|
|
|
|
|
{ |
|
|
|
|
|
if (container == _scrollToElement) |
|
|
|
|
|
return _scrollToIndex; |
|
|
|
|
|
if (container == _focusedElement) |
|
|
|
|
|
return _focusedIndex; |
|
|
|
|
|
return _realizedElements?.GetIndex(container) ?? -1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
protected internal override Control? ScrollIntoView(int index) |
|
|
protected internal override Control? ScrollIntoView(int index) |
|
|
{ |
|
|
{ |
|
|
@ -355,16 +368,19 @@ namespace Avalonia.Controls |
|
|
{ |
|
|
{ |
|
|
// Create and measure the element to be brought into view. Store it in a field so that
|
|
|
// 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.
|
|
|
// it can be re-used in the layout pass.
|
|
|
_scrollToElement = GetOrCreateElement(items, index); |
|
|
var scrollToElement = GetOrCreateElement(items, index); |
|
|
_scrollToElement.Measure(Size.Infinity); |
|
|
scrollToElement.Measure(Size.Infinity); |
|
|
_scrollToIndex = index; |
|
|
|
|
|
|
|
|
|
|
|
// Get the expected position of the elment and put it in place.
|
|
|
// Get the expected position of the elment and put it in place.
|
|
|
var anchorU = _realizedElements.GetOrEstimateElementU(index, ref _lastEstimatedElementSizeU); |
|
|
var anchorU = _realizedElements.GetOrEstimateElementU(index, ref _lastEstimatedElementSizeU); |
|
|
var rect = Orientation == Orientation.Horizontal ? |
|
|
var rect = Orientation == Orientation.Horizontal ? |
|
|
new Rect(anchorU, 0, _scrollToElement.DesiredSize.Width, _scrollToElement.DesiredSize.Height) : |
|
|
new Rect(anchorU, 0, scrollToElement.DesiredSize.Width, scrollToElement.DesiredSize.Height) : |
|
|
new Rect(0, anchorU, _scrollToElement.DesiredSize.Width, _scrollToElement.DesiredSize.Height); |
|
|
new Rect(0, anchorU, scrollToElement.DesiredSize.Width, scrollToElement.DesiredSize.Height); |
|
|
_scrollToElement.Arrange(rect); |
|
|
scrollToElement.Arrange(rect); |
|
|
|
|
|
|
|
|
|
|
|
// Store the element and index so that they can be used in the layout pass.
|
|
|
|
|
|
_scrollToElement = scrollToElement; |
|
|
|
|
|
_scrollToIndex = index; |
|
|
|
|
|
|
|
|
// If the item being brought into view was added since the last layout pass then
|
|
|
// 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
|
|
|
// our bounds won't be updated, so any containing scroll viewers will not have an
|
|
|
@ -378,7 +394,7 @@ namespace Avalonia.Controls |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Try to bring the item into view.
|
|
|
// Try to bring the item into view.
|
|
|
_scrollToElement.BringIntoView(); |
|
|
scrollToElement.BringIntoView(); |
|
|
|
|
|
|
|
|
// If the viewport does not contain the item to scroll to, set _isWaitingForViewportUpdate:
|
|
|
// If the viewport does not contain the item to scroll to, set _isWaitingForViewportUpdate:
|
|
|
// this should cause the following chain of events:
|
|
|
// this should cause the following chain of events:
|
|
|
@ -397,10 +413,9 @@ namespace Avalonia.Controls |
|
|
root.LayoutManager.ExecuteLayoutPass(); |
|
|
root.LayoutManager.ExecuteLayoutPass(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var result = _scrollToElement; |
|
|
|
|
|
_scrollToElement = null; |
|
|
_scrollToElement = null; |
|
|
_scrollToIndex = -1; |
|
|
_scrollToIndex = -1; |
|
|
return result; |
|
|
return scrollToElement; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return null; |
|
|
return null; |
|
|
@ -563,34 +578,48 @@ namespace Avalonia.Controls |
|
|
{ |
|
|
{ |
|
|
Debug.Assert(ItemContainerGenerator is not null); |
|
|
Debug.Assert(ItemContainerGenerator is not null); |
|
|
|
|
|
|
|
|
var e = GetRealizedElement(index); |
|
|
if ((GetRealizedElement(index) ?? |
|
|
|
|
|
GetRealizedElement(index, ref _focusedIndex, ref _focusedElement) ?? |
|
|
|
|
|
GetRealizedElement(index, ref _scrollToIndex, ref _scrollToElement)) is { } realized) |
|
|
|
|
|
return realized; |
|
|
|
|
|
|
|
|
|
|
|
var item = items[index]; |
|
|
|
|
|
var generator = ItemContainerGenerator!; |
|
|
|
|
|
|
|
|
if (e is null) |
|
|
if (generator.NeedsContainer(item, index, out var recycleKey)) |
|
|
{ |
|
|
{ |
|
|
var item = items[index]; |
|
|
return GetRecycledElement(item, index, recycleKey) ?? |
|
|
var generator = ItemContainerGenerator!; |
|
|
CreateElement(item, index, recycleKey); |
|
|
|
|
|
} |
|
|
if (generator.NeedsContainer(item, index, out var recycleKey)) |
|
|
else |
|
|
{ |
|
|
{ |
|
|
e = GetRecycledElement(item, index, recycleKey) ?? |
|
|
return GetItemAsOwnContainer(item, index); |
|
|
CreateElement(item, index, recycleKey); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
e = GetItemAsOwnContainer(item, index); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return e; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private Control? GetRealizedElement(int index) |
|
|
private Control? GetRealizedElement(int index) |
|
|
{ |
|
|
{ |
|
|
if (_scrollToIndex == index) |
|
|
|
|
|
return _scrollToElement; |
|
|
|
|
|
return _realizedElements?.GetElement(index); |
|
|
return _realizedElements?.GetElement(index); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static Control? GetRealizedElement( |
|
|
|
|
|
int index, |
|
|
|
|
|
ref int specialIndex, |
|
|
|
|
|
ref Control? specialElement) |
|
|
|
|
|
{ |
|
|
|
|
|
if (specialIndex == index) |
|
|
|
|
|
{ |
|
|
|
|
|
Debug.Assert(specialElement is not null); |
|
|
|
|
|
|
|
|
|
|
|
var result = specialElement; |
|
|
|
|
|
specialIndex = -1; |
|
|
|
|
|
specialElement = null; |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private Control GetItemAsOwnContainer(object? item, int index) |
|
|
private Control GetItemAsOwnContainer(object? item, int index) |
|
|
{ |
|
|
{ |
|
|
Debug.Assert(ItemContainerGenerator is not null); |
|
|
Debug.Assert(ItemContainerGenerator is not null); |
|
|
@ -619,15 +648,6 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
|
var generator = ItemContainerGenerator!; |
|
|
var generator = ItemContainerGenerator!; |
|
|
|
|
|
|
|
|
if (_unrealizedFocusedIndex == index && _unrealizedFocusedElement is not null) |
|
|
|
|
|
{ |
|
|
|
|
|
var element = _unrealizedFocusedElement; |
|
|
|
|
|
_unrealizedFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus; |
|
|
|
|
|
_unrealizedFocusedElement = null; |
|
|
|
|
|
_unrealizedFocusedIndex = -1; |
|
|
|
|
|
return element; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (_recyclePool?.TryGetValue(recycleKey, out var recyclePool) == true && recyclePool.Count > 0) |
|
|
if (_recyclePool?.TryGetValue(recycleKey, out var recyclePool) == true && recyclePool.Count > 0) |
|
|
{ |
|
|
{ |
|
|
var recycled = recyclePool.Pop(); |
|
|
var recycled = recyclePool.Pop(); |
|
|
@ -673,9 +693,9 @@ namespace Avalonia.Controls |
|
|
} |
|
|
} |
|
|
else if (element.IsKeyboardFocusWithin) |
|
|
else if (element.IsKeyboardFocusWithin) |
|
|
{ |
|
|
{ |
|
|
_unrealizedFocusedElement = element; |
|
|
_focusedElement = element; |
|
|
_unrealizedFocusedIndex = index; |
|
|
_focusedIndex = index; |
|
|
_unrealizedFocusedElement.LostFocus += OnUnrealizedFocusedElementLostFocus; |
|
|
_focusedElement.LostFocus += OnUnrealizedFocusedElementLostFocus; |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
@ -744,13 +764,13 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
|
private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e) |
|
|
private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e) |
|
|
{ |
|
|
{ |
|
|
if (_unrealizedFocusedElement is null || sender != _unrealizedFocusedElement) |
|
|
if (_focusedElement is null || sender != _focusedElement) |
|
|
return; |
|
|
return; |
|
|
|
|
|
|
|
|
_unrealizedFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus; |
|
|
_focusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus; |
|
|
RecycleElement(_unrealizedFocusedElement, _unrealizedFocusedIndex); |
|
|
RecycleElement(_focusedElement, _focusedIndex); |
|
|
_unrealizedFocusedElement = null; |
|
|
_focusedElement = null; |
|
|
_unrealizedFocusedIndex = -1; |
|
|
_focusedIndex = -1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
/// <inheritdoc/>
|
|
|
|