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

326 lines
9.5 KiB
C#
Raw Normal View History

2025-04-11 17:26:28 +08:00
using System;
using System.Collections.Generic;
using AlicizaX;
using AlicizaX.UI.Extension;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
using AudioType = AlicizaX.Audio.Runtime.AudioType;
[Serializable]
public enum ButtonModeType
{
Normal,
Toggle
}
2025-04-17 16:38:11 +08:00
2025-04-11 17:26:28 +08:00
[System.Serializable]
public class TransitionData
{
public Graphic targetGraphic;
public Selectable.Transition transition = Selectable.Transition.ColorTint;
public ColorBlock colors;
public SpriteState spriteState;
2025-07-25 13:29:20 +08:00
public AnimationTriggers animationTriggers = new AnimationTriggers();
2025-04-11 17:26:28 +08:00
}
internal enum SelectionState
{
Normal,
Highlighted,
Pressed,
Selected,
Disabled,
}
[DisallowMultipleComponent]
2025-04-17 16:03:39 +08:00
public class UXButton : UIBehaviour, IButton,
IPointerDownHandler,
IPointerUpHandler,
IPointerEnterHandler,
IPointerExitHandler,
IPointerClickHandler
2025-04-11 17:26:28 +08:00
{
[SerializeField] private bool m_Interactable = true;
[SerializeField] private ButtonModeType m_Mode;
2025-04-17 16:03:39 +08:00
[SerializeField] private Button.ButtonClickedEvent m_OnClick = new Button.ButtonClickedEvent();
2025-04-11 17:26:28 +08:00
[SerializeField] private TransitionData m_TransitionData = new TransitionData();
[SerializeField] private List<TransitionData> m_ChildTransitions = new List<TransitionData>();
[SerializeField] private UXGroup m_UXGroup;
2025-07-25 19:53:34 +08:00
[SerializeField] private AudioClip hoverAudioClip;
[SerializeField] private AudioClip clickAudioClip;
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;
internal Animator animator
{
get
{
_animator = _animator ?? GetComponent<Animator>();
return _animator;
}
}
2025-04-11 17:26:28 +08:00
public bool IsSelected
{
get { return m_IsTogSelected; }
internal set
{
m_IsTogSelected = value;
onValueChanged?.Invoke(m_IsTogSelected);
m_SelectionState = m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal;
UpdateVisualState(m_SelectionState, false);
}
}
2025-04-17 16:03:39 +08:00
public Button.ButtonClickedEvent onClick
2025-04-11 17:26:28 +08:00
{
get { return m_OnClick; }
set { m_OnClick = value; }
}
[SerializeField] private UnityEvent<bool> m_OnValueChanged = new UnityEvent<bool>();
public UnityEvent<bool> onValueChanged
{
get { return m_OnValueChanged; }
set { m_OnValueChanged = value; }
}
protected override void Awake()
{
base.Awake();
if (m_Mode == ButtonModeType.Toggle)
{
onValueChanged?.Invoke(IsSelected);
}
2025-07-25 13:29:20 +08:00
UpdateVisualState(m_SelectionState, true);
2025-04-11 17:26:28 +08:00
}
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
{
if (!m_Interactable) return;
2025-04-17 16:03:39 +08:00
if (eventData.button != PointerEventData.InputButton.Left) 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)
{
if (!m_Interactable) return;
2025-04-17 16:03:39 +08:00
2025-04-11 17:26:28 +08:00
m_IsDown = false;
if (!m_IsTogSelected)
{
2025-04-17 16:03:39 +08:00
m_SelectionState = m_DownAndExistUI ? SelectionState.Normal : SelectionState.Highlighted;
2025-04-11 17:26:28 +08:00
UpdateVisualState(m_SelectionState, false);
}
else
{
m_SelectionState = SelectionState.Selected;
UpdateVisualState(m_SelectionState, false);
}
}
void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
{
if (!m_Interactable || CantTouch()) return;
2025-04-17 16:03:39 +08:00
2025-04-11 17:26:28 +08:00
m_SelectionState = SelectionState.Highlighted;
2025-04-17 16:03:39 +08:00
m_DownAndExistUI = false;
if (m_IsDown) return;
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;
}
if (CantTouch())
{
return;
}
m_SelectionState = SelectionState.Normal;
UpdateVisualState(m_SelectionState, false);
}
private bool CantTouch()
{
return m_Mode == ButtonModeType.Toggle && m_IsTogSelected;
}
2025-04-15 17:27:57 +08:00
public void SetSelect(bool state, bool boardEvent = false)
2025-04-11 17:33:58 +08:00
{
if (m_Mode != ButtonModeType.Toggle) return;
2025-04-15 17:27:57 +08:00
m_IsTogSelected = state;
if (boardEvent) onValueChanged?.Invoke(m_IsTogSelected);
m_SelectionState = m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal;
UpdateVisualState(m_SelectionState, false);
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-04-11 17:26:28 +08:00
onClick?.Invoke();
}
else
{
if (m_UXGroup)
{
m_UXGroup.NotifyButtonClicked(this);
return;
}
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)
{
if (transition.targetGraphic == null) return;
Color tintColor;
Sprite transitionSprite;
2025-07-25 13:29:20 +08:00
string triggerName;
2025-04-11 17:26:28 +08:00
switch (state)
{
case SelectionState.Normal:
tintColor = transition.colors.normalColor;
transitionSprite = null;
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:
tintColor = Color.black;
transitionSprite = null;
2025-07-25 13:29:20 +08:00
triggerName = string.Empty;
2025-04-11 17:26:28 +08:00
break;
}
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:
TriggerAnimation(transition.animationTriggers, triggerName);
break;
2025-04-11 17:26:28 +08:00
}
}
protected void StartColorTween(TransitionData transitionData, Color targetColor, bool instant)
{
if (Application.isPlaying)
{
transitionData.targetGraphic.CrossFadeColor(targetColor, instant ? 0f : transitionData.colors.fadeDuration, true, true);
}
else
{
transitionData.targetGraphic.canvasRenderer.SetColor(targetColor);
}
}
protected void DoSpriteSwap(TransitionData transitionData, Sprite newSprite)
{
if (transitionData.targetGraphic is Image image)
{
image.overrideSprite = newSprite;
}
else if (transitionData.targetGraphic != null)
{
Log.Error($"Target Graphic must be Image for SpriteSwap. Object: {transitionData.targetGraphic.name}");
}
}
2025-07-25 13:29:20 +08:00
void TriggerAnimation(AnimationTriggers animationTriggers, string triggername)
{
if (animator == null || !animator.isActiveAndEnabled || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername))
return;
animator.ResetTrigger(animationTriggers.normalTrigger);
animator.ResetTrigger(animationTriggers.highlightedTrigger);
animator.ResetTrigger(animationTriggers.pressedTrigger);
animator.ResetTrigger(animationTriggers.selectedTrigger);
animator.ResetTrigger(animationTriggers.disabledTrigger);
animator.SetTrigger(triggername);
}
2025-07-25 19:53:34 +08:00
protected void PlayButtonSound(AudioClip clip)
2025-04-11 17:26:28 +08:00
{
2025-07-25 19:53:34 +08:00
GameApp.Audio?.Play(AudioType.UISound, clip, false, GameApp.Audio.UISoundVolume);
2025-04-11 17:26:28 +08:00
}
2025-04-17 16:03:39 +08:00
public void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
2025-07-25 19:53:34 +08:00
PlayButtonSound(clickAudioClip);
2025-04-17 16:03:39 +08:00
ProcessClick();
}
2025-04-11 17:26:28 +08:00
}