Files
XericLibrary-Publish/Runtime/UI/CircleSlider.cs
2025-04-10 15:32:00 +08:00

429 lines
11 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Deconstruction.Runtime.UI
{
public class CircleSlider : Selectable, IDragHandler, IEndDragHandler, IBeginDragHandler
{
[SerializeField] private RectTransform m_HandleRect;
public RectTransform handleRect
{
get { return m_HandleRect; }
set
{
if (SetPropertyUtility.SetClass(ref m_HandleRect, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
[SerializeField] private RectTransform m_FillRect;
public RectTransform fillrect
{
get { return m_FillRect; }
set
{
if (SetPropertyUtility.SetClass(ref m_FillRect, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
[SerializeField] private float m_MinValue = 0;
public float minValue
{
get => m_MinValue;
set
{
if (SetPropertyUtility.SetStruct(ref m_MinValue, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
[SerializeField] private float m_MaxValue = 1;
public float maxValue
{
get { return m_MaxValue; }
set
{
if (SetPropertyUtility.SetStruct(ref m_MaxValue, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
[SerializeField] private float m_Value;
public float value
{
get
{
if (wholeNumbers)
return Mathf.Round(m_Value);
return m_Value;
}
set { Set(value); }
}
[SerializeField] private Image.Origin360 m_FillOrigin;
public Image.Origin360 fillOrigin
{
get { return m_FillOrigin; }
set
{
if (SetPropertyUtility.SetStruct(ref m_FillOrigin, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
[SerializeField] private float m_Radius;
public float radius
{
get { return m_Radius; }
set
{
if (SetPropertyUtility.SetStruct(ref m_Radius, value))
{
UpdateVisuals();
}
}
}
[SerializeField] private bool m_ClockWise;
public bool clockWise
{
get { return m_ClockWise; }
set
{
if (SetPropertyUtility.SetStruct(ref m_ClockWise, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
[SerializeField] private bool m_WholeNumbers = false;
public bool wholeNumbers
{
get { return m_WholeNumbers; }
set
{
if (SetPropertyUtility.SetStruct(ref m_WholeNumbers, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
public float normalizedValue
{
get
{
if (Mathf.Approximately(m_MinValue, m_MaxValue))
return 0;
return Mathf.InverseLerp(m_MinValue, m_MaxValue, value);
}
set { this.value = Mathf.Lerp(m_MinValue, m_MaxValue, value); }
}
private Action<float> m_OnValueChanged;
private Action m_OnDragEnd;
private Action m_OnTweenComplete;
private Image m_FillImage;
private bool m_DelayedUpdateVisuals = false;
private bool m_dragHandler;
private Camera m_eventCamera;
private Coroutine m_TweenCor;
protected override void OnEnable()
{
UpdateCachedReferences();
Set(m_Value);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (IsActive())
{
UpdateCachedReferences();
Set(m_Value, false);
m_DelayedUpdateVisuals = true;
}
}
#endif
void Update()
{
if (m_DelayedUpdateVisuals)
{
m_DelayedUpdateVisuals = false;
UpdateVisuals();
}
}
void UpdateCachedReferences()
{
if (m_FillRect != null)
{
m_FillImage = m_FillRect.GetComponent<Image>();
m_FillImage.fillMethod = Image.FillMethod.Radial360;
m_FillImage.fillOrigin = (int)m_FillOrigin;
m_FillImage.fillClockwise = m_ClockWise;
}
}
private bool MayDrag(PointerEventData eventData)
{
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
public void OnDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
if (m_dragHandler)
{
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(m_FillRect, eventData.position,
eventData.pressEventCamera, out localCursor))
return;
float angle = CalAngle(localCursor);
normalizedValue = (angle / 360);
}
}
public void OnEndDrag(PointerEventData eventData)
{
m_dragHandler = false;
if (m_OnDragEnd != null)
{
m_OnDragEnd();
}
}
public void OnBeginDrag(PointerEventData eventData)
{
if (m_HandleRect != null)
{
if (RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position,
eventData.enterEventCamera))
{
m_dragHandler = true;
}
}
else
{
m_dragHandler = true;
}
}
private float CalAngle(Vector2 pos)
{
float angle = 0;
float rad = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
switch (m_FillOrigin)
{
case Image.Origin360.Top:
angle = m_ClockWise ? 90 - rad : rad + 270;
break;
case Image.Origin360.Left:
angle = m_ClockWise ? 180 - rad : rad + 180;
break;
case Image.Origin360.Right:
angle = m_ClockWise ? -rad : rad;
break;
case Image.Origin360.Bottom:
angle = m_ClockWise ? 270 - rad : rad + 90;
break;
}
if (angle > 360)
{
angle -= 360;
}
if (angle < 0)
{
angle += 360;
}
return angle;
}
private void UpdateVisuals()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
UpdateCachedReferences();
#endif
if (m_FillImage != null)
{
m_FillImage.fillAmount = normalizedValue;
}
if (m_HandleRect != null)
{
m_HandleRect.transform.localPosition = CalPos(normalizedValue * 360);
}
}
private Vector2 CalPos(float angle)
{
Vector2 pos = Vector2.zero;
switch (m_FillOrigin)
{
case Image.Origin360.Left:
angle = m_ClockWise ? 180 - angle : angle + 180;
break;
case Image.Origin360.Top:
angle = m_ClockWise ? 90 - angle : angle + 90;
break;
case Image.Origin360.Right:
angle = m_ClockWise ? -angle : angle;
break;
case Image.Origin360.Bottom:
angle = m_ClockWise ? 270 - angle : angle - 90;
break;
}
angle = Mathf.Deg2Rad * angle;
pos.y = Mathf.Sin(angle) * m_Radius;
pos.x = Mathf.Cos(angle) * m_Radius;
return pos;
}
private void Set(float input, bool sendCallback = true)
{
float newValue = ClampValue(input);
if (m_Value == newValue)
return;
m_Value = newValue;
UpdateVisuals();
if (sendCallback)
{
if (m_OnValueChanged != null)
{
m_OnValueChanged.Invoke(newValue);
}
}
}
float ClampValue(float input)
{
float newValue = Mathf.Clamp(input, m_MinValue, m_MaxValue);
if (wholeNumbers)
newValue = Mathf.Round(newValue);
return newValue;
}
public void OnValueChange(Action<float> call)
{
m_OnValueChanged = call;
}
public void OnDragEnd(Action call)
{
m_OnDragEnd = call;
}
public void TweenValue(float val, float time, Action call = null)
{
m_TweenCor = StartCoroutine(_tweenValue(val, time));
m_OnTweenComplete = call;
}
public void StopTween()
{
if (m_TweenCor != null)
{
StopCoroutine(m_TweenCor);
}
}
IEnumerator _tweenValue(float val, float time)
{
float t = 0;
float oldValue = value;
float interval = val - oldValue;
while (t < time)
{
t += Time.deltaTime;
value = oldValue + (t / time) * interval;
yield return 0;
}
if (m_OnTweenComplete != null)
{
m_OnTweenComplete();
}
value = val;
}
}
internal static class SetPropertyUtility
{
public static bool SetColor(ref Color currentValue, Color newValue)
{
if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b &&
currentValue.a == newValue.a)
return false;
currentValue = newValue;
return true;
}
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
{
if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
return false;
currentValue = newValue;
return true;
}
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;
}
}
}