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