Merge remote-tracking branch 'origin/master' into tizen
[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         private bool mScrollEnabled = true;
94
95         #endregion Fields
96
97
98         #region Constructors
99
100         /// <summary>
101         /// Create an empty CircularScrollbar.
102         /// </summary>
103         public CircularScrollbar() : base(new CircularScrollbarStyle())
104         {
105         }
106
107         /// <summary>
108         /// Create a CircularScrollbar and initialize with properties.
109         /// </summary>
110         /// <param name="contentLength">The length of the scrollable content area.</param>
111         /// <param name="viewportLength">The length of the viewport representing the amount of visible content.</param>
112         /// <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>
113         /// <param name="isHorizontal">Whether the direction of scrolling is horizontal or not. It is vertical by default.</param>
114         [EditorBrowsable(EditorBrowsableState.Never)]
115         public CircularScrollbar(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false) : base(new CircularScrollbarStyle())
116         {
117             Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
118         }
119
120         /// <summary>
121         /// Create an empty CircularScrollbar with a CircularScrollbarStyle instance to set style properties.
122         /// </summary>
123         [EditorBrowsable(EditorBrowsableState.Never)]
124         public CircularScrollbar(CircularScrollbarStyle style) : base(style)
125         {
126         }
127
128         /// <summary>
129         /// Static constructor to initialize bindable properties when loading.
130         /// </summary>
131         static CircularScrollbar()
132         {
133         }
134
135         #endregion Constructors
136
137
138         #region Properties
139
140         /// <summary>
141         /// Return a copied Style instance of CircularScrollbar
142         /// </summary>
143         /// <remarks>
144         /// It returns copied Style instance and changing it does not effect to the CircularScrollbar.
145         /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
146         /// </remarks>
147         [EditorBrowsable(EditorBrowsableState.Never)]
148         public new CircularScrollbarStyle Style
149         {
150             get
151             {
152                 var result = new CircularScrollbarStyle(ViewStyle as CircularScrollbarStyle);
153                 result.CopyPropertiesFromView(this);
154                 return result;
155             }
156         }
157
158         /// <summary>
159         /// The thickness of the scrollbar and track.
160         /// </summary>
161         [EditorBrowsable(EditorBrowsableState.Never)]
162         public float Thickness
163         {
164             get => (float)GetValue(ThicknessProperty);
165             set => SetValue(ThicknessProperty, value);
166         }
167
168         /// <summary>
169         /// The sweep angle of track area in degrees.
170         /// </summary>
171         /// <remarks>
172         /// Values below 6 degrees are treated as 6 degrees.
173         /// Values exceeding 180 degrees are treated as 180 degrees.
174         /// </remarks>
175         [EditorBrowsable(EditorBrowsableState.Never)]
176         public float TrackSweepAngle
177         {
178             get => (float)GetValue(TrackSweepAngleProperty);
179             set => SetValue(TrackSweepAngleProperty, value);
180         }
181
182         /// <summary>
183         /// The color of the track part.
184         /// </summary>
185         [EditorBrowsable(EditorBrowsableState.Never)]
186         public Color TrackColor
187         {
188             get => (Color)GetValue(TrackColorProperty);
189             set => SetValue(TrackColorProperty, value);
190         }
191
192         /// <summary>
193         /// The color of the thumb part.
194         /// </summary>
195         [EditorBrowsable(EditorBrowsableState.Never)]
196         public Color ThumbColor
197         {
198             get => (Color)GetValue(ThumbColorProperty);
199             set => SetValue(ThumbColorProperty, value);
200         }
201
202         #endregion Properties
203
204
205         #region Methods
206
207         /// <inheritdoc/>
208         [EditorBrowsable(EditorBrowsableState.Never)]
209         public override void Initialize(float contentLength, float viewportLenth, float currentPosition, bool isHorizontal = false)
210         {
211             this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
212             this.visibleLength = viewportLenth;
213             this.currentPosition = currentPosition;
214             this.directionAlpha = isHorizontal ? 270.0f : 0.0f;
215
216             thumbStartAngleAnimation?.Stop();
217             thumbStartAngleAnimation = null;
218
219             thumbSweepAngleAnimation?.Stop();
220             thumbSweepAngleAnimation = null;
221
222
223             float trackSweepAngle = CalculateTrackSweepAngle(TrackSweepAngle);
224             float trackStartAngle = CalculateTrackStartAngle(trackSweepAngle);
225             float thumbSweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
226             float thumbStartAngle = CalculateThumbStartAngle(currentPosition, trackStartAngle, trackSweepAngle, thumbSweepAngle);
227
228             if (trackVisual == null)
229             {
230                 trackVisual = new ArcVisual
231                 {
232                     SuppressUpdateVisual = true,
233                     Thickness = this.Thickness,
234                     Cap = ArcVisual.CapType.Round,
235                     MixColor = TrackColor,
236                     Size = containerSize - new Size(2, 2),
237                     SizePolicy = VisualTransformPolicyType.Absolute,
238                     SweepAngle = trackSweepAngle,
239                     StartAngle = trackStartAngle,
240                 };
241
242                 AddVisual("Track", trackVisual);
243             }
244             else
245             {
246                 trackVisual.SweepAngle = trackSweepAngle;
247                 trackVisual.StartAngle = trackStartAngle;
248                 trackVisual.UpdateVisual(true);
249             }
250
251             if (thumbVisual == null)
252             {
253                 thumbVisual = new ArcVisual
254                 {
255                     SuppressUpdateVisual = true,
256                     Thickness = trackVisual.Thickness,
257                     Cap = ArcVisual.CapType.Round,
258                     MixColor = ThumbColor,
259                     Size = containerSize - new Size(2, 2),
260                     SizePolicy = VisualTransformPolicyType.Absolute,
261                     SweepAngle = thumbSweepAngle,
262                     StartAngle = thumbStartAngle,
263                     Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f,
264                 };
265
266                 AddVisual("Thumb", thumbVisual);
267             }
268             else
269             {
270                 thumbVisual.SweepAngle = thumbSweepAngle;
271                 thumbVisual.StartAngle = thumbStartAngle;
272                 thumbVisual.UpdateVisual(true);
273             }
274         }
275
276         /// <inheritdoc/>
277         [EditorBrowsable(EditorBrowsableState.Never)]
278         public override void Update(float contentLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
279         {
280             this.currentPosition = position;
281             this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
282
283             if (thumbVisual == null)
284             {
285                 return;
286             }
287
288             thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
289             thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
290             thumbVisual.Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f;
291
292             if (durationMs == 0)
293             {
294                 thumbVisual.UpdateVisual(true);
295
296                 return;
297             }
298
299             // TODO Support non built-in alpha function for visual trainsition in DALi.
300             AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
301
302             thumbStartAngleAnimation?.Stop();
303             thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
304             thumbStartAngleAnimation.Play();
305
306             thumbSweepAngleAnimation?.Stop();
307             thumbSweepAngleAnimation = AnimateVisual(thumbVisual, "sweepAngle", thumbVisual.SweepAngle, 0, (int)durationMs, builtinAlphaFunction);
308             thumbSweepAngleAnimation.Play();
309         }
310
311         /// <inheritdoc/>
312         [EditorBrowsable(EditorBrowsableState.Never)]
313         public override void ScrollTo(float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
314         {
315             currentPosition = position;
316
317             if (mScrollEnabled == false)
318             {
319                 return;
320             }
321
322             if (thumbVisual == null)
323             {
324                 return;
325             }
326
327             var oldThumbStartAngle = thumbVisual.StartAngle;
328
329             thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
330
331             if (durationMs == 0)
332             {
333                 thumbVisual.UpdateVisual(true);
334
335                 return;
336             }
337
338             // TODO Support non built-in alpha function for visual trainsition in DALi.
339             AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
340
341             thumbStartAngleAnimation?.Stop();
342             thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
343             thumbStartAngleAnimation.Play();
344         }
345
346         /// <inheritdoc/>
347         [EditorBrowsable(EditorBrowsableState.Never)]
348         public override void OnRelayout(Vector2 size, RelayoutContainer container)
349         {
350             base.OnRelayout(size, container);
351
352             if (size.Width == containerSize?.Width && size.Height == containerSize.Height)
353             {
354                 return;
355             }
356
357             containerSize = new Size(size.Width, size.Height);
358
359             if (trackVisual == null)
360             {
361                 return;
362             }
363
364             trackVisual.Size = containerSize - new Size(2, 2);
365             thumbVisual.Size = containerSize - new Size(2, 2);
366             
367             trackVisual.UpdateVisual(true);
368             thumbVisual.UpdateVisual(true);
369         }
370
371         /// <inheritdoc/>
372         [EditorBrowsable(EditorBrowsableState.Never)]
373         protected override ViewStyle CreateViewStyle()
374         {
375             return new CircularScrollbarStyle();
376         }
377
378         private float CalculateTrackStartAngle(float currentTrackSweepAngle)
379         {
380             return ((180.0f - currentTrackSweepAngle) / 2.0f) + directionAlpha;
381         }
382
383         private float CalculateTrackSweepAngle(float inputTrackSweepAngle)
384         {
385             return Math.Min(Math.Max(inputTrackSweepAngle, 3), 180);
386         }
387
388         private float CalculateThumbStartAngle(float position, float trackStartAngle, float trackSweepAngle, float thumbSweepAngle)
389         {
390             float minAngle = trackStartAngle;
391             float maxAngle = trackStartAngle + trackSweepAngle - thumbSweepAngle;
392             float resultAngle = trackStartAngle + (trackSweepAngle * (position < 0.0f ? 0.0f : position) / contentLength);
393
394             return Math.Min(Math.Max(resultAngle, minAngle), maxAngle);
395         }
396
397         private float CalculateThumbSweepAngle(float trackSweepAngle)
398         {
399             return trackSweepAngle * visibleLength / contentLength;
400         }
401
402         private bool CalculateThumbVisibility()
403         {
404             return contentLength > visibleLength;
405         }
406
407         private void UpdateVisualThickness(float thickness)
408         {
409             if (trackVisual == null)
410             {
411                 return;
412             }
413
414             trackVisual.Thickness = thickness;
415             thumbVisual.Thickness = thickness;
416
417             trackVisual.UpdateVisual(true);
418             thumbVisual.UpdateVisual(true);
419         }
420
421         private void UpdateTrackVisualSweepAngle(float trackSweepAngle)
422         {
423             if (trackVisual == null || thumbVisual == null)
424             {
425                 return;
426             }
427
428             trackVisual.SweepAngle = CalculateTrackSweepAngle(trackSweepAngle);
429             trackVisual.StartAngle = CalculateTrackStartAngle(trackVisual.SweepAngle);
430
431             thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
432             thumbVisual.StartAngle = CalculateThumbStartAngle(currentPosition, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
433
434             trackVisual.UpdateVisual(true);
435             thumbVisual.UpdateVisual(true);
436         }
437
438         private void UpdateTrackVisualColor(Color trackColor)
439         {
440             if (trackVisual == null)
441             {
442                 return;
443             }
444
445             trackVisual.MixColor = trackColor;
446             trackVisual.UpdateVisual(true);
447         }
448
449         private void UpdateThumbVisualColor(Color thumbColor)
450         {
451             if (thumbVisual == null)
452             {
453                 return;
454             }
455
456             thumbVisual.MixColor = thumbColor;
457             thumbVisual.UpdateVisual(true);
458         }
459
460         /// <inheritdoc/>
461         [EditorBrowsable(EditorBrowsableState.Never)]
462         public override bool ScrollEnabled
463         {
464             get
465             {
466                 return mScrollEnabled;
467             }
468             set
469             {
470                 if (value != mScrollEnabled)
471                 {
472                     mScrollEnabled = value;
473                 }
474             }
475         }
476
477         #endregion Methods
478     }
479 }