First commit for private source control. Older commits available on Github.
This commit is contained in:
291
Assets/Scripts/UI/HUD/TutorialPopup.cs
Normal file
291
Assets/Scripts/UI/HUD/TutorialPopup.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user