using System; using DG.Tweening; using PrizeSystem; using TMPro; using UnityEngine; public sealed class PrizeStockPanel : MonoBehaviour, IControlable { public event Action OnClosed; [SerializeField] private CanvasGroup _canvasGroup; [SerializeField] private TextMeshProUGUI _count; [SerializeField] private MToggle _controlToggle; [SerializeField] private TextMeshProUGUI[] _options; [SerializeField] private TextMeshProUGUI _description; [SerializeField] private TextMeshProUGUI _hint; [SerializeField] private float _fadeDuration = 0.25f; [SerializeField] private Color _normalOptionColor = Color.white; [SerializeField] private Color _selectedOptionColor = new Color(1f, 0.85f, 0.1f, 1f); [Header("Ускорение числового ввода")] [SerializeField] private float _accelerationTime = 0.8f; [SerializeField] private int _maximumStepMultiplier = 10; [SerializeField] private float _initialRepeatInterval = 0.5f; [SerializeField] private float _minimumRepeatInterval = 0.03f; [SerializeField] private AnimationCurve _accelerationCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); private const int ToggleOptionIndex = 0; private const int AddOptionIndex = 1; private const int SetOptionIndex = 2; private const int BackOptionIndex = 3; private const int DefaultAddAmount = 10; private const int MaximumCount = 999999; private Tween _fadeTween; private int _selectedOptionIndex; private int _addAmount; private int _setAmount; private bool _editingNumber; private bool _actionReleased; private bool _isClosing; private bool _numberInputHeld; private int _heldNumberDirection; private int _heldNumberBaseStep; private float _numberHoldTime; private float _lastNumberChangeTime; public bool ControlsEnabled { get; set; } private void OnDisable() { PrizeStockService.StateChanged -= Refresh; _fadeTween?.Kill(); _fadeTween = null; ControlsEnabled = false; ResetNumberInputRepeat(); } public void Open() { _fadeTween?.Kill(); transform.SetAsLastSibling(); _canvasGroup.alpha = 0f; _canvasGroup.blocksRaycasts = true; _selectedOptionIndex = ToggleOptionIndex; _addAmount = DefaultAddAmount; _setAmount = PrizeStockService.Count; _editingNumber = false; _actionReleased = false; _isClosing = false; ResetNumberInputRepeat(); ControlsEnabled = true; PrizeStockService.StateChanged -= Refresh; PrizeStockService.StateChanged += Refresh; _controlToggle.DisplayImmediate(PrizeStockService.Enabled); Refresh(); _fadeTween = _canvasGroup.DOFade(1f, _fadeDuration).SetEase(Ease.OutCubic).SetUpdate(true); _fadeTween.PlayForward(); } public void UpdateControls() { if (_isClosing) return; Vector2Int axis = In.GetAxis(JoystickSide.Both, InteractMode.Discrete); bool actionPressed = In.GetAction(JoystickSide.Both, InteractMode.Discrete); bool actionHeld = In.GetAction(JoystickSide.Both, InteractMode.Continious); if (actionHeld == false) _actionReleased = true; if (_editingNumber) { UpdateNumberEditing(); if (_actionReleased && actionPressed) ApplyNumberChange(); return; } if (axis.y != 0) { int direction = axis.y > 0 ? -1 : 1; _selectedOptionIndex = (_selectedOptionIndex + direction + _options.Length) % _options.Length; RefreshSelection(); } if (_selectedOptionIndex == ToggleOptionIndex && axis.x != 0) SetControlEnabled(axis.x > 0); if (_actionReleased && actionPressed) ExecuteSelectedOption(); } private void ExecuteSelectedOption() { _actionReleased = false; if (_selectedOptionIndex == ToggleOptionIndex) { SetControlEnabled(PrizeStockService.Enabled == false); return; } if (_selectedOptionIndex == AddOptionIndex || _selectedOptionIndex == SetOptionIndex) { _editingNumber = true; ResetNumberInputRepeat(); RefreshSelection(); return; } Close(); } private void UpdateNumberEditing() { Vector2Int axis = In.GetAxis(JoystickSide.Both, InteractMode.Continious); UpdateNumberEditing(axis, Time.unscaledTime, Time.unscaledDeltaTime); } private void UpdateNumberEditing(Vector2Int axis, float currentTime, float deltaTime) { int direction = axis.y != 0 ? axis.y : axis.x; int baseStep = axis.y != 0 ? 10 : 1; if (direction == 0) { ResetNumberInputRepeat(); return; } if (_numberInputHeld == false || direction != _heldNumberDirection || baseStep != _heldNumberBaseStep) { _numberInputHeld = true; _heldNumberDirection = direction; _heldNumberBaseStep = baseStep; _numberHoldTime = 0f; _lastNumberChangeTime = currentTime; ChangeEditedNumber(direction * baseStep); return; } _numberHoldTime += deltaTime; float progress = Mathf.Clamp01(_numberHoldTime / Mathf.Max(_accelerationTime, 0.01f)); float acceleration = _accelerationCurve.Evaluate(progress); int maximumStepMultiplier = Mathf.Max(_maximumStepMultiplier, 1); int stepMultiplier = 1 + Mathf.RoundToInt(acceleration * (maximumStepMultiplier - 1)); float initialRepeatInterval = Mathf.Max(_initialRepeatInterval, 0.01f); float minimumRepeatInterval = Mathf.Clamp(_minimumRepeatInterval, 0.01f, initialRepeatInterval); float repeatInterval = Mathf.Lerp(initialRepeatInterval, minimumRepeatInterval, acceleration); if (currentTime - _lastNumberChangeTime < repeatInterval) return; _lastNumberChangeTime = currentTime; ChangeEditedNumber(direction * baseStep * stepMultiplier); } private void ChangeEditedNumber(int delta) { if (delta == 0) return; if (_selectedOptionIndex == AddOptionIndex) _addAmount = Mathf.Clamp(_addAmount + delta, 1, MaximumCount); else _setAmount = Mathf.Clamp(_setAmount + delta, 0, MaximumCount); Refresh(); } private void ApplyNumberChange() { _actionReleased = false; ResetNumberInputRepeat(); if (_selectedOptionIndex == AddOptionIndex) { PrizeStockService.Add(_addAmount); _setAmount = PrizeStockService.Count; } else { PrizeStockService.SetCount(_setAmount); } _editingNumber = false; Refresh(); } private void SetControlEnabled(bool enabled) { if (PrizeStockService.Enabled == enabled) return; PrizeStockService.SetEnabled(enabled); _controlToggle.SetValue(enabled); Refresh(); } private void Refresh() { _count.text = PrizeStockService.Count.ToString(); _options[0].text = PrizeStockService.Enabled ? "КОНТРОЛЬ ОСТАТКОВ: ВКЛЮЧЁН" : "КОНТРОЛЬ ОСТАТКОВ: ВЫКЛЮЧЕН"; _options[1].text = $"ДОБАВИТЬ ПРИЗЫ: +{_addAmount}"; _options[2].text = $"УСТАНОВИТЬ КОЛИЧЕСТВО: {_setAmount}"; RefreshSelection(); } private void RefreshSelection() { for (int i = 0; i < _options.Length; i++) { bool selected = i == _selectedOptionIndex; _options[i].color = selected ? _selectedOptionColor : _normalOptionColor; _options[i].rectTransform.localScale = selected ? Vector3.one * 1.06f : Vector3.one; } if (_editingNumber) { _description.text = _selectedOptionIndex == AddOptionIndex ? "Добавляет выбранное число к текущему остатку." : "Заменяет текущий остаток выбранным значением."; _hint.text = "УДЕРЖИВАЙТЕ ДЖОЙСТИК ДЛЯ УСКОРЕНИЯ КНОПКА: ПРИМЕНИТЬ"; return; } _description.text = _selectedOptionIndex switch { ToggleOptionIndex => "При выключении остаток не списывается, а предупреждение в меню скрыто.", AddOptionIndex => "Увеличивает текущий остаток на выбранное количество.", SetOptionIndex => "Присваивает счётчику указанное количество призов.", _ => "Вернуться к списку настроек." }; _hint.text = "ВВЕРХ / ВНИЗ: ВЫБОР КНОПКА: ОТКРЫТЬ ВЛЕВО / ВПРАВО: ПЕРЕКЛЮЧИТЬ"; } private void ResetNumberInputRepeat() { _numberInputHeld = false; _heldNumberDirection = 0; _heldNumberBaseStep = 0; _numberHoldTime = 0f; _lastNumberChangeTime = 0f; } private void Close() { if (_isClosing) return; _isClosing = true; ControlsEnabled = false; _fadeTween?.Kill(); _fadeTween = _canvasGroup.DOFade(0f, _fadeDuration).SetEase(Ease.InCubic).SetUpdate(true); _fadeTween.OnComplete(() => { _canvasGroup.blocksRaycasts = false; OnClosed?.Invoke(); }); _fadeTween.PlayForward(); } }