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