using System; using System.Threading; using BriarQueen.Framework.Coordinators.Events; using BriarQueen.Framework.Effects; using BriarQueen.Framework.Events.UI; using BriarQueen.Framework.Managers.Input; using BriarQueen.Framework.Managers.UI.Events; using BriarQueen.Framework.Services.Game; using BriarQueen.Game.Effects; using BriarQueen.UI.Menus.Components; using Cysharp.Threading.Tasks; using PrimeTween; using TMPro; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Serialization; using UnityEngine.UI; using VContainer; namespace BriarQueen.UI.Menus { public class MainMenuWindow : MonoBehaviour { [Header("Intro Screen")] [SerializeField] [FormerlySerializedAs("_mainMenuIntroScreenCanvasGroup")] private CanvasGroup _introScreenCanvas; [SerializeField] [FormerlySerializedAs("_introTextText")] private TextMeshProUGUI _pressStartText; [SerializeField] private UIDissolveImage _introGroupDissolveGroup; [Header("Main Menu")] [SerializeField] [FormerlySerializedAs("_mainMenuWindowCanvasGroup")] private CanvasGroup _mainMenuGroup; [Header("Buttons")] [SerializeField] private CanvasGroup _buttonsGroup; [SerializeField] private AnimatedSelectionButtonGroup _mainMenuSelectionGroup; [SerializeField] private AnimatedSelectionButton _startGameSelectionButton; [SerializeField] private AnimatedSelectionButton _settingsSelectionButton; [SerializeField] private AnimatedSelectionButton _quitSelectionButton; [SerializeField] private Button _startGameButton; [SerializeField] private Button _settingsButton; [SerializeField] private Button _quitButton; [Header("Select Save Window")] [SerializeField] private SelectSaveWindow _selectSaveWindow; [SerializeField] private CanvasGroup _selectSaveContainerCanvasGroup; [SerializeField] private CanvasGroup _selectSaveWindowCanvasGroup; [SerializeField] private UIFogReveal _selectSaveWindowFog; [Header("Tween Settings")] [SerializeField] private TweenSettings _selectSaveTweenSettings = new() { duration = 0.25f, ease = Ease.OutQuad, useUnscaledTime = true }; [SerializeField] private TweenSettings _pressStartFadeTweenSettings = new() { duration = 0.25f, ease = Ease.OutQuad, useUnscaledTime = true }; [SerializeField] private TweenSettings _pressStartPulseTweenSettings = new() { duration = 0.85f, ease = Ease.InOutSine, useUnscaledTime = true }; [SerializeField] [Range(0f, 1f)] private float _pressStartPulseMinimumAlpha = 0.25f; private CancellationTokenSource _introCts; private CancellationTokenSource _selectSaveCts; private EventCoordinator _eventCoordinator; private GameService _gameService; private InputManager _inputManager; private Sequence _pressStartFadeSequence; private Sequence _pressStartPulseSequence; private Sequence _selectSaveSequence; private bool _introFinished; private bool _introTransitioning; [Inject] public void Construct(GameService gameService, EventCoordinator eventCoordinator, InputManager inputManager) { _gameService = gameService; _eventCoordinator = eventCoordinator; _inputManager = inputManager; } private void Awake() { ApplyInitialVisualState(); if (_selectSaveWindow != null) { _selectSaveWindow.OnCloseWindow += CloseSelectSaveWindow; _selectSaveWindow.gameObject.SetActive(false); } } private void OnEnable() { BindButtons(); _eventCoordinator?.PublishImmediate(new UIToggleHudEvent(false)); _inputManager?.BindSubmitForStart(OnIntroSubmit); _eventCoordinator?.Subscribe(OnBackRequested); ApplyInitialVisualState(); } private void Update() { if (_introFinished || _introTransitioning) { return; } if (Mouse.current?.leftButton.wasPressedThisFrame == true || Gamepad.current?.buttonSouth.wasPressedThisFrame == true) { TransitionFromIntroToMainMenu().Forget(); } } private void OnDisable() { UnbindButtons(); _inputManager?.ResetSubmitBind(OnIntroSubmit); _eventCoordinator?.Unsubscribe(OnBackRequested); StopIntroTweens(); StopSelectSaveTween(); } private void OnDestroy() { if (_selectSaveWindow != null) { _selectSaveWindow.OnCloseWindow -= CloseSelectSaveWindow; } StopIntroTweens(); StopSelectSaveTween(); } private void BindButtons() { var startGameButton = ResolveButton(_startGameSelectionButton, _startGameButton); if (startGameButton != null) { startGameButton.onClick.AddListener(OnStartClicked); } var settingsButton = ResolveButton(_settingsSelectionButton, _settingsButton); if (settingsButton != null) { settingsButton.onClick.AddListener(OnSettingsClicked); } var quitButton = ResolveButton(_quitSelectionButton, _quitButton); if (quitButton != null) { quitButton.onClick.AddListener(OnQuitClicked); } } private void UnbindButtons() { var startGameButton = ResolveButton(_startGameSelectionButton, _startGameButton); if (startGameButton != null) { startGameButton.onClick.RemoveListener(OnStartClicked); } var settingsButton = ResolveButton(_settingsSelectionButton, _settingsButton); if (settingsButton != null) { settingsButton.onClick.RemoveListener(OnSettingsClicked); } var quitButton = ResolveButton(_quitSelectionButton, _quitButton); if (quitButton != null) { quitButton.onClick.RemoveListener(OnQuitClicked); } } private void ApplyInitialVisualState() { _introFinished = false; _introTransitioning = false; SetCanvasGroupState(_introScreenCanvas, 1f, true); if (_introScreenCanvas != null) { _introScreenCanvas.gameObject.SetActive(true); } SetCanvasGroupState(_mainMenuGroup, 1f, false); if (_mainMenuGroup != null) { _mainMenuGroup.gameObject.SetActive(true); } SetCanvasGroupState(_selectSaveContainerCanvasGroup, 0f, false); SetCanvasGroupState(_selectSaveWindowCanvasGroup, 0f, false); if (_pressStartText != null) { var color = _pressStartText.color; color.a = 1f; _pressStartText.color = color; _pressStartText.gameObject.SetActive(true); } StartPressStartPulse(); _introGroupDissolveGroup?.SetDissolveAmount(0f); _selectSaveWindowFog?.FogSet(0f); } private void OnIntroSubmit(InputAction.CallbackContext ctx) { if (_introFinished || _introTransitioning) { return; } if (!ctx.performed) { return; } TransitionFromIntroToMainMenu().Forget(); } private void OnBackRequested(UIBackRequestedEvent _) { if (!_introFinished || _selectSaveWindow == null || _selectSaveWindowCanvasGroup == null) { return; } if (!_selectSaveWindow.gameObject.activeInHierarchy || !_selectSaveWindowCanvasGroup.interactable) { return; } _selectSaveWindow.HandleBackRequest(); } private async UniTaskVoid TransitionFromIntroToMainMenu() { if (_introFinished || _introTransitioning) { return; } _introTransitioning = true; ResetIntroCtsAndCancelRunning(); try { var fadeTextTask = FadeOutPressStartText(_introCts.Token); var dissolveTask = _introGroupDissolveGroup != null ? _introGroupDissolveGroup.DissolveOut(false).AttachExternalCancellation(_introCts.Token) : UniTask.CompletedTask; await UniTask.WhenAll(fadeTextTask, dissolveTask); } catch (OperationCanceledException) { _introTransitioning = false; return; } finally { _pressStartFadeSequence = default; } SetCanvasGroupState(_introScreenCanvas, 0f, false); if (_introScreenCanvas != null) { _introScreenCanvas.gameObject.SetActive(false); } if (_mainMenuGroup != null) { _mainMenuGroup.gameObject.SetActive(true); } SetCanvasGroupState(_mainMenuGroup, 1f, true); _introFinished = true; _introTransitioning = false; _mainMenuSelectionGroup?.SelectIndex(0, true); } private async UniTask FadeOutPressStartText(CancellationToken token) { if (_pressStartText == null) { return; } _pressStartFadeSequence = Sequence.Create(useUnscaledTime: true) .Group(Tween.Alpha(_pressStartText, new TweenSettings { startValue = _pressStartText.color.a, endValue = 0f, settings = _pressStartFadeTweenSettings })); await _pressStartFadeSequence.ToUniTask(cancellationToken: token); var color = _pressStartText.color; color.a = 0f; _pressStartText.color = color; _pressStartText.gameObject.SetActive(false); } private void StartPressStartPulse() { StopSequence(ref _pressStartPulseSequence); if (_pressStartText == null) { return; } _pressStartPulseSequence = Sequence.Create( useUnscaledTime: true, cycleMode: Sequence.SequenceCycleMode.Yoyo, cycles: -1) .Group(Tween.Alpha(_pressStartText, new TweenSettings { startValue = _pressStartText.color.a, endValue = _pressStartPulseMinimumAlpha, settings = _pressStartPulseTweenSettings })); } private void OnStartClicked() { ShowSelectSaveWindow().Forget(); } private void OnSettingsClicked() { _eventCoordinator?.PublishImmediate(new UIToggleSettingsWindow(true)); } private void OnQuitClicked() { _gameService?.QuitGame(); } private async UniTask ShowSelectSaveWindow() { if (_selectSaveWindow == null || _selectSaveWindowCanvasGroup == null) { return; } ResetSelectSaveCtsAndCancelRunning(); _selectSaveWindow.gameObject.SetActive(true); _selectSaveWindow.Refresh(); SetCanvasGroupState(_selectSaveContainerCanvasGroup, 0f, false); SetCanvasGroupState(_selectSaveWindowCanvasGroup, 1f, false); // visible but not interactable yet SetCanvasGroupInteractivity(_mainMenuGroup, false); if (_selectSaveWindowCanvasGroup != null) { _selectSaveWindowFog.FogAmount = 0.2f; _selectSaveWindowFog.MaxFog = 0.4f; } try { // Step 1: fog rolls in fully if (_selectSaveWindowFog != null) { await _selectSaveWindowFog.FogIn().AttachExternalCancellation(_selectSaveCts.Token); } _selectSaveSequence = Sequence.Create(useUnscaledTime: true) .Group(Tween.Alpha(_selectSaveContainerCanvasGroup, new TweenSettings { startValue = 0f, endValue = 1f, settings = _selectSaveTweenSettings })) .Group(Tween.Alpha(_buttonsGroup, new TweenSettings { startValue = _buttonsGroup.alpha, endValue = 0f, settings = _selectSaveTweenSettings })); await _selectSaveSequence.ToUniTask(cancellationToken: _selectSaveCts.Token); } catch (OperationCanceledException) { return; } finally { _selectSaveSequence = default; } // Step 3: tween complete — enable interaction SetCanvasGroupInteractivity(_selectSaveContainerCanvasGroup, true); SetCanvasGroupInteractivity(_selectSaveWindowCanvasGroup, true); } private void CloseSelectSaveWindow() { CloseSelectSaveWindowInternal().Forget(); } private async UniTask CloseSelectSaveWindowInternal() { if (_selectSaveWindow == null || _selectSaveWindowCanvasGroup == null) { return; } ResetSelectSaveCtsAndCancelRunning(); // Disable interaction immediately, leave alpha for tween to read SetCanvasGroupInteractivity(_selectSaveContainerCanvasGroup, false); SetCanvasGroupInteractivity(_selectSaveWindowCanvasGroup, false); try { // Step 1: container fades out _selectSaveSequence = Sequence.Create(useUnscaledTime: true) .Group(Tween.Alpha(_selectSaveContainerCanvasGroup, new TweenSettings { startValue = _selectSaveContainerCanvasGroup.alpha, endValue = 0f, settings = _selectSaveTweenSettings })) .Group(Tween.Alpha(_buttonsGroup, new TweenSettings { startValue = _buttonsGroup.alpha, endValue = 1f, settings = _selectSaveTweenSettings })); await _selectSaveSequence.ToUniTask(cancellationToken: _selectSaveCts.Token); // Step 2: fog clears if (_selectSaveWindowFog != null) { await _selectSaveWindowFog.FogOut().AttachExternalCancellation(_selectSaveCts.Token); } } catch (OperationCanceledException) { return; } finally { _selectSaveSequence = default; } // Step 3: fully cleaned up SetCanvasGroupState(_selectSaveContainerCanvasGroup, 0f, false); SetCanvasGroupState(_selectSaveWindowCanvasGroup, 0f, false); _selectSaveWindow.gameObject.SetActive(false); _selectSaveWindowFog?.FogSet(0f); SetCanvasGroupState(_mainMenuGroup, 1f, true); } private void ResetIntroCtsAndCancelRunning() { StopSequence(ref _pressStartPulseSequence); StopSequence(ref _pressStartFadeSequence); CancelAndDispose(ref _introCts); _introCts = new CancellationTokenSource(); } private void StopIntroTweens() { StopSequence(ref _pressStartPulseSequence); StopSequence(ref _pressStartFadeSequence); CancelAndDispose(ref _introCts); } private void ResetSelectSaveCtsAndCancelRunning() { StopSequence(ref _selectSaveSequence); CancelAndDispose(ref _selectSaveCts); _selectSaveCts = new CancellationTokenSource(); } private void StopSelectSaveTween() { StopSequence(ref _selectSaveSequence); CancelAndDispose(ref _selectSaveCts); } private static void StopSequence(ref Sequence sequence) { if (sequence.isAlive) { sequence.Stop(); } sequence = default; } private static void CancelAndDispose(ref CancellationTokenSource cts) { if (cts == null) { return; } try { cts.Cancel(); } catch { } cts.Dispose(); cts = null; } private static Button ResolveButton(AnimatedSelectionButton animatedSelectionButton, Button fallbackButton) { if (animatedSelectionButton != null && animatedSelectionButton.Button != null) { return animatedSelectionButton.Button; } return fallbackButton; } private static void SetCanvasGroupState(CanvasGroup group, float alpha, bool inputEnabled) { if (group == null) return; group.alpha = alpha; group.interactable = inputEnabled; group.blocksRaycasts = inputEnabled; } private static void SetCanvasGroupInteractivity(CanvasGroup group, bool inputEnabled) { if (group == null) return; group.interactable = inputEnabled; group.blocksRaycasts = inputEnabled; } } }