[NUI] Fix build warning CA1062 (#2234)
[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         /// <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             result = default;
230
231             int index = ((List<StateValuePair<T>>)StateValueList).FindIndex(x => x.State == state);
232             if (index >= 0)
233             {
234                 result = StateValueList[index].Value;
235                 return true;
236             }
237
238             if (null == state)
239             {
240                 throw new ArgumentNullException(nameof(state));
241             }
242             if (state.IsCombined)
243             {
244                 index = ((List<StateValuePair<T>>)StateValueList).FindIndex(x => state.Contains(x.State));
245                 if (index >= 0)
246                 {
247                     result = StateValueList[index].Value;
248                     return true;
249                 }
250             }
251
252             index = ((List<StateValuePair<T>>)StateValueList).FindIndex(x => x.State == ControlState.Other);
253             if (index >= 0)
254             {
255                 result = StateValueList[index].Value;
256                 return true;
257             }
258
259             return false;
260         }
261
262         /// <summary>
263         /// Removes all elements.
264         /// </summary>
265         [EditorBrowsable(EditorBrowsableState.Never)]
266         public void Clear()
267         {
268             All = default;
269             StateValueList.Clear();
270         }
271
272         /// <inheritdoc/>
273         [EditorBrowsable(EditorBrowsableState.Never)]
274         public override string ToString()
275         {
276             string result = $"[All, {All}]";
277
278             foreach (var item in StateValueList)
279             {
280                 result += $", {item}";
281             }
282
283             return result;
284         }
285
286         /// <summary>
287         /// Clone itself.
288         /// If type T implements ICloneable, it calls Clone() method to clone values, otherwise use operator=.
289         /// </summary>
290         /// <since_tizen> 6 </since_tizen>
291         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
292         [EditorBrowsable(EditorBrowsableState.Never)]
293         public Selector<T> Clone()
294         {
295             var cloned = new Selector<T>();
296             cloned.Clone(this);
297             return cloned;
298         }
299
300         /// <summary>
301         /// Copy values from other selector.
302         /// </summary>
303         /// <exception cref="ArgumentNullException"> Thrown when other is null. </exception>
304         [EditorBrowsable(EditorBrowsableState.Never)]
305         public void Clone(Selector<T> other)
306         {
307             if (null == other)
308             {
309                 throw new ArgumentNullException(nameof(other));
310             }
311
312             if (cloneable)
313             {
314                 All = (T)((ICloneable)other.All)?.Clone();
315                 StateValueList = ((List<StateValuePair<T>>)other.StateValueList).ConvertAll(m => new StateValuePair<T>(m.State, (T)((ICloneable)m.Value)?.Clone()));
316             }
317             else
318             {
319                 All = other.All;
320                 StateValueList = ((List<StateValuePair<T>>)other.StateValueList).ConvertAll(m => m);
321             }
322         }
323
324         internal bool HasMultiValue()
325         {
326             return StateValueList.Count > 1;
327         }
328
329     }
330
331     /// <summary>
332     /// This will be attached to a View to detect ControlState change.
333     /// </summary>
334     [EditorBrowsable(EditorBrowsableState.Never)]
335     public class TriggerableSelector<T>
336     {
337         /// <summary>
338         /// Create an TriggerableSelector.
339         /// </summary>
340         [EditorBrowsable(EditorBrowsableState.Never)]
341         public delegate T ValueGetter(View view);
342
343         private readonly BindableProperty targetBindableProperty;
344         private readonly ValueGetter propertyGetter;
345         private bool dirty = true;
346         private Selector<T> selector;
347
348         /// <summary>
349         /// Create an TriggerableSelector.
350         /// </summary>
351         /// <param name="targetBindableProperty">The TriggerableSelector will change this bindable property value when the view's ControlState has changed.</param>
352         /// <param name="propertyGetter">It is optional value in case the target bindable property getter is not proper to use.</param>
353         [EditorBrowsable(EditorBrowsableState.Never)]
354         public TriggerableSelector(BindableProperty targetBindableProperty, ValueGetter propertyGetter = null)
355         {
356             this.targetBindableProperty = targetBindableProperty;
357             this.propertyGetter = propertyGetter;
358         }
359
360         /// <summary>
361         /// Return the containing selector. It can be null.
362         /// </summary>
363         /// <exception cref="ArgumentNullException"> Thrown when view is null. </exception>
364         [EditorBrowsable(EditorBrowsableState.Never)]
365         public Selector<T> Get(View view)
366         {
367             if (!dirty) return selector;
368             if (null == view)
369             {
370                 throw new ArgumentNullException(nameof(view));
371             }
372
373             T value = default;
374
375             if (propertyGetter != null)
376             {
377                 value = propertyGetter(view);
378             }
379             else
380             {
381                 value = (T)view.GetValue(targetBindableProperty);
382             }
383
384             Selector<T> converted = value == null ? null : new Selector<T>(value);
385             Update(view, converted);
386
387             return selector;
388         }
389
390         /// <summary>
391         /// Update containing selector from the other selector.
392         /// </summary>
393         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
394         /// <param name="otherSelector">The copy target.</param>
395         /// <param name="updateView">Whether it updates the target view after update the selector or not.</param>
396         /// <exception cref="ArgumentNullException"> Thrown when view is null. </exception>
397         [EditorBrowsable(EditorBrowsableState.Never)]
398         public void Update(View view, Selector<T> otherSelector, bool updateView = false)
399         {
400             Reset(view);
401
402             if (null == view)
403             {
404                 throw new ArgumentNullException(nameof(view));
405             }
406
407             if (otherSelector == null)
408             {
409                 return;
410             }
411
412             selector = otherSelector.Clone();
413
414             if (otherSelector.HasMultiValue())
415             {
416                 view.ControlStateChangeEventInternal += OnViewControlState;
417             }
418
419             if (updateView && otherSelector.GetValue(view.ControlState, out var value))
420             {
421                 view.SetValue(targetBindableProperty, value);
422             }
423         }
424
425         /// <summary>
426         /// Update containing selector value from a single value.
427         /// Note that, it updates lazily if possible.
428         /// If you need to udpate directly, please use <seealso cref="Update" />.
429         /// </summary>
430         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
431         /// <param name="value">The copy target.</param>
432         [EditorBrowsable(EditorBrowsableState.Never)]
433         public void UpdateIfNeeds(View view, T value)
434         {
435             if (selector != null && selector.HasMultiValue())
436             {
437                 Selector<T> converted = value == null ? null : new Selector<T>(value);
438                 Update(view, converted);
439                 return;
440             }
441
442             dirty = true;
443         }
444
445         /// <summary>
446         /// Reset selector and listeners.
447         /// </summary>
448         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
449         [EditorBrowsable(EditorBrowsableState.Never)]
450         public void Reset(View view)
451         {
452             if (view != null)
453             {
454                 view.ControlStateChangeEventInternal -= OnViewControlState;
455             }
456             selector?.Clear();
457             selector = null;
458             dirty = false;
459         }
460
461         private void OnViewControlState(object obj, View.ControlStateChangedEventArgs controlStateChangedInfo)
462         {
463             View view = obj as View;
464             if (null != view && selector.GetValue(controlStateChangedInfo.CurrentState, out var value))
465             {
466                 view.SetValue(targetBindableProperty, value);
467             }
468         }
469     }
470 }