using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace RR.Common.UI
{
///
/// Модальное окно для ввода текста через выбор символов.
/// Использует ячейки TextInputCell для ввода каждого символа.
///
public class TextInputModal : Modal
{
[Header("Text Input Settings")]
[SerializeField] private int cellCount = 10;
[SerializeField] private string[] alphabet = new string[]
{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z", " ", "-", "_"
};
[Header("UI References")]
[SerializeField] private Transform cellsContainer;
[SerializeField] private GameObject cellPrefab;
[SerializeField] private TextMeshProUGUI titleText;
[SerializeField] private TextMeshProUGUI previewText;
[SerializeField] private Button confirmButton;
[SerializeField] private Button cancelButton;
private List cells = new List();
private int currentCellIndex;
// Параметры для отложенной генерации (когда модалка станет активной)
private bool needsRegeneration = false;
private string pendingText = "";
// События
public event Action OnTextConfirmed;
public event Action OnTextCancelled;
protected override void Awake()
{
base.Awake();
// НЕ генерируем ячейки здесь - они будут созданы через SetCellCount() или SetAlphabet()
// Настраиваем кнопки
if (confirmButton)
{
confirmButton.onClick.AddListener(ConfirmInput);
}
if (cancelButton)
{
cancelButton.onClick.AddListener(CancelInput);
}
}
private void OnDestroy()
{
// Отписываемся от всех событий ячеек
foreach (var cell in cells)
{
if (cell != null)
{
cell.OnSymbolChanged -= OnCellSymbolChanged;
}
}
// Отписываемся от кнопок
if (confirmButton)
{
confirmButton.onClick.RemoveListener(ConfirmInput);
}
if (cancelButton)
{
cancelButton.onClick.RemoveListener(CancelInput);
}
}
private void Update()
{
if (!gameObject.activeInHierarchy) return;
// Синхронизируем currentCellIndex с текущим выбранным элементом EventSystem
SyncCurrentCellWithEventSystem();
}
///
/// Синхронизирует currentCellIndex с текущим выбранным элементом EventSystem
///
private void SyncCurrentCellWithEventSystem()
{
if (UnityEngine.EventSystems.EventSystem.current == null) return;
var selected = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject;
if (selected == null) return;
// Проверяем, является ли выбранный объект одной из наших ячеек
var selectedCell = selected.GetComponent();
if (selectedCell != null)
{
int index = cells.IndexOf(selectedCell);
if (index >= 0 && index != currentCellIndex)
{
currentCellIndex = index;
// Обновляем визуальное выделение без установки фокуса (чтобы избежать рекурсии)
for (int i = 0; i < cells.Count; i++)
{
cells[i].SetCellSelected(i == currentCellIndex);
}
UpdatePreview();
}
}
}
///
/// Генерирует ячейки для ввода текста
///
/// Начальный текст для заполнения ячеек
private void GenerateCells(string initialText = "")
{
Debug.Log($"[TextInputModal] GenerateCells: cellCount={cellCount}, initialText='{initialText}', activeInHierarchy={gameObject.activeInHierarchy}");
if (cellPrefab == null || cellsContainer == null)
{
Debug.LogError("[TextInputModal] Cell prefab or container is not assigned!");
return;
}
// Отписываемся от событий старых ячеек и очищаем их
foreach (var cell in cells)
{
if (cell != null)
{
cell.OnSymbolChanged -= OnCellSymbolChanged;
}
}
// Очищаем существующие ячейки
foreach (Transform child in cellsContainer)
{
Destroy(child.gameObject);
}
cells.Clear();
// Создаем новые ячейки с начальными символами
for (int i = 0; i < cellCount; i++)
{
GameObject cellObj = Instantiate(cellPrefab, cellsContainer);
TextInputCell cell = cellObj.GetComponent();
if (cell != null)
{
// Определяем начальный символ для этой ячейки
string symbol = (i < initialText.Length) ? initialText[i].ToString() : null;
// Инициализируем ячейку с алфавитом и символом
cell.Initialize(alphabet, 0, symbol);
// Подписываемся на изменение символа
cell.OnSymbolChanged += OnCellSymbolChanged;
cells.Add(cell);
}
else
{
Debug.LogError($"[TextInputModal] Cell prefab doesn't have TextInputCell component!");
}
}
Debug.Log($"[TextInputModal] Created {cells.Count} cells. Setting up navigation...");
// Настраиваем циклическую горизонтальную навигацию
SetupCyclicNavigation();
// Выделяем первую ячейку и устанавливаем фокус
if (cells.Count > 0)
{
currentCellIndex = 0;
SetFirstSelected(cells[0]);
UpdateCellSelection();
}
// Обновляем preview с актуальными данными из ячеек
UpdatePreview();
Debug.Log($"[TextInputModal] GenerateCells completed successfully");
}
///
/// Настраивает циклическую горизонтальную навигацию между всеми элементами
///
private void SetupCyclicNavigation()
{
if (cells.Count == 0) return;
// Создаем список всех элементов для навигации: ячейки + кнопки
List allElements = new List();
// Добавляем все ячейки
allElements.AddRange(cells);
// Добавляем кнопки в конец
if (confirmButton != null) allElements.Add(confirmButton);
if (cancelButton != null) allElements.Add(cancelButton);
// Настраиваем циклическую горизонтальную навигацию для всех элементов
for (int i = 0; i < allElements.Count; i++)
{
var element = allElements[i];
var nav = new Navigation();
nav.mode = Navigation.Mode.Explicit;
// Предыдущий элемент (циклично)
int prevIndex = (i - 1 + allElements.Count) % allElements.Count;
nav.selectOnLeft = allElements[prevIndex];
// Следующий элемент (циклично)
int nextIndex = (i + 1) % allElements.Count;
nav.selectOnRight = allElements[nextIndex];
// Вверх/вниз: только для ячеек (не для кнопок)
if (element is TextInputCell)
{
// Для ячеек вверх/вниз обрабатываются в OnMove (изменение символа)
nav.selectOnUp = null;
nav.selectOnDown = null;
}
else
{
// Для кнопок вверх/вниз не используются
nav.selectOnUp = null;
nav.selectOnDown = null;
}
element.navigation = nav;
}
}
///
/// Открывает модальное окно с заданным заголовком и начальным текстом
///
public void OpenForInput(string title, string initialText = "")
{
Debug.Log($"[TextInputModal] OpenForInput called. Title: '{title}', InitialText: '{initialText}'");
if (titleText)
{
titleText.text = title;
}
// Сохраняем текст для генерации после активации
pendingText = initialText ?? "";
// Помечаем что нужна регенерация (если еще не помечено)
if (cells.Count != cellCount || cells.Count == 0)
{
needsRegeneration = true;
}
// Проверяем что ячейки не null
if (!needsRegeneration && cells.Count > 0)
{
for (int i = 0; i < cells.Count; i++)
{
if (cells[i] == null)
{
Debug.LogWarning($"[TextInputModal] Cell {i} is null, will regenerate");
needsRegeneration = true;
break;
}
}
}
Debug.Log($"[TextInputModal] needsRegeneration: {needsRegeneration}, calling Open()");
// Open() активирует модалку и вызовет генерацию ячеек
Open();
}
///
/// Переопределяем Open для генерации ячеек ПОСЛЕ активации
///
public override void Open()
{
Debug.Log($"[TextInputModal] Open() called. needsRegeneration: {needsRegeneration}, pendingText: '{pendingText}'");
Debug.Log($"[TextInputModal] BEFORE base.Open() - gameObject.activeSelf: {gameObject.activeSelf}, activeInHierarchy: {gameObject.activeInHierarchy}");
// Сначала активируем модалку
base.Open();
Debug.Log($"[TextInputModal] AFTER base.Open() - gameObject.activeSelf: {gameObject.activeSelf}, activeInHierarchy: {gameObject.activeInHierarchy}");
// Теперь, когда модалка активна, генерируем ячейки если нужно
if (needsRegeneration)
{
Debug.Log($"[TextInputModal] Generating cells NOW (after activation) with text: '{pendingText}'");
GenerateCells(pendingText);
needsRegeneration = false;
}
else if (!string.IsNullOrEmpty(pendingText))
{
Debug.Log($"[TextInputModal] Setting text to existing cells: '{pendingText}'");
SetText(pendingText);
}
// Очищаем pending text
pendingText = "";
Debug.Log($"[TextInputModal] Open() completed. cells.Count: {cells.Count}");
}
///
/// Устанавливает текст в ячейки (переинициализирует ячейки с новыми символами)
///
public void SetText(string text)
{
if (text == null) text = "";
Debug.Log($"[TextInputModal] SetText called with: '{text}'");
// Проверяем, что ячейки созданы
if (cells.Count == 0)
{
Debug.LogWarning("[TextInputModal] No cells available, cannot set text!");
return;
}
Debug.Log($"[TextInputModal] Setting text to {cells.Count} cells");
// Переинициализируем каждую ячейку с новым символом
for (int i = 0; i < cells.Count; i++)
{
if (cells[i] == null)
{
Debug.LogWarning($"[TextInputModal] Cell {i} is null!");
continue;
}
// Определяем символ для этой ячейки
string symbol = (i < text.Length) ? text[i].ToString() : null;
Debug.Log($"[TextInputModal] Cell {i}: initializing with symbol '{symbol}', alphabet has {(cells[i] != null ? alphabet.Length : 0)} symbols");
// Переинициализируем ячейку с новым символом
cells[i].Initialize(alphabet, 0, symbol);
}
Debug.Log($"[TextInputModal] SetText completed, updating preview");
UpdatePreview();
}
///
/// Получает текущий введенный текст
///
public string GetText()
{
string result = "";
foreach (var cell in cells)
{
string symbol = cell.CurrentSymbol;
// Добавляем символ, но первый символ алфавита (обычно пробел или 0) считается "пустым"
if (cell.CurrentIndex == 0 && string.IsNullOrEmpty(symbol.Trim()))
{
result += " "; // Добавляем пробел для пустых ячеек
}
else
{
result += symbol;
}
}
return result.TrimEnd(); // Убираем пробелы в конце
}
///
/// Вызывается когда символ в ячейке изменяется
///
private void OnCellSymbolChanged(TextInputCell cell)
{
UpdatePreview();
}
///
/// Обновляет выделение ячеек
///
private void UpdateCellSelection()
{
for (int i = 0; i < cells.Count; i++)
{
cells[i].SetCellSelected(i == currentCellIndex);
}
// Устанавливаем фокус EventSystem на текущую ячейку
if (currentCellIndex >= 0 && currentCellIndex < cells.Count && cells[currentCellIndex] != null)
{
if (UnityEngine.EventSystems.EventSystem.current != null)
{
UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(cells[currentCellIndex].gameObject);
}
}
}
///
/// Обновляет предпросмотр текста
///
private void UpdatePreview()
{
if (previewText)
{
previewText.text = GetText();
}
}
///
/// Подтверждает ввод текста
///
private void ConfirmInput()
{
string text = GetText();
OnTextConfirmed?.Invoke(text);
Close();
}
///
/// Отменяет ввод текста
///
private void CancelInput()
{
OnTextCancelled?.Invoke();
Close();
}
///
/// Устанавливает алфавит для всех ячеек
///
public void SetAlphabet(string[] newAlphabet)
{
Debug.Log($"[TextInputModal] SetAlphabet called with {(newAlphabet != null ? newAlphabet.Length : 0)} symbols");
if (newAlphabet == null || newAlphabet.Length == 0)
{
Debug.LogWarning("[TextInputModal] Cannot set empty alphabet!");
return;
}
alphabet = newAlphabet;
// Помечаем что нужна регенерация (она произойдет при открытии модалки)
needsRegeneration = true;
Debug.Log($"[TextInputModal] Alphabet set, marked for regeneration");
}
///
/// Устанавливает количество ячеек и перегенерирует их
///
public void SetCellCount(int count)
{
if (count <= 0)
{
return;
}
cellCount = count;
// Помечаем что нужна регенерация (она произойдет при открытии модалки)
needsRegeneration = true;
}
///
/// Сбрасывает все ячейки к первому символу
///
public void ResetAllCells()
{
foreach (var cell in cells)
{
if (cell != null)
{
cell.Initialize(alphabet);
}
}
currentCellIndex = 0;
UpdateCellSelection();
UpdatePreview();
}
}
}