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