com.alicizax.unity.ui.exten.../Runtime/UXComponent/UXSlider/UXSlider.cs
陈思海 32c0fc13fd 重构
1.重构UXButton
2.主Transition有UXSeletable负责 ,同时兼具导航
3.新增UXSlider  支持平滑过渡 适配手柄
4.优化部分逻辑bug
2025-12-09 15:08:41 +08:00

604 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
{
}
}
}