From 0efcf44e0c257c80e21403a179b6634d6df398de Mon Sep 17 00:00:00 2001 From: "joogab.yun" Date: Tue, 14 Dec 2021 15:00:32 +0900 Subject: [PATCH] [NUI] Improves GetNextFocusableView 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. --- .../Controls/ScrollableBase.cs | 97 ++++++-------- .../src/internal/Interop/Interop.FocusManager.cs | 3 + src/Tizen.NUI/src/public/Input/FocusManager.cs | 16 +++ .../Samples/NestedScrollViewSamle.cs | 122 +++++++++++++++++ .../Samples/ScrollableFocusSample.cs | 146 +++++++++++++++++++++ .../Samples/ScrollableFocusSample2.cs | 116 ++++++++++++++++ 6 files changed, 445 insertions(+), 55 deletions(-) create mode 100755 test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/NestedScrollViewSamle.cs create mode 100755 test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample.cs create mode 100755 test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample2.cs diff --git a/src/Tizen.NUI.Components/Controls/ScrollableBase.cs b/src/Tizen.NUI.Components/Controls/ScrollableBase.cs index 3e10b34..3c08668 100755 --- a/src/Tizen.NUI.Components/Controls/ScrollableBase.cs +++ b/src/Tizen.NUI.Components/Controls/ScrollableBase.cs @@ -1635,79 +1635,66 @@ namespace Tizen.NUI.Components } } - /// [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; } } diff --git a/src/Tizen.NUI/src/internal/Interop/Interop.FocusManager.cs b/src/Tizen.NUI/src/internal/Interop/Interop.FocusManager.cs index ec9c4ab..29dfc0b 100755 --- a/src/Tizen.NUI/src/internal/Interop/Interop.FocusManager.cs +++ b/src/Tizen.NUI/src/internal/Interop/Interop.FocusManager.cs @@ -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); } diff --git a/src/Tizen.NUI/src/public/Input/FocusManager.cs b/src/Tizen.NUI/src/public/Input/FocusManager.cs index de36c9e..204d898 100755 --- a/src/Tizen.NUI/src/public/Input/FocusManager.cs +++ b/src/Tizen.NUI/src/public/Input/FocusManager.cs @@ -434,6 +434,22 @@ namespace Tizen.NUI return ret; } + /// + /// Get the nearest focusable view. + /// + /// The view group in which to find the next focusable view. + /// The current focused view. + /// The direction. + /// The nearest focusable view, or an empty handle if none exists. + [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(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 index 0000000..06f9a63 --- /dev/null +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/NestedScrollViewSamle.cs @@ -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 index 0000000..ac76986 --- /dev/null +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample.cs @@ -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 index 0000000..1435b72 --- /dev/null +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/ScrollableFocusSample2.cs @@ -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(); + } + } + } + +} -- 2.7.4