Browse Source

Implement INavigableContainer.

Steven Kirk 3 years ago
parent
commit
57b1ba965c

+ 20 - 2
src/Avalonia.Controls/VirtualizingPanel.cs

@@ -3,13 +3,14 @@ using System.Collections;
 using System.Collections.Specialized;
 using System.Diagnostics.CodeAnalysis;
 using Avalonia.Controls.Utils;
+using Avalonia.Input;
 
 namespace Avalonia.Controls
 {
     /// <summary>
     /// Base class for panels that can be used to virtualize items.
     /// </summary>
-    public abstract class VirtualizingPanel : Panel
+    public abstract class VirtualizingPanel : Panel, INavigableContainer
     {
         private ItemsControl? _itemsControl;
 
@@ -27,11 +28,19 @@ namespace Avalonia.Controls
             }
         }
 
+        IInputElement? INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
+        {
+            return GetControl(direction, from, wrap);
+        }
+
         /// <summary>
         /// Scrolls the specified item into view.
         /// </summary>
         /// <param name="index">The index of the item.</param>
-        protected internal abstract void ScrollIntoView(int index);
+        /// <returns>
+        /// The element with the specified index, or null if the element could not be brought into view.
+        /// </returns>
+        protected internal abstract Control? ScrollIntoView(int index);
 
         /// <summary>
         /// Returns the container for the item at the specified index.
@@ -53,6 +62,15 @@ namespace Avalonia.Controls
         /// </returns>
         protected internal abstract int IndexFromContainer(Control container);
 
+        /// <summary>
+        /// Gets the next control in the specified direction.
+        /// </summary>
+        /// <param name="direction">The movement direction.</param>
+        /// <param name="from">The control from which movement begins.</param>
+        /// <param name="wrap">Whether to wrap around when the first or last item is reached.</param>
+        /// <returns>The control.</returns>
+        protected abstract IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap);
+
         /// <summary>
         /// Called when the <see cref="ItemsControl"/> that owns the panel changes.
         /// </summary>

+ 67 - 3
src/Avalonia.Controls/VirtualizingStackPanel.cs

@@ -3,8 +3,8 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Diagnostics;
-using System.Xml.Linq;
 using Avalonia.Controls.Utils;
+using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Utilities;
 using Avalonia.VisualTree;
@@ -169,19 +169,79 @@ namespace Avalonia.Controls
             InvalidateMeasure();
         }
 
+        protected override IInputElement? GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
+        {
+            var count = (ItemsControl?.Items as IList)?.Count ?? 0;
+
+            if (count == 0)
+                return null;
+
+            var horiz = Orientation == Orientation.Horizontal;
+            var fromIndex = from != null ? Children.IndexOf(from) : -1;
+            var toIndex = fromIndex;
+
+            switch (direction)
+            {
+                case NavigationDirection.First:
+                    toIndex = 0;
+                    break;
+                case NavigationDirection.Last:
+                    toIndex = count - 1;
+                    break;
+                case NavigationDirection.Next:
+                    ++toIndex;
+                    break;
+                case NavigationDirection.Previous:
+                    --toIndex;
+                    break;
+                case NavigationDirection.Left:
+                    if (horiz)
+                        --toIndex;
+                    break;
+                case NavigationDirection.Right:
+                    if (horiz)
+                        ++toIndex;
+                    break;
+                case NavigationDirection.Up:
+                    if (!horiz)
+                        --toIndex;
+                    break;
+                case NavigationDirection.Down:
+                    if (!horiz)
+                        ++toIndex;
+                    break;
+                default:
+                    return null;
+            }
+
+            if (fromIndex == toIndex)
+                return from;
+
+            if (wrap)
+            {
+                if (toIndex < 0)
+                    toIndex = count - 1;
+                else if (toIndex >= count - 1)
+                    toIndex = 0;
+            }
+
+            return ScrollIntoView(toIndex);
+        }
+
         protected internal override Control? ContainerFromIndex(int index) => _realizedElements?.GetElement(index);
         protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1;
 
-        protected internal override void ScrollIntoView(int index)
+        protected internal override Control? ScrollIntoView(int index)
         {
             var items = ItemsControl?.Items as IList;
 
             if (_isInLayout || items is null || index < 0 || index >= items.Count)
-                return;
+                return null;
 
             if (GetRealizedElement(index) is Control element)
             {
                 element.BringIntoView();
+                return element;
             }
             else if (this.GetVisualRoot() is ILayoutRoot root)
             {
@@ -216,9 +276,13 @@ namespace Avalonia.Controls
                 root.LayoutManager.ExecuteLayoutPass();
                 _isWaitingForViewportUpdate = false;
 
+                var result = _anchorElement;
                 _anchorElement = null;
                 _anchorIndex = -1;
+                return result;
             }
+
+            return null;
         }
 
         internal IReadOnlyList<Control?> GetRealizedElements()