2 * Copyright(c) 2020 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.ComponentModel;
19 using System.Diagnostics;
20 using Tizen.NUI.BaseComponents;
21 using Tizen.NUI.Binding;
23 namespace Tizen.NUI.Components
25 // Represents padding data : Start, End, Top, Bottom
26 using PaddingType = ValueTuple<ushort, ushort, ushort, ushort>;
29 /// The Scrollbar is a component that contains track and thumb to indicate the current scrolled position of a scrollable object.
31 [EditorBrowsable(EditorBrowsableState.Never)]
32 public class Scrollbar : ScrollbarBase
36 /// <summary>Bindable property of TrackThickness</summary>
37 [EditorBrowsable(EditorBrowsableState.Never)]
38 public static readonly BindableProperty TrackThicknessProperty = BindableProperty.Create(nameof(TrackThickness), typeof(float), typeof(Scrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) =>
40 var instance = ((Scrollbar)bindable);
41 var thickness = (float?)newValue;
43 instance.scrollbarStyle.TrackThickness = thickness;
44 instance.UpdateTrackThickness(thickness ?? 0);
46 defaultValueCreator: (bindable) =>
48 return ((Scrollbar)bindable).scrollbarStyle.TrackThickness ?? 0;
51 /// <summary>Bindable property of ThumbThickness</summary>
52 [EditorBrowsable(EditorBrowsableState.Never)]
53 public static readonly BindableProperty ThumbThicknessProperty = BindableProperty.Create(nameof(ThumbThickness), typeof(float), typeof(Scrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) =>
55 var instance = ((Scrollbar)bindable);
56 var thickness = (float?)newValue;
58 instance.scrollbarStyle.ThumbThickness = thickness;
59 instance.UpdateThumbThickness(thickness ?? 0);
61 defaultValueCreator: (bindable) =>
63 return ((Scrollbar)bindable).scrollbarStyle.ThumbThickness ?? 0;
66 /// <summary>Bindable property of TrackColor</summary>
67 [EditorBrowsable(EditorBrowsableState.Never)]
68 public static readonly BindableProperty TrackColorProperty = BindableProperty.Create(nameof(TrackColor), typeof(Color), typeof(Scrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
70 var instance = ((Scrollbar)bindable);
71 var color = (Color)newValue;
73 instance.scrollbarStyle.TrackColor = color;
74 instance.UpdateTrackColor(color);
76 defaultValueCreator: (bindable) =>
78 return ((Scrollbar)bindable).scrollbarStyle.TrackColor;
81 /// <summary>Bindable property of ThumbColor</summary>
82 [EditorBrowsable(EditorBrowsableState.Never)]
83 public static readonly BindableProperty ThumbColorProperty = BindableProperty.Create(nameof(ThumbColor), typeof(Color), typeof(Scrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
85 var instance = ((Scrollbar)bindable);
86 var color = (Color)newValue;
88 instance.scrollbarStyle.ThumbColor = color;
89 instance.UpdateThumbColor(color);
91 defaultValueCreator: (bindable) =>
93 return ((Scrollbar)bindable).scrollbarStyle.ThumbColor;
96 /// <summary>Bindable property of TrackPadding</summary>
97 [EditorBrowsable(EditorBrowsableState.Never)]
98 public static readonly BindableProperty TrackPaddingProperty = BindableProperty.Create(nameof(TrackPadding), typeof(Extents), typeof(Scrollbar), null, propertyChanged: (bindable, oldValue, newValue) =>
100 var instance = ((Scrollbar)bindable);
101 var trackPadding = (Extents)newValue;
103 instance.scrollbarStyle.TrackPadding = trackPadding;
104 instance.UpdateTrackPadding(trackPadding);
106 defaultValueCreator: (bindable) =>
108 return ((Scrollbar)bindable).scrollbarStyle.TrackPadding;
111 private View trackView;
112 private View thumbView;
113 private Animation thumbPositionAnimation;
114 private Animation thumbSizeAnimation;
115 private Animation opacityAnimation;
116 private Calculator calculator;
117 private Size containerSize = new Size(0, 0);
118 private ScrollbarStyle scrollbarStyle => ViewStyle as ScrollbarStyle;
119 private bool mScrollEnabled = true;
120 private float previousPosition;
128 /// Create an empty Scrollbar.
130 public Scrollbar() : base()
135 /// Create a Scrollbar and initialize with properties.
137 /// <param name="contentLength">The length of the scrollable content area.</param>
138 /// <param name="viewportLength">The length of the viewport representing the amount of visible content.</param>
139 /// <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>
140 /// <param name="isHorizontal">Whether the direction of scrolling is horizontal or not. It is vertical by default.</param>
141 [EditorBrowsable(EditorBrowsableState.Never)]
142 public Scrollbar(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false) : this()
144 Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
148 /// Create an empty Scrollbar with a ScrollbarStyle instance to set style properties.
150 [EditorBrowsable(EditorBrowsableState.Never)]
151 public Scrollbar(ScrollbarStyle style) : base(style)
156 /// Static constructor to initialize bindable properties when loading.
162 #endregion Constructors
168 /// Return a copied Style instance of Scrollbar
171 /// It returns copied Style instance and changing it does not effect to the Scrollbar.
172 /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
174 [EditorBrowsable(EditorBrowsableState.Never)]
175 public new ScrollbarStyle Style
179 var result = new ScrollbarStyle(scrollbarStyle);
180 result.CopyPropertiesFromView(this);
186 /// The thickness of the track.
188 [EditorBrowsable(EditorBrowsableState.Never)]
189 public float TrackThickness
191 get => (float)GetValue(TrackThicknessProperty);
192 set => SetValue(TrackThicknessProperty, value);
196 /// The thickness of the thumb.
198 [EditorBrowsable(EditorBrowsableState.Never)]
199 public float ThumbThickness
201 get => (float)GetValue(ThumbThicknessProperty);
202 set => SetValue(ThumbThicknessProperty, value);
206 /// The color of the track part.
208 [EditorBrowsable(EditorBrowsableState.Never)]
209 public Color TrackColor
211 get => (Color)GetValue(TrackColorProperty);
212 set => SetValue(TrackColorProperty, value);
216 /// The color of the thumb part.
218 [EditorBrowsable(EditorBrowsableState.Never)]
219 public Color ThumbColor
221 get => (Color)GetValue(ThumbColorProperty);
222 set => SetValue(ThumbColorProperty, value);
226 /// The padding value of the track.
227 /// Note that when the scrollbar is for vertical direction, Start value is ignored.
228 /// In case of horizontal direction, Top value is ignored.
230 [EditorBrowsable(EditorBrowsableState.Never)]
231 public Extents TrackPadding
233 get => (Extents)GetValue(TrackPaddingProperty);
234 set => SetValue(TrackPaddingProperty, value);
237 #endregion Properties
243 [EditorBrowsable(EditorBrowsableState.Never)]
244 public override void Initialize(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false)
248 calculator = new HorizontalCalculator(contentLength > 0.0f ? contentLength : 0.0f, viewportLength, currentPosition);
252 calculator = new VerticalCalculator(contentLength > 0.0f ? contentLength : 0.0f, viewportLength, currentPosition);
255 thumbPositionAnimation?.Stop();
256 thumbPositionAnimation = null;
258 thumbSizeAnimation?.Stop();
259 thumbSizeAnimation = null;
261 opacityAnimation?.Stop();
262 opacityAnimation = null;
264 PaddingType ensuredPadding = EnsurePadding(TrackPadding);
265 var trackSize = calculator.CalculateTrackSize(TrackThickness, containerSize, ensuredPadding);
266 var trackPosition = calculator.CalculateTrackPosition(ensuredPadding);
267 var thumbSize = calculator.CalculateThumbSize(ThumbThickness, trackSize);
268 var thumbPosition = calculator.CalculateThumbPosition(trackSize, thumbSize, ensuredPadding);
270 trackView?.Unparent();
271 trackView = new View()
273 BackgroundColor = TrackColor,
274 ParentOrigin = calculator.CalculatorTrackAlign(),
275 PivotPoint = calculator.CalculatorTrackAlign(),
277 Position = trackPosition,
278 PositionUsesPivotPoint = true,
282 thumbView?.Unparent();
283 thumbView = new View()
285 BackgroundColor = ThumbColor,
286 ParentOrigin = calculator.CalculatorThumbAlign(),
287 PivotPoint = calculator.CalculatorThumbAlign(),
289 Position = thumbPosition,
290 PositionUsesPivotPoint = true,
294 Opacity = calculator.IsScrollable() ? 1.0f : 0.0f;
298 /// <exception cref="InvalidOperationException">Thrown when the scrollabr not yet initialized.</exception>
299 [EditorBrowsable(EditorBrowsableState.Never)]
300 public override void Update(float contentLength, float viewportLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
302 if (calculator == null)
304 throw new InvalidOperationException("Scrollbar is not initialized. Please call Initialize() first.");
307 calculator.visibleLength = viewportLength;
308 Update(contentLength, position, durationMs, alphaFunction);
312 /// <exception cref="InvalidOperationException">Thrown when the scrollabr not yet initialized.</exception>
313 [EditorBrowsable(EditorBrowsableState.Never)]
314 public override void Update(float contentLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
316 if (calculator == null)
318 throw new InvalidOperationException("Scrollbar is not initialized. Please call Initialize() first.");
321 calculator.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
322 calculator.currentPosition = position;
324 float newOpacity = calculator.IsScrollable() ? 1.0f : 0.0f;
325 bool opacityChanged = (int)Opacity != (int)newOpacity;
327 var thumbSize = calculator.CalculateThumbSize(ThumbThickness, trackView.Size);
328 var thumbPosition = calculator.CalculateThumbScrollPosition(trackView.Size, thumbView.Position, EnsurePadding(TrackPadding));
332 thumbView.Position = thumbPosition;
333 thumbView.Size = thumbSize;
337 Opacity = newOpacity;
342 EnsureThumbPositionAnimation().AnimateTo(thumbView, "Position", thumbPosition, 0, (int)durationMs, alphaFunction);
343 thumbPositionAnimation.Play();
345 EnsureThumbSizeAnimation().AnimateTo(thumbView, "Size", thumbSize, 0, (int)durationMs, alphaFunction);
346 thumbSizeAnimation.Play();
350 EnsureOpacityAnimation().AnimateTo(this, "Opacity", newOpacity, 0, (int)durationMs, alphaFunction);
351 opacityAnimation.Play();
356 /// <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>
357 [EditorBrowsable(EditorBrowsableState.Never)]
358 public override void ScrollTo(float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
360 if (mScrollEnabled == false)
365 if (calculator == null)
370 previousPosition = calculator.currentPosition;
371 calculator.currentPosition = position;
372 var thumbPosition = calculator.CalculateThumbScrollPosition(trackView.Size, thumbView.Position, EnsurePadding(TrackPadding));
376 thumbView.Position = thumbPosition;
380 EnsureThumbPositionAnimation().AnimateTo(thumbView, "position", thumbPosition, 0, (int)durationMs, alphaFunction);
381 thumbPositionAnimation.Play();
385 [EditorBrowsable(EditorBrowsableState.Never)]
386 public override void OnRelayout(Vector2 size, RelayoutContainer container)
388 base.OnRelayout(size, container);
390 if (size.Width == containerSize.Width && size.Height == containerSize.Height)
395 containerSize = new Size(size.Width, size.Height);
397 if (calculator == null)
402 PaddingType ensuredPadding = EnsurePadding(TrackPadding);
403 trackView.Size = calculator.CalculateTrackSize(TrackThickness, containerSize, ensuredPadding);
404 trackView.Position = calculator.CalculateTrackPosition(ensuredPadding);
405 thumbView.Size = calculator.CalculateThumbSize(ThumbThickness, trackView.Size);
406 thumbView.Position = calculator.CalculateThumbPosition(trackView.Size, thumbView.Size, ensuredPadding);
410 [EditorBrowsable(EditorBrowsableState.Never)]
411 public override void ApplyStyle(ViewStyle viewStyle)
413 if (viewStyle is ScrollbarStyle scrollbarStyle)
415 // Apply essential look.
416 if (scrollbarStyle.TrackThickness == null) scrollbarStyle.TrackThickness = 6.0f;
417 if (scrollbarStyle.ThumbThickness == null) scrollbarStyle.ThumbThickness = 6.0f;
418 if (scrollbarStyle.TrackColor == null) scrollbarStyle.TrackColor = new Color(1.0f, 1.0f, 1.0f, 0.15f);
419 if (scrollbarStyle.ThumbColor == null) scrollbarStyle.ThumbColor = new Color(0.6f, 0.6f, 0.6f, 1.0f);
420 if (scrollbarStyle.TrackPadding == null) scrollbarStyle.TrackPadding = 4;
421 if (scrollbarStyle.WidthResizePolicy == null) scrollbarStyle.WidthResizePolicy = ResizePolicyType.FillToParent;
422 if (scrollbarStyle.HeightResizePolicy == null) scrollbarStyle.HeightResizePolicy = ResizePolicyType.FillToParent;
425 base.ApplyStyle(viewStyle);
429 [EditorBrowsable(EditorBrowsableState.Never)]
430 protected override ViewStyle CreateViewStyle()
432 return new ScrollbarStyle();
436 /// Update TrackThickness property of the scrollbar.
438 /// <param name="thickness">The width of the track.</param>
439 [EditorBrowsable(EditorBrowsableState.Never)]
440 protected virtual void UpdateTrackThickness(float thickness)
442 if (trackView == null)
447 trackView.Size = calculator.CalculateTrackSize(thickness, containerSize, EnsurePadding(TrackPadding));
451 /// Update ThumbThickness property of the scrollbar.
453 /// <param name="thickness">The width of the track.</param>
454 [EditorBrowsable(EditorBrowsableState.Never)]
455 protected virtual void UpdateThumbThickness(float thickness)
457 if (thumbView == null)
462 thumbView.Size = calculator.CalculateThumbSize(thickness, trackView.Size);
466 /// Update TrackColor property of the scrollbar.
468 /// <param name="color">The color of the track.</param>
469 [EditorBrowsable(EditorBrowsableState.Never)]
470 protected virtual void UpdateTrackColor(Color color)
472 if (trackView == null)
477 trackView.BackgroundColor = color;
481 /// Update ThumbColor property of the scrollbar.
483 /// <param name="color">The color of the thumb.</param>
484 [EditorBrowsable(EditorBrowsableState.Never)]
485 protected virtual void UpdateThumbColor(Color color)
487 if (thumbView == null)
492 thumbView.BackgroundColor = color;
496 /// Update TrackPadding property of the scrollbar.
498 /// <param name="trackPadding">The padding of the track.</param>
499 protected virtual void UpdateTrackPadding(Extents trackPadding)
501 if (calculator == null || trackView == null || thumbView == null)
506 PaddingType ensuredPadding = EnsurePadding(trackPadding);
507 trackView.Size = calculator.CalculateTrackSize(TrackThickness, containerSize, ensuredPadding);
508 trackView.Position = calculator.CalculateTrackPosition(ensuredPadding);
509 thumbView.Size = calculator.CalculateThumbSize(ThumbThickness, trackView.Size);
510 thumbView.Position = calculator.CalculateThumbPaddingPosition(trackView.Size, thumbView.Size, thumbView.Position, ensuredPadding);
514 [EditorBrowsable(EditorBrowsableState.Never)]
515 public override bool ScrollEnabled
519 return mScrollEnabled;
523 if (value != mScrollEnabled)
525 mScrollEnabled = value;
531 [EditorBrowsable(EditorBrowsableState.Never)]
532 public override Position ScrollPosition
536 if (calculator == null)
538 return new Position(0.0f, 0.0f);
541 float length = Math.Min(Math.Max(calculator.currentPosition, 0.0f), calculator.contentLength - calculator.visibleLength);
543 if (calculator is HorizontalCalculator)
545 return new Position(length, 0.0f);
549 return new Position(0.0f, length);
555 [EditorBrowsable(EditorBrowsableState.Never)]
556 public override Position ScrollCurrentPosition
560 if (calculator == null)
562 return new Position(0.0f, 0.0f);
565 float length = Math.Min(Math.Max(calculator.currentPosition, 0.0f), calculator.contentLength - calculator.visibleLength);
567 if (thumbPositionAnimation != null)
569 float progress = thumbPositionAnimation.CurrentProgress;
570 float previousLength = Math.Min(Math.Max(previousPosition, 0.0f), calculator.contentLength - calculator.visibleLength);
572 length = ((1.0f - progress) * previousLength) + (progress * length);
575 if (calculator is HorizontalCalculator)
577 return new Position(length, 0.0f);
581 return new Position(0.0f, length);
586 private PaddingType EnsurePadding(Extents padding) => padding == null ? new PaddingType(0, 0, 0 ,0) : new PaddingType(padding.Start, padding.End, padding.Top, padding.Bottom);
588 private Animation EnsureThumbPositionAnimation()
590 if (thumbPositionAnimation == null)
592 thumbPositionAnimation = new Animation();
596 thumbPositionAnimation.Stop();
597 thumbPositionAnimation.Clear();
599 return thumbPositionAnimation;
602 private Animation EnsureThumbSizeAnimation()
604 if (thumbSizeAnimation == null)
606 thumbSizeAnimation = new Animation();
610 thumbSizeAnimation.Stop();
611 thumbSizeAnimation.Clear();
613 return thumbSizeAnimation;
616 private Animation EnsureOpacityAnimation()
618 if (opacityAnimation == null)
620 opacityAnimation = new Animation();
624 opacityAnimation.Stop();
625 opacityAnimation.Clear();
627 return opacityAnimation;
635 private abstract class Calculator
637 public float contentLength;
638 public float visibleLength;
639 public float currentPosition;
641 public Calculator(float contentLength, float visibleLength, float currentPosition)
643 this.contentLength = contentLength;
644 this.visibleLength = visibleLength;
645 this.currentPosition = currentPosition;
648 public bool IsScrollable()
650 return contentLength > visibleLength;
653 public abstract Position CalculatorTrackAlign();
654 public abstract Position CalculatorThumbAlign();
655 public abstract Size CalculateTrackSize(float thickness, Size containerSize, PaddingType trackPadding);
656 public abstract Position CalculateTrackPosition(PaddingType trackPadding);
657 public abstract Size CalculateThumbSize(float thickness, Size trackSize);
658 public abstract Position CalculateThumbPosition(Size trackSize, Size thumbSize, PaddingType trackPadding);
659 public abstract Position CalculateThumbPaddingPosition(Size trackSize, Size thumbSize, Position thumbCurrentPosition, PaddingType trackPadding);
660 public abstract Position CalculateThumbScrollPosition(Size trackSize, Position thumbCurrentPosition, PaddingType trackPadding);
663 private class HorizontalCalculator : Calculator
665 public HorizontalCalculator(float contentLength, float visibleLength, float currentPosition) : base(contentLength, visibleLength, currentPosition)
669 public override Position CalculatorTrackAlign()
671 return Tizen.NUI.ParentOrigin.BottomLeft;
674 public override Position CalculatorThumbAlign()
676 return Tizen.NUI.ParentOrigin.BottomLeft;
679 public override Size CalculateTrackSize(float thickness, Size containerSize, PaddingType trackPadding)
681 return new Size(containerSize.Width - trackPadding.Item1 - trackPadding.Item2, thickness);
684 public override Position CalculateTrackPosition(PaddingType trackPadding)
686 return new Position(trackPadding.Item1, -trackPadding.Item4);
689 public override Size CalculateThumbSize(float thickness, Size trackSize)
691 return new Size(trackSize.Width * (IsScrollable() ? (visibleLength / contentLength) : 0.0f), thickness);
694 public override Position CalculateThumbPosition(Size trackSize, Size thumbSize, PaddingType trackPadding)
696 float padding = ((trackSize.Height - thumbSize.Height) / 2.0f) + trackPadding.Item4;
697 return new Position(trackPadding.Item1 + (IsScrollable() ? (trackSize.Width * (Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength)) / contentLength) : 0.0f), -padding);
700 public override Position CalculateThumbPaddingPosition(Size trackSize, Size thumbSize, Position thumbCurrentPosition, PaddingType trackPadding)
702 float padding = ((trackSize.Height - thumbSize.Height) / 2.0f) + trackPadding.Item4;
703 return new Position(thumbCurrentPosition.X, -padding);
706 public override Position CalculateThumbScrollPosition(Size trackSize, Position thumbCurrentPosition, PaddingType trackPadding)
708 return new Position(trackPadding.Item1 + (IsScrollable() ? (trackSize.Width * (Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength)) / contentLength) : 0.0f), thumbCurrentPosition.Y);
712 private class VerticalCalculator : Calculator
714 public VerticalCalculator(float contentLength, float visibleLength, float currentPosition) : base(contentLength, visibleLength, currentPosition)
718 public override Position CalculatorTrackAlign()
720 return Tizen.NUI.ParentOrigin.TopRight;
723 public override Position CalculatorThumbAlign()
725 return Tizen.NUI.ParentOrigin.TopRight;
728 public override Size CalculateTrackSize(float thickness, Size containerSize, PaddingType trackPadding)
730 return new Size(thickness, containerSize.Height - trackPadding.Item3 - trackPadding.Item4);
733 public override Position CalculateTrackPosition(PaddingType trackPadding)
735 return new Position(-trackPadding.Item2, trackPadding.Item3);
738 public override Size CalculateThumbSize(float thickness, Size trackSize)
740 return new Size(thickness, trackSize.Height * (IsScrollable() ? (visibleLength / contentLength) : 0.0f));
743 public override Position CalculateThumbPosition(Size trackSize, Size thumbSize, PaddingType trackPadding)
745 float padding = ((trackSize.Width - thumbSize.Width) / 2.0f) + trackPadding.Item2;
746 return new Position(-padding, trackPadding.Item3 + (IsScrollable() ? (trackSize.Height * Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength) / contentLength) : 0.0f));
749 public override Position CalculateThumbPaddingPosition(Size trackSize, Size thumbSize, Position thumbCurrentPosition, PaddingType trackPadding)
751 float padding = ((trackSize.Width - thumbSize.Width) / 2.0f) + trackPadding.Item2;
752 return new Position(-padding, thumbCurrentPosition.Y);
755 public override Position CalculateThumbScrollPosition(Size trackSize, Position thumbPosition, PaddingType trackPadding)
757 return new Position(thumbPosition.X, trackPadding.Item3 + (IsScrollable() ? (trackSize.Height * Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength) / contentLength) : 0.0f));