2025-01-30 23:38:06 +01:00

178 lines
5.7 KiB
C#

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using TMPro;
namespace Michsky.MUIP
{
public class RadialSlider : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler, IPointerEnterHandler, IPointerExitHandler
{
private const string PREFS_UI_SAVE_NAME = "Radial";
// Content
public float currentValue = 50.0f;
// Resources
public Image sliderImage;
public Transform indicatorPivot;
public TextMeshProUGUI valueText;
// Settings
public float minValue = 0;
public float maxValue = 100;
[Range(0, 8)] public int decimals;
public bool isPercent;
public StartPoint startPoint = StartPoint.Left;
// Saving
public bool rememberValue;
public string sliderTag;
// Events
[System.Serializable]
public class SliderEvent : UnityEvent<float> { }
[SerializeField]
public SliderEvent onValueChanged = new SliderEvent();
public UnityEvent onPointerEnter;
public UnityEvent onPointerExit;
private GraphicRaycaster graphicRaycaster;
private RectTransform hitRectTransform;
private bool isPointerDown;
private float currentAngle;
private float currentAngleOnPointerDown;
private float valueDisplayPrecision;
public enum StartPoint { Left, Right, Top, Down }
public float SliderAngle
{
get { return currentAngle; }
set { currentAngle = Mathf.Clamp(value, 0.0f, 360.0f); }
}
// Slider value with applied display precision, i.e. the number of decimals to show.
public float SliderValue
{
get { return (long)(SliderValueRaw * valueDisplayPrecision) / valueDisplayPrecision; }
set { SliderValueRaw = value; }
}
// Raw slider value, i.e. without any display precision applied to its value.
public float SliderValueRaw
{
get { return SliderAngle / 360.0f * maxValue; }
set { SliderAngle = value * 360.0f / maxValue; }
}
private void Awake()
{
graphicRaycaster = GetComponentInParent<GraphicRaycaster>();
if (graphicRaycaster == null)
Debug.LogWarning("<b>[Radial Slider]</b> Could not find GraphicRaycaster component in parent.", this);
}
private void Start()
{
valueDisplayPrecision = Mathf.Pow(10, decimals);
if (rememberValue == true) { LoadState(); }
else { SliderAngle = currentValue * 3.6f; }
SliderValue = currentValue;
onValueChanged.Invoke(SliderValueRaw);
UpdateUI();
}
public void OnPointerDown(PointerEventData eventData)
{
hitRectTransform = eventData.pointerCurrentRaycast.gameObject.GetComponent<RectTransform>();
isPointerDown = true;
currentAngleOnPointerDown = SliderAngle;
HandleSliderMouseInput(eventData, true);
}
public void OnPointerUp(PointerEventData eventData)
{
if (HasValueChanged())
SaveState();
hitRectTransform = null;
isPointerDown = false;
}
public void OnDrag(PointerEventData eventData)
{
if (currentValue >= minValue) { HandleSliderMouseInput(eventData, false); }
else if (currentValue <= minValue) { SliderValueRaw = minValue; }
}
public void OnPointerEnter(PointerEventData eventData)
{
onPointerEnter.Invoke();
}
public void OnPointerExit(PointerEventData eventData)
{
onPointerExit.Invoke();
}
public void LoadState()
{
currentAngle = PlayerPrefs.GetFloat(sliderTag + PREFS_UI_SAVE_NAME);
}
public void SaveState()
{
if (!rememberValue)
return;
PlayerPrefs.SetFloat(sliderTag + PREFS_UI_SAVE_NAME, currentAngle);
}
public void UpdateUI()
{
if (SliderValueRaw >= minValue)
{
float normalizedAngle = SliderAngle / 360.0f;
indicatorPivot.transform.localEulerAngles = new Vector3(180.0f, 0.0f, SliderAngle);
sliderImage.fillAmount = normalizedAngle;
valueText.text = string.Format("{0}{1}", SliderValue, isPercent ? "%" : "");
currentValue = SliderValue;
}
}
private bool HasValueChanged()
{
return SliderAngle != currentAngleOnPointerDown;
}
private void HandleSliderMouseInput(PointerEventData eventData, bool allowValueWrap)
{
if (!isPointerDown)
return;
Vector2 localPos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(hitRectTransform, eventData.position, eventData.pressEventCamera, out localPos);
float newAngle = Mathf.Atan2(-localPos.y, localPos.x) * Mathf.Rad2Deg + 180f;
if (!allowValueWrap)
{
currentAngle = SliderAngle;
bool needsClamping = Mathf.Abs(newAngle - currentAngle) >= 180;
if (needsClamping)
newAngle = currentAngle < newAngle ? 0.0f : 360.0f;
}
SliderAngle = newAngle;
UpdateUI();
if (HasValueChanged())
onValueChanged.Invoke(SliderValueRaw);
}
}
}