[NUI] Fix Progress size issue (#3196)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Progress.cs
1 /*
2  * Copyright(c) 2021 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 System.ComponentModel;
21 using System.Diagnostics;
22
23 namespace Tizen.NUI.Components
24 {
25     /// <summary>
26     /// The Progress class is used to show the ongoing status with a long narrow bar.
27     /// </summary>
28     /// <since_tizen> 6 </since_tizen>
29     public class Progress : Control
30     {
31         /// <summary>
32         /// MaxValueProperty
33         /// </summary>
34         [EditorBrowsable(EditorBrowsableState.Never)]
35         public static readonly BindableProperty MaxValueProperty = BindableProperty.Create(nameof(MaxValue), typeof(float), typeof(Progress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
36         {
37             var instance = (Progress)bindable;
38             if (newValue != null)
39             {
40                 instance.maxValue = (float)newValue;
41                 instance.UpdateValue();
42             }
43         },
44         defaultValueCreator: (bindable) =>
45         {
46             var instance = (Progress)bindable;
47             return instance.maxValue;
48         });
49
50         /// <summary>
51         /// MinValueProperty
52         /// </summary>
53         [EditorBrowsable(EditorBrowsableState.Never)]
54         public static readonly BindableProperty MinValueProperty = BindableProperty.Create(nameof(MinValue), typeof(float), typeof(Progress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
55         {
56             var instance = (Progress)bindable;
57             if (newValue != null)
58             {
59                 instance.minValue = (float)newValue;
60                 instance.UpdateValue();
61             }
62         },
63         defaultValueCreator: (bindable) =>
64         {
65             var instance = (Progress)bindable;
66             return instance.minValue;
67         });
68
69         /// <summary>
70         /// CurrentValueProperty
71         /// </summary>
72         [EditorBrowsable(EditorBrowsableState.Never)]
73         public static readonly BindableProperty CurrentValueProperty = BindableProperty.Create(nameof(CurrentValue), typeof(float), typeof(Progress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
74         {
75             var instance = (Progress)bindable;
76             if (newValue != null)
77             {
78                 if ((float)newValue > instance.maxValue || (float)newValue < instance.minValue)
79                 {
80                     return;
81                 }
82                 instance.currentValue = (float)newValue;
83                 instance.UpdateValue();
84             }
85         },
86         defaultValueCreator: (bindable) =>
87         {
88             var instance = (Progress)bindable;
89             return instance.currentValue;
90         });
91
92         /// <summary>
93         /// BufferValueProperty
94         /// </summary>
95         [EditorBrowsable(EditorBrowsableState.Never)]
96         public static readonly BindableProperty BufferValueProperty = BindableProperty.Create(nameof(BufferValue), typeof(float), typeof(Progress), default(float), propertyChanged: (bindable, oldValue, newValue) =>
97         {
98             var instance = (Progress)bindable;
99             if (newValue != null)
100             {
101                 if ((float)newValue > instance.maxValue || (float)newValue < instance.minValue)
102                 {
103                     return;
104                 }
105                 instance.bufferValue = (float)newValue;
106                 instance.UpdateValue();
107             }
108         },
109         defaultValueCreator: (bindable) =>
110         {
111             var instance = (Progress)bindable;
112             return instance.bufferValue;
113         });
114
115         /// <summary>
116         /// ProgressStateProperty
117         /// </summary>
118         [EditorBrowsable(EditorBrowsableState.Never)]
119         public static readonly BindableProperty ProgressStateProperty = BindableProperty.Create(nameof(ProgressState), typeof(ProgressStatusType), typeof(Progress), ProgressStatusType.Indeterminate, propertyChanged: (bindable, oldValue, newValue) =>
120         {
121             var instance = (Progress)bindable;
122             if (newValue != null)
123             {
124                 instance.state = (ProgressStatusType)newValue;
125                 instance.UpdateStates();
126             }
127         },
128         defaultValueCreator: (bindable) =>
129         {
130             var instance = (Progress)bindable;
131             return instance.state;
132         });
133
134         /// This needs to be considered more if public-open is necessary.
135         private ProgressStatusType state = ProgressStatusType.Determinate;
136
137         private Vector2 size = null;
138         private const float round = 0.5f;
139         private ImageView trackImage = null;
140         private ImageView progressImage = null;
141         private ImageView bufferImage = null;
142         private ImageVisual indeterminateImage = null;
143         private float maxValue = 100;
144         private float minValue = 0;
145         private float currentValue = 0;
146         private float bufferValue = 0;
147         private Animation indeterminateAnimation = null;
148
149         static Progress() { }
150         /// <summary>
151         /// The constructor of Progress
152         /// </summary>
153         /// <since_tizen> 6 </since_tizen>
154         public Progress() : base()
155         {
156             Initialize();
157         }
158
159         /// <summary>
160         /// The constructor of the Progress class with specific style.
161         /// </summary>
162         /// <param name="style">style name</param>
163         /// <since_tizen> 8 </since_tizen>
164         public Progress(string style) : base(style)
165         {
166             Initialize();
167         }
168
169         /// <summary>
170         /// The constructor of the Progress class with specific style.
171         /// </summary>
172         /// <param name="progressStyle">The style object to initialize the Progress.</param>
173         /// <since_tizen> 8 </since_tizen>
174         public Progress(ProgressStyle progressStyle) : base(progressStyle)
175         {
176             Initialize();
177         }
178
179         /// <summary>
180         /// Prevents from showing child widgets in AT-SPI tree.
181         /// </summary>
182         [EditorBrowsable(EditorBrowsableState.Never)]
183         protected override bool AccessibilityShouldReportZeroChildren()
184         {
185             return true;
186         }
187
188         /// <summary>
189         /// Minimum value.
190         /// </summary>
191         [EditorBrowsable(EditorBrowsableState.Never)]
192         protected override double AccessibilityGetMinimum()
193         {
194             if (this.ProgressState == Components.Progress.ProgressStatusType.Determinate)
195                 return (double)MinValue;
196             else return 0.0;
197         }
198
199         /// <summary>
200         /// Current value.
201         /// </summary>
202         [EditorBrowsable(EditorBrowsableState.Never)]
203         protected override double AccessibilityGetCurrent()
204         {
205             if (this.ProgressState == Components.Progress.ProgressStatusType.Determinate)
206                 return (double)CurrentValue;
207             else return 0.0;
208         }
209
210         /// <summary>
211         /// Maximum value.
212         /// </summary>
213         [EditorBrowsable(EditorBrowsableState.Never)]
214         protected override double AccessibilityGetMaximum()
215         {
216             if (this.ProgressState == Components.Progress.ProgressStatusType.Determinate)
217                 return (double)MaxValue;
218             else return 0.0;
219         }
220
221         /// <summary>
222         /// The status type of the Progress.
223         /// </summary>
224         /// <since_tizen> 6 </since_tizen>
225         public enum ProgressStatusType
226         {
227             /// <summary>
228             /// Show BufferImage
229             /// </summary>
230             /// <since_tizen> 6 </since_tizen>
231             Buffering,
232
233             /// <summary>
234             /// Show ProgressImage and BufferImage
235             /// </summary>
236             /// <since_tizen> 6 </since_tizen>
237             Determinate,
238
239             /// <summary>
240             /// Show TrackImage
241             /// </summary>
242             /// <since_tizen> 6 </since_tizen>
243             Indeterminate
244         }
245
246         /// <summary>
247         /// Return currently applied style.
248         /// </summary>
249         /// <remarks>
250         /// Modifying contents in style may cause unexpected behaviour.
251         /// </remarks>
252         /// <since_tizen> 8 </since_tizen>
253         public ProgressStyle Style => (ProgressStyle)(ViewStyle as ProgressStyle)?.Clone();
254
255         /// <summary>
256         /// The property to get/set Track image object URL of the Progress.
257         /// </summary>
258         /// <since_tizen> 6 </since_tizen>
259         public string TrackImageURL
260         {
261             get => trackImage.ResourceUrl;
262             set => trackImage.ResourceUrl = value;
263         }
264
265         /// <summary>
266         /// The property to get/set Progress object image URL of the Progress.
267         /// </summary>
268         /// <since_tizen> 6 </since_tizen>
269         public string ProgressImageURL
270         {
271             get => progressImage.ResourceUrl;
272             set => progressImage.ResourceUrl = value;
273         }
274
275         /// <summary>
276         /// The property to get/set Buffer object image resource URL of the Progress.
277         /// </summary>
278         /// <since_tizen> 6 </since_tizen>
279         public string BufferImageURL
280         {
281             get => bufferImage.ResourceUrl;
282             set => bufferImage.ResourceUrl = value;
283         }
284
285         /// <summary>
286         /// The property to get/set the indeterminate image.
287         /// </summary>
288         /// <exception cref="NullReferenceException">Thrown when setting null value.</exception>
289         /// <since_tizen> 9 </since_tizen>
290         public string IndeterminateImageUrl
291         {
292             get
293             {
294                 if (indeterminateImage == null)
295                 {
296                     return null;
297                 }
298                 else
299                 {
300                     return indeterminateImage?.URL;
301                 }
302             }
303             set
304             {
305                 if (value == null || indeterminateImage == null)
306                 {
307                     throw new NullReferenceException("Progress.IndeterminateImage is null");
308                 }
309                 else
310                 {
311                     indeterminateImage.URL = value;
312                 }
313             }
314         }
315
316         /// <summary>
317         /// The property to get/set Track object color of the Progress.
318         /// </summary>
319         /// <since_tizen> 6 </since_tizen>
320         public Color TrackColor
321         {
322             get => trackImage.BackgroundColor;
323             set => trackImage.BackgroundColor = value;
324         }
325
326         /// <summary>
327         /// The property to get/set Progress object color of the Progress.
328         /// </summary>
329         /// <since_tizen> 6 </since_tizen>
330         public Color ProgressColor
331         {
332             get => progressImage.BackgroundColor;
333             set => progressImage.BackgroundColor = value;
334         }
335
336         /// <summary>
337         /// The property to get/set Buffer object color of the Progress.
338         /// </summary>
339         /// <since_tizen> 6 </since_tizen>
340         public Color BufferColor
341         {
342             get => bufferImage.BackgroundColor;
343             set => bufferImage.BackgroundColor = value;
344         }
345
346         /// <summary>
347         /// The property to get/set the maximum value of the Progress.
348         /// </summary>
349         /// <since_tizen> 6 </since_tizen>
350         public float MaxValue
351         {
352             get
353             {
354                 return (float)GetValue(MaxValueProperty);
355             }
356             set
357             {
358                 SetValue(MaxValueProperty, value);
359             }
360         }
361
362         /// <summary>
363         /// The property to get/set the minim value of the Progress.
364         /// </summary>
365         /// <since_tizen> 6 </since_tizen>
366         public float MinValue
367         {
368             get
369             {
370                 return (float)GetValue(MinValueProperty);
371             }
372             set
373             {
374                 SetValue(MinValueProperty, value);
375             }
376         }
377
378         /// <summary>
379         /// The property to get/set the current value of the Progress.
380         /// </summary>
381         /// <since_tizen> 6 </since_tizen>
382         public float CurrentValue
383         {
384             get
385             {
386                 return (float)GetValue(CurrentValueProperty);
387             }
388             set
389             {
390                 SetValue(CurrentValueProperty, value);
391                 if (IsHighlighted)
392                 {
393                     EmitAccessibilityEvent(AccessibilityPropertyChangeEvent.Value);
394                 }
395             }
396         }
397
398         /// <summary>
399         /// The property to get/set the buffer value of the Progress.
400         /// </summary>
401         /// <since_tizen> 6 </since_tizen>
402         public float BufferValue
403         {
404             get
405             {
406                 return (float)GetValue(BufferValueProperty);
407             }
408             set
409             {
410                 SetValue(BufferValueProperty, value);
411             }
412         }
413
414         /// <summary>
415         /// Gets or sets state of progress.
416         /// </summary>
417         /// <since_tizen> 6 </since_tizen>
418         public ProgressStatusType ProgressState
419         {
420             get
421             {
422                 return (ProgressStatusType)GetValue(ProgressStateProperty);
423             }
424             set
425             {
426                 SetValue(ProgressStateProperty, value);
427             }
428         }
429
430         /// <inheritdoc/>
431         [EditorBrowsable(EditorBrowsableState.Never)]
432         public override void OnInitialize()
433         {
434             base.OnInitialize();
435             SetAccessibilityConstructor(Role.ProgressBar, AccessibilityInterface.Value);
436             // create necessary components
437             InitializeTrack();
438             InitializeBuffer();
439             InitializeProgress();
440             InitializeIndeterminate();
441
442             indeterminateAnimation?.Stop();
443             indeterminateAnimation = null;
444         }
445
446         /// <inheritdoc/>
447         [EditorBrowsable(EditorBrowsableState.Never)]
448         public override void ApplyStyle(ViewStyle style)
449         {
450             base.ApplyStyle(style);
451
452             if (style is ProgressStyle progressStyle)
453             {
454                 Debug.Assert(trackImage != null);
455                 Debug.Assert(progressImage != null);
456                 Debug.Assert(bufferImage != null);
457
458                 trackImage.ApplyStyle(progressStyle.Track);
459                 progressImage.ApplyStyle(progressStyle.Progress);
460                 bufferImage.ApplyStyle(progressStyle.Buffer);
461
462                 if (null != indeterminateImage && null != progressStyle.IndeterminateImageUrl)
463                 {
464                     indeterminateImage.URL = progressStyle.IndeterminateImageUrl;
465                 }
466             }
467         }
468
469         /// <summary>
470         /// Dispose Progress and all children on it.
471         /// </summary>
472         /// <param name="type">Dispose type.</param>
473         /// <since_tizen> 6 </since_tizen>
474         protected override void Dispose(DisposeTypes type)
475         {
476             if (disposed)
477             {
478                 return;
479             }
480
481             if (type == DisposeTypes.Explicit)
482             {
483                 //Called by User
484                 //Release your own managed resources here.
485                 //You should release all of your own disposable objects here.
486                 Utility.Dispose(trackImage);
487                 Utility.Dispose(progressImage);
488                 Utility.Dispose(bufferImage);
489                 indeterminateImage = null;
490             }
491
492             //You must call base.Dispose(type) just before exit.
493             base.Dispose(type);
494         }
495
496         /// <summary>
497         /// Change Image status. It can be override.
498         /// </summary>
499         /// This needs to be considered more if public-open is necessary.
500         [EditorBrowsable(EditorBrowsableState.Never)]
501         private void UpdateStates()
502         {
503             ChangeImageState(state);
504         }
505
506         /// <inheritdoc/>
507         [EditorBrowsable(EditorBrowsableState.Never)]
508         public override void OnRelayout(Vector2 size, RelayoutContainer container)
509         {
510             if (size == null) return;
511
512             if (size.Equals(this.size))
513             {
514                 return;
515             }
516
517             this.size = new Vector2(size);
518             UpdateValue();
519         }
520
521         /// <summary>
522         /// Update progress value
523         /// </summary>
524         /// This needs to be considered more if public-open is necessary.
525         [EditorBrowsable(EditorBrowsableState.Never)]
526         private void UpdateValue()
527         {
528             if (null == trackImage || null == progressImage)
529             {
530                 return;
531             }
532
533             if (minValue >= maxValue || currentValue < minValue || currentValue > maxValue)
534             {
535                 return;
536             }
537
538             float width = this.SizeWidth;
539             float height = this.SizeHeight;
540             float progressRatio = (float)(currentValue - minValue) / (float)(maxValue - minValue);
541             float progressWidth = width * progressRatio;
542             progressImage.Size2D = new Size2D((int)(progressWidth + round), (int)height); //Add const round to reach Math.Round function.
543             if (null != bufferImage)
544             {
545                 if (bufferValue < minValue || bufferValue > maxValue)
546                 {
547                     return;
548                 }
549
550                 float bufferRatio = (float)(bufferValue - minValue) / (float)(maxValue - minValue);
551                 float bufferWidth = width * bufferRatio;
552                 bufferImage.Size2D = new Size2D((int)(bufferWidth + round), (int)height); //Add const round to reach Math.Round function.
553             }
554         }
555
556         private Vector4 destinationValue = new Vector4(-1.0f, 0.0f, 10.0f, 1.0f);
557         private Vector4 initialValue = new Vector4(0.0f, 0.0f, 10.0f, 1.0f);
558
559         /// <summary>
560         /// Update Animation for Indeterminate mode.
561         /// </summary>
562         /// This will be public opened later after ACR done. Before ACR, need to be hidden as inhouse API.
563         [EditorBrowsable(EditorBrowsableState.Never)]
564         private void UpdateIndeterminateAnimation()
565         {
566             indeterminateAnimation?.Stop();
567
568             if (null != indeterminateImage)
569             {
570                 indeterminateAnimation = AnimateVisual(indeterminateImage, "pixelArea", destinationValue, 0, 1000,  AlphaFunction.BuiltinFunctions.Default, initialValue);
571                 indeterminateAnimation.Looping = true;
572                 indeterminateAnimation.Play();
573             }
574         }
575
576         /// <summary>
577         /// Get Progress style.
578         /// </summary>
579         /// <returns>The default progress style.</returns>
580         /// <since_tizen> 8 </since_tizen>
581         protected override ViewStyle CreateViewStyle()
582         {
583             return new ProgressStyle();
584         }
585
586         /// <summary>
587         /// Change Image status
588         /// </summary>
589         /// <since_tizen> 6 </since_tizen>
590         /// <param name="statusType">New status type</param>
591         protected void ChangeImageState(ProgressStatusType statusType)
592         {
593             if (statusType == ProgressStatusType.Buffering)
594             {
595                 indeterminateAnimation?.Stop();
596                 indeterminateAnimation = null;
597
598                 if (null != indeterminateImage)
599                 {
600                     indeterminateImage.Opacity = 0.0f;
601                 }
602                 progressImage.Hide();
603                 bufferImage.Show();
604             }
605             else if (statusType == ProgressStatusType.Determinate)
606             {
607                 indeterminateAnimation?.Stop();
608                 indeterminateAnimation = null;
609
610                 if (null != indeterminateImage)
611                 {
612                     indeterminateImage.Opacity = 0.0f;
613                 }
614                 bufferImage.Hide();
615                 progressImage.Show();
616
617                 UpdateValue();
618             }
619             else if (statusType == ProgressStatusType.Indeterminate)
620             {
621                 bufferImage.Hide();
622                 progressImage.Hide();
623                 if (null != indeterminateImage)
624                 {
625                     indeterminateImage.Opacity = 1.0f;
626                 }
627
628                 UpdateIndeterminateAnimation();
629             }
630         }
631
632         private void Initialize()
633         {
634             AccessibilityHighlightable = true;
635         }
636
637         private void InitializeTrack()
638         {
639             if (null == trackImage)
640             {
641                 trackImage = new ImageView
642                 {
643                     WidthResizePolicy = ResizePolicyType.FillToParent,
644                     HeightResizePolicy = ResizePolicyType.FillToParent,
645                     PositionUsesPivotPoint = true,
646                     ParentOrigin = NUI.ParentOrigin.TopLeft,
647                     PivotPoint = NUI.PivotPoint.TopLeft
648                 };
649                 Add(trackImage);
650             }
651         }
652
653         private void InitializeProgress()
654         {
655             if (null == progressImage)
656             {
657                 progressImage = new ImageView
658                 {
659                     WidthResizePolicy = ResizePolicyType.FillToParent,
660                     HeightResizePolicy = ResizePolicyType.FillToParent,
661                     PositionUsesPivotPoint = true,
662                     ParentOrigin = Tizen.NUI.ParentOrigin.TopLeft,
663                     PivotPoint = Tizen.NUI.PivotPoint.TopLeft
664                 };
665                 Add(progressImage);
666             }
667         }
668
669         private void InitializeBuffer()
670         {
671             if (null == bufferImage)
672             {
673                 bufferImage = new ImageView
674                 {
675                     WidthResizePolicy = ResizePolicyType.FillToParent,
676                     HeightResizePolicy = ResizePolicyType.FillToParent,
677                     PositionUsesPivotPoint = true,
678                     ParentOrigin = Tizen.NUI.ParentOrigin.TopLeft,
679                     PivotPoint = Tizen.NUI.PivotPoint.TopLeft
680                 };
681                 Add(bufferImage);
682                 bufferImage.Hide(); // At first, buffer image does not show.
683             }
684         }
685
686         private void InitializeIndeterminate()
687         {
688             indeterminateImage = new ImageVisual
689             {
690                 PixelArea = new Vector4(0.0f, 0.0f, 10.0f, 1.0f),
691                 WrapModeU = WrapModeType.Repeat,
692                 SizePolicy = VisualTransformPolicyType.Relative,
693                 Origin = Visual.AlignType.Center,
694                 AnchorPoint = Visual.AlignType.Center,
695                 Opacity = 0.0f,
696                 Size = new Size2D(1, 1),
697                 URL = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "nui_component_default_progress_indeterminate.png"
698             };
699             AddVisual("Indeterminate", indeterminateImage);
700
701             if (state == ProgressStatusType.Indeterminate)
702             {
703                 indeterminateImage.Opacity = 1.0f;
704             }
705         }
706     }
707 }