[NUI] Add license, delete unnecessary code(public)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / BaseComponents / Style / Selector.cs
index bd2096b..34c44a6 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright(c) 2019 Samsung Electronics Co., Ltd.
+ * 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.
  * limitations under the License.
  *
  */
+using System;
+using System.Collections.Generic;
 using System.ComponentModel;
 using Tizen.NUI.Binding;
-using Tizen.NUI.Components;
 
 namespace Tizen.NUI.BaseComponents
 {
     /// <summary>
     /// Selector class, which is related by Control State, it is base class for other Selector.
     /// </summary>
+    /// <typeparam name="T">The property type of the selector. if it's reference type, it should be of type <see cref="ICloneable"/> that implement deep copy in <see cref="ICloneable.Clone"/>.</typeparam>
     /// <since_tizen> 6 </since_tizen>
     /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
     [EditorBrowsable(EditorBrowsableState.Never)]
-    public class Selector<T> : BindableObject
+    public class Selector<T>
     {
+        private readonly bool cloneable = typeof(ICloneable).IsAssignableFrom(typeof(T));
+
+        /// <summary>
+        /// The list for adding <see cref="SelectorItem{T}"/>.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        List<SelectorItem<T>> SelectorItems { get; set; } = new List<SelectorItem<T>>();
+
+        /// <summary>
+        /// Adds the specified state and value to the <see cref="SelectorItems"/>.
+        /// </summary>
+        /// <param name="state">The state.</param>
+        /// <param name="value">The value associated with state.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Add(ControlState state, T value)
+        {
+            SelectorItems.Add(new SelectorItem<T>(state, value));
+            All = default;
+        }
+
+        /// <summary>
+        /// Adds the specified state and value to the <see cref="SelectorItems"/>.
+        /// </summary>
+        /// <param name="selectorItem">The selector item class that stores a state-value pair.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Add(SelectorItem<T> selectorItem)
+        {
+            // To prevent a state from having multiple values, remove existing state-value pair.
+            int index = SelectorItems.FindIndex(x => x.State == selectorItem.State);
+            if (index != -1)
+                SelectorItems.RemoveAt(index);
+
+            SelectorItems.Add(selectorItem);
+        }
+
         /// <since_tizen> 6 </since_tizen>
         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
         public static implicit operator Selector<T>(T value)
         {
-            Selector<T> selector = new Selector<T>();
-            selector.All = value;
-            return selector;
+            return new Selector<T>(value);
         }
 
         /// Default Contructor
@@ -47,7 +82,14 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public Selector(T value) : this()
         {
-            All = value;
+            All = cloneable ? (T)((ICloneable)value)?.Clone() : value;
+        }
+
+        /// Copy constructor
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Selector(Selector<T> value) : this()
+        {
+            Clone(value);
         }
 
         /// <summary>
@@ -69,8 +111,8 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T Normal
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.Normal).Value;
+            set => Add(ControlState.Normal, value);
         }
         /// <summary>
         /// Pressed State.
@@ -80,8 +122,8 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T Pressed
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.Pressed).Value;
+            set => Add(ControlState.Pressed, value);
         }
         /// <summary>
         /// Focused State.
@@ -91,8 +133,8 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T Focused
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.Focused).Value;
+            set => Add(ControlState.Focused, value);
         }
         /// <summary>
         /// Selected State.
@@ -102,8 +144,8 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T Selected
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.Selected).Value;
+            set => Add(ControlState.Selected, value);
         }
         /// <summary>
         /// Disabled State.
@@ -113,8 +155,8 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T Disabled
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.Disabled).Value;
+            set => Add(ControlState.Disabled, value);
         }
         /// <summary>
         /// DisabledFocused State.
@@ -124,8 +166,8 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T DisabledFocused
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.DisabledFocused).Value;
+            set => Add(ControlState.DisabledFocused, value);
         }
         /// <summary>
         /// SelectedFocused State.
@@ -134,8 +176,8 @@ namespace Tizen.NUI.BaseComponents
         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
         public T SelectedFocused
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.SelectedFocused).Value;
+            set => Add(ControlState.SelectedFocused, value);
         }
         /// <summary>
         /// DisabledSelected State.
@@ -145,9 +187,10 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T DisabledSelected
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.DisabledSelected).Value;
+            set => Add(ControlState.DisabledSelected, value);
         }
+
         /// <summary>
         /// Other State.
         /// </summary>
@@ -156,238 +199,353 @@ namespace Tizen.NUI.BaseComponents
         [EditorBrowsable(EditorBrowsableState.Never)]
         public T Other
         {
-            get;
-            set;
+            get => SelectorItems.Find(x => x.State == ControlState.Other).Value;
+            set => Add(ControlState.Other, value);
         }
+
+        /// <summary>
+        /// Gets the number of elements.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public int Count => SelectorItems.Count;
+
         /// <summary>
         /// Get value by State.
         /// </summary>
+        /// <exception cref="ArgumentNullException"> Thrown when state is null. </exception>
         /// <since_tizen> 6 </since_tizen>
         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+        /// <returns>True if the selector has a given state value, false otherwise.</returns>
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public T GetValue(ControlStates state)
+        public bool GetValue(ControlState state, out T result)
         {
-            if(All != null)
+            if (All != null)
+            {
+                result = All;
+
+                return true;
+            }
+
+            if (state == null)
+                throw new ArgumentNullException(nameof(state));
+
+            result = default;
+
+            int index = SelectorItems.FindIndex(x => x.State == state);
+            if (index >= 0)
+            {
+                result = SelectorItems[index].Value;
+                return true;
+            }
+
+            if (null == state)
+            {
+                throw new ArgumentNullException(nameof(state));
+            }
+            if (state.IsCombined)
             {
-                return All;
+                index = SelectorItems.FindIndex(x => state.Contains(x.State));
+                if (index >= 0)
+                {
+                    result = SelectorItems[index].Value;
+                    return true;
+                }
             }
-            switch(state)
+
+            index = SelectorItems.FindIndex(x => x.State == ControlState.Other);
+            if (index >= 0)
             {
-                case ControlStates.Normal:
-                    return Normal != null? Normal : Other;
-                case ControlStates.Focused:
-                    return Focused != null? Focused : Other;
-                case ControlStates.Pressed:
-                    return Pressed != null? Pressed : Other;
-                case ControlStates.Disabled:
-                    return Disabled != null? Disabled : Other;
-                case ControlStates.Selected:
-                    return Selected != null? Selected : Other;
-                case ControlStates.DisabledFocused:
-                    return DisabledFocused != null? DisabledFocused : Other;
-                case ControlStates.DisabledSelected:
-                    return DisabledSelected != null? DisabledSelected : Other;
-                case ControlStates.SelectedFocused:
-                    return SelectedFocused != null ? SelectedFocused : Other;
-                default:
-                    return Other;
+                result = SelectorItems[index].Value;
+                return true;
             }
+
+            return false;
         }
+
         /// <summary>
-        /// Clone function.
+        /// Removes all elements.
         /// </summary>
-        /// <since_tizen> 6 </since_tizen>
-        /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public void Clone(Selector<T> selector)
+        public void Clear()
         {
-            All = selector.All;
-            Normal = selector.Normal;
-            Focused = selector.Focused;
-            Pressed = selector.Pressed;
-            Disabled = selector.Disabled;
-            Selected = selector.Selected;
-            DisabledSelected = selector.DisabledSelected;
-            DisabledFocused = selector.DisabledFocused;
-            SelectedFocused = selector.SelectedFocused;
-            Other = selector.Other;
+            All = default;
+            SelectorItems.Clear();
         }
 
-        internal void Clone<U>(Selector<U> other) where U : T, Tizen.NUI.Internal.ICloneable
+        /// <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override string ToString()
         {
-            // TODO Apply constraint to the Selector (not to Clone method)
-
-            All = (T)(other.All)?.Clone();
-            Normal = (T)(other.Normal)?.Clone();
-            Focused = (T)(other.Focused)?.Clone();
-            Pressed = (T)(other.Pressed)?.Clone();
-            Disabled = (T)(other.Disabled)?.Clone();
-            Selected = (T)(other.Selected)?.Clone();
-            DisabledSelected = (T)(other.DisabledSelected)?.Clone();
-            DisabledFocused = (T)(other.DisabledFocused)?.Clone();
-            SelectedFocused = (T)(other.SelectedFocused)?.Clone();
-            Other = (T)(other.Other)?.Clone();
-        }
-    }
+            string result = $"[All, {All}]";
 
-    /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    public class TriggerableSelector<T> : Selector<T>
-    {
-        public TriggerableSelector(View view, BindableProperty bindableProperty)
-        {
-            targetView = view;
-            targetBindableProperty = bindableProperty;
-            view.ControlStateChangeEvent += OnViewControlState;
+            foreach (var item in SelectorItems)
+            {
+                result += $", {item}";
+            }
+
+            return result;
         }
 
         /// <summary>
-        /// Clone function.
+        /// Clone itself.
+        /// If type T implements ICloneable, it calls Clone() method to clone values, otherwise use operator=.
         /// </summary>
         /// <since_tizen> 6 </since_tizen>
         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public new void Clone(Selector<T> selector)
+        public Selector<T> Clone()
         {
-            base.Clone(selector);
+            var cloned = new Selector<T>();
+            cloned.Clone(this);
+            return cloned;
+        }
 
-            if (null != targetView && null != GetValue(targetView.ControlState))
-            {
-                targetView.SetValue(targetBindableProperty, GetValue(targetView.ControlState));
-            }
+        /// <summary>
+        /// Clone with type converter.
+        /// </summary>
+        /// <exception cref="ArgumentNullException"> Thrown when converter is null. </exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Selector<TOut> Clone<TOut>(Converter<T, TOut> converter)
+        {
+            if (converter == null) throw new ArgumentNullException(nameof(converter));
+
+            Selector<TOut> result = new Selector<TOut>();
+            result.All = converter(All);
+            result.SelectorItems = SelectorItems.ConvertAll<SelectorItem<TOut>>(m => new SelectorItem<TOut>(m.State, converter(m.Value)));
+
+            return result;
         }
 
-        private void OnViewControlState(View obj, ControlStates state)
+        /// <summary>
+        /// Copy values from other selector.
+        /// </summary>
+        /// <exception cref="ArgumentNullException"> Thrown when other is null. </exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Clone(Selector<T> other)
         {
-            if (null != obj && null != GetValue(state))
+            if (null == other)
+            {
+                throw new ArgumentNullException(nameof(other));
+            }
+
+            if (cloneable)
+            {
+                All = (T)((ICloneable)other.All)?.Clone();
+                SelectorItems = other.SelectorItems.ConvertAll(m => new SelectorItem<T>(m.State, (T)((ICloneable)m.Value)?.Clone()));
+            }
+            else
             {
-                obj.SetValue(targetBindableProperty, GetValue(state));
+                All = other.All;
+                SelectorItems = other.SelectorItems.ConvertAll(m => m);
             }
         }
 
-        private View targetView;
-        private BindableProperty targetBindableProperty;
+        internal bool HasMultiValue()
+        {
+            return SelectorItems.Count > 1;
+        }
     }
 
     /// <summary>
-    /// A class that helps binding a non-selector property in View to selector property in ViewStyle.
+    /// This will be attached to a View to detect ControlState change.
     /// </summary>
-    internal class ViewSelector<T> where T : class, Tizen.NUI.Internal.ICloneable
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class TriggerableSelector<T>
     {
+        /// <summary>
+        /// Create an TriggerableSelector.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public delegate T ValueGetter(View view);
+
+        private readonly BindableProperty targetBindableProperty;
+        private readonly ValueGetter propertyGetter;
+        private bool dirty = true;
         private Selector<T> selector;
-        private View view;
-        private View.ControlStateChangesDelegate controlStateChanged;
 
-        internal ViewSelector(View view, View.ControlStateChangesDelegate controlStateChanged)
+        /// <summary>
+        /// Create an TriggerableSelector.
+        /// </summary>
+        /// <param name="targetBindableProperty">The TriggerableSelector will change this bindable property value when the view's ControlState has changed.</param>
+        /// <param name="propertyGetter">It is optional value in case the target bindable property getter is not proper to use.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public TriggerableSelector(BindableProperty targetBindableProperty, ValueGetter propertyGetter = null)
         {
-            if (view == null || controlStateChanged == null)
-            {
-                throw new global::System.ArgumentNullException();
-            }
-            this.view = view;
-            this.controlStateChanged = controlStateChanged;
-            this.selector = null;
+            this.targetBindableProperty = targetBindableProperty;
+            this.propertyGetter = propertyGetter;
         }
 
-        internal T GetValue()
+        /// <summary>
+        /// Return the containing selector. It can be null.
+        /// </summary>
+        /// <exception cref="ArgumentNullException"> Thrown when view is null. </exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Selector<T> Get(View view)
         {
-            return selector == null ? null : selector.GetValue(view.ControlState);
+            if (!dirty) return selector;
+            if (null == view)
+            {
+                throw new ArgumentNullException(nameof(view));
+            }
+
+            T value = default;
+
+            if (propertyGetter != null)
+            {
+                value = propertyGetter(view);
+            }
+            else
+            {
+                value = (T)view.GetValue(targetBindableProperty);
+            }
+
+            Selector<T> converted = value == null ? null : new Selector<T>(value);
+            Update(view, converted);
+
+            return selector;
         }
 
-        internal void Clone(object value)
+        /// <summary>
+        /// Update containing selector from the other selector.
+        /// </summary>
+        /// <param name="view">The View that is affected by this TriggerableSelector.</param>
+        /// <param name="otherSelector">The copy target.</param>
+        /// <param name="updateView">Whether it updates the target view after update the selector or not.</param>
+        /// <exception cref="ArgumentNullException"> Thrown when view is null. </exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Update(View view, Selector<T> otherSelector, bool updateView = false)
         {
-            bool hadMultiValue = HasMultiValue();
-            var type = value?.GetType();
+            Reset(view);
 
-            if (type == typeof(T))
+            if (null == view)
             {
-                selector = new Selector<T>();
-                selector.All = (T)((T)value).Clone();
+                throw new ArgumentNullException(nameof(view));
             }
-            else if (type == typeof(Selector<T>))
+
+            if (otherSelector == null)
             {
-                selector = new Selector<T>();
-                selector.Clone<T>((Selector<T>)value);
+                return;
             }
-            else
+
+            selector = otherSelector.Clone();
+
+            if (otherSelector.HasMultiValue())
             {
-                selector = null;
+                view.ControlStateChangeEventInternal += OnViewControlState;
             }
 
-            if (hadMultiValue != HasMultiValue())
+            if (updateView && otherSelector.GetValue(view.ControlState, out var value))
             {
-                if (hadMultiValue) view.ControlStateChangeEvent -= controlStateChanged;
-                else view.ControlStateChangeEvent += controlStateChanged;
+                view.SetValue(targetBindableProperty, value);
             }
         }
 
-        internal void Clear()
+        /// <summary>
+        /// Update containing selector value from a single value.
+        /// Note that, it updates lazily if possible.
+        /// If you need to udpate directly, please use <seealso cref="Update" />.
+        /// </summary>
+        /// <param name="view">The View that is affected by this TriggerableSelector.</param>
+        /// <param name="value">The copy target.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void UpdateIfNeeds(View view, T value)
         {
-            if (HasMultiValue())
+            if (selector != null && selector.HasMultiValue())
             {
-                view.ControlStateChangeEvent -= controlStateChanged;
+                Selector<T> converted = value == null ? null : new Selector<T>(value);
+                Update(view, converted);
+                return;
             }
-            selector = null;
+
+            dirty = true;
         }
 
-        internal bool IsEmpty()
+        /// <summary>
+        /// Reset selector and listeners.
+        /// </summary>
+        /// <param name="view">The View that is affected by this TriggerableSelector.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void Reset(View view)
         {
-            return selector == null;
+            if (view != null)
+            {
+                view.ControlStateChangeEventInternal -= OnViewControlState;
+            }
+            selector?.Clear();
+            selector = null;
+            dirty = false;
         }
 
-        private bool HasMultiValue()
+        private void OnViewControlState(object obj, View.ControlStateChangedEventArgs controlStateChangedInfo)
         {
-            return (selector != null && selector.All == null);
+            View view = obj as View;
+            if (null != view && selector.GetValue(controlStateChangedInfo.CurrentState, out var value))
+            {
+                view.SetValue(targetBindableProperty, value);
+            }
         }
     }
 
-    internal static class SelectorHelper<T> where T : class, Tizen.NUI.Internal.ICloneable
+    /// <summary>
+    /// The selector item class that stores a state-value pair.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public class SelectorItem<T>
     {
         /// <summary>
-        /// For the object type of T or Selector T, convert it to Selector T and return the cloned one.
-        /// Otherwise, return null. <br/>
+        /// The default constructor.
         /// </summary>
-        static internal Selector<T> Clone(object value)
-        {
-            var type = value?.GetType();
-
-            if (type == typeof(Selector<T>))
-            {
-                var result = new Selector<T>();
-                result.Clone<T>((Selector<T>)value);
-                return result;
-            }
-
-            if (type == typeof(T))
-            {
-                return new Selector<T>((T)((T)value).Clone());
-            }
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public SelectorItem() {}
 
-            return null;
+        /// <summary>
+        /// The constructor with the specified state and value.
+        /// </summary>
+        /// <param name="state">The state</param>
+        /// <param name="value">The value associated with state.</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public SelectorItem(ControlState state, T value)
+        {
+            State = state;
+            Value = value;
         }
 
         /// <summary>
-        /// For the object type of T or Selector T, convert it to T and return the cloned one.
-        /// Otherwise, return null. <br/>
+        /// The state
         /// </summary>
-        static internal T Clone(object value, View view)
-        {
-            var type = value?.GetType();
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public ControlState State { get; set; }
 
-            if (type == typeof(T))
-            {
-                return (T)((T)value).Clone();
-            }
+        /// <summary>
+        /// The value associated with state.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public T Value { get; set; }
 
-            if (type == typeof(Selector<T>) && view != null)
-            {
-                Selector<T> selector = (Selector<T>)value;
-                T valueInState = selector.GetValue(view.ControlState);
+        ///  <inheritdoc/>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public override string ToString() => $"[{State}, {Value}]";
+    }
 
-                return (T)valueInState?.Clone();
-            }
+    /// <summary>
+    /// Extension class for <see cref="Selector{T}"/>.
+    /// </summary>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public static class SelectorExtensions
+    {
+        /// <summary>
+        /// Adds the specified state and value to the <see cref="Selector{T}.SelectorItems"/>.
+        /// </summary>
+        /// <param name="list">The list for adding state-value pair.</param>
+        /// <param name="state">The state.</param>
+        /// <param name="value">The value associated with state.</param>
+        /// <exception cref="ArgumentNullException"> Thrown when given list is null. </exception>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public static void Add<T>(this IList<SelectorItem<T>> list, ControlState state, T value)
+        {
+            if (list == null) throw new ArgumentNullException(nameof(list));
 
-            return null;
+            list.Add(new SelectorItem<T>(state, value));
         }
     }
 }