using System; using System.Collections.Generic; using BriarQueen.Data.Identifiers; using BriarQueen.Framework.Coordinators.Events; using BriarQueen.Framework.Events.UI; using BriarQueen.Framework.Managers.Interaction; using BriarQueen.Framework.Managers.IO; using BriarQueen.Framework.Managers.UI.Base; using BriarQueen.Framework.Managers.UI.Events; using BriarQueen.Framework.Services.Settings; using Cysharp.Threading.Tasks; using UnityEngine; using VContainer; namespace BriarQueen.Framework.Managers.UI { /// /// UIManager: /// - Modal windows use the window stack /// - Non-modal UI (popups / fader) does not use the stack /// - HUD visibility is event-driven /// - Concrete UI implementations are hidden behind interfaces /// public class UIManager : IDisposable, IManager { private readonly EventCoordinator _eventCoordinator; private readonly InteractManager _interactManager; private readonly SaveManager _saveManager; private readonly SettingsService _settingsService; private readonly Dictionary _windows = new(); private readonly Stack _windowStack = new(); private bool _disposed; public bool Initialized { get; private set; } private IHud _hudContainer; private IPopup _infoPopup; private IPopup _tutorialPopup; private IScreenFader _screenFader; [Inject] public UIManager( EventCoordinator eventCoordinator, InteractManager interactManager, SettingsService settingsService, SaveManager saveManager) { _eventCoordinator = eventCoordinator; _interactManager = interactManager; _settingsService = settingsService; _saveManager = saveManager; } private IUIWindow ActiveWindow => _windowStack.Count > 0 ? _windowStack.Peek() : null; public bool IsAnyUIOpen => _windowStack.Count > 0; public void Initialize() { if (Initialized) return; _disposed = false; SubscribeToEvents(); Initialized = true; } public void Dispose() { if (!Initialized || _disposed) return; _disposed = true; UnsubscribeFromEvents(); ResetUIStateHard(); Initialized = false; } private void SubscribeToEvents() { _eventCoordinator.Subscribe(OnPauseClickReceived); _eventCoordinator.Subscribe(ToggleCodexWindow); _eventCoordinator.Subscribe(ToggleSettingsWindow); _eventCoordinator.Subscribe(OnFadeEvent); _eventCoordinator.Subscribe(OnDisplayInteractText); _eventCoordinator.Subscribe(OnTutorialDisplayPopup); _eventCoordinator.Subscribe(OnCodexChangedEvent); _eventCoordinator.Subscribe(OnHudToggleEvent); _eventCoordinator.Subscribe(OnToolbeltChangedEvent); } private void UnsubscribeFromEvents() { _eventCoordinator.Unsubscribe(OnPauseClickReceived); _eventCoordinator.Unsubscribe(ToggleCodexWindow); _eventCoordinator.Unsubscribe(ToggleSettingsWindow); _eventCoordinator.Unsubscribe(OnFadeEvent); _eventCoordinator.Unsubscribe(OnDisplayInteractText); _eventCoordinator.Unsubscribe(OnTutorialDisplayPopup); _eventCoordinator.Unsubscribe(OnCodexChangedEvent); _eventCoordinator.Unsubscribe(OnHudToggleEvent); _eventCoordinator.Unsubscribe(OnToolbeltChangedEvent); } public void RegisterWindow(IUIWindow window) { if (window == null) return; _windows[window.WindowType] = window; window.Hide().Forget(); } public void RegisterHUD(IHud hudContainer) { _hudContainer = hudContainer; if (_hudContainer != null) { _hudContainer.Hide().Forget(); _interactManager.AddUIRaycaster(_hudContainer.Raycaster); } } public void RegisterInfoPopup(IPopup infoPopup) { _infoPopup = infoPopup; if (_infoPopup != null) _infoPopup.Hide().Forget(); } public void RegisterTutorialPopup(IPopup tutorialPopup) { _tutorialPopup = tutorialPopup; if (_tutorialPopup != null) _tutorialPopup.Hide().Forget(); } public void RegisterScreenFader(IScreenFader screenFader) { _screenFader = screenFader; } private IUIWindow GetWindow(WindowType windowType) { return _windows.TryGetValue(windowType, out var window) ? window : null; } private async UniTask ApplyHudVisibility(bool visible) { if (_disposed || _hudContainer == null) return; try { if (visible) await _hudContainer.Show(); else await _hudContainer.Hide(); } catch (Exception ex) { Debug.LogError($"[UIManager] ApplyHudVisibility error: {ex}"); } } private void OnPauseClickReceived(PauseButtonClickedEvent _) { if (_windowStack.Count > 0) { CloseTopWindow(); return; } OpenWindow(WindowType.PauseMenuWindow); } private void ToggleSettingsWindow(UIToggleSettingsWindow eventData) { if (eventData.Show) OpenWindow(WindowType.SettingsWindow); else CloseWindow(WindowType.SettingsWindow); } private void ToggleCodexWindow(ToggleCodexEvent eventData) { if (eventData.Shown) OpenWindow(WindowType.CodexWindow); else CloseWindow(WindowType.CodexWindow); } private void OnCodexChangedEvent(CodexChangedEvent eventData) { if (_infoPopup == null) return; var duration = _settingsService?.Game?.PopupDisplayDuration ?? 3f; var codexText = GetCodexTextForEntry(eventData.EntryType); _infoPopup.Play(codexText, duration).Forget(); } private void OnToolbeltChangedEvent(ToolbeltChangedEvent eventData) { if (_infoPopup == null) return; var duration = _settingsService?.Game?.PopupDisplayDuration ?? 3f; var toolText = GetToolbeltTextForEntry(eventData.ToolID, eventData.Lost); _infoPopup.Play(toolText, duration).Forget(); } private string GetToolbeltTextForEntry(ToolID toolID, bool lost) { if (lost) return $"You lost the {toolID.ToString()}."; else return $"You gained the {toolID.ToString()}."; } private string GetCodexTextForEntry(CodexType codexType) { return codexType switch { CodexType.BookEntry => "You've acquired a new book entry.", CodexType.PuzzleClue => "You've acquired a new puzzle clue.", CodexType.Photo => "You've acquired a new photo.", _ => string.Empty }; } private void OnTutorialDisplayPopup(DisplayTutorialPopupEvent eventData) { if (_tutorialPopup == null) return; if (!_settingsService.AreTutorialsEnabled()) return; var duration = 3f; var tutorialText = TutorialPopupTexts.AllPopups[eventData.TutorialID]; _tutorialPopup.Play(tutorialText, duration).Forget(); } private void OnDisplayInteractText(DisplayInteractEvent eventData) { if (_hudContainer == null) return; _hudContainer.DisplayInteractText(eventData.Message).Forget(); } private void OnFadeEvent(FadeEvent eventData) { if (_screenFader == null) return; if (eventData.Hidden) _screenFader.FadeFromAsync(eventData.Duration).Forget(); else _screenFader.FadeToAsync(eventData.Duration).Forget(); } private void OnHudToggleEvent(UIToggleHudEvent eventData) { ApplyHudVisibility(eventData.Show).Forget(); } public void OpenWindow(WindowType windowType) { OpenWindowInternal(windowType).Forget(); } private async UniTask OpenWindowInternal(WindowType windowType) { if (_disposed) return; var window = GetWindow(windowType); if (window == null) { Debug.LogError($"[UIManager] Window of type {windowType} not registered."); return; } if (ActiveWindow == window) return; if (ActiveWindow != null) await ActiveWindow.Hide(); _windowStack.Push(window); await window.Show(); NotifyUIStackChanged(); } public void CloseWindow(WindowType windowType) { CloseWindowInternal(windowType).Forget(); } private async UniTask CloseWindowInternal(WindowType windowType) { if (_disposed || _windowStack.Count == 0) return; var target = GetWindow(windowType); if (target == null) return; while (_windowStack.Count > 0) { var current = _windowStack.Pop(); if (current != null) await current.Hide(); if (current == target) break; } if (ActiveWindow != null) await ActiveWindow.Show(); NotifyUIStackChanged(); } public void CloseTopWindow() { CloseTopWindowInternal().Forget(); } private async UniTask CloseTopWindowInternal() { if (_disposed || _windowStack.Count == 0) return; var top = _windowStack.Pop(); if (top != null) await top.Hide(); if (ActiveWindow != null) await ActiveWindow.Show(); NotifyUIStackChanged(); } public void ResetUIState() { ResetUIStateAsync().Forget(); } public async UniTask ResetUIStateAsync() { while (_windowStack.Count > 0) { var window = _windowStack.Pop(); if (window != null) { try { await window.Hide(); } catch { } } } if (_tutorialPopup != null) { try { await _tutorialPopup.Hide(); } catch { } } if (_infoPopup != null) { try { await _infoPopup.Hide(); } catch { } } NotifyUIStackChanged(); } private void ResetUIStateHard() { foreach (var kv in _windows) { if (kv.Value is Component component && component != null) component.gameObject.SetActive(false); } if (_tutorialPopup?.GameObject != null) _tutorialPopup.GameObject.SetActive(false); if (_infoPopup?.GameObject != null) _infoPopup.GameObject.SetActive(false); if (_hudContainer is Component hudComponent && hudComponent != null) hudComponent.gameObject.SetActive(false); if (_screenFader is Component faderComponent && faderComponent != null) faderComponent.gameObject.SetActive(false); _windowStack.Clear(); } private void NotifyUIStackChanged() { _eventCoordinator.Publish(new UIStackChangedEvent(_windowStack.Count > 0)); } } }