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 Tizen.NUI.BaseComponents;
20 using Tizen.NUI.Binding;
21 using Tizen.NUI.Components;
23 namespace Tizen.NUI.Wearable
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 />
29 [EditorBrowsable(EditorBrowsableState.Never)]
30 public class CircularScrollbar : ScrollbarBase
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) =>
38 var instance = ((CircularScrollbar)bindable);
39 float value = (float?)newValue ?? 0;
40 instance.CurrentStyle.Thickness = value;
41 instance.UpdateVisualThickness(value);
43 defaultValueCreator: (bindable) =>
45 var instance = (CircularScrollbar)bindable;
46 return instance.CurrentStyle.Thickness ?? 0;
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) =>
53 var instance = ((CircularScrollbar)bindable);
54 float value = (float?)newValue ?? 0;
55 instance.CurrentStyle.TrackSweepAngle = value;
56 instance.UpdateTrackVisualSweepAngle(value);
58 defaultValueCreator: (bindable) =>
60 var instance = (CircularScrollbar)bindable;
61 return instance.CurrentStyle.TrackSweepAngle ?? 0;
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) =>
68 var instance = ((CircularScrollbar)bindable);
69 instance.CurrentStyle.TrackColor = (Color)newValue;
70 instance.UpdateTrackVisualColor((Color)newValue);
72 defaultValueCreator: (bindable) =>
74 return ((CircularScrollbar)bindable).CurrentStyle.TrackColor;
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) =>
81 var instance = ((CircularScrollbar)bindable);
82 instance.CurrentStyle.ThumbColor = (Color)newValue;
83 instance.UpdateThumbVisualColor((Color)newValue);
85 defaultValueCreator: (bindable) =>
87 return ((CircularScrollbar)bindable).CurrentStyle.ThumbColor;
90 private ArcVisual trackVisual;
91 private ArcVisual thumbVisual;
92 private float contentLength;
93 private float visibleLength;
94 private float previousPosition;
95 private float currentPosition;
96 private float directionAlpha;
97 private Size containerSize = new Size(0, 0);
98 private Animation thumbStartAngleAnimation;
99 private Animation thumbSweepAngleAnimation;
100 private bool mScrollEnabled = true;
108 /// Create an empty CircularScrollbar.
110 public CircularScrollbar() : base()
115 /// Create a CircularScrollbar and initialize with properties.
117 /// <param name="contentLength">The length of the scrollable content area.</param>
118 /// <param name="viewportLength">The length of the viewport representing the amount of visible content.</param>
119 /// <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>
120 /// <param name="isHorizontal">Whether the direction of scrolling is horizontal or not. It is vertical by default.</param>
121 [EditorBrowsable(EditorBrowsableState.Never)]
122 public CircularScrollbar(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false) : base()
124 Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
128 /// Create an empty CircularScrollbar with a CircularScrollbarStyle instance to set style properties.
130 [EditorBrowsable(EditorBrowsableState.Never)]
131 public CircularScrollbar(CircularScrollbarStyle style) : base(style)
136 /// Static constructor to initialize bindable properties when loading.
138 static CircularScrollbar()
142 #endregion Constructors
148 /// Return a copied Style instance of CircularScrollbar
151 /// It returns copied Style instance and changing it does not effect to the CircularScrollbar.
152 /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
154 [EditorBrowsable(EditorBrowsableState.Never)]
155 public CircularScrollbarStyle Style
159 var result = new CircularScrollbarStyle(ViewStyle as CircularScrollbarStyle);
160 result.CopyPropertiesFromView(this);
166 /// The thickness of the scrollbar and track.
168 [EditorBrowsable(EditorBrowsableState.Never)]
169 public float Thickness
171 get => (float)GetValue(ThicknessProperty);
172 set => SetValue(ThicknessProperty, value);
176 /// The sweep angle of track area in degrees.
179 /// Values below 6 degrees are treated as 6 degrees.
180 /// Values exceeding 180 degrees are treated as 180 degrees.
182 [EditorBrowsable(EditorBrowsableState.Never)]
183 public float TrackSweepAngle
185 get => (float)GetValue(TrackSweepAngleProperty);
186 set => SetValue(TrackSweepAngleProperty, value);
190 /// The color of the track part.
192 [EditorBrowsable(EditorBrowsableState.Never)]
193 public Color TrackColor
195 get => (Color)GetValue(TrackColorProperty);
196 set => SetValue(TrackColorProperty, value);
200 /// The color of the thumb part.
202 [EditorBrowsable(EditorBrowsableState.Never)]
203 public Color ThumbColor
205 get => (Color)GetValue(ThumbColorProperty);
206 set => SetValue(ThumbColorProperty, value);
209 private CircularScrollbarStyle CurrentStyle => ViewStyle as CircularScrollbarStyle;
211 #endregion Properties
217 [EditorBrowsable(EditorBrowsableState.Never)]
218 public override void Initialize(float contentLength, float viewportLenth, float currentPosition, bool isHorizontal = false)
220 this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
221 this.visibleLength = viewportLenth;
222 this.currentPosition = currentPosition;
223 this.directionAlpha = isHorizontal ? 270.0f : 0.0f;
225 thumbStartAngleAnimation?.Stop();
226 thumbStartAngleAnimation = null;
228 thumbSweepAngleAnimation?.Stop();
229 thumbSweepAngleAnimation = null;
232 float trackSweepAngle = CalculateTrackSweepAngle(TrackSweepAngle);
233 float trackStartAngle = CalculateTrackStartAngle(trackSweepAngle);
234 float thumbSweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
235 float thumbStartAngle = CalculateThumbStartAngle(currentPosition, trackStartAngle, trackSweepAngle, thumbSweepAngle);
237 if (trackVisual == null)
239 trackVisual = new ArcVisual
241 SuppressUpdateVisual = true,
242 Thickness = this.Thickness,
243 Cap = ArcVisual.CapType.Round,
244 MixColor = TrackColor,
245 Size = containerSize - new Size(2, 2),
246 SizePolicy = VisualTransformPolicyType.Absolute,
247 SweepAngle = trackSweepAngle,
248 StartAngle = trackStartAngle,
251 AddVisual("Track", trackVisual);
255 trackVisual.SweepAngle = trackSweepAngle;
256 trackVisual.StartAngle = trackStartAngle;
257 trackVisual.UpdateVisual(true);
260 if (thumbVisual == null)
262 thumbVisual = new ArcVisual
264 SuppressUpdateVisual = true,
265 Thickness = trackVisual.Thickness,
266 Cap = ArcVisual.CapType.Round,
267 MixColor = ThumbColor,
268 Size = containerSize - new Size(2, 2),
269 SizePolicy = VisualTransformPolicyType.Absolute,
270 SweepAngle = thumbSweepAngle,
271 StartAngle = thumbStartAngle,
272 Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f,
275 AddVisual("Thumb", thumbVisual);
279 thumbVisual.SweepAngle = thumbSweepAngle;
280 thumbVisual.StartAngle = thumbStartAngle;
281 thumbVisual.UpdateVisual(true);
286 [EditorBrowsable(EditorBrowsableState.Never)]
287 public override void Update(float contentLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
289 this.previousPosition = this.currentPosition;
290 this.currentPosition = position;
291 this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
293 if (thumbVisual == null)
298 thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
299 thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
300 thumbVisual.Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f;
304 thumbVisual.UpdateVisual(true);
309 // TODO Support non built-in alpha function for visual trainsition in DALi.
310 AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
312 thumbStartAngleAnimation?.Stop();
313 thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
314 thumbStartAngleAnimation.Play();
316 thumbSweepAngleAnimation?.Stop();
317 thumbSweepAngleAnimation = AnimateVisual(thumbVisual, "sweepAngle", thumbVisual.SweepAngle, 0, (int)durationMs, builtinAlphaFunction);
318 thumbSweepAngleAnimation.Play();
322 [EditorBrowsable(EditorBrowsableState.Never)]
323 public override void ScrollTo(float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
325 previousPosition = currentPosition;
326 currentPosition = position;
328 if (mScrollEnabled == false)
333 if (thumbVisual == null)
338 var oldThumbStartAngle = thumbVisual.StartAngle;
340 thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
344 thumbVisual.UpdateVisual(true);
349 // TODO Support non built-in alpha function for visual trainsition in DALi.
350 AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
352 thumbStartAngleAnimation?.Stop();
353 thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
354 thumbStartAngleAnimation.Play();
358 [EditorBrowsable(EditorBrowsableState.Never)]
359 public override void OnRelayout(Vector2 size, RelayoutContainer container)
361 base.OnRelayout(size, container);
363 if (size.Width == containerSize?.Width && size.Height == containerSize.Height)
368 containerSize = new Size(size.Width, size.Height);
370 if (trackVisual == null)
375 trackVisual.Size = containerSize - new Size(2, 2);
376 thumbVisual.Size = containerSize - new Size(2, 2);
378 trackVisual.UpdateVisual(true);
379 thumbVisual.UpdateVisual(true);
383 [EditorBrowsable(EditorBrowsableState.Never)]
384 public override void ApplyStyle(ViewStyle viewStyle)
386 if (viewStyle == null) return;
387 if (viewStyle.WidthResizePolicy == null) viewStyle.WidthResizePolicy = ResizePolicyType.FillToParent;
388 if (viewStyle.HeightResizePolicy == null) viewStyle.HeightResizePolicy = ResizePolicyType.FillToParent;
390 base.ApplyStyle(viewStyle);
394 [EditorBrowsable(EditorBrowsableState.Never)]
395 protected override ViewStyle CreateViewStyle()
397 return new CircularScrollbarStyle();
400 private float CalculateTrackStartAngle(float currentTrackSweepAngle)
402 return ((180.0f - currentTrackSweepAngle) / 2.0f) + directionAlpha;
405 private float CalculateTrackSweepAngle(float inputTrackSweepAngle)
407 return Math.Min(Math.Max(inputTrackSweepAngle, 3), 180);
410 private float CalculateThumbStartAngle(float position, float trackStartAngle, float trackSweepAngle, float thumbSweepAngle)
412 float minAngle = trackStartAngle;
413 float maxAngle = trackStartAngle + trackSweepAngle - thumbSweepAngle;
414 float resultAngle = trackStartAngle + (trackSweepAngle * (position < 0.0f ? 0.0f : position) / contentLength);
416 return Math.Min(Math.Max(resultAngle, minAngle), maxAngle);
419 private float CalculateThumbSweepAngle(float trackSweepAngle)
421 return trackSweepAngle * visibleLength / contentLength;
424 private bool CalculateThumbVisibility()
426 return contentLength > visibleLength;
429 private void UpdateVisualThickness(float thickness)
431 if (trackVisual == null)
436 trackVisual.Thickness = thickness;
437 thumbVisual.Thickness = thickness;
439 trackVisual.UpdateVisual(true);
440 thumbVisual.UpdateVisual(true);
443 private void UpdateTrackVisualSweepAngle(float trackSweepAngle)
445 if (trackVisual == null || thumbVisual == null)
450 trackVisual.SweepAngle = CalculateTrackSweepAngle(trackSweepAngle);
451 trackVisual.StartAngle = CalculateTrackStartAngle(trackVisual.SweepAngle);
453 thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
454 thumbVisual.StartAngle = CalculateThumbStartAngle(currentPosition, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
456 trackVisual.UpdateVisual(true);
457 thumbVisual.UpdateVisual(true);
460 private void UpdateTrackVisualColor(Color trackColor)
462 if (trackVisual == null)
467 trackVisual.MixColor = trackColor;
468 trackVisual.UpdateVisual(true);
471 private void UpdateThumbVisualColor(Color thumbColor)
473 if (thumbVisual == null)
478 thumbVisual.MixColor = thumbColor;
479 thumbVisual.UpdateVisual(true);
483 [EditorBrowsable(EditorBrowsableState.Never)]
484 public override bool ScrollEnabled
488 return mScrollEnabled;
492 if (value != mScrollEnabled)
494 mScrollEnabled = value;
500 [EditorBrowsable(EditorBrowsableState.Never)]
501 public override Position ScrollPosition
505 bool isHorizontal = (directionAlpha == 270.0f) ? true : false;
506 float length = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
508 return (isHorizontal ? new Position(length, 0.0f) : new Position(0.0f, length));
513 [EditorBrowsable(EditorBrowsableState.Never)]
514 public override Position ScrollCurrentPosition
518 bool isHorizontal = (directionAlpha == 270.0f) ? true : false;
519 float length = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
521 if (thumbStartAngleAnimation != null)
523 float progress = thumbStartAngleAnimation.CurrentProgress;
524 float previousLength = Math.Min(Math.Max(previousPosition, 0.0f), contentLength - visibleLength);
526 length = ((1.0f - progress) * previousLength) + (progress * length);
529 return (isHorizontal ? new Position(length, 0.0f) : new Position(0.0f, length));