Skip to content

Commit

Permalink
fix(itemsRepeater): Fix regerssion that would prevent nested IR to re…
Browse files Browse the repository at this point in the history
…nder items after the first page if scrolling slowly
  • Loading branch information
dr1rrb committed May 1, 2023
1 parent a632ee5 commit 342b8a6
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,73 @@ public async Task When_NestedInSVAndOutOfViewportOnInitialLoad_Then_Materialized

sut.Children.Count.Should().BeGreaterThan(1);
}

[TestMethod]
[RunsOnUIThread]
#if __MACOS__
[Ignore("Currently fails on macOS, part of #9282 epic")]
#endif
public async Task When_NestedIRSlowlyChangeViewport_Then_MaterializedNeededItems()
{
const int viewportHeight = 500;

var sut = default(ItemsRepeater);
var sv = new ScrollViewer
{
Height = viewportHeight,
Content = (sut = new ItemsRepeater()
{
ItemsSource = Enumerable.Range(0, 10).Select(i => $"Group #{i:D2}"),
ItemTemplate = new DataTemplate(() => new StackPanel
{
Children =
{
new Border
{
Background = new SolidColorBrush(Colors.DeepPink),
Height = 100,
Width = 150,
Child = new TextBlock().Apply(tb => tb.SetBinding(TextBlock.TextProperty, new Binding()))
},
new ItemsRepeater
{
ItemsSource = Enumerable.Range(0, 50).Select(i => $"Item #{i:D2}"),
ItemTemplate = new DataTemplate(() => new Border
{
Width = 150,
Height = 100,
Background = new SolidColorBrush(Colors.DeepSkyBlue),
Child = new TextBlock().Apply(tb => tb.SetBinding(TextBlock.TextProperty, new Binding()))
})
}
}
})
})
};

TestServices.WindowHelper.WindowContent = sv;
await TestServices.WindowHelper.WaitForIdle();

sv.ChangeView(null, sv.ExtentHeight/2, null, disableAnimation: true);
await TestServices.WindowHelper.WaitForIdle();

var groupView = sut.Children.Single(g => g.DataContext as string == "Group #05");
var groupIr = (ItemsRepeater)((StackPanel)groupView).Children[1];

var beforeVisibleItems = groupIr.Children.Select(i => i.DataContext?.ToString()).Order().ToArray();

// Scroll by baby step to not be above the threshold which would cause a complete redraw
const int step = 10;
for (var i = 0; i < viewportHeight * 5; i += step)
{
sv.ChangeView(null, sv.VerticalOffset + step, null, disableAnimation: true);
await TestServices.WindowHelper.WaitForIdle();
}

var afterVisibleItems = groupIr.Children.Select(i => i.DataContext?.ToString()).Order().ToArray();

afterVisibleItems.Should().NotContain(beforeVisibleItems);
}
#endif


Expand Down
15 changes: 6 additions & 9 deletions src/Uno.UI/Microsoft/UI/Xaml/Controls/Repeater/StackLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ protected internal override Size MeasureOverride(VirtualizingLayoutContext conte
// Uno workaround [BEGIN]: Keep track of realized items count for viewport invalidation optimization
_uno_lastKnownItemsCount = context.ItemCount;
_uno_lastKnownRealizedElementsCount = algo.RealizedElementCount;
_uno_lastKnownDesiredSize = desiredSize;
// Uno workaround [END]

return desiredSize;
Expand Down Expand Up @@ -360,6 +361,7 @@ double GetAverageElementSize(
private double _uno_lastKnownAverageElementSize;
private int _uno_lastKnownRealizedElementsCount;
private int _uno_lastKnownItemsCount;
private Size _uno_lastKnownDesiredSize;

/// <inheritdoc />
protected internal override bool IsSignificantViewportChange(Rect oldViewport, Rect newViewport)
Expand All @@ -378,18 +380,13 @@ protected internal override bool IsSignificantViewportChange(Rect oldViewport, R
// Note2: Depending of the platform (Android), we might be invoked with empty viewport, make sure to consider out-of-bound in such case.
// Test case: When_NestedInSVAndOutOfViewportOnInitialLoad_Then_MaterializedEvenWhenScrollingOnMinorAxis
const int threshold = 100; // Allows 100px above and after to trigger loading even before IR is visible.
var wasOutOfBounds = oldViewport is { Width: 0 } or { Height: 0 }
|| MajorEnd(oldViewport) < -threshold
|| MajorStart(oldViewport) > MajorSize(oldViewport) + threshold
|| MinorEnd(oldViewport) < -threshold
|| MinorStart(oldViewport) > MinorSize(oldViewport) + threshold;
var isOutOfBounds = newViewport is { Width: 0 } or { Height: 0 }
|| MinorEnd(newViewport) < -threshold
|| MinorStart(newViewport) > MinorSize(newViewport) + threshold
|| MajorEnd(newViewport) < -threshold
|| MajorStart(newViewport) > MajorSize(newViewport) + threshold;
|| MajorStart(newViewport) > Major(_uno_lastKnownDesiredSize) + threshold
|| MinorEnd(newViewport) < -threshold
|| MinorStart(newViewport) > Minor(_uno_lastKnownDesiredSize) + threshold;

return wasOutOfBounds && !isOutOfBounds;
return !isOutOfBounds;
}

var size = Math.Max(MajorSize(oldViewport), MajorSize(newViewport));
Expand Down

0 comments on commit 342b8a6

Please sign in to comment.