[NUI] Improves GetNextFocusableView
authorjoogab.yun <joogab.yun@samsung.com>
Tue, 14 Dec 2021 06:00:32 +0000 (15:00 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Fri, 17 Dec 2021 01:53:10 +0000 (10:53 +0900)
Currently, GetNextFocusableView of FocusableBase was Index-based of ContentContainer Child.

This is a problem.

Changes the search for the next focus view from index-based to position-based.

src/Tizen.NUI.Components/Controls/ScrollableBase.cs
src/Tizen.NUI/src/internal/Interop/Interop.FocusManager.cs
src/Tizen.NUI/src/public/Input/FocusManager.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/NestedScrollViewSamle.cs [new file with mode: 0755]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample.cs [new file with mode: 0755]
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample2.cs [new file with mode: 0755]

index 3e10b34..3c08668 100755 (executable)
@@ -1635,79 +1635,66 @@ namespace Tizen.NUI.Components
             }
         }
 
-
         /// <inheritdoc/>
         [EditorBrowsable(EditorBrowsableState.Never)]
         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
         {
-            View nextFocusedView = null;
-
-            int currentIndex = ContentContainer.Children.IndexOf(currentFocusedView);
-
-            switch (direction)
+            if (currentFocusedView == this)
             {
-                case View.FocusDirection.Left:
-                case View.FocusDirection.Up:
-                {
-                    if (currentIndex > 0)
-                    {
-                        nextFocusedView = ContentContainer.Children[--currentIndex];
-                    }
-                    break;
-                }
-                case View.FocusDirection.Right:
-                case View.FocusDirection.Down:
-                {
-                    if (currentIndex < ContentContainer.Children.Count - 1)
-                    {
-                        nextFocusedView =  ContentContainer.Children[++currentIndex];
-                    }
-                    break;
-                }
+                return null;
             }
 
+            View nextFocusedView = FocusManager.Instance.GetNearestFocusableActor(this, currentFocusedView, direction);
+
             if (nextFocusedView != null)
             {
-                // 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)
+                View view = nextFocusedView;
+                while (view.GetParent() is View && view.GetParent() != ContentContainer)
                 {
-                    if (left < visibleRectangleLeft)
-                    {
-                        targetPosition = left;
-                    }
-                    else if (right > visibleRectangleRight)
-                    {
-                        targetPosition = right - Size.Width;
-                    }
+                    view = (View)view.GetParent();
                 }
-                else
+                if (view.GetParent() == ContentContainer)
                 {
-                    if (top < visibleRectangleTop)
+                    // 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 = view.Position.X;
+                    float right = view.Position.X + view.Size.Width;
+                    float top = view.Position.Y;
+                    float bottom = view.Position.Y + view.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)
                     {
-                        targetPosition = top;
+                        if (left < visibleRectangleLeft)
+                        {
+                            targetPosition = left;
+                        }
+                        else if (right > visibleRectangleRight)
+                        {
+                            targetPosition = right - Size.Width;
+                        }
                     }
-                    else if (bottom > visibleRectangleBottom)
+                    else
                     {
-                        targetPosition = bottom - Size.Height;
+                        if (top < visibleRectangleTop)
+                        {
+                            targetPosition = top;
+                        }
+                        else if (bottom > visibleRectangleBottom)
+                        {
+                            targetPosition = bottom - Size.Height;
+                        }
                     }
+                    ScrollTo(targetPosition, true);
                 }
-                ScrollTo(targetPosition, true);
             }
-
             return nextFocusedView;
         }
     }
index ec9c4ab..29dfc0b 100755 (executable)
@@ -89,6 +89,9 @@ namespace Tizen.NUI
             [return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.U1)]
             public static extern bool IsDefaultAlgorithmEnabled(global::System.Runtime.InteropServices.HandleRef jarg1);
 
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_KeyboardFocusManager_GetNearestFocusableActor")]
+            public static extern global::System.IntPtr GetNearestFocusableActor(global::System.Runtime.InteropServices.HandleRef rootView, global::System.Runtime.InteropServices.HandleRef currentView,  int direction);
+
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_KeyboardFocusManager_SWIGUpcast")]
             public static extern global::System.IntPtr Upcast(global::System.IntPtr jarg1);
         }
index de36c9e..204d898 100755 (executable)
@@ -434,6 +434,22 @@ namespace Tizen.NUI
             return ret;
         }
 
+        /// <summary>
+        /// Get the nearest focusable view.
+        /// </summary>
+        /// <param name="rootView">The view group in which to find the next focusable view.</param>
+        /// <param name="focusedView">The current focused view.</param>
+        /// <param name="direction">The direction.</param>
+        /// <returns>The nearest focusable view, or an empty handle if none exists.</returns>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View GetNearestFocusableActor(View rootView, View focusedView, View.FocusDirection direction)
+        {
+            //to fix memory leak issue, match the handle count with native side.
+            IntPtr cPtr = Interop.FocusManager.GetNearestFocusableActor(View.getCPtr(rootView), View.getCPtr(focusedView), (int)direction);
+            View ret = this.GetInstanceSafely<View>(cPtr);
+            return ret;
+        }
+
         internal static FocusManager Get()
         {
             FocusManager ret = new FocusManager(Interop.FocusManager.Get(), true);
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/NestedScrollViewSamle.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/NestedScrollViewSamle.cs
new file mode 100755 (executable)
index 0000000..06f9a63
--- /dev/null
@@ -0,0 +1,122 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+
+namespace Tizen.NUI.Samples
+{
+    public class NestedScrollViewSamle : IExample
+    {
+        private View root;
+
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+
+            FocusManager.Instance.EnableDefaultAlgorithm(true);
+
+            root = new View();
+            root.Layout = new AbsoluteLayout();
+            root.Size = new Size(300, 800);
+
+            var scrollview = new ScrollableBase{
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                Focusable = true,
+                FocusableInTouch = true,
+            };
+
+            root.Add(scrollview);
+
+            scrollview.ContentContainer.Layout = new LinearLayout
+            {
+                LinearOrientation = LinearLayout.Orientation.Vertical
+            };
+
+            scrollview.ContentContainer.Add(new View
+            {
+                BackgroundColor = new Vector4(1.0f, 0.6f, 0.2f, 1.0f),
+                SizeHeight = 100,
+                WidthResizePolicy = ResizePolicyType.FillToParent,
+            });
+
+            var scroll2 = new ScrollableBase{
+                BackgroundColor = Color.Gray,
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                Focusable = true,
+                FocusableInTouch = true,
+            };
+
+            scroll2.WidthSpecification = LayoutParamPolicies.MatchParent;
+            scroll2.HeightSpecification = LayoutParamPolicies.MatchParent;
+            scroll2.WidthResizePolicy = ResizePolicyType.FillToParent;
+            scroll2.SizeHeight = 400;
+
+            var abscontainer = new View
+            {
+                Layout = new AbsoluteLayout(),
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                SizeHeight = 400,
+            };
+            abscontainer.Add(scroll2);
+
+            scrollview.ContentContainer.Add(abscontainer);
+
+            scroll2.ContentContainer.Layout = new LinearLayout
+            {
+                LinearOrientation = LinearLayout.Orientation.Vertical
+            };
+
+            for(int i=0; i<50; i++)
+            {
+                scroll2.ContentContainer.Add(new TextLabel
+                {
+                    Text = $"Text {i}",
+                    WidthResizePolicy = ResizePolicyType.FillToParent,
+                    Focusable = true,
+                    FocusableInTouch = true,
+                    // Position = new Position(0, i*50+200),
+                });
+            }
+
+            scrollview.ContentContainer.Add(new View{
+                BackgroundColor = Color.Yellow,
+                SizeHeight = 400,
+                WidthResizePolicy = ResizePolicyType.FillToParent,
+            });
+
+            TextLabel titleView = new TextLabel
+            {
+                Text = "Title",
+                Focusable = true,
+                FocusableInTouch = true,
+                Size = new Size(300, 100),
+            };
+
+            TextLabel footView = new TextLabel
+            {
+                Text = "Foot",
+                Focusable = true,
+                FocusableInTouch = true,
+                Size = new Size(300, 100),
+                Position = new Position(0, 500),
+            };
+
+            window.Add(root);
+            window.Add(titleView);
+            window.Add(footView);
+        }
+
+
+        public void Deactivate()
+        {
+            if (root != null)
+            {
+                NUIApplication.GetDefaultWindow().Remove(root);
+                root.Dispose();
+            }
+        }
+    }
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample.cs
new file mode 100755 (executable)
index 0000000..ac76986
--- /dev/null
@@ -0,0 +1,146 @@
+using System;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+
+
+namespace Tizen.NUI.Samples
+{
+    public class ScrollableFocusSample : IExample
+    {
+        public View root;
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+
+            root = new View();
+            root.Layout = new AbsoluteLayout();
+            root.Size = new Size(500, 800);
+
+            root.BackgroundColor = Color.White;
+            window.Add(root);
+
+            FocusManager.Instance.EnableDefaultAlgorithm(true);
+            root.Layout = new LinearLayout
+            {
+                LinearOrientation = LinearLayout.Orientation.Vertical
+            };
+            root.WidthSpecification = LayoutParamPolicies.MatchParent;
+            root.HeightSpecification = LayoutParamPolicies.MatchParent;
+
+
+            var topbtn = new Button
+            {
+                Focusable = true,
+                FocusableInTouch = true,
+                Text = "Top"
+            };
+            root.Add(topbtn);
+
+            var scrollview = new ScrollableBase
+            {
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+                BackgroundColor = Color.Gray
+            };
+            scrollview.ContentContainer.Layout = new AbsoluteLayout();
+            scrollview.ContentContainer.WidthSpecification = LayoutParamPolicies.MatchParent;
+            scrollview.ContentContainer.SizeHeight = 1800;
+            root.Add(scrollview);
+            for (int i = 0; i < 40; i++)
+            {
+                scrollview.ContentContainer.Add(CreateButton(i));
+            }
+
+            var middle = new Button
+            {
+                Focusable = true,
+                FocusableInTouch = true,
+                Text = "Middle"
+            };
+            root.Add(middle);
+
+            var myscrollview = new ScrollableBase
+            {
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+                BackgroundColor = Color.Yellow
+            };
+            myscrollview.ContentContainer.Layout = new AbsoluteLayout();
+            myscrollview.ContentContainer.WidthSpecification = LayoutParamPolicies.MatchParent;
+            myscrollview.ContentContainer.SizeHeight = 1800;
+            root.Add(myscrollview);
+            for (int i = 0; i < 40; i++)
+            {
+                myscrollview.ContentContainer.Add(CreateButton(i));
+            }
+
+            var bottom = new Button
+            {
+                Focusable = true,
+                FocusableInTouch = true,
+                Text = "bottom"
+            };
+            root.Add(bottom);
+
+        }
+
+        static View CreateButton(int index)
+        {
+            var rnd = new Random();
+
+            var btn = new Button
+            {
+                Focusable = true,
+                FocusableInTouch = true,
+                Text = $"Item {index}",
+            };
+            // btn.FocusGained += (s, e) =>
+            // {
+            //   Tizen.Log.Error("NUI", $"[[{btn.Text}]] \n");
+            // };
+
+            var item = Wrapping(btn);
+            item.SizeWidth = 200;
+            item.SizeHeight = 90;
+
+            item.Position = new Position(220 * (index % 3), 100 * (index / 3) );
+
+            if (item is Button button)
+            {
+                button.Text = $"[{button.Text}]";
+            }
+
+            return item;
+        }
+
+        static View Wrapping(View view)
+        {
+            int cnt = new Random().Next(0, 4);
+
+            for (int i = 0; i < cnt; i++)
+            {
+                var wrapper = new View();
+                view.WidthSpecification = LayoutParamPolicies.MatchParent;
+                view.HeightSpecification = LayoutParamPolicies.MatchParent;
+                wrapper.Add(view);
+                view = wrapper;
+            }
+
+            return view;
+        }
+
+        public void Deactivate()
+        {
+            if (root != null)
+            {
+                NUIApplication.GetDefaultWindow().Remove(root);
+                root.Dispose();
+            }
+        }
+    }
+
+}
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample2.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample2.cs
new file mode 100755 (executable)
index 0000000..1435b72
--- /dev/null
@@ -0,0 +1,116 @@
+using System;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+
+
+namespace Tizen.NUI.Samples
+{
+    public class ScrollableFocusSample2 : IExample
+    {
+        public View root;
+        TextLabel _label;
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+
+            root = new View();
+            root.Layout = new AbsoluteLayout();
+            root.Size = new Size(300, 800);
+
+            root.BackgroundColor = Color.White;
+            window.Add(root);
+
+            FocusManager.Instance.EnableDefaultAlgorithm(true);
+            root.Layout = new AbsoluteLayout();
+            root.WidthSpecification = LayoutParamPolicies.MatchParent;
+            root.HeightSpecification = LayoutParamPolicies.MatchParent;
+
+
+            _label = new TextLabel();
+            root.Add(_label);
+            _label.Position = new Position(0, 0);
+            _label.SizeWidth = 300;
+            _label.SizeHeight = 100;
+
+            var topPanel = new View
+            {
+                Layout = new LinearLayout
+                {
+                    LinearOrientation = LinearLayout.Orientation.Vertical
+                },
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                HeightSpecification = LayoutParamPolicies.MatchParent,
+            };
+
+            for (int i = 0; i < 10; i++)
+            {
+                topPanel.Add(CreateButton(i, false));
+            }
+            root.Add(topPanel);
+            topPanel.Position = new Position(0, 100);
+
+            var bottomPanel = new View
+            {
+                Layout = new LinearLayout
+                {
+                    LinearOrientation = LinearLayout.Orientation.Vertical,
+                },
+                BackgroundColor = Color.Yellow,
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                SizeHeight = 500,
+            };
+
+            for (int i = 0; i < 10; i++)
+            {
+                bottomPanel.Add(CreateButton(11 + i, true));
+            }
+
+            root.Add(bottomPanel);
+            bottomPanel.Position = new Position(0, 500);
+
+            topPanel.RaiseToTop();
+
+        }
+
+        View CreateButton(int index, bool second)
+        {
+            var rnd = new Random();
+
+            var btn = new Button
+            {
+                Focusable = true,
+                FocusableInTouch = true,
+                Text = $"Item {index}",
+            };
+            if (second)
+                btn.BackgroundColor = Color.Red;
+
+            btn.WidthSpecification = LayoutParamPolicies.MatchParent;
+            btn.SizeHeight = 60;
+
+            btn.FocusGained += (s, e) =>
+            {
+                btn.Text = $"[Item {index}]";
+                _label.Text = btn.Text;
+            };
+            btn.FocusLost += (s, e) =>
+            {
+                btn.Text = $"Item {index}";
+            };
+
+            return btn;
+        }
+
+        public void Deactivate()
+        {
+            if (root != null)
+            {
+                NUIApplication.GetDefaultWindow().Remove(root);
+                root.Dispose();
+            }
+        }
+    }
+
+}