using System;
using System.Threading;
using BriarQueen.UI.Menus.Components;
using Cysharp.Threading.Tasks;
using PrimeTween;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.UI.Menus
{
///
/// Confirm delete modal with tweened show/hide (fade + scale).
/// - Open(): animates in
/// - Close(): animates out
/// - CloseImmediate(): hard hide (safe for OnDisable/OnDestroy)
/// Notes:
/// - Uses unscaled time so it still animates while paused.
/// - Gates input during transitions.
///
public class ConfirmDeleteWindow : MonoBehaviour
{
[Header("Root")]
[SerializeField]
private GameObject _root;
[Header("Animation Targets")]
[SerializeField]
private CanvasGroup _canvasGroup;
[SerializeField]
private RectTransform _panelTransform;
[Header("UI")]
[SerializeField]
private TextMeshProUGUI _titleText;
[SerializeField]
private Button _confirmButton;
[SerializeField]
private Button _cancelButton;
[Header("Tween Settings")]
[SerializeField]
private float _duration = 0.18f;
[SerializeField]
private Ease _easeIn = Ease.OutBack;
[SerializeField]
private Ease _easeOut = Ease.InQuad;
[SerializeField]
private bool _useUnscaledTime = true;
[Header("Scale")]
[SerializeField]
private float _fromScale = 0.92f;
[SerializeField]
private float _toScale = 1.0f;
private CancellationTokenSource _cts;
private bool _isAnimating;
private bool _isOpen;
private SaveFileInfo _pending;
private Sequence _sequence;
private void Awake()
{
if (_confirmButton != null) _confirmButton.onClick.AddListener(Confirm);
if (_cancelButton != null) _cancelButton.onClick.AddListener(Cancel);
CloseImmediate();
}
private void OnDestroy()
{
if (_confirmButton != null) _confirmButton.onClick.RemoveListener(Confirm);
if (_cancelButton != null) _cancelButton.onClick.RemoveListener(Cancel);
StopAnim();
}
public event Action OnConfirmDelete;
public event Action OnCancel;
public void Open(SaveFileInfo info)
{
_pending = info;
if (_titleText != null)
_titleText.text = $"Delete '{info.FileName}'?";
OpenAsync().Forget();
}
public void Close()
{
CloseAsync().Forget();
}
public void CloseImmediate()
{
StopAnim();
_pending = default;
_isOpen = false;
_isAnimating = false;
if (_root != null) _root.SetActive(false);
if (_canvasGroup != null)
{
_canvasGroup.alpha = 0f;
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
}
if (_panelTransform != null)
{
var s = _toScale;
_panelTransform.localScale = new Vector3(s, s, 1f);
}
}
private async UniTask OpenAsync()
{
if (_isOpen && !_isAnimating) return;
EnsureRefs();
StopAnim();
_cts = new CancellationTokenSource();
var token = _cts.Token;
if (_root != null) _root.SetActive(true);
_isAnimating = true;
// Prep start state
if (_canvasGroup != null)
{
_canvasGroup.alpha = 0f;
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
}
if (_panelTransform != null)
{
var s = _fromScale;
_panelTransform.localScale = new Vector3(s, s, 1f);
}
// Build sequence (fade + scale)
_sequence = Sequence.Create(useUnscaledTime: true);
if (_canvasGroup != null)
_sequence.Group(Tween.Alpha(_canvasGroup, new TweenSettings
{
startValue = 0f,
endValue = 1f,
settings = new TweenSettings
{
duration = _duration,
ease = _easeIn,
useUnscaledTime = _useUnscaledTime
}
}));
if (_panelTransform != null)
_sequence.Group(Tween.Scale(_panelTransform, new TweenSettings
{
startValue = new Vector3(_fromScale, _fromScale, 1f),
endValue = new Vector3(_toScale, _toScale, 1f),
settings = new TweenSettings
{
duration = _duration,
ease = _easeIn,
useUnscaledTime = _useUnscaledTime
}
}));
try
{
await _sequence.ToUniTask(cancellationToken: token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
_sequence = default;
_isAnimating = false;
_isOpen = true;
}
// Enable input once fully open
if (_canvasGroup != null)
{
_canvasGroup.alpha = 1f;
_canvasGroup.interactable = true;
_canvasGroup.blocksRaycasts = true;
}
// Optional: focus cancel for controller/keyboard flows
_cancelButton?.Select();
}
private async UniTask CloseAsync()
{
if (!_isOpen && !_isAnimating) return;
EnsureRefs();
StopAnim();
_cts = new CancellationTokenSource();
var token = _cts.Token;
_isAnimating = true;
// Disable input immediately while animating out
if (_canvasGroup != null)
{
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
}
var startAlpha = _canvasGroup != null ? _canvasGroup.alpha : 1f;
var startScale = _panelTransform != null ? _panelTransform.localScale : Vector3.one;
_sequence = Sequence.Create(useUnscaledTime: true);
if (_canvasGroup != null)
_sequence.Group(Tween.Alpha(_canvasGroup, new TweenSettings
{
startValue = startAlpha,
endValue = 0f,
settings = new TweenSettings
{
duration = _duration,
ease = _easeOut,
useUnscaledTime = _useUnscaledTime
}
}));
if (_panelTransform != null)
_sequence.Group(Tween.Scale(_panelTransform, new TweenSettings
{
startValue = startScale,
endValue = new Vector3(_fromScale, _fromScale, 1f),
settings = new TweenSettings
{
duration = _duration,
ease = _easeOut,
useUnscaledTime = _useUnscaledTime
}
}));
try
{
await _sequence.ToUniTask(cancellationToken: token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
_sequence = default;
_isAnimating = false;
_isOpen = false;
}
// Fully hidden
if (_canvasGroup != null) _canvasGroup.alpha = 0f;
if (_root != null) _root.SetActive(false);
_pending = default;
}
private void Confirm()
{
if (_isAnimating) return;
var info = _pending;
Close();
OnConfirmDelete?.Invoke(info);
}
private void Cancel()
{
if (_isAnimating) return;
Close();
OnCancel?.Invoke();
}
private void StopAnim()
{
if (_sequence.isAlive)
{
_sequence.Stop();
_sequence = default;
}
if (_cts != null)
{
try
{
_cts.Cancel();
}
catch
{
}
_cts.Dispose();
_cts = null;
}
}
private void EnsureRefs()
{
// If you forgot to wire these, try to find sensible defaults.
if (_canvasGroup == null && _root != null)
_canvasGroup = _root.GetComponent();
if (_panelTransform == null)
_panelTransform = GetComponentInChildren(true);
// Root fallback: if none specified, use this GO
if (_root == null)
_root = gameObject;
}
}
}