[NUI] Add CircularProgress to Wearable components (#1609)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Wearable / src / public / CircularProgress.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 CircularProgress class of Wearable is used to show the ongoing status with a circular bar.
27     /// CircularProgress can be counted in its percentage.
28     /// </summary>
29     [EditorBrowsable(EditorBrowsableState.Never)]
30     public class CircularProgress : 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(CircularProgress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
37         {
38             var instance = ((CircularProgress)bindable);
39
40             // TODO Set viewStyle.Thickness after style refactoring done.
41
42             instance.UpdateVisualThickness((float)newValue);
43         },
44         defaultValueCreator: (bindable) =>
45         {
46             return ((CircularProgressStyle)((CircularProgress)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(CircularProgress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
52         {
53             var instance = (CircularProgress)bindable;
54             if (newValue != null)
55             {
56                 instance.maxValue = (float)newValue;
57                 instance.UpdateValue();
58             }
59         },
60         defaultValueCreator: (bindable) =>
61         {
62             var instance = (CircularProgress)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(CircularProgress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
69         {
70             var instance = (CircularProgress)bindable;
71             if (newValue != null)
72             {
73                 instance.minValue = (float)newValue;
74                 instance.UpdateValue();
75             }
76         },
77         defaultValueCreator: (bindable) =>
78         {
79             var instance = (CircularProgress)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("currentValue", typeof(float), typeof(CircularProgress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
86         {
87             var instance = (CircularProgress)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 = (CircularProgress)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("trackColor", typeof(Color), typeof(CircularProgress), null, propertyChanged: (bindable, oldValue, newValue) =>
107         {
108             var instance = (CircularProgress)bindable;
109
110             // TODO : Set viewStyle.TrackColor after style refactoring done.
111
112             instance.UpdateTrackVisualColor((Color)newValue);
113         },
114         defaultValueCreator: (bindable) =>
115         {
116             return ((CircularProgressStyle)((CircularProgress)bindable).viewStyle)?.TrackColor;
117         });
118
119         /// <summary>Bindable property of ProgressColor</summary>
120         [EditorBrowsable(EditorBrowsableState.Never)]
121         public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create("progressColor", typeof(Color), typeof(CircularProgress), null, propertyChanged: (bindable, oldValue, newValue) =>
122         {
123             var instance = (CircularProgress)bindable;
124
125             // TODO : Set viewStyle.ProgressColor after style refactoring done.
126
127             instance.UpdateProgressVisualColor((Color)newValue);
128         },
129         defaultValueCreator: (bindable) =>
130         {
131             return ((CircularProgressStyle)((CircularProgress)bindable).viewStyle)?.ProgressColor;
132         });
133
134         /// <summary>Bindable property of IsEnabled</summary>
135         [EditorBrowsable(EditorBrowsableState.Never)]
136         public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool), typeof(CircularProgress), true, propertyChanged: (bindable, oldValue, newValue) =>
137         {
138             var instance = (CircularProgress)bindable;
139             if (newValue != null)
140             {
141                 instance.privateIsEnabled = (bool)newValue;
142             }
143         },
144         defaultValueCreator: (bindable) =>
145         {
146             var instance = (CircularProgress)bindable;
147             return instance.privateIsEnabled;
148         });
149
150         private static readonly string TrackVisualName = "Track";
151         private static readonly string ProgressVisualName = "Progress";
152         private ArcVisual trackVisual;
153         private ArcVisual progressVisual;
154
155         private float maxValue = 100;
156         private float minValue = 0;
157         private float currentValue = 0;
158         private bool isEnabled = true;
159
160         private Animation sweepAngleAnimation;
161
162         /// <summary>
163         /// Get style of progress.
164         /// </summary>
165         [EditorBrowsable(EditorBrowsableState.Never)]
166         public new CircularProgressStyle Style => ViewStyle as CircularProgressStyle;
167
168         #endregion Fields
169
170
171         #region Constructors
172
173         static CircularProgress() { }
174         /// <summary>
175         /// The constructor of CircularProgress.
176         /// Basically, CircularProgress is for full screen. (360 x 360)
177         /// But, it also can be displayed on the button or the list for small size.
178         /// User can set its size.
179         /// </summary>
180         [EditorBrowsable(EditorBrowsableState.Never)]
181         public CircularProgress() : base(new CircularProgressStyle())
182         {
183             Initialize();
184         }
185
186         /// <summary>
187         /// The constructor of the CircularProgress class with specific style.
188         /// </summary>
189         /// <param name="style">style name</param>
190         [EditorBrowsable(EditorBrowsableState.Never)]
191         public CircularProgress(string style) : base(style)
192         {
193             Initialize();
194         }
195
196         /// <summary>
197         /// The constructor of the CircularProgress class with specific style.
198         /// </summary>
199         /// <param name="progressStyle">The style object to initialize the CircularProgress.</param>
200         [EditorBrowsable(EditorBrowsableState.Never)]
201         public CircularProgress(CircularProgressStyle progressStyle) : base(progressStyle)
202         {
203             Initialize();
204         }
205
206         #endregion Constructors
207
208
209         #region Properties
210
211         /// <summary>
212         /// The thickness of the track and progress.
213         /// </summary>
214         [EditorBrowsable(EditorBrowsableState.Never)]
215         public float Thickness
216         {
217             get
218             {
219                 return (float)GetValue(ThicknessProperty);
220             }
221             set
222             {
223                 SetValue(ThicknessProperty, value);
224             }
225         }
226
227         /// <summary>
228         /// The property to get/set the maximum value of the CircularProgress.
229         /// The default value is 100.
230         /// </summary>
231         [EditorBrowsable(EditorBrowsableState.Never)]
232         public float MaxValue
233         {
234             get
235             {
236                 return (float)GetValue(MaxValueProperty);
237             }
238             set
239             {
240                 SetValue(MaxValueProperty, value);
241             }
242         }
243
244         /// <summary>
245         /// The property to get/set the minimum value of the CircularProgress.
246         /// The default value is 0.
247         /// </summary>
248         [EditorBrowsable(EditorBrowsableState.Never)]
249         public float MinValue
250         {
251             get
252             {
253                 return (float)GetValue(MinValueProperty);
254             }
255             set
256             {
257                 SetValue(MinValueProperty, value);
258             }
259         }
260
261         /// <summary>
262         /// The property to get/set the current value of the CircularProgress.
263         /// The default value is 0.
264         /// </summary>
265         [EditorBrowsable(EditorBrowsableState.Never)]
266         public float CurrentValue
267         {
268             get
269             {
270                 return (float)GetValue(CurrentValueProperty);
271             }
272             set
273             {
274                 sweepAngleAnimation = AnimateVisual(progressVisual, "sweepAngle", progressVisual.SweepAngle, 0, 100, AlphaFunction.BuiltinFunctions.EaseIn);
275
276                 SetValue(CurrentValueProperty, value);
277
278                 UpdateAnimation();
279             }
280         }
281
282         /// <summary>
283         /// The property to get/set Track object color of the CircularProgress.
284         /// </summary>
285         [EditorBrowsable(EditorBrowsableState.Never)]
286         public Color TrackColor
287         {
288             get
289             {
290                 return (Color)GetValue(TrackColorProperty);
291             }
292             set
293             {
294                 SetValue(TrackColorProperty, value);
295             }
296         }
297
298         /// <summary>
299         /// The property to get/set Progress object color of the CircularProgress.
300         /// </summary>
301         [EditorBrowsable(EditorBrowsableState.Never)]
302         public Color ProgressColor
303         {
304             get
305             {
306                 return (Color)GetValue(ProgressColorProperty);
307             }
308             set
309             {
310                 SetValue(ProgressColorProperty, value);
311             }
312         }
313
314         /// <summary>
315         /// Flag to be enabled or disabled in CircularProgress.
316         /// </summary>
317         [EditorBrowsable(EditorBrowsableState.Never)]
318         public bool IsEnabled
319         {
320             get
321             {
322                 return (bool)GetValue(IsEnabledProperty);
323             }
324             set
325             {
326                 SetValue(IsEnabledProperty, value);
327             }
328         }
329         private bool privateIsEnabled
330         {
331             get
332             {
333                 return isEnabled;
334             }
335             set
336             {
337                 isEnabled = value;
338                 if(isEnabled)
339                 {
340                     UpdateTrackVisualColor(new Color(0.0f, 0.16f, 0.30f, 1.0f)); // #002A4D
341                 }
342                 else
343                 {
344                     UpdateTrackVisualColor(new Color(0.25f, 0.25f, 0.25f, 1.0f)); // #404040
345                 }
346             }
347         }
348
349         #endregion Properties
350
351
352
353         #region Methods
354
355         /// <summary>
356         /// Dispose Progress and all children on it.
357         /// </summary>
358         /// <param name="type">Dispose type.</param>
359         [EditorBrowsable(EditorBrowsableState.Never)]
360         protected override void Dispose(DisposeTypes type)
361         {
362             if (disposed)
363             {
364                 return;
365             }
366
367             if (type == DisposeTypes.Explicit)
368             {
369                 trackVisual = null;
370                 progressVisual = null;
371             }
372
373             base.Dispose(type);
374         }
375
376         /// <summary>
377         /// Update progress value
378         /// </summary>
379         [EditorBrowsable(EditorBrowsableState.Never)]
380         protected virtual void UpdateValue()
381         {
382             if (null == trackVisual || null == progressVisual)
383             {
384                 return;
385             }
386
387             if (minValue >= maxValue || currentValue < minValue || currentValue > maxValue)
388             {
389                 return;
390             }
391
392             HandleProgressVisualVisibility();
393
394             UpdateProgressVisualSweepAngle();
395         }
396
397         /// <summary>
398         /// Get Progress style.
399         /// </summary>
400         /// <returns>The default progress style.</returns>
401         [EditorBrowsable(EditorBrowsableState.Never)]
402         protected override ViewStyle GetViewStyle()
403         {
404             return new CircularProgressStyle();
405         }
406
407
408         private void Initialize()
409         {
410             Size = new Size(360.0f, 360.0f);
411
412             sweepAngleAnimation?.Stop();
413             sweepAngleAnimation = null;
414
415             trackVisual = new ArcVisual
416             {
417                 SuppressUpdateVisual = true,
418                 Thickness = this.Thickness,
419                 Cap = ArcVisual.CapType.Butt,
420                 MixColor = TrackColor,
421                 StartAngle = 0.0f,
422                 SweepAngle = 360.0f
423             };
424             this.AddVisual(TrackVisualName, trackVisual);
425
426             progressVisual = new ArcVisual
427             {
428                 SuppressUpdateVisual = true,
429                 Thickness = this.Thickness,
430                 Cap = ArcVisual.CapType.Butt,
431                 MixColor = ProgressColor,
432                 StartAngle = 0.0f,
433                 SweepAngle = 0.0f
434             };
435             this.AddVisual(ProgressVisualName, progressVisual);
436
437             HandleProgressVisualVisibility();
438
439             UpdateProgressVisualSweepAngle();
440         }
441
442         private void HandleProgressVisualVisibility()
443         {
444             if (minValue == currentValue)
445             {
446                 progressVisual.Opacity = 0.0f;
447             }
448             else if (isEnabled)
449             {
450                 progressVisual.Opacity = 1.0f;
451             }
452             else if (!isEnabled)
453             {
454                 progressVisual.Opacity = 0.6f;
455             }
456         }
457
458         private void UpdateVisualThickness(float thickness)
459         {
460             if (trackVisual == null)
461             {
462                 return;
463             }
464
465             trackVisual.Thickness = thickness;
466             progressVisual.Thickness = thickness;
467
468             trackVisual.UpdateVisual(true);
469             progressVisual.UpdateVisual(true);
470         }
471
472         private void UpdateProgressVisualSweepAngle()
473         {
474             float progressRatio = (float)(currentValue - minValue) / (float)(maxValue - minValue);
475             float progressWidth = 360.0f * progressRatio; // Circle
476             progressVisual.SweepAngle = progressWidth;
477
478             if (!sweepAngleAnimation)
479             {
480                 progressVisual.UpdateVisual(true);
481             }
482         }
483
484         private void UpdateAnimation()
485         {
486             // TODO : Currently not sure which effect is needed.
487             AlphaFunction.BuiltinFunctions builtinAlphaFunction = AlphaFunction.BuiltinFunctions.EaseIn;
488
489             if (sweepAngleAnimation)
490             {
491                 sweepAngleAnimation?.Stop();
492             }
493
494             sweepAngleAnimation = AnimateVisual(progressVisual, "sweepAngle", progressVisual.SweepAngle, 0, 100, builtinAlphaFunction);
495
496             if (sweepAngleAnimation)
497             {
498                 sweepAngleAnimation.Play();
499             }
500
501         }
502
503         private void UpdateTrackVisualColor(Color trackColor)
504         {
505             if (trackVisual == null)
506             {
507                 return;
508             }
509
510             trackVisual.MixColor = trackColor;
511             trackVisual.UpdateVisual(true);
512         }
513
514         private void UpdateProgressVisualColor(Color progressColor)
515         {
516             if (progressVisual == null)
517             {
518                 return;
519             }
520
521             progressVisual.MixColor = progressColor;
522             if( !isEnabled ) // Dim state
523             {
524                 progressVisual.Opacity = 0.6f;
525             }
526
527             progressVisual.UpdateVisual(true);
528         }
529
530         #endregion Methods
531     }
532 }