using System; using System.Collections.Generic; using DanroJump.Settings; using DanroJump.UI.Modals; using UnityEngine; using UnityEngine.UIElements; namespace DanroJump.UI.MainMenu { /// /// Модальное окно сервисных настроек главного меню. /// Строит список полей динамически по базе . /// public sealed class SettingsModalWindow : ModalWindowBase, IDisposable { private const string GroupHeaderTemplateResourcePath = "UI/MainMenu/Templates/SettingsGroupHeader"; private const string BoolControlTemplateResourcePath = "UI/MainMenu/Templates/SettingsBoolControl"; private const string StepperControlTemplateResourcePath = "UI/MainMenu/Templates/SettingsStepperControl"; private const string TextControlTemplateResourcePath = "UI/MainMenu/Templates/SettingsTextControl"; private const string ButtonControlTemplateResourcePath = "UI/MainMenu/Templates/SettingsButtonControl"; 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 static VisualTreeAsset groupHeaderTemplate; private static VisualTreeAsset boolControlTemplate; private static VisualTreeAsset stepperControlTemplate; private static VisualTreeAsset textControlTemplate; private static VisualTreeAsset buttonControlTemplate; 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 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; /// /// Контейнер реального содержимого 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