[NUI] introduce CreateViewStyle that is alternative to GetViewStyle (#1681)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Wearable / src / public / CircularSlider.cs
1 /*
2  * Copyright(c) 2020 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 Tizen.NUI.BaseComponents;
19 using Tizen.NUI.Binding;
20 using Tizen.NUI.Components;
21 using System.ComponentModel;
22
23 namespace Tizen.NUI.Wearable
24 {
25     /// <summary>
26     /// The CircularSlider class of Wearable is used to let users select a value from a continuous or discrete range of values by moving the slider thumb.
27     /// CircularSlider shows the current value with the length of the line.
28     /// </summary>
29     [EditorBrowsable(EditorBrowsableState.Never)]
30     public class CircularSlider : Control
31     {
32         #region Fields
33
34         /// <summary>Bindable property of Thickness</summary>
35         [EditorBrowsable(EditorBrowsableState.Never)]
36         public static readonly BindableProperty ThicknessProperty = BindableProperty.Create(nameof(Thickness), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
37         {
38             var instance = ((CircularSlider)bindable);
39
40             // TODO Set viewStyle.Thickness after style refactoring done.
41
42             instance.UpdateVisualThickness((float)newValue);
43         },
44         defaultValueCreator: (bindable) =>
45         {
46             return ((CircularSliderStyle)((CircularSlider)bindable).viewStyle)?.Thickness;
47         });
48
49         /// <summary>Bindable property of MaxValue</summary>
50         [EditorBrowsable(EditorBrowsableState.Never)]
51         public static readonly BindableProperty MaxValueProperty = BindableProperty.Create(nameof(MaxValue), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
52         {
53             var instance = (CircularSlider)bindable;
54             if (newValue != null)
55             {
56                 instance.maxValue = (float)newValue;
57                 instance.UpdateValue();
58             }
59         },
60         defaultValueCreator: (bindable) =>
61         {
62             var instance = (CircularSlider)bindable;
63             return instance.maxValue;
64         });
65
66         /// <summary>Bindable property of MinValue</summary>
67         [EditorBrowsable(EditorBrowsableState.Never)]
68         public static readonly BindableProperty MinValueProperty = BindableProperty.Create(nameof(MinValue), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
69         {
70             var instance = (CircularSlider)bindable;
71             if (newValue != null)
72             {
73                 instance.minValue = (float)newValue;
74                 instance.UpdateValue();
75             }
76         },
77         defaultValueCreator: (bindable) =>
78         {
79             var instance = (CircularSlider)bindable;
80             return instance.minValue;
81         });
82
83         /// <summary>Bindable property of CurrentValue</summary>
84         [EditorBrowsable(EditorBrowsableState.Never)]
85         public static readonly BindableProperty CurrentValueProperty = BindableProperty.Create(nameof(CurrentValue), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
86         {
87             var instance = (CircularSlider)bindable;
88             if (newValue != null)
89             {
90                 if ((float)newValue > instance.maxValue || (float)newValue < instance.minValue)
91                 {
92                     return;
93                 }
94                 instance.currentValue = (float)newValue;
95                 instance.UpdateValue();
96             }
97         },
98         defaultValueCreator: (bindable) =>
99         {
100             var instance = (CircularSlider)bindable;
101             return instance.currentValue;
102         });
103
104         /// <summary>Bindable property of TrackColor</summary>
105         [EditorBrowsable(EditorBrowsableState.Never)]
106         public static readonly BindableProperty TrackColorProperty = BindableProperty.Create(nameof(TrackColor), typeof(Color), typeof(CircularSlider), null, propertyChanged: (bindable, oldValue, newValue) =>
107         {
108             var instance = (CircularSlider)bindable;
109
110             // TODO : Set viewStyle.TrackColor after style refactoring done.
111
112             instance.UpdateTrackVisualColor((Color)newValue);
113         },
114         defaultValueCreator: (bindable) =>
115         {
116             return ((CircularSliderStyle)((CircularSlider)bindable).viewStyle)?.TrackColor;
117         });
118
119         /// <summary>Bindable property of ProgressColor</summary>
120         [EditorBrowsable(EditorBrowsableState.Never)]
121         public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(nameof(ProgressColor), typeof(Color), typeof(CircularSlider), null, propertyChanged: (bindable, oldValue, newValue) =>
122         {
123             var instance = (CircularSlider)bindable;
124
125             // TODO : Set viewStyle.ProgressColor after style refactoring done.
126
127             instance.UpdateProgressVisualColor((Color)newValue);
128         },
129         defaultValueCreator: (bindable) =>
130         {
131             return ((CircularSliderStyle)((CircularSlider)bindable).viewStyle)?.ProgressColor;
132         });
133
134         /// <summary>Bindable property of ThumbSize</summary>
135         [EditorBrowsable(EditorBrowsableState.Never)]
136         public static readonly BindableProperty ThumbSizeProperty = BindableProperty.Create(nameof(ThumbSize), typeof(Size), typeof(CircularSlider), new Size(0,0), propertyChanged: (bindable, oldValue, newValue) =>
137         {
138             var instance = (CircularSlider)bindable;
139             if (newValue != null)
140             {
141                 instance.thumbSize = (Size)newValue;
142                 instance.UpdateThumbVisualSize((Size)newValue);
143             }
144         },
145         defaultValueCreator: (bindable) =>
146         {
147             var instance = (CircularSlider)bindable;
148             return instance.thumbSize;
149         });
150
151         /// <summary>Bindable property of ThumbColor</summary>
152         [EditorBrowsable(EditorBrowsableState.Never)]
153         public static readonly BindableProperty ThumbColorProperty = BindableProperty.Create(nameof(ThumbColor), typeof(Color), typeof(CircularSlider), null, propertyChanged: (bindable, oldValue, newValue) =>
154         {
155             var instance = (CircularSlider)bindable;
156
157             // TODO : Set viewStyle.ThumbColor after style refactoring done.
158
159             instance.UpdateThumbVisualColor((Color)newValue);
160         },
161         defaultValueCreator: (bindable) =>
162         {
163             return ((CircularSliderStyle)((CircularSlider)bindable).viewStyle)?.ThumbColor;
164         });
165
166         /// <summary>Bindable property of IsEnabled</summary>
167         [EditorBrowsable(EditorBrowsableState.Never)]
168         public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool), typeof(CircularSlider), true, propertyChanged: (bindable, oldValue, newValue) =>
169         {
170             var instance = (CircularSlider)bindable;
171             if (newValue != null)
172             {
173                 instance.privateIsEnabled = (bool)newValue;
174             }
175         },
176         defaultValueCreator: (bindable) =>
177         {
178             var instance = (CircularSlider)bindable;
179             return instance.privateIsEnabled;
180         });
181
182         private const string TrackVisualName = "Track";
183         private const string ProgressVisualName = "Progress";
184         private const string ThumbVisualName = "Thumb";
185         private ArcVisual trackVisual;
186         private ArcVisual progressVisual;
187         private ArcVisual thumbVisual;
188
189         private float maxValue = 100;
190         private float minValue = 0;
191         private float currentValue = 0;
192         private Size thumbSize;
193         private bool isEnabled = true;
194
195         float sliderPadding = 6.0f;
196
197         private Animation sweepAngleAnimation;
198         private Animation thumbAnimation;
199
200         /// <summary>
201         /// Get style of progress.
202         /// </summary>
203         [EditorBrowsable(EditorBrowsableState.Never)]
204         public new CircularSliderStyle Style => ViewStyle as CircularSliderStyle;
205
206         /// <summary>
207         /// Value Changed event data.
208         /// </summary>
209         [EditorBrowsable(EditorBrowsableState.Never)]
210         public class ValueChangedEventArgs : EventArgs
211         {
212             private float currentValue = 0.0f;
213
214             /// <summary>
215             /// Current value
216             /// </summary>
217             [EditorBrowsable(EditorBrowsableState.Never)]
218             public float CurrentValue
219             {
220                 get { return currentValue; }
221                 set { currentValue = value; }
222             }
223         }
224
225         #endregion Fields
226
227
228         #region Constructors
229
230         static CircularSlider() { }
231         /// <summary>
232         /// The constructor of CircularSlider.
233         /// </summary>
234         [EditorBrowsable(EditorBrowsableState.Never)]
235         public CircularSlider() : base(new CircularSliderStyle())
236         {
237             Initialize();
238         }
239
240         /// <summary>
241         /// The constructor of the CircularSlider class with specific style.
242         /// </summary>
243         /// <param name="progressStyle">The style object to initialize the CircularSlider.</param>
244         [EditorBrowsable(EditorBrowsableState.Never)]
245         public CircularSlider(CircularSliderStyle progressStyle) : base(progressStyle)
246         {
247             Initialize();
248         }
249
250         #endregion Constructors
251
252         #region Events
253
254         /// <summary>
255         /// The value changed event handler.
256         /// </summary>
257         [EditorBrowsable(EditorBrowsableState.Never)]
258         public event EventHandler<ValueChangedEventArgs> ValueChanged;
259
260         #endregion Events
261
262         #region Properties
263
264         /// <summary>
265         /// The thickness of the track and progress.
266         /// </summary>
267         [EditorBrowsable(EditorBrowsableState.Never)]
268         public float Thickness
269         {
270             get
271             {
272                 return (float)GetValue(ThicknessProperty);
273             }
274             set
275             {
276                 SetValue(ThicknessProperty, value);
277             }
278         }
279
280         /// <summary>
281         /// The property to get/set the maximum value of the CircularSlider.
282         /// The default value is 100.
283         /// </summary>
284         [EditorBrowsable(EditorBrowsableState.Never)]
285         public float MaxValue
286         {
287             get
288             {
289                 return (float)GetValue(MaxValueProperty);
290             }
291             set
292             {
293                 SetValue(MaxValueProperty, value);
294             }
295         }
296
297         /// <summary>
298         /// The property to get/set the minimum value of the CircularSlider.
299         /// The default value is 0.
300         /// </summary>
301         [EditorBrowsable(EditorBrowsableState.Never)]
302         public float MinValue
303         {
304             get
305             {
306                 return (float)GetValue(MinValueProperty);
307             }
308             set
309             {
310                 SetValue(MinValueProperty, value);
311             }
312         }
313
314         /// <summary>
315         /// The property to get/set the current value of the CircularSlider.
316         /// The default value is 0.
317         /// </summary>
318         [EditorBrowsable(EditorBrowsableState.Never)]
319         public float CurrentValue
320         {
321             get
322             {
323                 return (float)GetValue(CurrentValueProperty);
324             }
325             set
326             {
327                 if (sweepAngleAnimation)
328                 {
329                     sweepAngleAnimation.Stop();
330                 }
331                 // For the first Animation effect
332                 sweepAngleAnimation = AnimateVisual(progressVisual, "sweepAngle", progressVisual.SweepAngle, 0, 100, AlphaFunction.BuiltinFunctions.EaseIn);
333
334                 SetValue(CurrentValueProperty, value);
335
336                 UpdateAnimation();
337             }
338         }
339
340         /// <summary>
341         /// The property to get/set Track object color of the CircularSlider.
342         /// </summary>
343         [EditorBrowsable(EditorBrowsableState.Never)]
344         public Color TrackColor
345         {
346             get
347             {
348                 return (Color)GetValue(TrackColorProperty);
349             }
350             set
351             {
352                 SetValue(TrackColorProperty, value);
353             }
354         }
355
356         /// <summary>
357         /// The property to get/set Progress object color of the CircularSlider.
358         /// </summary>
359         [EditorBrowsable(EditorBrowsableState.Never)]
360         public Color ProgressColor
361         {
362             get
363             {
364                 return (Color)GetValue(ProgressColorProperty);
365             }
366             set
367             {
368                 SetValue(ProgressColorProperty, value);
369             }
370         }
371
372         /// <summary>
373         /// Gets or sets the size of the thumb of Slider.
374         /// </summary>
375         [EditorBrowsable(EditorBrowsableState.Never)]
376         public Size ThumbSize
377         {
378             get
379             {
380                 return (Size)GetValue(ThumbSizeProperty);
381             }
382             set
383             {
384                 SetValue(ThumbSizeProperty, value);
385             }
386         }
387
388         /// <summary>
389         /// The property to get/set Thumb object color of the CircularSlider.
390         /// </summary>
391         [EditorBrowsable(EditorBrowsableState.Never)]
392         public Color ThumbColor
393         {
394             get
395             {
396                 return (Color)GetValue(ThumbColorProperty);
397             }
398             set
399             {
400                 SetValue(ThumbColorProperty, value);
401             }
402         }
403
404         /// <summary>
405         /// Flag to be enabled or disabled in CircularSlider.
406         /// </summary>
407         [EditorBrowsable(EditorBrowsableState.Never)]
408         public bool IsEnabled
409         {
410             get
411             {
412                 return (bool)GetValue(IsEnabledProperty);
413             }
414             set
415             {
416                 SetValue(IsEnabledProperty, value);
417             }
418         }
419         private bool privateIsEnabled
420         {
421             get
422             {
423                 return isEnabled;
424             }
425             set
426             {
427                 isEnabled = value;
428                 if (isEnabled)
429                 {
430                     UpdateTrackVisualColor(new Color(0.0f, 0.16f, 0.30f, 1.0f)); // #002A4D
431                 }
432                 else
433                 {
434                     UpdateTrackVisualColor(new Color(0.25f, 0.25f, 0.25f, 1.0f)); // #404040
435                 }
436             }
437         }
438
439         #endregion Properties
440
441
442         #region Methods
443
444         /// <summary>
445         /// Dispose Progress and all children on it.
446         /// </summary>
447         /// <param name="type">Dispose type.</param>
448         [EditorBrowsable(EditorBrowsableState.Never)]
449         protected override void Dispose(DisposeTypes type)
450         {
451             if (disposed)
452             {
453                 return;
454             }
455
456             if (type == DisposeTypes.Explicit)
457             {
458                 trackVisual = null;
459                 progressVisual = null;
460                 thumbVisual = null;
461             }
462
463             base.Dispose(type);
464         }
465
466         /// <summary>
467         /// Update progress value
468         /// </summary>
469         [EditorBrowsable(EditorBrowsableState.Never)]
470         protected virtual void UpdateValue()
471         {
472             if (null == trackVisual || null == progressVisual || null == thumbVisual)
473             {
474                 return;
475             }
476
477             if (minValue >= maxValue || currentValue < minValue || currentValue > maxValue)
478             {
479                 return;
480             }
481
482             HandleProgressVisualVisibility();
483
484             UpdateProgressVisualSweepAngle();
485         }
486
487         /// <summary>
488         /// Get Progress style.
489         /// </summary>
490         /// <returns>The default progress style.</returns>
491         [EditorBrowsable(EditorBrowsableState.Never)]
492         protected override ViewStyle CreateViewStyle()
493         {
494             return new CircularSliderStyle();
495         }
496
497         private void Initialize()
498         {
499             Size = new Size(360.0f, 360.0f);
500
501             sweepAngleAnimation?.Stop();
502             sweepAngleAnimation = null;
503
504             thumbAnimation?.Stop();
505             thumbAnimation = null;
506
507             trackVisual = new ArcVisual
508             {
509                 SuppressUpdateVisual = true,
510                 Size = new Size(this.Size.Width - sliderPadding, this.Size.Height - sliderPadding),
511                 SizePolicy = VisualTransformPolicyType.Absolute,
512                 Thickness = this.Thickness,
513                 Cap = ArcVisual.CapType.Butt,
514                 MixColor = TrackColor,
515                 StartAngle = 0.0f,
516                 SweepAngle = 360.0f
517             };
518             this.AddVisual(TrackVisualName, trackVisual);
519
520             progressVisual = new ArcVisual
521             {
522                 SuppressUpdateVisual = true,
523                 Size = new Size(this.Size.Width - sliderPadding, this.Size.Height - sliderPadding),
524                 SizePolicy = VisualTransformPolicyType.Absolute,
525                 Thickness = this.Thickness,
526                 Cap = ArcVisual.CapType.Butt,
527                 MixColor = ProgressColor,
528                 StartAngle = 0.0f,
529                 SweepAngle = 0.0f
530             };
531             this.AddVisual(ProgressVisualName, progressVisual);
532
533             thumbVisual = new ArcVisual
534             {
535                 SuppressUpdateVisual = true,
536                 Size = new Size(this.Size.Width + sliderPadding, this.Size.Height + sliderPadding),
537                 SizePolicy = VisualTransformPolicyType.Absolute,
538                 Thickness = this.ThumbSize.Width,
539                 Cap = ArcVisual.CapType.Round,
540                 MixColor = this.ThumbColor,
541                 StartAngle = 0.0f,
542                 SweepAngle = 0.0f
543             };
544             this.AddVisual(ThumbVisualName,  thumbVisual);
545
546             HandleProgressVisualVisibility();
547
548             UpdateProgressVisualSweepAngle();
549         }
550
551         private void HandleProgressVisualVisibility()
552         {
553             if (isEnabled)
554             {
555                 progressVisual.Opacity = 1.0f;
556             }
557             else if (!isEnabled)
558             {
559                 progressVisual.Opacity = 0.6f;
560             }
561         }
562
563         private void UpdateVisualThickness(float thickness)
564         {
565             if (trackVisual == null)
566             {
567                 return;
568             }
569
570             trackVisual.Thickness = thickness;
571             progressVisual.Thickness = thickness;
572
573             trackVisual.UpdateVisual(true);
574             progressVisual.UpdateVisual(true);
575         }
576
577         private void UpdateProgressVisualSweepAngle()
578         {
579             float progressRatio = (float)(currentValue - minValue) / (float)(maxValue - minValue);
580             float progressWidth = 360.0f * progressRatio; // Circle
581
582             progressVisual.SweepAngle = progressWidth;
583             thumbVisual.StartAngle = progressWidth;
584
585             if (!sweepAngleAnimation)
586             {
587                 progressVisual.UpdateVisual(true);
588                 thumbVisual.UpdateVisual(true);
589             }
590         }
591
592         private void UpdateAnimation()
593         {
594             // TODO : Currently not sure which effect is needed.
595             AlphaFunction.BuiltinFunctions builtinAlphaFunction = AlphaFunction.BuiltinFunctions.EaseOut;
596
597             if (sweepAngleAnimation)
598             {
599                 sweepAngleAnimation.Stop();
600             }
601             if (thumbAnimation)
602             {
603                 thumbAnimation.Stop();
604             }
605
606             sweepAngleAnimation = AnimateVisual(progressVisual, "sweepAngle", progressVisual.SweepAngle, 0, 500, builtinAlphaFunction);
607             thumbAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, 500, builtinAlphaFunction);
608
609             if (sweepAngleAnimation)
610             {
611                 sweepAngleAnimation.Play();
612                 thumbAnimation.Play();
613             }
614
615             ValueChanged?.Invoke(this, new ValueChangedEventArgs() { CurrentValue = currentValue });
616         }
617
618         private void UpdateTrackVisualColor(Color trackColor)
619         {
620             if (trackVisual == null)
621             {
622                 return;
623             }
624
625             trackVisual.MixColor = trackColor;
626             trackVisual.UpdateVisual(true);
627         }
628
629         private void UpdateProgressVisualColor(Color progressColor)
630         {
631             if (progressVisual == null)
632             {
633                 return;
634             }
635
636             progressVisual.MixColor = progressColor;
637             if (!isEnabled) // Dim state
638             {
639                 progressVisual.Opacity = 0.6f;
640             }
641
642             progressVisual.UpdateVisual(true);
643         }
644
645         private void UpdateThumbVisualSize(Size thumbSize)
646         {
647             if (thumbVisual == null)
648             {
649                 return;
650             }
651
652             thumbVisual.Thickness = thumbSize.Width;
653             thumbVisual.UpdateVisual(true);
654         }
655
656         private void UpdateThumbVisualColor(Color thumbColor)
657         {
658             if (thumbVisual == null)
659             {
660                 return;
661             }
662
663             thumbVisual.MixColor = thumbColor;
664             thumbVisual.UpdateVisual(true);
665         }
666
667
668         #endregion Methods
669     }
670 }