Browse Source

Added IScrollable.IsLogicalScrollEnabled.

Steven Kirk 9 years ago
parent
commit
7db353e742

+ 1 - 0
samples/XamlTestApplicationPcl/TestScrollable.cs

@@ -14,6 +14,7 @@ namespace XamlTestApplication
         private Size _viewport;
         private Size _lineSize;
 
+        public bool IsLogicalScrollEnabled => true;
         public Action InvalidateScroll { get; set; }
 
         Size IScrollable.Extent

+ 42 - 19
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Controls.Presenters
         {
             AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
 
-            this.GetObservable(ChildProperty).Subscribe(ChildChanged);
+            this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
         }
 
         /// <summary>
@@ -194,22 +194,25 @@ namespace Avalonia.Controls.Presenters
         protected override Size ArrangeOverride(Size finalSize)
         {
             var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable;
-            var offset = default(Vector);
+            var logicalScroll = _scrollableSubscription != null;
 
-            if (_scrollableSubscription == null)
+            if (!logicalScroll)
             {
                 Viewport = finalSize;
                 Extent = _measuredExtent;
-                offset = Offset;
-            }
 
-            if (child != null)
-            {
-                var size = new Size(
+                if (child != null)
+                {
+                    var size = new Size(
                     Math.Max(finalSize.Width, child.DesiredSize.Width),
                     Math.Max(finalSize.Height, child.DesiredSize.Height));
-                child.Arrange(new Rect((Point)(-offset), size));
-                return finalSize;
+                    child.Arrange(new Rect((Point)(-Offset), size));
+                    return finalSize;
+                }
+            }
+            else if (child != null)
+            {
+                child.Arrange(new Rect(finalSize));
             }
 
             return new Size();
@@ -222,7 +225,7 @@ namespace Avalonia.Controls.Presenters
             {
                 var scrollable = Child as IScrollable;
 
-                if (scrollable != null)
+                if (scrollable?.IsLogicalScrollEnabled == true)
                 {                    
                     var y = Offset.Y + (-e.Delta.Y * scrollable.ScrollSize.Height);
                     y = Math.Max(y, 0);
@@ -246,7 +249,7 @@ namespace Avalonia.Controls.Presenters
             e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
         }
 
-        private void ChildChanged(IControl child)
+        private void UpdateScrollableSubscription(IControl child)
         {
             var scrollable = child as IScrollable;
 
@@ -256,18 +259,38 @@ namespace Avalonia.Controls.Presenters
             if (scrollable != null)
             {
                 scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
-                _scrollableSubscription = new CompositeDisposable(
-                    this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
-                    Disposable.Create(() => scrollable.InvalidateScroll = null));
-                UpdateFromScrollable(scrollable);
+
+                if (scrollable?.IsLogicalScrollEnabled == true)
+                {
+                    _scrollableSubscription = new CompositeDisposable(
+                        this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
+                        Disposable.Create(() => scrollable.InvalidateScroll = null));
+                    UpdateFromScrollable(scrollable);
+                }
             }
         }
 
         private void UpdateFromScrollable(IScrollable scrollable)
         {
-            Viewport = scrollable.Viewport;
-            Extent = scrollable.Extent;
-            Offset = scrollable.Offset;
+            var logicalScroll = _scrollableSubscription != null;
+
+            if (logicalScroll != scrollable.IsLogicalScrollEnabled)
+            {
+                UpdateScrollableSubscription(Child);
+
+                if (!scrollable.IsLogicalScrollEnabled)
+                {
+                    Offset = default(Vector);
+                    InvalidateMeasure();
+                }
+            }
+
+            if (scrollable.IsLogicalScrollEnabled)
+            {
+                Viewport = scrollable.Viewport;
+                Extent = scrollable.Extent;
+                Offset = scrollable.Offset;
+            }
         }
     }
 }

+ 1 - 0
src/Avalonia.Controls/Presenters/ThingamybobPresenter.cs

@@ -23,6 +23,7 @@ namespace Avalonia.Controls.Presenters
 
         public IPanel Panel => _panel;
 
+        bool IScrollable.IsLogicalScrollEnabled => true;
         Action IScrollable.InvalidateScroll { get; set; }
 
         Size IScrollable.Extent => new Size(1, 100 * AverageItemSize );

+ 12 - 0
src/Avalonia.Controls/Primitives/IScrollable.cs

@@ -9,8 +9,20 @@ namespace Avalonia.Controls.Primitives
     /// Interface implemented by controls that handle their own scrolling when placed inside a 
     /// <see cref="ScrollViewer"/>.
     /// </summary>
+    /// <remarks>
+    /// Controls that implement this interface, when placed inside a <see cref="ScrollViewer"/>
+    /// can override the physical scrolling behavior of the scroll viewer with logical scrolling.
+    /// Physical scrolling means that the scroll viewer is a simple viewport onto a larger canvas
+    /// whereas logical scrolling means that the scrolling is handled by the child control itself
+    /// and it can choose to do handle the scroll information as it sees fit.
+    /// </remarks>
     public interface IScrollable
     {
+        /// <summary>
+        /// Gets a value indicating whether logical scrolling is enabled on the control.
+        /// </summary>
+        bool IsLogicalScrollEnabled { get; }
+
         /// <summary>
         /// Gets or sets the scroll invalidation method.
         /// </summary>

+ 69 - 0
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests_IScrollable.cs

@@ -5,6 +5,7 @@ using System;
 using System.Reactive.Linq;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
+using Avalonia.Layout;
 using Xunit;
 
 namespace Avalonia.Controls.UnitTests
@@ -48,6 +49,27 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds);
         }
 
+        [Fact]
+        public void Arrange_Should_Offset_IScrollable_Bounds_When_Logical_Scroll_Disabled()
+        {
+            var scrollable = new TestScrollable
+            {
+                IsLogicalScrollEnabled = false,
+            };
+
+            var target = new ScrollContentPresenter
+            {
+                Content = scrollable,
+                Offset = new Vector(25, 25),
+            };
+
+            target.UpdateChild();
+            target.Measure(new Size(100, 100));
+            target.Arrange(new Rect(0, 0, 100, 100));
+
+            Assert.Equal(new Rect(-25, -25, 150, 150), scrollable.Bounds);
+        }
+
         [Fact]
         public void Arrange_Should_Not_Set_Viewport_And_Extent_With_IScrollable()
         {
@@ -169,12 +191,59 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Vector(50, 50), scrollable.Offset);
         }
 
+        [Fact]
+        public void Toggling_IsLogicalScrollEnabled_Should_Update_State()
+        {
+            var scrollable = new TestScrollable
+            {
+                Extent = new Size(100, 100),
+                Offset = new Vector(50, 50),
+                Viewport = new Size(25, 25),
+            };
+
+            var target = new ScrollContentPresenter
+            {
+                Content = scrollable,
+            };
+
+            target.UpdateChild();
+            target.Measure(new Size(100, 100));
+            target.Arrange(new Rect(0, 0, 100, 100));
+
+            Assert.Equal(scrollable.Extent, target.Extent);
+            Assert.Equal(scrollable.Offset, target.Offset);
+            Assert.Equal(scrollable.Viewport, target.Viewport);
+            Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds);
+
+            scrollable.IsLogicalScrollEnabled = false;
+            scrollable.InvalidateScroll();
+            target.Measure(new Size(100, 100));
+            target.Arrange(new Rect(0, 0, 100, 100));
+
+            Assert.Equal(new Size(150, 150), target.Extent);
+            Assert.Equal(new Vector(0, 0), target.Offset);
+            Assert.Equal(new Size(100, 100), target.Viewport);
+            Assert.Equal(new Rect(0, 0, 150, 150), scrollable.Bounds);
+
+            scrollable.IsLogicalScrollEnabled = true;
+            scrollable.InvalidateScroll();
+            target.Measure(new Size(100, 100));
+            target.Arrange(new Rect(0, 0, 100, 100));
+
+            Assert.Equal(scrollable.Extent, target.Extent);
+            Assert.Equal(scrollable.Offset, target.Offset);
+            Assert.Equal(scrollable.Viewport, target.Viewport);
+            Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds);
+        }
+
+
         private class TestScrollable : Control, IScrollable
         {
             private Size _extent;
             private Vector _offset;
             private Size _viewport;
 
+            public bool IsLogicalScrollEnabled { get; set; } = true;
             public Size AvailableSize { get; private set; }
             public Action InvalidateScroll { get; set; }