com.alicizax.unity.ui.exten.../Runtime/UXComponent/UX/UXButton.cs

437 lines
12 KiB
C#
Raw Normal View History

2025-04-11 17:26:28 +08:00
using System;
2025-07-28 13:04:49 +08:00
using System.Collections;
2025-04-11 17:26:28 +08:00
using System.Collections.Generic;
using AlicizaX;
using AlicizaX.UI.Extension;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
2025-08-08 16:25:09 +08:00
using UnityEngine.Serialization;
2025-04-11 17:26:28 +08:00
using UnityEngine.UI;
[Serializable]
public enum ButtonModeType
{
Normal,
Toggle
}
[System.Serializable]
public class TransitionData
{
public Graphic targetGraphic;
public Selectable.Transition transition = Selectable.Transition.ColorTint;
public ColorBlock colors;
public SpriteState spriteState;
2025-07-28 13:04:49 +08:00
public AnimationTriggers animationTriggers = new();
2025-04-11 17:26:28 +08:00
}
internal enum SelectionState
{
Normal,
Highlighted,
Pressed,
Selected,
Disabled,
}
2025-07-29 11:28:24 +08:00
2025-07-28 13:04:49 +08:00
[ExecuteInEditMode]
2025-04-11 17:26:28 +08:00
[DisallowMultipleComponent]
2025-04-17 16:03:39 +08:00
public class UXButton : UIBehaviour, IButton,
2025-07-28 13:04:49 +08:00
IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler,
IPointerExitHandler, IPointerClickHandler, ISubmitHandler
2025-04-11 17:26:28 +08:00
{
2025-07-28 13:04:49 +08:00
#region Serialized Fields
2025-04-11 17:26:28 +08:00
2025-07-28 13:04:49 +08:00
[SerializeField] private bool m_Interactable = true;
2025-04-11 17:26:28 +08:00
[SerializeField] private ButtonModeType m_Mode;
2025-07-28 13:04:49 +08:00
[SerializeField] private Button.ButtonClickedEvent m_OnClick = new();
[SerializeField] private TransitionData m_TransitionData = new();
[SerializeField] private List<TransitionData> m_ChildTransitions = new();
2025-04-11 17:26:28 +08:00
[SerializeField] private UXGroup m_UXGroup;
2025-07-25 19:53:34 +08:00
[SerializeField] private AudioClip hoverAudioClip;
[SerializeField] private AudioClip clickAudioClip;
2025-08-08 16:25:09 +08:00
#if INPUTSYSTEM_SUPPORT
[SerializeField] internal UnityEngine.InputSystem.InputActionReference _hotKeyRefrence;
[SerializeField] internal EHotkeyPressType _hotkeyPressType;
internal void OnHotkeyTriggered()
{
if (m_Interactable)
{
OnSubmit(null);
}
}
#endif
2025-07-25 19:53:34 +08:00
2025-07-28 13:04:49 +08:00
#endregion
#region Private Variables
2025-07-25 13:29:20 +08:00
[SerializeField] private SelectionState m_SelectionState = SelectionState.Normal;
2025-04-17 16:03:39 +08:00
private bool m_DownAndExistUI;
2025-04-11 17:26:28 +08:00
private bool m_IsDown;
private bool m_IsTogSelected;
2025-07-25 13:29:20 +08:00
private Animator _animator;
2025-07-28 13:04:49 +08:00
private WaitForSeconds _waitTimeFadeDuration;
private Coroutine _resetRoutine;
private readonly Dictionary<string, int> _animTriggerIDs = new();
private readonly Dictionary<string, int> _animResetTriggerIDs = new();
#endregion
#region Properties
2025-07-25 13:29:20 +08:00
2025-08-08 20:56:31 +08:00
internal bool Interactable
{
get => m_Interactable;
}
2025-07-25 13:29:20 +08:00
internal Animator animator
{
get
{
2025-07-28 13:04:49 +08:00
if (!_animator)
_animator = GetComponent<Animator>();
2025-07-25 13:29:20 +08:00
return _animator;
}
}
2025-04-11 17:26:28 +08:00
public bool IsSelected
{
2025-07-28 13:04:49 +08:00
get => m_IsTogSelected;
2025-04-11 17:26:28 +08:00
internal set
{
2025-07-28 13:04:49 +08:00
if (m_IsTogSelected == value) return;
2025-04-11 17:26:28 +08:00
m_IsTogSelected = value;
onValueChanged?.Invoke(m_IsTogSelected);
2025-07-28 13:04:49 +08:00
m_SelectionState = value ? SelectionState.Selected : SelectionState.Normal;
2025-04-11 17:26:28 +08:00
UpdateVisualState(m_SelectionState, false);
}
}
2025-04-17 16:03:39 +08:00
public Button.ButtonClickedEvent onClick
2025-04-11 17:26:28 +08:00
{
2025-07-28 13:04:49 +08:00
get => m_OnClick;
set => m_OnClick = value;
2025-04-11 17:26:28 +08:00
}
2025-07-28 13:04:49 +08:00
[SerializeField] private UnityEvent<bool> m_OnValueChanged = new();
2025-04-11 17:26:28 +08:00
public UnityEvent<bool> onValueChanged
{
2025-07-28 13:04:49 +08:00
get => m_OnValueChanged;
set => m_OnValueChanged = value;
2025-04-11 17:26:28 +08:00
}
2025-07-28 13:04:49 +08:00
#endregion
#region Unity Lifecycle
2025-07-29 17:34:54 +08:00
protected override void Awake()
{
base.Awake();
Initlize();
}
2025-07-29 17:25:18 +08:00
#if UNITY_EDITOR
protected override void OnValidate()
2025-04-11 17:26:28 +08:00
{
2025-07-29 17:25:18 +08:00
base.OnValidate();
if (!Application.isPlaying)
{
2025-07-29 17:34:54 +08:00
Initlize();
2025-07-29 17:25:18 +08:00
}
}
2025-07-28 13:04:49 +08:00
2025-07-29 17:25:18 +08:00
#endif // if UNITY_EDITOR
2025-07-28 13:04:49 +08:00
2025-07-29 17:34:54 +08:00
protected void Initlize()
{
_waitTimeFadeDuration = new WaitForSeconds(
Mathf.Max(0.01f, m_TransitionData.colors.fadeDuration));
_animTriggerIDs.Clear();
_animResetTriggerIDs.Clear();
var triggers = m_TransitionData.animationTriggers;
AddTriggerID(triggers.normalTrigger);
AddTriggerID(triggers.highlightedTrigger);
AddTriggerID(triggers.pressedTrigger);
AddTriggerID(triggers.selectedTrigger);
AddTriggerID(triggers.disabledTrigger);
UpdateVisualState(m_SelectionState, true);
}
2025-07-28 13:04:49 +08:00
protected override void OnDestroy()
{
if (_resetRoutine != null)
2025-04-11 17:26:28 +08:00
{
2025-07-28 13:04:49 +08:00
StopCoroutine(_resetRoutine);
_resetRoutine = null;
2025-04-11 17:26:28 +08:00
}
2025-07-25 13:29:20 +08:00
2025-07-28 13:04:49 +08:00
base.OnDestroy();
2025-04-11 17:26:28 +08:00
}
2025-07-28 13:04:49 +08:00
#endregion
#region Event Handlers
2025-04-11 17:26:28 +08:00
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
{
2025-07-28 13:04:49 +08:00
if (!ShouldProcessEvent(eventData)) return;
2025-04-11 17:26:28 +08:00
m_IsDown = true;
m_SelectionState = SelectionState.Pressed;
UpdateVisualState(m_SelectionState, false);
}
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
{
2025-07-28 13:04:49 +08:00
if (!m_Interactable || eventData.button != PointerEventData.InputButton.Left)
return;
2025-04-17 16:03:39 +08:00
2025-04-11 17:26:28 +08:00
m_IsDown = false;
2025-07-28 13:04:49 +08:00
if (m_IsTogSelected)
2025-04-11 17:26:28 +08:00
{
2025-07-28 13:04:49 +08:00
m_SelectionState = SelectionState.Selected;
2025-04-11 17:26:28 +08:00
}
else
{
2025-07-28 13:04:49 +08:00
m_SelectionState = m_DownAndExistUI ? SelectionState.Normal : SelectionState.Highlighted;
2025-04-11 17:26:28 +08:00
}
2025-07-28 13:04:49 +08:00
UpdateVisualState(m_SelectionState, false);
2025-04-11 17:26:28 +08:00
}
void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
{
2025-07-28 13:04:49 +08:00
if (!ShouldProcessEvent(eventData)) return;
2025-04-17 16:03:39 +08:00
m_DownAndExistUI = false;
if (m_IsDown) return;
2025-07-28 13:04:49 +08:00
m_SelectionState = SelectionState.Highlighted;
2025-04-11 17:26:28 +08:00
UpdateVisualState(m_SelectionState, false);
2025-07-25 19:53:34 +08:00
PlayButtonSound(hoverAudioClip);
2025-04-11 17:26:28 +08:00
}
void IPointerExitHandler.OnPointerExit(PointerEventData eventData)
{
if (!m_Interactable) return;
if (m_IsDown)
{
2025-04-17 16:03:39 +08:00
m_DownAndExistUI = true;
2025-04-11 17:26:28 +08:00
return;
}
2025-07-28 13:04:49 +08:00
m_SelectionState = IsSelected ? SelectionState.Selected : SelectionState.Normal;
UpdateVisualState(m_SelectionState, false);
}
2025-04-11 17:26:28 +08:00
2025-07-28 13:04:49 +08:00
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left || !m_Interactable)
return;
2025-04-11 17:26:28 +08:00
2025-07-28 13:04:49 +08:00
PlayButtonSound(clickAudioClip);
ProcessClick();
2025-04-11 17:26:28 +08:00
}
2025-08-08 16:25:09 +08:00
public void OnSubmit(BaseEventData eventData)
2025-04-11 17:26:28 +08:00
{
2025-07-28 13:04:49 +08:00
UpdateVisualState(SelectionState.Pressed, false);
ProcessClick();
if (_resetRoutine != null)
StopCoroutine(OnFinishSubmit());
_resetRoutine = StartCoroutine(OnFinishSubmit());
2025-04-11 17:26:28 +08:00
}
2025-07-28 13:04:49 +08:00
#endregion
#region Public Methods
2025-08-05 11:12:42 +08:00
public bool Selected
2025-04-11 17:33:58 +08:00
{
2025-08-05 11:12:42 +08:00
get => IsSelected;
set
{
if (m_Mode == ButtonModeType.Toggle)
{
ProcessClick();
}
}
2025-07-28 13:04:49 +08:00
}
#endregion
#region Private Methods
private bool ShouldProcessEvent(PointerEventData eventData)
{
return m_Interactable &&
eventData.button == PointerEventData.InputButton.Left &&
!(m_Mode == ButtonModeType.Toggle && IsSelected);
2025-04-11 17:33:58 +08:00
}
2025-04-11 17:26:28 +08:00
private void ProcessClick()
{
if (m_Mode == ButtonModeType.Normal)
{
2025-04-17 16:03:39 +08:00
UISystemProfilerApi.AddMarker("Button.onClick", this);
2025-07-28 13:04:49 +08:00
m_OnClick?.Invoke();
}
else if (m_UXGroup)
{
m_UXGroup.NotifyButtonClicked(this);
2025-04-11 17:26:28 +08:00
}
else
{
IsSelected = !IsSelected;
}
}
private void UpdateVisualState(SelectionState state, bool instant)
{
ProcessTransitionData(m_TransitionData, state, instant);
foreach (var transition in m_ChildTransitions)
{
ProcessTransitionData(transition, state, instant);
}
}
private void ProcessTransitionData(TransitionData transition, SelectionState state, bool instant)
{
2025-07-29 17:25:18 +08:00
if ((transition.transition == Selectable.Transition.ColorTint || transition.transition == Selectable.Transition.SpriteSwap) && transition.targetGraphic == null) return;
2025-04-11 17:26:28 +08:00
Color tintColor;
Sprite transitionSprite;
2025-07-25 13:29:20 +08:00
string triggerName;
2025-07-28 13:04:49 +08:00
2025-04-11 17:26:28 +08:00
switch (state)
{
case SelectionState.Normal:
tintColor = transition.colors.normalColor;
2025-07-28 13:04:49 +08:00
transitionSprite = transition.spriteState.highlightedSprite;
2025-07-25 13:29:20 +08:00
triggerName = transition.animationTriggers.normalTrigger;
2025-04-11 17:26:28 +08:00
break;
case SelectionState.Highlighted:
tintColor = transition.colors.highlightedColor;
transitionSprite = transition.spriteState.highlightedSprite;
2025-07-25 13:29:20 +08:00
triggerName = transition.animationTriggers.highlightedTrigger;
2025-04-11 17:26:28 +08:00
break;
case SelectionState.Pressed:
tintColor = transition.colors.pressedColor;
transitionSprite = transition.spriteState.pressedSprite;
2025-07-25 13:29:20 +08:00
triggerName = transition.animationTriggers.pressedTrigger;
2025-04-11 17:26:28 +08:00
break;
case SelectionState.Selected:
tintColor = transition.colors.selectedColor;
transitionSprite = transition.spriteState.selectedSprite;
2025-07-25 13:29:20 +08:00
triggerName = transition.animationTriggers.selectedTrigger;
2025-04-11 17:26:28 +08:00
break;
case SelectionState.Disabled:
tintColor = transition.colors.disabledColor;
transitionSprite = transition.spriteState.disabledSprite;
2025-07-25 13:29:20 +08:00
triggerName = transition.animationTriggers.disabledTrigger;
2025-04-11 17:26:28 +08:00
break;
default:
2025-07-28 13:04:49 +08:00
return;
2025-04-11 17:26:28 +08:00
}
switch (transition.transition)
{
case Selectable.Transition.ColorTint:
StartColorTween(transition, tintColor * transition.colors.colorMultiplier, instant);
break;
case Selectable.Transition.SpriteSwap:
DoSpriteSwap(transition, transitionSprite);
break;
2025-07-25 13:29:20 +08:00
case Selectable.Transition.Animation:
2025-07-28 13:04:49 +08:00
TriggerAnimation(triggerName);
2025-07-25 13:29:20 +08:00
break;
2025-04-11 17:26:28 +08:00
}
}
2025-07-28 13:04:49 +08:00
private void StartColorTween(TransitionData data, Color targetColor, bool instant)
2025-04-11 17:26:28 +08:00
{
if (Application.isPlaying)
{
2025-07-28 13:04:49 +08:00
data.targetGraphic.CrossFadeColor(
targetColor,
instant ? 0f : data.colors.fadeDuration,
true,
true
);
2025-04-11 17:26:28 +08:00
}
else
{
2025-07-28 13:04:49 +08:00
data.targetGraphic.canvasRenderer.SetColor(targetColor);
2025-04-11 17:26:28 +08:00
}
}
2025-07-28 13:04:49 +08:00
private void DoSpriteSwap(TransitionData data, Sprite newSprite)
2025-04-11 17:26:28 +08:00
{
2025-07-28 13:04:49 +08:00
if (data.targetGraphic is Image image)
2025-04-11 17:26:28 +08:00
{
image.overrideSprite = newSprite;
}
}
2025-07-28 13:04:49 +08:00
private void TriggerAnimation(string trigger)
2025-07-25 13:29:20 +08:00
{
2025-07-28 13:04:49 +08:00
if (animator == null ||
!animator.isActiveAndEnabled ||
!animator.hasBoundPlayables ||
string.IsNullOrEmpty(trigger))
2025-07-25 13:29:20 +08:00
return;
2025-07-28 13:04:49 +08:00
foreach (var resetTrigger in _animResetTriggerIDs.Keys)
{
animator.ResetTrigger(_animTriggerIDs[resetTrigger]);
}
2025-07-25 13:29:20 +08:00
2025-07-28 13:04:49 +08:00
if (_animTriggerIDs.TryGetValue(trigger, out int id))
{
animator.SetTrigger(id);
}
2025-07-25 13:29:20 +08:00
}
2025-07-28 13:04:49 +08:00
private void AddTriggerID(string triggerName)
2025-04-11 17:26:28 +08:00
{
2025-07-28 13:04:49 +08:00
if (!string.IsNullOrEmpty(triggerName))
{
int id = Animator.StringToHash(triggerName);
if (!_animTriggerIDs.ContainsKey(triggerName))
{
_animTriggerIDs.Add(triggerName, id);
_animResetTriggerIDs.Add(triggerName, id);
}
}
2025-04-11 17:26:28 +08:00
}
2025-07-28 13:04:49 +08:00
private void PlayButtonSound(AudioClip clip)
{
2025-08-01 19:32:36 +08:00
if (clip == null || UXComponentExtensionsHelper.AudioHelper == null) return;
UXComponentExtensionsHelper.AudioHelper.PlayAudio(clip);
2025-07-28 13:04:49 +08:00
}
2025-04-17 16:03:39 +08:00
2025-07-28 13:04:49 +08:00
private IEnumerator OnFinishSubmit()
2025-04-17 16:03:39 +08:00
{
2025-07-28 13:04:49 +08:00
yield return _waitTimeFadeDuration;
UpdateVisualState(
m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal,
false
);
_resetRoutine = null;
2025-04-17 16:03:39 +08:00
}
2025-07-28 13:04:49 +08:00
#endregion
2025-04-11 17:26:28 +08:00
}