/* * Copyright(c) 2020 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ using System; using System.ComponentModel; using Tizen.NUI.BaseComponents; using Tizen.NUI.Binding; using Tizen.NUI.Components; namespace Tizen.NUI.Wearable { /// /// The CircualrScrollbar is a wearable NUI component that can be linked to the scrollable objects /// indicating the current scroll position of the scrollable object.
///
[EditorBrowsable(EditorBrowsableState.Never)] public class CircularScrollbar : ScrollbarBase { #region Fields /// Bindable property of Thickness [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty ThicknessProperty = BindableProperty.Create(nameof(Thickness), typeof(float), typeof(CircularScrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) => { var instance = ((CircularScrollbar)bindable); var thickness = (float?)newValue; ((CircularScrollbarStyle)instance.viewStyle).Thickness = thickness; instance.UpdateVisualThickness(thickness ?? 0); }, defaultValueCreator: (bindable) => { return ((CircularScrollbarStyle)((CircularScrollbar)bindable).viewStyle)?.Thickness ?? 0; }); /// Bindable property of TrackSweepAngle [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty TrackSweepAngleProperty = BindableProperty.Create(nameof(TrackSweepAngle), typeof(float), typeof(CircularScrollbar), default(float), propertyChanged: (bindable, oldValue, newValue) => { var instance = ((CircularScrollbar)bindable); var angle = (float?)newValue; ((CircularScrollbarStyle)instance.viewStyle).TrackSweepAngle = angle; instance.UpdateTrackVisualSweepAngle(angle ?? 0); }, defaultValueCreator: (bindable) => { return ((CircularScrollbarStyle)((CircularScrollbar)bindable).viewStyle)?.TrackSweepAngle ?? 0; }); /// Bindable property of TrackColor [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty TrackColorProperty = BindableProperty.Create(nameof(TrackColor), typeof(Color), typeof(CircularScrollbar), null, propertyChanged: (bindable, oldValue, newValue) => { var instance = ((CircularScrollbar)bindable); var color = (Color)newValue; ((CircularScrollbarStyle)instance.viewStyle).TrackColor = color; instance.UpdateTrackVisualColor(color); }, defaultValueCreator: (bindable) => { return ((CircularScrollbarStyle)((CircularScrollbar)bindable).viewStyle)?.TrackColor; }); /// Bindable property of ThumbColor [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty ThumbColorProperty = BindableProperty.Create(nameof(ThumbColor), typeof(Color), typeof(CircularScrollbar), null, propertyChanged: (bindable, oldValue, newValue) => { var instance = ((CircularScrollbar)bindable); var color = (Color)newValue; ((CircularScrollbarStyle)instance.viewStyle).ThumbColor = color; instance.UpdateThumbVisualColor(color); }, defaultValueCreator: (bindable) => { return ((CircularScrollbarStyle)((CircularScrollbar)bindable).viewStyle)?.ThumbColor; }); private ArcVisual trackVisual; private ArcVisual thumbVisual; private float contentLength; private float visibleLength; private float currentPosition; private float directionAlpha; private Size containerSize = new Size(0, 0); private Animation thumbStartAngleAnimation; private Animation thumbSweepAngleAnimation; #endregion Fields #region Constructors /// /// Create an empty CircularScrollbar. /// public CircularScrollbar() : base(new CircularScrollbarStyle()) { } /// /// Create a CircularScrollbar and initialize with properties. /// /// The length of the scrollable content area. /// The length of the viewport representing the amount of visible content. /// The current position of the viewport in scrollable content area. This is the viewport's top position if the scroller is vertical, otherwise, left. /// Whether the direction of scrolling is horizontal or not. It is vertical by default. [EditorBrowsable(EditorBrowsableState.Never)] public CircularScrollbar(float contentLength, float viewportLength, float currentPosition, bool isHorizontal = false) : base(new CircularScrollbarStyle()) { Initialize(contentLength, viewportLength, currentPosition, isHorizontal); } /// /// Create an empty CircularScrollbar with a CircularScrollbarStyle instance to set style properties. /// [EditorBrowsable(EditorBrowsableState.Never)] public CircularScrollbar(CircularScrollbarStyle style) : base(style) { } /// /// Static constructor to initialize bindable properties when loading. /// static CircularScrollbar() { } #endregion Constructors #region Properties /// /// The thickness of the scrollbar and track. /// [EditorBrowsable(EditorBrowsableState.Never)] public float Thickness { get => (float)GetValue(ThicknessProperty); set => SetValue(ThicknessProperty, value); } /// /// The sweep angle of track area in degrees. /// /// /// Values below 6 degrees are treated as 6 degrees. /// Values exceeding 180 degrees are treated as 180 degrees. /// [EditorBrowsable(EditorBrowsableState.Never)] public float TrackSweepAngle { get => (float)GetValue(TrackSweepAngleProperty); set => SetValue(TrackSweepAngleProperty, value); } /// /// The color of the track part. /// [EditorBrowsable(EditorBrowsableState.Never)] public Color TrackColor { get => (Color)GetValue(TrackColorProperty); set => SetValue(TrackColorProperty, value); } /// /// The color of the thumb part. /// [EditorBrowsable(EditorBrowsableState.Never)] public Color ThumbColor { get => (Color)GetValue(ThumbColorProperty); set => SetValue(ThumbColorProperty, value); } #endregion Properties #region Methods /// [EditorBrowsable(EditorBrowsableState.Never)] public override void Initialize(float contentLength, float viewportLenth, float currentPosition, bool isHorizontal = false) { this.contentLength = contentLength > 0.0f ? contentLength : 0.0f; this.visibleLength = viewportLenth; this.currentPosition = currentPosition; this.directionAlpha = isHorizontal ? 270.0f : 0.0f; thumbStartAngleAnimation?.Stop(); thumbStartAngleAnimation = null; thumbSweepAngleAnimation?.Stop(); thumbSweepAngleAnimation = null; float trackSweepAngle = CalculateTrackSweepAngle(TrackSweepAngle); float trackStartAngle = CalculateTrackStartAngle(trackSweepAngle); float thumbSweepAngle = CalculateThumbSweepAngle(TrackSweepAngle); float thumbStartAngle = CalculateThumbStartAngle(currentPosition, trackStartAngle, trackSweepAngle, thumbSweepAngle); if (trackVisual == null) { trackVisual = new ArcVisual { SuppressUpdateVisual = true, Thickness = this.Thickness, Cap = ArcVisual.CapType.Round, MixColor = TrackColor, Size = containerSize - new Size(2, 2), SizePolicy = VisualTransformPolicyType.Absolute, SweepAngle = trackSweepAngle, StartAngle = trackStartAngle, }; AddVisual("Track", trackVisual); } else { trackVisual.SweepAngle = trackSweepAngle; trackVisual.StartAngle = trackStartAngle; trackVisual.UpdateVisual(true); } if (thumbVisual == null) { thumbVisual = new ArcVisual { SuppressUpdateVisual = true, Thickness = trackVisual.Thickness, Cap = ArcVisual.CapType.Round, MixColor = ThumbColor, Size = containerSize - new Size(2, 2), SizePolicy = VisualTransformPolicyType.Absolute, SweepAngle = thumbSweepAngle, StartAngle = thumbStartAngle, Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f, }; AddVisual("Thumb", thumbVisual); } else { thumbVisual.SweepAngle = thumbSweepAngle; thumbVisual.StartAngle = thumbStartAngle; thumbVisual.UpdateVisual(true); } } /// [EditorBrowsable(EditorBrowsableState.Never)] public override void Update(float contentLength, float position, uint durationMs = 0, AlphaFunction alphaFunction = null) { this.currentPosition = position; this.contentLength = contentLength > 0.0f ? contentLength : 0.0f; if (thumbVisual == null) { return; } thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle); thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle); thumbVisual.Opacity = CalculateThumbVisibility() ? 1.0f : 0.0f; if (durationMs == 0) { thumbVisual.UpdateVisual(true); return; } // TODO Support non built-in alpha function for visual trainsition in DALi. AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default; thumbStartAngleAnimation?.Stop(); thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction); thumbStartAngleAnimation.Play(); thumbSweepAngleAnimation?.Stop(); thumbSweepAngleAnimation = AnimateVisual(thumbVisual, "sweepAngle", thumbVisual.SweepAngle, 0, (int)durationMs, builtinAlphaFunction); thumbSweepAngleAnimation.Play(); } /// [EditorBrowsable(EditorBrowsableState.Never)] public override void ScrollTo(float position, uint durationMs = 0, AlphaFunction alphaFunction = null) { currentPosition = position; if (thumbVisual == null) { return; } var oldThumbStartAngle = thumbVisual.StartAngle; thumbVisual.StartAngle = CalculateThumbStartAngle(position, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle); if (durationMs == 0) { thumbVisual.UpdateVisual(true); return; } // TODO Support non built-in alpha function for visual trainsition in DALi. AlphaFunction.BuiltinFunctions builtinAlphaFunction = alphaFunction?.GetBuiltinFunction() ?? AlphaFunction.BuiltinFunctions.Default; thumbStartAngleAnimation?.Stop(); thumbStartAngleAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, (int)durationMs, builtinAlphaFunction); thumbStartAngleAnimation.Play(); } /// [EditorBrowsable(EditorBrowsableState.Never)] public override void OnRelayout(Vector2 size, RelayoutContainer container) { base.OnRelayout(size, container); if (size.Width == containerSize?.Width && size.Height == containerSize.Height) { return; } containerSize = new Size(size.Width, size.Height); if (trackVisual == null) { return; } trackVisual.Size = containerSize - new Size(2, 2); thumbVisual.Size = containerSize - new Size(2, 2); trackVisual.UpdateVisual(true); thumbVisual.UpdateVisual(true); } /// [EditorBrowsable(EditorBrowsableState.Never)] protected override ViewStyle GetViewStyle() { return new CircularScrollbarStyle(); } private float CalculateTrackStartAngle(float currentTrackSweepAngle) { return ((180.0f - currentTrackSweepAngle) / 2.0f) + directionAlpha; } private float CalculateTrackSweepAngle(float inputTrackSweepAngle) { return Math.Min(Math.Max(inputTrackSweepAngle, 3), 180); } private float CalculateThumbStartAngle(float position, float trackStartAngle, float trackSweepAngle, float thumbSweepAngle) { float minAngle = trackStartAngle; float maxAngle = trackStartAngle + trackSweepAngle - thumbSweepAngle; float resultAngle = trackStartAngle + (trackSweepAngle * (position < 0.0f ? 0.0f : position) / contentLength); return Math.Min(Math.Max(resultAngle, minAngle), maxAngle); } private float CalculateThumbSweepAngle(float trackSweepAngle) { return trackSweepAngle * visibleLength / contentLength; } private bool CalculateThumbVisibility() { return contentLength > visibleLength; } private void UpdateVisualThickness(float thickness) { if (trackVisual == null) { return; } trackVisual.Thickness = thickness; thumbVisual.Thickness = thickness; trackVisual.UpdateVisual(true); thumbVisual.UpdateVisual(true); } private void UpdateTrackVisualSweepAngle(float trackSweepAngle) { if (trackVisual == null || thumbVisual == null) { return; } trackVisual.SweepAngle = CalculateTrackSweepAngle(trackSweepAngle); trackVisual.StartAngle = CalculateTrackStartAngle(trackVisual.SweepAngle); thumbVisual.SweepAngle = CalculateThumbSweepAngle(TrackSweepAngle); thumbVisual.StartAngle = CalculateThumbStartAngle(currentPosition, trackVisual.StartAngle, trackVisual.SweepAngle, thumbVisual.SweepAngle); trackVisual.UpdateVisual(true); thumbVisual.UpdateVisual(true); } private void UpdateTrackVisualColor(Color trackColor) { if (trackVisual == null) { return; } trackVisual.MixColor = trackColor; trackVisual.UpdateVisual(true); } private void UpdateThumbVisualColor(Color thumbColor) { if (thumbVisual == null) { return; } thumbVisual.MixColor = thumbColor; thumbVisual.UpdateVisual(true); } #endregion Methods } }