[NUI] First version of RecyclerView (#1626)
authorneostom432 <31119276+neostom432@users.noreply.github.com>
Thu, 28 May 2020 07:08:01 +0000 (16:08 +0900)
committerGitHub <noreply@github.com>
Thu, 28 May 2020 07:08:01 +0000 (16:08 +0900)
First version of RecyclerView.

RecyclerView is a component that can improve performance by reusing user-defined child.

User can define what the child will look like through creating function in RecycleAdapter.
RecyclerView creates its children using this function and bind data with child if child is ready to shown.

src/Tizen.NUI.Components/Controls/RecyclerView/LayoutManager.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/LinearLayoutManager.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/RecycleAdapter.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/RecycleItem.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs [new file with mode: 0644]
src/Tizen.NUI.Components/Controls/ScrollableBase.cs
src/Tizen.NUI.Wearable/src/internal/FishEyeLayoutManager.cs [new file with mode: 0644]
src/Tizen.NUI.Wearable/src/public/WearableList.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs

diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/LayoutManager.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/LayoutManager.cs
new file mode 100644 (file)
index 0000000..2ca6295
--- /dev/null
@@ -0,0 +1,127 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// [Draft] Defalt layout manager for RecyclerView.
+    /// Lay out RecycleItem and recycle RecycleItem.
+    /// </summary>
+    /// <since_tizen> 8 </since_tizen>
+    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class LayoutManager
+    {
+        protected float mPrevScrollPosition = 0.0f;
+        protected int mPrevFirstDataIndex = 0;
+        protected float mStepSize = 0.0f;
+
+        /// <summary>
+        /// Enumeration for the direction in which the content is laid out
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public enum Orientation
+        {
+            /// <summary>
+            /// Vertical
+            /// </summary>
+            /// <since_tizen> 8 </since_tizen>
+            /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            Vertical = 0,
+            /// <summary>
+            /// Horizontal
+            /// </summary>
+            /// <since_tizen> 8 </since_tizen>
+            /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+            [EditorBrowsable(EditorBrowsableState.Never)]
+            Horizontal = 1,
+        }
+
+        /// <summary>
+        /// Container which contains RecycleItems.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public View Container{get;set;}
+        /// <summary>
+        /// Size of RecycleItem.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public Size ItemSize{get;set;} = new Size();
+        /// <summary>
+        /// Get/Set the orientation in the layout.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public Orientation LayoutOrientation{get;set;} = Orientation.Vertical;
+
+        /// <summary>
+        /// How far can you reach the next item.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public float StepSize{
+            get
+            {
+                return mStepSize;
+            }
+        }
+
+        /// <summary>
+        /// This is called to find out where items are lain out according to current scroll position.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public virtual void Layout(float scrollPosition)
+        {
+           
+        }
+
+        /// <summary>
+        /// This is called to find out which items should be recycled according to current scroll position.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <returns>List of RecycleItems which should be recycled.</returns>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public virtual List<RecycleItem> Recycle(float scrollPosition)
+        {
+            return new List<RecycleItem>();
+        }
+
+        /// <summary>
+        /// Adjust scrolling position by own scrolling rules.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public virtual float CalculateCandidateScrollPosition(float scrollPosition)
+        {
+            return scrollPosition;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/LinearLayoutManager.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/LinearLayoutManager.cs
new file mode 100644 (file)
index 0000000..5e6f2da
--- /dev/null
@@ -0,0 +1,137 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// [Draft] This class implements a linear box layout.
+    /// </summary>
+    /// <since_tizen> 8 </since_tizen>
+    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class LinearListLayoutManager : LayoutManager
+    {
+        private float mLayoutOriginPosition = 0;
+
+        /// <summary>
+        /// This is called to find out where items are lain out according to current scroll position.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public override void Layout(float scrollPosition)
+        {
+            RecycleItem previousItem = null;
+
+            foreach(RecycleItem item in Container.Children)
+            {
+                float targetPosition = mLayoutOriginPosition;
+
+                if(previousItem != null)
+                {
+                    targetPosition = LayoutOrientation == Orientation.Horizontal?
+                    previousItem.PositionX + previousItem.SizeWidth:
+                    previousItem.PositionY + previousItem.SizeHeight;
+                }
+
+                item.Position = LayoutOrientation == Orientation.Horizontal?
+                new Position(targetPosition,item.PositionY):
+                new Position(item.PositionX,targetPosition);
+
+                previousItem = item;
+            }
+
+            if(mStepSize == 0)
+            {
+                mStepSize = LayoutOrientation == Orientation.Horizontal?ItemSize.Width:ItemSize.Height;
+            }
+        }
+
+        /// <summary>
+        /// This is called to find out which items should be recycled according to current scroll position.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <returns>List of RecycleItems which should be recycled.</returns>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public override List<RecycleItem> Recycle(float scrollPosition)
+        {
+            List<RecycleItem> result = new List<RecycleItem>();
+
+            float itemSize = LayoutOrientation == Orientation.Horizontal?ItemSize.Width:ItemSize.Height;
+
+            View firstVisibleItem = Container.Children[3];
+            float firstVisibleItemPosition = LayoutOrientation == Orientation.Horizontal?firstVisibleItem.Position.X:firstVisibleItem.Position.Y;
+
+            firstVisibleItemPosition += 180;
+
+            bool checkFront = (mPrevScrollPosition - scrollPosition) > 0;
+
+            if(checkFront)
+            {
+                if(firstVisibleItemPosition < Math.Abs(scrollPosition))
+                {
+                    // Too many item is in front!!! move first item to back!!!!
+                    RecycleItem target = Container.Children[0] as RecycleItem;
+                    target.DataIndex = mPrevFirstDataIndex + Container.Children.Count;
+
+                    target.SiblingOrder = Container.Children.Count - 1;
+                    mPrevFirstDataIndex += 1;
+
+                    result.Add(target);
+
+                    mLayoutOriginPosition += LayoutOrientation == Orientation.Horizontal?
+                        ItemSize.Width:ItemSize.Height;
+                }
+            }
+            else
+            {
+                if(firstVisibleItemPosition > Math.Abs(scrollPosition) + itemSize)
+                {
+                    RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
+                    target.DataIndex = mPrevFirstDataIndex - 1;
+
+                    target.SiblingOrder = 0;
+                    mPrevFirstDataIndex -= 1;
+
+                    result.Add(target);
+
+                    mLayoutOriginPosition -= LayoutOrientation == Orientation.Horizontal?
+                        Container.Children[0].SizeWidth:Container.Children[0].SizeHeight;
+                }
+            }
+
+            mPrevScrollPosition = scrollPosition;
+
+            return result;
+        }
+
+        /// <summary>
+        /// Adjust scrolling position by own scrolling rules.
+        /// </summary>
+        /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public override float CalculateCandidateScrollPosition(float scrollPosition)
+        {
+            return scrollPosition;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/RecycleAdapter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/RecycleAdapter.cs
new file mode 100644 (file)
index 0000000..49e229c
--- /dev/null
@@ -0,0 +1,97 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// [Draft] Defalt adapter for RecyclerView.
+    /// Managing RecycleItem and Data for RecyclerView.
+    /// </summary>
+    /// <since_tizen> 8 </since_tizen>
+    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class RecycleAdapter
+    {
+        private List<object> mData = new List<object>();
+
+        /// <summary>
+        /// Create recycle item for RecyclerView.
+        /// RecyclerView will make its children using this api.
+        /// </summary>
+        /// <returns>Item for RecyclerView</returns>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual RecycleItem CreateRecycleItem()
+        {
+            return new RecycleItem();
+        }
+
+        /// <summary>
+        /// Bind data with recycler item.
+        /// This function is called when RecyclerItem is used again with new data.
+        /// Can update content of recycle item with new data at DataIndex of item. 
+        /// </summary>
+        /// <param name="item">Reused RecycleItem which needs data binding.</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual void BindData(RecycleItem item)
+        {
+
+        }
+
+        /// <summary>
+        /// Notify when data of adapter is changed.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Notify()
+        {
+            OnDataChanged?.Invoke(this, new EventArgs());
+        }
+
+        /// <summary>
+        /// Triggered when user called Notify().
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<EventArgs> OnDataChanged;
+
+        /// <summary>
+        /// Triggered when user called Notify().
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public List<object> Data{
+            get
+            {
+                return mData;
+            }
+            set
+            {
+                mData = value;
+                Notify();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/RecycleItem.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/RecycleItem.cs
new file mode 100644 (file)
index 0000000..2dd8a3c
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// [Draft] This class provides a basic item for RecyclerView.
+    /// </summary>
+    /// <since_tizen> 8 </since_tizen>
+    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class RecycleItem : Control
+    {
+        /// <summary>
+        /// Data index which is binded to item by RecycleAdapter.
+        /// Can access to data of RecycleAdapter using this index.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int DataIndex { get; set; } = 0;
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs
new file mode 100644 (file)
index 0000000..69d8e6c
--- /dev/null
@@ -0,0 +1,179 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// [Draft] This class provides a View that can recycle items to improve performance.
+    /// </summary>
+    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class RecyclerView : ScrollableBase
+    {
+        protected RecycleAdapter mAdapter;
+        protected View mContainer;
+        protected LayoutManager mLayoutManager;
+        protected int mTotalItemCount = 15;
+        private List<PropertyNotification> notifications = new List<PropertyNotification>();
+
+        /// <summary>
+        /// Default constructor.
+        /// </summary>
+        /// <param name="adapter">Recycle adapter of RecyclerView.</param>
+        /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecyclerView(RecycleAdapter adapter, LayoutManager layoutManager)
+        {
+            Name = "[List]";
+            mContainer = new View()
+            {
+                WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
+                HeightSpecification = ScrollingDirection == Direction.Horizontal ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
+                Layout = new AbsoluteLayout()
+                {
+                    SetPositionByLayout = false,
+                },
+                Name = "Container",
+            };
+
+            Add(mContainer);
+            ScrollEvent += OnScroll;
+
+            mAdapter = adapter;
+            mAdapter.OnDataChanged += OnAdapterDataChanged;
+
+            mLayoutManager = layoutManager;
+            mLayoutManager.Container = mContainer;
+            mLayoutManager.ItemSize = mAdapter.CreateRecycleItem().Size;
+
+            for (int i = 0; i < mTotalItemCount; i++)
+            {
+                RecycleItem item = mAdapter.CreateRecycleItem();
+                item.DataIndex = i;
+                item.Name = "[" + i + "] recycle";
+
+                if (i < mAdapter.Data.Count)
+                {
+                    mAdapter.BindData(item);
+                }
+                mContainer.Add(item);
+
+                PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
+                noti.Notified += (object source, PropertyNotification.NotifyEventArgs args) =>
+                {
+                    mLayoutManager.Layout(ScrollingDirection == Direction.Horizontal ? mContainer.CurrentPosition.X : mContainer.CurrentPosition.Y);
+                };
+                notifications.Add(noti);
+            }
+
+            mLayoutManager.Layout(0.0f);
+
+            if (ScrollingDirection == Direction.Horizontal)
+            {
+                mContainer.Size = new Size(mLayoutManager.StepSize * mAdapter.Data.Count, Size.Height);
+            }
+            else
+            {
+                mContainer.Size = new Size(360, mLayoutManager.StepSize * mAdapter.Data.Count);
+            }
+        }
+
+        /// <summary>
+        /// Recycler adpater.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public RecycleAdapter Adapter
+        {
+            get
+            {
+                return mAdapter;
+            }
+        }
+
+        /// <summary>
+        /// Recycler layoutManager.
+        /// </summary>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public LayoutManager ListLayoutManager
+        {
+            get
+            {
+                return mLayoutManager;
+            }
+        }
+
+        private void OnScroll(object source, ScrollableBase.ScrollEventArgs args)
+        {
+            mLayoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
+            List<RecycleItem> recycledItemList = mLayoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
+            BindData(recycledItemList);
+        }
+
+        private void OnAdapterDataChanged(object source, EventArgs args)
+        {
+            List<RecycleItem> changedData = new List<RecycleItem>();
+
+            foreach (RecycleItem item in mContainer.Children)
+            {
+                changedData.Add(item);
+            }
+
+            BindData(changedData);
+        }
+
+        private void BindData(List<RecycleItem> changedData)
+        {
+            foreach (RecycleItem item in changedData)
+            {
+                if (item.DataIndex > -1 && item.DataIndex < mAdapter.Data.Count)
+                {
+                    item.Show();
+                    mAdapter.BindData(item);
+                }
+                else
+                {
+                    item.Hide();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Adjust scrolling position by own scrolling rules.
+        /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
+        /// </summary>
+        /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
+        /// <returns>Adjusted scroll destination</returns>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override float AdjustTargetPositionOfScrollAnimation(float position)
+        {
+            // Destination is depending on implementation of layout manager.
+            // Get destination from layout manager.
+            return mLayoutManager.CalculateCandidateScrollPosition(position);
+        }
+    }
+}
\ No newline at end of file
index a9b2f33..54dcbfd 100755 (executable)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2019 Samsung Electronics Co., Ltd.
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ namespace Tizen.NUI.Components
     [EditorBrowsable(EditorBrowsableState.Never)]
     public class ScrollableBase : Control
     {
-           static bool LayoutDebugScrollableBase = false; // Debug flag
+        static bool LayoutDebugScrollableBase = false; // Debug flag
         private Direction mScrollingDirection = Direction.Vertical;
         private bool mScrollEnabled = true;
         private int mPageWidth = 0;
@@ -47,26 +47,26 @@ namespace Tizen.NUI.Components
                 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
                 if (scrollableBase)
                 {
-                   scrollingDirection = scrollableBase.ScrollingDirection;
+                    scrollingDirection = scrollableBase.ScrollingDirection;
                 }
 
                 // measure child, should be a single scrolling child
-                foreach( LayoutItem childLayout in LayoutChildren )
+                foreach (LayoutItem childLayout in LayoutChildren)
                 {
                     if (childLayout != null)
                     {
                         // Get size of child
                         // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
                         // or Width for horizontal scrolling
-                        MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification( heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
+                        MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
 
                         if (scrollingDirection == Direction.Vertical)
                         {
-                            MeasureChild( childLayout, widthMeasureSpec, unrestrictedMeasureSpec );  // Height unrestricted by parent
+                            MeasureChild(childLayout, widthMeasureSpec, unrestrictedMeasureSpec);  // Height unrestricted by parent
                         }
                         else
                         {
-                            MeasureChild( childLayout, unrestrictedMeasureSpec, heightMeasureSpec );  // Width unrestricted by parent
+                            MeasureChild(childLayout, unrestrictedMeasureSpec, heightMeasureSpec);  // Width unrestricted by parent
                         }
 
                         float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
@@ -101,14 +101,14 @@ namespace Tizen.NUI.Components
                 totalHeight = heightSizeAndState.Size.AsDecimal();
 
                 // Ensure layout respects it's given minimum size
-                totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() );
-                totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() );
+                totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
+                totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
 
                 widthSizeAndState.State = childWidthState;
                 heightSizeAndState.State = childHeightState;
 
-                SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState ),
-                                       ResolveSizeAndState( new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState ) );
+                SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
+                                       ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
 
                 // Size of ScrollableBase is changed. Change Page width too.
                 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
@@ -116,9 +116,9 @@ namespace Tizen.NUI.Components
 
             protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
             {
-                foreach( LayoutItem childLayout in LayoutChildren )
+                foreach (LayoutItem childLayout in LayoutChildren)
                 {
-                    if( childLayout != null )
+                    if (childLayout != null)
                     {
                         LayoutLength childWidth = childLayout.MeasuredWidth.Size;
                         LayoutLength childHeight = childLayout.MeasuredHeight.Size;
@@ -130,7 +130,7 @@ namespace Tizen.NUI.Components
                         LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
                         LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
 
-                        childLayout.Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
+                        childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
                     }
                 }
             }
@@ -198,7 +198,7 @@ namespace Tizen.NUI.Components
             }
             set
             {
-                if(value != mScrollingDirection)
+                if (value != mScrollingDirection)
                 {
                     mScrollingDirection = value;
                     mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ? PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
@@ -223,7 +223,7 @@ namespace Tizen.NUI.Components
                 if (value != mScrollEnabled)
                 {
                     mScrollEnabled = value;
-                    if(mScrollEnabled)
+                    if (mScrollEnabled)
                     {
                         mPanGestureDetector.Detected += OnPanGestureDetected;
                         mTapGestureDetector.Detected += OnTapGestureDetected;
@@ -265,7 +265,7 @@ namespace Tizen.NUI.Components
         /// </summary>
         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public Rectangle ScrollAvailableArea { set; get; }
+        public Vector2 ScrollAvailableArea { set; get; }
 
         /// <summary>
         /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
@@ -351,7 +351,7 @@ namespace Tizen.NUI.Components
         private TapGestureDetector mTapGestureDetector;
         private View mScrollingChild;
         private View mInterruptTouchingChild;
-        private float multiplier =1.0f;
+        private float multiplier = 1.0f;
         private bool scrolling = false;
         private float ratioOfScreenWidthToCompleteScroll = 0.5f;
         private float totalDisplacementForPan = 0.0f;
@@ -359,6 +359,7 @@ namespace Tizen.NUI.Components
         // If false then can only flick pages when the current animation/scroll as ended.
         private bool flickWhenAnimating = false;
         private PropertyNotification propertyNotification;
+        protected float finalTargetPosition;
 
         /// <summary>
         /// [Draft] Constructor
@@ -391,7 +392,8 @@ namespace Tizen.NUI.Components
                 BackgroundColor = Color.Transparent,
             };
 
-            mInterruptTouchingChild.TouchEvent += (object source, View.TouchEventArgs args) => {
+            mInterruptTouchingChild.TouchEvent += (object source, View.TouchEventArgs args) =>
+            {
                 return true;
             };
 
@@ -412,9 +414,9 @@ namespace Tizen.NUI.Components
         [EditorBrowsable(EditorBrowsableState.Never)]
         public override void OnChildAdd(View view)
         {
-            if(view.Name != "InterruptTouchingChild")
+            if (view.Name != "InterruptTouchingChild")
             {
-                if(mScrollingChild.Name != "DefaultScrollingChild")
+                if (mScrollingChild.Name != "DefaultScrollingChild")
                 {
                     propertyNotification.Notified -= OnPropertyChanged;
                     mScrollingChild.RemovePropertyNotification(propertyNotification);
@@ -435,7 +437,7 @@ namespace Tizen.NUI.Components
         [EditorBrowsable(EditorBrowsableState.Never)]
         public override void OnChildRemove(View view)
         {
-            if(view.Name != "InterruptTouchingChild")
+            if (view.Name != "InterruptTouchingChild")
             {
                 propertyNotification.Notified -= OnPropertyChanged;
                 mScrollingChild.RemovePropertyNotification(propertyNotification);
@@ -444,7 +446,6 @@ namespace Tizen.NUI.Components
             }
         }
 
-
         /// <summary>
         /// Scrolls to the item at the specified index.
         /// </summary>
@@ -454,12 +455,12 @@ namespace Tizen.NUI.Components
         [EditorBrowsable(EditorBrowsableState.Never)]
         public void ScrollToIndex(int index)
         {
-            if(mScrollingChild.ChildCount-1 < index || index < 0)
+            if (mScrollingChild.ChildCount - 1 < index || index < 0)
             {
                 return;
             }
 
-            if(SnapToPage)
+            if (SnapToPage)
             {
                 CurrentPage = index;
             }
@@ -494,10 +495,41 @@ namespace Tizen.NUI.Components
             ScrollAnimationEndEvent?.Invoke(this, eventArgs);
         }
 
+        private bool readyToNotice = false;
+
+        protected float noticeAnimationEndBeforePosition = 0.0f;
+
         private void OnScroll()
         {
             ScrollEventArgs eventArgs = new ScrollEventArgs(mScrollingChild.CurrentPosition);
             ScrollEvent?.Invoke(this, eventArgs);
+
+            CheckPreReachedTargetPosition();
+        }
+
+        private void CheckPreReachedTargetPosition()
+        {
+            // Check whether we reached pre-reached target position
+            if (readyToNotice &&
+                mScrollingChild.CurrentPosition.Y <= finalTargetPosition + noticeAnimationEndBeforePosition &&
+                mScrollingChild.CurrentPosition.Y >= finalTargetPosition - noticeAnimationEndBeforePosition)
+            {
+                //Notice first
+                readyToNotice = false;
+                OnPreReachedTargetPosition(finalTargetPosition);
+            }
+        }
+
+        /// <summary>
+        /// This helps developer who wants to know before scroll is reaching target position.
+        /// </summary>
+        /// <param name="targetPosition">Index of item.</param>
+        /// <since_tizen> 6 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual void OnPreReachedTargetPosition(float targetPosition)
+        {
+
         }
 
         private void StopScroll()
@@ -530,6 +562,7 @@ namespace Tizen.NUI.Components
         private void AnimateChildTo(int duration, float axisPosition)
         {
             Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
+            finalTargetPosition = axisPosition;
 
             StopScroll(); // Will replace previous animation so will stop existing one.
 
@@ -547,59 +580,84 @@ namespace Tizen.NUI.Components
             scrollAnimation.Play();
         }
 
+        /// <summary>
+        /// Scroll to specific position with or without animation.
+        /// </summary>
+        /// <param name="position">Destination.</param>
+        /// <param name="animate">Scroll with or without animation</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void ScrollTo(float position, bool animate)
+        {
+            float currentPositionX = mScrollingChild.CurrentPosition.X != 0 ? mScrollingChild.CurrentPosition.X : mScrollingChild.Position.X;
+            float currentPositionY = mScrollingChild.CurrentPosition.Y != 0 ? mScrollingChild.CurrentPosition.Y : mScrollingChild.Position.Y;
+            float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
+            delta -= position;
+
+            ScrollBy(delta, animate);
+        }
+
+        private float BoundScrollPosition(float targetPosition)
+        {
+            if (ScrollAvailableArea != null)
+            {
+                float minScrollPosition = ScrollAvailableArea.X;
+                float maxScrollPosition = ScrollAvailableArea.Y;
+
+                targetPosition = Math.Min(-minScrollPosition, targetPosition);
+                targetPosition = Math.Max(-maxScrollPosition, targetPosition);
+            }
+            else
+            {
+                targetPosition = Math.Min(0, targetPosition);
+                targetPosition = Math.Max(-maxScrollDistance, targetPosition);
+            }
+
+            return targetPosition;
+        }
+
         private void ScrollBy(float displacement, bool animate)
         {
-            if (GetChildCount() == 0 || displacement == 0 || maxScrollDistance < 0)
+            if (GetChildCount() == 0 || maxScrollDistance < 0)
             {
                 return;
             }
 
-            float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? mScrollingChild.PositionX: mScrollingChild.PositionY;
+            float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? mScrollingChild.PositionX : mScrollingChild.PositionY;
 
             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
                                                    " displacement:" + displacement,
-                                                   " maxScrollDistance:" + maxScrollDistance );
+                                                   " maxScrollDistance:" + maxScrollDistance);
 
             childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
 
-            if(ScrollAvailableArea != null)
-            {
-                float minScrollPosition = ScrollingDirection == Direction.Horizontal? ScrollAvailableArea.X:ScrollAvailableArea.Y;
-                float maxScrollPosition = ScrollingDirection == Direction.Horizontal? 
-                                        ScrollAvailableArea.X + ScrollAvailableArea.Width:
-                                        ScrollAvailableArea.Y + ScrollAvailableArea.Height;
 
-                childTargetPosition = Math.Min( -minScrollPosition, childTargetPosition );
-                childTargetPosition = Math.Max( -maxScrollPosition, childTargetPosition );
-            }
-            else
-            {
-                childTargetPosition = Math.Min(0, childTargetPosition);
-                childTargetPosition = Math.Max(-maxScrollDistance, childTargetPosition);
-            }
-
-            Debug.WriteLineIf( LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
+            Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
 
             if (animate)
             {
                 // Calculate scroll animaton duration
                 float scrollDistance = Math.Abs(displacement);
-                int duration = (int)((320*FlickAnimationSpeed) + (scrollDistance * FlickAnimationSpeed));
+                int duration = (int)((320 * FlickAnimationSpeed) + (scrollDistance * FlickAnimationSpeed));
                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Scroll Animation Duration:" + duration + " Distance:" + scrollDistance);
 
-                AnimateChildTo(duration, childTargetPosition);
+                readyToNotice = true;
+
+                AnimateChildTo(duration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
             }
             else
             {
+                finalTargetPosition = BoundScrollPosition(childTargetPosition);
+
                 // Set position of scrolling child without an animation
                 if (ScrollingDirection == Direction.Horizontal)
                 {
-                    mScrollingChild.PositionX = childTargetPosition;
+                    mScrollingChild.PositionX = finalTargetPosition;
                 }
                 else
                 {
-                    mScrollingChild.PositionY = childTargetPosition;
+                    mScrollingChild.PositionY = finalTargetPosition;
                 }
+
             }
         }
 
@@ -648,22 +706,22 @@ namespace Tizen.NUI.Components
 
             float flickDisplacement = 0.0f;
 
-            float speed = Math.Min(4.0f,Math.Abs(axisVelocity));
+            float speed = Math.Min(4.0f, Math.Abs(axisVelocity));
 
             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollableBase Candidate Flick speed:" + speed);
 
             if (speed > FlickThreshold)
             {
                 // Flick length is the length of the ScrollableBase.
-                float flickLength = (ScrollingDirection == Direction.Horizontal) ?CurrentSize.Width:CurrentSize.Height;
+                float flickLength = (ScrollingDirection == Direction.Horizontal) ? CurrentSize.Width : CurrentSize.Height;
 
                 // Calculate multiplier by mapping speed between the multiplier minimum and maximum.
-                multiplier =( (speed - speedMinimum) / ( (speedMaximum - speedMinimum) * (multiplierMaximum - multiplierMinimum) ) )+ multiplierMinimum;
+                multiplier = ((speed - speedMinimum) / ((speedMaximum - speedMinimum) * (multiplierMaximum - multiplierMinimum))) + multiplierMinimum;
 
                 // flick displacement is the product of the flick length and multiplier
                 flickDisplacement = ((flickLength * multiplier) * speed) / axisVelocity;  // *speed and /velocity to perserve sign.
 
-                Debug.WriteLineIf(LayoutDebugScrollableBase, "Calculated FlickDisplacement[" + flickDisplacement +"] from speed[" + speed + "] multiplier:"
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "Calculated FlickDisplacement[" + flickDisplacement + "] from speed[" + speed + "] multiplier:"
                                                         + multiplier);
             }
             return flickDisplacement;
@@ -691,20 +749,20 @@ namespace Tizen.NUI.Components
                                                    " parent length:" + scrollerLength +
                                                    " scrolling child length:" + scrollingChildLength);
 
-            return Math.Max(scrollingChildLength - scrollerLength,0);
+            return Math.Max(scrollingChildLength - scrollerLength, 0);
         }
 
         private void PageSnap()
         {
             Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
-                                                                " currentPage[" + CurrentPage + "]" );
+                                                                " currentPage[" + CurrentPage + "]");
 
             //Increment current page if total displacement enough to warrant a page change.
             if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
             {
                 if (totalDisplacementForPan < 0)
                 {
-                    CurrentPage = Math.Min(Math.Max(mScrollingChild.Children.Count - 1,0), ++CurrentPage);
+                    CurrentPage = Math.Min(Math.Max(mScrollingChild.Children.Count - 1, 0), ++CurrentPage);
                 }
                 else
                 {
@@ -713,37 +771,37 @@ namespace Tizen.NUI.Components
             }
 
             // Animate to new page or reposition to current page
-            float destinationX = -(mScrollingChild.Children[CurrentPage].Position.X + mScrollingChild.Children[CurrentPage].CurrentSize.Width/2 - CurrentSize.Width/2); // set to middle of current page
+            float destinationX = -(mScrollingChild.Children[CurrentPage].Position.X + mScrollingChild.Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
             Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + mScrollingChild.PositionX);
             AnimateChildTo(ScrollDuration, destinationX);
         }
 
         private void Flick(float flickDisplacement)
         {
-          if (SnapToPage)
-          {
-              if ( ( flickWhenAnimating && scrolling == true) || ( scrolling == false) )
-              {
-                  if(flickDisplacement < 0)
-                  {
-                      CurrentPage = Math.Min(Math.Max(mScrollingChild.Children.Count - 1,0), CurrentPage + 1);
-                      Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap - to page:" + CurrentPage);
-                  }
-                  else
-                  {
-                      CurrentPage = Math.Max(0, CurrentPage - 1);
-                      Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap + to page:" + CurrentPage);
-                  }
-
-                  float destinationX = -(mScrollingChild.Children[CurrentPage].Position.X + mScrollingChild.Children[CurrentPage].CurrentSize.Width/2.0f - CurrentSize.Width/2.0f); // set to middle of current page
-                  Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to :" + destinationX);
-                  AnimateChildTo(ScrollDuration, destinationX);
-              }
-          }
-          else
-          {
-              ScrollBy(flickDisplacement, true); // Animate flickDisplacement.
-          }
+            if (SnapToPage)
+            {
+                if ((flickWhenAnimating && scrolling == true) || (scrolling == false))
+                {
+                    if (flickDisplacement < 0)
+                    {
+                        CurrentPage = Math.Min(Math.Max(mScrollingChild.Children.Count - 1, 0), CurrentPage + 1);
+                        Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap - to page:" + CurrentPage);
+                    }
+                    else
+                    {
+                        CurrentPage = Math.Max(0, CurrentPage - 1);
+                        Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap + to page:" + CurrentPage);
+                    }
+
+                    float destinationX = -(mScrollingChild.Children[CurrentPage].Position.X + mScrollingChild.Children[CurrentPage].CurrentSize.Width / 2.0f - CurrentSize.Width / 2.0f); // set to middle of current page
+                    Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to :" + destinationX);
+                    AnimateChildTo(ScrollDuration, destinationX);
+                }
+            }
+            else
+            {
+                ScrollBy(flickDisplacement, true); // Animate flickDisplacement.
+            }
         }
 
         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
@@ -793,6 +851,10 @@ namespace Tizen.NUI.Components
                     {
                         PageSnap();
                     }
+                    else
+                    {
+                        ScrollBy(0, true);
+                    }
                 }
                 totalDisplacementForPan = 0;
 
@@ -806,7 +868,7 @@ namespace Tizen.NUI.Components
             {
                 // Stop scrolling if tap detected (press then relase).
                 // Unless in Pages mode, do not want a page change to stop part way.
-                if(scrolling && !SnapToPage)
+                if (scrolling && !SnapToPage)
                 {
                     StopScroll();
                 }
@@ -816,8 +878,22 @@ namespace Tizen.NUI.Components
         private void ScrollAnimationFinished(object sender, EventArgs e)
         {
             scrolling = false;
+            CheckPreReachedTargetPosition();
             OnScrollAnimationEnd();
         }
+
+        /// <summary>
+        /// Adjust scrolling position by own scrolling rules.
+        /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
+        {
+            return position;
+        }
+
     }
 
 } // namespace
diff --git a/src/Tizen.NUI.Wearable/src/internal/FishEyeLayoutManager.cs b/src/Tizen.NUI.Wearable/src/internal/FishEyeLayoutManager.cs
new file mode 100644 (file)
index 0000000..ab7e1a4
--- /dev/null
@@ -0,0 +1,254 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.Components;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Wearable
+{
+    /// <summary>
+    /// [Draft] This class implements a fish eye layout
+    /// </summary>
+    internal class FishEyeLayoutManager : LayoutManager
+    {
+        public int CurrentFocusedIndex { get; set; } = 0;
+        public int FocusedIndex { get; set; } = 0;
+        public FishEyeLayoutManager()
+        {
+        }
+
+        private float FindCandidatePosition(float startPosition, float scrollPosition, bool isBack)
+        {
+            // There are two ellipses which return how much scale needed.
+            // We can find candidate position by intersection of ellipse and line.
+            // Item size is always determined before calculation, so can get angle of it by height/width when orientation is vertical.
+            // Y intercept is on previous item.
+
+            double center = Math.Abs(scrollPosition);
+            double result = isBack ? center + 180.0f : center - 180.0f;
+
+            // double newFactor = Math.Pow(180,2) / CalculateXFactor(0);
+            double angle = ItemSize.Height / ItemSize.Width;
+            double intercept = (startPosition - center);
+            double constant = Math.Sqrt(Math.Pow((180 * 333), 2) / (Math.Pow(333, 2) - Math.Pow(153, 2)));
+
+            double semiMajorAxis = 333.0;
+            double centerOfEllipse = -153.0;
+            double centerOfEllipse2 = 153.0;
+
+            // Find intersection using qurdratic formula.
+            // We have two different equations because there are two different ellipse.
+            double a = Math.Pow(semiMajorAxis, 2) + Math.Pow(angle * constant, 2);
+            double b = -(intercept * Math.Pow(semiMajorAxis, 2) + centerOfEllipse * Math.Pow(angle * constant, 2));
+            double c = Math.Pow(intercept * semiMajorAxis, 2) + Math.Pow(angle * constant * centerOfEllipse, 2) - Math.Pow(angle * constant * semiMajorAxis, 2);
+            double b2 = -(intercept * Math.Pow(semiMajorAxis, 2) + centerOfEllipse2 * Math.Pow(angle * constant, 2));
+            double c2 = Math.Pow(intercept * semiMajorAxis, 2) + Math.Pow(angle * constant * centerOfEllipse2, 2) - Math.Pow(angle * constant * semiMajorAxis, 2);
+
+            double result1 = (-b + Math.Sqrt((Math.Pow(b, 2) - a * c))) / a;
+            double result2 = (-b - Math.Sqrt((Math.Pow(b, 2) - a * c))) / a;
+            double result3 = (-b2 + Math.Sqrt((Math.Pow(b2, 2) - a * c2))) / a;
+            double result4 = (-b2 - Math.Sqrt((Math.Pow(b2, 2) - a * c2))) / a;
+
+            result = isBack ? result1 : result4;
+            return (float)(center + result);
+        }
+
+        public override void Layout(float scrollPosition)
+        {
+            RecycleItem centerItem = Container.Children[FocusedIndex] as RecycleItem;
+            float centerItemPosition = LayoutOrientation == Orientation.Horizontal ? centerItem.Position.X : centerItem.Position.Y;
+
+            Vector2 stepRange = new Vector2(-scrollPosition - mStepSize + 1.0f, -scrollPosition + mStepSize - 1.0f);
+            Vector2 visibleRange = new Vector2(Math.Abs(scrollPosition) - 180, Math.Abs(scrollPosition) + 180);
+
+            if (mStepSize != 0 && centerItemPosition <= stepRange.X)
+            {
+                FocusedIndex = Math.Min(Container.Children.Count - 1, FocusedIndex + 1);
+                centerItem = Container.Children[FocusedIndex] as RecycleItem;
+                centerItem.Position = new Position(0.0f, Math.Abs(mStepSize * (centerItem.DataIndex)));
+                centerItem.Scale = new Vector3(1.0f, 1.0f, 1.0f);
+            }
+            else if (mStepSize != 0 && centerItemPosition >= stepRange.Y)
+            {
+                FocusedIndex = Math.Max(0, FocusedIndex - 1);
+                centerItem = Container.Children[FocusedIndex] as RecycleItem;
+                centerItem.Position = new Position(0.0f, Math.Abs(mStepSize * (centerItem.DataIndex)));
+                centerItem.Scale = new Vector3(1.0f, 1.0f, 1.0f);
+            }
+            else
+            {
+                float centerItemScale = CalculateScaleFactor(centerItemPosition, scrollPosition);
+                centerItem.Scale = new Vector3(centerItemScale, centerItemScale, 1.0f);
+            }
+
+            RecycleItem prevItem = centerItem;
+
+            // Front of center item
+            for (int i = FocusedIndex - 1; i > -1; i--)
+            {
+                RecycleItem target = Container.Children[i] as RecycleItem;
+
+                float prevItemPosition = LayoutOrientation == Orientation.Horizontal ? prevItem.Position.X : prevItem.Position.Y;
+                float prevItemSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.Size.Width : prevItem.Size.Height);
+                float prevItemCurrentSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.GetCurrentSizeFloat().Width : prevItem.GetCurrentSizeFloat().Height);
+                prevItemSize = prevItemCurrentSize == 0 ? prevItemSize : prevItemCurrentSize;
+                prevItemSize = prevItemSize * prevItem.Scale.X;
+
+                float startPosition = prevItemPosition - prevItemSize / 2.0f;
+
+                if (startPosition > visibleRange.X)
+                {
+                    float candidatePosition = visibleRange.X;
+                    candidatePosition = FindCandidatePosition(startPosition, scrollPosition, false);
+                    target.Position = LayoutOrientation == Orientation.Horizontal ?
+                                new Position(candidatePosition, target.Position.Y) :
+                                new Position(target.Position.X, candidatePosition);
+
+                    float scaleFactor = CalculateScaleFactor(candidatePosition, scrollPosition);
+                    target.Scale = new Vector3(scaleFactor, scaleFactor, 1.0f);
+
+                    prevItem = target;
+                }
+                else
+                {
+                    target.Scale = new Vector3(0.0f, 0.0f, 1.0f);
+                }
+            }
+
+            prevItem = centerItem;
+
+            // Back of center item
+            for (int i = FocusedIndex + 1; i < Container.Children.Count; i++)
+            {
+                RecycleItem target = Container.Children[i] as RecycleItem;
+
+                float prevItemPosition = LayoutOrientation == Orientation.Horizontal ? prevItem.Position.X : prevItem.Position.Y;
+                float prevItemSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.Size.Width : prevItem.Size.Height);
+                float prevItemCurrentSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.GetCurrentSizeFloat().Width : prevItem.GetCurrentSizeFloat().Height);
+                prevItemSize = prevItemCurrentSize == 0 ? prevItemSize : prevItemCurrentSize;
+                prevItemSize = prevItemSize * prevItem.Scale.X;
+
+                float startPosition = prevItemPosition + prevItemSize / 2.0f;
+
+                if (startPosition < visibleRange.Y)
+                {
+                    float candidatePosition = visibleRange.Y;
+                    candidatePosition = FindCandidatePosition(startPosition, scrollPosition, true);
+                    target.Position = LayoutOrientation == Orientation.Horizontal ?
+                                new Position(candidatePosition, target.Position.Y) :
+                                new Position(target.Position.X, candidatePosition);
+
+                    float scaleFactor = CalculateScaleFactor(candidatePosition, scrollPosition);
+                    target.Scale = new Vector3(scaleFactor, scaleFactor, 1.0f);
+
+                    prevItem = target;
+                }
+                else
+                {
+                    target.Scale = new Vector3(0.0f, 0.0f, 1.0f);
+                }
+            }
+
+            if (mStepSize == 0)
+            {
+                if (LayoutOrientation == Orientation.Horizontal)
+                {
+                    mStepSize = Container.Children[0].Size.Width / 2.0f + Container.Children[1].Size.Width * Container.Children[1].Scale.X / 2.0f;
+                }
+                else
+                {
+                    mStepSize = Container.Children[0].Size.Height / 2.0f + Container.Children[1].Size.Height * Container.Children[1].Scale.X / 2.0f;
+                }
+
+            }
+        }
+
+        public override List<RecycleItem> Recycle(float scrollPosition)
+        {
+            List<RecycleItem> result = new List<RecycleItem>();
+
+            bool isBack = scrollPosition - mPrevScrollPosition < 0;
+
+            int previousFocusIndex = FocusedIndex;
+
+            if (!isBack && FocusedIndex < 6)
+            {
+                RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
+
+                int previousSiblingOrder = target.SiblingOrder;
+                target.SiblingOrder = 0;
+                target.DataIndex = target.DataIndex - Container.Children.Count;
+                target.Position = new Position(0, Math.Abs(scrollPosition) - 360);
+                target.Scale = new Vector3(0, 0, 0);
+
+                result.Add(target);
+
+                FocusedIndex++;
+            }
+            else if (isBack && FocusedIndex > 8)
+            {
+                RecycleItem target = Container.Children[0] as RecycleItem;
+
+                int previousSiblingOrder = target.SiblingOrder;
+                target.SiblingOrder = Container.Children.Count - 1;
+                target.DataIndex = target.DataIndex + Container.Children.Count;
+                target.Position = new Position(0, Math.Abs(scrollPosition) + 360);
+                target.Scale = new Vector3(0, 0, 0);
+
+                result.Add(target);
+
+                FocusedIndex--;
+            }
+
+            mPrevScrollPosition = scrollPosition;
+
+            return result;
+        }
+
+        private double CalculateXFactor(double y)
+        {
+            double factor1 = Math.Pow(180, 2);
+            double factor2 = Math.Pow(333, 2);
+            double factor3 = Math.Pow((y + 153), 2);
+
+            return Math.Sqrt(factor1 - (factor1 / factor2 * factor3));
+        }
+
+        private float CalculateScaleFactor(float position, float scrollPosition)
+        {
+            float origin = Math.Abs(scrollPosition);
+            float diff = position - origin;
+
+            diff = Math.Max(diff, -180);
+            diff = Math.Min(diff, 180);
+            diff = Math.Abs(diff);
+
+            float result = (float)(CalculateXFactor(diff) / CalculateXFactor(0));
+            return result;
+        }
+
+        public override float CalculateCandidateScrollPosition(float scrollPosition)
+        {
+            int value = (int)(Math.Abs(scrollPosition) / mStepSize);
+            float remain = Math.Abs(scrollPosition) % mStepSize;
+
+            int newValue = remain > mStepSize / 2.0f ? value + 1 : value;
+
+            CurrentFocusedIndex = newValue;
+            return -newValue * mStepSize;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.NUI.Wearable/src/public/WearableList.cs b/src/Tizen.NUI.Wearable/src/public/WearableList.cs
new file mode 100644 (file)
index 0000000..98edd26
--- /dev/null
@@ -0,0 +1,121 @@
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Wearable
+{
+    /// <summary>
+    /// [Draft] This class provides a list view styled by wearable ux.
+    /// List will lay out all items with Fish-Eye layout manager.
+    /// </summary>
+    /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class WearableList : RecyclerView
+    {
+        private RecycleItem FocusedItem = null;
+
+        /// <summary>
+        /// Default constructor.
+        /// </summary>
+        /// <param name="adapter">Recycle adapter of List.</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public WearableList(RecycleAdapter adapter) : base(adapter, new FishEyeLayoutManager())
+        {
+            ScrollingDirection = ScrollableBase.Direction.Vertical;
+
+            ScrollDragStartEvent += OnScrollDragStart;
+            ScrollAnimationEndEvent += OnAnimationEnd;
+
+            foreach (View child in mContainer.Children)
+            {
+                child.PositionUsesPivotPoint = true;
+                child.ParentOrigin = Tizen.NUI.ParentOrigin.TopCenter;
+            }
+
+            mContainer.PositionUsesPivotPoint = true;
+            mContainer.ParentOrigin = Tizen.NUI.ParentOrigin.Center;
+            mContainer.PivotPoint = ScrollingDirection == Direction.Vertical ? Tizen.NUI.PivotPoint.TopCenter : Tizen.NUI.PivotPoint.CenterLeft;
+            ScrollAvailableArea = new Vector2( 0,ListLayoutManager.StepSize * (mAdapter.Data.Count - 1) );
+
+            noticeAnimationEndBeforePosition = 50;
+
+            SetFocus(0, false);
+        }
+
+        /// <summary>
+        /// Set focus to item which has specific data index.
+        /// </summary>
+        /// <param name="dataIndex">Data index of item.</param>
+        /// <param name="animated">If set true, scroll to item using animation.</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        public void SetFocus(int dataIndex, bool animated)
+        {
+            foreach (RecycleItem item in mContainer.Children)
+            {
+                if (item.DataIndex == dataIndex)
+                {
+                    RecycleItem prevFocusedItem = FocusedItem;
+                    prevFocusedItem?.OnFocusLost();
+                    FocusedItem = item;
+                    FocusedItem.OnFocusGained();
+
+                    ScrollTo(item.DataIndex * mLayoutManager.StepSize, animated);
+                }
+            }
+        }
+
+        private void OnAnimationEnd(object source, ScrollableBase.ScrollEventArgs args)
+        {
+        }
+
+        /// <summary>
+        /// This helps developer who wants to know before scroll is reaching target position.
+        /// </summary>
+        /// <param name="targetPosition">Index of item.</param>
+        /// <since_tizen> 8 </since_tizen>
+        /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+        protected override void OnPreReachedTargetPosition(float targetPosition)
+        {
+            int targetDataIndex = (int)Math.Round(Math.Abs(targetPosition) / mLayoutManager.StepSize);
+
+            for (int i = 0; i < mContainer.Children.Count; i++)
+            {
+                RecycleItem item = mContainer.Children[i] as RecycleItem;
+
+                if (targetDataIndex == item.DataIndex)
+                {
+                    FocusedItem = item;
+                    item.OnFocusGained();
+                    break;
+                }
+            }
+        }
+
+        private float dragStartPosition = 0.0f;
+
+        private void OnScrollDragStart(object source, ScrollableBase.ScrollEventArgs args)
+        {
+            RecycleItem prevFocusedItem = FocusedItem;
+            prevFocusedItem?.OnFocusLost();
+        }
+    }
+}
\ No newline at end of file
index 28cd0b5..9617922 100755 (executable)
@@ -554,6 +554,14 @@ namespace Tizen.NUI.BaseComponents
             return size;
         }
 
+        internal Size2D GetCurrentSizeFloat()
+        {
+            Size ret = new Size(Interop.Actor.Actor_GetCurrentSize(swigCPtr), true);
+            if (NDalicPINVOKE.SWIGPendingException.Pending)
+                throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            return ret;
+        }
+
         internal Vector3 GetNaturalSize()
         {
             Vector3 ret = new Vector3(Interop.Actor.Actor_GetNaturalSize(swigCPtr), true);