using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.InputSystem;
namespace RR.Common.UI
{
///
/// Базовый компонент модального окна.
/// Автоматически регистрируется в ModalManager при активации.
///
[DisallowMultipleComponent]
public class Modal : MonoBehaviour
{
[Header("Настройки модального окна")]
[Tooltip("Первый элемент, который получит фокус при открытии окна.")]
[SerializeField] private Selectable firstSelected;
[Tooltip("Блокировать интерактивность элементов за пределами модального окна.")]
[SerializeField] private bool blockBackground = true;
[Tooltip("Автоматически возвращать фокус внутрь окна, если он уходит наружу.")]
[SerializeField] private bool enforceFocus = true;
[Header("Анимация")]
[Tooltip("CanvasGroup для анимации появления/исчезновения.")]
[SerializeField] private CanvasGroup canvasGroup;
[Tooltip("Длительность анимации появления/исчезновения.")]
[SerializeField] private float fadeDuration = 0.15f;
[Header("Закрытие")]
[Tooltip("InputAction для закрытия модального окна (например, Escape).")]
[SerializeField] private InputActionReference closeAction;
[Tooltip("Кнопка закрытия модального окна.")]
[SerializeField] private Button closeButton;
///
/// Первый элемент для фокуса при открытии.
///
public Selectable FirstSelected => firstSelected;
///
/// Блокировать ли фон при открытии.
///
public bool BlockBackground => blockBackground;
///
/// Следить ли за фокусом и возвращать его внутрь окна.
///
public bool EnforceFocus => enforceFocus;
///
/// Событие, вызываемое перед открытием окна.
///
public event Action OnOpening;
///
/// Событие, вызываемое после открытия окна.
///
public event Action OnOpened;
///
/// Событие, вызываемое перед закрытием окна.
///
public event Action OnClosing;
///
/// Событие, вызываемое после закрытия окна.
///
public event Action OnClosed;
protected bool _isOpen;
protected Coroutine _fadeCoroutine;
///
/// Открыто ли модальное окно.
///
public bool IsOpen => _isOpen;
protected virtual void Awake()
{
if (closeButton)
{
closeButton.onClick.AddListener(Close);
}
// Если canvasGroup не указан или указан на родительском объекте,
// создаем/получаем CanvasGroup на самом модальном окне
if (!canvasGroup || canvasGroup.gameObject != gameObject)
{
canvasGroup = GetComponent();
if (!canvasGroup)
{
canvasGroup = gameObject.AddComponent();
}
}
// Изначально скрыто
if (canvasGroup)
{
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
gameObject.SetActive(false);
}
protected virtual void OnEnable()
{
// Регистрируемся в менеджере
ModalManager.Instance.Push(this);
// Подписываемся на действие закрытия
if (closeAction)
{
closeAction.action.Enable();
closeAction.action.performed += OnCloseActionPerformed;
}
}
protected virtual void OnDisable()
{
// Отписываемся от действия закрытия
if (closeAction)
{
closeAction.action.performed -= OnCloseActionPerformed;
}
// Удаляемся из менеджера
ModalManager.Instance.Pop(this);
}
private void OnCloseActionPerformed(InputAction.CallbackContext ctx)
{
Close();
}
///
/// Открывает модальное окно.
///
public virtual void Open()
{
if (_isOpen) return;
OnOpening?.Invoke();
gameObject.SetActive(true);
_isOpen = true;
if (canvasGroup)
{
if (_fadeCoroutine != null)
{
StopCoroutine(_fadeCoroutine);
}
// Проверяем, активен ли объект в иерархии после SetActive(true)
// Если родитель неактивен, запускаем корутину через ModalManager
if (gameObject.activeInHierarchy)
{
_fadeCoroutine = StartCoroutine(FadeCoroutine(0f, 1f, fadeDuration, () =>
{
OnOpened?.Invoke();
}));
}
else
{
// Объект неактивен в иерархии, запускаем через ModalManager
var modalManager = ModalManager.Instance;
if (modalManager != null && modalManager.gameObject.activeInHierarchy)
{
_fadeCoroutine = modalManager.StartCoroutine(FadeCoroutine(0f, 1f, fadeDuration, () =>
{
OnOpened?.Invoke();
}));
}
else
{
// Если ModalManager тоже недоступен, просто вызываем OnOpened без анимации
Debug.LogWarning($"[Modal] Cannot start fade coroutine for '{gameObject.name}' - object is inactive and ModalManager is not available");
canvasGroup.alpha = 1f;
OnOpened?.Invoke();
}
}
}
else
{
OnOpened?.Invoke();
}
}
///
/// Закрывает модальное окно.
///
public virtual void Close()
{
if (!_isOpen && !gameObject.activeSelf) return;
OnClosing?.Invoke();
if (canvasGroup && fadeDuration > 0f)
{
if (_fadeCoroutine != null)
{
StopCoroutine(_fadeCoroutine);
}
// Проверяем активность объекта в иерархии
if (gameObject.activeInHierarchy)
{
_fadeCoroutine = StartCoroutine(FadeCoroutine(canvasGroup.alpha, 0f, fadeDuration, () =>
{
gameObject.SetActive(false);
_isOpen = false;
OnClosed?.Invoke();
}));
}
else
{
// Если объект неактивен, запускаем через ModalManager
var modalManager = ModalManager.Instance;
if (modalManager != null && modalManager.gameObject.activeInHierarchy)
{
_fadeCoroutine = modalManager.StartCoroutine(FadeCoroutine(canvasGroup.alpha, 0f, fadeDuration, () =>
{
gameObject.SetActive(false);
_isOpen = false;
OnClosed?.Invoke();
}));
}
else
{
// Если ModalManager недоступен, просто закрываем без анимации
gameObject.SetActive(false);
_isOpen = false;
OnClosed?.Invoke();
}
}
}
else
{
if (canvasGroup)
{
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
gameObject.SetActive(false);
_isOpen = false;
OnClosed?.Invoke();
}
}
///
/// Мгновенно открывает окно без анимации.
///
public void OpenInstant()
{
if (_isOpen) return;
OnOpening?.Invoke();
gameObject.SetActive(true);
_isOpen = true;
if (canvasGroup)
{
canvasGroup.alpha = 1f;
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
}
OnOpened?.Invoke();
}
///
/// Мгновенно закрывает окно без анимации.
///
public void CloseInstant()
{
if (!_isOpen && !gameObject.activeSelf) return;
OnClosing?.Invoke();
if (canvasGroup)
{
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
gameObject.SetActive(false);
_isOpen = false;
OnClosed?.Invoke();
}
///
/// Переключает состояние окна (открыто/закрыто).
///
public void Toggle()
{
if (_isOpen)
Close();
else
Open();
}
private IEnumerator FadeCoroutine(float from, float to, float duration, Action onComplete = null)
{
if (!canvasGroup)
{
onComplete?.Invoke();
yield break;
}
// При открытии сразу делаем интерактивным
if (to > from)
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
}
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / duration);
canvasGroup.alpha = Mathf.Lerp(from, to, t);
yield return null;
}
canvasGroup.alpha = to;
// При закрытии выключаем интерактивность
if (to < from)
{
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
onComplete?.Invoke();
_fadeCoroutine = null;
}
///
/// Устанавливает первый элемент для фокуса (можно менять динамически).
///
public void SetFirstSelected(Selectable selectable)
{
firstSelected = selectable;
}
#if UNITY_EDITOR
protected virtual void Reset()
{
// Пытаемся автоматически найти CanvasGroup
canvasGroup = GetComponent();
if (!canvasGroup)
{
canvasGroup = GetComponentInChildren();
}
// Ищем кнопку закрытия
Button[] buttons = GetComponentsInChildren