Browse Source

ad virtualizing uniform grid

virtualizing_grid
Emmanuel Hansen 3 years ago
parent
commit
fb04570279
  1. 38
      src/Avalonia.Controls/Utils/RealizedGridElements.cs
  2. 126
      src/Avalonia.Controls/VirtualizingUniformGrid.cs

38
src/Avalonia.Controls/Utils/RealizedGridElements.cs

@ -95,7 +95,7 @@ namespace Avalonia.Controls.Utils
}
/// <summary>
/// Gets or estimates the index and start U position of the anchor element for the
/// Gets or estimates the index and grid position of the anchor element for the
/// specified viewport.
/// </summary>
/// <param name="viewportStart">The position of the start of the viewport.</param>
@ -105,22 +105,23 @@ namespace Avalonia.Controls.Utils
/// <returns>
/// A tuple containing:
/// - The index of the anchor element, or -1 if an anchor could not be determined
/// - The U position of the start of the anchor element, if determined
/// - The index of the last visible element
/// - The position of the anchor on the grid
/// - The position of the last visible element on the grid
/// </returns>
/// <remarks>
/// This method tries to find an existing element in the specified viewport from which
/// element realization can start. Failing that it estimates the first element in the
/// viewport.
/// </remarks>
public (int index, Vector coord, Vector lastCoord) GetOrEstimateAnchorElementForViewport(
public (int index, int last, Vector coord, Vector lastCoord) GetOrEstimateAnchorElementForViewport(
Point viewportStart,
Point viewportEnd,
int itemCount,
ref Size estimatedElementSize)
{
// We have no elements, nothing to do here.
if (itemCount <= 0)
return (-1, Vector.Zero, Vector.Zero);
if (itemCount <= 0 || ColumnCount == 0 || RowCount == 0)
return (-1, -1, Vector.Zero, Vector.Zero);
if (_sizes is not null)
{
@ -133,20 +134,23 @@ namespace Avalonia.Controls.Utils
var MaxWidth = ColumnCount * estimatedElementSize.Width;
var MaxHeight = RowCount * estimatedElementSize.Height;
var x = Math.Min((int)(Math.Min(viewportStart.X, MaxWidth) / estimatedElementSize.Width), ColumnCount);
var y = Math.Min((int)(Math.Min(viewportStart.Y, MaxHeight) / estimatedElementSize.Height), RowCount);
var x = Math.Min((int)(Math.Min(viewportStart.X, MaxWidth) / estimatedElementSize.Width), ColumnCount - 1);
var y = Math.Min((int)(Math.Min(viewportStart.Y, MaxHeight) / estimatedElementSize.Height), RowCount - 1);
var lastY = Math.Min((int)(Math.Min(viewportEnd.Y, MaxHeight) / estimatedElementSize.Height), RowCount);
var lastX = lastY > 0 ? ColumnCount : Math.Min((int)(Math.Min(viewportEnd.X, MaxWidth) / estimatedElementSize.Width), ColumnCount);
var lastY = Math.Min((int)(Math.Min(viewportEnd.Y, MaxHeight) / estimatedElementSize.Height), RowCount - 1);
var lastX = Math.Min((int)(Math.Min(viewportEnd.X, MaxWidth) / estimatedElementSize.Width), ColumnCount - 1);
return (Math.Max(y * ColumnCount + x - FirstColumn, 0), new Vector(x, y), new Vector(lastX, lastY));
return (Math.Max(y * ColumnCount + x - FirstColumn, 0),
MathUtilities.Clamp(lastY * ColumnCount + lastX - FirstColumn, 0, itemCount),
new Vector(x, y),
new Vector(lastY > 0 ? ColumnCount : lastX, lastY));
}
/// <summary>
/// Gets the position of the element with the requested index on the primary axis, if realized.
/// Gets the grid position of the element with the requested index.
/// </summary>
/// <returns>
/// The position of the element, or NaN if the element is not realized.
/// The position of the element kn the grid.
/// </returns>
public Vector GetElementCoord(int index)
{
@ -156,11 +160,6 @@ namespace Avalonia.Controls.Utils
}
public Vector GetOrEstimateElementU(int index)
{
return GetElementCoord(index);
}
/// <summary>
/// Gets the index of the specified element.
/// </summary>
@ -275,8 +274,7 @@ namespace Avalonia.Controls.Utils
_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.
// first index will be the index where the remove started.
if (startIndex <= 0 && end < last)
{
_firstIndex = first = index;

126
src/Avalonia.Controls/VirtualizingUniformGrid.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Emit;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -127,7 +126,6 @@ namespace Avalonia.Controls
if (_viewport.Size == default)
return DesiredSize;
if (viewport.viewportIsDisjunct)
_realizedElements.RecycleAllElements(_recycleElement);
@ -188,25 +186,36 @@ namespace Avalonia.Controls
var width = finalSize.Width / _columns;
var height = finalSize.Height / _rows;
for (var i = _measuredViewport.anchorIndex; i < Children.Count; i++)
foreach (var child in _realizedElements!.Elements)
{
if (i > _measuredViewport.lastIndex)
break;
var child = Children[i];
if (!child.IsVisible)
while (true)
{
continue;
}
if (child is not null)
{
var coord = new Vector(x, y);
child.Arrange(new Rect(x * width, y * height, width, height));
x++;
x++;
if (x >= _columns)
{
x = 0;
y++;
}
if (x >= _columns)
{
x = 0;
y++;
if (IsCoordVisible(coord, _measuredViewport.anchorCoord, _measuredViewport.endCoord))
{
child.Arrange(new Rect(coord.X * width, coord.Y * height, width, height));
_scrollViewer?.RegisterAnchorCandidate(child);
break;
}
if (coord == new Vector(_columns - 1, _rows - 1))
break;
}
else
break;
}
}
@ -268,7 +277,13 @@ namespace Avalonia.Controls
private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e)
{
throw new NotImplementedException();
if (_unrealizedFocusedElement is null || sender != _unrealizedFocusedElement)
return;
_unrealizedFocusedElement.LostFocus -= OnUnrealizedFocusedElementLostFocus;
RecycleElement(_unrealizedFocusedElement, _unrealizedFocusedIndex);
_unrealizedFocusedElement = null;
_unrealizedFocusedIndex = -1;
}
private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e)
@ -301,7 +316,8 @@ namespace Avalonia.Controls
Debug.Assert(items.Count > 0);
var index = viewport.anchorIndex;
var lastIndex = index;
_realizedElements.RecycleElementsBefore(viewport.anchorIndex, _recycleElement);
_realizedElements.RecycleElementsAfter(viewport.lastIndex, _recycleElement);
// Start at the anchor element and move forwards, realizing elements.
do
@ -322,26 +338,33 @@ namespace Avalonia.Controls
_lastEstimatedElementSize = size;
lastIndex = index;
++index;
} while (index < items.Count);
// Store the last index for the desired size calculation.
viewport.lastIndex = lastIndex;
// Calculate the last index and coordinates again, as the first child size is known.
if (index == 0)
{
(_, int last, _, var lastCoord) = _measureElements.GetOrEstimateAnchorElementForViewport(_viewport.TopLeft, _viewport.BottomRight, items.Count, ref _lastEstimatedElementSize);
viewport.lastIndex = last;
viewport.endCoord = lastCoord;
}
// We can now recycle elements before the first element.
_realizedElements.RecycleElementsBefore(viewport.anchorIndex, _recycleElement);
++index;
} while (index < items.Count && index <= viewport.lastIndex);
}
private bool IsIndexVisible(int index, Vector start, Vector end)
{
var div = Math.DivRem(index, _columns, out var rem);
var div = Math.DivRem(index + FirstColumn, _columns, out var rem);
var coord = new Vector(rem, div);
return coord.X >= start.X && coord.Y >= start.Y &&
coord.X <= end.X && coord.Y <= end.Y;
}
private bool IsCoordVisible(Vector coord, Vector start, Vector end)
{
return coord.X >= start.X && coord.Y >= start.Y &&
coord.X <= end.X && coord.Y <= end.Y;
}
private MeasureViewport CalculateMeasureViewport(IReadOnlyList<object?> items)
{
Debug.Assert(_realizedElements is not null);
@ -356,7 +379,7 @@ namespace Avalonia.Controls
// Get or estimate the anchor element from which to start realization.
var itemCount = items?.Count ?? 0;
var (anchorIndex, anchor, end) = _realizedElements.GetOrEstimateAnchorElementForViewport(
var (anchorIndex, lastIndex, anchor, end) = _realizedElements.GetOrEstimateAnchorElementForViewport(
viewportStart,
viewportEnd,
itemCount,
@ -374,6 +397,7 @@ namespace Avalonia.Controls
viewportEnd = viewportEnd,
viewportIsDisjunct = disjunct,
endCoord = end,
lastIndex = lastIndex
};
}
@ -598,7 +622,7 @@ namespace Avalonia.Controls
_scrollToIndex = index;
// Get the expected position of the elment and put it in place.
var anchor = _realizedElements.GetOrEstimateElementU(index);
var anchor = _realizedElements.GetElementCoord(index);
var rect = new Rect(anchor.X, anchor.Y, _scrollToElement.DesiredSize.Width, _scrollToElement.DesiredSize.Height);
_scrollToElement.Arrange(rect);
@ -666,9 +690,51 @@ namespace Avalonia.Controls
if (count == 0 || from is not Control fromControl)
return null;
var fromIndex = from != null ? IndexFromContainer(fromControl) : -1;
var toIndex = fromIndex;
// implement
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:
--toIndex;
break;
case NavigationDirection.Right:
++toIndex;
break;
case NavigationDirection.Up:
toIndex -= _columns;
break;
case NavigationDirection.Down:
toIndex += _columns;
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);
}

Loading…
Cancel
Save