471 lines
12 KiB
C#
Raw Normal View History

2025-09-08 14:51:28 +08:00
/*************************************************************************
* Copyright © 2020 Great1217. All rights reserved.
*------------------------------------------------------------------------
* File : AnnularSlider.cs
* Description : Null.
*------------------------------------------------------------------------
* Author : Great1217
* Version : 0.1.0
* Date : 13/11/2020
* Description : Initial development version.
*************************************************************************/
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(RectTransform)), ExecuteInEditMode]
public class AnnularSlider : Selectable, IDragHandler, ICanvasElement
{
[Serializable]
public class DragEvent : UnityEvent
{
}
[Serializable]
public class DragValueEvent : UnityEvent<float>
{
}
[SerializeField] private Image _fillImage;
[SerializeField] private Image.Origin360 _fillOrigin;
[SerializeField] private bool _clockwise;
[SerializeField] private bool _wholeNumbers;
[SerializeField] private float _minValue;
[SerializeField] private float _maxValue = 1f;
[SerializeField] private float _maxAngle = 360f;
[SerializeField] private float _value;
[SerializeField] private RectTransform _handleRect;
[SerializeField] private float _radius = 10f;
[SerializeField] private bool _towardCenter;
[SerializeField] private DragValueEvent _onValueChanged = new DragValueEvent();
[SerializeField] private DragEvent _onBeginDragged = new DragEvent();
[SerializeField] private DragEvent _onDragging = new DragEvent();
[SerializeField] private DragEvent _onEndDragged = new DragEvent();
private bool _delayedUpdateVisuals;
public Image FillImage
{
get { return _fillImage; }
set
{
if (SetClass(ref _fillImage, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
public Image.Origin360 FillOrigin
{
get { return _fillOrigin; }
set
{
if (SetStruct(ref _fillOrigin, value))
{
UpdateVisuals();
}
}
}
public bool Clockwise
{
get { return _clockwise; }
set
{
if (SetStruct(ref _clockwise, value))
{
UpdateVisuals();
}
}
}
public bool WholeNumbers
{
get { return _wholeNumbers; }
set
{
if (SetStruct(ref _wholeNumbers, value))
{
UpdateValue(_value);
UpdateVisuals();
}
}
}
public float MinValue
{
get { return _minValue; }
set
{
if (SetStruct(ref _minValue, value))
{
UpdateValue(_value);
UpdateVisuals();
}
}
}
public float MaxValue
{
get { return _maxValue; }
set
{
if (SetStruct(ref _maxValue, value))
{
UpdateValue(_value);
UpdateVisuals();
}
}
}
public float MaxAngle
{
get { return _maxAngle; }
set
{
if (SetStruct(ref _maxAngle, value))
{
UpdateVisuals();
}
}
}
public float Value
{
get
{
if (_wholeNumbers) return Mathf.Round(_value);
return _value;
}
set { UpdateValue(value); }
}
public RectTransform HandleRect
{
get { return _handleRect; }
set
{
if (SetClass(ref _handleRect, value))
{
UpdateVisuals();
}
}
}
public float Radius
{
get { return _radius; }
set
{
if (SetStruct(ref _radius, value))
{
UpdateVisuals();
}
}
}
public bool TowardCenter
{
get { return _towardCenter; }
set
{
if (SetStruct(ref _towardCenter, value))
{
UpdateVisuals();
}
}
}
public DragValueEvent OnValueChanged
{
get { return _onValueChanged; }
set { _onValueChanged = value; }
}
public DragEvent OnBeginDragged
{
get { return _onBeginDragged; }
set { _onBeginDragged = value; }
}
public DragEvent OnDragging
{
get { return _onDragging; }
set { _onDragging = value; }
}
public DragEvent OnEndDragged
{
get { return _onEndDragged; }
set { _onEndDragged = value; }
}
public float NormalizedValue
{
get
{
if (Mathf.Approximately(_minValue, _maxValue)) return 0;
return Mathf.InverseLerp(_minValue, _maxValue, Value);
}
set { Value = Mathf.Lerp(_minValue, _maxValue, value); }
}
protected override void OnEnable()
{
base.OnEnable();
UpdateCachedReferences();
UpdateValue(_value, false);
UpdateVisuals();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (WholeNumbers)
{
_minValue = Mathf.Round(_minValue);
_maxValue = Mathf.Round(_maxValue);
}
//Onvalidate is called before OnEnabled. We need to make sure not to touch any other objects before OnEnable is run.
if (IsActive())
{
UpdateCachedReferences();
UpdateValue(_value, false);
_delayedUpdateVisuals = true;
}
//if (!UnityEditor.PrefabUtility.IsComponentAddedToPrefabInstance(this) && !Application.isPlaying)
// CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
#endif
protected virtual void Update()
{
if (_delayedUpdateVisuals)
{
_delayedUpdateVisuals = false;
UpdateVisuals();
}
}
public override void OnPointerDown(PointerEventData eventData)
{
if (MayEvent(eventData))
{
OnBeginDragged.Invoke();
//Debug.Log("OnBeginDragged");
}
}
public void OnDrag(PointerEventData eventData)
{
if (!MayEvent(eventData)) return;
_onDragging.Invoke();
Vector2 localPoint;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(_fillImage.rectTransform, eventData.position, eventData.pressEventCamera, out localPoint))
{
//根据鼠标在rect的位置=>求出角度相对于X轴右侧 180°表示=>相对于起始点的角度360° 表示)=>_fillImage.fillAmount、_handleRect.localPosition
var angle = GetAngleFromFillOrigin(localPoint); //相对角度 [0f-360f]
//超出最大角度时,等于相近的边界值
if (angle > _maxAngle)
{
angle = angle - _maxAngle < 360 - angle ? _maxAngle : 0f;
}
NormalizedValue = angle / _maxAngle;
UpdateVisuals();
}
}
public override void OnPointerUp(PointerEventData eventData)
{
if (MayEvent(eventData))
{
OnEndDragged.Invoke();
//Debug.Log("OnEndDragged");
}
}
public void Rebuild(CanvasUpdate executing)
{
}
public void LayoutComplete()
{
}
public void GraphicUpdateComplete()
{
}
/// <summary>
/// 返回是否可交互
/// </summary>
/// <returns></returns>
private bool MayEvent(PointerEventData eventData)
{
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
/// <summary>
/// 更新缓存引用
/// </summary>
private void UpdateCachedReferences()
{
if (_fillImage)
{
_fillImage.type = Image.Type.Filled;
_fillImage.fillMethod = Image.FillMethod.Radial360;
_fillImage.fillOrigin = (int) _fillOrigin;
_fillImage.fillClockwise = _clockwise;
}
}
/// <summary>
/// 更新视觉效果
/// </summary>
private void UpdateVisuals()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
UpdateCachedReferences();
#endif
var angle = NormalizedValue * _maxAngle;
if (_fillImage)
{
_fillImage.fillAmount = angle / 360f;
}
if (_handleRect)
{
_handleRect.transform.localPosition = GetPointFromFillOrigin(ref angle);
if (_towardCenter)
{
_handleRect.transform.localEulerAngles = new Vector3(0f, 0f, angle);
}
}
}
/// <summary>
/// 更新Value
/// </summary>
/// <param name="value"></param>
/// <param name="sendCallback"></param>
private void UpdateValue(float value, bool sendCallback = true)
{
value = Mathf.Clamp(value, _minValue, _maxValue);
if (_wholeNumbers) value = Mathf.Round(value);
if (_value.Equals(value)) return;
_value = value;
UpdateVisuals();
if (sendCallback)
{
_onValueChanged.Invoke(_value);
//Debug.Log("OnValueChanged" + _value);
}
}
/// <summary>
/// 返回基于起始点的角度0°~360°
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private float GetAngleFromFillOrigin(Vector2 point)
{
var angle = Mathf.Atan2(point.y, point.x) * Mathf.Rad2Deg; //相对于X轴右侧FillOrigin.Right的角度
//转换为相对于起始点的角度
switch (_fillOrigin)
{
case Image.Origin360.Bottom:
angle = _clockwise ? 270 - angle : 90 + angle;
break;
case Image.Origin360.Right:
angle = _clockwise ? -angle : angle;
break;
case Image.Origin360.Top:
angle = _clockwise ? 90 - angle : 270 + angle;
break;
case Image.Origin360.Left:
angle = _clockwise ? 180 - angle : 180 + angle;
break;
default:
throw new ArgumentOutOfRangeException();
}
//转 360 °表示
if (angle > 360)
{
angle -= 360;
}
if (angle < 0)
{
angle += 360;
}
return angle;
}
/// <summary>
/// 返回基于起始点、角度、半径的位置
/// </summary>
/// <param name="angle"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private Vector2 GetPointFromFillOrigin(ref float angle)
{
//转化为相对于X轴右侧FillOrigin.Right的角度
switch (_fillOrigin)
{
case Image.Origin360.Bottom:
angle = _clockwise ? 270 - angle : angle - 90;
break;
case Image.Origin360.Right:
angle = _clockwise ? -angle : angle;
break;
case Image.Origin360.Top:
angle = _clockwise ? 90 - angle : 90 + angle;
break;
case Image.Origin360.Left:
angle = _clockwise ? 180 - angle : 180 + angle;
break;
default:
throw new ArgumentOutOfRangeException();
}
var radian = angle * Mathf.Deg2Rad;
return new Vector2(Mathf.Cos(radian) * _radius, Mathf.Sin(radian) * _radius);
}
private static bool SetStruct<T>(ref T setValue, T value) where T : struct
{
if (EqualityComparer<T>.Default.Equals(setValue, value)) return false;
setValue = value;
return true;
}
private static bool SetClass<T>(ref T setValue, T value) where T : class
{
if (setValue == null && value == null || setValue != null && setValue.Equals(value)) return false;
setValue = value;
return true;
}
}