291 lines
8.2 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |