using System; using DG.Tweening; using TMPro; using UnityEngine; using UnityEngine.UI; namespace Cielonos.MainGame.UI { /// /// 单条 Notification 的显示组件,只负责内容展示和动画。 /// public class NotificationItem : MonoBehaviour { [SerializeField] private RectTransform rectTransform; [SerializeField] private CanvasGroup canvasGroup; [SerializeField] private TMP_Text titleText; [SerializeField] private TMP_Text messageText; [SerializeField] private Image iconImage; [SerializeField] private Image accentImage; [SerializeField] private float slideDistance = 180f; private Sequence positionSequence; private Sequence fadeSequence; private Sequence lifetimeSequence; private Action onExpired; private float lifetimeDuration = 3f; private void Awake() { CacheReferences(); } private void OnDestroy() { KillAllSequences(); } public void Setup(NotificationData data, float defaultDuration) { CacheReferences(); NormalizeRectTransform(); KillAllSequences(); if (titleText != null) { titleText.text = data.title; } if (messageText != null) { messageText.text = data.message; } if (iconImage != null) { iconImage.sprite = data.icon; iconImage.enabled = data.icon != null; } if (accentImage != null) { accentImage.color = data.accentColor; } onExpired = null; lifetimeDuration = data.duration > 0f ? data.duration : defaultDuration; } public void PlayIn(Vector2 target, float duration, Action expiredCallback) { onExpired = expiredCallback; gameObject.SetActive(true); SetCanvasVisible(false); SetPosition(target + Vector2.right * slideDistance); StartLifetime(); FadeTo(1f, duration, null); MoveTo(target, duration, Ease.OutCubic); } public void MoveTo(Vector2 target, float duration) { MoveTo(target, duration, Ease.OutCubic); } public void SetPosition(Vector2 position) { if (rectTransform != null) { rectTransform.anchoredPosition = position; } } public void PlayOut(Action completed, float duration) { if (!gameObject.activeSelf) { completed?.Invoke(); Destroy(gameObject); return; } KillLifetimeSequence(); Vector2 target = rectTransform != null ? rectTransform.anchoredPosition + Vector2.right * slideDistance : Vector2.right * slideDistance; bool positionCompleted = rectTransform == null; bool fadeCompleted = canvasGroup == null; void TryComplete() { if (!positionCompleted || !fadeCompleted) { return; } completed?.Invoke(); Destroy(gameObject); } if (rectTransform != null) { MoveTo(target, duration, Ease.InCubic, () => { positionCompleted = true; TryComplete(); }); } if (canvasGroup != null) { FadeTo(0f, duration, () => { fadeCompleted = true; TryComplete(); }); } TryComplete(); } public void DismissImmediate(Action completed) { KillAllSequences(); completed?.Invoke(); Destroy(gameObject); } private void StartLifetime() { KillLifetimeSequence(); lifetimeSequence = CreateBaseSequence() .AppendInterval(Mathf.Max(0.1f, lifetimeDuration)) .OnComplete(() => { Action callback = onExpired; onExpired = null; callback?.Invoke(); }); lifetimeSequence.Play(); } private void MoveTo(Vector2 target, float duration, Ease ease, Action completed = null) { if (rectTransform == null) { completed?.Invoke(); return; } KillPositionSequence(); positionSequence = CreateBaseSequence() .Join(rectTransform.DOAnchorPos(target, duration).SetEase(ease)) .OnComplete(() => completed?.Invoke()); positionSequence.Play(); } private void FadeTo(float alpha, float duration, Action completed) { if (canvasGroup == null) { completed?.Invoke(); return; } KillFadeSequence(); fadeSequence = CreateBaseSequence() .Join(canvasGroup.DOFade(alpha, duration)) .OnComplete(() => completed?.Invoke()); fadeSequence.Play(); } private static Sequence CreateBaseSequence() { return DOTween.Sequence() .SetUpdate(true) .SetAutoKill(true) .Pause(); } private void KillAllSequences() { KillPositionSequence(); KillFadeSequence(); KillLifetimeSequence(); } private void KillPositionSequence() { positionSequence?.Kill(); positionSequence = null; } private void KillFadeSequence() { fadeSequence?.Kill(); fadeSequence = null; } private void KillLifetimeSequence() { lifetimeSequence?.Kill(); lifetimeSequence = null; } private void CacheReferences() { rectTransform ??= GetComponent(); canvasGroup ??= GetComponent(); } private void SetCanvasVisible(bool visible) { if (canvasGroup == null) { return; } canvasGroup.alpha = visible ? 1f : 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } private void NormalizeRectTransform() { if (rectTransform == null) { return; } rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.localScale = Vector3.one; rectTransform.localRotation = Quaternion.identity; } } }