[NUI] Change type of Selector.StateValueList to IList (#2149)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / BaseComponents / Style / Selector.cs
1 /*
2  * Copyright(c) 2019 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using Tizen.NUI.Binding;
21 using Tizen.NUI.Components;
22
23 namespace Tizen.NUI.BaseComponents
24 {
25     /// <summary>
26     /// Selector class, which is related by Control State, it is base class for other Selector.
27     /// </summary>
28     /// <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>
29     /// <since_tizen> 6 </since_tizen>
30     /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
31     [EditorBrowsable(EditorBrowsableState.Never)]
32     public class Selector<T>
33     {
34         private readonly bool cloneable = typeof(ICloneable).IsAssignableFrom(typeof(T));
35
36         /// <summary>
37         /// The list for adding state-value pair.
38         /// </summary>
39         [EditorBrowsable(EditorBrowsableState.Never)]
40         public IList<StateValuePair<T>> StateValueList { get; set; } = new List<StateValuePair<T>>();
41
42         /// <summary>
43         /// Adds the specified state and value.
44         /// </summary>
45         /// <param name="state">The state.</param>
46         /// <param name="value">The value associated with state.</param>
47         [EditorBrowsable(EditorBrowsableState.Never)]
48         public void Add(ControlState state, T value) => Add(new StateValuePair<T>(state, value));
49
50         /// <summary>
51         /// Adds the specified state and value.
52         /// </summary>
53         /// <param name="stateValuePair"></param>
54         [EditorBrowsable(EditorBrowsableState.Never)]
55         public void Add(StateValuePair<T> stateValuePair)
56         {
57             // To prevent a state from having multiple values, remove existing state-value pair.
58             int index = ((List<StateValuePair<T>>)StateValueList).FindIndex(x => x.State == stateValuePair.State);
59             if (index != -1)
60                StateValueList.RemoveAt(index);
61
62             StateValueList.Add(stateValuePair);
63         }
64
65         /// <since_tizen> 6 </since_tizen>
66         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
67         public static implicit operator Selector<T>(T value)
68         {
69             return new Selector<T>(value);
70         }
71
72         /// Default Contructor
73         [EditorBrowsable(EditorBrowsableState.Never)]
74         public Selector()
75         {
76         }
77
78         /// Contructor with T
79         [EditorBrowsable(EditorBrowsableState.Never)]
80         public Selector(T value) : this()
81         {
82             All = cloneable ? (T)((ICloneable)value)?.Clone() : value;
83         }
84
85         /// Copy constructor
86         [EditorBrowsable(EditorBrowsableState.Never)]
87         public Selector(Selector<T> value) : this()
88         {
89             Clone(value);
90         }
91
92         /// <summary>
93         /// All State.
94         /// </summary>
95         /// <since_tizen> 6 </since_tizen>
96         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
97         [EditorBrowsable(EditorBrowsableState.Never)]
98         public T All
99         {
100             get;
101             set;
102         }
103         /// <summary>
104         /// Normal State.
105         /// </summary>
106         /// <since_tizen> 6 </since_tizen>
107         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
108         [EditorBrowsable(EditorBrowsableState.Never)]
109         public T Normal
110         {
111             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.Normal).Value;
112             set => Add(ControlState.Normal, value);
113         }
114         /// <summary>
115         /// Pressed State.
116         /// </summary>
117         /// <since_tizen> 6 </since_tizen>
118         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
119         [EditorBrowsable(EditorBrowsableState.Never)]
120         public T Pressed
121         {
122
123             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.Pressed).Value;
124             set => Add(ControlState.Pressed, value);
125         }
126         /// <summary>
127         /// Focused State.
128         /// </summary>
129         /// <since_tizen> 6 </since_tizen>
130         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
131         [EditorBrowsable(EditorBrowsableState.Never)]
132         public T Focused
133         {
134             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.Focused).Value;
135             set => Add(ControlState.Focused, value);
136         }
137         /// <summary>
138         /// Selected State.
139         /// </summary>
140         /// <since_tizen> 6 </since_tizen>
141         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
142         [EditorBrowsable(EditorBrowsableState.Never)]
143         public T Selected
144         {
145             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.Selected).Value;
146             set => Add(ControlState.Selected, value);
147         }
148         /// <summary>
149         /// Disabled State.
150         /// </summary>
151         /// <since_tizen> 6 </since_tizen>
152         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
153         [EditorBrowsable(EditorBrowsableState.Never)]
154         public T Disabled
155         {
156
157             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.Disabled).Value;
158             set => Add(ControlState.Disabled, value);
159         }
160         /// <summary>
161         /// DisabledFocused State.
162         /// </summary>
163         /// <since_tizen> 6 </since_tizen>
164         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
165         [EditorBrowsable(EditorBrowsableState.Never)]
166         public T DisabledFocused
167         {
168             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.DisabledFocused).Value;
169             set => Add(ControlState.DisabledFocused, value);
170         }
171         /// <summary>
172         /// SelectedFocused State.
173         /// </summary>
174         /// <since_tizen> 6 </since_tizen>
175         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
176         public T SelectedFocused
177         {
178             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.SelectedFocused).Value;
179             set => Add(ControlState.SelectedFocused, value);
180         }
181         /// <summary>
182         /// DisabledSelected State.
183         /// </summary>
184         /// <since_tizen> 6 </since_tizen>
185         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
186         [EditorBrowsable(EditorBrowsableState.Never)]
187         public T DisabledSelected
188         {
189
190             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.DisabledSelected).Value;
191             set => Add(ControlState.DisabledSelected, value);
192         }
193
194         /// <summary>
195         /// Other State.
196         /// </summary>
197         /// <since_tizen> 6 </since_tizen>
198         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
199         [EditorBrowsable(EditorBrowsableState.Never)]
200         public T Other
201         {
202             get => ((List<StateValuePair<T>>)StateValueList).Find(x => x.State == ControlState.Other).Value;
203             set => Add(ControlState.Other, value);
204         }
205
206         /// <summary>
207         /// Gets the number of elements.
208         /// </summary>
209         [EditorBrowsable(EditorBrowsableState.Never)]
210         public int Count => StateValueList.Count;
211
212         /// <summary>
213         /// Get value by State.
214         /// </summary>
215         /// <since_tizen> 6 </since_tizen>
216         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
217         /// <returns>True if the selector has a given state value, false otherwise.</returns>
218         [EditorBrowsable(EditorBrowsableState.Never)]
219         public bool GetValue(ControlState state, out T result)
220         {
221             if (All != null)
222             {
223                 result = All;
224
225                 return true;
226             }
227
228             result = default;
229
230             int index = ((List<StateValuePair<T>>)StateValueList).FindIndex(x => x.State == state);
231             if (index >= 0)
232             {
233                 result = StateValueList[index].Value;
234                 return true;
235             }
236
237             if (state.IsCombined)
238             {
239                 index = ((List<StateValuePair<T>>)StateValueList).FindIndex(x => state.Contains(x.State));
240                 if (index >= 0)
241                 {
242                     result = StateValueList[index].Value;
243                     return true;
244                 }
245             }
246
247             index = ((List<StateValuePair<T>>)StateValueList).FindIndex(x => x.State == ControlState.Other);
248             if (index >= 0)
249             {
250                 result = StateValueList[index].Value;
251                 return true;
252             }
253
254             return false;
255         }
256
257         /// <summary>
258         /// Removes all elements.
259         /// </summary>
260         [EditorBrowsable(EditorBrowsableState.Never)]
261         public void Clear()
262         {
263             All = default;
264             StateValueList.Clear();
265         }
266
267         /// <inheritdoc/>
268         [EditorBrowsable(EditorBrowsableState.Never)]
269         public override string ToString()
270         {
271             string result = $"[All, {All}]";
272
273             foreach (var item in StateValueList)
274             {
275                 result += $", {item}";
276             }
277
278             return result;
279         }
280
281         /// <summary>
282         /// Clone itself.
283         /// If type T implements ICloneable, it calls Clone() method to clone values, otherwise use operator=.
284         /// </summary>
285         /// <since_tizen> 6 </since_tizen>
286         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
287         [EditorBrowsable(EditorBrowsableState.Never)]
288         public Selector<T> Clone()
289         {
290             var cloned = new Selector<T>();
291             cloned.Clone(this);
292             return cloned;
293         }
294
295         /// <summary>
296         /// Copy values from other selector.
297         /// </summary>
298         [EditorBrowsable(EditorBrowsableState.Never)]
299         public void Clone(Selector<T> other)
300         {
301             if (cloneable)
302             {
303                 All = (T)((ICloneable)other.All)?.Clone();
304                 StateValueList = ((List<StateValuePair<T>>)other.StateValueList).ConvertAll(m => new StateValuePair<T>(m.State, (T)((ICloneable)m.Value)?.Clone()));
305             }
306             else
307             {
308                 All = other.All;
309                 StateValueList = ((List<StateValuePair<T>>)other.StateValueList).ConvertAll(m => m);
310             }
311         }
312
313         internal bool HasMultiValue()
314         {
315             return StateValueList.Count > 1;
316         }
317
318     }
319
320     /// <summary>
321     /// This will be attached to a View to detect ControlState change.
322     /// </summary>
323     [EditorBrowsable(EditorBrowsableState.Never)]
324     public class TriggerableSelector<T>
325     {
326         /// <summary>
327         /// Create an TriggerableSelector.
328         /// </summary>
329         [EditorBrowsable(EditorBrowsableState.Never)]
330         public delegate T ValueGetter(View view);
331
332         private readonly BindableProperty targetBindableProperty;
333         private readonly ValueGetter propertyGetter;
334         private bool dirty = true;
335         private Selector<T> selector;
336
337         /// <summary>
338         /// Create an TriggerableSelector.
339         /// </summary>
340         /// <param name="targetBindableProperty">The TriggerableSelector will change this bindable property value when the view's ControlState has changed.</param>
341         /// <param name="propertyGetter">It is optional value in case the target bindable property getter is not proper to use.</param>
342         [EditorBrowsable(EditorBrowsableState.Never)]
343         public TriggerableSelector(BindableProperty targetBindableProperty, ValueGetter propertyGetter = null)
344         {
345             this.targetBindableProperty = targetBindableProperty;
346             this.propertyGetter = propertyGetter;
347         }
348
349         /// <summary>
350         /// Return the containing selector. It can be null.
351         /// </summary>
352         [EditorBrowsable(EditorBrowsableState.Never)]
353         public Selector<T> Get(View view)
354         {
355             if (!dirty) return selector;
356
357             T value = default;
358
359             if (propertyGetter != null)
360             {
361                 value = propertyGetter(view);
362             }
363             else
364             {
365                 value = (T)view.GetValue(targetBindableProperty);
366             }
367
368             Selector<T> converted = value == null ? null : new Selector<T>(value);
369             Update(view, converted);
370
371             return selector;
372         }
373
374         /// <summary>
375         /// Update containing selector from the other selector.
376         /// </summary>
377         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
378         /// <param name="otherSelector">The copy target.</param>
379         /// <param name="updateView">Whether it updates the target view after update the selector or not.</param>
380         [EditorBrowsable(EditorBrowsableState.Never)]
381         public void Update(View view, Selector<T> otherSelector, bool updateView = false)
382         {
383             Reset(view);
384
385             if (otherSelector == null)
386             {
387                 return;
388             }
389
390             selector = otherSelector.Clone();
391
392             if (otherSelector.HasMultiValue())
393             {
394                 view.ControlStateChangeEventInternal += OnViewControlState;
395             }
396
397             if (updateView && otherSelector.GetValue(view.ControlState, out var value))
398             {
399                 view.SetValue(targetBindableProperty, value);
400             }
401         }
402
403         /// <summary>
404         /// Update containing selector value from a single value.
405         /// Note that, it updates lazily if possible.
406         /// If you need to udpate directly, please use <seealso cref="Update" />.
407         /// </summary>
408         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
409         /// <param name="value">The copy target.</param>
410         [EditorBrowsable(EditorBrowsableState.Never)]
411         public void UpdateIfNeeds(View view, T value)
412         {
413             if (selector != null && selector.HasMultiValue())
414             {
415                 Selector<T> converted = value == null ? null : new Selector<T>(value);
416                 Update(view, converted);
417                 return;
418             }
419
420             dirty = true;
421         }
422
423         /// <summary>
424         /// Reset selector and listeners.
425         /// </summary>
426         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
427         [EditorBrowsable(EditorBrowsableState.Never)]
428         public void Reset(View view)
429         {
430             view.ControlStateChangeEventInternal -= OnViewControlState;
431             selector?.Clear();
432             selector = null;
433             dirty = false;
434         }
435
436         private void OnViewControlState(object obj, View.ControlStateChangedEventArgs controlStateChangedInfo)
437         {
438             View view = obj as View;
439             if (null != view && selector.GetValue(controlStateChangedInfo.CurrentState, out var value))
440             {
441                 view.SetValue(targetBindableProperty, value);
442             }
443         }
444     }
445 }