341 lines
9.3 KiB
C#
341 lines
9.3 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<SaveFileInfo> 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<float>
|
|
{
|
|
startValue = 0f,
|
|
endValue = 1f,
|
|
settings = new TweenSettings
|
|
{
|
|
duration = _duration,
|
|
ease = _easeIn,
|
|
useUnscaledTime = _useUnscaledTime
|
|
}
|
|
}));
|
|
|
|
if (_panelTransform != null)
|
|
_sequence.Group(Tween.Scale(_panelTransform, new TweenSettings<Vector3>
|
|
{
|
|
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<float>
|
|
{
|
|
startValue = startAlpha,
|
|
endValue = 0f,
|
|
settings = new TweenSettings
|
|
{
|
|
duration = _duration,
|
|
ease = _easeOut,
|
|
useUnscaledTime = _useUnscaledTime
|
|
}
|
|
}));
|
|
|
|
if (_panelTransform != null)
|
|
_sequence.Group(Tween.Scale(_panelTransform, new TweenSettings<Vector3>
|
|
{
|
|
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<CanvasGroup>();
|
|
|
|
if (_panelTransform == null)
|
|
_panelTransform = GetComponentInChildren<RectTransform>(true);
|
|
|
|
// Root fallback: if none specified, use this GO
|
|
if (_root == null)
|
|
_root = gameObject;
|
|
}
|
|
}
|
|
} |