434 lines
13 KiB
C#
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));
|
|
}
|
|
}
|
|
} |