|
|
|
@ -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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|