Skip to content

Commit 33b288f

Browse files
MrJulgrokys
andauthored
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>
1 parent 648edcd commit 33b288f

3 files changed

Lines changed: 55 additions & 11 deletions

File tree

src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,17 +251,20 @@ public bool BringDescendantIntoView(Visual target, Rect targetRect)
251251
return scrollable.BringIntoView(control, targetRect);
252252
}
253253

254-
var transform = target.TransformToVisual(this);
255-
256-
if (transform == null)
254+
// The `viewport` rectangle computed below is in extent coordinates, so transform
255+
// `targetRect` into that space too. Going via Child rather than via this + Offset
256+
// keeps the result independent of Offset, which may have changed since the last
257+
// arrange.
258+
if (target.TransformToVisual(Child) is not { } transform)
257259
{
258260
return false;
259261
}
260262

261-
transform *= Matrix.CreateTranslation(Offset);
262-
263-
var rectangle = targetRect.TransformToAABB(transform.Value);
264-
Rect viewport = new Rect(Offset.X, Offset.Y, Viewport.Width, Viewport.Height);
263+
var childPadding = GetChildPadding();
264+
var childMargin = GetChildMargin();
265+
var childContentOrigin = new Vector(childPadding.Left + childMargin.Left, childPadding.Top + childMargin.Top);
266+
var rectangle = targetRect.TransformToAABB(transform).Translate(childContentOrigin);
267+
var viewport = new Rect(Offset.X, Offset.Y, Viewport.Width, Viewport.Height);
265268

266269
double minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right);
267270
double minY = ComputeScrollOffsetWithMinimalScroll(viewport.Top, viewport.Bottom, rectangle.Top, rectangle.Bottom);
@@ -528,16 +531,26 @@ private Thickness GetChildPadding()
528531
return padding + borderThickness;
529532
}
530533

531-
private Size ComputeExtent(Size viewportSize, Thickness padding)
534+
private Thickness GetChildMargin()
532535
{
533-
var childMargin = Child!.Margin;
536+
if (Child is not { } child)
537+
return default;
538+
539+
var margin = child.Margin;
534540

535541
if (Child.UseLayoutRounding)
536542
{
537543
var scale = LayoutHelper.GetLayoutScale(Child);
538-
childMargin = LayoutHelper.RoundLayoutThickness(childMargin, scale);
544+
margin = LayoutHelper.RoundLayoutThickness(margin, scale);
539545
}
540546

547+
return margin;
548+
}
549+
550+
private Size ComputeExtent(Size viewportSize, Thickness padding)
551+
{
552+
var childMargin = GetChildMargin();
553+
541554
var extent = Child!.Bounds.Size.Inflate(childMargin).Inflate(padding);
542555

543556
if (MathUtilities.AreClose(extent.Width, viewportSize.Width, LayoutHelper.LayoutEpsilon))

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,37 @@ public void BringDescendantIntoView_Should_Update_Offset()
475475
Assert.Equal(new Vector(100, 100), target.Offset);
476476
}
477477

478+
[Fact]
479+
public void BringDescendantIntoView_Should_Be_Idempotent_Before_Next_Layout_Pass()
480+
{
481+
var panel = new StackPanel();
482+
483+
for (var i = 0; i < 100; ++i)
484+
panel.Children.Add(new Border { Height = 20 });
485+
486+
var target = new ScrollContentPresenter
487+
{
488+
Width = 50,
489+
Height = 100,
490+
CanVerticallyScroll = true,
491+
Content = panel,
492+
};
493+
494+
target.UpdateChild();
495+
target.Measure(Size.Infinity);
496+
target.Arrange(new Rect(0, 0, 50, 100));
497+
498+
// The 50th child spans 1000..1020, so with a 100px viewport it is brought into view by scrolling to 920.
499+
var child = panel.Children[50];
500+
501+
target.BringDescendantIntoView(child, new Rect(child.Bounds.Size));
502+
Assert.Equal(920, target.Offset.Y);
503+
Assert.False(target.IsArrangeValid);
504+
505+
target.BringDescendantIntoView(child, new Rect(child.Bounds.Size));
506+
Assert.Equal(920, target.Offset.Y);
507+
}
508+
478509
[Fact]
479510
public void BringDescendantIntoView_Should_Handle_Child_Margin()
480511
{

tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1724,7 +1724,7 @@ public void Focused_Container_Is_Positioned_Correctly_When_Scrolled_Past_Items_W
17241724
[InlineData(0.5d,
17251725
0, 7,
17261726
0, 7,
1727-
7, 17)]
1727+
0, 9)]
17281728
public void Focused_Container_Is_Positioned_Correctly_when_Container_Size_Change_Causes_It_To_Be_Moved_Into_Visible_Viewport(double bufferFactor,
17291729
int firstIndex1, int lastIndex1,
17301730
int firstIndex2, int lastIndex2,

0 commit comments

Comments
 (0)