[OAPSAN-4460] Add CarouselSelector (#18)
authorLukasz Stanislawski/IoT & UI Sample (PLT) /SRPOL/Engineer/Samsung Electronics <l.stanislaws@samsung.com>
Fri, 27 Mar 2020 07:55:24 +0000 (08:55 +0100)
committerGitHub Enterprise <noreply-CODE@samsung.com>
Fri, 27 Mar 2020 07:55:24 +0000 (08:55 +0100)
* Add CarouselPicker

Provide CarouselPicker control with limited styling
capabilities. Currently the line color and TextLablel
properties can be set using styles.

For now the only animatable properties on scrolling are:
* TextColor
* Opacity

Oobe/OobeCommon/Controls/CarouselPicker.cs [new file with mode: 0644]
Oobe/OobeCommon/Controls/CarouselPickerItemData.cs [new file with mode: 0644]
Oobe/OobeCommon/Controls/CarouselPickerStyle.cs [new file with mode: 0644]
Oobe/OobeCommon/Controls/ScrollableBase.cs [new file with mode: 0755]
Oobe/OobeCommon/Styles/CarouselPickerStyles.cs [new file with mode: 0644]
Oobe/OobeCommon/Styles/DropDownStyles.cs [deleted file]
Oobe/OobeCommon/Styles/ScrollBarStyles.cs [deleted file]
Oobe/OobeCommon/Utils/ColorUtils.cs [new file with mode: 0644]
Oobe/OobeLanguage/LanguageStep.cs
Oobe/OobeRegion/RegionStep.cs

diff --git a/Oobe/OobeCommon/Controls/CarouselPicker.cs b/Oobe/OobeCommon/Controls/CarouselPicker.cs
new file mode 100644 (file)
index 0000000..aa0673b
--- /dev/null
@@ -0,0 +1,268 @@
+using System;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.NUI.Binding;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Oobe.Common.Utils;
+
+namespace Oobe.Common.Controls
+{
+    public class CarouselPicker : Control
+    {
+        public event EventHandler SelectedItemChanged;
+
+        private Oobe.Common.Controls.ScrollableBase scrollableBase;
+        private View itemsListView;
+        private View upperLine, lowerLine;
+
+        private Vector3 textCenterColorHSV = new Vector3();
+        private Vector3 textOuterColorHSV = new Vector3();
+
+        private float textCenterOpacity;
+        private float textOuterOpacity;
+
+        private List<CarouselPickerItemData> items = new List<CarouselPickerItemData>();
+
+        public CarouselPicker() : base()
+        {
+        }
+
+        public CarouselPicker(CarouselPickerStyle style) : base(style)
+        {
+            //TODO fix a bug with style not properly applied by base class
+            ApplyStyle(style);
+        }
+
+        public void AddItem(CarouselPickerItemData item)
+        {
+            var view = CreateItemView(item);
+            itemsListView.Add(view);
+            items.Add(item);
+        }
+
+        public void RemoveItem(CarouselPickerItemData item)
+        {
+            var index = items.IndexOf(item);
+            items.Remove(item);
+            itemsListView.Children.RemoveAt(index);
+        }
+
+        public int SelectedItemIndex
+        {
+            get
+            {
+                return scrollableBase.CurrentPage;
+            }
+            set
+            {
+                if (scrollableBase.CurrentPage != value)
+                {
+                    scrollableBase.ScrollToIndex(value);
+                    SelectedItemChanged?.Invoke(this, null);
+                }
+            }
+        }
+
+        public override void ApplyStyle(ViewStyle viewStyle)
+        {
+            base.ApplyStyle(viewStyle);
+
+            CarouselPickerStyle style = viewStyle as CarouselPickerStyle;
+
+            if (style != null)
+            {
+                Layout = new LinearLayout
+                {
+                    LinearOrientation = LinearLayout.Orientation.Vertical,
+                    LinearAlignment = LinearLayout.Alignment.Center,
+                };
+                ClippingMode = ClippingModeType.ClipToBoundingBox;
+
+                // Animatable properties
+                textCenterColorHSV = Style.CenterText?.TextColor?.All?.ToHSV() ?? new Vector3();
+                textCenterOpacity = Style.CenterText?.Opacity?.All ?? 1.0f;
+
+                textOuterColorHSV = Style.OuterText?.TextColor?.All?.ToHSV() ?? new Vector3();
+                textOuterOpacity = Style.OuterText?.Opacity?.All ?? 1.0f;
+
+
+                if (itemsListView != null)
+                {
+                    scrollableBase?.Remove(itemsListView);
+                    itemsListView.Dispose();
+                }
+
+                itemsListView = new View
+                {
+                    Layout = new LinearLayout
+                    {
+                        LinearOrientation = LinearLayout.Orientation.Vertical,
+                    },
+                    WidthSpecification = LayoutParamPolicies.MatchParent,
+                    HeightSpecification = LayoutParamPolicies.WrapContent,
+                };
+
+                if (scrollableBase != null)
+                {
+                    Remove(scrollableBase);
+                    scrollableBase.Dispose();
+                }
+
+                scrollableBase = new Oobe.Common.Controls.ScrollableBase
+                {
+                    ClippingMode = ClippingModeType.Disabled,
+                    WidthResizePolicy = ResizePolicyType.FillToParent,
+                    SnapToPage = true,
+                    ScrollingDirection = ScrollableBase.Direction.Vertical,
+                    ScrollEnabled = true,
+                    SizeHeight = CalculateScrollerSize(),
+                    FlickDistanceMultiplierRange = new Vector2(4.6f, 5.8f),
+                    EventsView = this,
+                };
+
+                scrollableBase.ScrollEvent += (sender, args) =>
+                {
+                    UpdateItems();
+                };
+
+                if (upperLine != null)
+                {
+                    Remove(upperLine);
+                    upperLine.Dispose();
+                }
+
+                upperLine = new View()
+                {
+                    BackgroundColor = Style.LinesColor,
+                    Size2D = new Size2D(0, 1),
+                    WidthResizePolicy = ResizePolicyType.FillToParent,
+                    Position2D = new Position2D(0, 93),
+                    Opacity = 0.95f,
+                };
+
+                if (lowerLine != null)
+                {
+                    Remove(lowerLine);
+                    lowerLine.Dispose();
+                }
+
+                lowerLine = new View()
+                {
+                    BackgroundColor = Style.LinesColor,
+                    Size2D = new Size2D(0, 1),
+                    WidthResizePolicy = ResizePolicyType.FillToParent,
+                    Position2D = new Position2D(0, 156),
+                    Opacity = 0.95f,
+                };
+
+                scrollableBase.Add(itemsListView);
+
+                Add(upperLine);
+                Add(scrollableBase);
+                Add(lowerLine);
+            }
+        }
+
+        private float CalculateScrollerSize()
+        {
+            float size = 0.0f;
+
+            size += Style.CenterText?.Size2D?.Height ?? 0.0f;
+            size += (float)Style.CenterText?.Margin?.Top;
+            size += (float)Style.CenterText?.Margin?.Bottom;
+
+            return size;
+        }
+
+        private View CreateItemView(CarouselPickerItemData item)
+        {
+            var view = new TextLabel();
+            // TODO for some reason TextLabel(Style.CenterText)
+            // or view.ApplyStyle(Style.CenterText) do not work here so set
+            // everything manually
+            // var view = new TextLabel(Style.CenterText);
+            // view.ApplyStyle(Style.CenterText);
+            view.Text = item.Text;
+            view.Size2D = Style.CenterText?.Size2D ?? new Size2D();
+            view.PixelSize = Style.CenterText?.PixelSize ?? 10.0f;
+            view.Margin = Style.CenterText?.Margin ?? new Extents();
+            view.HorizontalAlignment = Style.CenterText?.HorizontalAlignment ?? HorizontalAlignment.Center;
+            view.VerticalAlignment = Style.CenterText?.VerticalAlignment ?? VerticalAlignment.Center;
+            view.Opacity = Style.CenterText?.Opacity?.All ?? 1.0f;
+            //TODO other properties?
+            return view;
+        }
+
+        protected override void OnUpdate()
+        {
+            base.OnUpdate();
+            UpdateItems();
+        }
+
+        protected override void Dispose(Tizen.NUI.DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                // Dispose all containing widgets
+                scrollableBase.Dispose();
+                upperLine.Dispose();
+                lowerLine.Dispose();
+            }
+
+            base.Dispose(type);
+        }
+
+        public new CarouselPickerStyle Style => ViewStyle as CarouselPickerStyle;
+
+        protected override ViewStyle GetViewStyle()
+        {
+            var ret = new CarouselPickerStyle();
+            return ret;
+        }
+
+        private void CreateMainList()
+        {
+        }
+
+        private Vector3 ScaleVector(Vector3 from, Vector3 to, float percent)
+        {
+            percent = Math.Clamp(percent, 0.0f, 1.0f);
+
+            float a = from[0] + (to[0] - from[0]) * percent;
+            float b = from[1] + (to[1] - from[1]) * percent;
+            float c = from[2] + (to[2] - from[2]) * percent;
+
+            return new Vector3(a, b, c);
+        }
+
+        private float ScaleScalar(float from, float to, float percent)
+        {
+            percent = Math.Clamp(percent, 0.0f, 1.0f);
+            return from + (to - from) * percent;
+        }
+
+        private void UpdateItems()
+        {
+            float mid = itemsListView.PositionY - (int)(scrollableBase.Size2D.Height / 2.0f);
+            int threshold = 80; // after this value the color will become outer
+
+            foreach (View view in itemsListView.Children)
+            {
+                TextLabel itemView = view as TextLabel;
+                if (itemView == null) continue;
+
+                float viewMid = view.PositionY + view.Size2D.Height / 2.0f;
+                float percent = Math.Abs(viewMid + mid) / threshold;
+
+                itemView.Opacity = ScaleScalar(textCenterOpacity, textOuterOpacity, percent);
+                itemView.TextColor = ColorUtils.ColorFromHSV(ScaleVector(textCenterColorHSV, textOuterColorHSV, percent));
+            }
+        }
+    }
+}
diff --git a/Oobe/OobeCommon/Controls/CarouselPickerItemData.cs b/Oobe/OobeCommon/Controls/CarouselPickerItemData.cs
new file mode 100644 (file)
index 0000000..1a0af35
--- /dev/null
@@ -0,0 +1,17 @@
+using System;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+
+namespace Oobe.Common.Controls
+{
+    public class CarouselPickerItemData
+    {
+        public CarouselPickerItemData()
+        {
+        }
+
+        public string Text { get; set; }
+
+        public string TranslatableText { get; set; }
+    }
+}
diff --git a/Oobe/OobeCommon/Controls/CarouselPickerStyle.cs b/Oobe/OobeCommon/Controls/CarouselPickerStyle.cs
new file mode 100644 (file)
index 0000000..1cbf156
--- /dev/null
@@ -0,0 +1,61 @@
+using System;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace Oobe.Common.Controls
+{
+    public class CarouselPickerStyle : ControlStyle
+    {
+        public CarouselPickerStyle() : base()
+        {
+            InitSubStyle();
+        }
+
+        public TextLabelStyle CenterText { get; set; }
+
+        public TextLabelStyle OuterText { get; set; }
+
+        public Color LinesColor { get; set; }
+
+        private void InitSubStyle()
+        {
+            CenterText = new TextLabelStyle
+            {
+                PositionUsesPivotPoint = true,
+                ParentOrigin = Tizen.NUI.ParentOrigin.Center,
+                PivotPoint = Tizen.NUI.PivotPoint.Center,
+                WidthResizePolicy = ResizePolicyType.FillToParent,
+                HeightResizePolicy = ResizePolicyType.FillToParent,
+                HorizontalAlignment = HorizontalAlignment.Center,
+                VerticalAlignment = VerticalAlignment.Center
+            };
+            OuterText = new TextLabelStyle
+            {
+                PositionUsesPivotPoint = true,
+                ParentOrigin = Tizen.NUI.ParentOrigin.Center,
+                PivotPoint = Tizen.NUI.PivotPoint.Center,
+                WidthResizePolicy = ResizePolicyType.FillToParent,
+                HeightResizePolicy = ResizePolicyType.FillToParent,
+                HorizontalAlignment = HorizontalAlignment.Center,
+                VerticalAlignment = VerticalAlignment.Center
+            };
+            LinesColor = Color.Black;
+        }
+
+        public override void CopyFrom(BindableObject bindableObject)
+        {
+            base.CopyFrom(bindableObject);
+
+            CarouselPickerStyle carouselSelectorStyle = bindableObject as CarouselPickerStyle;
+
+            if (carouselSelectorStyle != null)
+            {
+                CenterText?.CopyFrom(carouselSelectorStyle.CenterText);
+                OuterText?.CopyFrom(carouselSelectorStyle.OuterText);
+                LinesColor = carouselSelectorStyle.LinesColor;
+            }
+        }
+    }
+}
diff --git a/Oobe/OobeCommon/Controls/ScrollableBase.cs b/Oobe/OobeCommon/Controls/ScrollableBase.cs
new file mode 100755 (executable)
index 0000000..d3ffe3f
--- /dev/null
@@ -0,0 +1,867 @@
+/* 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;
+using Tizen.NUI.Components;
+using Tizen.NUI;
+
+namespace Oobe.Common.Controls
+{
+    /// <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 bool mScrollEnabled = true;
+        private int mPageWidth = 0;
+        private int mPageHeight = 0;
+
+        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 + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
+                MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), 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 + 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();
+                scrollableBase.mPageHeight = (int)MeasuredHeight.Size.AsRoundedValue();
+                Tizen.Log.Debug("ScrollableBase", $"Page.Width: {scrollableBase.mPageWidth}");
+                Tizen.Log.Debug("ScrollableBase", $"Page.Height: {scrollableBase.mPageHeight}");
+            }
+
+            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] Enable or disable 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 bool ScrollEnabled
+        {
+            get
+            {
+                return mScrollEnabled;
+            }
+            set
+            {
+                if (value != mScrollEnabled)
+                {
+                    mScrollEnabled = value;
+                    if(mScrollEnabled)
+                    {
+                        mPanGestureDetector.Detected += OnPanGestureDetected;
+                        mTapGestureDetector.Detected += OnTapGestureDetected;
+                    }
+                    else
+                    {
+                        mPanGestureDetector.Detected -= OnPanGestureDetected;
+                        mTapGestureDetector.Detected -= OnTapGestureDetected;
+                    }
+                }
+            }
+        }
+
+        /// <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] Get current page.
+        /// Working propery with SnapToPage property.
+        /// </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 CurrentPage { get; private set; } = 0;
+
+        /// <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] Scroll Available area.
+        /// </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; }
+
+        /// <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
+        {
+            Position position;
+
+            /// <summary>
+            /// Default constructor.
+            /// </summary>
+            /// <param name="position">Current scroll position</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
+            public ScrollEventArgs(Position position)
+            {
+                this.position = position;
+            }
+
+            /// <summary>
+            /// [Draft] Current scroll position.
+            /// </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 Position Position
+            {
+                get
+                {
+                    return position;
+                }
+            }
+        }
+
+        /// <summary>
+        /// An event emitted when user starts dragging ScrollableBase, 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> ScrollDragStartEvent;
+
+        /// <summary>
+        /// An event emitted when user stops dragging ScrollableBase, 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> ScrollDragEndEvent;
+
+
+        /// <summary>
+        /// An event emitted when the scrolling slide animation 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> ScrollAnimationStartEvent;
+
+        /// <summary>
+        /// An event emitted when the scrolling slide animation 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> ScrollAnimationEndEvent;
+
+
+        /// <summary>
+        /// An event emitted when scrolling, 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> ScrollEvent;
+
+        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.4f;
+        private float totalDisplacementForPan = 0.0f;
+
+        // If false then can only flick pages when the current animation/scroll as ended.
+        private bool flickWhenAnimating = false;
+        private PropertyNotification propertyNotification;
+
+        /// <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.AddDirection(PanGestureDetector.DirectionVertical);
+            mPanGestureDetector.Detected += OnPanGestureDetected;
+
+            mTapGestureDetector = new TapGestureDetector();
+            //mTapGestureDetector.Attach(this);
+            mTapGestureDetector.Detected += OnTapGestureDetected;
+
+
+            ClippingMode = ClippingModeType.ClipToBoundingBox;
+
+            mScrollingChild = new View();
+            mScrollingChild.Name = "DefaultScrollingChild";
+
+            Layout = new ScrollableBaseCustomLayout();
+            EventsView = this;
+        }
+
+        private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
+        {
+            OnScroll();
+        }
+
+        /// <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)
+        {
+            if(mScrollingChild.Name != "DefaultScrollingChild")
+            {
+                propertyNotification.Notified -= OnPropertyChanged;
+                mScrollingChild.RemovePropertyNotification(propertyNotification);
+            }
+
+            mScrollingChild = view;
+            propertyNotification = mScrollingChild?.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
+            propertyNotification.Notified += OnPropertyChanged;
+
+            {
+                if (Children.Count > 1)
+                    Tizen.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)
+        {
+            propertyNotification.Notified -= OnPropertyChanged;
+            mScrollingChild.RemovePropertyNotification(propertyNotification);
+
+            mScrollingChild = new View();
+        }
+
+
+        /// <summary>
+        /// Scrolls to the item at the specified index.
+        /// </summary>
+        /// <param name="index">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)]
+        public void ScrollToIndex(int index)
+        {
+            if(mScrollingChild.ChildCount-1 < index || index < 0)
+            {
+                return;
+            }
+
+            if(SnapToPage)
+            {
+                CurrentPage = index;
+            }
+
+            maxScrollDistance = CalculateMaximumScrollDistance();
+
+            float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? mScrollingChild.Children[index].Position.Y : mScrollingChild.Children[index].Position.X, maxScrollDistance);
+            AnimateChildTo(ScrollDuration, -targetPosition);
+        }
+
+        private void OnScrollDragStart()
+        {
+            ScrollEventArgs eventArgs = new ScrollEventArgs(mScrollingChild.CurrentPosition);
+            ScrollDragStartEvent?.Invoke(this, eventArgs);
+        }
+
+        private void OnScrollDragEnd()
+        {
+            ScrollEventArgs eventArgs = new ScrollEventArgs(mScrollingChild.CurrentPosition);
+            ScrollDragEndEvent?.Invoke(this, eventArgs);
+        }
+
+        private void OnScrollAnimationStart()
+        {
+            ScrollEventArgs eventArgs = new ScrollEventArgs(mScrollingChild.CurrentPosition);
+            ScrollAnimationStartEvent?.Invoke(this, eventArgs);
+        }
+
+        private void OnScrollAnimationEnd()
+        {
+            ScrollEventArgs eventArgs = new ScrollEventArgs(mScrollingChild.CurrentPosition);
+            ScrollAnimationEndEvent?.Invoke(this, eventArgs);
+        }
+
+        private void OnScroll()
+        {
+            ScrollEventArgs eventArgs = new ScrollEventArgs(mScrollingChild.CurrentPosition);
+            ScrollEvent?.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);
+                    OnScrollAnimationEnd();
+                }
+                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)
+        {
+            Tizen.Log.Debug("ScrollableBase", "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;
+            OnScrollAnimationStart();
+            scrollAnimation.Play();
+        }
+
+        private void ScrollBy(float displacement, bool animate)
+        {
+            if (GetChildCount() == 0 || displacement == 0 || maxScrollDistance < 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
+
+            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);
+
+            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)
+        {
+            Tizen.Log.Debug("ScrollableBase", "axisVelocity:" + 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));
+
+            Tizen.Log.Debug("ScrollableBase", "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;
+
+                flickDisplacement = ((flickLength * multiplier) * speed) * (axisVelocity > 0.0f ? 1.0f : -1.0f);
+
+                Tizen.Log.Debug("ScrollableBase", "axisVelocity:" + axisVelocity);
+                Tizen.Log.Debug("ScrollableBase", "Calculated FlickDisplacement[" + flickDisplacement);
+                Tizen.Log.Debug("ScrollableBase", "speed[" + speed);
+                Tizen.Log.Debug("ScrollableBase", "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 Math.Max(scrollingChildLength - scrollerLength,0);
+        }
+
+        private void PageSnap()
+        {
+            Tizen.Log.Debug("ScrollableBase", "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
+                                                                " currentPage[" + CurrentPage + "]" );
+
+            var axisToCompare = ScrollingDirection == Direction.Horizontal ? mPageWidth : mPageHeight;
+
+            //Increment current page if total displacement enough to warrant a page change.
+            if (Math.Abs(totalDisplacementForPan) > (axisToCompare * ratioOfScreenWidthToCompleteScroll))
+            {
+                // if totalDisplacementForPan < 0 move index forward, backward otherwise
+                int pagesDiff = totalDisplacementForPan > 0.0f ? -1 : 1;
+                pagesDiff += (int)(-totalDisplacementForPan / axisToCompare);
+
+                Tizen.Log.Debug("ScrollableBase", $"totalDisplacement {totalDisplacementForPan}");
+                Tizen.Log.Debug("ScrollableBase", $"axisToCompare {axisToCompare}");
+                Tizen.Log.Debug("ScrollableBase", $"pages diff {pagesDiff}");
+                Tizen.Log.Debug("ScrollableBase", $"CurrentPge {CurrentPage}");
+                CurrentPage = Math.Clamp(CurrentPage + pagesDiff, 0, mScrollingChild.Children.Count - 1);
+                Tizen.Log.Debug("ScrollableBase", $"NextPage {CurrentPage}");
+            }
+
+            float destinationX;
+            // Animate to new page or reposition to current page
+            if (ScrollingDirection == Direction.Horizontal)
+            {
+                destinationX = -(mScrollingChild.Children[CurrentPage].Position.X + mScrollingChild.Children[CurrentPage].CurrentSize.Width/2 - CurrentSize.Width/2); // set to middle of current page
+                Tizen.Log.Debug("ScrollableBase", "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + mScrollingChild.PositionX);
+            } else
+            {
+                destinationX = -(mScrollingChild.Children[CurrentPage].Position.Y + mScrollingChild.Children[CurrentPage].CurrentSize.Height/2 - CurrentSize.Height/2); // set to middle of current page
+                Tizen.Log.Debug("ScrollableBase", "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + mScrollingChild.PositionY);
+            }
+            AnimateChildTo(ScrollDuration, destinationX);
+        }
+
+        private void Flick(float flickDisplacement)
+        {
+            Tizen.Log.Debug("ScrollableBase", $"flickDisplacement: {flickDisplacement}");
+          if (SnapToPage)
+          {
+              if ( ( flickWhenAnimating && scrolling == true) || ( scrolling == false) )
+              {
+                  float axisToCompare = ScrollingDirection == Direction.Horizontal ? mPageWidth : mPageHeight;
+
+                  int pagesDiff = (int)(-flickDisplacement / axisToCompare);
+                  Tizen.Log.Debug("ScrollableBase", $"CurrentPage: {CurrentPage}");
+                  Tizen.Log.Debug("ScrollableBase", $"pagasDiff: {pagesDiff}");
+
+                  CurrentPage = Math.Clamp(CurrentPage + pagesDiff, 0, mScrollingChild.Children.Count - 1);
+
+                  Tizen.Log.Debug("ScrollableBase", $"NextPage: {CurrentPage}");
+
+                  float destinationX;
+                  if (ScrollingDirection == Direction.Horizontal)
+                  {
+                    destinationX = -(mScrollingChild.Children[CurrentPage].Position.X + mScrollingChild.Children[CurrentPage].CurrentSize.Width/2.0f - CurrentSize.Width/2.0f); // set to middle of current page
+                  } else
+                  {
+                    destinationX = -(mScrollingChild.Children[CurrentPage].Position.Y + mScrollingChild.Children[CurrentPage].CurrentSize.Height/2.0f - CurrentSize.Height/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)
+        {
+            if (e.PanGesture.State == Gesture.StateType.Started)
+            {
+                Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
+                if (scrolling && !SnapToPage)
+                {
+                    StopScroll();
+                }
+                maxScrollDistance = CalculateMaximumScrollDistance();
+                totalDisplacementForPan = 0.0f;
+                OnScrollDragStart();
+            }
+            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);
+
+                flickDisplacement = CalculateDisplacementFromVelocity(axisVelocity);
+
+                Tizen.Log.Debug("ScrollableBase", "FlickDisplacement:" + flickDisplacement + "TotalDisplacementForPan:" + totalDisplacementForPan);
+                OnScrollDragEnd();
+
+                if (flickDisplacement > 0 | flickDisplacement < 0)// Flick detected
+                {
+                    Tizen.Log.Debug("ScrollableBase", "Flick detected from Pan");
+                    Flick(flickDisplacement);
+                }
+                else
+                {
+                    // End of panning gesture but was not a flick
+                    if (SnapToPage)
+                    {
+                        PageSnap();
+                    }
+                }
+                totalDisplacementForPan = 0;
+            }
+        }
+
+        private new 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;
+            OnScrollAnimationEnd();
+        }
+
+        private View eventsView = null;
+        public View EventsView
+        {
+            get
+            {
+                return eventsView;
+            }
+            set
+            {
+                if (eventsView)
+                {
+                    mPanGestureDetector.Detach(eventsView);
+                    mTapGestureDetector.Detach(eventsView);
+                }
+                eventsView = value;
+                mPanGestureDetector.Attach(value);
+                mTapGestureDetector.Attach(value);
+            }
+        }
+    }
+
+} // namespace
diff --git a/Oobe/OobeCommon/Styles/CarouselPickerStyles.cs b/Oobe/OobeCommon/Styles/CarouselPickerStyles.cs
new file mode 100644 (file)
index 0000000..15c3de0
--- /dev/null
@@ -0,0 +1,30 @@
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI;
+using Oobe.Common.Controls;
+
+namespace Oobe.Common.Styles
+{
+    public class CarouselPickerStyles
+    {
+        public static CarouselPickerStyle Default = new CarouselPickerStyle{
+            CenterText = new TextLabelStyle{
+                Size2D = new Size2D(312, 26),
+                PixelSize = 20.0f,
+                Margin = new Extents(24, 24, 16, 16),
+                HorizontalAlignment = HorizontalAlignment.Center,
+                TextColor = new Color(0, 20.0f / 255.0f, 71.0f / 255.0f, 1.0f),
+                Opacity = 1.0f,
+            },
+            OuterText = new TextLabelStyle{
+                Size2D = new Size2D(312, 26),
+                PixelSize = 20.0f,
+                Margin = new Extents(24, 24, 16, 16),
+                HorizontalAlignment = HorizontalAlignment.Center,
+                TextColor = new Color(195.0f / 255.0f, 202.0f / 255.0f, 210.0f / 255.0f, 1.0f),
+                Opacity = 0.4f,
+            },
+            LinesColor = new Color(0, 20.0f / 255.0f, 71.0f / 255.0f, 1.0f),
+        };
+    }
+}
diff --git a/Oobe/OobeCommon/Styles/DropDownStyles.cs b/Oobe/OobeCommon/Styles/DropDownStyles.cs
deleted file mode 100644 (file)
index 666ee96..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-using Tizen.NUI.Components;
-using Tizen.NUI.BaseComponents;
-using Tizen.NUI;
-
-namespace Oobe.Common.Styles
-{
-    public class DropDownStyles
-    {
-        public static DropDownStyle Default = new DropDownStyle{
-            Button = new ButtonStyle {
-                Text = new TextLabelStyle{
-                    TextColor = new Color(0,0,0,1),
-                    PointSize = 20,
-                    FontFamily = "BreezeSans",
-                    Position = new Position(28, 0),
-                    Size2D = new Size2D(360, 30),
-                },
-                Icon = new ImageViewStyle{
-                    ResourceUrl = NUIApplication.Current.DirectoryInfo.Resource  + "drop-down/list_ic_dropdown.png",
-                    Size = new Size(48, 48),
-                },
-                IconRelativeOrientation = Button.IconOrientation.Right,
-                PositionX = 56,
-            },
-            ListBackgroundImage = new ImageViewStyle{
-                ResourceUrl = NUIApplication.Current.DirectoryInfo.Resource + "drop-down/dropdown_bg.png",
-                Border = new Rectangle(51, 51, 51, 51),
-                Size = new Size(360, 500),
-            },
-            ListMargin = new Extents{
-                Start = 20,
-                Top = 20,
-            },
-            ListPadding = new Extents(4,4,4,4),
-            Size2D = new Size2D(360, 108),
-            Position2D = new Position2D(412, 242),
-            SpaceBetweenButtonTextAndIcon = 8,
-        };
-        public static DropDownItemStyle ItemDefault = new DropDownItemStyle{
-            Text = new TextLabelStyle{
-                PointSize = 20,
-                FontFamily = "BreezeSans",
-            },
-            CheckImage = new ImageViewStyle{
-                Size = new Size(40, 40),
-                ResourceUrl = NUIApplication.Current.DirectoryInfo.Resource + "drop-down/dropdown_checkbox_on.png",
-            },
-            CheckImageGapToBoundary = 16,
-            BackgroundColor = new Selector<Color>{
-                Pressed = new Color(0, 0, 0, 0.4f),
-                Other = new Color(1, 1, 1, 0),
-            },
-            Size = new Size(360, 96),
-        };
-    }
-}
\ No newline at end of file
diff --git a/Oobe/OobeCommon/Styles/ScrollBarStyles.cs b/Oobe/OobeCommon/Styles/ScrollBarStyles.cs
deleted file mode 100644 (file)
index 709b335..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using Tizen.NUI;
-using Tizen.NUI.BaseComponents;
-using Tizen.NUI.Components;
-
-namespace Oobe.Common.Styles
-{
-    public class ScrollBarStyles
-    {
-        public static ScrollBarStyle Default = new ScrollBarStyle{
-
-            Direction = ScrollBar.DirectionType.Vertical,
-            Position2D = new Position2D(394, 2),
-            Size2D = new Size2D(4, 446),
-            Track = new ImageViewStyle{
-                BackgroundColor = Color.Green,
-            },
-            Thumb = new ImageViewStyle{
-                Size = new Size(4, 30),
-                BackgroundColor = Color.Yellow,
-            }
-        };
-    }
-}
\ No newline at end of file
diff --git a/Oobe/OobeCommon/Utils/ColorUtils.cs b/Oobe/OobeCommon/Utils/ColorUtils.cs
new file mode 100644 (file)
index 0000000..4e6c30d
--- /dev/null
@@ -0,0 +1,98 @@
+using Tizen.NUI;
+using System;
+
+namespace Oobe.Common.Utils
+{
+    public static class ColorUtils
+    {
+        // source: https://www.cs.rit.edu/~ncs/color/t_convert.html
+        public static Vector3 ToHSV(this Color color)
+        {
+            float h, s, v;
+            float min, max, delta;
+
+            min = Math.Min(color.R, Math.Min(color.G, color.B));
+            max = Math.Max(color.R, Math.Max(color.G, color.B));
+            v = max;
+
+            if (max == min)
+            {
+                // in grayscale
+                return new Vector3(0.0f, 0.0f, v);
+            }
+
+            delta = max - min;
+            s = delta / max;           // s
+
+            if( color.R == max )
+                h = ( color.G - color.B ) / delta;             // between yellow & magenta
+            else if( color.G == max )
+                h = 2 + ( color.B - color.R ) / delta; // between cyan & yellow
+            else
+                h = 4 + ( color.R - color.G ) / delta; // between magenta & cyan
+
+            h *= 60;                           // degrees
+            if( h < 0 )
+                h += 360;
+
+            return new Vector3(h, s, v);
+        }
+
+        public static Color ColorFromHSV(Vector3 hsv)
+        {
+            int i;
+            float r, g, b;
+            float f, p, q, t;
+            float h = hsv[0];
+            float s = hsv[1];
+            float v = hsv[2];
+
+            if (s == 0) {
+                // achromatic (grey)
+                r = g = b = v;
+                return new Color(r, g, b, 1.0f);
+            }
+
+            h /= 60;                   // sector 0 to 5
+            i = (int)Math.Floor( h );
+            f = h - i;                 // factorial part of h
+            p = v * ( 1 - s );
+            q = v * ( 1 - s * f );
+            t = v * ( 1 - s * ( 1 - f ) );
+
+            switch( i ) {
+                case 0:
+                    r = v;
+                    g = t;
+                    b = p;
+                    break;
+                case 1:
+                    r = q;
+                    g = v;
+                    b = p;
+                    break;
+                case 2:
+                    r = p;
+                    g = v;
+                    b = t;
+                    break;
+                case 3:
+                    r = p;
+                    g = q;
+                    b = v;
+                    break;
+                case 4:
+                    r = t;
+                    g = p;
+                    b = v;
+                    break;
+                default:               // case 5:
+                    r = v;
+                    g = p;
+                    b = q;
+                    break;
+            }
+            return new Color(r, g, b, 1.0f);
+        }
+    }
+}
index 8cb50ea..157b02f 100644 (file)
@@ -4,6 +4,8 @@ using Tizen.NUI;
 using Tizen.NUI.Components;\r
 using Tizen.NUI.BaseComponents;\r
 using Oobe.Language.Model;\r
+using Oobe.Common.Controls;\r
+using Oobe.Common.Utils;\r
 \r
 namespace Oobe.Language\r
 {\r
@@ -34,24 +36,23 @@ namespace Oobe.Language
             title.FontFamily = "BreezeSans";\r
             title.FontStyle = FontsStyles.Light();\r
 \r
-            var dropDown = new DropDown(DropDownStyles.Default);\r
+            var carousel = new CarouselPicker(CarouselPickerStyles.Default);\r
+            carousel.Position2D = new Position2D(412, 242);\r
+            carousel.Size2D = new Size2D(360, 249);\r
 \r
             foreach (LanguageInfo info in manager.Languages)\r
             {\r
-                DropDown.DropDownDataItem item = new DropDown.DropDownDataItem(DropDownStyles.ItemDefault);\r
+                CarouselPickerItemData item = new CarouselPickerItemData();\r
                 item.Text = info.LocalName;\r
-                item.TextPosition = new Position(28, 0);\r
-                dropDown.AddItem(item);\r
+                carousel.AddItem(item);\r
             }\r
-            var scrollBar = new ScrollBar(ScrollBarStyles.Default);\r
-            dropDown.AttachScrollBar(scrollBar);\r
 \r
             Button btn = new Button(ButtonStyles.Next);\r
             btn.Position2D = new Position2D(888, 512);\r
             btn.ClickEvent += (obj, args) => {\r
-                if (dropDown.SelectedItemIndex >= 0 && dropDown.SelectedItemIndex < manager.Languages.Count)\r
+                if (carousel.SelectedItemIndex >= 0 && carousel.SelectedItemIndex < manager.Languages.Count)\r
                 {\r
-                    var lang = manager.Languages[dropDown.SelectedItemIndex];\r
+                    var lang = manager.Languages[carousel.SelectedItemIndex];\r
                     manager.CurrentLanguage = lang;\r
                 }\r
                 nav.Next();\r
@@ -59,9 +60,7 @@ namespace Oobe.Language
 \r
             container.Add(title);\r
             container.Add(btn);\r
-            container.Add(dropDown);\r
-\r
-            dropDown.SelectedItemIndex = 1;\r
+            container.Add(carousel);\r
 \r
             return container;\r
          }\r
index d183083..ad376e4 100644 (file)
@@ -4,6 +4,7 @@ using Tizen.NUI.BaseComponents;
 using Oobe.Common.Interfaces;\r
 using Tizen.NUI.Components;\r
 using Oobe.Region.Model;\r
+using Oobe.Common.Controls;\r
 \r
 namespace Oobe.Region\r
 {\r
@@ -34,24 +35,23 @@ namespace Oobe.Region
             title.FontFamily = "BreezeSans";\r
             title.FontStyle = FontsStyles.Light();\r
 \r
-            var dropDown = new DropDown(DropDownStyles.Default);\r
+            var carousel = new CarouselPicker(CarouselPickerStyles.Default);\r
+            carousel.Position2D = new Position2D(412, 242);\r
+            carousel.Size2D = new Size2D(360, 249);\r
 \r
             foreach (RegionInfo info in manager.Regions)\r
             {\r
-                DropDown.DropDownDataItem item = new DropDown.DropDownDataItem(DropDownStyles.ItemDefault);\r
+                CarouselPickerItemData item = new CarouselPickerItemData();\r
                 item.Text = info.Name;\r
-                item.TextPosition = new Position(28, 0);\r
-                dropDown.AddItem(item);\r
+                carousel.AddItem(item);\r
             }\r
-            var scrollBar = new ScrollBar(ScrollBarStyles.Default);\r
-            dropDown.AttachScrollBar(scrollBar);\r
 \r
             Button next = new Button(ButtonStyles.Next);\r
             next.Position2D = new Position2D(888, 512);\r
             next.ClickEvent += (obj, args) => {\r
-                if (dropDown.SelectedItemIndex >= 0 && dropDown.SelectedItemIndex < manager.Regions.Count)\r
+                if (carousel.SelectedItemIndex >= 0 && carousel.SelectedItemIndex < manager.Regions.Count)\r
                 {\r
-                    var region = manager.Regions[dropDown.SelectedItemIndex];\r
+                    var region = manager.Regions[carousel.SelectedItemIndex];\r
                     manager.CurrentRegion = region;\r
                 }\r
                 nav.Next();\r
@@ -63,13 +63,11 @@ namespace Oobe.Region
                 nav.Previous();\r
             };\r
 \r
-            container.Add(dropDown);\r
+            container.Add(carousel);\r
             container.Add(title);\r
             container.Add(prev);\r
             container.Add(next);\r
 \r
-            dropDown.SelectedItemIndex = 1;\r
-\r
             return container;\r
          }\r
      }\r