Files

434 lines
13 KiB
C#

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
{
/// <summary>
/// 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
/// </summary>
public class UIManager : IDisposable, IManager
{
private readonly EventCoordinator _eventCoordinator;
private readonly InteractManager _interactManager;
private readonly SaveManager _saveManager;
private readonly SettingsService _settingsService;
private readonly Dictionary<WindowType, IUIWindow> _windows = new();
private readonly Stack<IUIWindow> _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<PauseButtonClickedEvent>(OnPauseClickReceived);
_eventCoordinator.Subscribe<ToggleCodexEvent>(ToggleCodexWindow);
_eventCoordinator.Subscribe<UIToggleSettingsWindow>(ToggleSettingsWindow);
_eventCoordinator.Subscribe<FadeEvent>(OnFadeEvent);
_eventCoordinator.Subscribe<DisplayInteractEvent>(OnDisplayInteractText);
_eventCoordinator.Subscribe<DisplayTutorialPopupEvent>(OnTutorialDisplayPopup);
_eventCoordinator.Subscribe<CodexChangedEvent>(OnCodexChangedEvent);
_eventCoordinator.Subscribe<UIToggleHudEvent>(OnHudToggleEvent);
_eventCoordinator.Subscribe<ToolbeltChangedEvent>(OnToolbeltChangedEvent);
}
private void UnsubscribeFromEvents()
{
_eventCoordinator.Unsubscribe<PauseButtonClickedEvent>(OnPauseClickReceived);
_eventCoordinator.Unsubscribe<ToggleCodexEvent>(ToggleCodexWindow);
_eventCoordinator.Unsubscribe<UIToggleSettingsWindow>(ToggleSettingsWindow);
_eventCoordinator.Unsubscribe<FadeEvent>(OnFadeEvent);
_eventCoordinator.Unsubscribe<DisplayInteractEvent>(OnDisplayInteractText);
_eventCoordinator.Unsubscribe<DisplayTutorialPopupEvent>(OnTutorialDisplayPopup);
_eventCoordinator.Unsubscribe<CodexChangedEvent>(OnCodexChangedEvent);
_eventCoordinator.Unsubscribe<UIToggleHudEvent>(OnHudToggleEvent);
_eventCoordinator.Unsubscribe<ToolbeltChangedEvent>(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));
}
}
}