|
@@ -1,10 +1,13 @@
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
using Avalonia.Controls.Presenters;
|
|
|
using Avalonia.Controls.Primitives;
|
|
|
using Avalonia.Controls.Templates;
|
|
|
using Avalonia.Layout;
|
|
|
+using Avalonia.Media;
|
|
|
using Avalonia.UnitTests;
|
|
|
+using Avalonia.VisualTree;
|
|
|
using Moq;
|
|
|
using Xunit;
|
|
|
|
|
@@ -12,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
|
|
|
{
|
|
|
public class ScrollViewerTests
|
|
|
{
|
|
|
+ private readonly MouseTestHelper _mouse = new();
|
|
|
+
|
|
|
[Fact]
|
|
|
public void Content_Is_Created()
|
|
|
{
|
|
@@ -249,6 +254,73 @@ namespace Avalonia.Controls.UnitTests
|
|
|
Assert.Equal(new Vector(20, 20), target.Offset);
|
|
|
}
|
|
|
|
|
|
+ [Fact]
|
|
|
+ public void Scroll_Does_Not_Jump_When_Viewport_Becomes_Smaller_While_Dragging_ScrollBar_Thumb()
|
|
|
+ {
|
|
|
+ var content = new TestContent
|
|
|
+ {
|
|
|
+ MeasureSize = new Size(1000, 10000),
|
|
|
+ };
|
|
|
+
|
|
|
+ var target = new ScrollViewer
|
|
|
+ {
|
|
|
+ Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
|
|
|
+ Content = content,
|
|
|
+ };
|
|
|
+ var root = new TestRoot(target);
|
|
|
+
|
|
|
+ root.LayoutManager.ExecuteInitialLayoutPass();
|
|
|
+
|
|
|
+ Assert.Equal(new Size(1000, 10000), target.Extent);
|
|
|
+ Assert.Equal(new Size(1000, 1000), target.Viewport);
|
|
|
+
|
|
|
+ // We're working in absolute coordinates (i.e. relative to the root) and clicking on
|
|
|
+ // the center of the vertical thumb.
|
|
|
+ var thumb = GetVerticalThumb(target);
|
|
|
+ var p = GetRootPoint(thumb, thumb.Bounds.Center);
|
|
|
+
|
|
|
+ // Press the mouse button in the center of the thumb.
|
|
|
+ _mouse.Down(thumb, position: p);
|
|
|
+ root.LayoutManager.ExecuteLayoutPass();
|
|
|
+
|
|
|
+ // Drag the thumb down 300 pixels.
|
|
|
+ _mouse.Move(thumb, p += new Vector(0, 300));
|
|
|
+ root.LayoutManager.ExecuteLayoutPass();
|
|
|
+
|
|
|
+ Assert.Equal(new Vector(0, 3000), target.Offset);
|
|
|
+ Assert.Equal(300, thumb.Bounds.Top);
|
|
|
+
|
|
|
+ // Now the extent changes from 10,000 to 5000.
|
|
|
+ content.MeasureSize /= 2;
|
|
|
+ content.InvalidateMeasure();
|
|
|
+ root.LayoutManager.ExecuteLayoutPass();
|
|
|
+
|
|
|
+ // Due to the extent change, the thumb moves down but the value remains the same.
|
|
|
+ Assert.Equal(600, thumb.Bounds.Top);
|
|
|
+ Assert.Equal(new Vector(0, 3000), target.Offset);
|
|
|
+
|
|
|
+ // Drag the thumb down another 100 pixels.
|
|
|
+ _mouse.Move(thumb, p += new Vector(0, 100));
|
|
|
+ root.LayoutManager.ExecuteLayoutPass();
|
|
|
+
|
|
|
+ // The drag should not cause the offset/thumb to jump *up* to the current absolute
|
|
|
+ // mouse position, i.e. it should move down in the direction of the drag even if the
|
|
|
+ // absolute mouse position is now above the thumb.
|
|
|
+ Assert.Equal(700, thumb.Bounds.Top);
|
|
|
+ Assert.Equal(new Vector(0, 3500), target.Offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Point GetRootPoint(Visual control, Point p)
|
|
|
+ {
|
|
|
+ if (control.GetVisualRoot() is Visual root &&
|
|
|
+ control.TransformToVisual(root) is Matrix m)
|
|
|
+ {
|
|
|
+ return p.Transform(m);
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new InvalidOperationException("Could not get the point in root coordinates.");
|
|
|
+ }
|
|
|
+
|
|
|
private Control CreateTemplate(ScrollViewer control, INameScope scope)
|
|
|
{
|
|
|
return new Grid
|
|
@@ -273,6 +345,7 @@ namespace Avalonia.Controls.UnitTests
|
|
|
{
|
|
|
Name = "PART_HorizontalScrollBar",
|
|
|
Orientation = Orientation.Horizontal,
|
|
|
+ Template = new FuncControlTemplate<ScrollBar>(CreateScrollBarTemplate),
|
|
|
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
|
|
|
[Grid.RowProperty] = 1,
|
|
|
}.RegisterInNameScope(scope),
|
|
@@ -280,6 +353,7 @@ namespace Avalonia.Controls.UnitTests
|
|
|
{
|
|
|
Name = "PART_VerticalScrollBar",
|
|
|
Orientation = Orientation.Vertical,
|
|
|
+ Template = new FuncControlTemplate<ScrollBar>(CreateScrollBarTemplate),
|
|
|
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
|
|
|
[Grid.ColumnProperty] = 1,
|
|
|
}.RegisterInNameScope(scope),
|
|
@@ -287,6 +361,44 @@ namespace Avalonia.Controls.UnitTests
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ private Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope)
|
|
|
+ {
|
|
|
+ return new Border
|
|
|
+ {
|
|
|
+ Child = new Track
|
|
|
+ {
|
|
|
+ Name = "track",
|
|
|
+ IsDirectionReversed = true,
|
|
|
+ [!Track.MinimumProperty] = scrollBar[!RangeBase.MinimumProperty],
|
|
|
+ [!Track.MaximumProperty] = scrollBar[!RangeBase.MaximumProperty],
|
|
|
+ [!!Track.ValueProperty] = scrollBar[!!RangeBase.ValueProperty],
|
|
|
+ [!Track.ViewportSizeProperty] = scrollBar[!ScrollBar.ViewportSizeProperty],
|
|
|
+ [!Track.OrientationProperty] = scrollBar[!ScrollBar.OrientationProperty],
|
|
|
+ Thumb = new Thumb
|
|
|
+ {
|
|
|
+ Template = new FuncControlTemplate<Thumb>(CreateThumbTemplate),
|
|
|
+ },
|
|
|
+ }.RegisterInNameScope(scope),
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Control CreateThumbTemplate(Thumb control, INameScope scope)
|
|
|
+ {
|
|
|
+ return new Border
|
|
|
+ {
|
|
|
+ Background = Brushes.Gray,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private Thumb GetVerticalThumb(ScrollViewer target)
|
|
|
+ {
|
|
|
+ var scrollbar = Assert.IsType<ScrollBar>(
|
|
|
+ target.GetTemplateChildren().FirstOrDefault(x => x.Name == "PART_VerticalScrollBar"));
|
|
|
+ var track = Assert.IsType<Track>(
|
|
|
+ scrollbar.GetTemplateChildren().FirstOrDefault(x => x.Name == "track"));
|
|
|
+ return Assert.IsType<Thumb>(track.Thumb);
|
|
|
+ }
|
|
|
+
|
|
|
private static void InitializeScrollViewer(ScrollViewer target)
|
|
|
{
|
|
|
target.ApplyTemplate();
|
|
@@ -295,5 +407,15 @@ namespace Avalonia.Controls.UnitTests
|
|
|
presenter.AttachToScrollViewer();
|
|
|
presenter.UpdateChild();
|
|
|
}
|
|
|
+
|
|
|
+ private class TestContent : Control
|
|
|
+ {
|
|
|
+ public Size MeasureSize { get; set; } = new Size(1000, 2000);
|
|
|
+
|
|
|
+ protected override Size MeasureOverride(Size availableSize)
|
|
|
+ {
|
|
|
+ return MeasureSize;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|