[NUI] Support default focus logic for RecyclerView (#1869)
authorneostom432 <31119276+neostom432@users.noreply.github.com>
Thu, 30 Jul 2020 11:02:06 +0000 (20:02 +0900)
committerGitHub <noreply@github.com>
Thu, 30 Jul 2020 11:02:06 +0000 (20:02 +0900)
Support default focus chain.

Focus logic can vary depending on the type of LayoutManager.

src/Tizen.NUI.Components/Controls/RecyclerView/GridRecycleLayoutManager.cs
src/Tizen.NUI.Components/Controls/RecyclerView/LinearRecycleLayoutManager.cs
src/Tizen.NUI.Components/Controls/RecyclerView/RecycleLayoutManager.cs
src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs

index 327f623..abfa02d 100644 (file)
@@ -250,5 +250,47 @@ namespace Tizen.NUI.Components
         {
             return scrollPosition;
         }
+
+        public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            View nextFocusedView = null;
+            int targetSibling = -1;
+            bool isHorizontal = LayoutOrientation == Orientation.Horizontal;
+
+            switch(direction)
+            {
+                case View.FocusDirection.Left :
+                {
+                    targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - 1 : currentFocusedView.SiblingOrder - Rows;
+                    break;
+                }
+                case View.FocusDirection.Right :
+                {
+                    targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + 1 : currentFocusedView.SiblingOrder + Rows;
+                    break;
+                }
+                case View.FocusDirection.Up :
+                {
+                    targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - Columns : currentFocusedView.SiblingOrder - 1;
+                    break;
+                }
+                case View.FocusDirection.Down :
+                {
+                    targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + Columns : currentFocusedView.SiblingOrder + 1;
+                    break;
+                }
+            }
+
+            if(targetSibling > -1 && targetSibling < Container.Children.Count)
+            {
+                RecycleItem candidate = Container.Children[targetSibling] as RecycleItem;
+                if(candidate.DataIndex >= 0 && candidate.DataIndex < DataCount)
+                {
+                    nextFocusedView = candidate;
+                }
+            }
+
+            return nextFocusedView;
+        }
     }
 }
\ No newline at end of file
index c8630a2..1cb35b9 100644 (file)
@@ -176,5 +176,47 @@ namespace Tizen.NUI.Components
         {
             return scrollPosition;
         }
+
+        public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            View nextFocusedView = null;
+            int targetSibling = -1;
+            bool isHorizontal = LayoutOrientation == Orientation.Horizontal;
+
+            switch(direction)
+            {
+                case View.FocusDirection.Left :
+                {
+                    targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
+                    break;
+                }
+                case View.FocusDirection.Right :
+                {
+                    targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
+                    break;
+                }
+                case View.FocusDirection.Up :
+                {
+                    targetSibling = isHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
+                    break;
+                }
+                case View.FocusDirection.Down :
+                {
+                    targetSibling = isHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
+                    break;
+                }
+            }
+
+            if(targetSibling > -1 && targetSibling < Container.Children.Count)
+            {
+                RecycleItem candidate = Container.Children[targetSibling] as RecycleItem;
+                if(candidate.DataIndex >= 0 && candidate.DataIndex < DataCount)
+                {
+                    nextFocusedView = candidate;
+                }
+            }
+
+            return nextFocusedView;
+        }
     }
 }
index 95e3407..6a946e2 100644 (file)
@@ -146,5 +146,20 @@ namespace Tizen.NUI.Components
         {
             return scrollPosition;
         }
+
+        /// <summary>
+        /// Gets the next keyboard focusable view in this control towards the given direction.<br />
+        /// A control needs to override this function in order to support two dimensional keyboard navigation.<br />
+        /// </summary>
+        /// <param name="currentFocusedView">The current focused view.</param>
+        /// <param name="direction">The direction to move the focus towards.</param>
+        /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
+        /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            return null;
+        }
+
     }
 }
index 87d6bbb..cf4a989 100644 (file)
@@ -52,6 +52,8 @@ namespace Tizen.NUI.Components
 
         private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
         {
+            FocusGroup = true;
+            SetKeyboardNavigationSupport(true);
             Scrolling += OnScrolling;
 
             this.adapter = adapter;
@@ -69,8 +71,8 @@ namespace Tizen.NUI.Components
         {
             layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
         }
-        
-        public int TotalItemCount 
+
+        public int TotalItemCount
         {
             get
             {
@@ -85,7 +87,7 @@ namespace Tizen.NUI.Components
 
         private void InitializeItems()
         {
-            for(int i = Children.Count -1 ; i > -1 ; i--)
+            for (int i = Children.Count - 1; i > -1; i--)
             {
                 Children[i].Unparent();
                 notifications[i].Notified -= OnItemSizeChanged;
@@ -218,7 +220,7 @@ namespace Tizen.NUI.Components
                 if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
                 {
                     item.Show();
-                    item.Name = "["+item.DataIndex+"]";
+                    item.Name = "[" + item.DataIndex + "]";
                     adapter.BindData(item);
                 }
                 else
@@ -243,5 +245,121 @@ namespace Tizen.NUI.Components
             // Get destination from layout manager.
             return layoutManager.CalculateCandidateScrollPosition(position);
         }
+
+        private View focusedView;
+        private int prevFocusedDataIndex = 0;
+
+        public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+        {
+            View nextFocusedView = null;
+
+            if (!focusedView)
+            {
+                // If focusedView is null, find child which has previous data index
+                if (Children.Count > 0 && Adapter.Data.Count > 0)
+                {
+                    for (int i = 0; i < Children.Count; i++)
+                    {
+                        RecycleItem item = Children[i] as RecycleItem;
+                        if (item.DataIndex == prevFocusedDataIndex)
+                        {
+                            nextFocusedView = item;
+                            break;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                // If this is not first focus, request next focus to LayoutManager
+                nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
+            }
+
+            if (nextFocusedView)
+            {
+                // Check next focused view is inside of visible area.
+                // If it is not, move scroll position to make it visible.
+                Position scrollPosition = ContentContainer.CurrentPosition;
+                float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
+
+                float left = nextFocusedView.Position.X;
+                float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
+                float top = nextFocusedView.Position.Y;
+                float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
+
+                float visibleRectangleLeft = -scrollPosition.X;
+                float visibleRectangleRight = -scrollPosition.X + Size.Width;
+                float visibleRectangleTop = -scrollPosition.Y;
+                float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
+
+                if (ScrollingDirection == Direction.Horizontal)
+                {
+                    if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
+                    {
+                        targetPosition = left;
+                    }
+                    else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
+                    {
+                        targetPosition = right - Size.Width;
+                    }
+                }
+                else
+                {
+                    if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
+                    {
+                        targetPosition = top;
+                    }
+                    else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
+                    {
+                        targetPosition = bottom - Size.Height;
+                    }
+                }
+
+                focusedView = nextFocusedView;
+                prevFocusedDataIndex = (nextFocusedView as RecycleItem).DataIndex;
+
+                ScrollTo(targetPosition, true);
+            }
+            else
+            {
+                // If nextView is null, it means that we should move focus to outside of Control.
+                // Return FocusableView depending on direction.
+                switch (direction)
+                {
+                    case View.FocusDirection.Left:
+                    {
+                        nextFocusedView = LeftFocusableView;
+                        break;
+                    }
+                    case View.FocusDirection.Right:
+                    {
+                        nextFocusedView = RightFocusableView;
+                        break;
+                    }
+                    case View.FocusDirection.Up:
+                    {
+                        nextFocusedView = UpFocusableView;
+                        break;
+                    }
+                    case View.FocusDirection.Down:
+                    {
+                        nextFocusedView = DownFocusableView;
+                        break;
+                    }
+                }
+
+                if(nextFocusedView)
+                {
+                    focusedView = null;
+                }
+                else
+                {
+                    //If FocusableView doesn't exist, not move focus.
+                    nextFocusedView = focusedView;
+                }
+            }
+
+            return nextFocusedView;
+        }
     }
 }