Browse Source

WIP Trying to get virt scrolling working.

Steven Kirk 9 years ago
parent
commit
1c7a99dc5e

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

@@ -23,6 +23,7 @@ namespace Avalonia.Controls.Presenters
 
         public abstract bool IsLogicalScrollEnabled { get; }
         public abstract Size Extent { get; }
+        public abstract Vector Offset { get; set; }
         public abstract Size Viewport { get; }
 
         public static ItemVirtualizer Create(ItemsPresenter owner)

+ 8 - 8
src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs

@@ -21,18 +21,18 @@ namespace Avalonia.Controls.Presenters
 
         public override Size Extent
         {
-            get
-            {
-                throw new NotSupportedException();
-            }
+            get { throw new NotSupportedException(); }
+        }
+
+        public override Vector Offset
+        {
+            get { throw new NotSupportedException(); }
+            set { throw new NotSupportedException(); }
         }
 
         public override Size Viewport
         {
-            get
-            {
-                throw new NotSupportedException();
-            }
+            get { throw new NotSupportedException(); }
         }
 
         public override void Arranging(Size finalSize)

+ 69 - 0
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -31,6 +31,75 @@ namespace Avalonia.Controls.Presenters
             }
         }
 
+        public override Vector Offset
+        {
+            get
+            {
+                if (VirtualizingPanel.ScrollDirection == Orientation.Vertical)
+                {
+                    return new Vector(0, FirstIndex);
+                }
+                else
+                {
+                    return new Vector(FirstIndex, 0);
+                }
+            }
+
+            set
+            {
+                var scroll = (VirtualizingPanel.ScrollDirection == Orientation.Vertical) ?
+                    value.Y : value.X;
+                var delta = (int)(scroll - FirstIndex);
+                var panel = VirtualizingPanel;
+
+                if (delta != 0)
+                {
+                    if (delta >= panel.Children.Count)
+                    {
+                        var index = FirstIndex + delta;
+
+                        foreach (var container in panel.Children)
+                        {
+                            container.DataContext = Items.ElementAt(index++);
+                        }
+                    }
+                    else if (delta > 0)
+                    {
+                        var containers = panel.Children.GetRange(0, delta).ToList();
+                        panel.Children.RemoveRange(0, delta);
+
+                        var index = LastIndex + 1;
+
+                        foreach (var container in containers)
+                        {
+                            container.DataContext = Items.ElementAt(index++);
+                        }
+
+                        panel.Children.AddRange(containers);
+                    }
+                    else
+                    {
+                        var first = panel.Children.Count + delta;
+                        var count = -delta;
+                        var containers = panel.Children.GetRange(first, count).ToList();
+                        panel.Children.RemoveRange(first, count);
+
+                        var index = FirstIndex + delta;
+
+                        foreach (var container in containers)
+                        {
+                            container.DataContext = Items.ElementAt(index++);
+                        }
+
+                        panel.Children.InsertRange(0, containers);
+                    }
+
+                    FirstIndex += delta;
+                    LastIndex += delta;
+                }
+            }
+        }
+
         public override Size Viewport
         {
             get

+ 15 - 6
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -2,14 +2,10 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Collections;
-using System.Collections.Generic;
 using System.Collections.Specialized;
-using System.Linq;
-using Avalonia.Controls.Generators;
 using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Utils;
 using Avalonia.Input;
+using static Avalonia.Utilities.MathUtilities;
 
 namespace Avalonia.Controls.Presenters
 {
@@ -60,7 +56,11 @@ namespace Avalonia.Controls.Presenters
         Size IScrollable.Extent => _virtualizer.Extent;
 
         /// <inheritdoc/>
-        Vector IScrollable.Offset { get; set; }
+        Vector IScrollable.Offset
+        {
+            get { return _virtualizer.Offset; }
+            set { _virtualizer.Offset = CoerceOffset(value); }
+        }
 
         /// <inheritdoc/>
         Size IScrollable.Viewport => _virtualizer.Viewport;
@@ -83,6 +83,7 @@ namespace Avalonia.Controls.Presenters
         protected override void PanelCreated(IPanel panel)
         {
             _virtualizer = ItemVirtualizer.Create(this);
+            ((IScrollable)this).InvalidateScroll?.Invoke();
 
             if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty))
             {
@@ -100,5 +101,13 @@ namespace Avalonia.Controls.Presenters
         {
             _virtualizer?.ItemsChanged(Items, e);
         }
+
+        private Vector CoerceOffset(Vector value)
+        {
+            var scrollable = (IScrollable)this;
+            var maxX = Math.Max(scrollable.Extent.Width - scrollable.Viewport.Width, 0);
+            var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0);
+            return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY));
+        }
     }
 }

+ 62 - 0
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@@ -182,6 +182,68 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
                 Assert.Equal(8, target.Panel.Children.Count);
             }
+
+            [Fact]
+            public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items()
+            {
+                var target = CreateTarget();
+                var items = (IList<string>)target.Items;
+
+                target.ApplyTemplate();
+                target.Measure(new Size(100, 100));
+                target.Arrange(new Rect(0, 0, 100, 100));
+
+                var containers = target.Panel.Children.ToList();
+                var scroller = (ScrollContentPresenter)target.Parent;
+
+                scroller.Offset = new Vector(0, 5);
+
+                var scrolledContainers = containers
+                    .Skip(5)
+                    .Take(5)
+                    .Concat(containers.Take(5)).ToList();
+
+                Assert.Equal(new Vector(0, 5), ((IScrollable)target).Offset);
+                Assert.Equal(scrolledContainers, target.Panel.Children);
+
+                for (var i = 0; i < target.Panel.Children.Count; ++i)
+                {
+                    Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext);
+                }
+                 
+                scroller.Offset = new Vector(0, 0);
+                Assert.Equal(new Vector(0, 0), ((IScrollable)target).Offset);
+                Assert.Equal(containers, target.Panel.Children);
+
+                for (var i = 0; i < target.Panel.Children.Count; ++i)
+                {
+                    Assert.Equal(items[i], target.Panel.Children[i].DataContext);
+                }
+            }
+
+            [Fact]
+            public void Scrolling_More_Than_A_Page_Should_Recycle_Items()
+            {
+                var target = CreateTarget();
+                var items = (IList<string>)target.Items;
+
+                target.ApplyTemplate();
+                target.Measure(new Size(100, 100));
+                target.Arrange(new Rect(0, 0, 100, 100));
+
+                var containers = target.Panel.Children.ToList();
+                var scroller = (ScrollContentPresenter)target.Parent;
+
+                scroller.Offset = new Vector(0, 10);
+
+                Assert.Equal(new Vector(0, 10), ((IScrollable)target).Offset);
+                Assert.Equal(containers, target.Panel.Children);
+
+                for (var i = 0; i < target.Panel.Children.Count; ++i)
+                {
+                    Assert.Equal(items[i + 10], target.Panel.Children[i].DataContext);
+                }
+            }
         }
 
         private static ItemsPresenter CreateTarget(