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.UI.Extension;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.Events;
|
|
|
|
|
using UnityEngine.EventSystems;
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
|
2025-04-11 17:26:28 +08:00
|
|
|
[Serializable]
|
|
|
|
|
public enum ButtonModeType
|
|
|
|
|
{
|
|
|
|
|
Normal,
|
|
|
|
|
Toggle
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
[Serializable]
|
2025-04-11 17:26:28 +08:00
|
|
|
public class TransitionData
|
|
|
|
|
{
|
|
|
|
|
public Graphic targetGraphic;
|
|
|
|
|
public Selectable.Transition transition = Selectable.Transition.ColorTint;
|
2025-10-13 20:20:01 +08:00
|
|
|
public ColorBlock colors = ColorBlock.defaultColorBlock;
|
2025-04-11 17:26:28 +08:00
|
|
|
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-10-13 20:20:01 +08:00
|
|
|
[SerializeField] private UnityEvent<bool> m_OnValueChanged = new();
|
2025-07-25 19:53:34 +08:00
|
|
|
|
2025-07-28 13:04:49 +08:00
|
|
|
#endregion
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
#region Private Fields
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-07-25 13:29:20 +08:00
|
|
|
[SerializeField] private SelectionState m_SelectionState = SelectionState.Normal;
|
2025-04-11 17:26:28 +08:00
|
|
|
private bool m_IsDown;
|
2025-10-13 20:20:01 +08:00
|
|
|
private bool m_HasExitedWhileDown;
|
|
|
|
|
private bool _mTogSelected;
|
2025-07-25 13:29:20 +08:00
|
|
|
private Animator _animator;
|
2025-07-28 13:04:49 +08:00
|
|
|
private Coroutine _resetRoutine;
|
2025-10-13 20:20:01 +08:00
|
|
|
private WaitForSeconds _waitFadeDuration;
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private static readonly Dictionary<string, int> _animTriggerCache = new()
|
|
|
|
|
{
|
|
|
|
|
{ "Normal", Animator.StringToHash("Normal") },
|
|
|
|
|
{ "Highlighted", Animator.StringToHash("Highlighted") },
|
|
|
|
|
{ "Pressed", Animator.StringToHash("Pressed") },
|
|
|
|
|
{ "Selected", Animator.StringToHash("Selected") },
|
|
|
|
|
{ "Disabled", Animator.StringToHash("Disabled") },
|
|
|
|
|
};
|
2025-07-28 13:04:49 +08:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Properties
|
2025-07-25 13:29:20 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private Animator Animator => _animator ? _animator : _animator = GetComponent<Animator>();
|
2025-08-08 20:56:31 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
public bool Interactable
|
2025-07-25 13:29:20 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
get => m_Interactable;
|
|
|
|
|
set
|
2025-07-25 13:29:20 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (m_Interactable == value) return;
|
|
|
|
|
m_Interactable = value;
|
|
|
|
|
SetState(m_Interactable ? SelectionState.Normal : SelectionState.Disabled);
|
2025-07-25 13:29:20 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
public bool Selected
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
get => _mTogSelected;
|
2025-04-11 17:26:28 +08:00
|
|
|
internal set
|
|
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (_mTogSelected == value) return;
|
|
|
|
|
_mTogSelected = value;
|
|
|
|
|
m_OnValueChanged?.Invoke(value);
|
|
|
|
|
SetState(value ? SelectionState.Selected : SelectionState.Normal);
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2025-10-13 20:20:01 +08:00
|
|
|
_waitFadeDuration = new WaitForSeconds(Mathf.Max(0.01f, m_TransitionData.colors.fadeDuration));
|
|
|
|
|
ApplyVisualState(m_SelectionState, true);
|
2025-07-29 17:34:54 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-28 13:04:49 +08:00
|
|
|
protected override void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
if (_resetRoutine != null)
|
|
|
|
|
StopCoroutine(_resetRoutine);
|
|
|
|
|
base.OnDestroy();
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-28 13:04:49 +08:00
|
|
|
#endregion
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
#region Pointer Handlers
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
public void OnPointerDown(PointerEventData eventData)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (!CanProcess(eventData)) return;
|
2025-04-11 17:26:28 +08:00
|
|
|
m_IsDown = true;
|
2025-10-13 20:20:01 +08:00
|
|
|
SetState(SelectionState.Pressed);
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
public void OnPointerUp(PointerEventData eventData)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
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-10-13 20:20:01 +08:00
|
|
|
var newState = _mTogSelected
|
|
|
|
|
? SelectionState.Selected
|
|
|
|
|
: m_HasExitedWhileDown
|
|
|
|
|
? SelectionState.Normal
|
|
|
|
|
: SelectionState.Highlighted;
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
SetState(newState);
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
public void OnPointerEnter(PointerEventData eventData)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (!CanProcess(eventData)) return;
|
|
|
|
|
m_HasExitedWhileDown = false;
|
2025-04-17 16:03:39 +08:00
|
|
|
if (m_IsDown) return;
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
SetState(SelectionState.Highlighted);
|
|
|
|
|
PlayAudio(hoverAudioClip);
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
public void OnPointerExit(PointerEventData eventData)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
|
|
|
|
if (!m_Interactable) return;
|
|
|
|
|
if (m_IsDown)
|
|
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
m_HasExitedWhileDown = true;
|
2025-04-11 17:26:28 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
SetState(_mTogSelected ? SelectionState.Selected : SelectionState.Normal);
|
2025-07-28 13:04:49 +08:00
|
|
|
}
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
public void OnPointerClick(PointerEventData eventData)
|
2025-07-28 13:04:49 +08:00
|
|
|
{
|
|
|
|
|
if (eventData.button != PointerEventData.InputButton.Left || !m_Interactable)
|
|
|
|
|
return;
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
PlayAudio(clickAudioClip);
|
|
|
|
|
HandleClick();
|
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-10-13 20:20:01 +08:00
|
|
|
SetState(SelectionState.Pressed);
|
|
|
|
|
HandleClick();
|
2025-07-28 13:04:49 +08:00
|
|
|
|
|
|
|
|
if (_resetRoutine != null)
|
2025-10-13 20:20:01 +08:00
|
|
|
StopCoroutine(_resetRoutine);
|
|
|
|
|
_resetRoutine = StartCoroutine(ResetAfterSubmit());
|
2025-07-28 13:04:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
#region Logic
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private bool CanProcess(PointerEventData eventData)
|
2025-07-28 13:04:49 +08:00
|
|
|
{
|
|
|
|
|
return m_Interactable &&
|
|
|
|
|
eventData.button == PointerEventData.InputButton.Left &&
|
2025-10-13 20:20:01 +08:00
|
|
|
!(m_Mode == ButtonModeType.Toggle && Selected);
|
2025-04-11 17:33:58 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private void HandleClick()
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
Selected = !Selected;
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private void SetState(SelectionState state)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (m_SelectionState == state) return;
|
|
|
|
|
m_SelectionState = state;
|
|
|
|
|
ApplyVisualState(state, false);
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private void ApplyVisualState(SelectionState state, bool instant)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
ApplyTransition(m_TransitionData, state, instant);
|
|
|
|
|
for (int i = 0; i < m_ChildTransitions.Count; i++)
|
|
|
|
|
ApplyTransition(m_ChildTransitions[i], state, instant);
|
|
|
|
|
}
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private void ApplyTransition(TransitionData data, SelectionState state, bool instant)
|
|
|
|
|
{
|
|
|
|
|
if (data.targetGraphic == null && data.transition != Selectable.Transition.Animation)
|
|
|
|
|
return;
|
2025-07-28 13:04:49 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
(Color color, Sprite sprite, string trigger) = state switch
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
SelectionState.Normal => (data.colors.normalColor, data.spriteState.highlightedSprite, data.animationTriggers.normalTrigger),
|
|
|
|
|
SelectionState.Highlighted => (data.colors.highlightedColor, data.spriteState.highlightedSprite, data.animationTriggers.highlightedTrigger),
|
|
|
|
|
SelectionState.Pressed => (data.colors.pressedColor, data.spriteState.pressedSprite, data.animationTriggers.pressedTrigger),
|
|
|
|
|
SelectionState.Selected => (data.colors.selectedColor, data.spriteState.selectedSprite, data.animationTriggers.selectedTrigger),
|
|
|
|
|
SelectionState.Disabled => (data.colors.disabledColor, data.spriteState.disabledSprite, data.animationTriggers.disabledTrigger),
|
|
|
|
|
_ => (Color.white, null, null)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (data.transition)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
|
|
|
|
case Selectable.Transition.ColorTint:
|
2025-10-13 20:20:01 +08:00
|
|
|
TweenColor(data, color * data.colors.colorMultiplier, instant);
|
2025-04-11 17:26:28 +08:00
|
|
|
break;
|
|
|
|
|
case Selectable.Transition.SpriteSwap:
|
2025-10-13 20:20:01 +08:00
|
|
|
SwapSprite(data, sprite);
|
2025-04-11 17:26:28 +08:00
|
|
|
break;
|
2025-07-25 13:29:20 +08:00
|
|
|
case Selectable.Transition.Animation:
|
2025-10-13 20:20:01 +08:00
|
|
|
PlayAnimation(trigger);
|
2025-07-25 13:29:20 +08:00
|
|
|
break;
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private void TweenColor(TransitionData data, Color color, bool instant)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
data.targetGraphic.CrossFadeColor(color, instant ? 0f : data.colors.fadeDuration, true, true);
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private static void SwapSprite(TransitionData data, Sprite sprite)
|
2025-04-11 17:26:28 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (data.targetGraphic is Image img)
|
|
|
|
|
img.overrideSprite = sprite;
|
2025-04-11 17:26:28 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private void PlayAnimation(string trigger)
|
2025-07-25 13:29:20 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (!Animator || !Animator.isActiveAndEnabled || string.IsNullOrEmpty(trigger))
|
2025-07-25 13:29:20 +08:00
|
|
|
return;
|
|
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
foreach (int id in _animTriggerCache.Values)
|
|
|
|
|
Animator.ResetTrigger(id);
|
2025-07-25 13:29:20 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
if (_animTriggerCache.TryGetValue(trigger, out int hash))
|
|
|
|
|
Animator.SetTrigger(hash);
|
2025-07-25 13:29:20 +08:00
|
|
|
}
|
|
|
|
|
|
2025-04-11 17:26:28 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private void PlayAudio(AudioClip clip)
|
2025-07-28 13:04:49 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
if (clip && UXComponentExtensionsHelper.AudioHelper != null)
|
|
|
|
|
UXComponentExtensionsHelper.AudioHelper.PlayAudio(clip);
|
2025-07-28 13:04:49 +08:00
|
|
|
}
|
2025-04-17 16:03:39 +08:00
|
|
|
|
2025-10-13 20:20:01 +08:00
|
|
|
private IEnumerator ResetAfterSubmit()
|
2025-04-17 16:03:39 +08:00
|
|
|
{
|
2025-10-13 20:20:01 +08:00
|
|
|
yield return _waitFadeDuration;
|
|
|
|
|
SetState(_mTogSelected ? SelectionState.Selected : SelectionState.Normal);
|
2025-07-28 13:04:49 +08:00
|
|
|
_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
|
|
|
}
|