34c44a695e540bfcbc0c49dd2fd89ad84403ce4b
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / BaseComponents / Style / Selector.cs
1 /*
2  * Copyright(c) 2021 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
22 namespace Tizen.NUI.BaseComponents
23 {
24     /// <summary>
25     /// Selector class, which is related by Control State, it is base class for other Selector.
26     /// </summary>
27     /// <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>
28     /// <since_tizen> 6 </since_tizen>
29     /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
30     [EditorBrowsable(EditorBrowsableState.Never)]
31     public class Selector<T>
32     {
33         private readonly bool cloneable = typeof(ICloneable).IsAssignableFrom(typeof(T));
34
35         /// <summary>
36         /// The list for adding <see cref="SelectorItem{T}"/>.
37         /// </summary>
38         [EditorBrowsable(EditorBrowsableState.Never)]
39         List<SelectorItem<T>> SelectorItems { get; set; } = new List<SelectorItem<T>>();
40
41         /// <summary>
42         /// Adds the specified state and value to the <see cref="SelectorItems"/>.
43         /// </summary>
44         /// <param name="state">The state.</param>
45         /// <param name="value">The value associated with state.</param>
46         [EditorBrowsable(EditorBrowsableState.Never)]
47         public void Add(ControlState state, T value)
48         {
49             SelectorItems.Add(new SelectorItem<T>(state, value));
50             All = default;
51         }
52
53         /// <summary>
54         /// Adds the specified state and value to the <see cref="SelectorItems"/>.
55         /// </summary>
56         /// <param name="selectorItem">The selector item class that stores a state-value pair.</param>
57         [EditorBrowsable(EditorBrowsableState.Never)]
58         public void Add(SelectorItem<T> selectorItem)
59         {
60             // To prevent a state from having multiple values, remove existing state-value pair.
61             int index = SelectorItems.FindIndex(x => x.State == selectorItem.State);
62             if (index != -1)
63                 SelectorItems.RemoveAt(index);
64
65             SelectorItems.Add(selectorItem);
66         }
67
68         /// <since_tizen> 6 </since_tizen>
69         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
70         public static implicit operator Selector<T>(T value)
71         {
72             return new Selector<T>(value);
73         }
74
75         /// Default Contructor
76         [EditorBrowsable(EditorBrowsableState.Never)]
77         public Selector()
78         {
79         }
80
81         /// Contructor with T
82         [EditorBrowsable(EditorBrowsableState.Never)]
83         public Selector(T value) : this()
84         {
85             All = cloneable ? (T)((ICloneable)value)?.Clone() : value;
86         }
87
88         /// Copy constructor
89         [EditorBrowsable(EditorBrowsableState.Never)]
90         public Selector(Selector<T> value) : this()
91         {
92             Clone(value);
93         }
94
95         /// <summary>
96         /// All State.
97         /// </summary>
98         /// <since_tizen> 6 </since_tizen>
99         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
100         [EditorBrowsable(EditorBrowsableState.Never)]
101         public T All
102         {
103             get;
104             set;
105         }
106         /// <summary>
107         /// Normal State.
108         /// </summary>
109         /// <since_tizen> 6 </since_tizen>
110         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
111         [EditorBrowsable(EditorBrowsableState.Never)]
112         public T Normal
113         {
114             get => SelectorItems.Find(x => x.State == ControlState.Normal).Value;
115             set => Add(ControlState.Normal, value);
116         }
117         /// <summary>
118         /// Pressed State.
119         /// </summary>
120         /// <since_tizen> 6 </since_tizen>
121         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
122         [EditorBrowsable(EditorBrowsableState.Never)]
123         public T Pressed
124         {
125             get => SelectorItems.Find(x => x.State == ControlState.Pressed).Value;
126             set => Add(ControlState.Pressed, value);
127         }
128         /// <summary>
129         /// Focused State.
130         /// </summary>
131         /// <since_tizen> 6 </since_tizen>
132         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
133         [EditorBrowsable(EditorBrowsableState.Never)]
134         public T Focused
135         {
136             get => SelectorItems.Find(x => x.State == ControlState.Focused).Value;
137             set => Add(ControlState.Focused, value);
138         }
139         /// <summary>
140         /// Selected State.
141         /// </summary>
142         /// <since_tizen> 6 </since_tizen>
143         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
144         [EditorBrowsable(EditorBrowsableState.Never)]
145         public T Selected
146         {
147             get => SelectorItems.Find(x => x.State == ControlState.Selected).Value;
148             set => Add(ControlState.Selected, value);
149         }
150         /// <summary>
151         /// Disabled State.
152         /// </summary>
153         /// <since_tizen> 6 </since_tizen>
154         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
155         [EditorBrowsable(EditorBrowsableState.Never)]
156         public T Disabled
157         {
158             get => SelectorItems.Find(x => x.State == ControlState.Disabled).Value;
159             set => Add(ControlState.Disabled, value);
160         }
161         /// <summary>
162         /// DisabledFocused State.
163         /// </summary>
164         /// <since_tizen> 6 </since_tizen>
165         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
166         [EditorBrowsable(EditorBrowsableState.Never)]
167         public T DisabledFocused
168         {
169             get => SelectorItems.Find(x => x.State == ControlState.DisabledFocused).Value;
170             set => Add(ControlState.DisabledFocused, value);
171         }
172         /// <summary>
173         /// SelectedFocused State.
174         /// </summary>
175         /// <since_tizen> 6 </since_tizen>
176         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
177         public T SelectedFocused
178         {
179             get => SelectorItems.Find(x => x.State == ControlState.SelectedFocused).Value;
180             set => Add(ControlState.SelectedFocused, value);
181         }
182         /// <summary>
183         /// DisabledSelected State.
184         /// </summary>
185         /// <since_tizen> 6 </since_tizen>
186         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
187         [EditorBrowsable(EditorBrowsableState.Never)]
188         public T DisabledSelected
189         {
190             get => SelectorItems.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 => SelectorItems.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 => SelectorItems.Count;
211
212         /// <summary>
213         /// Get value by State.
214         /// </summary>
215         /// <exception cref="ArgumentNullException"> Thrown when state is null. </exception>
216         /// <since_tizen> 6 </since_tizen>
217         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
218         /// <returns>True if the selector has a given state value, false otherwise.</returns>
219         [EditorBrowsable(EditorBrowsableState.Never)]
220         public bool GetValue(ControlState state, out T result)
221         {
222             if (All != null)
223             {
224                 result = All;
225
226                 return true;
227             }
228
229             if (state == null)
230                 throw new ArgumentNullException(nameof(state));
231
232             result = default;
233
234             int index = SelectorItems.FindIndex(x => x.State == state);
235             if (index >= 0)
236             {
237                 result = SelectorItems[index].Value;
238                 return true;
239             }
240
241             if (null == state)
242             {
243                 throw new ArgumentNullException(nameof(state));
244             }
245             if (state.IsCombined)
246             {
247                 index = SelectorItems.FindIndex(x => state.Contains(x.State));
248                 if (index >= 0)
249                 {
250                     result = SelectorItems[index].Value;
251                     return true;
252                 }
253             }
254
255             index = SelectorItems.FindIndex(x => x.State == ControlState.Other);
256             if (index >= 0)
257             {
258                 result = SelectorItems[index].Value;
259                 return true;
260             }
261
262             return false;
263         }
264
265         /// <summary>
266         /// Removes all elements.
267         /// </summary>
268         [EditorBrowsable(EditorBrowsableState.Never)]
269         public void Clear()
270         {
271             All = default;
272             SelectorItems.Clear();
273         }
274
275         /// <inheritdoc/>
276         [EditorBrowsable(EditorBrowsableState.Never)]
277         public override string ToString()
278         {
279             string result = $"[All, {All}]";
280
281             foreach (var item in SelectorItems)
282             {
283                 result += $", {item}";
284             }
285
286             return result;
287         }
288
289         /// <summary>
290         /// Clone itself.
291         /// If type T implements ICloneable, it calls Clone() method to clone values, otherwise use operator=.
292         /// </summary>
293         /// <since_tizen> 6 </since_tizen>
294         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
295         [EditorBrowsable(EditorBrowsableState.Never)]
296         public Selector<T> Clone()
297         {
298             var cloned = new Selector<T>();
299             cloned.Clone(this);
300             return cloned;
301         }
302
303         /// <summary>
304         /// Clone with type converter.
305         /// </summary>
306         /// <exception cref="ArgumentNullException"> Thrown when converter is null. </exception>
307         [EditorBrowsable(EditorBrowsableState.Never)]
308         public Selector<TOut> Clone<TOut>(Converter<T, TOut> converter)
309         {
310             if (converter == null) throw new ArgumentNullException(nameof(converter));
311
312             Selector<TOut> result = new Selector<TOut>();
313             result.All = converter(All);
314             result.SelectorItems = SelectorItems.ConvertAll<SelectorItem<TOut>>(m => new SelectorItem<TOut>(m.State, converter(m.Value)));
315
316             return result;
317         }
318
319         /// <summary>
320         /// Copy values from other selector.
321         /// </summary>
322         /// <exception cref="ArgumentNullException"> Thrown when other is null. </exception>
323         [EditorBrowsable(EditorBrowsableState.Never)]
324         public void Clone(Selector<T> other)
325         {
326             if (null == other)
327             {
328                 throw new ArgumentNullException(nameof(other));
329             }
330
331             if (cloneable)
332             {
333                 All = (T)((ICloneable)other.All)?.Clone();
334                 SelectorItems = other.SelectorItems.ConvertAll(m => new SelectorItem<T>(m.State, (T)((ICloneable)m.Value)?.Clone()));
335             }
336             else
337             {
338                 All = other.All;
339                 SelectorItems = other.SelectorItems.ConvertAll(m => m);
340             }
341         }
342
343         internal bool HasMultiValue()
344         {
345             return SelectorItems.Count > 1;
346         }
347     }
348
349     /// <summary>
350     /// This will be attached to a View to detect ControlState change.
351     /// </summary>
352     [EditorBrowsable(EditorBrowsableState.Never)]
353     public class TriggerableSelector<T>
354     {
355         /// <summary>
356         /// Create an TriggerableSelector.
357         /// </summary>
358         [EditorBrowsable(EditorBrowsableState.Never)]
359         public delegate T ValueGetter(View view);
360
361         private readonly BindableProperty targetBindableProperty;
362         private readonly ValueGetter propertyGetter;
363         private bool dirty = true;
364         private Selector<T> selector;
365
366         /// <summary>
367         /// Create an TriggerableSelector.
368         /// </summary>
369         /// <param name="targetBindableProperty">The TriggerableSelector will change this bindable property value when the view's ControlState has changed.</param>
370         /// <param name="propertyGetter">It is optional value in case the target bindable property getter is not proper to use.</param>
371         [EditorBrowsable(EditorBrowsableState.Never)]
372         public TriggerableSelector(BindableProperty targetBindableProperty, ValueGetter propertyGetter = null)
373         {
374             this.targetBindableProperty = targetBindableProperty;
375             this.propertyGetter = propertyGetter;
376         }
377
378         /// <summary>
379         /// Return the containing selector. It can be null.
380         /// </summary>
381         /// <exception cref="ArgumentNullException"> Thrown when view is null. </exception>
382         [EditorBrowsable(EditorBrowsableState.Never)]
383         public Selector<T> Get(View view)
384         {
385             if (!dirty) return selector;
386             if (null == view)
387             {
388                 throw new ArgumentNullException(nameof(view));
389             }
390
391             T value = default;
392
393             if (propertyGetter != null)
394             {
395                 value = propertyGetter(view);
396             }
397             else
398             {
399                 value = (T)view.GetValue(targetBindableProperty);
400             }
401
402             Selector<T> converted = value == null ? null : new Selector<T>(value);
403             Update(view, converted);
404
405             return selector;
406         }
407
408         /// <summary>
409         /// Update containing selector from the other selector.
410         /// </summary>
411         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
412         /// <param name="otherSelector">The copy target.</param>
413         /// <param name="updateView">Whether it updates the target view after update the selector or not.</param>
414         /// <exception cref="ArgumentNullException"> Thrown when view is null. </exception>
415         [EditorBrowsable(EditorBrowsableState.Never)]
416         public void Update(View view, Selector<T> otherSelector, bool updateView = false)
417         {
418             Reset(view);
419
420             if (null == view)
421             {
422                 throw new ArgumentNullException(nameof(view));
423             }
424
425             if (otherSelector == null)
426             {
427                 return;
428             }
429
430             selector = otherSelector.Clone();
431
432             if (otherSelector.HasMultiValue())
433             {
434                 view.ControlStateChangeEventInternal += OnViewControlState;
435             }
436
437             if (updateView && otherSelector.GetValue(view.ControlState, out var value))
438             {
439                 view.SetValue(targetBindableProperty, value);
440             }
441         }
442
443         /// <summary>
444         /// Update containing selector value from a single value.
445         /// Note that, it updates lazily if possible.
446         /// If you need to udpate directly, please use <seealso cref="Update" />.
447         /// </summary>
448         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
449         /// <param name="value">The copy target.</param>
450         [EditorBrowsable(EditorBrowsableState.Never)]
451         public void UpdateIfNeeds(View view, T value)
452         {
453             if (selector != null && selector.HasMultiValue())
454             {
455                 Selector<T> converted = value == null ? null : new Selector<T>(value);
456                 Update(view, converted);
457                 return;
458             }
459
460             dirty = true;
461         }
462
463         /// <summary>
464         /// Reset selector and listeners.
465         /// </summary>
466         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
467         [EditorBrowsable(EditorBrowsableState.Never)]
468         public void Reset(View view)
469         {
470             if (view != null)
471             {
472                 view.ControlStateChangeEventInternal -= OnViewControlState;
473             }
474             selector?.Clear();
475             selector = null;
476             dirty = false;
477         }
478
479         private void OnViewControlState(object obj, View.ControlStateChangedEventArgs controlStateChangedInfo)
480         {
481             View view = obj as View;
482             if (null != view && selector.GetValue(controlStateChangedInfo.CurrentState, out var value))
483             {
484                 view.SetValue(targetBindableProperty, value);
485             }
486         }
487     }
488
489     /// <summary>
490     /// The selector item class that stores a state-value pair.
491     /// </summary>
492     [EditorBrowsable(EditorBrowsableState.Never)]
493     public class SelectorItem<T>
494     {
495         /// <summary>
496         /// The default constructor.
497         /// </summary>
498         [EditorBrowsable(EditorBrowsableState.Never)]
499         public SelectorItem() {}
500
501         /// <summary>
502         /// The constructor with the specified state and value.
503         /// </summary>
504         /// <param name="state">The state</param>
505         /// <param name="value">The value associated with state.</param>
506         [EditorBrowsable(EditorBrowsableState.Never)]
507         public SelectorItem(ControlState state, T value)
508         {
509             State = state;
510             Value = value;
511         }
512
513         /// <summary>
514         /// The state
515         /// </summary>
516         [EditorBrowsable(EditorBrowsableState.Never)]
517         public ControlState State { get; set; }
518
519         /// <summary>
520         /// The value associated with state.
521         /// </summary>
522         [EditorBrowsable(EditorBrowsableState.Never)]
523         public T Value { get; set; }
524
525         ///  <inheritdoc/>
526         [EditorBrowsable(EditorBrowsableState.Never)]
527         public override string ToString() => $"[{State}, {Value}]";
528     }
529
530     /// <summary>
531     /// Extension class for <see cref="Selector{T}"/>.
532     /// </summary>
533     [EditorBrowsable(EditorBrowsableState.Never)]
534     public static class SelectorExtensions
535     {
536         /// <summary>
537         /// Adds the specified state and value to the <see cref="Selector{T}.SelectorItems"/>.
538         /// </summary>
539         /// <param name="list">The list for adding state-value pair.</param>
540         /// <param name="state">The state.</param>
541         /// <param name="value">The value associated with state.</param>
542         /// <exception cref="ArgumentNullException"> Thrown when given list is null. </exception>
543         [EditorBrowsable(EditorBrowsableState.Never)]
544         public static void Add<T>(this IList<SelectorItem<T>> list, ControlState state, T value)
545         {
546             if (list == null) throw new ArgumentNullException(nameof(list));
547
548             list.Add(new SelectorItem<T>(state, value));
549         }
550     }
551 }