using System;
using RR.Common.Settings;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
namespace RR.Common.UI
{
public class SettingUIItem : MonoBehaviour
{
[Header("Common")] public TMP_Text titleText;
public TMP_Text descriptionText;
[Header("Controls")] public Toggle toggle;
public Slider slider;
public TMP_Dropdown dropdown;
public TMP_InputField input;
public Button button;
[Header("Option Picker")]
public Button prevButton;
public Button nextButton;
public TMP_Text optionValueText;
[Header("Optional")] public TMP_Text sliderValueText; // например справа от слайдера
private SettingEntry entry;
private bool showDescription;
///
/// Безопасно устанавливает значение настройки с проверкой на уничтоженный SettingsManager
///
private void SetSafe(string key, SettingValue value)
{
var manager = SettingsManager.Instance;
if (!manager)
{
Debug.LogWarning($"[SettingUIItem] SettingsManager.Instance is null, cannot set {key}");
return;
}
manager.Set(key, value);
}
public void Bind(SettingEntry entryToBind, bool showDescriptionOption)
{
this.entry = entryToBind;
this.showDescription = showDescriptionOption;
if (titleText)
titleText.text = string.IsNullOrWhiteSpace(entryToBind.displayName)
? entryToBind.key
: entryToBind.displayName;
if (descriptionText)
{
bool hasDesc = showDescriptionOption && !string.IsNullOrWhiteSpace(entryToBind.description);
descriptionText.gameObject.SetActive(hasDesc);
if (hasDesc) descriptionText.text = entryToBind.description;
}
// Отключаем все контролы, включим нужный
if (toggle) toggle.gameObject.SetActive(false);
if (slider) slider.gameObject.SetActive(false);
if (dropdown) dropdown.gameObject.SetActive(false);
if (input) input.gameObject.SetActive(false);
if (button) button.gameObject.SetActive(false);
if (prevButton) prevButton.gameObject.SetActive(false);
if (nextButton) nextButton.gameObject.SetActive(false);
if (optionValueText) optionValueText.gameObject.SetActive(false);
// Биндим по applyTarget/valueType
SetupControl();
RefreshFromSettings();
}
public void BindExternalDescriptionTarget(TMP_Text targetText)
{
if (entry == null) return;
var selectables = new List();
GetComponentsInChildren(true, selectables);
string description = entry.description ?? string.Empty;
foreach (Selectable selectable in selectables)
{
if (selectable == null) continue;
SelectableDescriptionTarget descriptionTarget =
selectable.GetComponent();
if (descriptionTarget == null)
descriptionTarget = selectable.gameObject.AddComponent();
descriptionTarget.Configure(targetText, description);
}
}
public void RefreshFromSettings()
{
if (entry == null) return;
SettingsManager manager = SettingsManager.Instance;
if (!manager) return;
var v = manager.Get(entry.key);
if (toggle && toggle.gameObject.activeSelf)
toggle.SetIsOnWithoutNotify(v.b);
if (slider && slider.gameObject.activeSelf)
slider.SetValueWithoutNotify(slider.wholeNumbers ? v.i : v.f);
if (dropdown && dropdown.gameObject.activeSelf)
dropdown.SetValueWithoutNotify(Mathf.Clamp(v.i, 0, dropdown.options.Count - 1));
if (input && input.gameObject.activeSelf)
input.SetTextWithoutNotify(v.s ?? "");
if (optionValueText && optionValueText.gameObject.activeSelf && entry.valueType == SettingValueType.OptionPicker)
UpdateOptionPickerDisplay(entry.options);
UpdateSliderValueText();
}
private void SetupControl()
{
if (entry == null) return;
// Настройки по valueType
switch (entry.valueType)
{
case SettingValueType.Bool:
UseToggle(v => SetSafe(entry.key, SettingValue.Bool(v)));
break;
case SettingValueType.Float:
// Для ValueSelector не создаём слайдер - его обрабатывает ValueSelectorRow
if (entry.floatDisplayFormat == FloatDisplayFormat.ValueSelector)
{
// ValueSelectorRow сам управляет значением
}
else
{
UseSliderFloat(entry.minFloat, entry.maxFloat, v =>
SetSafe(entry.key, SettingValue.Float(v)));
}
break;
case SettingValueType.Int:
// Для ValueSelector не создаём слайдер - его обрабатывает ValueSelectorRow
if (entry.intDisplayFormat == IntDisplayFormat.ValueSelector)
{
// ValueSelectorRow сам управляет значением
// SettingUIItem здесь только отображает label и description
}
else
{
// Int — делаем слайдер целочисленным (лучше чем инпут)
UseSliderInt(entry.minInt, entry.maxInt, v =>
SetSafe(entry.key, SettingValue.Int(v)));
}
break;
case SettingValueType.String:
UseInput(s => SetSafe(entry.key, SettingValue.String(s)));
break;
case SettingValueType.Button:
UseButton(() =>
{
// Вызываем биндеры на сцене (SettingsButtonBinder)
SettingsButtonDispatcher.Dispatch(entry.key);
});
break;
case SettingValueType.OptionPicker:
UseOptionPicker(entry.options, idx =>
SetSafe(entry.key, SettingValue.Int(idx)));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void UseToggle(UnityEngine.Events.UnityAction onChanged)
{
if (!toggle) return;
toggle.gameObject.SetActive(true);
toggle.onValueChanged.RemoveAllListeners();
toggle.onValueChanged.AddListener(onChanged);
}
private void UseSliderFloat(float min, float max, UnityEngine.Events.UnityAction onChanged)
{
if (!slider) return;
slider.gameObject.SetActive(true);
slider.wholeNumbers = false;
slider.minValue = Mathf.Min(min, max);
slider.maxValue = Mathf.Max(min, max);
slider.onValueChanged.RemoveAllListeners();
slider.onValueChanged.AddListener(v =>
{
onChanged(v);
UpdateSliderValueText();
});
}
private void UseSliderInt(int min, int max, System.Action onChanged)
{
if (!slider) return;
slider.gameObject.SetActive(true);
slider.wholeNumbers = true;
slider.minValue = Mathf.Min(min, max);
slider.maxValue = Mathf.Max(min, max);
slider.onValueChanged.RemoveAllListeners();
slider.onValueChanged.AddListener(v =>
{
onChanged(Mathf.RoundToInt(v));
UpdateSliderValueText();
});
}
private void UseDropdown(List options, System.Action onChanged)
{
if (!dropdown) return;
dropdown.gameObject.SetActive(true);
dropdown.ClearOptions();
dropdown.AddOptions(options ?? new List());
dropdown.onValueChanged.RemoveAllListeners();
dropdown.onValueChanged.AddListener(idx => onChanged(idx));
}
private void UseInput(System.Action onEndEdit)
{
if (!input) return;
input.gameObject.SetActive(true);
input.onEndEdit.RemoveAllListeners();
input.onEndEdit.AddListener(text => onEndEdit(text));
}
private void UseButton(System.Action onClick)
{
if (!button) return;
button.gameObject.SetActive(true);
// Убеждаемся что кнопка интерактивна
button.interactable = true;
// Навигация настраивается централизованно в SettingsUI.SetupExplicitNavigation()
// Устанавливаем текст кнопки
var buttonText = button.GetComponentInChildren();
if (buttonText && entry != null)
{
string text = !string.IsNullOrEmpty(entry.buttonText) ? entry.buttonText : entry.displayName;
if (string.IsNullOrEmpty(text)) text = entry.key;
buttonText.text = text;
}
// Устанавливаем цвет и спрайт кнопки
if (entry != null)
{
var image = button.GetComponent();
if (image)
{
// Устанавливаем цвет напрямую на Image
image.color = entry.buttonColor;
// Устанавливаем спрайт
if (entry.buttonSprite != null)
{
image.sprite = entry.buttonSprite;
}
}
}
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() => onClick());
}
private void UseOptionPicker(List options, System.Action onChanged)
{
if (options == null || options.Count == 0) return;
// Если есть dropdown - используем его
if (dropdown)
{
UseDropdown(options, onChanged);
return;
}
// Иначе используем кнопки prev/next
if (!prevButton || !nextButton || !optionValueText) return;
prevButton.gameObject.SetActive(true);
nextButton.gameObject.SetActive(true);
optionValueText.gameObject.SetActive(true);
prevButton.onClick.RemoveAllListeners();
nextButton.onClick.RemoveAllListeners();
prevButton.onClick.AddListener(() =>
{
if (entry == null) return;
int current = SettingsManager.Instance.Get(entry.key).i;
int newIndex = current - 1;
if (newIndex < 0) newIndex = options.Count - 1; // Wrap around
onChanged(newIndex);
UpdateOptionPickerDisplay(options);
});
nextButton.onClick.AddListener(() =>
{
if (entry == null) return;
int current = SettingsManager.Instance.Get(entry.key).i;
int newIndex = (current + 1) % options.Count; // Wrap around
onChanged(newIndex);
UpdateOptionPickerDisplay(options);
});
UpdateOptionPickerDisplay(options);
}
private void UpdateOptionPickerDisplay(List options)
{
if (!optionValueText || entry == null) return;
int currentIndex = SettingsManager.Instance.Get(entry.key).i;
if (currentIndex >= 0 && currentIndex < options.Count)
optionValueText.text = options[currentIndex];
}
private void UpdateSliderValueText()
{
if (!sliderValueText || !slider || !slider.gameObject.activeSelf) return;
if (slider.wholeNumbers)
{
sliderValueText.text = Mathf.RoundToInt(slider.value).ToString();
}
else
{
// Проверяем флаг displayAsPercentage
if (entry != null && entry.displayAsPercentage)
{
// Отображаем как проценты (0-100%)
float percentage = slider.value * 100f;
sliderValueText.text = percentage.ToString("0") + "%";
}
else
{
sliderValueText.text = slider.value.ToString("0.##");
}
}
}
}
}