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"); } } }