Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Добавил новый тип отображения настроек с целочисленным значением - У этого типа можно настраивать шаг, выглядит приятнее
341 lines
12 KiB
C#
341 lines
12 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using RR.Common.Settings;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
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;
|
||
|
||
/// <summary>
|
||
/// Безопасно устанавливает значение настройки с проверкой на уничтоженный SettingsManager
|
||
/// </summary>
|
||
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 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:
|
||
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<bool> 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<float> 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<int> 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<string> options, System.Action<int> onChanged)
|
||
{
|
||
if (!dropdown) return;
|
||
|
||
dropdown.gameObject.SetActive(true);
|
||
dropdown.ClearOptions();
|
||
dropdown.AddOptions(options ?? new List<string>());
|
||
|
||
dropdown.onValueChanged.RemoveAllListeners();
|
||
dropdown.onValueChanged.AddListener(idx => onChanged(idx));
|
||
}
|
||
|
||
private void UseInput(System.Action<string> 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<TMP_Text>();
|
||
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<UnityEngine.UI.Image>();
|
||
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<string> options, System.Action<int> 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<string> 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.##");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |