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();
}
}
}