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; } } }