| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 |
- /// Credit Ben MacKinnon @Dover8
- /// Sourced from - https://github.com/Dover8/Unity-UI-Extensions/tree/range-slider
- /// Usage: Extension of the standard slider. Two handles determine a low and high value between a Min and Max.
- /// Raises a UnityEvent passing the low and high values
- using System;
- using UnityEngine.Events;
- using UnityEngine.EventSystems;
- namespace UnityEngine.UI.Extensions
- {
- [AddComponentMenu("UI/Extensions/Range Slider", 34)]
- [ExecuteInEditMode]
- [RequireComponent(typeof(RectTransform))]
- public class RangeSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
- {
- [Serializable]
- public class RangeSliderEvent : UnityEvent<float, float> { }
- [SerializeField]
- private RectTransform m_FillRect;
- public RectTransform FillRect { get { return m_FillRect; } set { if (SetClass(ref m_FillRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
- [SerializeField]
- private RectTransform m_LowHandleRect;
- public RectTransform LowHandleRect { get { return m_LowHandleRect; } set { if (SetClass(ref m_LowHandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
- [SerializeField]
- private RectTransform m_HighHandleRect;
- public RectTransform HighHandleRect { get { return m_HighHandleRect; } set { if (SetClass(ref m_HighHandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
- [Space]
- [SerializeField]
- private float m_MinValue = 0;
- public float MinValue { get { return m_MinValue; } set { if (SetStruct(ref m_MinValue, value)) { SetLow(m_LowValue); SetHigh(m_HighValue); UpdateVisuals(); } } }
- [SerializeField]
- private float m_MaxValue = 1;
- public float MaxValue { get { return m_MaxValue; } set { if (SetStruct(ref m_MaxValue, value)) { SetLow(m_LowValue); SetHigh(m_HighValue); UpdateVisuals(); } } }
- [SerializeField]
- private bool m_WholeNumbers = false;
- public bool WholeNumbers { get { return m_WholeNumbers; } set { if (SetStruct(ref m_WholeNumbers, value)) { SetLow(m_LowValue); SetHigh(m_HighValue); UpdateVisuals(); } } }
- [SerializeField]
- private float m_LowValue;
- public virtual float LowValue
- {
- get
- {
- if (WholeNumbers)
- {
- return Mathf.Round(m_LowValue);
- }
- return m_LowValue;
- }
- set
- {
- SetLow(value);
- }
- }
- public float NormalizedLowValue
- {
- get
- {
- if (Mathf.Approximately(MinValue, MaxValue))
- {
- return 0;
- }
- return Mathf.InverseLerp(MinValue, MaxValue, LowValue);
- }
- set
- {
- this.LowValue = Mathf.Lerp(MinValue, MaxValue, value);
- }
- }
- [SerializeField]
- private float m_HighValue;
- public virtual float HighValue
- {
- get
- {
- if (WholeNumbers)
- {
- return Mathf.Round(m_HighValue);
- }
- return m_HighValue;
- }
- set
- {
- SetHigh(value);
- }
- }
- public float NormalizedHighValue
- {
- get
- {
- if (Mathf.Approximately(MinValue, MaxValue))
- {
- return 0;
- }
- return Mathf.InverseLerp(MinValue, MaxValue, HighValue);
- }
- set
- {
- this.HighValue = Mathf.Lerp(MinValue, MaxValue, value);
- }
- }
- /// <summary>
- /// Set the value of the slider without invoking onValueChanged callback.
- /// </summary>
- /// <param name="input">The new value for the slider.</param>
- public virtual void SetValueWithoutNotify(float low, float high)
- {
- SetLow(low, false);
- SetHigh(high, false);
- }
- [Space]
- [SerializeField]
- private RangeSliderEvent m_OnValueChanged = new RangeSliderEvent();
- public RangeSliderEvent OnValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
- // Private fields
- /// <summary>
- /// An Enum that says in what state we and interacting with the slider
- /// </summary>
- private enum InteractionState
- {
- Low,
- High,
- Bar,
- None
- }
- private InteractionState interactionState = InteractionState.None;
- private Image m_FillImage;
- private Transform m_FillTransform;
- private RectTransform m_FillContainerRect;
- private Transform m_HighHandleTransform;
- private RectTransform m_HighHandleContainerRect;
- private Transform m_LowHandleTransform;
- private RectTransform m_LowHandleContainerRect;
- // The offset from handle position to mouse down position
- private Vector2 m_LowOffset = Vector2.zero;
- // The offset from handle position to mouse down position
- private Vector2 m_HighOffset = Vector2.zero;
- private DrivenRectTransformTracker m_Tracker;
- // This "delayed" mechanism is required for case 1037681.
- private bool m_DelayedUpdateVisuals = false;
- // Size of each step.
- float StepSize { get { return WholeNumbers ? 1 : (MaxValue - MinValue) * 0.1f; } }
- protected RangeSlider()
- { }
- #if UNITY_EDITOR
- protected override void OnValidate()
- {
- base.OnValidate();
- if (WholeNumbers)
- {
- m_MinValue = Mathf.Round(m_MinValue);
- m_MaxValue = Mathf.Round(m_MaxValue);
- }
- if (IsActive())
- {
- UpdateCachedReferences();
- SetLow(m_LowValue, false);
- SetHigh(m_HighValue, false);
- //Update rects since other things might affect them even if value didn't change
- m_DelayedUpdateVisuals = true;
- }
- if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
- {
- CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
- }
- }
- #endif
- public virtual void Rebuild(CanvasUpdate executing)
- {
- #if UNITY_EDITOR
- if (executing == CanvasUpdate.Prelayout)
- {
- OnValueChanged.Invoke(LowValue, HighValue);
- }
- #endif
- }
- /// <summary>
- /// See ICanvasElement.LayoutComplete
- /// </summary>
- public virtual void LayoutComplete()
- { }
- /// <summary>
- /// See ICanvasElement.GraphicUpdateComplete
- /// </summary>
- public virtual void GraphicUpdateComplete()
- { }
- public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
- {
- if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
- return false;
- currentValue = newValue;
- return true;
- }
- public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
- {
- if (currentValue.Equals(newValue))
- return false;
- currentValue = newValue;
- return true;
- }
- protected override void OnEnable()
- {
- base.OnEnable();
- UpdateCachedReferences();
- SetLow(LowValue, false);
- SetHigh(HighValue, false);
- // Update rects since they need to be initialized correctly.
- UpdateVisuals();
- }
- protected override void OnDisable()
- {
- m_Tracker.Clear();
- base.OnDisable();
- }
- /// <summary>
- /// Update the rect based on the delayed update visuals.
- /// Got around issue of calling sendMessage from onValidate.
- /// </summary>
- protected virtual void Update()
- {
- if (m_DelayedUpdateVisuals)
- {
- m_DelayedUpdateVisuals = false;
- UpdateVisuals();
- }
- }
- protected override void OnDidApplyAnimationProperties()
- {
- base.OnDidApplyAnimationProperties();
- }
- void UpdateCachedReferences()
- {
- if (m_FillRect && m_FillRect != (RectTransform)transform)
- {
- m_FillTransform = m_FillRect.transform;
- m_FillImage = m_FillRect.GetComponent<Image>();
- if (m_FillTransform.parent != null)
- m_FillContainerRect = m_FillTransform.parent.GetComponent<RectTransform>();
- }
- else
- {
- m_FillRect = null;
- m_FillContainerRect = null;
- m_FillImage = null;
- }
- if (m_HighHandleRect && m_HighHandleRect != (RectTransform)transform)
- {
- m_HighHandleTransform = m_HighHandleRect.transform;
- if (m_HighHandleTransform.parent != null)
- m_HighHandleContainerRect = m_HighHandleTransform.parent.GetComponent<RectTransform>();
- }
- else
- {
- m_HighHandleRect = null;
- m_HighHandleContainerRect = null;
- }
- if (m_LowHandleRect && m_LowHandleRect != (RectTransform)transform)
- {
- m_LowHandleTransform = m_LowHandleRect.transform;
- if (m_LowHandleTransform.parent != null)
- {
- m_LowHandleContainerRect = m_LowHandleTransform.parent.GetComponent<RectTransform>();
- }
- }
- else
- {
- m_LowHandleRect = null;
- m_LowHandleContainerRect = null;
- }
- }
-
- void SetLow(float input)
- {
- SetLow(input, true);
- }
- protected virtual void SetLow(float input, bool sendCallback)
- {
- // Clamp the input
- float newValue = Mathf.Clamp(input, MinValue, HighValue); //clamp between min and High
- if (WholeNumbers)
- {
- newValue = Mathf.Round(newValue);
- }
- // If the stepped value doesn't match the last one, it's time to update
- if (m_LowValue == newValue)
- return;
- m_LowValue = newValue;
- UpdateVisuals();
- if (sendCallback)
- {
- UISystemProfilerApi.AddMarker("RangeSlider.lowValue", this);
- m_OnValueChanged.Invoke(newValue, HighValue);
- }
- }
- void SetHigh(float input)
- {
- SetHigh(input, true);
- }
- protected virtual void SetHigh(float input, bool sendCallback)
- {
- // Clamp the input
- float newValue = Mathf.Clamp(input, LowValue, MaxValue); //clamp between min and High
- if (WholeNumbers)
- {
- newValue = Mathf.Round(newValue);
- }
- // If the stepped value doesn't match the last one, it's time to update
- if (m_HighValue == newValue)
- return;
- m_HighValue = newValue;
- UpdateVisuals();
- if (sendCallback)
- {
- UISystemProfilerApi.AddMarker("RangeSlider.highValue", this);
- m_OnValueChanged.Invoke(LowValue, newValue);
- }
- }
- protected override void OnRectTransformDimensionsChange()
- {
- base.OnRectTransformDimensionsChange();
- //This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called.
- if (!IsActive())
- return;
- UpdateVisuals();
- }
- // Force-update the slider. Useful if you've changed the properties and want it to update visually.
- private void UpdateVisuals()
- {
- #if UNITY_EDITOR
- if (!Application.isPlaying)
- UpdateCachedReferences();
- #endif
- m_Tracker.Clear();
- if (m_FillContainerRect != null)
- {
- m_Tracker.Add(this, m_FillRect, DrivenTransformProperties.Anchors);
- Vector2 anchorMin = Vector2.zero;
- Vector2 anchorMax = Vector2.one;
- //this is where some new magic must happen. Slider just uses a filled image
- //and changes the % of fill. We must move the image anchors to be between the two handles.
- anchorMin[0] = NormalizedLowValue;
- anchorMax[0] = NormalizedHighValue;
- m_FillRect.anchorMin = anchorMin;
- m_FillRect.anchorMax = anchorMax;
- }
- if (m_LowHandleContainerRect != null)
- {
- m_Tracker.Add(this, m_LowHandleRect, DrivenTransformProperties.Anchors);
- Vector2 anchorMin = Vector2.zero;
- Vector2 anchorMax = Vector2.one;
- anchorMin[0] = anchorMax[0] = NormalizedLowValue;
- m_LowHandleRect.anchorMin = anchorMin;
- m_LowHandleRect.anchorMax = anchorMax;
- }
- if (m_HighHandleContainerRect != null)
- {
- m_Tracker.Add(this, m_HighHandleRect, DrivenTransformProperties.Anchors);
- Vector2 anchorMin = Vector2.zero;
- Vector2 anchorMax = Vector2.one;
- anchorMin[0] = anchorMax[0] = NormalizedHighValue;
- m_HighHandleRect.anchorMin = anchorMin;
- m_HighHandleRect.anchorMax = anchorMax;
- }
- }
- // Update the slider's position based on the mouse.
- void UpdateDrag(PointerEventData eventData, Camera cam)
- {
- //this needs to differ from slider in that we have two handles, and need to move the right one.
- //and if it was neither handle, we will have a separate case where both handles move uniformly
- //moving the entire range
- //this is where we use our interationState
- switch (interactionState)
- {
- case InteractionState.Low:
- NormalizedLowValue = CalculateDrag(eventData, cam, m_LowHandleContainerRect, m_LowOffset);
- break;
- case InteractionState.High:
- NormalizedHighValue = CalculateDrag(eventData, cam, m_HighHandleContainerRect, m_HighOffset);
- break;
- case InteractionState.Bar:
- //special case
- CalculateBarDrag(eventData, cam);
- break;
- case InteractionState.None:
- break;
- }
- }
- private float CalculateDrag(PointerEventData eventData, Camera cam, RectTransform containerRect, Vector2 offset)
- {
- RectTransform clickRect = containerRect ?? m_FillContainerRect;
- if (clickRect != null && clickRect.rect.size[0] > 0)
- {
- Vector2 localCursor;
- if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor))
- {
- return 0f;
- }
- localCursor -= clickRect.rect.position;
- float val = Mathf.Clamp01((localCursor - offset)[0] / clickRect.rect.size[0]);
- return val;
- }
- return 0;
- }
- private void CalculateBarDrag(PointerEventData eventData, Camera cam)
- {
- RectTransform clickRect = m_FillContainerRect;
- if (clickRect != null && clickRect.rect.size[0] > 0)
- {
- Vector2 localCursor;
- if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor))
- {
- return;
- }
- localCursor -= clickRect.rect.position;
- //now we need to get the delta drag on the bar
- //and move both the normalized low and high values by this amount
- //but also check that neither is going beyond the bounds
- if (NormalizedLowValue >= 0 && NormalizedHighValue <= 1)
- {
- //find the mid point on the current bar
- float mid = (NormalizedHighValue + NormalizedLowValue)/2;
- //find where the new mid point should be
- float val = Mathf.Clamp01((localCursor)[0] / clickRect.rect.size[0]);
- //calculate the delta
- float delta = val - mid;
- //check the clamp range
- if (NormalizedLowValue + delta < 0)
- {
- delta = -NormalizedLowValue;
- }
- else if (NormalizedHighValue + delta > 1)
- {
- delta = 1 - NormalizedHighValue;
- }
- //adjust both ends
- NormalizedLowValue += delta;
- NormalizedHighValue += delta;
- }
- }
- }
- private bool MayDrag(PointerEventData eventData)
- {
- return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
- }
- public override void OnPointerDown(PointerEventData eventData)
- {
- if (!MayDrag(eventData))
- return;
- //HANDLE DRAG EVENTS
- m_LowOffset = m_HighOffset = Vector2.zero;
- Vector2 localMousePos;
- if (m_HighHandleRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HighHandleRect, eventData.position, eventData.enterEventCamera))
- {
- //dragging the high value handle
- if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HighHandleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
- {
- m_HighOffset = localMousePos;
- }
- interactionState = InteractionState.High;
- if (transition == Transition.ColorTint)
- {
- targetGraphic = m_HighHandleRect.GetComponent<Graphic>();
- }
- }
- else if (m_LowHandleRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_LowHandleRect, eventData.position, eventData.enterEventCamera))
- {
- //dragging the low value handle
- if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_LowHandleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
- {
- m_LowOffset = localMousePos;
- }
- interactionState = InteractionState.Low;
- if (transition == Transition.ColorTint)
- {
- targetGraphic = m_LowHandleRect.GetComponent<Graphic>();
- }
- }
- else
- {
- //outside the handles, move the entire slider along
- UpdateDrag(eventData, eventData.pressEventCamera);
- if (eventData.pointerCurrentRaycast.gameObject == m_FillRect.gameObject)
- {
- interactionState = InteractionState.Bar;
- }
- if (transition == Transition.ColorTint)
- {
- targetGraphic = m_FillImage;
- }
- }
- base.OnPointerDown(eventData);
- }
- public virtual void OnDrag(PointerEventData eventData)
- {
- if (!MayDrag(eventData))
- {
- return;
- }
- UpdateDrag(eventData, eventData.pressEventCamera);
- }
- public override void OnPointerUp(PointerEventData eventData)
- {
- base.OnPointerUp(eventData);
- interactionState = InteractionState.None;
- }
- public override void OnMove(AxisEventData eventData)
- {
- //this requires further investigation
- }
- public virtual void OnInitializePotentialDrag(PointerEventData eventData)
- {
- eventData.useDragThreshold = false;
- }
- }
- }
|