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