[NUI] Scrollbar : Separate the concept of viewport size and view size. (#1632)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Scrollbar.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
22 namespace Tizen.NUI.Components
23 {
24     /// <summary>
25     /// The Scrollbar is a component that contains track and thumb to indicate the current scrolled position of a scrollable object.
26     /// </summary>
27     [EditorBrowsable(EditorBrowsableState.Never)]
28     public class Scrollbar : ScrollbarBase
29     {
30         #region Fields
31
32         /// <summary>Bindable property of TrackThickness</summary>
33         [EditorBrowsable(EditorBrowsableState.Never)]
34         public static readonly BindableProperty TrackThicknessProperty = BindableProperty.Create(nameof(TrackThickness), typeof(float), typeof(Scrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) =>
35         {
36             var instance = ((Scrollbar)bindable);
37             var thickness = (float?)newValue;
38
39             ((ScrollbarStyle)instance.viewStyle).TrackThickness = thickness;
40             instance.UpdateTrackThickness(thickness ?? 0);
41         },
42         defaultValueCreator: (bindable) =>
43         {
44             return ((ScrollbarStyle)((Scrollbar)bindable).viewStyle)?.TrackThickness ?? 0;
45         });
46
47         /// <summary>Bindable property of ThumbThickness</summary>
48         [EditorBrowsable(EditorBrowsableState.Never)]
49         public static readonly BindableProperty ThumbThicknessProperty = BindableProperty.Create(nameof(ThumbThickness), typeof(float), typeof(Scrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) =>
50         {
51             var instance = ((Scrollbar)bindable);
52             var thickness = (float?)newValue;
53
54             ((ScrollbarStyle)instance.viewStyle).ThumbThickness = thickness;
55             instance.UpdateThumbThickness(thickness ?? 0);
56         },
57         defaultValueCreator: (bindable) =>
58         {
59             return ((ScrollbarStyle)((Scrollbar)bindable).viewStyle)?.ThumbThickness ?? 0;
60         });
61
62         /// <summary>Bindable property of TrackColor</summary>
63         [EditorBrowsable(EditorBrowsableState.Never)]
64         public static readonly BindableProperty TrackColorProperty = BindableProperty.Create(nameof(TrackColor), typeof(Color), typeof(Scrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
65         {
66             var instance = ((Scrollbar)bindable);
67             var color = (Color)newValue;
68
69             ((ScrollbarStyle)instance.viewStyle).TrackColor = color;
70             instance.UpdateTrackColor(color);
71         },
72         defaultValueCreator: (bindable) =>
73         {
74             return ((ScrollbarStyle)((Scrollbar)bindable).viewStyle)?.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(Scrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
80         {
81             var instance = ((Scrollbar)bindable);
82             var color = (Color)newValue;
83
84             ((ScrollbarStyle)instance.viewStyle).ThumbColor = color;
85             instance.UpdateThumbColor(color);
86         },
87         defaultValueCreator: (bindable) =>
88         {
89             return ((ScrollbarStyle)((Scrollbar)bindable).viewStyle)?.ThumbColor;
90         });
91
92         /// <summary>Bindable property of TrackPadding</summary>
93         [EditorBrowsable(EditorBrowsableState.Never)]
94         public static readonly BindableProperty TrackPaddingProperty = BindableProperty.Create(nameof(TrackPadding), typeof(Extents), typeof(Scrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
95         {
96             var instance = ((Scrollbar)bindable);
97             var trackPadding = (Extents)newValue;
98
99             ((ScrollbarStyle)instance.viewStyle).TrackPadding = trackPadding;
100             instance.UpdateTrackPadding(trackPadding);
101         },
102         defaultValueCreator: (bindable) =>
103         {
104             return ((ScrollbarStyle)((Scrollbar)bindable).viewStyle)?.TrackPadding;
105         });
106
107         private ColorVisual trackVisual;
108         private ColorVisual thumbVisual;
109         private Animation thumbPositionAnimation;
110         private Animation thumbSizeAnimation;
111         private Calculator calculator;
112         private Size containerSize = new Size(0, 0);
113         private float currentPosition;
114
115         #endregion Fields
116
117
118         #region Constructors
119
120         /// <summary>
121         /// Create an empty Scrollbar.
122         /// </summary>
123         public Scrollbar() : base(new ScrollbarStyle())
124         {
125         }
126
127         /// <summary>
128         /// Create a Scrollbar and initialize with properties.
129         /// </summary>
130         /// <param name="contentLength">The length of the scrollable content area.</param>
131         /// <param name="viewportLength">The length of the viewport representing the amount of visible content.</param>
132         /// <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>
133         /// <param name="isHorizontal">Whether the direction of scrolling is horizontal or not. It is vertical by default.</param>
134         [EditorBrowsable(EditorBrowsableState.Never)]
135         public Scrollbar(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false) : base(new ScrollbarStyle())
136         {
137             Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
138         }
139
140         /// <summary>
141         /// Create an empty Scrollbar with a ScrollbarStyle instance to set style properties.
142         /// </summary>
143         [EditorBrowsable(EditorBrowsableState.Never)]
144         public Scrollbar(ScrollbarStyle style) : base(style)
145         {
146         }
147
148         /// <summary>
149         /// Static constructor to initialize bindable properties when loading.
150         /// </summary>
151         static Scrollbar()
152         {
153         }
154
155         #endregion Constructors
156
157
158         #region Properties
159
160         /// <summary>
161         /// The thickness of the track.
162         /// </summary>
163         [EditorBrowsable(EditorBrowsableState.Never)]
164         public float TrackThickness
165         {
166             get => (float)GetValue(TrackThicknessProperty);
167             set => SetValue(TrackThicknessProperty, value);
168         }
169
170         /// <summary>
171         /// The thickness of the thumb.
172         /// </summary>
173         [EditorBrowsable(EditorBrowsableState.Never)]
174         public float ThumbThickness
175         {
176             get => (float)GetValue(ThumbThicknessProperty);
177             set => SetValue(ThumbThicknessProperty, value);
178         }
179
180         /// <summary>
181         /// The color of the track part.
182         /// </summary>
183         [EditorBrowsable(EditorBrowsableState.Never)]
184         public Color TrackColor
185         {
186             get => (Color)GetValue(TrackColorProperty);
187             set => SetValue(TrackColorProperty, value);
188         }
189
190         /// <summary>
191         /// The color of the thumb part.
192         /// </summary>
193         [EditorBrowsable(EditorBrowsableState.Never)]
194         public Color ThumbColor
195         {
196             get => (Color)GetValue(ThumbColorProperty);
197             set => SetValue(ThumbColorProperty, value);
198         }
199
200         /// <summary>
201         /// The padding value of the track.
202         /// </summary>
203         [EditorBrowsable(EditorBrowsableState.Never)]
204         public Extents TrackPadding
205         {
206             get => (Extents)GetValue(TrackPaddingProperty);
207             set => SetValue(TrackPaddingProperty, value);
208         }
209
210         #endregion Properties
211
212
213         #region Methods
214
215         /// <inheritdoc/>
216         [EditorBrowsable(EditorBrowsableState.Never)]
217         public override void Initialize(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false)
218         {
219             if (isHorizontal)
220             {
221                 calculator = new HorizontalCalculator(contentLength > 0.0f ? contentLength : 0.0f, viewportLength, currentPosition);
222             }
223             else
224             {
225                 calculator = new VerticalCalculator(contentLength > 0.0f ? contentLength : 0.0f, viewportLength, currentPosition);
226             }
227
228             thumbPositionAnimation?.Stop();
229             thumbPositionAnimation = null;
230
231             thumbSizeAnimation?.Stop();
232             thumbSizeAnimation = null;
233
234             Size trackSize = calculator.CalculateTrackSize(TrackThickness, containerSize, TrackPadding);
235             Vector2 trackPosition = calculator.CalculateTrackPosition(TrackPadding);
236             Size thumbSize = calculator.CalculateThumbSize(ThumbThickness, trackSize);
237             Vector2 thumbPosition = calculator.CalculateThumbPosition(trackSize, thumbSize, TrackPadding);
238
239             if (trackVisual == null)
240             {
241                 trackVisual = new ColorVisual
242                 {
243                     SuppressUpdateVisual = true,
244                     Color = TrackColor,
245                     SizePolicy = VisualTransformPolicyType.Absolute,
246                     Origin = calculator.CalculatorTrackAlign(),
247                     AnchorPoint = calculator.CalculatorTrackAlign(),
248                     Size = trackSize,
249                     Position = trackPosition,
250                 };
251
252                 AddVisual("Track", trackVisual);
253             }
254             else
255             {
256                 trackVisual.Size = trackSize;
257                 trackVisual.Position = trackPosition;
258                 trackVisual.UpdateVisual(true);
259             }
260
261             if (thumbVisual == null)
262             {
263                 thumbVisual = new ColorVisual
264                 {
265                     SuppressUpdateVisual = true,
266                     MixColor = ThumbColor,
267                     SizePolicy = VisualTransformPolicyType.Absolute,
268                     Origin = calculator.CalculatorThumbAlign(),
269                     AnchorPoint = calculator.CalculatorThumbAlign(),
270                     Opacity = calculator.CalculateThumbVisibility() ? 1.0f : 0.0f,
271                     Size = thumbSize,
272                     Position = thumbPosition,
273                 };
274
275                 AddVisual("Thumb", thumbVisual);
276             }
277             else
278             {
279                 thumbVisual.Size = thumbSize;
280                 thumbVisual.Position = thumbPosition;
281                 thumbVisual.UpdateVisual(true);
282             }
283         }
284
285         /// <inheritdoc/>
286         /// <remarks>Please note that, for now, only alpha functions created with BuiltinFunctions are valid when animating. Otherwise, it will be treated as a linear alpha function. </remarks>
287         [EditorBrowsable(EditorBrowsableState.Never)]
288         public override void Update(float contentLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
289         {
290             if (calculator == null)
291             {
292                 return;
293             }
294
295             calculator.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
296             calculator.currentPosition = position;
297
298             thumbVisual.Size = calculator.CalculateThumbSize(ThumbThickness, trackVisual.Size);
299             thumbVisual.Position = calculator.CalculateThumbScrollPosition(trackVisual.Size, thumbVisual.Position, TrackPadding);
300             thumbVisual.Opacity = calculator.CalculateThumbVisibility() ? 1.0f : 0.0f;
301
302             if (durationMs == 0)
303             {
304                 thumbVisual.UpdateVisual(true);
305
306                 return;
307             }
308
309             // TODO Support non built-in alpha function for visual trainsition in DALi.
310             AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
311
312             thumbPositionAnimation?.Stop();
313             thumbPositionAnimation = AnimateVisual(thumbVisual, "position", thumbVisual.Position, 0, (int)durationMs, builtinAlphaFunction);
314             thumbPositionAnimation.Play();
315
316             thumbSizeAnimation?.Stop();
317             thumbSizeAnimation = AnimateVisual(thumbVisual, "size", thumbVisual.Size, 0, (int)durationMs, builtinAlphaFunction);
318             thumbSizeAnimation.Play();
319         }
320
321         /// <inheritdoc/>
322         /// <remarks>Please note that, for now, only alpha functions created with BuiltinFunctions are valid when animating. Otherwise, it will be treated as a linear alpha function. </remarks>
323         [EditorBrowsable(EditorBrowsableState.Never)]
324         public override void ScrollTo(float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
325         {
326             if (calculator == null)
327             {
328                 return;
329             }
330
331             calculator.currentPosition = position;
332             thumbVisual.Position = calculator.CalculateThumbScrollPosition(trackVisual.Size, thumbVisual.Position, TrackPadding);
333
334             if (durationMs == 0)
335             {
336                 thumbVisual.UpdateVisual(true);
337
338                 return;
339             }
340
341             // TODO Support non built-in alpha function for visual trainsition in DALi.
342             AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
343
344             thumbPositionAnimation?.Stop();
345             thumbPositionAnimation = AnimateVisual(thumbVisual, "position", thumbVisual.Position, 0, (int)durationMs, builtinAlphaFunction);
346             thumbPositionAnimation.Play();
347         }
348
349         /// <inheritdoc/>
350         [EditorBrowsable(EditorBrowsableState.Never)]
351         public override void OnRelayout(Vector2 size, RelayoutContainer container)
352         {
353             base.OnRelayout(size, container);
354
355             if (size.Width == containerSize.Width && size.Height == containerSize.Height)
356             {
357                 return;
358             }
359
360             containerSize = new Size(size.Width, size.Height);
361
362             if (calculator == null)
363             {
364                 return;
365             }
366
367             trackVisual.Size = calculator.CalculateTrackSize(TrackThickness, containerSize, TrackPadding);
368             trackVisual.Position = calculator.CalculateTrackPosition(TrackPadding);
369             thumbVisual.Size = calculator.CalculateThumbSize(ThumbThickness, trackVisual.Size);
370             thumbVisual.Position = calculator.CalculateThumbPosition(trackVisual.Size, thumbVisual.Size, TrackPadding);
371
372             trackVisual.UpdateVisual(true);
373             thumbVisual.UpdateVisual(true);
374         }
375
376         /// <inheritdoc/>
377         [EditorBrowsable(EditorBrowsableState.Never)]
378         protected override ViewStyle GetViewStyle()
379         {
380             return new ScrollbarStyle();
381         }
382
383         /// <summary>
384         /// Update TrackThickness property of the scrollbar.
385         /// </summary>
386         /// <param name="thickness">The width of the track.</param>
387         [EditorBrowsable(EditorBrowsableState.Never)]
388         protected virtual void UpdateTrackThickness(float thickness)
389         {
390             if (trackVisual == null)
391             {
392                 return;
393             }
394
395             trackVisual.Size = calculator.CalculateTrackSize(thickness, containerSize, TrackPadding);
396             trackVisual.UpdateVisual(true);
397         }
398
399         /// <summary>
400         /// Update ThumbThickness property of the scrollbar.
401         /// </summary>
402         /// <param name="thickness">The width of the track.</param>
403         [EditorBrowsable(EditorBrowsableState.Never)]
404         protected virtual void UpdateThumbThickness(float thickness)
405         {
406             if (thumbVisual == null)
407             {
408                 return;
409             }
410
411             thumbVisual.Size = calculator.CalculateThumbSize(thickness, trackVisual.Size);
412             thumbVisual.UpdateVisual(true);
413         }
414
415         /// <summary>
416         /// Update TrackColor property of the scrollbar.
417         /// </summary>
418         /// <param name="color">The color of the track.</param>
419         [EditorBrowsable(EditorBrowsableState.Never)]
420         protected virtual void UpdateTrackColor(Color color)
421         {
422             if (trackVisual == null)
423             {
424                 return;
425             }
426
427             trackVisual.MixColor = color;
428             trackVisual.UpdateVisual(true);
429         }
430
431         /// <summary>
432         /// Update ThumbColor property of the scrollbar.
433         /// </summary>
434         /// <param name="color">The color of the thumb.</param>
435         [EditorBrowsable(EditorBrowsableState.Never)]
436         protected virtual void UpdateThumbColor(Color color)
437         {
438             if (thumbVisual == null)
439             {
440                 return;
441             }
442
443             thumbVisual.MixColor = color;
444             thumbVisual.UpdateVisual(true);
445         }
446
447         /// <summary>
448         /// Update TrackPadding property of the scrollbar.
449         /// </summary>
450         /// <param name="trackPadding">The padding of the track.</param>
451         protected virtual void UpdateTrackPadding(Extents trackPadding)
452         {
453             if (calculator == null)
454             {
455                 return;
456             }
457
458             trackVisual.Size = calculator.CalculateTrackSize(TrackThickness, containerSize, trackPadding); 
459             trackVisual.Position = calculator.CalculateTrackPosition(trackPadding);
460             thumbVisual.Size = calculator.CalculateThumbSize(ThumbThickness, trackVisual.Size);
461             thumbVisual.Position = calculator.CalculateThumbPaddingPosition(trackVisual.Size, thumbVisual.Size, thumbVisual.Position, trackPadding);
462
463             trackVisual.UpdateVisual(true);
464             thumbVisual.UpdateVisual(true);
465         }
466
467         #endregion Methods
468
469
470         #region Classes
471
472         private abstract class Calculator
473         {
474             public float contentLength;
475             public float visibleLength;
476             public float currentPosition;
477
478             public Calculator(float contentLength, float visibleLength, float currentPosition)
479             {
480                 this.contentLength = contentLength;
481                 this.visibleLength = visibleLength;
482                 this.currentPosition = currentPosition;
483             }
484
485             public bool CalculateThumbVisibility()
486             {
487                 return contentLength > visibleLength;
488             }
489
490             public abstract Visual.AlignType CalculatorTrackAlign();
491             public abstract Visual.AlignType CalculatorThumbAlign();
492             public abstract Size CalculateTrackSize(float thickness, Size containerSize, Extents trackPadding);
493             public abstract Vector2 CalculateTrackPosition(Extents trackPadding);
494             public abstract Size CalculateThumbSize(float thickness, Size trackSize);
495             public abstract Vector2 CalculateThumbPosition(Size trackSize, Size thumbSize, Extents trackPadding);
496             public abstract Vector2 CalculateThumbPaddingPosition(Size trackSize, Size thumbSize, Vector2 thumbCurrentPosition, Extents trackPadding);
497             public abstract Vector2 CalculateThumbScrollPosition(Size trackSize, Vector2 thumbCurrentPosition, Extents trackPadding);
498         }
499
500         private class HorizontalCalculator : Calculator
501         {
502             public HorizontalCalculator(float contentLength, float visibleLength, float currentPosition) : base(contentLength, visibleLength, currentPosition)
503             {
504             }
505
506             public override Visual.AlignType CalculatorTrackAlign()
507             {
508                 return Visual.AlignType.BottomCenter;
509             }
510
511             public override Visual.AlignType CalculatorThumbAlign()
512             {
513                 return Visual.AlignType.BottomBegin;
514             }
515
516             public override Size CalculateTrackSize(float thickness, Size containerSize, Extents trackPadding)
517             {
518                 return new Size(containerSize.Width - trackPadding.Start - trackPadding.End, thickness);
519             }
520
521             public override Vector2 CalculateTrackPosition(Extents trackPadding)
522             {
523                 return new Vector2(0, -trackPadding.Bottom);
524             }
525
526             public override Size CalculateThumbSize(float thickness, Size trackSize)
527             {
528                 return new Size(trackSize.Width * visibleLength / contentLength, thickness);
529             }
530
531             public override Vector2 CalculateThumbPosition(Size trackSize, Size thumbSize, Extents trackPadding)
532             {
533                 float padding = ((trackSize.Height - thumbSize.Height) / 2.0f) + trackPadding.Bottom;
534                 float pos = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
535                 return new Vector2(trackPadding.Start + trackSize.Width * pos / contentLength, -padding);
536             }
537
538             public override Vector2 CalculateThumbPaddingPosition(Size trackSize, Size thumbSize, Vector2 thumbCurrentPosition, Extents trackPadding)
539             {
540                 float padding = ((trackSize.Height - thumbSize.Height) / 2.0f) + trackPadding.Bottom;
541                 return new Vector2(thumbCurrentPosition.X, -padding);
542             }
543
544             public override Vector2 CalculateThumbScrollPosition(Size trackSize, Vector2 thumbCurrentPosition, Extents trackPadding)
545             {
546                 float pos = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
547                 return new Vector2(trackPadding.Start + trackSize.Width * pos / contentLength, thumbCurrentPosition.Y);
548             }
549         }
550
551         private class VerticalCalculator : Calculator
552         {
553             public VerticalCalculator(float contentLength, float visibleLength, float currentPosition) : base(contentLength, visibleLength, currentPosition)
554             {
555             }
556
557             public override Visual.AlignType CalculatorTrackAlign()
558             {
559                 return Visual.AlignType.CenterEnd;
560             }
561
562             public override Visual.AlignType CalculatorThumbAlign()
563             {
564                 return Visual.AlignType.TopEnd;
565             }
566
567             public override Size CalculateTrackSize(float thickness, Size containerSize, Extents trackPadding)
568             {
569                 return new Size(thickness, containerSize.Height - trackPadding.Top - trackPadding.Bottom);
570             }
571
572             public override Vector2 CalculateTrackPosition(Extents trackPadding)
573             {
574                 return new Vector2(-trackPadding.End, 0);
575             }
576
577             public override Size CalculateThumbSize(float thickness, Size trackSize)
578             {
579                 return new Size(thickness, trackSize.Height * visibleLength / contentLength);
580             }
581
582             public override Vector2 CalculateThumbPosition(Size trackSize, Size thumbSize, Extents trackPadding)
583             {
584                 float padding = ((trackSize.Width - thumbSize.Width) / 2.0f) + trackPadding.End;
585                 float pos = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
586                 return new Vector2(-padding, trackPadding.Top + trackSize.Height * pos / contentLength);
587             }
588
589             public override Vector2 CalculateThumbPaddingPosition(Size trackSize, Size thumbSize, Vector2 thumbCurrentPosition, Extents trackPadding)
590             {
591                 float padding = ((trackSize.Width - thumbSize.Width) / 2.0f) + trackPadding.End;
592                 return new Vector2(-padding, thumbCurrentPosition.Y);
593             }
594
595             public override Vector2 CalculateThumbScrollPosition(Size trackSize, Vector2 thumbPosition, Extents trackPadding)
596             {
597                 float pos = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
598                 return new Vector2(thumbPosition.X, trackPadding.Top + trackSize.Height * pos / contentLength);
599             }
600         }
601
602         #endregion Classes
603     }
604 }