Danro/Assets/Scripts/UI/TextInput/TextInputCell.cs
Danil Iarosh 2417f58f61
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
feat: Add text input system and balance management with settings improvements
- Добавлена система ввода текста через модальное окно с ячейками для аркадного автомата
- Реализован TextInputModal с поддержкой кастомных алфавитов и навигации
- Добавлен TextInputCell для ввода отдельных символов с циклической навигацией
- Создан TextInputSettingsBinder для интеграции ввода текста с системой настроек
- Реализована компактная сериализация настроек (только нужные поля в JSON)

- Внедрена система баланса игрока (BalanceManager)
- Баланс начисляется при оплате импульсом, списывается при начале/продолжении игры
- Добавлен InactivityManager для выхода в меню при отсутствии активности (5 минут)

- Улучшена система настроек
- Добавлены флаги shownInSettings и exposed для управления видимостью настроек
- Реализована автоматическая регистрация/отписка SettingsButtonBinder при активации/деактивации
- Исправлена загрузка настроек с проверкой типов через Sanitize()
- SettingsApplier упрощен - оставлена только поддержка AudioManager
- Добавлен ValueSelectorRow для выбора целочисленных значений с шагом

- Удалены примеры и документация из production кода (QUICK_SETUP.md, TextInputExample, AdvancedLoggingExample)
- Убран весь отладочный вывод Debug.Log из системы настроек и ввода текста
- Очищены неразрывные пробелы из файлов проекта
- Добавлены префабы UI (InputCell)
2026-02-11 16:27:33 +03:00

247 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace RR.Common.UI
{
/// <summary>
/// Ячейка для ввода одного символа.
/// Символы переключаются циклически при нажатии кнопки "вверх".
/// Наследуется от Selectable для поддержки навигации.
/// </summary>
public class TextInputCell : Selectable
{
[Header("UI References")]
[SerializeField] private TextMeshProUGUI symbolText;
[SerializeField] private Image backgroundImage;
[SerializeField] private Image selectionHighlight;
[Header("Visual Settings")]
[SerializeField] private Color normalColor = Color.white;
[SerializeField] private Color selectedColor = Color.yellow;
[SerializeField] private Color highlightColor = new Color(1f, 1f, 0f, 0.3f);
private string[] alphabet;
private int currentIndex;
private bool isSelected;
/// <summary>
/// Событие, вызываемое при изменении символа
/// </summary>
public event System.Action<TextInputCell> OnSymbolChanged;
/// <summary>
/// Текущий выбранный символ
/// </summary>
public string CurrentSymbol => alphabet != null && alphabet.Length > 0 ? alphabet[currentIndex] : "";
/// <summary>
/// Индекс текущего символа
/// </summary>
public int CurrentIndex => currentIndex;
protected override void Awake()
{
base.Awake();
if (selectionHighlight)
{
selectionHighlight.gameObject.SetActive(false);
}
// Устанавливаем targetGraphic для визуальной обратной связи
if (backgroundImage != null)
{
targetGraphic = backgroundImage;
}
}
/// <summary>
/// Переопределяем DoStateTransition для кастомной визуализации
/// </summary>
protected override void DoStateTransition(SelectionState state, bool instant)
{
base.DoStateTransition(state, instant);
if (!gameObject.activeInHierarchy)
return;
// Обновляем визуализацию в зависимости от состояния
bool shouldHighlight = (state == SelectionState.Highlighted ||
state == SelectionState.Selected ||
state == SelectionState.Pressed) && isSelected;
if (selectionHighlight != null)
{
selectionHighlight.gameObject.SetActive(shouldHighlight);
}
}
/// <summary>
/// Переопределяем OnMove для обработки вверх/вниз и изменения символа
/// </summary>
public override void OnMove(UnityEngine.EventSystems.AxisEventData eventData)
{
switch (eventData.moveDir)
{
case UnityEngine.EventSystems.MoveDirection.Up:
// Вверх всегда меняет символ
NextSymbol();
eventData.Use(); // Отмечаем событие как обработанное
break;
case UnityEngine.EventSystems.MoveDirection.Down:
// Вниз всегда меняет символ (на предыдущий)
PreviousSymbol();
eventData.Use();
break;
case UnityEngine.EventSystems.MoveDirection.Left:
case UnityEngine.EventSystems.MoveDirection.Right:
// Для влево/вправо используем стандартную навигацию
base.OnMove(eventData);
break;
}
}
/// <summary>
/// Инициализирует ячейку с алфавитом
/// </summary>
/// <param name="newAlphabet">Алфавит символов</param>
/// <param name="startIndex">Начальный индекс (используется только если symbol = null)</param>
/// <param name="symbol">Начальный символ (если указан, ищется в алфавите)</param>
public void Initialize(string[] newAlphabet, int startIndex = 0, string symbol = null)
{
this.alphabet = newAlphabet;
// Если указан символ, ищем его в алфавите
if (!string.IsNullOrEmpty(symbol))
{
bool found = false;
// Ищем точное совпадение
for (int i = 0; i < newAlphabet.Length; i++)
{
if (newAlphabet[i] == symbol)
{
this.currentIndex = i;
found = true;
break;
}
}
// Если не найдено, ищем без учета регистра
if (!found)
{
for (int i = 0; i < newAlphabet.Length; i++)
{
if (string.Equals(newAlphabet[i], symbol, System.StringComparison.OrdinalIgnoreCase))
{
this.currentIndex = i;
found = true;
break;
}
}
}
// Если символ не найден, устанавливаем первый символ из алфавита
if (!found)
{
this.currentIndex = 0;
}
}
else
{
// Если символ не указан, используем startIndex
this.currentIndex = Mathf.Clamp(startIndex, 0, newAlphabet.Length - 1);
}
UpdateDisplay();
}
/// <summary>
/// Переключает на следующий символ в алфавите (циклически)
/// </summary>
public void NextSymbol()
{
if (alphabet == null || alphabet.Length == 0) return;
currentIndex = (currentIndex + 1) % alphabet.Length;
UpdateDisplay();
OnSymbolChanged?.Invoke(this);
}
/// <summary>
/// Переключает на предыдущий символ в алфавите (циклически)
/// </summary>
public void PreviousSymbol()
{
if (alphabet == null || alphabet.Length == 0) return;
currentIndex--;
if (currentIndex < 0)
currentIndex = alphabet.Length - 1;
UpdateDisplay();
OnSymbolChanged?.Invoke(this);
}
/// <summary>
/// Устанавливает индекс символа
/// </summary>
public void SetSymbolIndex(int index)
{
if (alphabet == null || alphabet.Length == 0) return;
currentIndex = Mathf.Clamp(index, 0, alphabet.Length - 1);
UpdateDisplay();
}
/// <summary>
/// Устанавливает состояние выделения ячейки
/// </summary>
public void SetCellSelected(bool selected)
{
isSelected = selected;
UpdateDisplay();
}
/// <summary>
/// Обновляет визуальное отображение ячейки
/// </summary>
private void UpdateDisplay()
{
// Обновляем текст символа
if (symbolText && alphabet != null && alphabet.Length > 0)
{
symbolText.text = alphabet[currentIndex];
symbolText.color = isSelected ? selectedColor : normalColor;
}
// Обновляем фон
if (backgroundImage)
{
backgroundImage.color = isSelected ? selectedColor : normalColor;
}
// Обновляем подсветку выделения
if (selectionHighlight)
{
selectionHighlight.gameObject.SetActive(isSelected);
selectionHighlight.color = highlightColor;
}
}
/// <summary>
/// Сбрасывает ячейку к первому символу
/// </summary>
public new void Reset()
{
currentIndex = 0;
UpdateDisplay();
}
}
}