using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using RR.Common.Settings;
using RR.Common.Settings.UI;
using TMPro;
using UnityEngine;
namespace RR.Common.UI
{
public class SettingsUIGenerator : MonoBehaviour
{
[Header("Source")] [SerializeField] private SettingsDatabase database;
[Header("Layout")] [SerializeField] private Transform contentRoot;
///
/// Корневой Transform для контента настроек
///
public Transform ContentRoot => contentRoot;
[Header("Grouping")] [SerializeField] private bool groupByGroup = true;
[SerializeField] private bool collapsibleGroups = true;
[SerializeField] private SettingsGroupPanel groupPanelPrefab; // префаб группы (foldout)
[SerializeField] private bool defaultGroupsExpanded = true;
[SerializeField] private bool rememberGroupFoldout = true;
[Tooltip("Если заполнить — группы будут идти в этом порядке, остальные добавятся в конец по алфавиту.")]
[SerializeField]
private List groupOrder = new();
[Header("Sorting")] [SerializeField] private bool sortByNameInsideGroup = true;
[Header("Prefabs (rows)")] [SerializeField]
private SettingUIItem toggleRowPrefab;
[SerializeField] private SettingUIItem sliderRowPrefab;
[SerializeField] private SettingUIItem valueSelectorRowPrefab;
[SerializeField] private SettingUIItem dropdownRowPrefab;
[SerializeField] private SettingUIItem inputRowPrefab;
[SerializeField] private SettingUIItem buttonRowPrefab;
[SerializeField] private SettingUIItem optionPickerRowPrefab;
[Header("Behaviour")] [SerializeField] private bool buildOnEnable = true;
[SerializeField] private bool clearBeforeBuild = true;
[SerializeField] private bool showDescription = true;
private readonly List spawnedItems = new();
private readonly List spawnedGroups = new();
private Coroutine _buildCoroutine;
///
/// Событие вызывается когда построение UI завершено
///
public event Action OnBuildComplete;
///
/// Событие вызывается при сворачивании/разворачивании любой группы настроек
///
public event Action OnGroupExpandedChanged;
///
/// Первый созданный Selectable элемент
///
public UnityEngine.UI.Selectable FirstSelectable { get; private set; }
private void OnEnable()
{
if (buildOnEnable)
{
// Запускаем корутину, которая ждёт SettingsManager
if (_buildCoroutine != null) StopCoroutine(_buildCoroutine);
_buildCoroutine = StartCoroutine(BuildWhenReady());
}
else
{
RefreshValues();
}
}
private void OnDisable()
{
if (_buildCoroutine != null)
{
StopCoroutine(_buildCoroutine);
_buildCoroutine = null;
}
}
private IEnumerator BuildWhenReady()
{
// Ждём пока SettingsManager инициализируется (максимум 2 секунды)
float timeout = 2f;
float elapsed = 0f;
while (!SettingsManager.Instance && elapsed < timeout)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}
if (!SettingsManager.Instance)
{
Debug.LogError("[SettingsUIGenerator] SettingsManager.Instance not found after waiting. Make sure SettingsManager is in scene.");
yield break;
}
Build();
// Ждём кадр чтобы layout обновился
yield return null;
// Принудительно обновляем layout для корректной навигации
if (contentRoot != null && contentRoot is RectTransform rectTransform)
{
UnityEngine.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
}
// Ещё кадр для применения layout
yield return null;
// Находим первый Selectable элемент
if (contentRoot != null)
{
FirstSelectable = contentRoot.GetComponentInChildren();
}
// Оповещаем о завершении
OnBuildComplete?.Invoke();
_buildCoroutine = null;
}
public void Build()
{
if (!database || !contentRoot)
{
Debug.LogWarning("[SettingsUIGenerator] database/contentRoot not set.");
return;
}
if (!SettingsManager.Instance)
{
Debug.LogWarning("[SettingsUIGenerator] SettingsManager.Instance not found in scene.");
return;
}
if (clearBeforeBuild) Clear();
spawnedItems.Clear();
List entries = database.entries
.Where(e => e != null && !string.IsNullOrWhiteSpace(e.key) && e.shownInSettings)
.ToList();
if (!groupByGroup)
{
// Без групп — просто одним списком
if (sortByNameInsideGroup)
entries = entries.OrderBy(e => e.displayName ?? e.key).ToList();
foreach (SettingEntry entry in entries)
SpawnSettingRow(entry, contentRoot);
return;
}
// Группировка по e.group
Dictionary> groups = entries
.GroupBy(e => string.IsNullOrWhiteSpace(e.group) ? "General" : e.group)
.ToDictionary(g => g.Key, g => g.ToList());
// Порядок групп: сначала заданные, потом остальные по алфавиту
List orderedGroupNames = new();
if (groupOrder != null && groupOrder.Count > 0)
{
orderedGroupNames.AddRange(
groupOrder.Where(g => !string.IsNullOrWhiteSpace(g) && groups.ContainsKey(g)));
}
List rest = groups.Keys
.Except(orderedGroupNames)
.OrderBy(x => x)
.ToList();
orderedGroupNames.AddRange(rest);
foreach (string groupName in orderedGroupNames)
{
List list = groups[groupName];
if (sortByNameInsideGroup)
list = list.OrderBy(e => e.displayName ?? e.key).ToList();
Transform parentForRows = contentRoot;
// Создаём UI группы (foldout) если включено
if (collapsibleGroups && groupPanelPrefab)
{
SettingsGroupPanel panel = Instantiate(groupPanelPrefab, contentRoot);
panel.name = $"Group_{groupName}";
string prefsKey = $"settings.ui.group.{groupName}";
panel.Init(groupName, prefsKey, defaultGroupsExpanded, rememberGroupFoldout);
// Подписываемся на изменение состояния группы
panel.OnExpandedChanged += OnGroupPanelExpandedChanged;
spawnedGroups.Add(panel);
parentForRows = panel.ContentRoot;
}
else
{
// Если не используем foldout — можно хотя бы заголовок простым текстом
// (не обязательно; если не надо — удали блок)
GameObject headerGO = new GameObject($"Header_{groupName}", typeof(RectTransform),
typeof(TextMeshProUGUI));
headerGO.transform.SetParent(contentRoot, false);
TextMeshProUGUI text = headerGO.GetComponent();
text.text = groupName;
text.fontSize = 28;
text.fontStyle = FontStyles.Bold;
}
foreach (var entry in list)
SpawnSettingRow(entry, parentForRows);
}
}
private void SpawnSettingRow(SettingEntry entry, Transform parent)
{
if (entry == null || parent == null)
{
Debug.LogWarning("[SettingsUIGenerator] Cannot spawn setting row: entry or parent is null");
return;
}
SettingUIItem prefab = PickPrefab(entry);
if (!prefab)
{
Debug.LogWarning(
$"[SettingsUIGenerator] No prefab for '{entry.key}' ({entry.valueType}/{entry.applyTarget})");
return;
}
SettingUIItem item = Instantiate(prefab, parent);
item.name = $"Setting_{entry.key}";
item.Bind(entry, showDescription);
// Если это Int ValueSelector, настраиваем компонент ValueSelectorRow
if (entry.valueType == SettingValueType.Int &&
entry.intDisplayFormat == IntDisplayFormat.ValueSelector)
{
ValueSelectorRow valueSelector = item.GetComponent();
if (valueSelector != null)
{
int currentValue = SettingsManager.Instance.GetInt(entry.key);
valueSelector.Setup(entry, currentValue);
}
else
{
Debug.LogWarning($"[SettingsUIGenerator] ValueSelectorRow component not found on prefab for '{entry.key}'");
}
}
// Если это Float ValueSelector, настраиваем компонент ValueSelectorRow
if (entry.valueType == SettingValueType.Float &&
entry.floatDisplayFormat == FloatDisplayFormat.ValueSelector)
{
ValueSelectorRow valueSelector = item.GetComponent();
if (valueSelector != null)
{
float currentValue = SettingsManager.Instance.GetFloat(entry.key);
valueSelector.Setup(entry, currentValue);
}
else
{
Debug.LogWarning($"[SettingsUIGenerator] ValueSelectorRow component not found on prefab for '{entry.key}'");
}
}
if (entry.valueType == SettingValueType.OptionPicker &&
entry.optionPickerDisplayFormat == OptionPickerDisplayFormat.ValueSelector)
{
ValueSelectorRow valueSelector = item.GetComponent();
if (valueSelector != null)
{
int currentValue = SettingsManager.Instance.GetInt(entry.key);
valueSelector.SetupOptionPicker(entry, currentValue);
}
else
{
Debug.LogWarning($"[SettingsUIGenerator] ValueSelectorRow component not found on prefab for '{entry.key}'");
}
}
spawnedItems.Add(item);
}
private SettingUIItem PickPrefab(SettingEntry e)
{
return e.valueType switch
{
SettingValueType.Bool => toggleRowPrefab,
SettingValueType.Float => e.floatDisplayFormat == FloatDisplayFormat.ValueSelector
? valueSelectorRowPrefab
: sliderRowPrefab,
SettingValueType.Int => e.intDisplayFormat == IntDisplayFormat.ValueSelector
? valueSelectorRowPrefab
: sliderRowPrefab ? sliderRowPrefab : dropdownRowPrefab,
SettingValueType.String => inputRowPrefab,
SettingValueType.Button => buttonRowPrefab,
SettingValueType.OptionPicker => e.optionPickerDisplayFormat == OptionPickerDisplayFormat.ValueSelector
? valueSelectorRowPrefab
: optionPickerRowPrefab ? optionPickerRowPrefab : dropdownRowPrefab,
_ => null
};
}
public void RefreshValues()
{
for (int i = 0; i < spawnedItems.Count; i++)
{
if (spawnedItems[i])
{
spawnedItems[i].RefreshFromSettings();
// Если есть компонент ValueSelectorRow, обновляем и его
ValueSelectorRow valueSelector = spawnedItems[i].GetComponent();
if (valueSelector != null && !string.IsNullOrEmpty(valueSelector.Key))
{
if (valueSelector.IsFloat)
{
float fValue = SettingsManager.Instance.GetFloat(valueSelector.Key);
valueSelector.SetValue(fValue);
}
else
{
int iValue = SettingsManager.Instance.GetInt(valueSelector.Key);
valueSelector.SetValue(iValue);
}
}
}
}
}
private void OnGroupPanelExpandedChanged(SettingsGroupPanel panel, bool expanded)
{
// Уведомляем подписчиков (например SettingsUI) об изменении состояния группы
OnGroupExpandedChanged?.Invoke();
}
public void Clear()
{
// Отписываемся от событий групп
foreach (var group in spawnedGroups)
{
if (group != null)
group.OnExpandedChanged -= OnGroupPanelExpandedChanged;
}
spawnedGroups.Clear();
if (contentRoot != null)
{
for (int i = contentRoot.childCount - 1; i >= 0; i--)
{
Transform child = contentRoot.GetChild(i);
if (child != null)
Destroy(child.gameObject);
}
}
spawnedItems.Clear();
}
}
}