[NUI] replace NUI.ISelectorItem with System.ICloneable (#1965)
[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.ComponentModel;
19 using Tizen.NUI.Binding;
20 using Tizen.NUI.Components;
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> : StateValueCollection<T>
32     {
33         private readonly bool isCloneable = typeof(ICloneable).IsAssignableFrom(typeof(T));
34         /// <since_tizen> 6 </since_tizen>
35         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
36         public static implicit operator Selector<T>(T value)
37         {
38             return new Selector<T>(value);
39         }
40
41         /// Default Contructor
42         [EditorBrowsable(EditorBrowsableState.Never)]
43         public Selector()
44         {
45         }
46
47         /// Contructor with T
48         [EditorBrowsable(EditorBrowsableState.Never)]
49         public Selector(T value) : this()
50         {
51             All = isCloneable ? (T)((ICloneable)value)?.Clone() : value;
52         }
53
54         /// Copy constructor
55         [EditorBrowsable(EditorBrowsableState.Never)]
56         public Selector(Selector<T> value) : this()
57         {
58             Clone(value);
59         }
60
61         internal Selector(SelectorChangedCallback<T> cb) : this()
62         {
63             callback = cb;
64         }
65
66         internal Selector(SelectorChangedCallback<T> cb, T value) : this(value)
67         {
68             callback = cb;
69         }
70
71         internal Selector(SelectorChangedCallback<T> cb, Selector<T> value) : this(value)
72         {
73             callback = cb;
74         }
75
76         internal delegate void SelectorChangedCallback<T>(Selector<T> value);
77         private SelectorChangedCallback<T> callback = null;
78
79         /// <summary>
80         /// All State.
81         /// </summary>
82         /// <since_tizen> 6 </since_tizen>
83         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
84         [EditorBrowsable(EditorBrowsableState.Never)]
85         public T All
86         {
87             get;
88             set;
89         }
90         /// <summary>
91         /// Normal State.
92         /// </summary>
93         /// <since_tizen> 6 </since_tizen>
94         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
95         [EditorBrowsable(EditorBrowsableState.Never)]
96         public T Normal
97         {
98             get
99             {
100                 return Find(x => x.State == ControlState.Normal).Value;
101             }
102             set
103             {
104                 Add(ControlState.Normal, value);
105                 callback?.Invoke(this);
106             }
107         }
108         /// <summary>
109         /// Pressed State.
110         /// </summary>
111         /// <since_tizen> 6 </since_tizen>
112         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
113         [EditorBrowsable(EditorBrowsableState.Never)]
114         public T Pressed
115         {
116             get
117             {
118                 return Find(x => x.State == ControlState.Pressed).Value;
119             }
120             set
121             {
122                 Add(ControlState.Pressed, value);
123                 callback?.Invoke(this);
124             }
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
135             {
136                 return Find(x => x.State == ControlState.Focused).Value;
137             }
138             set
139             {
140                 Add(ControlState.Focused, value);
141                 callback?.Invoke(this);
142             }
143         }
144         /// <summary>
145         /// Selected State.
146         /// </summary>
147         /// <since_tizen> 6 </since_tizen>
148         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
149         [EditorBrowsable(EditorBrowsableState.Never)]
150         public T Selected
151         {
152             get
153             {
154                 return Find(x => x.State == ControlState.Selected).Value;
155             }
156             set
157             {
158                 Add(ControlState.Selected, value);
159                 callback?.Invoke(this);
160             }
161         }
162         /// <summary>
163         /// Disabled State.
164         /// </summary>
165         /// <since_tizen> 6 </since_tizen>
166         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
167         [EditorBrowsable(EditorBrowsableState.Never)]
168         public T Disabled
169         {
170             get
171             {
172                 return Find(x => x.State == ControlState.Disabled).Value;
173             }
174             set
175             {
176                 Add(ControlState.Disabled, value);
177                 callback?.Invoke(this);
178             }
179         }
180         /// <summary>
181         /// DisabledFocused State.
182         /// </summary>
183         /// <since_tizen> 6 </since_tizen>
184         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
185         [EditorBrowsable(EditorBrowsableState.Never)]
186         public T DisabledFocused
187         {
188             get
189             {
190                 return Find(x => x.State == ControlState.DisabledFocused).Value;
191             }
192             set
193             {
194                 Add(ControlState.DisabledFocused, value);
195                 callback?.Invoke(this);
196             }
197         }
198         /// <summary>
199         /// SelectedFocused State.
200         /// </summary>
201         /// <since_tizen> 6 </since_tizen>
202         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
203         public T SelectedFocused
204         {
205             get
206             {
207                 return Find(x => x.State == ControlState.SelectedFocused).Value;
208             }
209             set
210             {
211                 Add(ControlState.SelectedFocused, value);
212                 callback?.Invoke(this);
213             }
214         }
215         /// <summary>
216         /// DisabledSelected State.
217         /// </summary>
218         /// <since_tizen> 6 </since_tizen>
219         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
220         [EditorBrowsable(EditorBrowsableState.Never)]
221         public T DisabledSelected
222         {
223             get
224             {
225                 return Find(x => x.State == ControlState.DisabledSelected).Value;
226             }
227             set
228             {
229                 Add(ControlState.DisabledSelected, value);
230                 callback?.Invoke(this);
231             }
232         }
233
234         /// <summary>
235         /// Other State.
236         /// </summary>
237         /// <since_tizen> 6 </since_tizen>
238         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
239         [EditorBrowsable(EditorBrowsableState.Never)]
240         public T Other
241         {
242             get
243             {
244                 return Find(x => x.State == ControlState.Other).Value;
245             }
246             set
247             {
248                 Add(ControlState.Other, value);
249                 callback?.Invoke(this);
250             }
251         }
252         /// <summary>
253         /// Get value by State.
254         /// </summary>
255         /// <since_tizen> 6 </since_tizen>
256         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
257         /// <returns>True if the selector has a given state value, false otherwise.</returns>
258         [EditorBrowsable(EditorBrowsableState.Never)]
259         public bool GetValue(ControlState state, out T result)
260         {
261             if (All != null)
262             {
263                 result = All;
264
265                 return true;
266             }
267
268             result = default;
269
270             int index = StateValueList.FindIndex(x => x.State == state);
271             if (index >= 0)
272             {
273                 result = StateValueList[index].Value;
274                 return true;
275             }
276
277             if (state.IsCombined)
278             {
279                 index = StateValueList.FindIndex(x => state.Contains(x.State));
280                 if (index >= 0)
281                 {
282                     result = StateValueList[index].Value;
283                     return true;
284                 }
285             }
286
287             index = StateValueList.FindIndex(x => x.State == ControlState.Other);
288             if (index >= 0)
289             {
290                 result = StateValueList[index].Value;
291                 return true;
292             }
293
294             return false;
295         }
296
297         /// <inheritdoc/>
298         [EditorBrowsable(EditorBrowsableState.Never)]
299         public override void Clear()
300         {
301             All = default;
302             base.Clear();
303         }
304
305         /// <inheritdoc/>
306         [EditorBrowsable(EditorBrowsableState.Never)]
307         public override string ToString()
308         {
309             string result = $"[All, {All}]";
310
311             foreach (var item in StateValueList)
312             {
313                 result += $", {item}";
314             }
315
316             return result;
317         }
318
319         /// <summary>
320         /// Clone itself.
321         /// If type T implements ISelectorItem, it calls Clone() method to clone values, otherwise use operator=.
322         /// </summary>
323         /// <since_tizen> 6 </since_tizen>
324         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
325         [EditorBrowsable(EditorBrowsableState.Never)]
326         public Selector<T> Clone()
327         {
328             var cloned = new Selector<T>();
329             cloned.Clone(this);
330             return cloned;
331         }
332
333         /// <summary>
334         /// Copy values from other selector.
335         /// </summary>
336         [EditorBrowsable(EditorBrowsableState.Never)]
337         public void Clone(Selector<T> other)
338         {
339             Clear();
340
341             if (isCloneable)
342             {
343                 All = (T)((ICloneable)other.All)?.Clone();
344                 foreach (var item in other.StateValueList)
345                 {
346                     AddWithoutCheck(new StateValuePair<T>(item.State, (T)((ICloneable)item.Value)?.Clone()));
347                 }
348             }
349             else
350             {
351                 All = other.All;
352                 foreach (var item in other.StateValueList)
353                 {
354                     AddWithoutCheck(item);
355                 }
356             }
357         }
358
359         internal bool HasMultiValue()
360         {
361             return StateValueList.Count > 1;
362         }
363     }
364
365     /// <summary>
366     /// This will be attached to a View to detect ControlState change.
367     /// </summary>
368     [EditorBrowsable(EditorBrowsableState.Never)]
369     public class TriggerableSelector<T>
370     {
371         /// <summary>
372         /// Create an TriggerableSelector.
373         /// </summary>
374         [EditorBrowsable(EditorBrowsableState.Never)]
375         public delegate T ValueGetter(View view);
376
377         private readonly BindableProperty targetBindableProperty;
378         private readonly ValueGetter propertyGetter;
379         private bool dirty = true;
380         private Selector<T> selector;
381
382         /// <summary>
383         /// Create an TriggerableSelector.
384         /// </summary>
385         /// <param name="targetBindableProperty">The TriggerableSelector will change this bindable property value when the view's ControlState has changed.</param>
386         /// <param name="propertyGetter">It is optional value in case the target bindable property getter is not proper to use.</param>
387         [EditorBrowsable(EditorBrowsableState.Never)]
388         public TriggerableSelector(BindableProperty targetBindableProperty, ValueGetter propertyGetter = null)
389         {
390             this.targetBindableProperty = targetBindableProperty;
391             this.propertyGetter = propertyGetter;
392         }
393
394         /// <summary>
395         /// Return the containing selector. It can be null.
396         /// </summary>
397         [EditorBrowsable(EditorBrowsableState.Never)]
398         public Selector<T> Get(View view)
399         {
400             if (!dirty) return selector;
401
402             T value = default;
403
404             if (propertyGetter != null)
405             {
406                 value = propertyGetter(view);
407             }
408             else
409             {
410                 value = (T)view.GetValue(targetBindableProperty);
411             }
412
413             Selector<T> converted = value == null ? null : new Selector<T>(value);
414             Update(view, converted);
415
416             return selector;
417         }
418
419         /// <summary>
420         /// Update containing selector from the other selector.
421         /// </summary>
422         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
423         /// <param name="otherSelector">The copy target.</param>
424         /// <param name="updateView">Whether it updates the target view after update the selector or not.</param>
425         [EditorBrowsable(EditorBrowsableState.Never)]
426         public void Update(View view, Selector<T> otherSelector, bool updateView = false)
427         {
428             Reset(view);
429
430             if (otherSelector == null)
431             {
432                 return;   
433             }
434
435             selector = otherSelector.Clone();
436
437             if (otherSelector.HasMultiValue())
438             {
439                 view.ControlStateChangeEventInternal += OnViewControlState;
440             }
441
442             if (updateView && otherSelector.GetValue(view.ControlState, out var value))
443             {
444                 view.SetValue(targetBindableProperty, value);
445             }
446         }
447
448         /// <summary>
449         /// Update containing selector value from a single value.
450         /// Note that, it updates lazily if possible.
451         /// If you need to udpate directly, please use <seealso cref="Update" />.
452         /// </summary>
453         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
454         /// <param name="value">The copy target.</param>
455         [EditorBrowsable(EditorBrowsableState.Never)]
456         public void UpdateIfNeeds(View view, T value)
457         {
458             if (selector != null && selector.HasMultiValue())
459             {
460                 Selector<T> converted = value == null ? null : new Selector<T>(value);
461                 Update(view, converted);
462                 return;
463             }
464
465             dirty = true;
466         }
467
468         /// <summary>
469         /// Reset selector and listeners.
470         /// </summary>
471         /// <param name="view">The View that is affected by this TriggerableSelector.</param>
472         [EditorBrowsable(EditorBrowsableState.Never)]
473         public void Reset(View view)
474         {
475             view.ControlStateChangeEventInternal -= OnViewControlState;
476             selector?.Clear();
477             selector = null;
478             dirty = false;
479         }
480
481         private void OnViewControlState(object obj, View.ControlStateChangedEventArgs controlStateChangedInfo)
482         {
483             View view = obj as View;
484             if (null != view && selector.GetValue(controlStateChangedInfo.CurrentState, out var value))
485             {
486                 view.SetValue(targetBindableProperty, value);
487             }
488         }
489     }
490 }