瀏覽代碼

Fix BringDescendantIntoView with respect to margins (#19544)

* remove margin deflation in BringDescendantIntoView and update related test to test  cases with margin and no margin

* create transform relative to presenter, instead of child.

* update tests

* fix tests
Emmanuel Hansen 1 月之前
父節點
當前提交
267d2470cc

+ 4 - 2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -251,14 +251,16 @@ namespace Avalonia.Controls.Presenters
                 return scrollable.BringIntoView(control, targetRect);
             }
 
-            var transform = target.TransformToVisual(Child);
+            var transform = target.TransformToVisual(this);
 
             if (transform == null)
             {
                 return false;
             }
 
-            var rectangle = targetRect.TransformToAABB(transform.Value).Deflate(new Thickness(Child.Margin.Left, Child.Margin.Top, 0, 0));
+            transform *= Matrix.CreateTranslation(Offset);
+
+            var rectangle = targetRect.TransformToAABB(transform.Value);
             Rect viewport = new Rect(Offset.X, Offset.Y, Viewport.Width, Viewport.Height);
 
             double minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right);

+ 73 - 7
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Reactive.Linq;
 using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
 using Avalonia.Layout;
 using Avalonia.UnitTests;
 using Xunit;
@@ -359,6 +360,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
             {
                 Width = 100,
                 Height = 100,
+                CanVerticallyScroll = true,
+                CanHorizontallyScroll = true,
                 Content = new Border
                 {
                     Width = 200,
@@ -404,13 +407,68 @@ namespace Avalonia.Controls.UnitTests.Presenters
         }
 
         [Fact]
-        public void BringDescendantIntoView_Should_Not_Move_Child_If_Completely_In_View()
+        public void BringDescendantIntoView_Should_Move_Child_Even_With_Margin_In_Parent()
         {
-            Border border = new Border
+            var namescope = new NameScope();
+            var content = new StackPanel()
             {
+                Orientation = Orientation.Vertical,
                 Width = 100,
-                Height = 20
+                Margin = new Thickness(0, 200),
+            };
+
+            for(int i = 0; i < 100; i++)
+            {
+                var child = new Border
+                {
+                    Width = 100,
+                    Height = 20,
+                    Name = $"Border{i}"
+                }.RegisterInNameScope(namescope);
+                content.Children.Add(child);
+            }
+            var target = new ScrollContentPresenter
+            {
+                CanHorizontallyScroll = true,
+                CanVerticallyScroll = true,
+                Width = 200,
+                Height = 100,
+                Content = new Decorator
+                {
+                    Child = content
+                }
             };
+
+            NameScope.SetNameScope(target, namescope);
+
+            target.UpdateChild();
+            target.Measure(Size.Infinity);
+            target.Arrange(new Rect(0, 0, 100, 100));
+
+            // Border20 is at position 0,600 with bottom at Y=620
+            var border20 = target.FindControl<Border>("Border20");
+            target.BringDescendantIntoView(border20, new Rect(border20.Bounds.Size));
+
+            // With viewport Height of 100, border becomes fully visible when alligned from the bottom at Offset Y=520, i.e. 620-100
+            Assert.Equal(new Vector(0, 520), target.Offset);
+
+            // Reset stack panel's margin
+            content.Margin = default;
+            target.Measure(Size.Infinity);
+            target.Arrange(new Rect(0, 0, 100, 100));
+
+            // Border20 is at position 0,800 with bottom at Y=820
+            var border40 = target.FindControl<Border>("Border40");
+            target.BringDescendantIntoView(border40, new Rect(border40.Bounds.Size));
+
+            // With viewport Height of 100, border becomes fully visible when alligned from the bottom at Offset Y=720, i.e. 820-100
+            Assert.Equal(new Vector(0, 720), target.Offset);
+        }
+
+        [Fact]
+        public void BringDescendantIntoView_Should_Not_Move_Child_If_Completely_In_View()
+        {
+            var namescope = new NameScope();
             var content = new StackPanel()
             {
                 Orientation = Orientation.Vertical,
@@ -419,12 +477,12 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
             for(int i = 0; i < 100; i++)
             {
-                // border position will be (0,60)
-                var child = i == 3 ? border : new Border
+                var child = new Border
                 {
                     Width = 100,
                     Height = 20,
-                };
+                    Name = $"Border{i}"
+                }.RegisterInNameScope(namescope);
                 content.Children.Add(child);
             }
             var target = new ScrollContentPresenter
@@ -439,11 +497,15 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 }
             };
 
+            NameScope.SetNameScope(target, namescope);
+
             target.UpdateChild();
             target.Measure(Size.Infinity);
             target.Arrange(new Rect(0, 0, 100, 100));
-            target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
+            var border3 = target.FindControl<Border>("Border3");
+            target.BringDescendantIntoView(border3, new Rect(border3.Bounds.Size));
 
+            // Border3 is still in view, offset hasn't changed
             Assert.Equal(new Vector(0, 0), target.Offset);
         }
 
@@ -486,14 +548,17 @@ namespace Avalonia.Controls.UnitTests.Presenters
             target.UpdateChild();
             target.Measure(Size.Infinity);
             target.Arrange(new Rect(0, 0, 100, 100));
+
             // move border to above the view port
             target.Offset = new Vector(0, 90);
+            target.Arrange(new Rect(0, 0, 100, 100));
             target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
 
             Assert.Equal(new Vector(0, 60), target.Offset);
 
             // move border to partially above the view port
             target.Offset = new Vector(0, 70);
+            target.Arrange(new Rect(0, 0, 100, 100));
             target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
 
             Assert.Equal(new Vector(0, 60), target.Offset);
@@ -540,6 +605,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             target.Arrange(new Rect(0, 0, 100, 100));
             // move border such that it's partially above viewport and partially below viewport
             target.Offset = new Vector(0, 90);
+            target.Arrange(new Rect(0, 0, 100, 100));
             target.BringDescendantIntoView(border, new Rect(border.Bounds.Size));
 
             Assert.Equal(new Vector(0, 90), target.Offset);

+ 1 - 1
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@@ -1561,7 +1561,7 @@ namespace Avalonia.Controls.UnitTests
         [InlineData(0.5d,
             0, 7,
             0, 7,
-            0, 9)]
+            7, 17)]
         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,