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()
140 ThemeManager.AddPackageTheme(new DefaultThemeCreator());
143 #endregion Constructors
149 /// Return a copied Style instance of CircularScrollbar
152 /// It returns copied Style instance and changing it does not effect to the CircularScrollbar.
153 /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
155 [EditorBrowsable(EditorBrowsableState.Never)]
156 public CircularScrollbarStyle Style
160 var result = new CircularScrollbarStyle(ViewStyle as CircularScrollbarStyle);
161 result.CopyPropertiesFromView(this);
167 /// The thickness of the scrollbar and track.
169 [EditorBrowsable(EditorBrowsableState.Never)]
170 public float Thickness
172 get => (float)GetValue(ThicknessProperty);
173 set => SetValue(ThicknessProperty, value);
177 /// The sweep angle of track area in degrees.
180 /// Values below 6 degrees are treated as 6 degrees.
181 /// Values exceeding 180 degrees are treated as 180 degrees.
183 [EditorBrowsable(EditorBrowsableState.Never)]
184 public float TrackSweepAngle
186 get => (float)GetValue(TrackSweepAngleProperty);
187 set => SetValue(TrackSweepAngleProperty, value);
191 /// The color of the track part.
193 [EditorBrowsable(EditorBrowsableState.Never)]
194 public Color TrackColor
196 get => (Color)GetValue(TrackColorProperty);
197 set => SetValue(TrackColorProperty, value);
201 /// The color of the thumb part.
203 [EditorBrowsable(EditorBrowsableState.Never)]
204 public Color ThumbColor
206 get => (Color)GetValue(ThumbColorProperty);
207 set => SetValue(ThumbColorProperty, value);
210 private CircularScrollbarStyle CurrentStyle => ViewStyle as CircularScrollbarStyle;
212 #endregion Properties
218 [EditorBrowsable(EditorBrowsableState.Never)]
219 public override void Initialize(float contentLength, float viewportLenth, float currentPosition, bool isHorizontal = false)
221 this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
222 this.visibleLength = viewportLenth;
223 this.currentPosition = currentPosition;
224 this.directionAlpha = isHorizontal ? 270.0f : 0.0f;
226 thumbStartAngleAnimation?.Stop();
227 thumbStartAngleAnimation = null;
229 thumbSweepAngleAnimation?.Stop();
230 thumbSweepAngleAnimation = null;
233 float trackSweepAngle = CalculateTrackSweepAngle(TrackSweepAngle);
234 float trackStartAngle = CalculateTrackStartAngle(trackSweepAngle);
235 float thumbSweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
236 float thumbStartAngle = CalculateThumbStartAngle(currentPosition, trackStartAngle, trackSweepAngle, thumbSweepAngle);
238 if (trackVisual == null)
240 trackVisual = new ArcVisual
242 SuppressUpdateVisual = true,
243 Thickness = this.Thickness,
244 Cap = ArcVisual.CapType.Round,
245 MixColor = TrackColor,
246 Size = containerSize - new Size(2, 2),
247 SizePolicy = VisualTransformPolicyType.Absolute,
248 SweepAngle = trackSweepAngle,
249 StartAngle = trackStartAngle,
252 AddVisual("Track", trackVisual);
256 trackVisual.SweepAngle = trackSweepAngle;
257 trackVisual.StartAngle = trackStartAngle;
258 trackVisual.UpdateVisual(true);
261 if (thumbVisual == null)
263 thumbVisual = new ArcVisual
265 SuppressUpdateVisual = true,
266 Thickness = trackVisual.Thickness,
267 Cap = ArcVisual.CapType.Round,
268 MixColor = ThumbColor,
269 Size = containerSize - new Size(2, 2),
270 SizePolicy = VisualTransformPolicyType.Absolute,
271 SweepAngle = thumbSweepAngle,
272 StartAngle = thumbStartAngle,
273 Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f,
276 AddVisual("Thumb", thumbVisual);
280 thumbVisual.SweepAngle = thumbSweepAngle;
281 thumbVisual.StartAngle = thumbStartAngle;
282 thumbVisual.UpdateVisual(true);
287 [EditorBrowsable(EditorBrowsableState.Never)]
288 public override void Update(float contentLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
290 this.previousPosition = this.currentPosition;
291 this.currentPosition = position;
292 this.contentLength = contentLength > 0.0f ? contentLength : 0.0f;
294 if (thumbVisual == null)
299 thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
300 thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
301 thumbVisual.Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f;
305 thumbVisual.UpdateVisual(true);
310 // TODO Support non built-in alpha function for visual trainsition in DALi.
311 AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
313 thumbStartAngleAnimation?.Stop();
314 thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
315 thumbStartAngleAnimation.Play();
317 thumbSweepAngleAnimation?.Stop();
318 thumbSweepAngleAnimation = AnimateVisual(thumbVisual, "sweepAngle", thumbVisual.SweepAngle, 0, (int)durationMs, builtinAlphaFunction);
319 thumbSweepAngleAnimation.Play();
323 [EditorBrowsable(EditorBrowsableState.Never)]
324 public override void ScrollTo(float position, uint durationMs = 0, AlphaFunction alphaFunction = null)
326 previousPosition = currentPosition;
327 currentPosition = position;
329 if (mScrollEnabled == false)
334 if (thumbVisual == null)
339 var oldThumbStartAngle = thumbVisual.StartAngle;
341 thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
345 thumbVisual.UpdateVisual(true);
350 // TODO Support non built-in alpha function for visual trainsition in DALi.
351 AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default;
353 thumbStartAngleAnimation?.Stop();
354 thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction);
355 thumbStartAngleAnimation.Play();
359 [EditorBrowsable(EditorBrowsableState.Never)]
360 public override void OnRelayout(Vector2 size, RelayoutContainer container)
362 base.OnRelayout(size, container);
364 if (size.Width == containerSize?.Width && size.Height == containerSize.Height)
369 containerSize = new Size(size.Width, size.Height);
371 if (trackVisual == null)
376 trackVisual.Size = containerSize - new Size(2, 2);
377 thumbVisual.Size = containerSize - new Size(2, 2);
379 trackVisual.UpdateVisual(true);
380 thumbVisual.UpdateVisual(true);
384 [EditorBrowsable(EditorBrowsableState.Never)]
385 public override void ApplyStyle(ViewStyle viewStyle)
387 if (viewStyle == null) return;
388 if (viewStyle.WidthResizePolicy == null) viewStyle.WidthResizePolicy = ResizePolicyType.FillToParent;
389 if (viewStyle.HeightResizePolicy == null) viewStyle.HeightResizePolicy = ResizePolicyType.FillToParent;
391 base.ApplyStyle(viewStyle);
395 [EditorBrowsable(EditorBrowsableState.Never)]
396 protected override ViewStyle CreateViewStyle()
398 return new CircularScrollbarStyle();
401 private float CalculateTrackStartAngle(float currentTrackSweepAngle)
403 return ((180.0f - currentTrackSweepAngle) / 2.0f) + directionAlpha;
406 private float CalculateTrackSweepAngle(float inputTrackSweepAngle)
408 return Math.Min(Math.Max(inputTrackSweepAngle, 3), 180);
411 private float CalculateThumbStartAngle(float position, float trackStartAngle, float trackSweepAngle, float thumbSweepAngle)
413 float minAngle = trackStartAngle;
414 float maxAngle = trackStartAngle + trackSweepAngle - thumbSweepAngle;
415 float resultAngle = trackStartAngle + (trackSweepAngle * (position < 0.0f ? 0.0f : position) / contentLength);
417 return Math.Min(Math.Max(resultAngle, minAngle), maxAngle);
420 private float CalculateThumbSweepAngle(float trackSweepAngle)
422 return trackSweepAngle * visibleLength / contentLength;
425 private bool CalculateThumbVisibility()
427 return contentLength > visibleLength;
430 private void UpdateVisualThickness(float thickness)
432 if (trackVisual == null)
437 trackVisual.Thickness = thickness;
438 thumbVisual.Thickness = thickness;
440 trackVisual.UpdateVisual(true);
441 thumbVisual.UpdateVisual(true);
444 private void UpdateTrackVisualSweepAngle(float trackSweepAngle)
446 if (trackVisual == null || thumbVisual == null)
451 trackVisual.SweepAngle = CalculateTrackSweepAngle(trackSweepAngle);
452 trackVisual.StartAngle = CalculateTrackStartAngle(trackVisual.SweepAngle);
454 thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle);
455 thumbVisual.StartAngle = CalculateThumbStartAngle(currentPosition, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle);
457 trackVisual.UpdateVisual(true);
458 thumbVisual.UpdateVisual(true);
461 private void UpdateTrackVisualColor(Color trackColor)
463 if (trackVisual == null)
468 trackVisual.MixColor = trackColor;
469 trackVisual.UpdateVisual(true);
472 private void UpdateThumbVisualColor(Color thumbColor)
474 if (thumbVisual == null)
479 thumbVisual.MixColor = thumbColor;
480 thumbVisual.UpdateVisual(true);
484 [EditorBrowsable(EditorBrowsableState.Never)]
485 public override bool ScrollEnabled
489 return mScrollEnabled;
493 if (value != mScrollEnabled)
495 mScrollEnabled = value;
501 [EditorBrowsable(EditorBrowsableState.Never)]
502 public override Position ScrollPosition
506 bool isHorizontal = (directionAlpha == 270.0f) ? true : false;
507 float length = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
509 return (isHorizontal ? new Position(length, 0.0f) : new Position(0.0f, length));
514 [EditorBrowsable(EditorBrowsableState.Never)]
515 public override Position ScrollCurrentPosition
519 bool isHorizontal = (directionAlpha == 270.0f) ? true : false;
520 float length = Math.Min(Math.Max(currentPosition, 0.0f), contentLength - visibleLength);
522 if (thumbStartAngleAnimation != null)
524 float progress = thumbStartAngleAnimation.CurrentProgress;
525 float previousLength = Math.Min(Math.Max(previousPosition, 0.0f), contentLength - visibleLength);
527 length = ((1.0f - progress) * previousLength) + (progress * length);
530 return (isHorizontal ? new Position(length, 0.0f) : new Position(0.0f, length));