[NUI] Introduce NUI Picker (#2769)
authorWoochan <48237284+lwc0917@users.noreply.github.com>
Wed, 31 Mar 2021 07:16:46 +0000 (16:16 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Thu, 1 Apr 2021 01:07:37 +0000 (10:07 +0900)
Co-authored-by: Woochanlee <wc0917.lee@samsung.com>
Co-authored-by: Jiyun Yang <ji.yang@samsung.com>
src/Tizen.NUI.Components/Controls/Picker.cs [new file with mode: 0755]
src/Tizen.NUI.Components/Style/PickerStyle.cs [new file with mode: 0755]
src/Tizen.NUI.Components/Theme/DefaultTheme.cs
src/Tizen.NUI.Components/Theme/DefaultThemeCommon.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/PickerSample.cs [new file with mode: 0755]

diff --git a/src/Tizen.NUI.Components/Controls/Picker.cs b/src/Tizen.NUI.Components/Controls/Picker.cs
new file mode 100755 (executable)
index 0000000..08da631
--- /dev/null
@@ -0,0 +1,586 @@
+/* Copyright (c) 2021 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;
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// ValueChangedEventArgs is a class to notify changed Picker value argument which will sent to user.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class ValueChangedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// ValueChangedEventArgs default constructor.
+        /// <param name="value">value of Picker.</param>
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]   
+        public ValueChangedEventArgs(int value)
+        {
+            Value = value;
+        }
+
+        /// <summary>
+        /// ValueChangedEventArgs default constructor.
+        /// <returns>The current value of Picker.</returns>
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]   
+        public int Value { get; }
+        
+    }
+
+    /// <summary>
+    /// Picker is a class which provides a function that allows the user to select 
+    /// a value through a scrolling motion by expressing the specified value as a list.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class Picker : Control
+    {
+        //Tizen 6.5 base components Picker guide visible scroll item is 5.
+        private const int scrollVisibleItems = 5;
+        //Dummy item count for loop feature. Max value of scrolling distance in 
+        //RPI target is bigger than 20 items height. it can adjust depends on the internal logic and device env.
+        private const int dummyItemsForLoop = 20;             
+        private int startScrollOffset;
+        private int itemHeight;
+        private int startScrollY;
+        private int startY;
+        private int pageSize;
+        private int currentValue;
+        private int maxValue;
+        private int minValue;
+        private int lastScrollPosion;
+        private bool onAnimation; //Scroller on animation check.
+        private bool displayedValuesUpdate; //User sets displayed value check.
+        private bool needItemUpdate; //min, max or display value updated check.
+        private bool loopEnabled;
+        private ReadOnlyCollection<string> displayedValues;
+        private PickerScroller pickerScroller;
+        private View upLine;
+        private View downLine;
+        private IList<TextLabel> itemList;
+        private PickerStyle pickerStyle => ViewStyle as PickerStyle;
+
+        /// <summary>
+        /// Creates a new instance of Picker.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Picker()
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of Picker.
+        /// </summary>
+        /// <param name="style">Creates Picker by special style defined in UX.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Picker(string style) : base(style)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Creates a new instance of Picker.
+        /// </summary>
+        /// <param name="pickerStyle">Creates Picker by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Picker(PickerStyle pickerStyle) : base(pickerStyle)
+        {
+            Initialize();
+        }
+
+        /// <summary>
+        /// Dispose Picker and all children on it.
+        /// </summary>
+        /// <param name="type">Dispose type.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                if (itemList != null)
+                {
+                    foreach (TextLabel textLabel in itemList)
+                    {
+                        if (pickerScroller) pickerScroller.Remove(textLabel);
+                        Utility.Dispose(textLabel);
+                    }
+
+                    itemList = null;
+                }
+
+                if (pickerScroller != null)
+                {
+                    Remove(pickerScroller);
+                    Utility.Dispose(pickerScroller);
+                    pickerScroller = null;
+                }
+
+                Remove(upLine);
+                Utility.Dispose(upLine);
+                Remove(downLine);
+                Utility.Dispose(downLine);
+            }
+
+            base.Dispose(type);
+        }
+
+        /// <summary>
+        /// An event emitted when Picker value changed, user can subscribe or unsubscribe to this event handler.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public event EventHandler<ValueChangedEventArgs> ValueChanged;
+
+        //TODO Fomatter here
+
+        /// <summary>
+        /// The values to be displayed instead of numbers.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ReadOnlyCollection<String> DisplayedValues
+        {
+            get
+            {
+                return displayedValues;
+            }
+            set
+            {
+                displayedValues = value;
+
+                needItemUpdate = true;
+                displayedValuesUpdate = true;
+
+                UpdateValueList();
+            }
+        }
+        
+        /// <summary>
+        /// The Current value of Picker.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int CurrentValue
+        {
+            get
+            {
+                return currentValue;
+            }
+            set
+            {
+                if (currentValue == value) return;
+
+                if (currentValue < minValue) currentValue = minValue;
+                else if (currentValue > maxValue) currentValue = maxValue;
+
+                currentValue = value;
+
+                UpdateCurrentValue();
+            }
+        }
+
+        /// <summary>
+        /// The max value of Picker.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int MaxValue
+        {
+            get
+            {
+                return maxValue;
+            }
+            set
+            {
+                if (maxValue == value) return;
+                if (currentValue > value) currentValue = value;
+                
+                maxValue = value;
+                needItemUpdate = true;
+
+                UpdateValueList();
+            }
+        }
+
+        /// <summary>
+        /// The min value of Picker.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int MinValue
+        {
+            get
+            {
+                return minValue;
+            }
+            set
+            {
+                if (minValue == value) return;
+                if (currentValue < value) currentValue = value;
+                
+                minValue = value;
+                needItemUpdate = true;
+
+                UpdateValueList();
+            }
+        }
+
+        /// <summary>
+        /// Applies style to Picker.
+        /// </summary>
+        /// <param name="viewStyle">The style to apply.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void ApplyStyle(ViewStyle viewStyle)
+        {
+            base.ApplyStyle(viewStyle);
+
+            //Apply StartScrollOffset style.
+            if (pickerStyle?.StartScrollOffset != null)
+                startScrollOffset = (int)pickerStyle.StartScrollOffset.Height;
+
+            //Apply ItemTextLabel style.
+            if (pickerStyle?.ItemTextLabel != null)
+            {
+                itemHeight = (int)pickerStyle.ItemTextLabel.Size.Height;
+
+                if (itemList != null)
+                    foreach (TextLabel textLabel in itemList)
+                        textLabel.ApplyStyle(pickerStyle.ItemTextLabel);
+            }
+
+            //Apply PickerCenterLine style.
+            if (pickerStyle?.Divider != null && upLine != null && downLine != null)
+            {
+                upLine.ApplyStyle(pickerStyle.Divider);
+                downLine.ApplyStyle(pickerStyle.Divider);
+                downLine.PositionY = (int)pickerStyle.Divider.PositionY + itemHeight;
+            }
+        }
+                
+        private void Initialize()
+        {
+            HeightSpecification = LayoutParamPolicies.MatchParent;
+
+            //Picker Using scroller internally. actually it is a kind of scroller which has infinity loop,
+            //and item center align features.
+            pickerScroller = new PickerScroller(pickerStyle)
+            {
+                Size = new Size(-1, pickerStyle.Size.Height),
+                ScrollingDirection = ScrollableBase.Direction.Vertical,
+                Layout = new LinearLayout()
+                {
+                    LinearOrientation = LinearLayout.Orientation.Vertical,
+                },
+                //FIXME: Need to expand as many as possible;
+                //       When user want to start list middle of the list item. currently confused how to create list before render.
+                ScrollAvailableArea = new Vector2(0, 10000),
+                Name = "pickerScroller",
+            };
+            pickerScroller.Scrolling += OnScroll;
+            pickerScroller.ScrollAnimationEnded += OnScrollAnimationEnded;
+            pickerScroller.ScrollAnimationStarted += OnScrollAnimationStarted;
+
+            itemList = new List<TextLabel>();
+            
+            minValue = maxValue = currentValue = 0;
+            displayedValues = null;
+            //Those many flags for min, max, value method calling sequence dependency.
+            needItemUpdate = true;
+            displayedValuesUpdate = false;
+            onAnimation = false;
+            loopEnabled = false;
+
+            startScrollOffset = (int)pickerStyle.StartScrollOffset.Height;
+            itemHeight = (int)pickerStyle.ItemTextLabel.Size.Height;
+            startScrollY = (itemHeight * dummyItemsForLoop) + startScrollOffset;
+            startY = startScrollOffset;
+
+            Add(pickerScroller);
+            AddLine();
+        }
+
+        private void OnValueChanged()
+        { 
+            ValueChangedEventArgs eventArgs =
+                new ValueChangedEventArgs(displayedValuesUpdate ? Int32.Parse(itemList[currentValue].Name) : Int32.Parse(itemList[currentValue].Text));
+            ValueChanged?.Invoke(this, eventArgs);
+        }
+
+        private void OnScroll(object sender, ScrollEventArgs e)
+        {
+            //Ignore if the scrolling animation happened.
+            if (!loopEnabled || onAnimation) return;
+            
+            //Check the scroll is going out to the dummys if so, bring it back to page.
+            if (e.Position.Y > -(startScrollY - (itemHeight * 2))) 
+                pickerScroller.ScrollTo(-e.Position.Y + pageSize, false);
+            else if (e.Position.Y < -(startScrollY + pageSize - (itemHeight * 2)))
+                pickerScroller.ScrollTo(-e.Position.Y - pageSize, false);
+        }
+
+        private void OnScrollAnimationStarted(object sender, ScrollEventArgs e)
+        {
+            onAnimation = true;
+        }
+
+        private void OnScrollAnimationEnded(object sender, ScrollEventArgs e)
+        {
+            //Ignore if the scroll position was not changed. (called it from this function)
+            if (lastScrollPosion == (int)e.Position.Y) return;
+
+            //Calc offset from closest item.
+            int offset = (int)(e.Position.Y + startScrollOffset) % itemHeight;
+            if (offset < -(itemHeight / 2)) offset += itemHeight;
+
+            lastScrollPosion = (int)(-e.Position.Y + offset);
+
+            //Item center align with animation, otherwise changed event emit.
+            if (offset != 0) 
+                pickerScroller.ScrollTo(-e.Position.Y + offset, true);
+            else {
+                currentValue = ((int)(-e.Position.Y / itemHeight) + 2);
+                OnValueChanged();
+            }
+
+            onAnimation = false;
+        }
+
+        //This is UI requirement. It helps where exactly center item is.
+        private void AddLine()
+        {
+            upLine = new View(pickerStyle.Divider);
+            downLine = new View(pickerStyle.Divider)
+            {
+                Position = new Position(0, (int)pickerStyle.Divider.PositionY + itemHeight),
+            };
+
+            Add(upLine);
+            Add(downLine);
+        }
+
+        private String GetItemText(bool loopEnabled, int idx)
+        {
+            if (!loopEnabled) return " ";
+            else {
+                if (displayedValuesUpdate) {
+                    idx = idx - MinValue;
+                    if (idx <= displayedValues.Count) {
+                        return displayedValues[idx];
+                    }
+                    return " ";
+                }
+
+                return idx.ToString();
+            }
+        }
+
+        //FIXME: If textVisual can add in scroller please change it to textVisual for performance
+        [SuppressMessage("Microsoft.Reliability",
+                         "CA2000:DisposeObjectsBeforeLosingScope",
+                         Justification = "The items are added to itemList and are disposed in Picker.Dispose().")]
+        private void AddPickerItem(bool loopEnabled, int idx)
+        {
+            TextLabel temp = new TextLabel(pickerStyle.ItemTextLabel)
+            {
+                WidthSpecification = LayoutParamPolicies.MatchParent,
+                Text = GetItemText(loopEnabled, idx),
+                Name = idx.ToString(),
+            };
+
+            itemList.Add(temp);
+            pickerScroller.Add(temp);
+        }
+
+        private void UpdateCurrentValue()
+        {
+            // -2 for center align
+            int startItemIdx = (currentValue == 0) ? -2 : currentValue - minValue - 2;
+
+            if (loopEnabled) startY = ((dummyItemsForLoop + startItemIdx) * itemHeight) + startScrollOffset;
+            // + 2 for non loop picker center align
+            else startY = ((2 + startItemIdx) * itemHeight) + startScrollOffset;
+            pickerScroller.ScrollTo(startY, false);
+        }
+
+        private void UpdateValueList()
+        {
+            if (!needItemUpdate) return;
+            if (minValue > maxValue) return;
+
+            //FIXME: This is wrong.
+            //       But scroller can't update item property after added please fix me.
+            if (itemList.Count > 0) {
+                itemList.Clear();
+                pickerScroller.RemoveAllChildren();
+            }
+
+            if (maxValue - minValue + 1 >= scrollVisibleItems)
+            {
+                loopEnabled = true;
+                //Current scroller can't add at specific index.
+                //So need below calc.
+                int dummyStartIdx = 0;
+                if (maxValue - minValue >= dummyItemsForLoop)
+                  dummyStartIdx = maxValue - dummyItemsForLoop + 1;
+                else
+                  dummyStartIdx = maxValue - (dummyItemsForLoop % (maxValue - minValue + 1)) + 1;
+
+                //Start add items in scroller. first dummys for scroll anim.
+                for (int i = 0; i < dummyItemsForLoop; i++)
+                {
+                    if (dummyStartIdx > maxValue) dummyStartIdx = minValue;
+                    AddPickerItem(loopEnabled, dummyStartIdx++);
+                }
+                //Second real items.
+                for (int i = minValue; i <= maxValue; i++)
+                {
+                    AddPickerItem(loopEnabled, i);
+                }
+                //Last dummys for scroll anim.
+                dummyStartIdx = minValue;
+                for (int i = 0; i < dummyItemsForLoop; i++)
+                {
+                    if (dummyStartIdx > maxValue) dummyStartIdx = minValue;
+                    AddPickerItem(loopEnabled, dummyStartIdx++);
+                }
+            }
+            else
+            {
+                loopEnabled = false;
+
+                for (int i = 0; i < 2; i++)
+                    AddPickerItem(loopEnabled, 0);
+                for (int i = minValue; i <= maxValue; i++)
+                    AddPickerItem(!loopEnabled, i);
+                for (int i = 0; i < 2; i++)
+                    AddPickerItem(loopEnabled, 0);
+
+            }
+            pageSize = itemHeight * (maxValue - minValue + 1);
+
+            UpdateCurrentValue();
+
+            //Give a correct scroll area.
+            pickerScroller.ScrollAvailableArea = new Vector2(0, (itemList.Count * itemHeight) - pickerStyle.Size.Height);
+
+            needItemUpdate = false;
+        }
+
+        internal class PickerScroller : ScrollableBase
+        {
+            private int itemHeight;
+            private int startScrollOffset;
+            private float velocityOfLastPan = 0.0f;
+            private float panAnimationDuration = 0.0f;
+            private float panAnimationDelta = 0.0f;
+            private float decelerationRate = 0.0f;
+            private float logValueOfDeceleration = 0.0f;
+            private delegate float UserAlphaFunctionDelegate(float progress);
+            private UserAlphaFunctionDelegate customScrollAlphaFunction;
+
+            public PickerScroller(PickerStyle pickerStyle) : base()
+            {
+                //Default rate is 0.998. this is for reduce scroll animation length.
+                decelerationRate = 0.994f;
+                startScrollOffset = (int)pickerStyle.StartScrollOffset.Height;
+                itemHeight = (int)pickerStyle.ItemTextLabel.Size.Height;
+                logValueOfDeceleration = (float)Math.Log(decelerationRate);
+            }
+
+            private float CustomScrollAlphaFunction(float progress)
+            {
+                if (panAnimationDelta == 0)
+                {
+                    return 1.0f;
+                }
+                else
+                {
+                    // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
+                    // Can get real distance using equation of deceleration (check Decelerating function)
+                    // After get real distance, normalize it
+                    float realDuration = progress * panAnimationDuration;
+                    float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
+                    float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
+
+                    return result;
+                }
+            }
+
+            //Override Decelerating for Picker feature.
+            protected override void Decelerating(float velocity, Animation animation)
+            {
+                velocityOfLastPan = Math.Abs(velocity);
+
+                float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
+                panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
+                panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
+
+                float destination = -(panAnimationDelta + currentScrollPosition);
+                //Animation destination has to center of the item.
+                float align = destination % itemHeight;
+                destination -= align;
+                destination -= startScrollOffset;
+
+                float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
+
+                float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : ScrollAvailableArea.Y;
+                float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
+
+                if (destination < -maxPosition || destination > minPosition)
+                {
+                    panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
+                    destination = velocity > 0 ? minPosition : -maxPosition;
+                    destination = -maxPosition + itemHeight;
+
+                    if (panAnimationDelta == 0)
+                    {
+                        panAnimationDuration = 0.0f;
+                    }
+                    else
+                    {
+                        panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
+                    }
+                }
+                else
+                {
+                    panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
+
+                    if (adjustDestination != destination)
+                    {
+                        destination = adjustDestination;
+                        panAnimationDelta = destination + currentScrollPosition;
+                        velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
+                        panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
+                    }
+                }
+
+                customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
+                animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
+                animation.Duration = (int)panAnimationDuration;
+                animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", (int)destination);
+                animation.Play();
+            }
+        }
+    }
+}
diff --git a/src/Tizen.NUI.Components/Style/PickerStyle.cs b/src/Tizen.NUI.Components/Style/PickerStyle.cs
new file mode 100755 (executable)
index 0000000..70965fe
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Components
+{
+    /// <summary>
+    /// PickerStyle is a class which saves PickerStyle's ux data.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class PickerStyle : ControlStyle
+    {
+        /// <summary>
+        /// Creates a new instance of a PickerStyle.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public PickerStyle() : base()
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of a PickerStyle with style.
+        /// </summary>
+        /// <param name="style">Creates PickerStyle by style customized by user.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public PickerStyle(PickerStyle style) : base(style)
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the PickerStyle Item TextLabel style.
+        /// This style is applied if PickerStyle Item is a TextLabel.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TextLabelStyle ItemTextLabel { get; set; } = new TextLabelStyle();
+
+        /// <summary>
+        /// Gets or sets the PickerStyle Center line style.
+        /// </summary>
+        public ViewStyle Divider { get; set;} = new ViewStyle();
+        
+        /// <summary>
+        /// Gets or sets the PickerStyle Item list start offset value.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Size2D StartScrollOffset { get; set; } = new Size2D();
+
+        /// <summary>
+        /// Style's clone function.
+        /// </summary>
+        /// <param name="bindableObject">The style that needs to copy.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override void CopyFrom(BindableObject bindableObject)
+        {
+            base.CopyFrom(bindableObject);
+
+            if (bindableObject is PickerStyle pickerStyle)
+            {
+                ItemTextLabel.CopyFrom(pickerStyle.ItemTextLabel);
+                Divider.CopyFrom(pickerStyle.Divider);
+                StartScrollOffset = (pickerStyle.StartScrollOffset == null) ?
+                                    new Size2D() : new Size2D(pickerStyle.StartScrollOffset.Width, pickerStyle.StartScrollOffset.Height);
+            }
+        }
+    }
+}
index 3980320..d501a06 100755 (executable)
@@ -105,6 +105,13 @@ namespace Tizen.NUI.Components
 
                 // AppBar
                 (new ExternalThemeKeyList(typeof(AppBar), typeof(AppBarStyle))),
+
+                // Picker
+                (new ExternalThemeKeyList(typeof(Picker), typeof(PickerStyle)))
+                    .AddSelector("/ItemTextLabel/Background", (ViewStyle style, Selector<Color> value) => ((PickerStyle)style).ItemTextLabel.BackgroundColor = value)
+                    .AddSelector("/ItemTextLabel/TextColor", (ViewStyle style, Selector<Color> value) => ((PickerStyle)style).ItemTextLabel.TextColor = value)
+                    .AddSelector("/ItemTextLabel/PixelSize", (ViewStyle style, Selector<float?> value) => ((PickerStyle)style).ItemTextLabel.PixelSize = value)
+                    .AddSelector("/Divider/Background", (ViewStyle style, Selector<Color> value) => ((PickerStyle)style).Divider.BackgroundColor = value),
             };
 
             return actionSet;
index 721c0c8..f1ce065 100755 (executable)
@@ -409,6 +409,32 @@ namespace Tizen.NUI.Components
                 ActionCellPadding = new Size2D(40, 0),
             });
 
+            theme.AddStyleWithoutClone("Tizen.NUI.Components.Picker", new PickerStyle()
+            {
+                Size = new Size(160, 339),
+                ItemTextLabel = new TextLabelStyle()
+                {
+                    //FIXME: Should be check PointSize. given size from UX is too large.
+                    PixelSize = 32,
+                    VerticalAlignment = VerticalAlignment.Center,
+                    HorizontalAlignment = HorizontalAlignment.Center,
+                    Size = new Size(0,72),
+                    TextColor = new Selector<Color>()
+                    {
+                        Normal = new Color("#000C2BFF"),
+                    },
+                    BackgroundColor = Color.White,
+                },
+                Divider = new ViewStyle()
+                {
+                    SizeHeight = 2.0f,
+                    WidthResizePolicy = ResizePolicyType.FillToParent,
+                    Position = new Position(0, 132),
+                    BackgroundColor = new Color("#0A0E4AFF"),
+                },
+                StartScrollOffset = new Size2D(0, 12),
+            });
+
             return theme;
         }
     }
diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/PickerSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/PickerSample.cs
new file mode 100755 (executable)
index 0000000..5dd0231
--- /dev/null
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using System.Collections.ObjectModel;
+
+namespace Tizen.NUI.Samples
+{
+    public class PickerSample : IExample
+    {
+        private static int pickerWidth = 160;
+        private static int pickerHeight = 339;
+        private Window window;
+        private Picker picker;
+
+        private void onValueChanged(object sender, ValueChangedEventArgs e)
+        {
+            Console.WriteLine("Value is " + e.Value);
+        }
+
+        public void Activate()
+        {
+            window = NUIApplication.GetDefaultWindow();
+            window.BackgroundColor = Color.White;
+
+            String[] textValue = new string[] { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"};
+
+            picker = new Picker()
+            {
+                Size = new Size(pickerWidth, pickerHeight),
+                Position = new Position(Window.Instance.Size.Width / 2 - pickerWidth / 2, Window.Instance.Size.Height/ 2 - pickerHeight / 2),
+                MinValue = 1,
+                MaxValue = 10,
+                CurrentValue = 3,
+                DisplayedValues = new ReadOnlyCollection<string>(textValue),
+            };
+            picker.ValueChanged += onValueChanged;
+            window.Add(picker);
+        }
+        public void Deactivate()
+        {
+            window.Remove(picker);
+            picker.Dispose();
+            picker = null;
+        }
+    }
+}