From 86bfc26aceeb3000f47e3a67c39daa78deb0acda Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 10 Mar 2024 23:55:36 +0100 Subject: [PATCH] Fix ScrollContentPresenter incorrectly handling BringIntoView without changing its offset (#14900) * Added failing test for nested ScrollContentPresenters * Fix nested ScrollContentPresenter w/ content larger than viewport --- .../Presenters/ScrollContentPresenter.cs | 12 ++---- .../VirtualizingStackPanel.cs | 2 - .../Presenters/ScrollContentPresenterTests.cs | 43 ++++++++++++++++++- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 4a54c1b35c..04f6b1d8a1 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -259,38 +259,34 @@ namespace Avalonia.Controls.Presenters var rect = targetRect.TransformToAABB(transform.Value); var offset = Offset; - var result = false; if (rect.Bottom > offset.Y + Viewport.Height) { offset = offset.WithY((rect.Bottom - Viewport.Height) + Child.Margin.Top); - result = true; } if (rect.Y < offset.Y) { offset = offset.WithY(rect.Y); - result = true; } if (rect.Right > offset.X + Viewport.Width) { offset = offset.WithX((rect.Right - Viewport.Width) + Child.Margin.Left); - result = true; } if (rect.X < offset.X) { offset = offset.WithX(rect.X); - result = true; } - if (result) + if (Offset.NearlyEquals(offset)) { - SetCurrentValue(OffsetProperty, offset); + return false; } - return result; + SetCurrentValue(OffsetProperty, offset); + return true; } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index f702f09e8a..65410021a8 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -453,8 +453,6 @@ namespace Avalonia.Controls { Debug.Assert(_realizedElements is not null); - // If the control has not yet been laid out then the effective viewport won't have been set. - // Try to work it out from an ancestor control. var viewport = _viewport; // Get the viewport in the orientation direction. diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index 30628b1af8..274bee1528 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; using Avalonia.Layout; using Avalonia.UnitTests; using Xunit; @@ -366,7 +365,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.UpdateChild(); target.Measure(Size.Infinity); target.Arrange(new Rect(0, 0, 100, 100)); - target.BringDescendantIntoView(target.Child, new Rect(200, 200, 0, 0)); + target.BringDescendantIntoView(target.Child!, new Rect(200, 200, 0, 0)); Assert.Equal(new Vector(100, 100), target.Offset); } @@ -400,6 +399,46 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(new Vector(150, 150), target.Offset); } + [Fact] + public void Nested_Presenters_Should_Scroll_Outer_When_Content_Exceeds_Viewport() + { + ScrollContentPresenter innerPresenter; + Border border; + + var outerPresenter = new ScrollContentPresenter + { + CanHorizontallyScroll = true, + CanVerticallyScroll = true, + Width = 100, + Height = 100, + Content = innerPresenter = new ScrollContentPresenter + { + CanHorizontallyScroll = true, + CanVerticallyScroll = true, + Width = 100, + Height = 200, + Content = border = new Border + { + Width = 200, // larger than viewport + Height = 25, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + Margin = new Thickness(0, 120, 0, 0) + } + } + }; + + innerPresenter.UpdateChild(); + outerPresenter.UpdateChild(); + outerPresenter.Measure(new Size(100, 100)); + outerPresenter.Arrange(new Rect(0, 0, 100, 100)); + + border.BringIntoView(); + + Assert.Equal(new Vector(0, 45), outerPresenter.Offset); + Assert.Equal(new Vector(0, 0), innerPresenter.Offset); + } + private class TestControl : Control { public Size AvailableSize { get; private set; }