Danro/Assets/Scripts/Settings/UI/ValueSelectorRow.cs
Danil Iarosh a40b761c81
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
feat(prizes, ui): add configurable level prize flow and launcher assets
- добавлена новая система призов:
  PrizeBase, PrizeType, PrizeDatabase, LevelPrizeConfig и реализации
  для hopper, QR-кода, отсутствия приза и стойки с призом
- добавлена конфигурация призов уровней через `prizes.json`
  и связанная логика загрузки/обработки в менеджерах игры
- расширен UI главного меню и настроек уровней:
  добавлены LevelSettingsModal, LevelPrizeSettingsModal,
  QRCodeModal и PrizeStandModal
- обновлена навигация модальных окон и связанный UI-код:
  ModalManager, MainMenu, RulesModal, PauseMenu,
  PrizeErrorDisplay, UIRowPointer и WinBannerController
- доработаны настройки и генерация UI-элементов:
  обновлены SettingsDatabase, SettingsUIGenerator,
  SettingUIItem, ValueSelectorRow и editor drawer для настроек
- обновлены и добавлены UI-prefab'ы для кнопок, строк настроек,
  панели групп и кнопки настроек уровня
- обновлены сцены `MainMenu` и `Level UI` под новый флоу
  настройки уровней, выбора призов и отображения модалок
- добавлены файлы лаунчера в `StreamingAssets`,
  включая `RRLauncher.exe`, служебные meta-файлы и документацию
- улучшено логирование через FileLogger
  и удален устаревший код лицензирования
- удалены больше неиспользуемые файлы:
  `Licensing.cs`, `SteppedSlider.cs` и recovery-сцены
2026-03-16 08:15:36 +03:00

276 lines
9.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace RR.Common.Settings.UI
{
/// <summary>
/// Компонент-Selectable для элемента настроек с выбором значения стрелками влево/вправо.
/// Поддерживает как int, так и float значения.
/// При зажатии кнопки скорость изменения плавно увеличивается.
/// Автоповтор целиком управляется Event System (inputActionsPerSecond).
/// Работает на одном префабе вместе с SettingUIItem.
/// </summary>
public class ValueSelectorRow : Selectable, IMoveHandler
{
[Header("References")]
[SerializeField] private TMP_Text valueText;
private SettingEntry _entry;
private bool _isFloat;
private int _currentInt;
private float _currentFloat;
private int _intStep = 1;
private float _floatStep = 0.1f;
// ───────────────────── Ускорение при зажатии ─────────────────────
/// <summary>Максимальный множитель шага при длительном зажатии.</summary>
private const int MaxStepMultiplier = 10;
/// <summary>Сколько вызовов OnMove до начала роста множителя.</summary>
private const int MultiplierStartCount = 6;
/// <summary>Сколько вызовов OnMove до достижения максимального множителя.</summary>
private const int MultiplierMaxCount = 30;
/// <summary>Если между двумя OnMove прошло больше этого времени — сброс множителя.</summary>
private const float HoldResetTimeout = 0.25f;
private int _holdDirection; // -1 = left, +1 = right, 0 = idle
private int _holdCount; // Счётчик последовательных OnMove в одном направлении
private float _lastMoveTime; // Time.unscaledTime последнего OnMove
// ───────────────────── Int Setup ─────────────────────
public void Setup(SettingEntry entry, int currentValue)
{
if (entry == null)
{
Debug.LogError("[ValueSelectorRow] Cannot setup with null entry!");
return;
}
_entry = entry;
_isFloat = false;
if (entry.intStep <= 0)
{
Debug.LogWarning($"[ValueSelectorRow] Invalid step {entry.intStep} for key {entry.key}, using 1");
_intStep = 1;
}
else
{
_intStep = entry.intStep;
}
_currentInt = Mathf.Clamp(currentValue, entry.minInt, entry.maxInt);
UpdateUI();
}
// ───────────────────── Float Setup ─────────────────────
public void Setup(SettingEntry entry, float currentValue)
{
if (entry == null)
{
Debug.LogError("[ValueSelectorRow] Cannot setup with null entry!");
return;
}
_entry = entry;
_isFloat = true;
if (entry.floatStep <= 0f)
{
Debug.LogWarning($"[ValueSelectorRow] Invalid step {entry.floatStep} for key {entry.key}, using 0.1");
_floatStep = 0.1f;
}
else
{
_floatStep = entry.floatStep;
}
_currentFloat = Mathf.Clamp(currentValue, entry.minFloat, entry.maxFloat);
UpdateUI();
}
// ───────────────────── Navigation ─────────────────────
public new void OnMove(AxisEventData eventData)
{
if (_entry == null || !IsInteractable())
{
base.OnMove(eventData);
return;
}
switch (eventData.moveDir)
{
case MoveDirection.Left:
ApplyMove(-1);
eventData.Use();
break;
case MoveDirection.Right:
ApplyMove(1);
eventData.Use();
break;
case MoveDirection.Up:
case MoveDirection.Down:
ResetHold();
base.OnMove(eventData);
break;
}
}
// ───────────────────── Move Logic ─────────────────────
private void ApplyMove(int direction)
{
float now = Time.unscaledTime;
// Если направление сменилось или прошло слишком много времени — сбрасываем
if (_holdDirection != direction || (now - _lastMoveTime) > HoldResetTimeout)
{
_holdDirection = direction;
_holdCount = 0;
}
_lastMoveTime = now;
_holdCount++;
// Вычисляем множитель шага по количеству последовательных нажатий
int multiplier = 1;
if (_holdCount > MultiplierStartCount)
{
float progress = Mathf.Clamp01(
(float)(_holdCount - MultiplierStartCount) / (MultiplierMaxCount - MultiplierStartCount));
multiplier = Mathf.Max(1, Mathf.RoundToInt(Mathf.Lerp(1f, MaxStepMultiplier, progress)));
}
if (direction > 0)
IncreaseValue(multiplier);
else
DecreaseValue(multiplier);
}
private void ResetHold()
{
_holdDirection = 0;
_holdCount = 0;
}
protected override void OnDisable()
{
base.OnDisable();
ResetHold();
}
public override void OnDeselect(BaseEventData eventData)
{
base.OnDeselect(eventData);
ResetHold();
}
// ───────────────────── Value Changes ─────────────────────
private void DecreaseValue(int multiplier)
{
if (_isFloat)
{
float newValue = Mathf.Max(_entry.minFloat, _currentFloat - _floatStep * multiplier);
if (!Mathf.Approximately(newValue, _currentFloat))
SetValue(newValue);
}
else
{
int newValue = Mathf.Max(_entry.minInt, _currentInt - _intStep * multiplier);
if (newValue != _currentInt)
SetValue(newValue);
}
}
private void IncreaseValue(int multiplier)
{
if (_isFloat)
{
float newValue = Mathf.Min(_entry.maxFloat, _currentFloat + _floatStep * multiplier);
if (!Mathf.Approximately(newValue, _currentFloat))
SetValue(newValue);
}
else
{
int newValue = Mathf.Min(_entry.maxInt, _currentInt + _intStep * multiplier);
if (newValue != _currentInt)
SetValue(newValue);
}
}
// ───────────────────── UI ─────────────────────
private void UpdateUI()
{
if (valueText == null) return;
if (_isFloat)
{
if (_entry != null && _entry.displayAsPercentage)
valueText.text = (_currentFloat * 100f).ToString("0") + "%";
else
valueText.text = _currentFloat.ToString("0.##");
}
else
{
valueText.text = _currentInt.ToString();
}
}
// ───────────────────── Public API (int) ─────────────────────
public void SetValue(int value)
{
if (_entry == null) return;
int clamped = Mathf.Clamp(value, _entry.minInt, _entry.maxInt);
if (clamped != _currentInt)
{
_currentInt = clamped;
UpdateUI();
var manager = SettingsManager.Instance;
if (manager != null)
manager.SetInt(_entry.key, _currentInt);
else
Debug.LogError("[ValueSelectorRow] SettingsManager.Instance is null!");
}
}
// ───────────────────── Public API (float) ─────────────────────
public void SetValue(float value)
{
if (_entry == null) return;
float clamped = Mathf.Clamp(value, _entry.minFloat, _entry.maxFloat);
if (!Mathf.Approximately(clamped, _currentFloat))
{
_currentFloat = clamped;
UpdateUI();
var manager = SettingsManager.Instance;
if (manager != null)
manager.SetFloat(_entry.key, _currentFloat);
else
Debug.LogError("[ValueSelectorRow] SettingsManager.Instance is null!");
}
}
public bool IsFloat => _isFloat;
public string Key => _entry?.key;
}
}