using System; using System.Collections.Generic; using System.Reflection; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.UI; using RR.Common.Managers; using RR.Common.Prizes; using RR.Common.Utility; namespace RR.Common.UI { /// /// Отдельная модалка глобальной настройки QR-кодов по градациям. /// Не привязана к уровням: уровень выбирает только qrTier в LevelPrizeSettingsModal. /// public class QRCodeSettingsModal : Modal { [Header("UI")] [SerializeField] private TMP_Text titleText; [SerializeField] private TMP_Text tierLabel; [SerializeField] private TMP_Text prizeTextLabel; [SerializeField] private Button editPrizeTextButton; [SerializeField] private TextInputModal textInputModal; [Header("Rows")] [SerializeField] private RectTransform tierRow; [SerializeField] private RectTransform prizeTextRow; [Header("Pointer")] [SerializeField] private UIRowPointer rowPointer; [Header("Close")] [SerializeField] private Button closeModalButton; [Header("Navigation")] [SerializeField] private InputActionReference navigateAction; [SerializeField] private InputActionReference submitAction; [SerializeField] private float deadZone = 0.5f; [Header("Behaviour")] [SerializeField] private bool createEntryIfMissing = true; [SerializeField] private string prizeTextModalTitle = "Введите текст приза QR"; [SerializeField] private int prizeTextMaxLength = 12; private static readonly QRTier[] EditableTiers = { QRTier.Bronze, QRTier.Silver, QRTier.Gold }; private const int MaxPrizeTextLength = 12; private const int RowTier = 0; private const int RowPrizeText = 1; private const int RowClose = 2; private const int RowCount = 3; private PrizeSystemManager _manager; private List _workingEntries; private int _currentTierIndex; private int _activeRow; private string _currentPrizeText = string.Empty; private bool _axisHeldX; private bool _axisHeldY; private bool _isPrizeTextInputOpen; private Selectable _pendingFocusTarget; private int _pendingFocusFrames; private Selectable _tierRowSelectable; private Selectable _prizeTextRowSelectable; private RectTransform _prizeTextPointerTarget; protected override void Awake() { base.Awake(); if (editPrizeTextButton != null) { Graphic buttonGraphic = editPrizeTextButton.GetComponent(); if (buttonGraphic != null) { editPrizeTextButton.targetGraphic = buttonGraphic; } editPrizeTextButton.onClick.AddListener(OpenPrizeTextInput); } if (textInputModal == null) { textInputModal = FindFirstObjectByType(FindObjectsInactive.Include); } } protected override void OnEnable() { base.OnEnable(); if (navigateAction != null && navigateAction.action != null) { navigateAction.action.Enable(); } } protected override void OnDisable() { _isPrizeTextInputOpen = false; _pendingFocusTarget = null; _pendingFocusFrames = 0; _axisHeldX = false; _axisHeldY = false; base.OnDisable(); } protected override void OnReturnedToTop() { _isPrizeTextInputOpen = false; _pendingFocusTarget = null; _pendingFocusFrames = 0; _axisHeldX = false; _axisHeldY = false; RefreshRowHighlights(); } private void OnDestroy() { if (editPrizeTextButton != null) { editPrizeTextButton.onClick.RemoveListener(OpenPrizeTextInput); } if (textInputModal != null) { textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed; textInputModal.OnTextCancelled -= OnPrizeTextCancelled; } } public override void Open() { PrepareForEditing(); base.Open(); RefreshRowHighlights(); } public void OpenForEditing() { Open(); } public override void Close() { Save(); base.Close(); } private void Save() { ApplyCurrentFieldValues(); if (_manager == null) { Debug.LogWarning("[QRCodeSettingsModal] PrizeSystemManager не найден."); return; } _manager.SetQRCodeConfigs(_workingEntries); } private void PrepareForEditing() { _manager = PrizeSystemManager.Instance; _workingEntries = _manager != null ? _manager.GetQRCodeConfigs() : new List(); _currentTierIndex = Mathf.Clamp(_currentTierIndex, 0, EditableTiers.Length - 1); _activeRow = RowTier; AutoResolveRows(); EnsureRowSelectables(); LoadCurrentTierIntoFields(); RefreshRowHighlights(); } private void ProcessNavigationInput() { if (navigateAction == null || navigateAction.action == null) { _axisHeldX = false; _axisHeldY = false; return; } Vector2 value = navigateAction.action.ReadValue(); if (Mathf.Abs(value.y) >= deadZone) { if (!_axisHeldY) { _axisHeldY = true; MoveRow(value.y > 0f ? -1 : 1); } } else { _axisHeldY = false; } if (Mathf.Abs(value.x) >= deadZone) { if (!_axisHeldX) { _axisHeldX = true; HandleLeftRight(value.x > 0f ? 1 : -1); } } else { _axisHeldX = false; } } private void Update() { if (!_isOpen || _isPrizeTextInputOpen) { return; } ProcessNavigationInput(); HandleSubmit(); } private void LateUpdate() { if (!_isOpen || _isPrizeTextInputOpen) { return; } ApplyPendingFocus(); } private void MoveRow(int delta) { _activeRow = (_activeRow + delta + RowCount) % RowCount; RefreshRowHighlights(); } private void HandleLeftRight(int direction) { if (_activeRow != RowTier) return; ApplyCurrentFieldValues(); _currentTierIndex = (_currentTierIndex + direction + EditableTiers.Length) % EditableTiers.Length; LoadCurrentTierIntoFields(); RefreshRowHighlights(); } private void LoadCurrentTierIntoFields() { if (titleText != null) { titleText.text = $"Настройки QR: {GetTierName(CurrentTier)}"; } if (tierLabel != null) { tierLabel.text = GetTierName(CurrentTier); } List entries = GetEntriesForTier(CurrentTier, includeFallbackTemplates: false); _currentPrizeText = ResolveDisplayedPrizeText(entries); UpdatePrizeTextLabel(_currentPrizeText); } private void ApplyCurrentFieldValues() { if (_workingEntries == null) { return; } _currentPrizeText = NormalizePrizeText(_currentPrizeText); List tierIndices = GetEntryIndicesForTier(CurrentTier); if (tierIndices.Count > 0) { for (int i = 0; i < tierIndices.Count; i++) { int index = tierIndices[i]; QRCodeInfo info = _workingEntries[index]; info.tier = CurrentTier; info.prizeText = _currentPrizeText; _workingEntries[index] = info; } return; } if (!createEntryIfMissing) { return; } List templateEntries = GetEntriesForTier(QRTier.None, includeFallbackTemplates: false); if (templateEntries.Count == 0) { templateEntries.Add(CreateDefaultEntry(CurrentTier)); } for (int i = 0; i < templateEntries.Count; i++) { QRCodeInfo clone = templateEntries[i]; clone.tier = CurrentTier; clone.prizeText = _currentPrizeText; _workingEntries.Add(clone); } } private static QRCodeInfo CreateDefaultEntry(QRTier tier) { return new QRCodeInfo { tier = tier, generateQRCode = true, linkPrefix = string.Empty, messageFormat = "{0}", prizeText = string.Empty, darkColor = new Vector3(0f, 0f, 0f), lightColor = new Vector3(1f, 1f, 1f) }; } private void OpenPrizeTextInput() { if (textInputModal == null) { Debug.LogWarning("[QRCodeSettingsModal] TextInputModal не назначен."); return; } _isPrizeTextInputOpen = true; FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton); textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed; textInputModal.OnTextCancelled -= OnPrizeTextCancelled; textInputModal.OnTextConfirmed += OnPrizeTextConfirmed; textInputModal.OnTextCancelled += OnPrizeTextCancelled; _currentPrizeText = NormalizePrizeText(_currentPrizeText); textInputModal.SetAlphabet(TextInputAlphabets.RussianPrizeText); textInputModal.SetCellCount(GetPrizeTextCellCount()); textInputModal.OpenForInput(prizeTextModalTitle, _currentPrizeText); } public void OpenPrizeTextEditor() { OpenPrizeTextInput(); } private void OnPrizeTextConfirmed(string text) { _isPrizeTextInputOpen = false; if (textInputModal != null) { textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed; textInputModal.OnTextCancelled -= OnPrizeTextCancelled; } _currentPrizeText = NormalizePrizeText(text); UpdatePrizeTextLabel(_currentPrizeText); FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton); } private void OnPrizeTextCancelled() { _isPrizeTextInputOpen = false; if (textInputModal != null) { textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed; textInputModal.OnTextCancelled -= OnPrizeTextCancelled; } FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton); } private void UpdatePrizeTextLabel(string text) { if (prizeTextLabel != null) { string normalized = NormalizePrizeText(text); prizeTextLabel.text = string.IsNullOrWhiteSpace(normalized) ? "<пусто>" : normalized; } } private void RefreshRowHighlights() { RectTransform target = GetActiveRowTransform(); Selectable selectableToFocus = null; switch (_activeRow) { case RowTier: selectableToFocus = _tierRowSelectable; break; case RowPrizeText: selectableToFocus = _prizeTextRowSelectable; break; case RowClose: selectableToFocus = closeModalButton; break; } if (_activeRow == RowClose) { if (rowPointer != null) { rowPointer.Hide(); } } else if (rowPointer != null && target != null) { rowPointer.SetTarget(target); } FocusSelectable(selectableToFocus); } private void AutoResolveRows() { if (tierRow == null && tierLabel != null) { tierRow = tierLabel.transform.parent as RectTransform; } if (prizeTextRow == null) { if (prizeTextLabel != null) prizeTextRow = prizeTextLabel.transform.parent as RectTransform; else if (editPrizeTextButton != null) prizeTextRow = editPrizeTextButton.transform.parent as RectTransform; } _prizeTextPointerTarget = ResolvePrizeTextPointerTarget(); } private QRTier CurrentTier => EditableTiers[Mathf.Clamp(_currentTierIndex, 0, EditableTiers.Length - 1)]; private static string GetTierName(QRTier tier) { return tier switch { QRTier.Bronze => "Бронзовый", QRTier.Silver => "Серебряный", QRTier.Gold => "Золотой", _ => "Не задан" }; } private List GetEntriesForTier(QRTier tier, bool includeFallbackTemplates) { List result = new(); if (_workingEntries == null) return result; for (int i = 0; i < _workingEntries.Count; i++) { if (_workingEntries[i].tier == tier) result.Add(_workingEntries[i]); } if (result.Count == 0 && includeFallbackTemplates && tier != QRTier.None) { for (int i = 0; i < _workingEntries.Count; i++) { if (_workingEntries[i].tier == QRTier.None) result.Add(_workingEntries[i]); } } return result; } private List GetEntryIndicesForTier(QRTier tier) { List result = new(); if (_workingEntries == null) return result; for (int i = 0; i < _workingEntries.Count; i++) { if (_workingEntries[i].tier == tier) result.Add(i); } return result; } private string ResolveDisplayedPrizeText(List tierEntries) { if (tierEntries == null || tierEntries.Count == 0) return string.Empty; for (int i = 0; i < tierEntries.Count; i++) { string candidate = NormalizePrizeText(tierEntries[i].prizeText); if (!string.IsNullOrWhiteSpace(candidate)) return candidate; } return string.Empty; } private int GetPrizeTextCellCount() { return Mathf.Clamp(prizeTextMaxLength, 1, MaxPrizeTextLength); } private RectTransform GetActiveRowTransform() { return _activeRow switch { RowTier => tierRow, RowPrizeText => _prizeTextPointerTarget, _ => null }; } private void EnsureRowSelectables() { _tierRowSelectable = EnsureRowSelectable(tierRow); _prizeTextRowSelectable = editPrizeTextButton != null ? editPrizeTextButton : EnsureRowSelectable(_prizeTextPointerTarget); } private RectTransform ResolvePrizeTextPointerTarget() { if (editPrizeTextButton != null) { RectTransform buttonRect = editPrizeTextButton.transform as RectTransform; if (buttonRect != null) { return buttonRect; } } if (prizeTextRow != null) { return prizeTextRow; } return prizeTextLabel != null ? prizeTextLabel.transform.parent as RectTransform : null; } private Selectable EnsureRowSelectable(RectTransform row) { if (row == null) { return null; } Selectable selectable = row.GetComponent(); if (selectable == null) { selectable = row.gameObject.AddComponent(); selectable.transition = Selectable.Transition.None; selectable.navigation = new Navigation { mode = Navigation.Mode.None }; } selectable.interactable = true; return selectable; } private void FocusSelectable(Selectable selectable) { if (selectable == null || !selectable.gameObject.activeInHierarchy) { return; } SetFirstSelected(selectable); _pendingFocusTarget = selectable; _pendingFocusFrames = 2; ApplyPendingFocus(); } private void ApplyPendingFocus() { if (_pendingFocusTarget == null || _pendingFocusFrames <= 0) { return; } if (EventSystem.current == null || !_pendingFocusTarget.gameObject.activeInHierarchy) { _pendingFocusTarget = null; _pendingFocusFrames = 0; return; } if (EventSystem.current.currentSelectedGameObject != _pendingFocusTarget.gameObject) { EventSystem.current.SetSelectedGameObject(null); EventSystem.current.SetSelectedGameObject(_pendingFocusTarget.gameObject); _pendingFocusTarget.Select(); } _pendingFocusFrames--; if (_pendingFocusFrames <= 0) { _pendingFocusTarget = null; } } private void HandleSubmit() { InputAction currentSubmitAction = GetSubmitAction(); if (currentSubmitAction == null || !currentSubmitAction.WasPerformedThisFrame()) { return; } GameObject selectedObject = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null; switch (_activeRow) { case RowPrizeText: if (selectedObject == editPrizeTextButton?.gameObject) { return; } OpenPrizeTextInput(); break; case RowClose: if (selectedObject == closeModalButton?.gameObject) { return; } Close(); break; } } private InputAction GetSubmitAction() { if (submitAction != null && submitAction.action != null) { return submitAction.action; } if (EventSystem.current?.currentInputModule == null) { return null; } object inputModule = EventSystem.current.currentInputModule; PropertyInfo submitProperty = inputModule.GetType().GetProperty("submit", BindingFlags.Instance | BindingFlags.Public); object submitObject = submitProperty != null ? submitProperty.GetValue(inputModule) : null; InputActionReference submitReference = submitObject as InputActionReference; return submitReference != null ? submitReference.action : null; } private string NormalizePrizeText(string text) { if (string.IsNullOrWhiteSpace(text)) { return string.Empty; } string normalized = text.Trim(); int maxLength = GetPrizeTextCellCount(); if (normalized.Length > maxLength) { normalized = normalized.Substring(0, maxLength); } return normalized; } } }