using UnityEngine;
using UnityEngine.UI;
using UnityEngine.InputSystem;
using UnityEngine.EventSystems;
using RR.Game2D.Core.Managers;
using RR.Common.Settings;
namespace RR.Common.UI
{
///
/// Модальное окно настроек игры.
/// Управляет отображением и сохранением настроек через новый SettingsManager.
///
[RequireComponent(typeof(CanvasGroup))]
public class SettingsUI : Modal
{
[Header("Дополнительное управление")]
[SerializeField] private InputActionReference triggerKey;
[SerializeField] private Button saveButton;
[Header("Контент настроек")]
[SerializeField] private Transform content;
[SerializeField] private SettingsUIGenerator settingsGenerator;
[Header("Навигация")]
[Tooltip("Первый элемент для фокуса при открытии. Если не задан - ищется автоматически.")]
[SerializeField] private Selectable firstSelectable;
private SettingsManager settingsManager;
protected override void Awake()
{
base.Awake();
settingsManager = SettingsManager.Instance;
if (triggerKey != null)
triggerKey.action.started += OnTriggerKey;
if (settingsManager == null)
{
GameManager.Logger?.LogError(name, "SettingsManager is null!");
// Не выходим - подпишемся позже когда SettingsManager появится
}
else
{
settingsManager.OnSettingsLoaded += OnSettingsLoaded;
settingsManager.OnSettingsSaved += OnSettingsSaved;
}
if (saveButton)
saveButton.onClick.AddListener(SaveAndClose);
// Подписываемся на события settingsGenerator сразу (даже если неактивен)
SubscribeToGenerator();
}
private bool _subscribedToGenerator;
private void SubscribeToGenerator()
{
if (_subscribedToGenerator) return;
// Если не назначен - пробуем найти в детях
if (settingsGenerator == null)
{
settingsGenerator = GetComponentInChildren(true);
}
if (settingsGenerator != null)
{
settingsGenerator.OnBuildComplete += OnGeneratorBuildComplete;
settingsGenerator.OnGroupExpandedChanged += OnGroupExpandedChanged;
_subscribedToGenerator = true;
}
else
{
Debug.LogWarning("[SettingsUI] settingsGenerator is NULL - cannot subscribe to events! Make sure to assign it in Inspector or add SettingsUIGenerator as a child.");
}
}
protected override void OnEnable()
{
base.OnEnable();
// Пробуем подписаться снова если не удалось в Awake
SubscribeToGenerator();
}
private void OnDestroy()
{
if (settingsManager != null)
{
settingsManager.OnSettingsLoaded -= OnSettingsLoaded;
settingsManager.OnSettingsSaved -= OnSettingsSaved;
}
if (_subscribedToGenerator && settingsGenerator != null)
{
settingsGenerator.OnBuildComplete -= OnGeneratorBuildComplete;
settingsGenerator.OnGroupExpandedChanged -= OnGroupExpandedChanged;
}
if (triggerKey != null)
triggerKey.action.started -= OnTriggerKey;
if (saveButton)
saveButton.onClick.RemoveListener(SaveAndClose);
}
private void OnGeneratorBuildComplete()
{
// Настраиваем навигацию после построения
SetupExplicitNavigation();
// Выбираем первый элемент
if (settingsGenerator != null && settingsGenerator.FirstSelectable != null)
{
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(settingsGenerator.FirstSelectable.gameObject);
}
}
}
private void OnGroupExpandedChanged()
{
// При сворачивании/разворачивании группы - перестраиваем навигацию
StartCoroutine(RebuildNavigationOnGroupChange());
}
private System.Collections.IEnumerator RebuildNavigationOnGroupChange()
{
// Запоминаем текущий выбранный объект
GameObject currentSelected = EventSystem.current?.currentSelectedGameObject;
// Ждём кадр чтобы layout обновился
yield return null;
// Перестраиваем навигацию
SetupExplicitNavigation();
// Проверяем, активен ли текущий выбранный объект
if (currentSelected == null || !currentSelected.activeInHierarchy)
{
// Текущий элемент стал неактивен (свёрнут) - выбираем первый активный
var allSelectables = new System.Collections.Generic.List();
Transform searchRoot = settingsGenerator != null && settingsGenerator.ContentRoot != null
? settingsGenerator.ContentRoot
: content;
if (searchRoot != null)
{
CollectActiveSelectables(searchRoot, allSelectables);
if (allSelectables.Count > 0 && EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(allSelectables[0].gameObject);
}
}
}
}
///
/// Настраивает явную (Explicit) навигацию между всеми элементами настроек и кнопкой сохранения
///
private void SetupExplicitNavigation()
{
// Собираем все активные Selectable элементы
var allSelectables = new System.Collections.Generic.List();
// Ищем в ContentRoot из settingsGenerator или в content
Transform searchRoot = null;
if (settingsGenerator != null && settingsGenerator.ContentRoot != null)
{
searchRoot = settingsGenerator.ContentRoot;
}
else if (content != null)
{
searchRoot = content;
}
if (searchRoot == null)
{
Debug.LogWarning("[SettingsUI] No content root found for navigation setup");
return;
}
// Получаем все Selectable в порядке иерархии (только активные)
CollectActiveSelectables(searchRoot, allSelectables);
if (allSelectables.Count == 0)
{
Debug.LogWarning("[SettingsUI] No active selectables found for navigation setup");
return;
}
// Настраиваем вертикальную навигацию для каждого элемента
for (int i = 0; i < allSelectables.Count; i++)
{
var current = allSelectables[i];
var nav = new Navigation();
nav.mode = Navigation.Mode.Explicit;
// Вверх - предыдущий элемент или saveButton (для первого)
if (i == 0)
{
nav.selectOnUp = saveButton;
}
else
{
nav.selectOnUp = allSelectables[i - 1];
}
// Вниз - следующий элемент или saveButton (для последнего)
if (i == allSelectables.Count - 1)
{
nav.selectOnDown = saveButton;
}
else
{
nav.selectOnDown = allSelectables[i + 1];
}
// Горизонтальная навигация - оставляем null (не переходим влево/вправо на другие элементы)
nav.selectOnLeft = null;
nav.selectOnRight = null;
current.navigation = nav;
}
// Настраиваем кнопку сохранения
if (saveButton != null)
{
var saveNav = new Navigation();
saveNav.mode = Navigation.Mode.Explicit;
saveNav.selectOnUp = allSelectables[allSelectables.Count - 1]; // Последний элемент
saveNav.selectOnDown = allSelectables[0]; // Первый элемент (циклическая)
saveNav.selectOnLeft = null;
saveNav.selectOnRight = null;
saveButton.navigation = saveNav;
}
}
///
/// Рекурсивно собирает все активные Selectable элементы в порядке иерархии
///
private void CollectActiveSelectables(Transform parent, System.Collections.Generic.List result)
{
for (int i = 0; i < parent.childCount; i++)
{
var child = parent.GetChild(i);
// Пропускаем неактивные объекты
if (!child.gameObject.activeInHierarchy) continue;
// Проверяем, есть ли Selectable на этом объекте
var selectable = child.GetComponent();
if (selectable != null && selectable.interactable)
{
result.Add(selectable);
}
// Рекурсивно ищем в детях
CollectActiveSelectables(child, result);
}
}
private void OnTriggerKey(InputAction.CallbackContext ctx)
{
if (IsOpen)
{
SaveAndClose();
}
else
{
Open();
}
}
///
/// Переопределяем Open для обновления настроек при открытии.
///
public override void Open()
{
base.Open();
RefreshSettings();
// Выбираем первый элемент для навигации
SelectFirstElement();
}
private void SelectFirstElement()
{
// Запускаем корутину с задержкой, чтобы UI успел построиться
StartCoroutine(SelectFirstElementDelayed());
}
private System.Collections.IEnumerator SelectFirstElementDelayed()
{
// Ждём несколько кадров чтобы SettingsUIGenerator успел построить элементы
yield return null;
yield return null;
yield return new WaitForSecondsRealtime(0.1f);
// Настраиваем явную навигацию
SetupExplicitNavigation();
Selectable target = firstSelectable;
// Если не задан явно - ищем первый интерактивный элемент в content
if (target == null && content != null)
{
target = content.GetComponentInChildren();
}
// Если всё ещё не нашли - ищем в settingsGenerator
if (target == null && settingsGenerator != null)
{
target = settingsGenerator.GetComponentInChildren();
}
// Последняя попытка - ищем во всём объекте
if (target == null)
{
target = GetComponentInChildren();
}
if (target != null && EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(target.gameObject);
}
else
{
Debug.LogWarning("[SettingsUI] Could not find any selectable element for focus");
}
}
private void RefreshSettings()
{
// Если используется новый генератор
if (settingsGenerator != null)
{
settingsGenerator.RefreshValues();
}
}
private void Save()
{
if (settingsManager != null)
{
settingsManager.SaveSettings();
}
}
public void SaveAndClose()
{
Save();
Close();
}
private void OnSettingsLoaded()
{
RefreshSettings();
}
private void OnSettingsSaved()
{
GameManager.Logger?.Log(name, "Saved settings");
}
}
}