Browse Source

Fix multiple ScrollContentPresenter.BringDescendantIntoView calls (#22001)

* Add failing test for ScrollContentPresenter

* Fix ScrollContentPresenter multiple BringDescendantIntoView

* Update src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

Co-authored-by: Steven Kirk <grokys@users.noreply.github.com>

---------

Co-authored-by: Steven Kirk <grokys@users.noreply.github.com>
pull/22079/head
Julien Lebosquain 1 month ago
committed by GitHub
parent
commit
33b288ffd6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 33
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  2. 31
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs
  3. 2
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

33
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -251,17 +251,20 @@ namespace Avalonia.Controls.Presenters
return scrollable.BringIntoView(control, targetRect);
}
var transform = target.TransformToVisual(this);
if (transform == null)
// The `viewport` rectangle computed below is in extent coordinates, so transform
// `targetRect` into that space too. Going via Child rather than via this + Offset
// keeps the result independent of Offset, which may have changed since the last
// arrange.
if (target.TransformToVisual(Child) is not { } transform)
{
return false;
}
transform *= Matrix.CreateTranslation(Offset);
var rectangle = targetRect.TransformToAABB(transform.Value);
Rect viewport = new Rect(Offset.X, Offset.Y, Viewport.Width, Viewport.Height);
var childPadding = GetChildPadding();
var childMargin = GetChildMargin();
var childContentOrigin = new Vector(childPadding.Left + childMargin.Left, childPadding.Top + childMargin.Top);
var rectangle = targetRect.TransformToAABB(transform).Translate(childContentOrigin);
var viewport = new Rect(Offset.X, Offset.Y, Viewport.Width, Viewport.Height);
double minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right);
double minY = ComputeScrollOffsetWithMinimalScroll(viewport.Top, viewport.Bottom, rectangle.Top, rectangle.Bottom);
@ -528,16 +531,26 @@ namespace Avalonia.Controls.Presenters
return padding + borderThickness;
}
private Size ComputeExtent(Size viewportSize, Thickness padding)
private Thickness GetChildMargin()
{
var childMargin = Child!.Margin;
if (Child is not { } child)
return default;
var margin = child.Margin;
if (Child.UseLayoutRounding)
{
var scale = LayoutHelper.GetLayoutScale(Child);
childMargin = LayoutHelper.RoundLayoutThickness(childMargin, scale);
margin = LayoutHelper.RoundLayoutThickness(margin, scale);
}
return margin;
}
private Size ComputeExtent(Size viewportSize, Thickness padding)
{
var childMargin = GetChildMargin();
var extent = Child!.Bounds.Size.Inflate(childMargin).Inflate(padding);
if (MathUtilities.AreClose(extent.Width, viewportSize.Width, LayoutHelper.LayoutEpsilon))

31
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs

@ -475,6 +475,37 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Vector(100, 100), target.Offset);
}
[Fact]
public void BringDescendantIntoView_Should_Be_Idempotent_Before_Next_Layout_Pass()
{
var panel = new StackPanel();
for (var i = 0; i < 100; ++i)
panel.Children.Add(new Border { Height = 20 });
var target = new ScrollContentPresenter
{
Width = 50,
Height = 100,
CanVerticallyScroll = true,
Content = panel,
};
target.UpdateChild();
target.Measure(Size.Infinity);
target.Arrange(new Rect(0, 0, 50, 100));
// The 50th child spans 1000..1020, so with a 100px viewport it is brought into view by scrolling to 920.
var child = panel.Children[50];
target.BringDescendantIntoView(child, new Rect(child.Bounds.Size));
Assert.Equal(920, target.Offset.Y);
Assert.False(target.IsArrangeValid);
target.BringDescendantIntoView(child, new Rect(child.Bounds.Size));
Assert.Equal(920, target.Offset.Y);
}
[Fact]
public void BringDescendantIntoView_Should_Handle_Child_Margin()
{

2
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -1724,7 +1724,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData(0.5d,
0, 7,
0, 7,
7, 17)]
0, 9)]
public void Focused_Container_Is_Positioned_Correctly_when_Container_Size_Change_Causes_It_To_Be_Moved_Into_Visible_Viewport(double bufferFactor,
int firstIndex1, int lastIndex1,
int firstIndex2, int lastIndex2,

Loading…
Cancel
Save