/*
* 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 Tizen.NUI.BaseComponents;
using Tizen.NUI.Binding;
using Tizen.NUI.Components;
using System.ComponentModel;
namespace Tizen.NUI.Wearable
{
///
/// Value Changed event data.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public class CircularSliderValueChangedEventArgs : EventArgs
{
private float currentValue = 0.0f;
///
/// Current value
///
[EditorBrowsable(EditorBrowsableState.Never)]
public float CurrentValue
{
get { return currentValue; }
set { currentValue = value; }
}
}
///
/// The CircularSlider class of Wearable is used to let users select a value from a continuous or discrete range of values by moving the slider thumb.
/// CircularSlider shows the current value with the length of the line.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public class CircularSlider : Control
{
#region Fields
/// Bindable property of Thickness
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty ThicknessProperty = BindableProperty.Create(nameof(Thickness), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = ((CircularSlider)bindable);
instance.CurrentStyle.Thickness = (float)newValue;
instance.UpdateVisualThickness((float)newValue);
},
defaultValueCreator: (bindable) =>
{
return ((CircularSlider)bindable).CurrentStyle.Thickness;
});
/// Bindable property of MaxValue
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty MaxValueProperty = BindableProperty.Create(nameof(MaxValue), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
if (newValue != null)
{
instance.maxValue = (float)newValue;
instance.UpdateValue();
}
},
defaultValueCreator: (bindable) =>
{
var instance = (CircularSlider)bindable;
return instance.maxValue;
});
/// Bindable property of MinValue
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty MinValueProperty = BindableProperty.Create(nameof(MinValue), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
if (newValue != null)
{
instance.minValue = (float)newValue;
instance.UpdateValue();
}
},
defaultValueCreator: (bindable) =>
{
var instance = (CircularSlider)bindable;
return instance.minValue;
});
/// Bindable property of CurrentValue
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty CurrentValueProperty = BindableProperty.Create(nameof(CurrentValue), typeof(float), typeof(CircularSlider), default(float), propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
if (newValue != null)
{
if ((float)newValue > instance.maxValue || (float)newValue < instance.minValue)
{
return;
}
instance.currentValue = (float)newValue;
instance.UpdateValue();
}
},
defaultValueCreator: (bindable) =>
{
var instance = (CircularSlider)bindable;
return instance.currentValue;
});
/// Bindable property of TrackColor
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty TrackColorProperty = BindableProperty.Create(nameof(TrackColor), typeof(Color), typeof(CircularSlider), null, propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
instance.CurrentStyle.TrackColor = (Color)newValue;
instance.UpdateTrackVisualColor((Color)newValue);
},
defaultValueCreator: (bindable) =>
{
return ((CircularSlider)bindable).CurrentStyle.TrackColor;
});
/// Bindable property of ProgressColor
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(nameof(ProgressColor), typeof(Color), typeof(CircularSlider), null, propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
instance.CurrentStyle.ProgressColor = (Color)newValue;
instance.UpdateProgressVisualColor((Color)newValue);
},
defaultValueCreator: (bindable) =>
{
return ((CircularSlider)bindable).CurrentStyle.ProgressColor;
});
/// Bindable property of ThumbSize
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty ThumbSizeProperty = BindableProperty.Create(nameof(ThumbSize), typeof(Size), typeof(CircularSlider), new Size(0,0), propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
if (newValue != null)
{
instance.thumbSize = (Size)newValue;
instance.UpdateThumbVisualSize((Size)newValue);
}
},
defaultValueCreator: (bindable) =>
{
var instance = (CircularSlider)bindable;
return instance.thumbSize;
});
/// Bindable property of ThumbColor
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty ThumbColorProperty = BindableProperty.Create(nameof(ThumbColor), typeof(Color), typeof(CircularSlider), null, propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
instance.CurrentStyle.ThumbColor = (Color)newValue;
instance.UpdateThumbVisualColor((Color)newValue);
},
defaultValueCreator: (bindable) =>
{
return ((CircularSlider)bindable).CurrentStyle.ThumbColor;
});
/// Bindable property of IsEnabled
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool), typeof(CircularSlider), true, propertyChanged: (bindable, oldValue, newValue) =>
{
var instance = (CircularSlider)bindable;
if (newValue != null)
{
instance.privateIsEnabled = (bool)newValue;
}
},
defaultValueCreator: (bindable) =>
{
var instance = (CircularSlider)bindable;
return instance.privateIsEnabled;
});
private const string TrackVisualName = "Track";
private const string ProgressVisualName = "Progress";
private const string ThumbVisualName = "Thumb";
private ArcVisual trackVisual;
private ArcVisual progressVisual;
private ArcVisual thumbVisual;
private float maxValue = 100;
private float minValue = 0;
private float currentValue = 0;
private Size thumbSize;
private bool isEnabled = true;
float sliderPadding = 6.0f;
private Animation sweepAngleAnimation;
private Animation thumbAnimation;
#endregion Fields
#region Constructors
static CircularSlider() { }
///
/// The constructor of CircularSlider.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public CircularSlider() : base()
{
Initialize();
}
///
/// The constructor of the CircularSlider class with specific style.
///
/// The style object to initialize the CircularSlider.
[EditorBrowsable(EditorBrowsableState.Never)]
public CircularSlider(CircularSliderStyle progressStyle) : base(progressStyle)
{
Initialize();
}
#endregion Constructors
#region Events
///
/// The value changed event handler.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler ValueChanged;
#endregion Events
#region Properties
///
/// Return a copied Style instance of CircularSlider
///
///
/// It returns copied Style instance and changing it does not effect to the CircularSlider.
/// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
///
[EditorBrowsable(EditorBrowsableState.Never)]
public new CircularSliderStyle Style
{
get
{
var result = new CircularSliderStyle(ViewStyle as CircularSliderStyle);
result.CopyPropertiesFromView(this);
return result;
}
}
///
/// The thickness of the track and progress.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public float Thickness
{
get
{
return (float)GetValue(ThicknessProperty);
}
set
{
SetValue(ThicknessProperty, value);
}
}
///
/// The property to get/set the maximum value of the CircularSlider.
/// The default value is 100.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public float MaxValue
{
get
{
return (float)GetValue(MaxValueProperty);
}
set
{
SetValue(MaxValueProperty, value);
}
}
///
/// The property to get/set the minimum value of the CircularSlider.
/// The default value is 0.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public float MinValue
{
get
{
return (float)GetValue(MinValueProperty);
}
set
{
SetValue(MinValueProperty, value);
}
}
///
/// The property to get/set the current value of the CircularSlider.
/// The default value is 0.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public float CurrentValue
{
get
{
return (float)GetValue(CurrentValueProperty);
}
set
{
if (sweepAngleAnimation)
{
sweepAngleAnimation.Stop();
}
// For the first Animation effect
sweepAngleAnimation = AnimateVisual(progressVisual, "sweepAngle", progressVisual.SweepAngle, 0, 100, AlphaFunction.BuiltinFunctions.EaseIn);
SetValue(CurrentValueProperty, value);
UpdateAnimation();
}
}
///
/// The property to get/set Track object color of the CircularSlider.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public Color TrackColor
{
get
{
return (Color)GetValue(TrackColorProperty);
}
set
{
SetValue(TrackColorProperty, value);
}
}
///
/// The property to get/set Progress object color of the CircularSlider.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public Color ProgressColor
{
get
{
return (Color)GetValue(ProgressColorProperty);
}
set
{
SetValue(ProgressColorProperty, value);
}
}
///
/// Gets or sets the size of the thumb of Slider.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public Size ThumbSize
{
get
{
return (Size)GetValue(ThumbSizeProperty);
}
set
{
SetValue(ThumbSizeProperty, value);
}
}
///
/// The property to get/set Thumb object color of the CircularSlider.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public Color ThumbColor
{
get
{
return (Color)GetValue(ThumbColorProperty);
}
set
{
SetValue(ThumbColorProperty, value);
}
}
///
/// Flag to be enabled or disabled in CircularSlider.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEnabled
{
get
{
return (bool)GetValue(IsEnabledProperty);
}
set
{
SetValue(IsEnabledProperty, value);
}
}
private bool privateIsEnabled
{
get
{
return isEnabled;
}
set
{
isEnabled = value;
if (isEnabled)
{
UpdateTrackVisualColor(new Color(0.0f, 0.16f, 0.30f, 1.0f)); // #002A4D
}
else
{
UpdateTrackVisualColor(new Color(0.25f, 0.25f, 0.25f, 1.0f)); // #404040
}
}
}
private CircularSliderStyle CurrentStyle => ViewStyle as CircularSliderStyle;
#endregion Properties
#region Methods
///
/// Dispose Progress and all children on it.
///
/// Dispose type.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void Dispose(DisposeTypes type)
{
if (disposed)
{
return;
}
if (type == DisposeTypes.Explicit)
{
trackVisual = null;
progressVisual = null;
thumbVisual = null;
}
base.Dispose(type);
}
///
/// Update progress value
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected virtual void UpdateValue()
{
if (null == trackVisual || null == progressVisual || null == thumbVisual)
{
return;
}
if (minValue >= maxValue || currentValue < minValue || currentValue > maxValue)
{
return;
}
HandleProgressVisualVisibility();
UpdateProgressVisualSweepAngle();
}
///
/// Get Progress style.
///
/// The default progress style.
[EditorBrowsable(EditorBrowsableState.Never)]
protected override ViewStyle CreateViewStyle()
{
return new CircularSliderStyle();
}
private void Initialize()
{
Size = new Size(360.0f, 360.0f);
sweepAngleAnimation?.Stop();
sweepAngleAnimation = null;
thumbAnimation?.Stop();
thumbAnimation = null;
trackVisual = new ArcVisual
{
SuppressUpdateVisual = true,
Size = new Size(this.Size.Width - sliderPadding, this.Size.Height - sliderPadding),
SizePolicy = VisualTransformPolicyType.Absolute,
Thickness = this.Thickness,
Cap = ArcVisual.CapType.Butt,
MixColor = TrackColor,
StartAngle = 0.0f,
SweepAngle = 360.0f
};
this.AddVisual(TrackVisualName, trackVisual);
progressVisual = new ArcVisual
{
SuppressUpdateVisual = true,
Size = new Size(this.Size.Width - sliderPadding, this.Size.Height - sliderPadding),
SizePolicy = VisualTransformPolicyType.Absolute,
Thickness = this.Thickness,
Cap = ArcVisual.CapType.Butt,
MixColor = ProgressColor,
StartAngle = 0.0f,
SweepAngle = 0.0f
};
this.AddVisual(ProgressVisualName, progressVisual);
thumbVisual = new ArcVisual
{
SuppressUpdateVisual = true,
Size = new Size(this.Size.Width + sliderPadding, this.Size.Height + sliderPadding),
SizePolicy = VisualTransformPolicyType.Absolute,
Thickness = this.ThumbSize.Width,
Cap = ArcVisual.CapType.Round,
MixColor = this.ThumbColor,
StartAngle = 0.0f,
SweepAngle = 0.0f
};
this.AddVisual(ThumbVisualName, thumbVisual);
HandleProgressVisualVisibility();
UpdateProgressVisualSweepAngle();
}
private void HandleProgressVisualVisibility()
{
if (isEnabled)
{
progressVisual.Opacity = 1.0f;
}
else if (!isEnabled)
{
progressVisual.Opacity = 0.6f;
}
}
private void UpdateVisualThickness(float thickness)
{
if (trackVisual == null)
{
return;
}
trackVisual.Thickness = thickness;
progressVisual.Thickness = thickness;
trackVisual.UpdateVisual(true);
progressVisual.UpdateVisual(true);
}
private void UpdateProgressVisualSweepAngle()
{
float progressRatio = (float)(currentValue - minValue) / (float)(maxValue - minValue);
float progressWidth = 360.0f * progressRatio; // Circle
progressVisual.SweepAngle = progressWidth;
thumbVisual.StartAngle = progressWidth;
if (!sweepAngleAnimation)
{
progressVisual.UpdateVisual(true);
thumbVisual.UpdateVisual(true);
}
}
private void UpdateAnimation()
{
// TODO : Currently not sure which effect is needed.
AlphaFunction.BuiltinFunctions builtinAlphaFunction = AlphaFunction.BuiltinFunctions.EaseOut;
if (sweepAngleAnimation)
{
sweepAngleAnimation.Stop();
}
if (thumbAnimation)
{
thumbAnimation.Stop();
}
sweepAngleAnimation = AnimateVisual(progressVisual, "sweepAngle", progressVisual.SweepAngle, 0, 500, builtinAlphaFunction);
thumbAnimation = AnimateVisual(thumbVisual, "startAngle", thumbVisual.StartAngle, 0, 500, builtinAlphaFunction);
if (sweepAngleAnimation)
{
sweepAngleAnimation.Play();
thumbAnimation.Play();
}
ValueChanged?.Invoke(this, new CircularSliderValueChangedEventArgs() { CurrentValue = currentValue });
}
private void UpdateTrackVisualColor(Color trackColor)
{
if (trackVisual == null)
{
return;
}
trackVisual.MixColor = trackColor;
trackVisual.UpdateVisual(true);
}
private void UpdateProgressVisualColor(Color progressColor)
{
if (progressVisual == null)
{
return;
}
progressVisual.MixColor = progressColor;
if (!isEnabled) // Dim state
{
progressVisual.Opacity = 0.6f;
}
progressVisual.UpdateVisual(true);
}
private void UpdateThumbVisualSize(Size thumbSize)
{
if (thumbVisual == null)
{
return;
}
thumbVisual.Thickness = thumbSize.Width;
thumbVisual.UpdateVisual(true);
}
private void UpdateThumbVisualColor(Color thumbColor)
{
if (thumbVisual == null)
{
return;
}
thumbVisual.MixColor = thumbColor;
thumbVisual.UpdateVisual(true);
}
#endregion Methods
}
}