[NUI.Components] Layout scroller/Scrollable Horizontal support and Pages (#1184)
authoragnelovaz <agnelo.vaz@samsung.com>
Thu, 19 Dec 2019 12:39:59 +0000 (12:39 +0000)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Thu, 19 Dec 2019 12:39:59 +0000 (21:39 +0900)
* [NUI.Components] LayoutScroller flick added

Flick support
Configurable Flick animation
Ensuring uniform duration of flick animation by removing flick multipler

Change-Id: Ib654a38cb2070c29520069a75b5782d458ab4da6

* [NUI.Components] Horizontal Scroll supported

* [NUI.Components] LayoutScroller with horizontal Pages support

Other features added:

FlickDurationModifier renamed to FlickAnimationSpeed
Defaults changed so faster.

Uses View API for Add/Remove child instead of the new dedicated API.
ScrollStart and ScrollEnd signals added
Vertical and Horizontal axis enums to set Horizontal or Vertical scrolling

Fixes:
Tap gesture doesn't stop pages scrolling mid way.
Flick in Pages should not stack pages changes. Must wait until scroll ended to flick again.

Change-Id: I8dcabff1e956b747987865ceb4272ccb2ce55061

* [NUI.Components] LayoutScroller renamed to Scrollable

Change-Id: I42805c3b240997d1921db4b3c1008b120d264cbb

* [NUI.Components] Scrollable derives from NUI.Components.Control

Change-Id: Iec6f083fe4601ceff40f74bbe1ae8de4d8da6917

* [NUI.Components] Scrollable Pages MatchParent fix

Fixed bug in which the Scrollable would be the size of it's content
even when set to MatchParent

Change-Id: I2be91b1a6f975b4e8aa2a132dc63009a21ef4b62

* [NUI] Rename component

Rename component Scrollable -> Scroll

* [NUI.Components] rename Scroll to ScrollableBase

* [NUI.Components] change PanGesture direction when scrolling direction is changed

When scrolling direction is changed, pan gesture direction should be changed.
This change makes ScrollableBase support nested list cases.

src/Tizen.NUI.Components/Controls/DropDown.cs
src/Tizen.NUI.Components/Controls/LayoutScroller.cs [deleted file]
src/Tizen.NUI.Components/Controls/ScrollableBase.cs [new file with mode: 0755]
src/Tizen.NUI/src/public/Layouting/LayoutGroup.cs

index a34c93c..d4e0b50 100755 (executable)
@@ -120,7 +120,8 @@ namespace Tizen.NUI.Components
         private TextLabel buttonText = null;
         private ImageView listBackgroundImage = null;
         // Component that scrolls the child added to it.
-        private LayoutScroller layoutScroller = null;
+        private Scrollable scrollable = null;
+
         // The LinearLayout container to house the items in the drop down list.
         private View dropDownMenuFullList = null;
         private DropDownListBridge adapter = new DropDownListBridge();
@@ -350,7 +351,7 @@ namespace Tizen.NUI.Components
         [EditorBrowsable(EditorBrowsableState.Never)]
         public void AttachScrollBar(ScrollBar scrollBar)
         {
-            if (layoutScroller == null)
+            if (scrollable == null)
             {
                 return;
             }
@@ -365,7 +366,7 @@ namespace Tizen.NUI.Components
         [EditorBrowsable(EditorBrowsableState.Never)]
         public void DetachScrollBar()
         {
-            if (layoutScroller == null)
+            if (scrollable == null)
             {
                 return;
             }
@@ -386,7 +387,7 @@ namespace Tizen.NUI.Components
                 CreateButton();
 
                 CreateListBackgroundImage();
-                if (null == layoutScroller) // layoutScroller used to test of ListContainer Setup invoked already
+                if (null == scrollable) // scrollable used to test of ListContainer Setup invoked already
                 {
                     SetUpListContainer();
                 }
@@ -405,11 +406,11 @@ namespace Tizen.NUI.Components
         [EditorBrowsable(EditorBrowsableState.Never)]
         protected void UpdateDropDown()
         {
-            if (null == layoutScroller || null == listBackgroundImage || null == dropDownMenuFullList) return;
+            if (null == scrollable || null == listBackgroundImage || null == dropDownMenuFullList) return;
             if (null == Style.ListBackgroundImage.Size) return;
             // Resize and position scrolling list within the drop down list container.  Can be used to position list in relation to the background image.
-            layoutScroller.Size = Style.ListBackgroundImage.Size - new Size((listPadding.Start + listPadding.End), (listPadding.Top + listPadding.Bottom), 0);
-            layoutScroller.Position2D = new Position2D(listPadding.Start, listPadding.Top);
+            scrollable.Size = Style.ListBackgroundImage.Size - new Size((listPadding.Start + listPadding.End), (listPadding.Top + listPadding.Bottom), 0);
+            scrollable.Position2D = new Position2D(listPadding.Start, listPadding.Top);
 
             int listBackgroundImageX = 0;
             int listBackgroundImageY = 0;
@@ -473,7 +474,7 @@ namespace Tizen.NUI.Components
                 Utility.Dispose(headerText);
                 Utility.Dispose(buttonText);
                 Utility.Dispose(button);
-                Utility.Dispose(layoutScroller);
+                Utility.Dispose(scrollable);
                 Utility.Dispose(dropDownMenuFullList);
                 Utility.Dispose(listBackgroundImage);
             }
@@ -593,13 +594,13 @@ namespace Tizen.NUI.Components
                 Focusable = true,
             };
 
-            layoutScroller = new LayoutScroller()
+            scrollable = new Scrollable()
             {
-                Name = "LayoutScroller",
+                Name = "Scrollable",
             };
-            layoutScroller.AddLayoutToScroll(dropDownMenuFullList);
+            scrollable.Add(dropDownMenuFullList);
 
-            listBackgroundImage.Add(layoutScroller);
+            listBackgroundImage.Add(scrollable);
             listBackgroundImage.Hide();
         }
 
diff --git a/src/Tizen.NUI.Components/Controls/LayoutScroller.cs b/src/Tizen.NUI.Components/Controls/LayoutScroller.cs
deleted file mode 100755 (executable)
index 03b15c9..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-/* Copyright (c) 2019 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.ComponentModel;
-using System.Diagnostics;
-namespace Tizen.NUI.Components
-{
-    /// <summary>
-    /// [Draft] This class provides a View that can scroll a single View with a layout.
-    /// </summary>
-    internal class LayoutScroller : CustomView
-    {
-       static bool LayoutDebugScroller = true; // Debug flag
-
-        private class ScrollerCustomLayout : LayoutGroup
-        {
-            protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
-            {
-                float totalHeight = 0.0f;
-                float totalWidth = 0.0f;
-
-                MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
-                MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
-
-                // measure children
-                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)
-                        MeasureSpecification heightMeasureSpecUnrestricted = new MeasureSpecification( heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
-                        MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpecUnrestricted );
-                        float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
-                        float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
-
-                        // Determine the width and height needed by the children using their given position and size.
-                        // Children could overlap so find the left most and right most child.
-                        Position2D childPosition = childLayout.Owner.Position2D;
-                        float childLeft = childPosition.X;
-                        float childTop = childPosition.Y;
-
-                        // Store current width and height needed to contain all children.
-                        Extents padding = Padding;
-                        Extents childMargin = childLayout.Margin;
-                        totalWidth = childWidth + padding.Start + padding.End + childMargin.Start + childMargin.End;
-                        totalHeight = childHeight + padding.Top + padding.Bottom + childMargin.Top + childMargin.Bottom;
-
-                        if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
-                        {
-                            childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
-                        }
-                        if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
-                        {
-                            childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
-                        }
-                    }
-                }
-
-
-                MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
-                MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
-                totalWidth = widthSizeAndState.Size.AsDecimal();
-                totalHeight = heightSizeAndState.Size.AsDecimal();
-
-                // Ensure layout respects it's given minimum size
-                totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() );
-                totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() );
-
-                widthSizeAndState.State = childWidthState;
-                heightSizeAndState.State = childHeightState;
-
-                SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ),
-                                       ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) );
-            }
-
-            protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
-            {
-                foreach( LayoutItem childLayout in LayoutChildren )
-                {
-                    if( childLayout != null )
-                    {
-                        LayoutLength childWidth = childLayout.MeasuredWidth.Size;
-                        LayoutLength childHeight = childLayout.MeasuredHeight.Size;
-
-                        Position2D childPosition = childLayout.Owner.Position2D;
-                        Extents padding = Padding;
-                        Extents childMargin = childLayout.Margin;
-
-                        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 );
-                    }
-                }
-            }
-        }
-
-        private Animation scrollAnimation;
-        private float maxScrollDistance;
-        private float childTargetPosition = 0.0f;
-        private PanGestureDetector mPanGestureDetector;
-        private TapGestureDetector mTapGestureDetector;
-        private View mScrollingChild;
-
-        private bool Scrolling = false;
-
-        /// <summary>
-        /// [Draft] Constructor
-        /// </summary>
-        /// <since_tizen> 6 </since_tizen>
-        public LayoutScroller() : base(typeof(VisualView).FullName, CustomViewBehaviour.ViewBehaviourDefault | CustomViewBehaviour.RequiresTouchEventsSupport)
-        {
-            mPanGestureDetector = new PanGestureDetector();
-            mPanGestureDetector.Attach(this);
-            mPanGestureDetector.Detected += OnPanGestureDetected;
-
-            mTapGestureDetector = new TapGestureDetector();
-            mTapGestureDetector.Attach(this);
-            mTapGestureDetector.Detected += OnTapGestureDetected;
-
-            ClippingMode = ClippingModeType.ClipToBoundingBox;
-
-            mScrollingChild = new View();
-
-            Layout = new ScrollerCustomLayout();
-        }
-
-        public void AddLayoutToScroll(View child)
-        {
-            mScrollingChild = child;
-            Add(mScrollingChild);
-        }
-
-
-        /// <summary>
-        /// Scroll vertically by displacement pixels in screen coordinates.
-        /// </summary>
-        /// <param name="displacement">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
-        /// <since_tizen> 6 </since_tizen>
-        /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
-        public float ScrollVerticallyBy(float displacement)
-        {
-            Debug.WriteLineIf( LayoutDebugScroller, "ScrollVerticallyBy displacement:" + displacement);
-            return ScrollBy(displacement, false);
-        }
-
-        internal void StopScroll()
-        {
-            if (scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
-            {
-                scrollAnimation.Stop(Animation.EndActions.Cancel);
-                scrollAnimation.Clear();
-            }
-        }
-
-        // static constructor registers the control type (for user can add kinds of visuals to it)
-        static LayoutScroller()
-        {
-            // ViewRegistry registers control type with DALi type registry
-            // also uses introspection to find any properties that need to be registered with type registry
-            CustomViewRegistry.Instance.Register(CreateInstance, typeof(LayoutScroller));
-        }
-
-        internal static CustomView CreateInstance()
-        {
-            return new LayoutScroller();
-        }
-
-        public void OffsetChildVertically(float displacement, bool animate)
-        {
-            float previousTargetPosition = childTargetPosition;
-
-            childTargetPosition = childTargetPosition + displacement;
-            childTargetPosition = Math.Min(0,childTargetPosition);
-            childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition);
-
-            Debug.WriteLineIf( LayoutDebugScroller, "OffsetChildVertically currentYPosition:" + mScrollingChild.PositionY + "childTargetPosition:" + childTargetPosition);
-
-            if (animate)
-            {
-                if (scrollAnimation == null)
-                {
-                    scrollAnimation = new Animation();
-                    scrollAnimation.Finished += ScrollAnimationFinished;
-
-                }
-                else if (scrollAnimation.State == Animation.States.Playing)
-                {
-                    scrollAnimation.Stop(Animation.EndActions.StopFinal);
-                    scrollAnimation.Clear();
-                }
-                scrollAnimation.Duration = 1000;
-                scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
-                scrollAnimation.AnimateTo(mScrollingChild, "PositionY", childTargetPosition);
-                scrollAnimation.Play();
-            }
-            else
-            {
-                // Set position of scrolling child without an animation
-                mScrollingChild.PositionY = childTargetPosition;
-            }
-        }
-
-        /// <summary>
-        /// you can override it to clean-up your own resources.
-        /// </summary>
-        /// <param name="type">DisposeTypes</param>
-        /// <since_tizen> 6 </since_tizen>
-        /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
-        [EditorBrowsable(EditorBrowsableState.Never)]
-        protected override void Dispose(DisposeTypes type)
-        {
-            if (disposed)
-            {
-                return;
-            }
-
-            if (type == DisposeTypes.Explicit)
-            {
-                StopScroll();
-
-                if (mPanGestureDetector != null)
-                {
-                    mPanGestureDetector.Detected -= OnPanGestureDetected;
-                    mPanGestureDetector.Dispose();
-                    mPanGestureDetector = null;
-                }
-
-                if (mTapGestureDetector != null)
-                {
-                    mTapGestureDetector.Detected -= OnTapGestureDetected;
-                    mTapGestureDetector.Dispose();
-                    mTapGestureDetector = null;
-                }
-            }
-            base.Dispose(type);
-        }
-
-        private float ScrollBy(float displacement, bool animate)
-        {
-            if (GetChildCount() == 0 || displacement == 0)
-            {
-                return 0;
-            }
-
-            int scrollingChildHeight = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue();
-            maxScrollDistance = scrollingChildHeight - CurrentSize.Height;
-
-            Debug.WriteLineIf( LayoutDebugScroller, "ScrollBy maxScrollDistance:" + maxScrollDistance +
-                                                    " parent length:" + CurrentSize.Height +
-                                                    " scrolling child length:" + mScrollingChild.CurrentSize.Height);
-
-            float absDisplacement = Math.Abs(displacement);
-
-            OffsetChildVertically(displacement, animate);
-
-            return absDisplacement;
-        }
-
-        private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
-        {
-            if (e.PanGesture.State == Gesture.StateType.Started)
-            {
-                if(Scrolling)
-                {
-                    StopScroll();
-                }
-            }
-            else if (e.PanGesture.State == Gesture.StateType.Continuing)
-            {
-                ScrollVerticallyBy(e.PanGesture.Displacement.Y);
-            }
-            else if (e.PanGesture.State == Gesture.StateType.Finished)
-            {
-               ScrollVerticallyBy(e.PanGesture.Velocity.Y * 600);
-            }
-        }
-
-        private void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
-        {
-            if (e.TapGesture.Type == Gesture.GestureType.Tap)
-            {
-                // Stop scrolling if touch detected
-                if(Scrolling)
-                {
-                    StopScroll();
-                }
-            }
-        }
-
-        private void ScrollAnimationFinished(object sender, EventArgs e)
-        {
-            Scrolling = false;
-        }
-
-
-    }
-
-} // namespace
diff --git a/src/Tizen.NUI.Components/Controls/ScrollableBase.cs b/src/Tizen.NUI.Components/Controls/ScrollableBase.cs
new file mode 100755 (executable)
index 0000000..358a6fa
--- /dev/null
@@ -0,0 +1,650 @@
+/* Copyright (c) 2019 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.ComponentModel;
+using System.Diagnostics;
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// [Draft] This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
+    /// </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 ScrollableBase : Control
+    {
+           static bool LayoutDebugScrollableBase = false; // Debug flag
+        private Direction mScrollingDirection = Direction.Vertical;
+
+        private class ScrollableBaseCustomLayout : LayoutGroup
+        {
+            protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
+            {
+                Extents padding = Padding;
+                float totalHeight = padding.Top + padding.Bottom;
+                float totalWidth = padding.Start + padding.End;
+
+                MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
+                MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
+
+                Direction scrollingDirection = Direction.Vertical;
+                ScrollableBase scrollableBase = this.Owner as ScrollableBase;
+                if (scrollableBase)
+                {
+                   scrollingDirection = scrollableBase.ScrollingDirection;
+                }
+
+                // measure child, should be a single scrolling child
+                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);
+
+                        if (scrollingDirection == Direction.Vertical)
+                        {
+                            MeasureChild( childLayout, widthMeasureSpec, unrestrictedMeasureSpec );  // Height unrestricted by parent
+                        }
+                        else
+                        {
+                            MeasureChild( childLayout, unrestrictedMeasureSpec, heightMeasureSpec );  // Width unrestricted by parent
+                        }
+
+                        float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
+                        float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
+
+                        // Determine the width and height needed by the children using their given position and size.
+                        // Children could overlap so find the left most and right most child.
+                        Position2D childPosition = childLayout.Owner.Position2D;
+                        float childLeft = childPosition.X;
+                        float childTop = childPosition.Y;
+
+                        // Store current width and height needed to contain all children.
+                        Extents childMargin = childLayout.Margin;
+                        totalWidth = childWidth + childMargin.Start + childMargin.End;
+                        totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
+
+                        if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
+                        {
+                            childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
+                        }
+                        if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
+                        {
+                            childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
+                        }
+                    }
+                }
+
+
+                MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
+                MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
+                totalWidth = widthSizeAndState.Size.AsDecimal();
+                totalHeight = heightSizeAndState.Size.AsDecimal();
+
+                // Ensure layout respects it's given minimum size
+                totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() );
+                totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() );
+
+                widthSizeAndState.State = childWidthState;
+                heightSizeAndState.State = childHeightState;
+
+                SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ),
+                                       ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) );
+
+            }
+
+            protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
+            {
+                foreach( LayoutItem childLayout in LayoutChildren )
+                {
+                    if( childLayout != null )
+                    {
+                        LayoutLength childWidth = childLayout.MeasuredWidth.Size;
+                        LayoutLength childHeight = childLayout.MeasuredHeight.Size;
+
+                        Position2D childPosition = childLayout.Owner.Position2D;
+                        Extents padding = Padding;
+                        Extents childMargin = childLayout.Margin;
+
+                        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 );
+                    }
+                }
+            }
+        } //  ScrollableBaseCustomLayout
+
+        /// <summary>
+        /// The direction axis to scroll.
+        /// </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)]
+        public enum Direction
+        {
+            /// <summary>
+            /// Horizontal axis.
+            /// </summary>
+            /// <since_tizen> 6 </since_tizen>
+            Horizontal,
+
+            /// <summary>
+            /// Vertical axis.
+            /// </summary>
+            /// <since_tizen> 6 </since_tizen>
+            Vertical
+        }
+
+        /// <summary>
+        /// [Draft] Configurable speed threshold that register the gestures as a flick.
+        /// If the flick speed less than the threshold then will not be considered a flick.
+        /// </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 float FlickThreshold { get; set; } = 0.2f;
+
+        /// <summary>
+        /// [Draft] Configurable duration modifer for the flick animation.
+        /// Determines the speed of the scroll, large value results in a longer flick animation. Range (0.1 - 1.0)
+        /// </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 float FlickAnimationSpeed { get; set; } = 0.4f;
+
+        /// <summary>
+        /// [Draft] Configurable modifer for the distance to be scrolled when flicked detected.
+        /// It a ratio of the ScrollableBase's length. (not child's length).
+        /// First value is the ratio of the distance to scroll with the weakest flick.
+        /// Second value is the ratio of the distance to scroll with the strongest flick.
+        /// Second > First.
+        /// </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 Vector2 FlickDistanceMultiplierRange { get; set; } = new Vector2(0.6f, 1.8f);
+
+        /// <summary>
+        /// [Draft] Scrolling direction mode.
+        /// Default is Vertical scrolling.
+        /// </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 Direction ScrollingDirection
+        {
+            get
+            {
+                return mScrollingDirection;
+            }
+            set
+            {
+                if(value != mScrollingDirection)
+                {
+                    mScrollingDirection = value;
+                    mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ? PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
+                    mPanGestureDetector.AddDirection(value == Direction.Horizontal ? PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
+                }
+            }
+        }
+
+        /// <summary>
+        /// [Draft] Pages mode, enables moving to the next or return to current page depending on pan displacement.
+        /// Default is false.
+        /// </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 bool SnapToPage { set; get; } = false;
+
+        /// <summary>
+        /// [Draft] Pages mode, Number of pages.
+        /// </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 int NumberOfPages { set; get; } = 1;
+
+        /// <summary>
+        /// [Draft] Duration of scroll animation.
+        /// </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 int ScrollDuration { set; get; } = 125;
+
+        /// <summary>
+        /// [Draft] Width of the Page.
+        /// </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 int PageWidth { set; get; } = 1080; // Temporary use for prototype, should get ScrollableBase width
+
+        /// <summary>
+        /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
+        /// </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)]
+        public class ScrollEventArgs : EventArgs
+        {
+        }
+
+        /// <summary>
+        /// An event emitted when the scrolling starts, user can subscribe or unsubscribe to this event handler.<br />
+        /// </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)]
+        public event EventHandler<ScrollEventArgs> ScrollStartedEvent;
+
+        /// <summary>
+        /// An event emitted when the scrolling ends, user can subscribe or unsubscribe to this event handler.<br />
+        /// </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)]
+        public event EventHandler<ScrollEventArgs> ScrollEndedEvent;
+
+        private Animation scrollAnimation;
+        private float maxScrollDistance;
+        private float childTargetPosition = 0.0f;
+        private PanGestureDetector mPanGestureDetector;
+        private TapGestureDetector mTapGestureDetector;
+        private View mScrollingChild;
+        private float multiplier =1.0f;
+        private bool scrolling = false;
+        private float ratioOfScreenWidthToCompleteScroll = 0.5f;
+        private float totalDisplacementForPan = 0.0f;
+
+        // If false then can only flick pages when the current animation/scroll as ended.
+        private bool flickWhenAnimating = false;
+
+        private int currentPage = 0;
+
+        /// <summary>
+        /// [Draft] Constructor
+        /// </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)]
+        public ScrollableBase() : base()
+        {
+            mPanGestureDetector = new PanGestureDetector();
+            mPanGestureDetector.Attach(this);
+            mPanGestureDetector.Detected += OnPanGestureDetected;
+
+            mTapGestureDetector = new TapGestureDetector();
+            mTapGestureDetector.Attach(this);
+            mTapGestureDetector.Detected += OnTapGestureDetected;
+
+            ClippingMode = ClippingModeType.ClipToBoundingBox;
+
+            mScrollingChild = new View();
+
+            Layout = new ScrollableBaseCustomLayout();
+        }
+
+        /// <summary>
+        /// Called after a child has been added to the owning view.
+        /// </summary>
+        /// <param name="view">The child which has been added.</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)]
+        public override void OnChildAdd(View view)
+        {
+            mScrollingChild = view;
+            {
+            if (Children.Count > 1)
+                Log.Error("ScrollableBase", $"Only 1 child should be added to ScrollableBase.");
+            }
+        }
+
+        /// <summary>
+        /// Called after a child has been removed from the owning view.
+        /// </summary>
+        /// <param name="view">The child which has been removed.</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)]
+        public override void OnChildRemove(View view)
+        {
+            mScrollingChild = new View();
+        }
+
+        private void OnScrollStart()
+        {
+            ScrollEventArgs eventArgs = new ScrollEventArgs();
+            ScrollStartedEvent?.Invoke(this, eventArgs);
+        }
+
+        private void OnScrollEnd()
+        {
+            ScrollEventArgs eventArgs = new ScrollEventArgs();
+            ScrollEndedEvent?.Invoke(this, eventArgs);
+        }
+
+        private void StopScroll()
+        {
+            if (scrollAnimation != null)
+            {
+                if (scrollAnimation.State == Animation.States.Playing)
+                {
+                    Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
+                    scrollAnimation.Stop(Animation.EndActions.Cancel);
+                    OnScrollEnd();
+                }
+                scrollAnimation.Clear();
+            }
+        }
+
+        // static constructor registers the control type
+        static ScrollableBase()
+        {
+            // ViewRegistry registers control type with DALi type registry
+            // also uses introspection to find any properties that need to be registered with type registry
+            CustomViewRegistry.Instance.Register(CreateInstance, typeof(ScrollableBase));
+        }
+
+        internal static CustomView CreateInstance()
+        {
+            return new ScrollableBase();
+        }
+
+        private void AnimateChildTo(int duration, float axisPosition)
+        {
+            Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
+
+            StopScroll(); // Will replace previous animation so will stop existing one.
+
+            if (scrollAnimation == null)
+            {
+                scrollAnimation = new Animation();
+                scrollAnimation.Finished += ScrollAnimationFinished;
+            }
+
+            scrollAnimation.Duration = duration;
+            scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
+            scrollAnimation.AnimateTo(mScrollingChild, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition);
+            scrolling = true;
+            OnScrollStart();
+            scrollAnimation.Play();
+        }
+
+        private void ScrollBy(float displacement, bool animate)
+        {
+            if (GetChildCount() == 0 || displacement == 0)
+            {
+                return;
+            }
+
+            float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? mScrollingChild.PositionX: mScrollingChild.PositionY;
+
+            Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
+                                                   " displacement:" + displacement,
+                                                   " maxScrollDistance:" + maxScrollDistance );
+
+            childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
+            childTargetPosition = Math.Min(0,childTargetPosition);
+            childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition);
+
+            Debug.WriteLineIf( LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
+
+            if (animate)
+            {
+                // Calculate scroll animaton duration
+                float scrollDistance = 0.0f;
+                if (childCurrentPosition < childTargetPosition)
+                {
+                    scrollDistance = Math.Abs(childCurrentPosition + childTargetPosition);
+                }
+                else
+                {
+                    scrollDistance = Math.Abs(childCurrentPosition - childTargetPosition);
+                }
+
+                int duration = (int)((320*FlickAnimationSpeed) + (scrollDistance * FlickAnimationSpeed));
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "Scroll Animation Duration:" + duration + " Distance:" + scrollDistance);
+
+                AnimateChildTo(duration, childTargetPosition);
+            }
+            else
+            {
+                // Set position of scrolling child without an animation
+                if (ScrollingDirection == Direction.Horizontal)
+                {
+                    mScrollingChild.PositionX = childTargetPosition;
+                }
+                else
+                {
+                    mScrollingChild.PositionY = childTargetPosition;
+                }
+            }
+        }
+
+        /// <summary>
+        /// you can override it to clean-up your own resources.
+        /// </summary>
+        /// <param name="type">DisposeTypes</param>
+        /// <since_tizen> 6 </since_tizen>
+        /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                StopScroll();
+
+                if (mPanGestureDetector != null)
+                {
+                    mPanGestureDetector.Detected -= OnPanGestureDetected;
+                    mPanGestureDetector.Dispose();
+                    mPanGestureDetector = null;
+                }
+
+                if (mTapGestureDetector != null)
+                {
+                    mTapGestureDetector.Detected -= OnTapGestureDetected;
+                    mTapGestureDetector.Dispose();
+                    mTapGestureDetector = null;
+                }
+            }
+            base.Dispose(type);
+        }
+
+        private float CalculateDisplacementFromVelocity(float axisVelocity)
+        {
+            // Map: flick speed of range (2.0 - 6.0) to flick multiplier of range (0.7 - 1.6)
+            float speedMinimum = FlickThreshold;
+            float speedMaximum = FlickThreshold + 6.0f;
+            float multiplierMinimum = FlickDistanceMultiplierRange.X;
+            float multiplierMaximum = FlickDistanceMultiplierRange.Y;
+
+            float flickDisplacement = 0.0f;
+
+            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;
+
+                // Calculate multiplier by mapping speed between the multiplier minimum and maximum.
+                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:"
+                                                        + multiplier);
+            }
+            return flickDisplacement;
+        }
+
+        private float CalculateMaximumScrollDistance()
+        {
+            int scrollingChildLength = 0;
+            int scrollerLength = 0;
+            if (ScrollingDirection == Direction.Horizontal)
+            {
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
+
+                scrollingChildLength = (int)mScrollingChild.Layout.MeasuredWidth.Size.AsRoundedValue();
+                scrollerLength = CurrentSize.Width;
+            }
+            else
+            {
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
+                scrollingChildLength = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue();
+                scrollerLength = CurrentSize.Height;
+            }
+
+            Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
+                                                   " parent length:" + scrollerLength +
+                                                   " scrolling child length:" + scrollingChildLength);
+
+            return scrollingChildLength - scrollerLength;
+        }
+
+        private void PageSnap()
+        {
+            Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
+                                                                " currentPage[" + currentPage + "]" );
+
+            //Increment current page if total displacement enough to warrant a page change.
+            if (Math.Abs(totalDisplacementForPan) > (PageWidth * ratioOfScreenWidthToCompleteScroll))
+            {
+                if (totalDisplacementForPan < 0)
+                {
+                    currentPage = Math.Min(NumberOfPages-1, ++currentPage);
+                }
+                else
+                {
+                    currentPage = Math.Max(0, --currentPage);
+                }
+            }
+
+            // Animate to new page or reposition to current page
+            int destinationX = -(currentPage * PageWidth);
+            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(NumberOfPages - 1, currentPage + 1);
+                      Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap - to page:" + currentPage);
+                  }
+                  else
+                  {
+                      currentPage = Math.Max(0, currentPage - 1);
+                      Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap + to page:" + currentPage);
+                  }
+                  float targetPosition = -(currentPage* PageWidth); // page size
+                  Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to :" + targetPosition);
+                  AnimateChildTo(ScrollDuration,targetPosition);
+              }
+          }
+          else
+          {
+              ScrollBy(flickDisplacement, true); // Animate flickDisplacement.
+          }
+        }
+
+        private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
+        {
+            if (e.PanGesture.State == Gesture.StateType.Started)
+            {
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
+                if (scrolling && !SnapToPage)
+                {
+                    StopScroll();
+                }
+                maxScrollDistance = CalculateMaximumScrollDistance();
+                totalDisplacementForPan = 0.0f;
+            }
+            else if (e.PanGesture.State == Gesture.StateType.Continuing)
+            {
+                if (ScrollingDirection == Direction.Horizontal)
+                {
+                    ScrollBy(e.PanGesture.Displacement.X, false);
+                    totalDisplacementForPan += e.PanGesture.Displacement.X;
+                }
+                else
+                {
+                    ScrollBy(e.PanGesture.Displacement.Y, false);
+                    totalDisplacementForPan += e.PanGesture.Displacement.Y;
+                }
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
+
+            }
+            else if (e.PanGesture.State == Gesture.StateType.Finished)
+            {
+                float axisVelocity = (ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y;
+                float flickDisplacement = CalculateDisplacementFromVelocity(axisVelocity);
+
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "FlickDisplacement:" + flickDisplacement + "TotalDisplacementForPan:" + totalDisplacementForPan);
+
+                if (flickDisplacement > 0 | flickDisplacement < 0)// Flick detected
+                {
+                    Flick(flickDisplacement);
+                }
+                else
+                {
+                    // End of panning gesture but was not a flick
+                    if (SnapToPage)
+                    {
+                        PageSnap();
+                    }
+                }
+                totalDisplacementForPan = 0;
+            }
+        }
+
+        private void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
+        {
+            if (e.TapGesture.Type == Gesture.GestureType.Tap)
+            {
+                // 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)
+                {
+                    StopScroll();
+                }
+            }
+        }
+
+        private void ScrollAnimationFinished(object sender, EventArgs e)
+        {
+            scrolling = false;
+            OnScrollEnd();
+        }
+
+    }
+
+} // namespace
index 070231d..1623b54 100755 (executable)
@@ -461,7 +461,7 @@ namespace Tizen.NUI
         {
             View childOwner = child.Owner;
 
-            Extents padding = Padding; // Padding of this layout's owner, not of the child being measured.
+            Extents padding = childOwner.Padding; // Padding of this layout's owner, not of the child being measured.
 
             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec,
                                                                                        new LayoutLength(padding.Start + padding.End ),