433 lines
13 KiB
C#
433 lines
13 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;
|
|
using UnityEngine.UI;
|
|
|
|
namespace BriarQueen.UI.HUD
|
|
{
|
|
public class InfoPopup : MonoBehaviour, IPopup
|
|
{
|
|
[SerializeField]
|
|
private TextMeshProUGUI _text;
|
|
|
|
[SerializeField]
|
|
private RectTransform _rectTransform;
|
|
|
|
[SerializeField]
|
|
private CanvasGroup _canvasGroup;
|
|
|
|
[Header("Sizing")]
|
|
[SerializeField]
|
|
private float _minWidth = 160f;
|
|
|
|
[SerializeField]
|
|
private float _maxWidth = 500f;
|
|
|
|
[SerializeField]
|
|
private float _minHeight = 60f;
|
|
|
|
[SerializeField]
|
|
private float _extraWidthSafety = 4f;
|
|
|
|
[SerializeField]
|
|
private float _extraHeightSafety = 4f;
|
|
|
|
[Header("Animation")]
|
|
[SerializeField]
|
|
private float _slideDuration = 1.2f;
|
|
|
|
[SerializeField]
|
|
private Vector2 _hiddenAnchoredPosition = new(0f, -250f);
|
|
|
|
[SerializeField]
|
|
private Vector2 _shownAnchoredPosition = new(0f, 0f);
|
|
|
|
[SerializeField]
|
|
private float _shownAlpha = 1f;
|
|
|
|
[Header("Queue")]
|
|
[SerializeField]
|
|
private bool _suppressDuplicateMessages = true;
|
|
|
|
[Header("Editor Debug")]
|
|
[SerializeField]
|
|
private bool _debugResizeInEditor = true;
|
|
|
|
private readonly Queue<PopupRequest> _queue = new();
|
|
|
|
private string _currentMessage = string.Empty;
|
|
|
|
private CancellationTokenSource _destroyCts;
|
|
private bool _isProcessingQueue;
|
|
private string _lastEditorText = string.Empty;
|
|
private float _lastHeight = -1f;
|
|
private float _lastWidth = -1f;
|
|
|
|
private Sequence _sequence;
|
|
private CancellationTokenSource _sequenceCts;
|
|
|
|
public bool IsModal => false;
|
|
public GameObject GameObject => gameObject;
|
|
|
|
private void Awake()
|
|
{
|
|
_destroyCts = new CancellationTokenSource();
|
|
|
|
if (_rectTransform == null)
|
|
_rectTransform = GetComponent<RectTransform>();
|
|
|
|
if (_canvasGroup == null)
|
|
_canvasGroup = GetComponent<CanvasGroup>();
|
|
|
|
if (_rectTransform != null)
|
|
_rectTransform.anchoredPosition = _hiddenAnchoredPosition;
|
|
|
|
if (_canvasGroup != null)
|
|
{
|
|
_canvasGroup.alpha = 0f;
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
private void Update()
|
|
{
|
|
if (Application.isPlaying)
|
|
return;
|
|
|
|
if (_text == null)
|
|
return;
|
|
|
|
if (_lastEditorText != _text.text)
|
|
ResizeToFitText();
|
|
}
|
|
#endif
|
|
|
|
private void OnDestroy()
|
|
{
|
|
CancelTweenIfRunning();
|
|
|
|
if (_destroyCts != null)
|
|
{
|
|
_destroyCts.Cancel();
|
|
_destroyCts.Dispose();
|
|
_destroyCts = null;
|
|
}
|
|
|
|
_queue.Clear();
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (_rectTransform == null)
|
|
_rectTransform = GetComponent<RectTransform>();
|
|
|
|
if (_canvasGroup == null)
|
|
_canvasGroup = GetComponent<CanvasGroup>();
|
|
|
|
ResizeToFitText();
|
|
}
|
|
|
|
public async UniTask Show()
|
|
{
|
|
if (_rectTransform == null || _canvasGroup == null)
|
|
return;
|
|
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
|
|
CancelTweenIfRunning();
|
|
|
|
var localTweenCts = new CancellationTokenSource();
|
|
_sequenceCts = localTweenCts;
|
|
|
|
_rectTransform.anchoredPosition = _hiddenAnchoredPosition;
|
|
_canvasGroup.alpha = 0f;
|
|
|
|
_sequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.UIAnchoredPosition(_rectTransform, new TweenSettings<Vector2>
|
|
{
|
|
startValue = _hiddenAnchoredPosition,
|
|
endValue = _shownAnchoredPosition,
|
|
settings = new TweenSettings
|
|
{
|
|
duration = _slideDuration,
|
|
ease = Ease.OutCubic,
|
|
useUnscaledTime = true
|
|
}
|
|
}))
|
|
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = 0f,
|
|
endValue = _shownAlpha,
|
|
settings = new TweenSettings
|
|
{
|
|
duration = _slideDuration,
|
|
ease = Ease.OutCubic,
|
|
useUnscaledTime = true
|
|
}
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _sequence.ToUniTask(cancellationToken: localTweenCts.Token);
|
|
_rectTransform.anchoredPosition = _shownAnchoredPosition;
|
|
_canvasGroup.alpha = _shownAlpha;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
if (ReferenceEquals(_sequenceCts, localTweenCts))
|
|
_sequenceCts = null;
|
|
|
|
localTweenCts.Dispose();
|
|
_sequence = default;
|
|
}
|
|
}
|
|
|
|
public async UniTask Hide()
|
|
{
|
|
if (_rectTransform == null || _canvasGroup == null)
|
|
return;
|
|
|
|
CancelTweenIfRunning();
|
|
|
|
var localTweenCts = new CancellationTokenSource();
|
|
_sequenceCts = localTweenCts;
|
|
|
|
_sequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.UIAnchoredPosition(_rectTransform, new TweenSettings<Vector2>
|
|
{
|
|
startValue = _rectTransform.anchoredPosition,
|
|
endValue = _hiddenAnchoredPosition,
|
|
settings = new TweenSettings
|
|
{
|
|
duration = _slideDuration,
|
|
ease = Ease.InCubic,
|
|
useUnscaledTime = true
|
|
}
|
|
}))
|
|
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = _canvasGroup.alpha,
|
|
endValue = 0f,
|
|
settings = new TweenSettings
|
|
{
|
|
duration = _slideDuration,
|
|
ease = Ease.InCubic,
|
|
useUnscaledTime = true
|
|
}
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _sequence.ToUniTask(cancellationToken: localTweenCts.Token);
|
|
_rectTransform.anchoredPosition = _hiddenAnchoredPosition;
|
|
_canvasGroup.alpha = 0f;
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
if (ReferenceEquals(_sequenceCts, localTweenCts))
|
|
_sequenceCts = null;
|
|
|
|
localTweenCts.Dispose();
|
|
_sequence = default;
|
|
}
|
|
}
|
|
|
|
public void SetText(string text)
|
|
{
|
|
if (_text != null)
|
|
_text.text = text ?? string.Empty;
|
|
|
|
ResizeToFitText();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public void ClearQueue()
|
|
{
|
|
_queue.Clear();
|
|
_currentMessage = string.Empty;
|
|
|
|
CancelTweenIfRunning();
|
|
|
|
if (_rectTransform != null)
|
|
_rectTransform.anchoredPosition = _hiddenAnchoredPosition;
|
|
|
|
if (_canvasGroup != null)
|
|
{
|
|
_canvasGroup.alpha = 0f;
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = 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)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
_currentMessage = string.Empty;
|
|
_isProcessingQueue = false;
|
|
}
|
|
}
|
|
|
|
private void ResizeToFitText()
|
|
{
|
|
if (_text == null || _rectTransform == null)
|
|
return;
|
|
|
|
_text.ForceMeshUpdate();
|
|
|
|
var margin = _text.margin;
|
|
|
|
var leftInset = margin.x;
|
|
var topInset = margin.y;
|
|
var rightInset = margin.z;
|
|
var bottomInset = margin.w;
|
|
|
|
var totalHorizontalInset = leftInset + rightInset;
|
|
var totalVerticalInset = topInset + bottomInset;
|
|
|
|
var maxTextWidth = Mathf.Max(0f, _maxWidth - totalHorizontalInset - _extraWidthSafety);
|
|
|
|
var preferredSize = _text.GetPreferredValues(_text.text, maxTextWidth, Mathf.Infinity);
|
|
|
|
var width = Mathf.Clamp(
|
|
preferredSize.x + totalHorizontalInset + _extraWidthSafety,
|
|
_minWidth,
|
|
_maxWidth);
|
|
|
|
var availableTextWidth = Mathf.Max(0f, width - totalHorizontalInset - _extraWidthSafety);
|
|
|
|
var wrappedPreferredSize = _text.GetPreferredValues(_text.text, availableTextWidth, Mathf.Infinity);
|
|
|
|
var height = Mathf.Max(
|
|
_minHeight,
|
|
wrappedPreferredSize.y + totalVerticalInset + _extraHeightSafety);
|
|
|
|
_rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
|
|
_rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
|
|
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(_rectTransform);
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
#if UNITY_EDITOR
|
|
var changed =
|
|
!Mathf.Approximately(width, _lastWidth) ||
|
|
!Mathf.Approximately(height, _lastHeight) ||
|
|
_lastEditorText != _text.text;
|
|
|
|
if (_debugResizeInEditor && changed)
|
|
Debug.Log(
|
|
$"[InfoPopup] '{_text.text}'\n" +
|
|
$"Preferred: {preferredSize.x:F1} x {preferredSize.y:F1}\n" +
|
|
$"Wrapped: {wrappedPreferredSize.x:F1} x {wrappedPreferredSize.y:F1}\n" +
|
|
$"Insets: L{leftInset:F1} T{topInset:F1} R{rightInset:F1} B{bottomInset:F1}\n" +
|
|
$"Final Size: {width:F1} x {height:F1}",
|
|
this);
|
|
#endif
|
|
|
|
_lastEditorText = _text.text;
|
|
_lastWidth = width;
|
|
_lastHeight = height;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
} |