Release 8.0.0.15408
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Wearable / src / public / CircularScrollbar.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 System.ComponentModel;
19 using Tizen.NUI.BaseComponents;
20 using Tizen.NUI.Binding;
21 using Tizen.NUI.Components;
22
23 namespace Tizen.NUI.Wearable
24 {
25     /// <summary>
26     /// The CircualrScrollbar is a wearable NUI component that can be linked to the scrollable objects
27     /// indicating the current scroll position of the scrollable object.<br />
28     /// </summary>
29     [EditorBrowsable(EditorBrowsableState.Never)]
30     public class CircularScrollbar : ScrollbarBase
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(CircularScrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) =>
37         {
38             var instance = ((CircularScrollbar)bindable);
39             instance.UpdateVisualThickness((float?)newValue ?? 0);
40         },
41         defaultValueCreator: (bindable) =>
42         {
43             var instance = (CircularScrollbar)bindable;
44             return instance.trackVisual == null ? 0 : instance.trackVisual.Thickness;
45         });
46
47         /// <summary>Bindable property of TrackSweepAngle</summary>
48         [EditorBrowsable(EditorBrowsableState.Never)]
49         public static readonly BindableProperty TrackSweepAngleProperty = BindableProperty.Create(nameof(TrackSweepAngle), typeof(float), typeof(CircularScrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) =>
50         {
51             var instance = ((CircularScrollbar)bindable);
52             instance.UpdateTrackVisualSweepAngle((float?)newValue ?? 0);
53         },
54         defaultValueCreator: (bindable) =>
55         {
56             var instance = (CircularScrollbar)bindable;
57             return instance.trackVisual == null ? 0 : instance.trackVisual.SweepAngle;
58         });
59
60         /// <summary>Bindable property of TrackColor</summary>
61         [EditorBrowsable(EditorBrowsableState.Never)]
62         public static readonly BindableProperty TrackColorProperty = BindableProperty.Create(nameof(TrackColor), typeof(Color), typeof(CircularScrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
63         {
64             var instance = ((CircularScrollbar)bindable);
65             instance.UpdateTrackVisualColor((Color)newValue);
66         },
67         defaultValueCreator: (bindable) =>
68         {
69             return ((CircularScrollbar)bindable).trackVisual?.MixColor;
70         });
71
72         /// <summary>Bindable property of ThumbColor</summary>
73         [EditorBrowsable(EditorBrowsableState.Never)]
74         public static readonly BindableProperty ThumbColorProperty = BindableProperty.Create(nameof(ThumbColor), typeof(Color), typeof(CircularScrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
75         {
76             var instance = ((CircularScrollbar)bindable);
77             instance.UpdateThumbVisualColor((Color)newValue);
78         },
79         defaultValueCreator: (bindable) =>
80         {
81             return ((CircularScrollbar)bindable).thumbVisual?.MixColor;
82         });
83
84         private ArcVisual trackVisual;
85         private ArcVisual thumbVisual;
86         private float contentLength;
87         private float visibleLength;
88         private float currentPosition;
89         private float directionAlpha;
90         private Size containerSize = new Size(0, 0);
91         private Animation thumbStartAngleAnimation;
92         private Animation thumbSweepAngleAnimation;
93
94         #endregion Fields
95
96
97         #region Constructors
98
99         /// <summary>
100         /// Create an empty CircularScrollbar.
101         /// </summary>
102         public CircularScrollbar() : base(new CircularScrollbarStyle())
103         {
104         }
105
106         /// <summary>
107         /// Create a CircularScrollbar and initialize with properties.
108         /// </summary>
109         /// <param name="contentLength">The length of the scrollable content area.</param>
110         /// <param name="viewportLength">The length of the viewport representing the amount of visible content.</param>
111         /// <param name="currentPosition">The current position of the viewport in scrollable content area. This is the viewport's top position if the scroller is vertical, otherwise, left.</param>
112         /// <param name="isHorizontal">Whether the direction of scrolling is horizontal or not. It is vertical by default.</param>
113         [EditorBrowsable(EditorBrowsableState.Never)]
114         public CircularScrollbar(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false) : base(new CircularScrollbarStyle())
115         {
116             Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
117         }
118
119         /// <summary>
120         /// Create an empty CircularScrollbar with a CircularScrollbarStyle instance to set style properties.
121         /// </summary>
122         [EditorBrowsable(EditorBrowsableState.Never)]
123         public CircularScrollbar(CircularScrollbarStyle style) : base(style)
124         {
125         }
126
127         /// <summary>
128         /// Static constructor to initialize bindable properties when loading.
129         /// </summary>
130         static CircularScrollbar()
131         {
132         }
133
134         #endregion Constructors
135
136
137         #region Properties
138
139         /// <summary>
140         /// Return a copied Style instance of CircularScrollbar
141         /// </summary>
142         /// <remarks>
143         /// It returns copied Style instance and changing it does not effect to the CircularScrollbar.
144         /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
145         /// </remarks>
146         [EditorBrowsable(EditorBrowsableState.Never)]
147         public new CircularScrollbarStyle Style
148         {
149             get
150             {
151                 var result = new CircularScrollbarStyle(ViewStyle as CircularScrollbarStyle);
152                 result.CopyPropertiesFromView(this);
153                 return result;
154             }
155         }
156
157         /// <summary>
158         /// The thickness of the scrollbar and track.
159         /// </summary>
160         [EditorBrowsable(EditorBrowsableState.Never)]
161         public float Thickness
162         {
163             get => (float)GetValue(ThicknessProperty);
164             set => SetValue(ThicknessProperty, value);
165         }
166
167         /// <summary>
168         /// The sweep angle of track area in degrees.
169         /// </summary>
170         /// <remarks>
171         /// Values below 6 degrees are treated as 6 degrees.
172         /// Values exceeding 180 degrees are treated as 180 degrees.
173         /// </remarks>
174         [EditorBrowsable(EditorBrowsableState.Never)]
175         public float TrackSweepAngle
176         {
177             get => (float)GetValue(TrackSweepAngleProperty);
178             set => SetValue(TrackSweepAngleProperty, value);
179         }
180
181         /// <summary>
182         /// The color of the track part.
183         /// </summary>
184         [EditorBrowsable(EditorBrowsableState.Never)]
185         public Color TrackColor
186         {
187             get => (Color)GetValue(TrackColorProperty);
188             set => SetValue(TrackColorProperty, value);
189         }
190
191         /// <summary>
192         /// The color of the thumb part.
193         /// </summary>
194         [EditorBrowsable(EditorBrowsableState.Never)]
195         public Color ThumbColor
196         {
197             get => (Color)GetValue(ThumbColorProperty);
198             set => SetValue(ThumbColorProperty, value);
199         }
200
201         #endregion Properties
202
203
204         #region Methods
205
206         /// <inheritdoc/>
207         [EditorBrowsable(EditorBrowsableState.Never)]
208         public override void Initialize(float contentLength, float viewportLenth, float currentPosition, bool isHorizontal = false)
209         {
210             this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
211             this.visibleLength = viewportLenth;
212             this.currentPosition = currentPosition;
213             this.directionAlpha = isHorizontal ? 270.0f : 0.0f;
214
215             thumbStartAngleAnimation?.Stop();
216             thumbStartAngleAnimation = null;
217
218             thumbSweepAngleAnimation?.Stop();
219             thumbSweepAngleAnimation = null;
220
221
222             float trackSweepAngle = CalculateTrackSweepAngle(TrackSweepAngle);
223             float trackStartAngle = CalculateTrackStartAngle(trackSweepAngle);
224             float thumbSweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
225             float thumbStartAngle = CalculateThumbStartAngle(currentPosition, trackStartAngle, trackSweepAngle, thumbSweepAngle);
226
227             if (trackVisual == null)
228             {
229                 trackVisual = new ArcVisual
230                 {
231                     SuppressUpdateVisual = true,
232                     Thickness = this.Thickness,
233                     Cap = ArcVisual.CapType.Round,
234                     MixColor = TrackColor,
235                     Size = containerSize - new Size(2, 2),
236                     SizePolicy = VisualTransformPolicyType.Absolute,
237                     SweepAngle = trackSweepAngle,
238                     StartAngle = trackStartAngle,
239                 };
240
241                 AddVisual("Track", trackVisual);
242             }
243             else
244             {
245                 trackVisual.SweepAngle = trackSweepAngle;
246                 trackVisual.StartAngle = trackStartAngle;
247                 trackVisual.UpdateVisual(true);
248             }
249
250             if (thumbVisual == null)
251             {
252                 thumbVisual = new ArcVisual
253                 {
254                     SuppressUpdateVisual = true,
255                     Thickness = trackVisual.Thickness,
256                     Cap = ArcVisual.CapType.Round,
257                     MixColor = ThumbColor,
258                     Size = containerSize - new Size(2, 2),
259                     SizePolicy = VisualTransformPolicyType.Absolute,
260                     SweepAngle = thumbSweepAngle,
261                     StartAngle = thumbStartAngle,
262                     Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f,
263                 };
264
265                 AddVisual("Thumb", thumbVisual);
266             }
267             else
268             {
269                 thumbVisual.SweepAngle = thumbSweepAngle;
270                 thumbVisual.StartAngle = thumbStartAngle;
271                 thumbVisual.UpdateVisual(true);
272             }
273         }
274
275         /// <inheritdoc/>
276         [EditorBrowsable(EditorBrowsableState.Never)]
277         public override void Update(float contentLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
278         {
279             this.currentPosition = position;
280             this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
281
282             if (thumbVisual == null)
283             {
284                 return;
285             }
286
287             thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
288             thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
289             thumbVisual.Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f;
290
291             if (durationMs == 0)
292             {
293                 thumbVisual.UpdateVisual(true);
294
295                 return;
296             }
297
298             // TODO Support non built-in alpha function for visual trainsition in DALi.
299             AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
300
301             thumbStartAngleAnimation?.Stop();
302             thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
303             thumbStartAngleAnimation.Play();
304
305             thumbSweepAngleAnimation?.Stop();
306             thumbSweepAngleAnimation = AnimateVisual(thumbVisual, "sweepAngle", thumbVisual.SweepAngle, 0, (int)durationMs, builtinAlphaFunction);
307             thumbSweepAngleAnimation.Play();
308         }
309
310         /// <inheritdoc/>
311         [EditorBrowsable(EditorBrowsableState.Never)]
312         public override void ScrollTo(float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
313         {
314             currentPosition = position;
315
316             if (thumbVisual == null)
317             {
318                 return;
319             }
320
321             var oldThumbStartAngle = thumbVisual.StartAngle;
322
323             thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
324
325             if (durationMs == 0)
326             {
327                 thumbVisual.UpdateVisual(true);
328
329                 return;
330             }
331
332             // TODO Support non built-in alpha function for visual trainsition in DALi.
333             AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
334
335             thumbStartAngleAnimation?.Stop();
336             thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
337             thumbStartAngleAnimation.Play();
338         }
339
340         /// <inheritdoc/>
341         [EditorBrowsable(EditorBrowsableState.Never)]
342         public override void OnRelayout(Vector2 size, RelayoutContainer container)
343         {
344             base.OnRelayout(size, container);
345
346             if (size.Width == containerSize?.Width && size.Height == containerSize.Height)
347             {
348                 return;
349             }
350
351             containerSize = new Size(size.Width, size.Height);
352
353             if (trackVisual == null)
354             {
355                 return;
356             }
357
358             trackVisual.Size = containerSize - new Size(2, 2);
359             thumbVisual.Size = containerSize - new Size(2, 2);
360             
361             trackVisual.UpdateVisual(true);
362             thumbVisual.UpdateVisual(true);
363         }
364
365         /// <inheritdoc/>
366         [EditorBrowsable(EditorBrowsableState.Never)]
367         protected override ViewStyle CreateViewStyle()
368         {
369             return new CircularScrollbarStyle();
370         }
371
372         private float CalculateTrackStartAngle(float currentTrackSweepAngle)
373         {
374             return ((180.0f - currentTrackSweepAngle) / 2.0f) + directionAlpha;
375         }
376
377         private float CalculateTrackSweepAngle(float inputTrackSweepAngle)
378         {
379             return Math.Min(Math.Max(inputTrackSweepAngle, 3), 180);
380         }
381
382         private float CalculateThumbStartAngle(float position, float trackStartAngle, float trackSweepAngle, float thumbSweepAngle)
383         {
384             float minAngle = trackStartAngle;
385             float maxAngle = trackStartAngle + trackSweepAngle - thumbSweepAngle;
386             float resultAngle = trackStartAngle + (trackSweepAngle * (position < 0.0f ? 0.0f : position) / contentLength);
387
388             return Math.Min(Math.Max(resultAngle, minAngle), maxAngle);
389         }
390
391         private float CalculateThumbSweepAngle(float trackSweepAngle)
392         {
393             return trackSweepAngle * visibleLength / contentLength;
394         }
395
396         private bool CalculateThumbVisibility()
397         {
398             return contentLength > visibleLength;
399         }
400
401         private void UpdateVisualThickness(float thickness)
402         {
403             if (trackVisual == null)
404             {
405                 return;
406             }
407
408             trackVisual.Thickness = thickness;
409             thumbVisual.Thickness = thickness;
410
411             trackVisual.UpdateVisual(true);
412             thumbVisual.UpdateVisual(true);
413         }
414
415         private void UpdateTrackVisualSweepAngle(float trackSweepAngle)
416         {
417             if (trackVisual == null || thumbVisual == null)
418             {
419                 return;
420             }
421
422             trackVisual.SweepAngle = CalculateTrackSweepAngle(trackSweepAngle);
423             trackVisual.StartAngle = CalculateTrackStartAngle(trackVisual.SweepAngle);
424
425             thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
426             thumbVisual.StartAngle = CalculateThumbStartAngle(currentPosition, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
427
428             trackVisual.UpdateVisual(true);
429             thumbVisual.UpdateVisual(true);
430         }
431
432         private void UpdateTrackVisualColor(Color trackColor)
433         {
434             if (trackVisual == null)
435             {
436                 return;
437             }
438
439             trackVisual.MixColor = trackColor;
440             trackVisual.UpdateVisual(true);
441         }
442
443         private void UpdateThumbVisualColor(Color thumbColor)
444         {
445             if (thumbVisual == null)
446             {
447                 return;
448             }
449
450             thumbVisual.MixColor = thumbColor;
451             thumbVisual.UpdateVisual(true);
452         }
453
454         #endregion Methods
455     }
456 }