1100 lines
38 KiB
C#
1100 lines
38 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Doozy.Runtime.Common.Attributes;
|
|
using Doozy.Runtime.Common.Events;
|
|
using Doozy.Runtime.Common.Extensions;
|
|
using Doozy.Runtime.Common.Utils;
|
|
using Doozy.Runtime.Mody;
|
|
using Doozy.Runtime.Reactor;
|
|
using Doozy.Runtime.Signals;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
// ReSharper disable MemberCanBePrivate.Global
|
|
// ReSharper disable PartialTypeWithSinglePart
|
|
// ReSharper disable UnusedMember.Local
|
|
|
|
namespace Doozy.Runtime.UIManager.Components
|
|
{
|
|
/// <summary>
|
|
/// Stepper component that can be used to increment or decrement a value.
|
|
/// Has category/name id identifier.
|
|
/// </summary>
|
|
[AddComponentMenu("Doozy/UI/Components/UI Stepper")]
|
|
public partial class UIStepper : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
|
|
{
|
|
private const float TOLERANCE = 0.0001f;
|
|
private const float DRAG_DISTANCE = 20f;
|
|
private const float WAIT_BEFORE_STARTING = 0.6f;
|
|
private const float WAIT_TIME = 0.4f;
|
|
private const float WAIT_TIME_MIN = 0.04f;
|
|
private const float WAIT_TIME_REDUCTION = 0.4f;
|
|
|
|
#if UNITY_EDITOR
|
|
[UnityEditor.MenuItem("GameObject/Doozy/UI/Components/UIStepper", false, 8)]
|
|
private static void CreateComponent(UnityEditor.MenuCommand menuCommand)
|
|
{
|
|
GameObjectUtils.AddToScene<UIStepper>("UIStepper", false, true);
|
|
}
|
|
#endif
|
|
|
|
/// <summary> UISteppers database </summary>
|
|
public static HashSet<UIStepper> database { get; private set; } = new HashSet<UIStepper>();
|
|
|
|
[ExecuteOnReload]
|
|
private static void OnReload()
|
|
{
|
|
database ??= new HashSet<UIStepper>();
|
|
}
|
|
|
|
[ClearOnReload]
|
|
private static SignalStream s_stream;
|
|
/// <summary> UIStepper signal stream </summary>
|
|
public static SignalStream stream => s_stream ??= SignalsService.GetStream(UISelectable.k_StreamCategory, nameof(UIStepper));
|
|
|
|
/// <summary> All steppers that are active and enabled </summary>
|
|
public static IEnumerable<UIStepper> availableSteppers => database.Where(item => item.isActiveAndEnabled);
|
|
|
|
/// <summary> UIStepper identifier </summary>
|
|
public UIStepperId Id;
|
|
|
|
/// <summary>
|
|
/// Drag direction for a UIStepper
|
|
/// </summary>
|
|
public enum Direction
|
|
{
|
|
/// <summary>
|
|
/// Value increases when dragging to the right and decreases when dragging to the left
|
|
/// </summary>
|
|
Horizontal,
|
|
|
|
/// <summary>
|
|
/// Value increases when dragging up and decreases when dragging down
|
|
/// </summary>
|
|
Vertical
|
|
}
|
|
|
|
/// <summary>
|
|
/// The initial and maximum time in seconds to wait before the value starts to auto repeat (increment/decrement)
|
|
/// </summary>
|
|
public float AutoRepeatWaitTime = WAIT_TIME;
|
|
|
|
/// <summary>
|
|
/// When the stepper is auto-repeating, the wait time between each increase/decrease will be reduced by multiplying the remaining wait time with this value until it reaches AutoRepeatMinWaitTime limit.
|
|
/// This reduction makes the stepper feel more responsive and less laggy.
|
|
/// </summary>
|
|
public float AutoRepeatWaitTimeReduction = WAIT_TIME_REDUCTION;
|
|
|
|
/// <summary> The minimum wait time between each increase/decrease when the stepper is auto-repeating </summary>
|
|
public float AutoRepeatMinWaitTime = WAIT_TIME_MIN;
|
|
|
|
[SerializeField] private UIButton MinusButton;
|
|
/// <summary> Reference to a UIButton that will be used to decrease the value of the stepper </summary>
|
|
public UIButton minusButton
|
|
{
|
|
get => MinusButton;
|
|
private set
|
|
{
|
|
if (MinusButton != null)
|
|
{
|
|
MinusButton.pressedState.stateEvent.Event.RemoveListener(OnMinusButtonClicked);
|
|
MinusButton.onPointerDownEvent.RemoveListener(OnMinusButtonDown);
|
|
MinusButton.onPointerUpEvent.RemoveListener(OnMinusButtonUp);
|
|
}
|
|
if (value != null)
|
|
{
|
|
value.pressedState.stateEvent.Event.AddListener(OnMinusButtonClicked);
|
|
value.onPointerDownEvent.AddListener(OnMinusButtonDown);
|
|
value.onPointerUpEvent.AddListener(OnMinusButtonUp);
|
|
}
|
|
MinusButton = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField] private UIButton PlusButton;
|
|
/// <summary> Reference to a UIButton that will be used to increase the value of the stepper </summary>
|
|
public UIButton plusButton
|
|
{
|
|
get => PlusButton;
|
|
private set
|
|
{
|
|
if (PlusButton != null)
|
|
{
|
|
PlusButton.pressedState.stateEvent.Event.RemoveListener(OnPlusButtonClicked);
|
|
PlusButton.onPointerDownEvent.RemoveListener(OnPlusButtonDown);
|
|
PlusButton.onPointerUpEvent.RemoveListener(OnPlusButtonUp);
|
|
}
|
|
if (value != null)
|
|
{
|
|
value.pressedState.stateEvent.Event.AddListener(OnPlusButtonClicked);
|
|
value.onPointerDownEvent.AddListener(OnPlusButtonDown);
|
|
value.onPointerUpEvent.AddListener(OnPlusButtonUp);
|
|
}
|
|
PlusButton = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField] private UIButton ResetButton;
|
|
/// <summary> Reference to a UIButton that will reset the stepper value to its default value </summary>
|
|
public UIButton resetButton
|
|
{
|
|
get => ResetButton;
|
|
private set
|
|
{
|
|
if (ResetButton != null)
|
|
{
|
|
ResetButton.pressedState.stateEvent.Event.RemoveListener(OnResetButtonClicked);
|
|
}
|
|
if (value != null)
|
|
{
|
|
value.pressedState.stateEvent.Event.AddListener(OnResetButtonClicked);
|
|
}
|
|
ResetButton = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField] private TMP_Text TargetLabel;
|
|
/// <summary> Reference to the value label that displays the current value </summary>
|
|
public TMP_Text targetLabel
|
|
{
|
|
get => TargetLabel;
|
|
private set
|
|
{
|
|
TargetLabel = value;
|
|
UpdateValueLabel();
|
|
}
|
|
}
|
|
|
|
[SerializeField] private float MinValue;
|
|
/// <summary> The minimum value that the stepper can have </summary>
|
|
public float minValue
|
|
{
|
|
get => MinValue;
|
|
private set
|
|
{
|
|
MinValue = value.Round(ValuePrecision);
|
|
MaxValue = MinValue > MaxValue ? MinValue : MaxValue;
|
|
|
|
if (Value < MinValue)
|
|
{
|
|
SetValue(MinValue);
|
|
}
|
|
|
|
UpdateTargetProgressorMinMax();
|
|
UpdateTargetProgressorValue();
|
|
|
|
UpdateTargetProgressorMinMax();
|
|
UpdateTargetSliderValue(Value);
|
|
}
|
|
}
|
|
|
|
[SerializeField] private float MaxValue = 1f;
|
|
/// <summary> The maximum value that the stepper can reach </summary>
|
|
public float maxValue
|
|
{
|
|
get => MaxValue;
|
|
private set
|
|
{
|
|
MaxValue = value.Round(ValuePrecision);
|
|
MinValue = MaxValue < MinValue ? MaxValue : MinValue;
|
|
|
|
if (Value > MaxValue)
|
|
{
|
|
SetValue(MaxValue);
|
|
}
|
|
|
|
UpdateTargetProgressorMinMax();
|
|
UpdateTargetProgressorValue();
|
|
|
|
UpdateTargetProgressorMinMax();
|
|
UpdateTargetSliderValue(Value);
|
|
}
|
|
}
|
|
|
|
[SerializeField] private float Value;
|
|
/// <summary> The current value of the stepper </summary>
|
|
public float value
|
|
{
|
|
get => Value;
|
|
set => SetValue(value);
|
|
}
|
|
|
|
[SerializeField] private float DefaultValue;
|
|
/// <summary> Reset value for the stepper </summary>
|
|
public float defaultValue
|
|
{
|
|
get => DefaultValue;
|
|
set => DefaultValue = Mathf.Clamp(value, minValue, maxValue);
|
|
}
|
|
|
|
[SerializeField] private float Step = 0.1f;
|
|
/// <summary> Value by which the stepper will increase or decrease when the value is changed </summary>
|
|
public float step
|
|
{
|
|
get => Step;
|
|
private set
|
|
{
|
|
Step = value;
|
|
SetValue(NearestStep(value));
|
|
UpdateTargetSliderValue(value);
|
|
stepValueChanged = true;
|
|
}
|
|
}
|
|
|
|
[SerializeField] private UISlider TargetSlider;
|
|
/// <summary>
|
|
/// Reference to a UISlider that will be updated when the stepper value changes.
|
|
/// The slider value also updates the stepper value, when it changes.
|
|
/// </summary>
|
|
public UISlider targetSlider
|
|
{
|
|
get => TargetSlider;
|
|
private set
|
|
{
|
|
if (TargetSlider != null)
|
|
{
|
|
TargetSlider.OnValueChanged.RemoveListener(OnTargetSliderValueChanged);
|
|
OnValueChanged.RemoveListener(UpdateTargetSliderValue);
|
|
}
|
|
|
|
if (value != null)
|
|
{
|
|
value.OnValueChanged.AddListener(OnTargetSliderValueChanged);
|
|
OnValueChanged.AddListener(UpdateTargetSliderValue);
|
|
}
|
|
|
|
TargetSlider = value;
|
|
UpdateTargetSliderMinMax();
|
|
UpdateTargetSliderValue(Value);
|
|
}
|
|
}
|
|
|
|
[SerializeField] private Progressor TargetProgressor;
|
|
/// <summary> Reference to a Progressor that will be updated when the stepper value changes </summary>
|
|
public Progressor targetProgressor
|
|
{
|
|
get => TargetProgressor;
|
|
private set
|
|
{
|
|
TargetProgressor = value;
|
|
if (value == null) return;
|
|
UpdateTargetProgressorMinMax();
|
|
UpdateTargetProgressorValue();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When true, the stepper will update the target progressor value with SetValueAt instead of PlayToValue.
|
|
/// Basically, if true, the progressor will not animate when the stepper value changes.
|
|
/// </summary>
|
|
public bool InstantProgressorUpdate = true;
|
|
|
|
/// <summary> Reset the stepper value to its default value OnEnable </summary>
|
|
public bool ResetValueOnEnable = true;
|
|
|
|
/// <summary> Number of decimal places to round the value to, if the step is not a whole number </summary>
|
|
public int ValuePrecision = 2;
|
|
|
|
/// <summary>
|
|
/// Enable or disable the drag functionality for this stepper.
|
|
/// </summary>
|
|
public bool DragEnabled;
|
|
|
|
/// <summary>
|
|
/// Reference to the RectTransform used as the drag handle.
|
|
/// </summary>
|
|
[SerializeField] private RectTransform DragHandle;
|
|
public RectTransform dragHandle
|
|
{
|
|
get => DragHandle;
|
|
set
|
|
{
|
|
DragHandle = value;
|
|
if (DragHandle)
|
|
{
|
|
m_DragHandleInitialPosition = DragHandle.anchoredPosition;
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField] private Direction DragDirection = Direction.Horizontal;
|
|
/// <summary> Drag direction for the Drag Handle to increase or decrease the value </summary>
|
|
public Direction dragDirection
|
|
{
|
|
get => DragDirection;
|
|
private set => DragDirection = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// During a drag operation, this is the maximum distance the drag handle can be moved from the initial position.
|
|
/// This also affects the speed of the value change (inversely proportional).
|
|
/// The longer the distance the finer the control the user has to change the value.
|
|
/// </summary>
|
|
public float MaxDragDistance = DRAG_DISTANCE;
|
|
|
|
/// <summary>
|
|
/// Fired when the value changed.
|
|
/// Returns the new value.
|
|
/// </summary>
|
|
public FloatEvent OnValueChanged = new FloatEvent();
|
|
|
|
/// <summary>
|
|
/// Fired when the value increases.
|
|
/// Returns the difference between the new and old value.
|
|
/// <para/> Example: if the previous value was 0.5 and the new value is 0.7, the returned value will be 0.2
|
|
/// </summary>
|
|
public FloatEvent OnValueIncremented = new FloatEvent();
|
|
|
|
/// <summary>
|
|
/// Fired when the value decreases.
|
|
/// Returns the difference between the new and old value.
|
|
/// <para/> Example: if the previous value was 10 and the new value is 5, the returned value will be -5
|
|
/// </summary>
|
|
public FloatEvent OnValueDecremented = new FloatEvent();
|
|
|
|
/// <summary> Fired when the value was reset </summary>
|
|
public ModyEvent OnValueReset = new ModyEvent();
|
|
|
|
/// <summary> Fired when the value has reached the minimum value </summary>
|
|
public ModyEvent OnValueReachedMin = new ModyEvent();
|
|
|
|
/// <summary> Fired when the value has reached the maximum value </summary>
|
|
public ModyEvent OnValueReachedMax = new ModyEvent();
|
|
|
|
/// <summary>
|
|
/// Coroutine called when the user is holding down the plus button.
|
|
/// It's used to auto-repeat the increment action.
|
|
/// </summary>
|
|
private Coroutine autoIncrementCoroutine { get; set; }
|
|
|
|
/// <summary>
|
|
/// Coroutine called when the user is holding down the minus button.
|
|
/// It's used to auto-repeat the decrement action.
|
|
/// </summary>
|
|
private Coroutine autoDecrementCoroutine { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag to indicate if the stepper is currently auto-repeating an increment action.
|
|
/// </summary>
|
|
private bool autoIncrementing { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag to indicate if the stepper is currently auto-repeating the decrement action.
|
|
/// </summary>
|
|
private bool autoDecrementing { get; set; }
|
|
|
|
/// <summary>
|
|
/// During a drag operation, this is the distance travelled from the start of the drag.
|
|
/// It is used to determine the direction of the drag and the speed of the value change (the further the drag, the faster the value change).
|
|
/// </summary>
|
|
private float draggedDistance =>
|
|
DragDirection switch
|
|
{
|
|
Direction.Horizontal => dragHandle.anchoredPosition.x - m_DragHandleInitialPosition.x,
|
|
Direction.Vertical => dragHandle.anchoredPosition.y - m_DragHandleInitialPosition.y,
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
|
|
/// <summary>
|
|
/// During a drag operation, this is the time in seconds that the stepper will wait before changing the value (incrementing or decrementing).
|
|
/// The value will change depending on how far, from the initial position, the drag area has been dragged.
|
|
/// The further the drag area is dragged, the faster the value will change (thus this value will be lower).
|
|
/// </summary>
|
|
private float dragWaitTime
|
|
{
|
|
get
|
|
{
|
|
float distance = Mathf.Abs(draggedDistance);
|
|
distance = Mathf.Clamp(distance, 0, MaxDragDistance);
|
|
float dragRatio = 1f - distance / MaxDragDistance;
|
|
float waitTime = Mathf.Clamp(dragRatio * AutoRepeatWaitTime, AutoRepeatMinWaitTime, AutoRepeatWaitTime);
|
|
return waitTime;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initial position of the drag handle when the drag operation starts.
|
|
/// </summary>
|
|
private Vector2 m_DragHandleInitialPosition;
|
|
|
|
/// <summary>
|
|
/// Coroutine called when the user is dragging the drag handle in the increment direction (right or up).
|
|
/// It's used to drag-repeat the decrement action.
|
|
/// </summary>
|
|
private Coroutine dragIncrementCoroutine { get; set; }
|
|
|
|
/// <summary>
|
|
/// Coroutine called when the user is dragging the drag handle in the decrement direction (left or down).
|
|
/// It's used to drag-repeat the decrement action.
|
|
/// </summary>
|
|
private Coroutine dragDecrementCoroutine { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag to indicate if the stepper is currently drag-repeating an increment action.
|
|
/// </summary>
|
|
private bool dragIncrementing { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag to indicate if the stepper is currently drag-repeating the decrement action.
|
|
/// </summary>
|
|
private bool dragDecrementing { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag to indicate if the stepper DragHandle can be dragged.
|
|
/// </summary>
|
|
private bool canDrag { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag to indicate if the stepper DragHandle is currently being dragged.
|
|
/// </summary>
|
|
private bool isDragging { get; set; }
|
|
|
|
/// <summary>
|
|
/// Flag to indicate that the stepper's step vale has changed.
|
|
/// </summary>
|
|
private bool stepValueChanged { get; set; }
|
|
|
|
protected UIStepper()
|
|
{
|
|
Id = new UIStepperId();
|
|
}
|
|
|
|
protected virtual void OnValidate()
|
|
{
|
|
SetValue(value);
|
|
}
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
database.Add(this);
|
|
}
|
|
|
|
protected virtual void Start()
|
|
{
|
|
UpdateValueLabel();
|
|
}
|
|
|
|
protected virtual void OnEnable()
|
|
{
|
|
database.Remove(null);
|
|
if (!Application.isPlaying) return;
|
|
|
|
if (minusButton != null)
|
|
{
|
|
minusButton.pressedState.stateEvent.Event.AddListener(OnMinusButtonClicked);
|
|
minusButton.onPointerUpEvent.AddListener(OnMinusButtonUp);
|
|
minusButton.onPointerDownEvent.AddListener(OnMinusButtonDown);
|
|
}
|
|
|
|
if (plusButton != null)
|
|
{
|
|
plusButton.pressedState.stateEvent.Event.AddListener(OnPlusButtonClicked);
|
|
plusButton.onPointerUpEvent.AddListener(OnPlusButtonUp);
|
|
plusButton.onPointerDownEvent.AddListener(OnPlusButtonDown);
|
|
}
|
|
|
|
if (resetButton != null)
|
|
{
|
|
resetButton.pressedState.stateEvent.Event.AddListener(OnResetButtonClicked);
|
|
}
|
|
|
|
if (TargetSlider)
|
|
{
|
|
TargetSlider.OnValueChanged.AddListener(OnTargetSliderValueChanged);
|
|
OnValueChanged.AddListener(UpdateTargetSliderValue);
|
|
}
|
|
|
|
UpdateTargetProgressorMinMax();
|
|
UpdateTargetSliderMinMax();
|
|
if (ResetValueOnEnable)
|
|
{
|
|
ResetValue();
|
|
}
|
|
else
|
|
{
|
|
UpdateTargetProgressorValue();
|
|
UpdateTargetSliderValue(value);
|
|
}
|
|
|
|
if (dragHandle)
|
|
{
|
|
m_DragHandleInitialPosition = dragHandle.anchoredPosition;
|
|
}
|
|
|
|
canDrag = true;
|
|
isDragging = false;
|
|
}
|
|
|
|
protected virtual void OnDisable()
|
|
{
|
|
database.Remove(null);
|
|
if (!Application.isPlaying) return;
|
|
|
|
if (plusButton != null)
|
|
{
|
|
plusButton.pressedState.stateEvent.Event.RemoveListener(OnPlusButtonClicked);
|
|
plusButton.onPointerUpEvent.RemoveListener(OnPlusButtonUp);
|
|
plusButton.onPointerDownEvent.RemoveListener(OnPlusButtonDown);
|
|
}
|
|
|
|
if (minusButton != null)
|
|
{
|
|
minusButton.pressedState.stateEvent.Event.RemoveListener(OnMinusButtonClicked);
|
|
minusButton.onPointerUpEvent.RemoveListener(OnMinusButtonUp);
|
|
minusButton.onPointerDownEvent.RemoveListener(OnMinusButtonDown);
|
|
}
|
|
|
|
if (resetButton != null)
|
|
{
|
|
resetButton.pressedState.stateEvent.Event.RemoveListener(OnResetButtonClicked);
|
|
}
|
|
|
|
autoIncrementing = false;
|
|
if (autoIncrementCoroutine != null)
|
|
{
|
|
StopCoroutine(autoIncrementCoroutine);
|
|
}
|
|
|
|
autoDecrementing = false;
|
|
if (autoDecrementCoroutine != null)
|
|
{
|
|
StopCoroutine(autoDecrementCoroutine);
|
|
}
|
|
|
|
dragIncrementing = false;
|
|
if (dragIncrementCoroutine != null)
|
|
{
|
|
StopCoroutine(dragIncrementCoroutine);
|
|
}
|
|
|
|
dragDecrementing = false;
|
|
if (dragDecrementCoroutine != null)
|
|
{
|
|
StopCoroutine(dragDecrementCoroutine);
|
|
}
|
|
|
|
UpdateTargetProgressorValue();
|
|
UpdateTargetSliderValue(value);
|
|
|
|
if (TargetSlider)
|
|
{
|
|
TargetSlider.OnValueChanged.RemoveListener(OnTargetSliderValueChanged);
|
|
OnValueChanged.RemoveListener(UpdateTargetSliderValue);
|
|
}
|
|
}
|
|
|
|
protected virtual void OnDestroy()
|
|
{
|
|
database.Remove(null);
|
|
database.Remove(this);
|
|
}
|
|
|
|
protected virtual void OnMinusButtonClicked()
|
|
{
|
|
DecrementValue();
|
|
canDrag = true;
|
|
}
|
|
|
|
protected virtual void OnPlusButtonClicked()
|
|
{
|
|
IncrementValue();
|
|
canDrag = true;
|
|
}
|
|
|
|
protected virtual void OnPlusButtonDown()
|
|
{
|
|
StopAutoIncrement();
|
|
if (isDragging) return;
|
|
canDrag = false;
|
|
StartAutoIncrement();
|
|
}
|
|
|
|
protected virtual void OnPlusButtonUp()
|
|
{
|
|
StopAutoIncrement();
|
|
canDrag = true;
|
|
}
|
|
|
|
protected virtual void OnMinusButtonDown()
|
|
{
|
|
StopAutoDecrement();
|
|
if (isDragging) return;
|
|
canDrag = false;
|
|
StartAutoDecrement();
|
|
}
|
|
|
|
protected virtual void OnMinusButtonUp()
|
|
{
|
|
StopAutoDecrement();
|
|
canDrag = true;
|
|
}
|
|
|
|
protected virtual void OnResetButtonClicked()
|
|
{
|
|
ResetValue();
|
|
}
|
|
|
|
protected virtual void OnTargetSliderValueChanged(float sliderValue)
|
|
{
|
|
float nearestStepValue = NearestStep(sliderValue);
|
|
TargetSlider.SetValueWithoutNotify(nearestStepValue);
|
|
SetValue(nearestStepValue);
|
|
}
|
|
|
|
/// <summary> Reset the current int or float value, depending on the stepper's value type, to the default value. </summary>
|
|
public void ResetValue()
|
|
{
|
|
SetValue(defaultValue);
|
|
OnValueReset.Execute();
|
|
}
|
|
|
|
/// <summary> Set the current float value to the given value </summary>
|
|
/// <param name="newValue"> New value </param>
|
|
public void SetValue(float newValue)
|
|
{
|
|
bool valueChanged = Math.Abs(Value - newValue) > TOLERANCE; //check if the value has changed
|
|
Value = Mathf.Clamp(newValue, minValue, maxValue).Round(ValuePrecision); //set the new value
|
|
if (stepValueChanged)
|
|
{
|
|
Value = NearestStep(Value);
|
|
stepValueChanged = false;
|
|
}
|
|
UpdateValueLabel(); //update the value label
|
|
if (valueChanged)
|
|
{
|
|
OnValueChanged.Invoke(Value); //invoke the OnValueChanged event only if the value has changed
|
|
|
|
if (InstantProgressorUpdate)
|
|
{
|
|
UpdateTargetProgressorValue();
|
|
}
|
|
else
|
|
{
|
|
PlayTargetProgressorValue();
|
|
}
|
|
|
|
UpdateTargetSliderValue(Value);
|
|
stream.SendSignal(new UIStepperSignalData(Id.Category, Id.Name, StepperState.ValueChanged, this));
|
|
}
|
|
|
|
if (Value <= minValue)
|
|
{
|
|
//value is equal to the min value
|
|
//invoke the OnValueReachedMin event if the value is equal to the min value
|
|
//disable the minus button if the value is equal to the min value
|
|
|
|
OnValueReachedMin.Execute();
|
|
stream.SendSignal(new UIStepperSignalData(Id.Category, Id.Name, StepperState.ReachedMinValue, this));
|
|
if (minusButton != null && minusButton.interactable)
|
|
{
|
|
minusButton.interactable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//value is not equal to the min value
|
|
//enable the minus button if the value is not equal to the min value
|
|
|
|
if (minusButton != null && !minusButton.interactable)
|
|
{
|
|
minusButton.interactable = true;
|
|
}
|
|
}
|
|
|
|
if (Value >= maxValue)
|
|
{
|
|
//value is equal to the max value
|
|
//invoke the OnValueReachedMax event if the value is equal to the max value
|
|
//disable the plus button if the value is equal to the max value
|
|
|
|
OnValueReachedMax.Execute();
|
|
stream.SendSignal(new UIStepperSignalData(Id.Category, Id.Name, StepperState.ReachedMaxValue, this));
|
|
if (plusButton != null && plusButton.interactable)
|
|
{
|
|
plusButton.interactable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//value is not equal to the max value
|
|
//enable the plus button if the value is not equal to the max value
|
|
|
|
if (plusButton != null && !plusButton.interactable)
|
|
{
|
|
plusButton.interactable = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary> Increment the value by the step value </summary>
|
|
public void IncrementValue()
|
|
{
|
|
IncrementValue(step);
|
|
}
|
|
|
|
/// <summary> Increment the value </summary>
|
|
/// <param name="increment"> Increment value </param>
|
|
public void IncrementValue(float increment)
|
|
{
|
|
bool currentValueIsMax = Math.Abs(value - maxValue) < TOLERANCE; //we need to know this before we increment the value
|
|
if (!currentValueIsMax)
|
|
{
|
|
OnValueIncremented.Invoke(increment); //invoke the OnValueIncremented event only if the value was not already at max
|
|
//stream.SendSignal(new UIStepperSignalData(Id.Category, Id.Name, StepperState.ValueIncremented, this));
|
|
}
|
|
SetValue(value + increment); //increment value
|
|
}
|
|
|
|
/// <summary> Decrement the value by the step value </summary>
|
|
public void DecrementValue()
|
|
{
|
|
DecrementValue(step);
|
|
}
|
|
|
|
/// <summary> Decrement the value </summary>
|
|
/// <param name="decrement"> Decrement value </param>
|
|
public void DecrementValue(float decrement)
|
|
{
|
|
bool currentValueIsMin = Math.Abs(value - minValue) < TOLERANCE; //we need to know this before we decrement the value
|
|
if (!currentValueIsMin)
|
|
{
|
|
OnValueDecremented.Invoke(-decrement); //invoke the OnValueDecremented event only if the value was not already at min
|
|
//stream.SendSignal(new UIStepperSignalData(Id.Category, Id.Name, StepperState.ValueDecremented, this));
|
|
}
|
|
SetValue(value - decrement); //decrement value
|
|
}
|
|
|
|
/// <summary> Update the value label text with the current value </summary>
|
|
public void UpdateValueLabel()
|
|
{
|
|
if (targetLabel == null) return;
|
|
targetLabel.text = value.ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private void UpdateTargetProgressorMinMax()
|
|
{
|
|
if (!targetProgressor) return;
|
|
targetProgressor.fromValue = minValue;
|
|
targetProgressor.toValue = maxValue;
|
|
targetProgressor.SetValueAt(value);
|
|
}
|
|
|
|
private void UpdateTargetProgressorValue()
|
|
{
|
|
if (!targetProgressor) return;
|
|
targetProgressor.SetValueAt(value);
|
|
}
|
|
|
|
private void PlayTargetProgressorValue()
|
|
{
|
|
if (!targetProgressor) return;
|
|
targetProgressor.PlayToValue(value);
|
|
}
|
|
|
|
private void UpdateTargetSliderMinMax()
|
|
{
|
|
if (targetSlider == null) return;
|
|
targetSlider.minValue = minValue;
|
|
targetSlider.maxValue = maxValue;
|
|
}
|
|
|
|
private void UpdateTargetSliderValue(float newValue)
|
|
{
|
|
if (targetSlider == null) return;
|
|
targetSlider.SetValueWithoutNotify(newValue);
|
|
}
|
|
|
|
#region Chainable Methods
|
|
|
|
/// <summary> Reference a new UIButton to the minus button </summary>
|
|
/// <param name="button"> The UIButton to reference </param>
|
|
public T SetMinusButton<T>(UIButton button) where T : UIStepper
|
|
{
|
|
minusButton = button;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Reference a new UIButton to the plus button </summary>
|
|
/// <param name="button"> The UIButton to reference </param>
|
|
public T SetPlusButton<T>(UIButton button) where T : UIStepper
|
|
{
|
|
plusButton = button;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Reference a new TMP_Text to the value label </summary>
|
|
/// <param name="label"> The TMP_Text to reference </param>
|
|
public T SetValueLabel<T>(TMP_Text label) where T : UIStepper
|
|
{
|
|
targetLabel = label;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Reference a new UIButton to the reset button </summary>
|
|
/// <param name="button"> The UIButton to reference </param>
|
|
public T SetResetButton<T>(UIButton button) where T : UIStepper
|
|
{
|
|
resetButton = button;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Set a new min value </summary>
|
|
/// <param name="newMinValue"> The new min value </param>
|
|
public T SetMinValue<T>(float newMinValue) where T : UIStepper
|
|
{
|
|
minValue = newMinValue;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Set a new max value </summary>
|
|
/// <param name="newMaxValue"> The new max value </param>
|
|
public T SetMaxValue<T>(float newMaxValue) where T : UIStepper
|
|
{
|
|
maxValue = newMaxValue;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Set a new default value (reset value) </summary>
|
|
/// <param name="newResetValue"> The new default value (reset value) </param>
|
|
public T SetDefaultValue<T>(float newResetValue) where T : UIStepper
|
|
{
|
|
defaultValue = newResetValue;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Set a new target progressor to update when the value changes </summary>
|
|
/// <param name="progressor"> The new target progressor to update when the value changes </param>
|
|
public T SetTargetProgressor<T>(Progressor progressor) where T : UIStepper
|
|
{
|
|
targetProgressor = progressor;
|
|
return (T)this;
|
|
}
|
|
|
|
/// <summary> Set a new direction for the stepper </summary>
|
|
/// <param name="direction"> The new direction for the stepper </param>
|
|
public T SetStepperDirection<T>(Direction direction) where T : UIStepper
|
|
{
|
|
DragDirection = direction;
|
|
return (T)this;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Flag used to determine if a drag can occur
|
|
/// </summary>
|
|
private bool cannotDrag => !DragEnabled || !canDrag;
|
|
|
|
public void OnBeginDrag(PointerEventData eventData)
|
|
{
|
|
if (cannotDrag) return;
|
|
isDragging = true;
|
|
// m_DragHandleInitialPosition = DragHandle.anchoredPosition;
|
|
}
|
|
|
|
public void OnEndDrag(PointerEventData eventData)
|
|
{
|
|
if (cannotDrag) return;
|
|
isDragging = false;
|
|
dragHandle.anchoredPosition = m_DragHandleInitialPosition;
|
|
}
|
|
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
if (cannotDrag) return;
|
|
if (!isDragging) return;
|
|
|
|
float iX = m_DragHandleInitialPosition.x;
|
|
float iY = m_DragHandleInitialPosition.y;
|
|
switch (DragDirection)
|
|
{
|
|
case Direction.Horizontal:
|
|
float x = dragHandle.anchoredPosition.x + eventData.delta.x;
|
|
x = Mathf.Clamp(x, iX - MaxDragDistance, iX + MaxDragDistance);
|
|
dragHandle.anchoredPosition = new Vector2(x, m_DragHandleInitialPosition.y);
|
|
break;
|
|
case Direction.Vertical:
|
|
float y = dragHandle.anchoredPosition.y + eventData.delta.y;
|
|
y = Mathf.Clamp(y, iY - MaxDragDistance, iY + MaxDragDistance);
|
|
dragHandle.anchoredPosition = new Vector2(m_DragHandleInitialPosition.x, y);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (!isDragging)
|
|
{
|
|
if (dragIncrementing) StopDragIncrement();
|
|
if (dragDecrementing) StopDragDecrement();
|
|
return;
|
|
}
|
|
|
|
if (autoIncrementing) StopAutoIncrement();
|
|
if (autoDecrementing) StopAutoDecrement();
|
|
|
|
if (draggedDistance > 0)
|
|
{
|
|
if (dragDecrementing) StopDragDecrement();
|
|
if (dragIncrementing) return;
|
|
StartDragIncrement();
|
|
}
|
|
else
|
|
{
|
|
if (dragIncrementing) StopDragIncrement();
|
|
if (dragDecrementing) return;
|
|
StartDragDecrement();
|
|
}
|
|
}
|
|
|
|
#region Increment
|
|
|
|
private bool CanIncrementValue() =>
|
|
value < maxValue;
|
|
|
|
#region Auto Increment
|
|
|
|
private void StartAutoIncrement()
|
|
{
|
|
StopAutoIncrement();
|
|
autoIncrementCoroutine = StartCoroutine(AutoIncrementValue());
|
|
}
|
|
|
|
private void StopAutoIncrement()
|
|
{
|
|
autoIncrementing = false;
|
|
if (autoIncrementCoroutine == null) return;
|
|
StopCoroutine(autoIncrementCoroutine);
|
|
}
|
|
|
|
private IEnumerator AutoIncrementValue()
|
|
{
|
|
autoIncrementing = true;
|
|
yield return new WaitForSecondsRealtime(WAIT_BEFORE_STARTING);
|
|
float waitTime = AutoRepeatWaitTime;
|
|
while (CanIncrementValue())
|
|
{
|
|
IncrementValue();
|
|
yield return new WaitForSecondsRealtime(waitTime);
|
|
waitTime = Mathf.Clamp(waitTime * AutoRepeatWaitTimeReduction, AutoRepeatMinWaitTime, AutoRepeatWaitTime);
|
|
}
|
|
autoIncrementing = false;
|
|
autoIncrementCoroutine = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Drag Increment
|
|
|
|
private void StartDragIncrement()
|
|
{
|
|
StopDragIncrement();
|
|
dragIncrementCoroutine = StartCoroutine(DragIncrementValue());
|
|
}
|
|
|
|
private void StopDragIncrement()
|
|
{
|
|
dragIncrementing = false;
|
|
if (dragIncrementCoroutine == null) return;
|
|
StopCoroutine(dragIncrementCoroutine);
|
|
}
|
|
|
|
private IEnumerator DragIncrementValue()
|
|
{
|
|
dragIncrementing = true;
|
|
while (CanIncrementValue())
|
|
{
|
|
IncrementValue();
|
|
yield return new WaitForSecondsRealtime(dragWaitTime);
|
|
}
|
|
dragIncrementing = false;
|
|
dragIncrementCoroutine = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Decrement
|
|
|
|
private bool CanDecrementValue() =>
|
|
value > minValue;
|
|
|
|
#region Auto Decrement
|
|
|
|
private void StartAutoDecrement()
|
|
{
|
|
StopAutoDecrement();
|
|
autoDecrementCoroutine = StartCoroutine(AutoDecrementValue());
|
|
}
|
|
|
|
private void StopAutoDecrement()
|
|
{
|
|
autoDecrementing = false;
|
|
if (autoDecrementCoroutine == null) return;
|
|
StopCoroutine(autoDecrementCoroutine);
|
|
}
|
|
|
|
private IEnumerator AutoDecrementValue()
|
|
{
|
|
autoDecrementing = true;
|
|
yield return new WaitForSecondsRealtime(WAIT_BEFORE_STARTING);
|
|
float waitTime = AutoRepeatWaitTime;
|
|
while (CanDecrementValue())
|
|
{
|
|
DecrementValue();
|
|
yield return new WaitForSecondsRealtime(waitTime);
|
|
waitTime = Mathf.Clamp(waitTime * AutoRepeatWaitTimeReduction, AutoRepeatMinWaitTime, AutoRepeatWaitTime);
|
|
}
|
|
autoDecrementing = false;
|
|
autoDecrementCoroutine = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Drag Decrement
|
|
|
|
private void StartDragDecrement()
|
|
{
|
|
StopDragDecrement();
|
|
dragDecrementCoroutine = StartCoroutine(DragDecrementValue());
|
|
}
|
|
|
|
private void StopDragDecrement()
|
|
{
|
|
dragDecrementing = false;
|
|
if (dragDecrementCoroutine == null) return;
|
|
StopCoroutine(dragDecrementCoroutine);
|
|
}
|
|
|
|
private IEnumerator DragDecrementValue()
|
|
{
|
|
dragDecrementing = true;
|
|
while (CanDecrementValue())
|
|
{
|
|
DecrementValue();
|
|
yield return new WaitForSecondsRealtime(dragWaitTime);
|
|
}
|
|
dragDecrementing = false;
|
|
dragDecrementCoroutine = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
/// <summary> Get the nearest value to the given value that is a multiple of the step size. </summary>
|
|
/// <param name="uncorrectedValue"> Value that has not been corrected. </param>
|
|
private float NearestStep(float uncorrectedValue)
|
|
{
|
|
if (Step == 0) return uncorrectedValue;
|
|
return (int)Math.Round(uncorrectedValue / (double)step, MidpointRounding.AwayFromZero) * step;
|
|
}
|
|
}
|
|
}
|