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 } [System.Serializable] public class TransitionData { public Graphic targetGraphic; public Selectable.Transition transition = Selectable.Transition.ColorTint; public ColorBlock colors; public SpriteState spriteState; } [Serializable] public struct ButtonSoundData { public ButtonSoundType ButtonSoundType; public string ButtonUISoundName; } internal enum SelectionState { /// /// The UI object can be selected. /// Normal, /// /// The UI object is highlighted. /// Highlighted, /// /// The UI object is pressed. /// Pressed, /// /// The UI object is selected /// Selected, /// /// The UI object cannot be selected. /// Disabled, } [DisallowMultipleComponent] [RequireComponent(typeof(Graphic))] public class UXButton : UIBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler { [SerializeField] private bool m_Interactable = true; [SerializeField] private ButtonModeType m_Mode; [SerializeField] private UnityEvent m_OnClick = new UnityEvent(); [SerializeField] private TransitionData m_TransitionData = new TransitionData(); [SerializeField] private List m_ChildTransitions = new List(); [SerializeField] private UXGroup m_UXGroup; [SerializeField] private List m_ButtonUISounds = new List(); private SelectionState m_SelectionState = SelectionState.Normal; private bool m_ExistUI; private bool m_IsDown; private bool m_IsTogSelected; 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); } } public UnityEvent onClick { get { return m_OnClick; } set { m_OnClick = value; } } [SerializeField] private UnityEvent m_OnValueChanged = new UnityEvent(); public UnityEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } protected override void Awake() { base.Awake(); if (m_Mode == ButtonModeType.Toggle) { onValueChanged?.Invoke(IsSelected); } } void IPointerDownHandler.OnPointerDown(PointerEventData eventData) { if (!m_Interactable) return; m_IsDown = true; m_SelectionState = SelectionState.Pressed; UpdateVisualState(m_SelectionState, false); PlayButtonSound(ButtonSoundType.Down); } void IPointerUpHandler.OnPointerUp(PointerEventData eventData) { if (!m_Interactable) return; m_IsDown = false; if (!m_IsTogSelected) { m_SelectionState = m_ExistUI ? SelectionState.Normal : SelectionState.Highlighted; UpdateVisualState(m_SelectionState, false); } else { m_SelectionState = SelectionState.Selected; UpdateVisualState(m_SelectionState, false); } if (!m_ExistUI) { ProcessClick(); } PlayButtonSound(ButtonSoundType.Up); } void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData) { if (!m_Interactable || CantTouch()) return; m_SelectionState = SelectionState.Highlighted; m_ExistUI = false; UpdateVisualState(m_SelectionState, false); PlayButtonSound(ButtonSoundType.Enter); } void IPointerExitHandler.OnPointerExit(PointerEventData eventData) { if (!m_Interactable) return; if (m_IsDown) { m_ExistUI = true; return; } if (CantTouch()) { return; } m_SelectionState = SelectionState.Normal; UpdateVisualState(m_SelectionState, false); PlayButtonSound(ButtonSoundType.Exit); } private bool CantTouch() { return m_Mode == ButtonModeType.Toggle && m_IsTogSelected; } public void SetSelect(bool state, bool boardEvent = false) { if (m_Mode != ButtonModeType.Toggle) return; m_IsTogSelected = state; if (boardEvent) onValueChanged?.Invoke(m_IsTogSelected); m_SelectionState = m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal; UpdateVisualState(m_SelectionState, false); } private void ProcessClick() { if (m_Mode == ButtonModeType.Normal) { 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; switch (state) { case SelectionState.Normal: tintColor = transition.colors.normalColor; transitionSprite = null; break; case SelectionState.Highlighted: tintColor = transition.colors.highlightedColor; transitionSprite = transition.spriteState.highlightedSprite; break; case SelectionState.Pressed: tintColor = transition.colors.pressedColor; transitionSprite = transition.spriteState.pressedSprite; break; case SelectionState.Selected: tintColor = transition.colors.selectedColor; transitionSprite = transition.spriteState.selectedSprite; break; case SelectionState.Disabled: tintColor = transition.colors.disabledColor; transitionSprite = transition.spriteState.disabledSprite; break; default: tintColor = Color.black; transitionSprite = null; break; } switch (transition.transition) { case Selectable.Transition.ColorTint: StartColorTween(transition, tintColor * transition.colors.colorMultiplier, instant); break; case Selectable.Transition.SpriteSwap: DoSpriteSwap(transition, transitionSprite); break; } } 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}"); } } protected void PlayButtonSound(ButtonSoundType buttonSoundType) { ButtonSoundCell buttonSound = GetButtonSound(buttonSoundType); if (buttonSound == null || string.IsNullOrEmpty(buttonSound.ButtonUISoundName)) { return; } GameApp.Audio.Play(AudioType.UISound, buttonSound.ButtonUISoundName, false, GameApp.Audio.UISoundVolume, true); } protected ButtonSoundCell GetButtonSound(ButtonSoundType buttonSoundType) { foreach (var buttonSound in m_ButtonUISounds) { if (buttonSound.ButtonSoundType == buttonSoundType) { return buttonSound; } } return null; } }