using System; using System.Collections.Generic; using DanroJump.Settings; using DanroJump.UI.Modals; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.UIElements; #if UNITY_EDITOR using UnityEditor; #endif namespace DanroJump.UI.MainMenu { /// /// Модальное окно сервисных настроек главного меню. /// Строит список полей динамически по базе . /// public sealed class SettingsModalWindow : ModalWindowBase, IDisposable { private const string GroupHeaderTemplateAssetPath = "Assets/UI/MainMenu/Templates/SettingsGroupHeader.uxml"; private const string BoolControlTemplateAssetPath = "Assets/UI/MainMenu/Templates/SettingsBoolControl.uxml"; private const string StepperControlTemplateAssetPath = "Assets/UI/MainMenu/Templates/SettingsStepperControl.uxml"; private const string TextControlTemplateAssetPath = "Assets/UI/MainMenu/Templates/SettingsTextControl.uxml"; private const string ButtonControlTemplateAssetPath = "Assets/UI/MainMenu/Templates/SettingsButtonControl.uxml"; private const string GroupHeaderRootName = "SettingsGroupHeaderRoot"; private const string GroupHeaderTitleName = "SettingsGroupTitle"; private const string GroupHeaderDisclosureName = "SettingsGroupDisclosure"; private const string BoolControlRootName = "SettingsBoolControlRoot"; private const string StepperControlRootName = "SettingsStepperControlRoot"; private const string StepperPreviousButtonName = "SettingsStepperPreviousButton"; private const string StepperValueLabelName = "SettingsStepperValueLabel"; private const string StepperNextButtonName = "SettingsStepperNextButton"; private const string TextControlRootName = "SettingsTextControlRoot"; private const string TextFieldName = "SettingsTextField"; private const string ButtonControlRootName = "SettingsButtonControlRoot"; private const string ActionButtonName = "SettingsActionButton"; private const string ControlTitleName = "SettingsControlTitle"; private const string BoolIndicatorName = "SettingsBoolIndicator"; private const float FocusPointerGap = 18f; private const float FocusPointerMinLeft = 12f; private const float FocusPointerWidth = 46f; private const float FocusPointerHeight = 64f; private const float FocusPointerAnimationDuration = 0.12f; private const int FocusPointerAnimationFrameMs = 16; private const float ScrollVisibilityPadding = 18f; private const float ScrollOffsetEpsilon = 0.5f; private const float VerticalNavigationCooldown = 0.18f; private const float DuplicateNavigationSourceWindow = 0.3f; private const float RelativeSpacingFallbackWidth = 620f; private const float GroupHeaderTopSpacingRatio = 0.035f; private const float GroupHeaderBottomSpacingRatio = 0.019f; private const float SettingControlVerticalSpacingRatio = 0.01f; private const float ActionButtonVerticalSpacingRatio = 0.019f; private const float GroupHeaderTopSpacingMin = 16f; private const float GroupHeaderTopSpacingMax = 34f; private const float GroupHeaderBottomSpacingMin = 8f; private const float GroupHeaderBottomSpacingMax = 20f; private const float SettingControlVerticalSpacingMin = 4f; private const float SettingControlVerticalSpacingMax = 10f; private const float ActionButtonVerticalSpacingMin = 8f; private const float ActionButtonVerticalSpacingMax = 20f; private static VisualTreeAsset groupHeaderTemplate; private static VisualTreeAsset boolControlTemplate; private static VisualTreeAsset stepperControlTemplate; private static VisualTreeAsset textControlTemplate; private static VisualTreeAsset buttonControlTemplate; private static AsyncOperationHandle? groupHeaderTemplateHandle; private static AsyncOperationHandle? boolControlTemplateHandle; private static AsyncOperationHandle? stepperControlTemplateHandle; private static AsyncOperationHandle? textControlTemplateHandle; private static AsyncOperationHandle? buttonControlTemplateHandle; private readonly ScrollView settingsList; private readonly Label statusLabel; private readonly Label currentGroupTitleLabel; private readonly Button saveButton; private readonly Button resetButton; private readonly Button reloadButton; private readonly Button closeButton; private readonly Dictionary groupHeaders = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> groupRows = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary groupDisclosureLabels = new(StringComparer.OrdinalIgnoreCase); private readonly HashSet collapsedGroups = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary settingAdjustments = new(); private readonly Dictionary settingActivations = new(); private readonly List settingsNavigationTargets = new(); private readonly List settingControls = new(); private readonly Label focusPointer; private VisualElement firstFocusableControl; private VisualElement currentFocusPointerTarget; private IVisualElementScheduledItem focusPointerAnimation; private Vector2 focusPointerPosition; private bool hasFocusPointerPosition; private int lastNavigationFrame = -1; private SettingsNavigationCommand lastNavigationCommand; private SettingsNavigationSource lastNavigationSource; private float lastNavigationCommandTime = -DuplicateNavigationSourceWindow; private float lastVerticalNavigationTime = -VerticalNavigationCooldown; private IServiceSettingsService settingsService; public event Action ServiceActionActivated; /// /// Контейнер реального содержимого ScrollView. /// Нужен, чтобы не очищать внутреннюю служебную структуру UI Toolkit. /// private VisualElement SettingsContainer => settingsList?.contentContainer ?? settingsList; /// /// Создает окно настроек и привязывает кнопки управления к сервису настроек. /// /// Корневой VisualElement окна в UXML. /// Сервис чтения, применения и сохранения настроек. public SettingsModalWindow(VisualElement root, IServiceSettingsService settingsService) : base(root) { if (root == null) { return; } settingsList = root.Q("SettingsList"); statusLabel = root.Q