diff --git a/src/Avalonia.Controls/Utils/RealizedGridElements.cs b/src/Avalonia.Controls/Utils/RealizedGridElements.cs
index 17ee76cbd7..62af09b8a4 100644
--- a/src/Avalonia.Controls/Utils/RealizedGridElements.cs
+++ b/src/Avalonia.Controls/Utils/RealizedGridElements.cs
@@ -95,7 +95,7 @@ namespace Avalonia.Controls.Utils
}
///
- /// 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.
///
/// The position of the start of the viewport.
@@ -105,22 +105,23 @@ namespace Avalonia.Controls.Utils
///
/// 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
///
///
/// 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.
///
- 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));
}
///
- /// 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.
///
///
- /// The position of the element, or NaN if the element is not realized.
+ /// The position of the element kn the grid.
///
public Vector GetElementCoord(int index)
{
@@ -156,11 +160,6 @@ namespace Avalonia.Controls.Utils
}
- public Vector GetOrEstimateElementU(int index)
- {
- return GetElementCoord(index);
- }
-
///
/// Gets the index of the specified element.
///
@@ -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;
diff --git a/src/Avalonia.Controls/VirtualizingUniformGrid.cs b/src/Avalonia.Controls/VirtualizingUniformGrid.cs
index 828afbc262..742fa53b9b 100644
--- a/src/Avalonia.Controls/VirtualizingUniformGrid.cs
+++ b/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