com.alicizax.unity.ui.exten.../Runtime/UXComponent/UXSlider/UXSlider.cs

604 lines
20 KiB
C#
Raw Normal View History

using AlicizaX.UI.Extension;
using UnityEngine.EventSystems;
using Direction = UnityEngine.UI.Slider.Direction;
namespace UnityEngine.UI
{
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
public class UXSlider : UXSelectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
{
[SerializeField] private RectTransform m_FillRect;
public RectTransform fillRect
{
get { return m_FillRect; }
set
{
if (SetPropertyUtility.SetClass(ref m_FillRect, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
[SerializeField] private RectTransform m_HandleRect;
public RectTransform handleRect
{
get { return m_HandleRect; }
set
{
if (SetPropertyUtility.SetClass(ref m_HandleRect, value))
{
UpdateCachedReferences();
UpdateVisuals();
}
}
}
[Space] [SerializeField] private Slider.Direction m_Direction = Slider.Direction.LeftToRight;
public Slider.Direction direction
{
get { return m_Direction; }
set
{
if (SetPropertyUtility.SetStruct(ref m_Direction, value)) UpdateVisuals();
}
}
[SerializeField] private float m_MinValue = 0;
public float minValue
{
get { return 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 bool m_WholeNumbers = false;
public bool wholeNumbers
{
get { return m_WholeNumbers; }
set
{
if (SetPropertyUtility.SetStruct(ref m_WholeNumbers, value))
{
Set(m_Value);
UpdateVisuals();
}
}
}
[Space] [SerializeField] protected float m_Value;
public virtual float value
{
get { return wholeNumbers ? Mathf.Round(m_Value) : m_Value; }
set { Set(value); }
}
public virtual void SetValueWithoutNotify(float input)
{
Set(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); }
}
[Space] [SerializeField] private Slider.SliderEvent m_OnValueChanged = new Slider.SliderEvent();
public Slider.SliderEvent onValueChanged
{
get { return m_OnValueChanged; }
set { m_OnValueChanged = value; }
}
// --- SMOOTHING FIELDS ---
[Space]
[SerializeField] private bool m_SmoothMovement = false;
[SerializeField] private float m_SmoothSpeed = 8f; // value-per-second, larger=faster
public bool smoothMovement
{
get { return m_SmoothMovement; }
set { m_SmoothMovement = value; }
}
/// <summary>
/// 平滑移动目标值(只有当 smoothMovement && !wholeNumbers 时生效)
/// </summary>
protected float m_TargetValue;
protected bool m_IsSmoothMoving = false;
// ------------------------
private Image m_FillImage;
private Transform m_FillTransform;
private RectTransform m_FillContainerRect;
private Transform m_HandleTransform;
private RectTransform m_HandleContainerRect;
private Vector2 m_Offset = Vector2.zero;
#pragma warning disable 649
private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649
private bool m_DelayedUpdateVisuals = false;
float stepSize
{
get { return wholeNumbers ? 1 : (maxValue - minValue) * 0.1f; }
}
protected UXSlider()
{
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (wholeNumbers)
{
m_MinValue = Mathf.Round(m_MinValue);
m_MaxValue = Mathf.Round(m_MaxValue);
}
//Onvalidate is called before OnEnabled. We need to make sure not to touch any other objects before OnEnable is run.
if (IsActive())
{
UpdateCachedReferences();
// Update rects in next update 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
protected override void OnEnable()
{
base.OnEnable();
UpdateCachedReferences();
Set(m_Value, false);
// Update rects since they need to be initialized correctly.
UpdateVisuals();
// 初始化 target
m_TargetValue = m_Value;
m_IsSmoothMoving = false;
}
protected override void OnDisable()
{
m_Tracker.Clear();
base.OnDisable();
}
protected virtual void Update()
{
if (m_DelayedUpdateVisuals)
{
m_DelayedUpdateVisuals = false;
Set(m_Value, false);
UpdateVisuals();
}
// 处理平滑移动(仅当开启时)
if (m_IsSmoothMoving)
{
// 如果 wholeNumbers 为 true则取消平滑直接设置最终值避免小数
if (wholeNumbers)
{
m_IsSmoothMoving = false;
Set(m_TargetValue);
}
else
{
float maxDelta = m_SmoothSpeed * Time.unscaledDeltaTime;
float newValue = Mathf.MoveTowards(m_Value, m_TargetValue, maxDelta);
// 使用 Set 来保持更新视觉并触发回调(会在数值改变时触发)
Set(newValue);
if (Mathf.Approximately(newValue, m_TargetValue))
{
m_IsSmoothMoving = false;
// 确保最终值精确到目标(避免浮点残留)
Set(m_TargetValue);
}
}
}
}
protected override void OnDidApplyAnimationProperties()
{
m_Value = ClampValue(m_Value);
float oldNormalizedValue = normalizedValue;
if (m_FillContainerRect != null)
{
if (m_FillImage != null && m_FillImage.type == Image.Type.Filled)
oldNormalizedValue = m_FillImage.fillAmount;
else
oldNormalizedValue = (reverseValue ? 1 - m_FillRect.anchorMin[(int)axis] : m_FillRect.anchorMax[(int)axis]);
}
else if (m_HandleContainerRect != null)
oldNormalizedValue = (reverseValue ? 1 - m_HandleRect.anchorMin[(int)axis] : m_HandleRect.anchorMin[(int)axis]);
UpdateVisuals();
if (oldNormalizedValue != normalizedValue)
{
UISystemProfilerApi.AddMarker("Slider.value", this);
onValueChanged.Invoke(m_Value);
}
// UUM-34170 Apparently, some properties on slider such as IsInteractable and Normalcolor Animation is broken.
// We need to call base here to render the animation on Scene
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_HandleRect && m_HandleRect != (RectTransform)transform)
{
m_HandleTransform = m_HandleRect.transform;
if (m_HandleTransform.parent != null)
m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>();
}
else
{
m_HandleRect = null;
m_HandleContainerRect = null;
}
}
float ClampValue(float input)
{
float newValue = Mathf.Clamp(input, minValue, maxValue);
if (wholeNumbers)
newValue = Mathf.Round(newValue);
return newValue;
}
protected virtual void Set(float input, bool sendCallback = true)
{
// Clamp the input
float newValue = ClampValue(input);
// If the stepped value doesn't match the last one, it's time to update
if (m_Value == newValue)
return;
m_Value = newValue;
UpdateVisuals();
if (sendCallback)
{
UISystemProfilerApi.AddMarker("Slider.value", this);
m_OnValueChanged.Invoke(newValue);
}
}
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
if (!IsActive())
return;
UpdateVisuals();
}
enum Axis
{
Horizontal = 0,
Vertical = 1
}
Axis axis
{
get { return (m_Direction == Direction.LeftToRight || m_Direction == Direction.RightToLeft) ? Axis.Horizontal : Axis.Vertical; }
}
bool reverseValue
{
get { return m_Direction == Direction.RightToLeft || m_Direction == Direction.TopToBottom; }
}
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;
if (m_FillImage != null && m_FillImage.type == Image.Type.Filled)
{
m_FillImage.fillAmount = normalizedValue;
}
else
{
if (reverseValue)
anchorMin[(int)axis] = 1 - normalizedValue;
else
anchorMax[(int)axis] = normalizedValue;
}
m_FillRect.anchorMin = anchorMin;
m_FillRect.anchorMax = anchorMax;
}
if (m_HandleContainerRect != null)
{
m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);
Vector2 anchorMin = Vector2.zero;
Vector2 anchorMax = Vector2.one;
anchorMin[(int)axis] = anchorMax[(int)axis] = (reverseValue ? (1 - normalizedValue) : normalizedValue);
m_HandleRect.anchorMin = anchorMin;
m_HandleRect.anchorMax = anchorMax;
}
}
void UpdateDrag(PointerEventData eventData, Camera cam)
{
RectTransform clickRect = m_HandleContainerRect ?? m_FillContainerRect;
if (clickRect != null && clickRect.rect.size[(int)axis] > 0)
{
Vector2 position = Vector2.zero;
if (!MultipleDisplayUtilities.GetRelativeMousePositionForDrag(eventData, ref position))
return;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, position, cam, out localCursor))
return;
localCursor -= clickRect.rect.position;
float val = Mathf.Clamp01((localCursor - m_Offset)[(int)axis] / clickRect.rect.size[(int)axis]);
normalizedValue = (reverseValue ? 1f - val : val);
}
}
private bool MayDrag(PointerEventData eventData)
{
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
public override void OnPointerDown(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
base.OnPointerDown(eventData);
// 取消任何平滑移动(开始拖拽时)
m_IsSmoothMoving = false;
m_TargetValue = m_Value;
m_Offset = Vector2.zero;
if (m_HandleContainerRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.pointerPressRaycast.screenPosition, eventData.enterEventCamera))
{
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.pointerPressRaycast.screenPosition, eventData.pressEventCamera, out localMousePos))
m_Offset = localMousePos;
}
else
{
// Outside the slider handle - jump to this point instead
UpdateDrag(eventData, eventData.pressEventCamera);
}
}
public void OnDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
// 拖拽也取消平滑
m_IsSmoothMoving = false;
m_TargetValue = m_Value;
UpdateDrag(eventData, eventData.pressEventCamera);
}
public override void OnMove(AxisEventData eventData)
{
if (!IsActive() || !IsInteractable())
{
base.OnMove(eventData);
return;
}
// 基准值:如果当前已经在平滑移动中,累加到 target 上;否则从当前 value 开始。
float currentBase = m_IsSmoothMoving ? m_TargetValue : value;
switch (eventData.moveDir)
{
case MoveDirection.Left:
if (axis == Axis.Horizontal && FindSelectableOnLeft() == null)
{
float dest = ClampValue(reverseValue ? currentBase + stepSize : currentBase - stepSize);
if (m_SmoothMovement && !wholeNumbers)
{
m_TargetValue = dest;
m_IsSmoothMoving = true;
}
else
{
Set(dest);
}
}
else
base.OnMove(eventData);
break;
case MoveDirection.Right:
if (axis == Axis.Horizontal && FindSelectableOnRight() == null)
{
float dest = ClampValue(reverseValue ? currentBase - stepSize : currentBase + stepSize);
if (m_SmoothMovement && !wholeNumbers)
{
m_TargetValue = dest;
m_IsSmoothMoving = true;
}
else
{
Set(dest);
}
}
else
base.OnMove(eventData);
break;
case MoveDirection.Up:
if (axis == Axis.Vertical && FindSelectableOnUp() == null)
{
float dest = ClampValue(reverseValue ? currentBase - stepSize : currentBase + stepSize);
if (m_SmoothMovement && !wholeNumbers)
{
m_TargetValue = dest;
m_IsSmoothMoving = true;
}
else
{
Set(dest);
}
}
else
base.OnMove(eventData);
break;
case MoveDirection.Down:
if (axis == Axis.Vertical && FindSelectableOnDown() == null)
{
float dest = ClampValue(reverseValue ? currentBase + stepSize : currentBase - stepSize);
if (m_SmoothMovement && !wholeNumbers)
{
m_TargetValue = dest;
m_IsSmoothMoving = true;
}
else
{
Set(dest);
}
}
else
base.OnMove(eventData);
break;
}
}
public override UXSelectable FindSelectableOnLeft()
{
if (navigation.mode == UXNavigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnLeft();
}
public override UXSelectable FindSelectableOnRight()
{
if (navigation.mode == UXNavigation.Mode.Automatic && axis == Axis.Horizontal)
return null;
return base.FindSelectableOnRight();
}
public override UXSelectable FindSelectableOnUp()
{
if (navigation.mode == UXNavigation.Mode.Automatic && axis == Axis.Vertical)
return null;
return base.FindSelectableOnUp();
}
public override UXSelectable FindSelectableOnDown()
{
if (navigation.mode == UXNavigation.Mode.Automatic && axis == Axis.Vertical)
return null;
return base.FindSelectableOnDown();
}
public void OnInitializePotentialDrag(PointerEventData eventData)
{
eventData.useDragThreshold = false;
}
public void SetDirection(Direction direction, bool includeRectLayouts)
{
Axis oldAxis = axis;
bool oldReverse = reverseValue;
this.direction = direction;
if (!includeRectLayouts)
return;
if (axis != oldAxis)
RectTransformUtility.FlipLayoutAxes(transform as RectTransform, true, true);
if (reverseValue != oldReverse)
RectTransformUtility.FlipLayoutOnAxis(transform as RectTransform, (int)axis, true, true);
}
public void Rebuild(CanvasUpdate executing)
{
#if UNITY_EDITOR
if (executing == CanvasUpdate.Prelayout)
onValueChanged.Invoke(value);
#endif
}
public void LayoutComplete()
{
}
public void GraphicUpdateComplete()
{
}
}
}