Files

291 lines
8.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using BriarQueen.Framework.Managers.UI.Base;
using Cysharp.Threading.Tasks;
using PrimeTween;
using TMPro;
using UnityEngine;
namespace BriarQueen.UI.HUD
{
public class TutorialPopup : MonoBehaviour, IPopup
{
[SerializeField]
private TextMeshProUGUI _text;
[SerializeField]
private CanvasGroup _canvasGroup;
[Header("Animation")]
[SerializeField]
private float _fadeDuration = 0.3f;
[Header("Queue")]
[SerializeField]
private bool _suppressDuplicateMessages = true;
private readonly Queue<PopupRequest> _queue = new();
private string _currentMessage = string.Empty;
private CancellationTokenSource _destroyCts;
private bool _isProcessingQueue;
private Sequence _sequence;
private CancellationTokenSource _sequenceCts;
public bool IsModal => false;
public GameObject GameObject => gameObject;
private void Awake()
{
_destroyCts = new CancellationTokenSource();
if (_canvasGroup != null)
{
_canvasGroup.alpha = 0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
}
gameObject.SetActive(false);
}
private void OnDestroy()
{
CancelTweenIfRunning();
if (_destroyCts != null)
{
_destroyCts.Cancel();
_destroyCts.Dispose();
_destroyCts = null;
}
_queue.Clear();
}
public async UniTask Show()
{
if (_canvasGroup == null)
return;
gameObject.SetActive(true);
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
CancelTweenIfRunning();
var localTweenCts = new CancellationTokenSource();
_sequenceCts = localTweenCts;
_canvasGroup.alpha = 0f;
_sequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
{
startValue = _canvasGroup.alpha,
endValue = 1f,
settings = new TweenSettings
{
duration = _fadeDuration,
useUnscaledTime = true
}
}));
try
{
await _sequence.ToUniTask(cancellationToken: localTweenCts.Token);
_canvasGroup.alpha = 1f;
}
catch (OperationCanceledException)
{
// Interrupted by another tween or destroy.
}
finally
{
if (ReferenceEquals(_sequenceCts, localTweenCts))
_sequenceCts = null;
localTweenCts.Dispose();
_sequence = default;
}
}
public async UniTask Hide()
{
if (_canvasGroup == null)
return;
CancelTweenIfRunning();
var localTweenCts = new CancellationTokenSource();
_sequenceCts = localTweenCts;
_sequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
{
startValue = _canvasGroup.alpha,
endValue = 0f,
settings = new TweenSettings
{
duration = _fadeDuration,
useUnscaledTime = true
}
}));
try
{
await _sequence.ToUniTask(cancellationToken: localTweenCts.Token);
_canvasGroup.alpha = 0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
gameObject.SetActive(false);
}
catch (OperationCanceledException)
{
// Interrupted by another tween or destroy.
}
finally
{
if (ReferenceEquals(_sequenceCts, localTweenCts))
_sequenceCts = null;
localTweenCts.Dispose();
_sequence = default;
}
}
private void OnClose()
{
Hide().Forget();
}
public void SetText(string text)
{
if (_text != null)
_text.text = text ?? string.Empty;
}
/// <summary>
/// Enqueue a popup to be shown in order. Does not interrupt the current popup.
/// Duplicate messages can be suppressed if they are currently showing or already queued.
/// </summary>
public UniTask Play(string text, float duration)
{
if (string.IsNullOrWhiteSpace(text))
return UniTask.CompletedTask;
if (_suppressDuplicateMessages && IsDuplicateMessage(text))
return UniTask.CompletedTask;
_queue.Enqueue(new PopupRequest(text, duration));
if (!_isProcessingQueue)
ProcessQueue().Forget();
return UniTask.CompletedTask;
}
/// <summary>
/// Clears pending popups and hides the current popup immediately.
/// Useful for scene transitions or hard UI resets.
/// </summary>
public void ClearQueue()
{
_queue.Clear();
_currentMessage = string.Empty;
CancelTweenIfRunning();
if (_canvasGroup != null)
{
_canvasGroup.alpha = 0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
}
gameObject.SetActive(false);
SetText(string.Empty);
_isProcessingQueue = false;
}
private bool IsDuplicateMessage(string text)
{
if (!string.IsNullOrEmpty(_currentMessage) &&
string.Equals(_currentMessage, text, StringComparison.Ordinal))
return true;
foreach (var queued in _queue)
if (string.Equals(queued.Text, text, StringComparison.Ordinal))
return true;
return false;
}
private async UniTaskVoid ProcessQueue()
{
if (_isProcessingQueue)
return;
_isProcessingQueue = true;
try
{
while (_destroyCts != null && !_destroyCts.IsCancellationRequested && _queue.Count > 0)
{
var request = _queue.Dequeue();
_currentMessage = request.Text;
SetText(request.Text);
await Show();
if (request.Duration > 0f)
await UniTask.Delay(
TimeSpan.FromSeconds(request.Duration),
cancellationToken: _destroyCts.Token);
await Hide();
_currentMessage = string.Empty;
}
}
catch (OperationCanceledException)
{
// Object destroyed or queue processing canceled.
}
finally
{
_currentMessage = string.Empty;
_isProcessingQueue = false;
}
}
private void CancelTweenIfRunning()
{
if (_sequence.isAlive)
_sequence.Stop();
_sequence = default;
if (_sequenceCts != null)
{
_sequenceCts.Cancel();
_sequenceCts.Dispose();
_sequenceCts = null;
}
}
private struct PopupRequest
{
public readonly string Text;
public readonly float Duration;
public PopupRequest(string text, float duration)
{
Text = text;
Duration = duration;
}
}
}
}