Browse Source

Fix initial logical scrollable state (#13066)

* Add failing test for #13064

* Remove Content binding from ScrollViewer template.

Since #10803, the `ScrollContentPresenter.Content` binding is managed internally, not via a binding in the template.

* Assign owner before doing subscriptions.

Fixes #13064.
Steven Kirk 2 years ago
parent
commit
bf1be0cd2f

+ 1 - 1
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -323,6 +323,7 @@ namespace Avalonia.Controls.Presenters
             }
 
             _ownerSubscriptions?.Dispose();
+            _owner = owner;
 
             var subscriptionDisposables = new IDisposable?[]
             {
@@ -333,7 +334,6 @@ namespace Avalonia.Controls.Presenters
                 IfUnset(ContentProperty, p => Bind(p, owner.GetBindingObservable(ContentProperty), Data.BindingPriority.Template)),
             }.Where(d => d != null).Cast<IDisposable>().ToArray();
 
-            _owner = owner;
             _ownerSubscriptions = new CompositeDisposable(subscriptionDisposables);
 
             static bool NotDisabled(ScrollBarVisibility v) => v != ScrollBarVisibility.Disabled;

+ 0 - 1
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@@ -1182,7 +1182,6 @@ namespace Avalonia.Controls.UnitTests
                         new ScrollContentPresenter
                         {
                             Name = "PART_ContentPresenter",
-                            [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(),
                         }.RegisterInNameScope(scope),
                         new ScrollBar
                         {

+ 0 - 1
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@@ -1328,7 +1328,6 @@ namespace Avalonia.Controls.UnitTests.Primitives
                         new ScrollContentPresenter
                         {
                             Name = "PART_ContentPresenter",
-                            [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(),
                         }.RegisterInNameScope(scope),
                         new ScrollBar
                         {

+ 161 - 0
tests/Avalonia.Controls.UnitTests/ScrollViewerTests_ILogicalScrollable.cs

@@ -0,0 +1,161 @@
+using System;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Input;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+using Xunit;
+
+#nullable enable
+
+namespace Avalonia.Controls.UnitTests;
+
+public class ScrollViewerTests_ILogicalScrollable
+{
+    [Fact]
+    public void Extent_Offset_And_Viewport_Should_Be_Read_From_ILogicalScrollable()
+    {
+        var scrollable = new TestScrollable
+        {
+            Extent = new Size(100, 100),
+            Offset = new Vector(50, 50),
+            Viewport = new Size(25, 25),
+        };
+
+        var target = CreateTarget(scrollable);
+
+        Assert.Equal(scrollable.Extent, target.Extent);
+        Assert.Equal(scrollable.Offset, target.Offset);
+        Assert.Equal(scrollable.Viewport, target.Viewport);
+
+        scrollable.Extent = new Size(200, 200);
+        scrollable.Offset = new Vector(100, 100);
+        scrollable.Viewport = new Size(50, 50);
+
+        Assert.Equal(scrollable.Extent, target.Extent);
+        Assert.Equal(scrollable.Offset, target.Offset);
+        Assert.Equal(scrollable.Viewport, target.Viewport);
+    }
+
+    private static ScrollViewer CreateTarget(object? content)
+    {
+        var result = new ScrollViewer
+        {
+            Content = content,
+        };
+
+        var root = new TestRoot
+        {
+            Resources =
+            {
+                { typeof(ScrollViewer), CreateScrollViewerTheme() },
+            },
+            Child = result,
+        };
+
+        root.LayoutManager.ExecuteInitialLayoutPass();
+        return result;
+    }
+
+    private static ControlTheme CreateScrollViewerTheme()
+    {
+        return new ControlTheme(typeof(ScrollViewer))
+        {
+            Setters =
+            {
+                new Setter(TreeView.TemplateProperty, CreateScrollViewerTemplate()),
+            },
+        };
+    }
+
+    private static FuncControlTemplate CreateScrollViewerTemplate()
+    {
+        return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
+            new Panel
+            {
+                Children =
+                {
+                    new ScrollContentPresenter
+                    {
+                        Name = "PART_ContentPresenter",
+                    }.RegisterInNameScope(scope),
+                }
+            });
+    }
+
+    private class TestScrollable : Control, ILogicalScrollable
+    {
+        private Size _extent;
+        private Vector _offset;
+        private Size _viewport;
+        private EventHandler? _scrollInvalidated;
+
+        public bool CanHorizontallyScroll { get; set; }
+        public bool CanVerticallyScroll { get; set; }
+        public bool IsLogicalScrollEnabled { get; set; } = true;
+        public Size AvailableSize { get; private set; }
+
+        public bool HasScrollInvalidatedSubscriber => _scrollInvalidated != null;
+        
+        public event EventHandler? ScrollInvalidated
+        {
+            add => _scrollInvalidated += value;
+            remove => _scrollInvalidated -= value;
+        }
+
+        public Size Extent
+        {
+            get => _extent;
+            set
+            {
+                _extent = value;
+                _scrollInvalidated?.Invoke(this, EventArgs.Empty);
+            }
+        }
+
+        public Vector Offset
+        {
+            get => _offset;
+            set
+            {
+                _offset = value;
+                _scrollInvalidated?.Invoke(this, EventArgs.Empty);
+            }
+        }
+
+        public Size Viewport
+        {
+            get => _viewport;
+            set
+            {
+                _viewport = value;
+                _scrollInvalidated?.Invoke(this, EventArgs.Empty);
+            }
+        }
+
+        public Size ScrollSize => new(double.PositiveInfinity, 1);
+        public Size PageScrollSize => new(double.PositiveInfinity, Viewport.Height);
+
+        public bool BringIntoView(Control target, Rect targetRect)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void RaiseScrollInvalidated(EventArgs e)
+        {
+            _scrollInvalidated?.Invoke(this, e);
+        }
+
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            AvailableSize = availableSize;
+            return new Size(150, 150);
+        }
+
+        public Control? GetControlInDirection(NavigationDirection direction, Control? from)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}