com.alicizax.unity.ui.exten.../Runtime/UXComponent/Group/UXToggle.cs
2025-12-22 13:41:19 +08:00

301 lines
8.7 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 System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
#if INPUTSYSTEM_SUPPORT
using UnityEngine.InputSystem;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.UI
{
[AddComponentMenu("UI/UXToggle", 30)]
[RequireComponent(typeof(RectTransform))]
public class UXToggle : UXSelectable, IPointerClickHandler, ISubmitHandler, ICanvasElement
#if INPUTSYSTEM_SUPPORT
, IHotkeyTrigger
#endif
{
[Serializable]
public class ToggleEvent : UnityEvent<bool>
{
}
public Toggle.ToggleTransition toggleTransition = Toggle.ToggleTransition.Fade;
public Graphic graphic;
[SerializeField] private UXGroup m_Group;
public UXGroup group
{
get { return m_Group; }
set
{
SetToggleGroup(value, true);
PlayEffect(true);
}
}
public ToggleEvent onValueChanged = new ToggleEvent();
[Tooltip("Is the toggle currently on or off?")] [SerializeField]
private bool m_IsOn;
protected UXToggle()
{
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
#endif
public virtual void Rebuild(CanvasUpdate executing)
{
#if UNITY_EDITOR
if (executing == CanvasUpdate.Prelayout)
onValueChanged.Invoke(m_IsOn);
#endif
}
public virtual void LayoutComplete()
{
}
public virtual void GraphicUpdateComplete()
{
}
protected override void OnDestroy()
{
if (m_Group != null)
m_Group.EnsureValidState();
base.OnDestroy();
}
protected override void OnEnable()
{
base.OnEnable();
PlayEffect(true);
}
protected override void OnDidApplyAnimationProperties()
{
if (graphic != null)
{
bool oldValue = !Mathf.Approximately(graphic.canvasRenderer.GetColor().a, 0);
if (m_IsOn != oldValue)
{
m_IsOn = oldValue;
Set(!oldValue);
}
}
base.OnDidApplyAnimationProperties();
}
// Centralized group setter logic.
private void SetToggleGroup(UXGroup newGroup, bool setMemberValue)
{
// 如果组没有改变,仍然需要确保组里包含此 toggle修复编辑器中批量拖拽只注册最后一项的问题
if (m_Group == newGroup)
{
if (setMemberValue)
m_Group = newGroup;
if (newGroup != null && !newGroup.ContainsToggle(this))
{
newGroup.RegisterToggle(this);
}
// 尝试同步组状态,确保编辑器批量赋值时能稳定显示
if (newGroup != null)
newGroup.EnsureValidState();
return;
}
// 从旧组注销(如果存在)
if (m_Group != null)
m_Group.UnregisterToggle(this);
if (setMemberValue)
m_Group = newGroup;
// 注册到新组(不再强依赖 IsActive(),以保证编辑器批量赋值时也能正确注册)
if (newGroup != null)
{
if (!newGroup.ContainsToggle(this))
{
newGroup.RegisterToggle(this);
}
// 如果正在 on通知组维持单选逻辑
if (isOn)
newGroup.NotifyToggleOn(this);
// 同步组的内部状态,确保 Inspector 列表正确显示
newGroup.EnsureValidState();
}
}
public bool isOn
{
get { return m_IsOn; }
set { Set(value); }
}
public void SetIsOnWithoutNotify(bool value)
{
Set(value, false);
}
// 在 UXToggle 类内(建议放在类底部较靠近 Set 和 PlayEffect 的位置)加入:
// 覆盖 DoStateTransition保证 isOn 时视觉上总是 Selected除非 Disabled
protected override void DoStateTransition(Selectable.SelectionState state, bool instant)
{
// 如果被禁用,照常显示 Disabled
if (state == Selectable.SelectionState.Disabled)
{
base.DoStateTransition(state, instant);
return;
}
// 当 isOn 为 true 时,总是以 Selected 的样式渲染(但不改变 EventSystem 的真实 selection
if (m_IsOn)
state = Selectable.SelectionState.Selected;
base.DoStateTransition(state, instant);
}
void Set(bool value, bool sendCallback = true)
{
if (m_IsOn == value)
return;
m_IsOn = value;
if (m_Group != null && m_Group.isActiveAndEnabled && IsActive())
{
if (m_IsOn || (!m_Group.AnyTogglesOn() && !m_Group.allowSwitchOff))
{
m_IsOn = true;
m_Group.NotifyToggleOn(this, sendCallback);
}
}
PlayEffect(toggleTransition == Toggle.ToggleTransition.None);
if (sendCallback)
{
UISystemProfilerApi.AddMarker("Toggle.value", this);
onValueChanged.Invoke(m_IsOn);
}
// 触发选择态视觉刷新(当 isOn 为 true 显示 Selectedfalse 时回到 normal/hover/... 的计算结果)
// instant 参数:如果 ToggleTransition 是 None 我们使用 instant为保持和原来 PlayEffect 一致。
bool instant = (toggleTransition == Toggle.ToggleTransition.None);
// 传入的 state当 isOn 为 trueDoStateTransition 会将其替换为 Selected
// 当 isOn 为 false使用 currentSelectionState 以让正常的鼠标/键盘状态生效。
var stateToApply = m_IsOn ? Selectable.SelectionState.Selected : currentSelectionState;
DoStateTransition(stateToApply, instant);
}
private void PlayEffect(bool instant)
{
if (graphic == null)
return;
#if UNITY_EDITOR
if (!Application.isPlaying)
graphic.canvasRenderer.SetAlpha(m_IsOn ? 1f : 0f);
else
#endif
graphic.CrossFadeAlpha(m_IsOn ? 1f : 0f, instant ? 0f : 0.1f, true);
}
protected override void Start()
{
PlayEffect(true);
}
private void InternalToggle()
{
if (!IsActive() || !IsInteractable())
return;
isOn = !isOn;
}
public override void OnPointerEnter(PointerEventData eventData)
{
base.OnPointerEnter(eventData);
PlayAudio(hoverAudioClip);
}
public virtual void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
InternalToggle();
PlayAudio(clickAudioClip);
}
public virtual void OnSubmit(BaseEventData eventData)
{
InternalToggle();
PlayAudio(clickAudioClip);
}
private void PlayAudio(AudioClip clip)
{
if (clip && UXComponentExtensionsHelper.AudioHelper != null)
UXComponentExtensionsHelper.AudioHelper.PlayAudio(clip);
}
#if INPUTSYSTEM_SUPPORT
InputActionReference IHotkeyTrigger.HotkeyAction
{
get => _hotkeyAction;
set => _hotkeyAction = value;
}
EHotkeyPressType IHotkeyTrigger.HotkeyPressType
{
get => _hotkeyPressType;
set => _hotkeyPressType = value;
}
void IHotkeyTrigger.HotkeyActionTrigger()
{
if (interactable)
{
OnSubmit(null);
}
}
[SerializeField] internal InputActionReference _hotkeyAction;
[SerializeField] internal EHotkeyPressType _hotkeyPressType;
public InputActionReference HotKeyRefrence
{
get { return _hotkeyAction; }
}
#endif
[SerializeField] private AudioClip hoverAudioClip;
[SerializeField] private AudioClip clickAudioClip;
}
}