using System; using System.Threading; using BriarQueen.Framework.Coordinators.Events; using BriarQueen.Framework.Events.UI; using BriarQueen.Framework.Managers.UI.Base; using Cysharp.Threading.Tasks; using PrimeTween; using UnityEngine; using UnityEngine.UI; using VContainer; namespace BriarQueen.UI { public class ScreenFader : MonoBehaviour, IScreenFader { private const float ALPHA_EPSILON = 0.001f; [Header("UI Elements")] [SerializeField] private CanvasGroup _canvasGroup; [Tooltip("Used for black / solid-color fades.")] [SerializeField] private Image _solidImage; private Sequence _currentFadeSequence; private EventCoordinator _eventCoordinator; private CancellationTokenSource _fadeCts; public bool IsModal => true; private void Awake() { if (_canvasGroup == null) Debug.LogError($"{nameof(ScreenFader)} on {name} is missing a CanvasGroup reference.", this); SetInteractionState(_canvasGroup != null && _canvasGroup.alpha > ALPHA_EPSILON); } private void OnDestroy() { CancelAndDisposeFadeToken(); StopCurrentSequence(); } // Window Stubs - Fader is Non-Interactive public async UniTask Show() { await FadeToAsync( 1f); } public async UniTask Hide() { await FadeFromAsync(1f); } [Inject] public void Construct(EventCoordinator eventCoordinator) { Debug.Log("ScreenFader constructed"); _eventCoordinator = eventCoordinator; } public async UniTask FadeToAsync(float duration) { if (_canvasGroup == null) return; _solidImage.color = Color.black; BeginNewFade(); gameObject.SetActive(true); SetInteractionState(true); await FadeScreen(1f, duration, _fadeCts.Token); } public async UniTask FadeFromAsync(float duration) { if (_canvasGroup == null) return; BeginNewFade(); await FadeScreen(0f, duration, _fadeCts.Token); if (!_fadeCts.IsCancellationRequested) { _canvasGroup.alpha = 0f; SetInteractionState(false); } } public UniTask FadeToAsync(FadeStyle style, System.Drawing.Color tint, float duration) { throw new NotImplementedException(); } private void ApplyTint(FadeStyle style, Color tint) { if (_solidImage != null) { _solidImage.enabled = true; _solidImage.color = Color.black; } } private async UniTask FadeScreen(float targetAlpha, float duration, CancellationToken token) { targetAlpha = Mathf.Clamp01(targetAlpha); duration = Mathf.Max(0f, duration); var currentAlpha = _canvasGroup.alpha; if (Mathf.Abs(currentAlpha - targetAlpha) <= ALPHA_EPSILON) { _canvasGroup.alpha = targetAlpha; SetInteractionState(targetAlpha > ALPHA_EPSILON); return; } StopCurrentSequence(); if (duration <= 0f) { _canvasGroup.alpha = targetAlpha; SetInteractionState(targetAlpha > ALPHA_EPSILON); _eventCoordinator?.PublishImmediate(new FadeCompletedEvent()); return; } _currentFadeSequence = Sequence.Create() .Group(Tween.Alpha(_canvasGroup, targetAlpha, duration, Ease.InOutSine)); try { await _currentFadeSequence.ToUniTask(cancellationToken: token); _canvasGroup.alpha = targetAlpha; SetInteractionState(targetAlpha > ALPHA_EPSILON); _eventCoordinator?.PublishImmediate(new FadeCompletedEvent()); } catch (OperationCanceledException) { // Another fade interrupted this one. Intentionally ignore completion. } } private void BeginNewFade() { CancelAndDisposeFadeToken(); StopCurrentSequence(); _fadeCts = new CancellationTokenSource(); } private void CancelAndDisposeFadeToken() { if (_fadeCts == null) return; if (!_fadeCts.IsCancellationRequested) _fadeCts.Cancel(); _fadeCts.Dispose(); _fadeCts = null; } private void StopCurrentSequence() { if (_currentFadeSequence.isAlive) _currentFadeSequence.Stop(); } private void SetInteractionState(bool isBlocking) { _canvasGroup.interactable = isBlocking; _canvasGroup.blocksRaycasts = isBlocking; } } }