143 lines
5.9 KiB
C#
143 lines
5.9 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.EventSystems;
|
|
using BaseGames.Core.Events;
|
|
using BaseGames.UI.Theme;
|
|
|
|
namespace BaseGames.UI
|
|
{
|
|
/// <summary>按钮视觉变体(驱动从主题派生的 <see cref="ColorBlock"/>)。</summary>
|
|
public enum UIButtonVariant { Primary, Secondary, Accent, Danger, Ghost }
|
|
|
|
/// <summary>
|
|
/// 主题化按钮变体 + 交互反馈。薄封装在 uGUI <see cref="Button"/> 之上:
|
|
/// - 按变体从 <see cref="UIThemeSO"/> 取基色,派生 normal/highlighted/pressed/disabled 并写入 Button.colors。
|
|
/// - 选中/悬停时缩放反馈(走 <see cref="UITween.Scale"/>)。
|
|
/// - 可选点击/悬停音效(事件频道驱动,不直接耦合 Audio 程序集)。
|
|
///
|
|
/// 主题来源:优先 Inspector 直接赋的 <see cref="_theme"/>,否则就近向上找 <see cref="UIThemeApplier"/>。
|
|
/// </summary>
|
|
[RequireComponent(typeof(Button))]
|
|
[DisallowMultipleComponent]
|
|
public class UIButton : MonoBehaviour,
|
|
ISelectHandler, IDeselectHandler, IPointerEnterHandler, IPointerExitHandler
|
|
{
|
|
[Header("变体")]
|
|
[SerializeField] private UIButtonVariant _variant = UIButtonVariant.Primary;
|
|
[Tooltip("主题资产;为空则就近向上查找 UIThemeApplier。")]
|
|
[SerializeField] private UIThemeSO _theme;
|
|
|
|
[Header("反馈")]
|
|
[Tooltip("选中/悬停时的缩放反馈。")]
|
|
[SerializeField] private bool _scaleFeedback = true;
|
|
[SerializeField] private float _selectedScale = 1.06f;
|
|
[SerializeField] private float _scaleDuration = 0.08f;
|
|
|
|
[Header("音效(可选,事件频道驱动)")]
|
|
[SerializeField] private VoidEventChannelSO _onClickSfx;
|
|
[SerializeField] private VoidEventChannelSO _onHoverSfx;
|
|
|
|
private Button _button;
|
|
private Coroutine _scaleRoutine;
|
|
private Vector3 _baseScale = Vector3.one;
|
|
|
|
private void Awake()
|
|
{
|
|
_button = GetComponent<Button>();
|
|
_baseScale = transform.localScale;
|
|
_button.onClick.AddListener(RaiseClickSfx);
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
ApplyTheme();
|
|
transform.localScale = _baseScale;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (_scaleRoutine != null) { StopCoroutine(_scaleRoutine); _scaleRoutine = null; }
|
|
transform.localScale = _baseScale;
|
|
}
|
|
|
|
// ── 主题应用 ──────────────────────────────────────────────────────────
|
|
/// <summary>按当前变体与主题刷新 Button 配色。</summary>
|
|
public void ApplyTheme()
|
|
{
|
|
var theme = ResolveTheme();
|
|
if (theme == null) return;
|
|
if (_button == null) _button = GetComponent<Button>();
|
|
if (_button == null) return;
|
|
|
|
Color baseColor = VariantBaseColor(theme, _variant);
|
|
var cb = _button.colors;
|
|
cb.colorMultiplier = 1f;
|
|
cb.fadeDuration = 0.1f;
|
|
cb.disabledColor = theme.ButtonDisabled;
|
|
|
|
if (_variant == UIButtonVariant.Ghost)
|
|
{
|
|
cb.normalColor = new Color(baseColor.r, baseColor.g, baseColor.b, 0f);
|
|
cb.highlightedColor = new Color(baseColor.r, baseColor.g, baseColor.b, 0.25f);
|
|
cb.pressedColor = new Color(baseColor.r, baseColor.g, baseColor.b, 0.40f);
|
|
cb.selectedColor = cb.highlightedColor;
|
|
}
|
|
else
|
|
{
|
|
cb.normalColor = baseColor;
|
|
cb.highlightedColor = Color.Lerp(baseColor, Color.white, 0.12f);
|
|
cb.pressedColor = Color.Lerp(baseColor, Color.black, 0.15f);
|
|
cb.selectedColor = cb.highlightedColor;
|
|
}
|
|
_button.colors = cb;
|
|
}
|
|
|
|
private static Color VariantBaseColor(UIThemeSO t, UIButtonVariant v) => v switch
|
|
{
|
|
UIButtonVariant.Primary => t.Primary,
|
|
UIButtonVariant.Secondary => t.Secondary,
|
|
UIButtonVariant.Accent => t.Accent,
|
|
UIButtonVariant.Danger => t.Danger,
|
|
UIButtonVariant.Ghost => t.TextPrimary,
|
|
_ => t.Primary,
|
|
};
|
|
|
|
private UIThemeSO ResolveTheme()
|
|
{
|
|
if (_theme != null) return _theme;
|
|
var applier = GetComponentInParent<UIThemeApplier>(includeInactive: true);
|
|
return applier != null ? applier.Theme : null;
|
|
}
|
|
|
|
// ── 交互反馈 ──────────────────────────────────────────────────────────
|
|
public void OnSelect(BaseEventData e) { PlayScale(_selectedScale); RaiseHoverSfx(); }
|
|
public void OnDeselect(BaseEventData e) { PlayScale(1f); }
|
|
public void OnPointerEnter(PointerEventData e) { PlayScale(_selectedScale); RaiseHoverSfx(); }
|
|
public void OnPointerExit(PointerEventData e)
|
|
{
|
|
// 仍为当前选中项则保持放大
|
|
if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject) return;
|
|
PlayScale(1f);
|
|
}
|
|
|
|
private void PlayScale(float factor)
|
|
{
|
|
if (!_scaleFeedback || !isActiveAndEnabled) return;
|
|
if (_scaleRoutine != null) StopCoroutine(_scaleRoutine);
|
|
_scaleRoutine = StartCoroutine(UITween.Scale(transform, _baseScale * factor, _scaleDuration));
|
|
}
|
|
|
|
private void RaiseClickSfx() => _onClickSfx?.Raise();
|
|
private void RaiseHoverSfx() => _onHoverSfx?.Raise();
|
|
|
|
#if UNITY_EDITOR
|
|
private void OnValidate()
|
|
{
|
|
if (Application.isPlaying) return;
|
|
_button = GetComponent<Button>();
|
|
ApplyTheme();
|
|
}
|
|
#endif
|
|
}
|
|
}
|