Pārlūkot izejas kodu

More WIP on virtualization.

Kinda nearly working in the test app.
Steven Kirk 9 gadi atpakaļ
vecāks
revīzija
1d83126d20

+ 9 - 3
src/Avalonia.Controls/Generators/IItemContainerGenerator.cs

@@ -37,7 +37,7 @@ namespace Avalonia.Controls.Generators
         /// Creates a container control for an item.
         /// </summary>
         /// <param name="index">
-        /// The index of the item of the data in the containing collection.
+        /// The index of the item of data in the control's items.
         /// </param>
         /// <param name="item">The item.</param>
         /// <param name="selector">An optional member selector.</param>
@@ -51,7 +51,7 @@ namespace Avalonia.Controls.Generators
         /// Removes a set of created containers.
         /// </summary>
         /// <param name="startingIndex">
-        /// The index of the first item of the data in the containing collection.
+        /// The index of the first item in the control's items.
         /// </param>
         /// <param name="count">The the number of items to remove.</param>
         /// <returns>The removed containers.</returns>
@@ -69,12 +69,18 @@ namespace Avalonia.Controls.Generators
         /// the gap.
         /// </summary>
         /// <param name="startingIndex">
-        /// The index of the first item of the data in the containing collection.
+        /// The index of the first item in the control's items.
         /// </param>
         /// <param name="count">The the number of items to remove.</param>
         /// <returns>The removed containers.</returns>
         IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count);
 
+        bool TryRecycle(
+            int oldIndex,
+            int newIndex,
+            object item,
+            IMemberSelector selector);
+
         /// <summary>
         /// Clears all created containers and returns the removed controls.
         /// </summary>

+ 26 - 0
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@@ -7,6 +7,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Reactive.Subjects;
 using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
 
 namespace Avalonia.Controls.Generators
 {
@@ -102,6 +103,16 @@ namespace Avalonia.Controls.Generators
             return result;
         }
 
+        /// <inheritdoc/>
+        public virtual bool TryRecycle(
+            int oldIndex,
+            int newIndex,
+            object item,
+            IMemberSelector selector)
+        {
+            return false;
+        }
+
         /// <inheritdoc/>
         public virtual IEnumerable<ItemContainerInfo> Clear()
         {
@@ -189,6 +200,21 @@ namespace Avalonia.Controls.Generators
             }
         }
 
+        /// <summary>
+        /// Moves a container.
+        /// </summary>
+        /// <param name="oldIndex">The old index.</param>
+        /// <param name="newIndex">The new index.</param>
+        /// <param name="item">The new item.</param>
+        /// <returns>The container info.</returns>
+        protected void MoveContainer(int oldIndex, int newIndex, object item)
+        {
+            var container = _containers[oldIndex];
+            var newContainer = new ItemContainerInfo(container.ContainerControl, item, newIndex);
+            _containers[oldIndex] = null;
+            AddContainer(newContainer);
+        }
+
         /// <summary>
         /// Gets all containers with an index that fall within a range.
         /// </summary>

+ 22 - 0
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@@ -75,5 +75,27 @@ namespace Avalonia.Controls.Generators
                 return result;
             }
         }
+
+        /// <inheritdoc/>
+        public override bool TryRecycle(
+            int oldIndex,
+            int newIndex,
+            object item,
+            IMemberSelector selector)
+        {
+            var container = ContainerFromIndex(oldIndex);
+            var i = selector != null ? selector.Select(item) : item;
+
+            container.SetValue(ContentProperty, i);
+
+            if (!(item is IControl))
+            {
+                container.DataContext = i;
+            }
+
+            MoveContainer(oldIndex, newIndex, i);
+
+            return true;
+        }
     }
 }

+ 5 - 0
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@@ -118,6 +118,11 @@ namespace Avalonia.Controls.Generators
             return base.RemoveRange(startingIndex, count);
         }
 
+        public override bool TryRecycle(int oldIndex, int newIndex, object item, IMemberSelector selector)
+        {
+            return false;
+        }
+
         /// <summary>
         /// Gets the data template for the specified item.
         /// </summary>

+ 38 - 41
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -53,50 +53,10 @@ namespace Avalonia.Controls.Presenters
                 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);
-                    }
-
+                    RecycleContainers(delta);
                     FirstIndex += delta;
                     LastIndex += delta;
                 }
@@ -167,5 +127,42 @@ namespace Avalonia.Controls.Presenters
                 LastIndex -= count;
             }
         }
+
+        private void RecycleContainers(int delta)
+        {
+            var panel = VirtualizingPanel;
+            var generator = Owner.ItemContainerGenerator;
+            var selector = Owner.MemberSelector;
+            var sign = delta < 0 ? -1 : 1;
+            var first = delta < 0 ? panel.Children.Count + delta : 0;
+            var count = Math.Abs(delta);
+            var containers = panel.Children.GetRange(first, count).ToList();
+
+            for (var i = 0; i < containers.Count; ++i)
+            {
+                var oldItemIndex = FirstIndex + first + i;
+                var newItemIndex = oldItemIndex + delta + ((panel.Children.Count - count) * sign);
+                var item = Items.ElementAt(newItemIndex);
+
+                if (!generator.TryRecycle(oldItemIndex, newItemIndex, item, selector))
+                {
+                    throw new NotImplementedException();
+                }
+            }
+
+            if (delta < panel.Children.Count)
+            {
+                panel.Children.RemoveRange(first, count);
+
+                if (delta > 0)
+                {
+                    panel.Children.AddRange(containers);
+                }
+                else
+                {
+                    panel.Children.InsertRange(0, containers);
+                }
+            }
+        }
     }
 }

+ 21 - 8
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@@ -101,14 +101,7 @@ namespace Avalonia.Controls.Presenters
             {
                 if (_generator == null)
                 {
-                    var i = TemplatedParent as ItemsControl;
-                    _generator = i?.ItemContainerGenerator;
-
-                    if (_generator == null)
-                    {
-                        _generator = new ItemContainerGenerator(this);
-                        _generator.ItemTemplate = ItemTemplate;
-                    }
+                    _generator = CreateItemContainerGenerator();
                 }
 
                 return _generator;
@@ -170,6 +163,26 @@ namespace Avalonia.Controls.Presenters
             }
         }
 
+        /// <summary>
+        /// Creates the <see cref="ItemContainerGenerator"/> for the control.
+        /// </summary>
+        /// <returns>
+        /// An <see cref="IItemContainerGenerator"/> or null.
+        /// </returns>
+        protected virtual IItemContainerGenerator CreateItemContainerGenerator()
+        {
+            var i = TemplatedParent as ItemsControl;
+            var result = i?.ItemContainerGenerator;
+
+            if (result == null)
+            {
+                result = new ItemContainerGenerator(this);
+                result.ItemTemplate = ItemTemplate;
+            }
+
+            return result;
+        }
+
         /// <inheritdoc/>
         protected override Size MeasureOverride(Size availableSize)
         {

+ 32 - 2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@@ -3,6 +3,7 @@
 
 using System.Collections.Generic;
 using System.Linq;
+using Avalonia.Controls.Generators;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
@@ -205,7 +206,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
                 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);
@@ -215,6 +216,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 Assert.Equal(new Vector(0, 0), ((IScrollable)target).Offset);
                 Assert.Equal(containers, target.Panel.Children);
 
+                var dcs = target.Panel.Children.Select(x => x.DataContext).ToList();
+
                 for (var i = 0; i < target.Panel.Children.Count; ++i)
                 {
                     Assert.Equal(items[i], target.Panel.Children[i].DataContext);
@@ -249,6 +252,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
         private static ItemsPresenter CreateTarget(
             ItemVirtualizationMode mode = ItemVirtualizationMode.Simple,
             Orientation orientation = Orientation.Vertical,
+            bool useContainers = true,
             int itemCount = 20)
         {
             ItemsPresenter result;
@@ -256,7 +260,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
             var scroller = new ScrollContentPresenter
             {
-                Content = result = new ItemsPresenter
+                Content = result = new TestItemsPresenter(useContainers)
                 {
                     Items = items,
                     ItemsPanel = VirtualizingPanelTemplate(orientation),
@@ -287,5 +291,31 @@ namespace Avalonia.Controls.UnitTests.Presenters
                 Orientation = orientation,
             });
         }
+
+        private class TestItemsPresenter : ItemsPresenter
+        {
+            private bool _useContainers;
+
+            public TestItemsPresenter(bool useContainers)
+            {
+                _useContainers = useContainers;
+            }
+
+            protected override IItemContainerGenerator CreateItemContainerGenerator()
+            {
+                return _useContainers ?
+                    new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) :
+                    new ItemContainerGenerator(this);
+            }
+        }
+
+        private class TestContainer : ContentControl
+        {
+            public TestContainer()
+            {
+                Width = 10;
+                Height = 10;
+            }
+        }
     }
 }