/*
- * 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
[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>
[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.
[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.
[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.
[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.
[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.
[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.
/// 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.
[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>
[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));
}
}
}