Files
2024-10-10 12:46:34 +08:00

509 lines
14 KiB
C#

using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine;
using System.Collections.Generic;
using TMPro;
/*
//Set properties in C# example codes.
using RainbowArt.CleanFlatUI;
public class SliderCircularDemo : MonoBehaviour
{
//The SliderCircular Component.
public SliderCircular m_Slider;
void Start()
{
//Add OnValueChanged event listener.
m_Slider.OnValueChanged.AddListener(SliderCircularValueChange);
//Set the minimum value.
m_Slider.MinValue = 0;
//Set the maximum value.
m_Slider.MaxValue = 1;
//Set whether to show the whole numbers.
m_Slider.WholeNumbers = false;
//Set the value.
m_Slider.Value = 0.8;
//Set whether to show the text.
m_Slider.HasText = true;
//Set whether to fill clockwise.
//m_Slider.Clockwise = true;
//Set the fill start position which include four positions such as Top, Bottom, Left, Right.
m_Slider.CurFillOrigin = SliderCircular.FillOrigin.Top;
}
public void SliderCircularValueChange(float val)
{
Debug.Log("SliderCircularValueChange, value: " + val);
}
}
*/
namespace RainbowArt.CleanFlatUI
{
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
public class SliderCircular : UIBehaviour, IDragHandler, IInitializePotentialDragHandler, ICanvasElement, IPointerDownHandler
{
public enum FillOrigin
{
Top = 0,
Right,
Bottom,
Left,
}
[SerializeField]
FillOrigin fillOrigin = FillOrigin.Top;
[SerializeField]
Image fillImage;
[SerializeField]
RectTransform handleRect;
[SerializeField]
RectTransform handleRootRect;
[SerializeField]
bool clockwise = true;
[SerializeField]
float minValue = 0;
[SerializeField]
float maxValue = 1;
[SerializeField]
bool wholeNumbers = false;
[SerializeField]
float value;
[SerializeField]
bool hasText = true;
[SerializeField]
TextMeshProUGUI text;
Vector2 offset = Vector2.zero;
bool bDelayedUpdate = false;
RectTransform fillImageRect;
public float MinValue
{
get => minValue;
set
{
float newMinValue = value;
if (wholeNumbers)
{
newMinValue = Mathf.Round(newMinValue);
}
if (newMinValue != minValue)
{
minValue = newMinValue;
SetValue(this.value);
UpdateGUI();
}
}
}
public float MaxValue
{
get => maxValue;
set
{
float newMaxValue = value;
if (wholeNumbers)
{
newMaxValue = Mathf.Round(newMaxValue);
}
if (newMaxValue != maxValue)
{
maxValue = newMaxValue;
SetValue(this.value);
UpdateGUI();
}
}
}
public bool WholeNumbers
{
get => wholeNumbers;
set
{
if (wholeNumbers != value)
{
wholeNumbers = value;
SetValue(this.value);
UpdateGUI();
}
}
}
public virtual float Value
{
get
{
if (wholeNumbers)
{
return Mathf.Round(value);
}
return value;
}
set
{
SetValue(value);
}
}
public bool HasText
{
get => hasText;
set
{
hasText = value;
UpdateText();
}
}
public bool Clockwise
{
get => clockwise;
set
{
if (clockwise == value)
{
return;
}
clockwise = value;
UpdateGUI();
}
}
public FillOrigin CurFillOrigin
{
get => fillOrigin;
set
{
if (fillOrigin == value)
{
return;
}
fillOrigin = value;
UpdateGUI();
}
}
public virtual void SetValueWithoutNotify(float input)
{
SetValue(input, false);
}
public float NormalizedValue
{
get
{
if (Mathf.Approximately(MinValue, MaxValue))
{
return 0;
}
return Mathf.InverseLerp(MinValue, MaxValue, Value);
}
set
{
this.Value = Mathf.Lerp(MinValue, MaxValue, value);
}
}
[Serializable]
public class SliderCircularEvent : UnityEvent<float> {}
[SerializeField]
SliderCircularEvent onValueChanged = new SliderCircularEvent();
public SliderCircularEvent OnValueChanged
{
get => onValueChanged;
set
{
onValueChanged = value;
}
}
protected SliderCircular() { }
public virtual void Rebuild(CanvasUpdate executing)
{
#if UNITY_EDITOR
if (executing == CanvasUpdate.Prelayout)
{
OnValueChanged.Invoke(Value);
}
#endif
}
public virtual void LayoutComplete() { }
public virtual void GraphicUpdateComplete() { }
protected override void OnEnable()
{
base.OnEnable();
fillImageRect = fillImage.GetComponent<RectTransform>();
UpdateFillImageOrign();
SetValue(value, false);
UpdateGUI();
}
protected override void OnDisable()
{
base.OnDisable();
}
protected virtual void Update()
{
if (bDelayedUpdate)
{
bDelayedUpdate = false;
UpdateFillImageOrign();
SetValue(value, false);
UpdateGUI();
}
}
protected override void OnDidApplyAnimationProperties()
{
if (!IsActive())
{
return;
}
SetValue(value, false);
UpdateGUI();
}
protected virtual void SetValue(float val, bool sendCallback = true)
{
float newValue = val;
newValue = Mathf.Clamp(newValue, MinValue, MaxValue);
if (wholeNumbers)
{
newValue = Mathf.Round(newValue);
}
if (value == newValue)
{
return;
}
value = newValue;
UpdateGUI();
if (sendCallback)
{
onValueChanged.Invoke(newValue);
}
}
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
if (!IsActive())
{
return;
}
UpdateGUI();
}
void UpdateGUI()
{
float curNormalizedValue = NormalizedValue;
fillImage.fillAmount = curNormalizedValue;
if(clockwise)
{
float finalAngle = -curNormalizedValue * 360 - (int)fillOrigin * 90;
handleRootRect.localEulerAngles = new Vector3(0, 0, finalAngle);
}
else
{
float finalAngle = curNormalizedValue * 360 - (int)fillOrigin * 90;
handleRootRect.localEulerAngles = new Vector3(0, 0, finalAngle);
}
UpdateText();
}
void UpdateText()
{
if (text != null && text.gameObject.activeSelf != hasText)
{
text.gameObject.SetActive(hasText);
}
if (hasText && (text != null))
{
float useValue = (float)Math.Round((double)value, 1);
text.text = useValue + "";
}
}
float GetAngleWithFillOrign(Vector2 pos)
{
Vector2 fillOrignVector = GetOriginVector(fillOrigin);
if(clockwise)
{
float curAngle = Vector2.SignedAngle(fillOrignVector, pos);
if (curAngle > 0)
{
curAngle = 360 - curAngle;
}
else
{
curAngle = -curAngle;
}
return curAngle;
}
else
{
float curAngle = Vector2.SignedAngle(fillOrignVector, pos);
if (curAngle > 0)
{
return curAngle;
}
else
{
curAngle = 360 + curAngle;
}
return curAngle;
}
}
Vector2 GetOriginVector(FillOrigin origin)
{
if (origin == FillOrigin.Top)
{
return new Vector2(0, 1);
}
else if (origin == FillOrigin.Bottom)
{
return new Vector2(0, -1);
}
else if (origin == FillOrigin.Left)
{
return new Vector2(-1, 0);
}
else if (origin == FillOrigin.Right)
{
return new Vector2(1, 0);
}
return Vector2.zero;
}
void UpdateFillImageOrign()
{
if(fillOrigin == FillOrigin.Top)
{
fillImage.fillOrigin = (int)Image.Origin360.Top;
}
else if (fillOrigin == FillOrigin.Bottom)
{
fillImage.fillOrigin = (int)Image.Origin360.Bottom;
}
else if (fillOrigin == FillOrigin.Left)
{
fillImage.fillOrigin = (int)Image.Origin360.Left;
}
else if (fillOrigin == FillOrigin.Right)
{
fillImage.fillOrigin = (int)Image.Origin360.Right;
}
fillImage.fillClockwise = clockwise;
}
void UpdateDrag(PointerEventData eventData, Camera cam)
{
RectTransform clickRect = fillImageRect;
Vector2 position = eventData.position;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(fillImageRect, position, cam, out localCursor))
{
return;
}
float curAngle = GetAngleWithFillOrign(localCursor);
float val = curAngle / 360;
NormalizedValue = val;
}
bool MayDrag(PointerEventData eventData)
{
return IsActive() && eventData.button == PointerEventData.InputButton.Left;
}
public void OnPointerDown(PointerEventData eventData)
{
if (!MayDrag(eventData))
{
return;
}
offset = Vector2.zero;
if (RectTransformUtility.RectangleContainsScreenPoint(handleRect, eventData.pointerPressRaycast.screenPosition, eventData.enterEventCamera))
{
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(handleRect, eventData.pointerPressRaycast.screenPosition, eventData.pressEventCamera, out localMousePos))
{
offset = localMousePos;
}
}
else
{
RectTransform clickRect = fillImageRect;
Vector2 position = eventData.position;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(fillImageRect, position, eventData.pressEventCamera, out localCursor))
{
return;
}
float curAngle = GetAngleWithFillOrign(localCursor);
float val = curAngle / 360;
NormalizedValue = val;
}
}
public virtual void OnDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
{
return;
}
UpdateDrag(eventData, eventData.pressEventCamera);
}
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
eventData.useDragThreshold = true;
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (wholeNumbers)
{
minValue = Mathf.Round(minValue);
maxValue = Mathf.Round(maxValue);
}
if (IsActive())
{
bDelayedUpdate = true;
}
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
{
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
if(text != null)
{
text.gameObject.SetActive(hasText);
}
}
#endif
}
}