|
|
|
@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
{ |
|
|
|
using var app = App(); |
|
|
|
var items = Enumerable.Range(0, 100).Select(x => new Button { Width = 25, Height = 10}); |
|
|
|
var (target, scroll, itemsControl) = CreateTarget(items: items, useItemTemplate: false); |
|
|
|
var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: null); |
|
|
|
|
|
|
|
Assert.Equal(1000, scroll.Extent.Height); |
|
|
|
|
|
|
|
@ -252,7 +252,7 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
{ |
|
|
|
using var app = App(); |
|
|
|
var items = new ObservableCollection<Button>(Enumerable.Range(0, 100).Select(x => new Button { Width = 25, Height = 10 })); |
|
|
|
var (target, scroll, itemsControl) = CreateTarget(items: items, useItemTemplate: false); |
|
|
|
var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: null); |
|
|
|
|
|
|
|
Assert.Equal(1000, scroll.Extent.Height); |
|
|
|
|
|
|
|
@ -472,6 +472,122 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
Assert.Equal(10, raised); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void Scrolling_Down_With_Larger_Element_Does_Not_Cause_Jump_And_Arrives_At_End() |
|
|
|
{ |
|
|
|
using var app = App(); |
|
|
|
|
|
|
|
var items = Enumerable.Range(0, 1000).Select(x => new ItemWithHeight(x)).ToList(); |
|
|
|
items[20].Height = 200; |
|
|
|
|
|
|
|
var itemTemplate = new FuncDataTemplate<ItemWithHeight>((x, _) => |
|
|
|
new Canvas |
|
|
|
{ |
|
|
|
Width = 100, |
|
|
|
[!Canvas.HeightProperty] = new Binding("Height"), |
|
|
|
}); |
|
|
|
|
|
|
|
var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: itemTemplate); |
|
|
|
|
|
|
|
var index = target.FirstRealizedIndex; |
|
|
|
|
|
|
|
// Scroll down to the larger element.
|
|
|
|
while (target.LastRealizedIndex < items.Count - 1) |
|
|
|
{ |
|
|
|
scroll.LineDown(); |
|
|
|
Layout(target); |
|
|
|
|
|
|
|
Assert.True( |
|
|
|
target.FirstRealizedIndex >= index, |
|
|
|
$"{target.FirstRealizedIndex} is not greater or equal to {index}"); |
|
|
|
|
|
|
|
if (scroll.Offset.Y + scroll.Viewport.Height == scroll.Extent.Height) |
|
|
|
Assert.Equal(items.Count - 1, target.LastRealizedIndex); |
|
|
|
|
|
|
|
index = target.FirstRealizedIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void Scrolling_Up_To_Larger_Element_Does_Not_Cause_Jump() |
|
|
|
{ |
|
|
|
using var app = App(); |
|
|
|
|
|
|
|
var items = Enumerable.Range(0, 100).Select(x => new ItemWithHeight(x)).ToList(); |
|
|
|
items[20].Height = 200; |
|
|
|
|
|
|
|
var itemTemplate = new FuncDataTemplate<ItemWithHeight>((x, _) => |
|
|
|
new Canvas |
|
|
|
{ |
|
|
|
Width = 100, |
|
|
|
[!Canvas.HeightProperty] = new Binding("Height"), |
|
|
|
}); |
|
|
|
|
|
|
|
var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: itemTemplate); |
|
|
|
|
|
|
|
// Scroll past the larger element.
|
|
|
|
scroll.Offset = new Vector(0, 600); |
|
|
|
Layout(target); |
|
|
|
|
|
|
|
// Precondition checks
|
|
|
|
Assert.True(target.FirstRealizedIndex > 20); |
|
|
|
|
|
|
|
var index = target.FirstRealizedIndex; |
|
|
|
|
|
|
|
// Scroll up to the top.
|
|
|
|
while (scroll.Offset.Y > 0) |
|
|
|
{ |
|
|
|
scroll.LineUp(); |
|
|
|
Layout(target); |
|
|
|
|
|
|
|
Assert.True(target.FirstRealizedIndex <= index, $"{target.FirstRealizedIndex} is not less than {index}"); |
|
|
|
index = target.FirstRealizedIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void Scrolling_Up_To_Smaller_Element_Does_Not_Cause_Jump() |
|
|
|
{ |
|
|
|
using var app = App(); |
|
|
|
|
|
|
|
var items = Enumerable.Range(0, 100).Select(x => new ItemWithHeight(x, 30)).ToList(); |
|
|
|
items[20].Height = 25; |
|
|
|
|
|
|
|
var itemTemplate = new FuncDataTemplate<ItemWithHeight>((x, _) => |
|
|
|
new Canvas |
|
|
|
{ |
|
|
|
Width = 100, |
|
|
|
[!Canvas.HeightProperty] = new Binding("Height"), |
|
|
|
}); |
|
|
|
|
|
|
|
var (target, scroll, itemsControl) = CreateTarget(items: items, itemTemplate: itemTemplate); |
|
|
|
|
|
|
|
// Scroll past the larger element.
|
|
|
|
scroll.Offset = new Vector(0, 25 * items[0].Height); |
|
|
|
Layout(target); |
|
|
|
|
|
|
|
// Precondition checks
|
|
|
|
Assert.True(target.FirstRealizedIndex > 20); |
|
|
|
|
|
|
|
var index = target.FirstRealizedIndex; |
|
|
|
|
|
|
|
// Scroll up to the top.
|
|
|
|
while (scroll.Offset.Y > 0) |
|
|
|
{ |
|
|
|
scroll.Offset = scroll.Offset - new Vector(0, 5); |
|
|
|
Layout(target); |
|
|
|
|
|
|
|
Assert.True( |
|
|
|
target.FirstRealizedIndex <= index, |
|
|
|
$"{target.FirstRealizedIndex} is not less than {index}"); |
|
|
|
Assert.True( |
|
|
|
index - target.FirstRealizedIndex <= 1, |
|
|
|
$"FirstIndex changed from {index} to {target.FirstRealizedIndex}"); |
|
|
|
|
|
|
|
index = target.FirstRealizedIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static IReadOnlyList<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl) |
|
|
|
{ |
|
|
|
return target.GetRealizedElements() |
|
|
|
@ -516,7 +632,7 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
|
|
|
|
private static (VirtualizingStackPanel, ScrollViewer, ItemsControl) CreateTarget( |
|
|
|
IEnumerable<object>? items = null, |
|
|
|
bool useItemTemplate = true, |
|
|
|
Optional<IDataTemplate?> itemTemplate = default, |
|
|
|
IEnumerable<Style>? styles = null) |
|
|
|
{ |
|
|
|
var target = new VirtualizingStackPanel(); |
|
|
|
@ -530,6 +646,7 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
|
|
|
|
var scroll = new ScrollViewer |
|
|
|
{ |
|
|
|
Name = "PART_ScrollViewer", |
|
|
|
Content = presenter, |
|
|
|
Template = ScrollViewerTemplate(), |
|
|
|
}; |
|
|
|
@ -537,13 +654,11 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
var itemsControl = new ItemsControl |
|
|
|
{ |
|
|
|
ItemsSource = items, |
|
|
|
Template = new FuncControlTemplate<ItemsControl>((_, _) => scroll), |
|
|
|
Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)), |
|
|
|
ItemsPanel = new FuncTemplate<Panel?>(() => target), |
|
|
|
ItemTemplate = itemTemplate.GetValueOrDefault(DefaultItemTemplate()), |
|
|
|
}; |
|
|
|
|
|
|
|
if (useItemTemplate) |
|
|
|
itemsControl.ItemTemplate = new FuncDataTemplate<object>((x, _) => new Canvas { Width = 100, Height = 10 }); |
|
|
|
|
|
|
|
var root = new TestRoot(true, itemsControl); |
|
|
|
root.ClientSize = new(100, 100); |
|
|
|
|
|
|
|
@ -555,6 +670,11 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
return (target, scroll, itemsControl); |
|
|
|
} |
|
|
|
|
|
|
|
private static IDataTemplate DefaultItemTemplate() |
|
|
|
{ |
|
|
|
return new FuncDataTemplate<object>((x, _) => new Canvas { Width = 100, Height = 10 }); |
|
|
|
} |
|
|
|
|
|
|
|
private static void Layout(Control target) |
|
|
|
{ |
|
|
|
var root = (ILayoutRoot?)target.GetVisualRoot(); |
|
|
|
@ -578,6 +698,18 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
|
|
|
|
private static IDisposable App() => UnitTestApplication.Start(TestServices.RealFocus); |
|
|
|
|
|
|
|
private class ItemWithHeight |
|
|
|
{ |
|
|
|
public ItemWithHeight(int index, double height = 10) |
|
|
|
{ |
|
|
|
Caption = $"Item {index}"; |
|
|
|
Height = height; |
|
|
|
} |
|
|
|
|
|
|
|
public string Caption { get; set; } |
|
|
|
public double Height { get; set; } |
|
|
|
} |
|
|
|
|
|
|
|
private class ResettingCollection : List<string>, INotifyCollectionChanged |
|
|
|
{ |
|
|
|
public ResettingCollection(IEnumerable<string> items) |
|
|
|
|