using UnityEngine; using UnityEngine.UI; using TMPro; namespace RR.Common.UI { /// /// Ячейка для ввода одного символа. /// Символы переключаются циклически при нажатии кнопки "вверх". /// Наследуется от Selectable для поддержки навигации. /// 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; /// /// Событие, вызываемое при изменении символа /// public event System.Action OnSymbolChanged; /// /// Текущий выбранный символ /// public string CurrentSymbol => alphabet != null && alphabet.Length > 0 ? alphabet[currentIndex] : ""; /// /// Индекс текущего символа /// public int CurrentIndex => currentIndex; protected override void Awake() { base.Awake(); if (selectionHighlight) { selectionHighlight.gameObject.SetActive(false); } // Устанавливаем targetGraphic для визуальной обратной связи if (backgroundImage != null) { targetGraphic = backgroundImage; } } /// /// Переопределяем DoStateTransition для кастомной визуализации /// 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); } } /// /// Переопределяем OnMove для обработки вверх/вниз и изменения символа /// 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; } } /// /// Инициализирует ячейку с алфавитом /// /// Алфавит символов /// Начальный индекс (используется только если symbol = null) /// Начальный символ (если указан, ищется в алфавите) 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(); } /// /// Переключает на следующий символ в алфавите (циклически) /// public void NextSymbol() { if (alphabet == null || alphabet.Length == 0) return; currentIndex = (currentIndex + 1) % alphabet.Length; UpdateDisplay(); OnSymbolChanged?.Invoke(this); } /// /// Переключает на предыдущий символ в алфавите (циклически) /// public void PreviousSymbol() { if (alphabet == null || alphabet.Length == 0) return; currentIndex--; if (currentIndex < 0) currentIndex = alphabet.Length - 1; UpdateDisplay(); OnSymbolChanged?.Invoke(this); } /// /// Устанавливает индекс символа /// public void SetSymbolIndex(int index) { if (alphabet == null || alphabet.Length == 0) return; currentIndex = Mathf.Clamp(index, 0, alphabet.Length - 1); UpdateDisplay(); } /// /// Устанавливает состояние выделения ячейки /// public void SetCellSelected(bool selected) { isSelected = selected; UpdateDisplay(); } /// /// Обновляет визуальное отображение ячейки /// 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; } } /// /// Сбрасывает ячейку к первому символу /// public new void Reset() { currentIndex = 0; UpdateDisplay(); } } }