using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UIElements; namespace Ironspore { // Owns a settings draft and its UI. It does not write files or mutate gameplay state. public sealed class SettingsScreen { readonly SettingsService settings; readonly Func backPressed; readonly List focusOrder = new List(); readonly Dictionary buttonActions = new Dictionary(); VisualElement parent, screen; ScrollView scroll; Label countdown; Action closed; GameSettingsData draft; BindingSlot capture; string messageKey; object[] messageArgs = Array.Empty(); int tab, lastKeyFrame = -1; bool open; static string L(string key, params object[] args) => GameLocalization.Text(key, args); public SettingsScreen(SettingsService settings, Func backPressed) { this.settings = settings; this.backPressed = backPressed; } public void Open(VisualElement parent, Action closed) { this.parent = parent; this.closed = closed; draft = settings.Current; messageKey = null; open = true; Draw(); } public void Close() { if (!open) return; open = false; capture = null; settings.DiscardDraft(); screen?.RemoveFromHierarchy(); screen = null; scroll = null; closed = null; focusOrder.Clear(); buttonActions.Clear(); } public void RefreshLocale() { if (!open) return; string focusedName = (screen.focusController.focusedElement as VisualElement)?.name; Draw(); if (!string.IsNullOrEmpty(focusedName)) FocusAfterDispatch(screen.Q(focusedName)); } public void CancelInput() => settings.Bindings.Cancel(); void Draw() { Vector2 offset = scroll?.scrollOffset ?? Vector2.zero; screen?.RemoveFromHierarchy(); focusOrder.Clear(); buttonActions.Clear(); screen = Element(parent, "settings-screen", "settings-screen"); screen.RegisterCallback(evt => { if (HandleKey(evt.keyCode, evt.shiftKey)) evt.StopImmediatePropagation(); }, TrickleDown.TrickleDown); screen.RegisterCallback(CapturePointer, TrickleDown.TrickleDown); var panel = Element(screen, "settings-panel"); var header = Element(panel, "settings-header"); var title = Element(header, "settings-title-group"); Text(title, L("settings.title"), "heading"); Text(title, L("settings.help"), "settings-help"); var locales = GameLocalization.Catalog.locales; var names = new List(); foreach (var locale in locales) names.Add(locale.nativeName); int languageIndex = Array.FindIndex(locales, x => x.code == GameLocalization.CurrentCode); var language = Dropdown(header, "settings.language", names, languageIndex, index => GameLocalization.SelectLocale(locales[index].code)); language.name = "settings-language"; language.AddToClassList("settings-language"); var body = Element(panel, "settings-body"); var navigation = Element(body, "settings-navigation"); string[] tabs = { "settings.audio", "settings.display", "settings.controls" }; for (int i = 0; i < tabs.Length; i++) { int choice = i; var button = AddButton(navigation, L(tabs[i]), () => { tab = choice; scroll = null; Draw(); }, "settings-tab"); button.name = "settings-tab-" + i; if (tab == i) button.AddToClassList("selected"); } scroll = new ScrollView(ScrollViewMode.Vertical) { horizontalScrollerVisibility = ScrollerVisibility.Hidden }; scroll.AddToClassList("settings-scroll"); body.Add(scroll); if (tab == 0) Audio(scroll); else if (tab == 1) Display(scroll); else Controls(scroll); var renderedScroll = scroll; renderedScroll.schedule.Execute(() => { if (renderedScroll.panel != null) renderedScroll.scrollOffset = offset; }); var status = Text(panel, string.IsNullOrEmpty(messageKey) ? "" : L(messageKey, messageArgs), "settings-status"); status.name = "settings-status"; var footer = Element(panel, "settings-footer"); var reset = AddButton(footer, L("settings.reset"), Reset, "button"); reset.name = "settings-reset"; var footerActions = Element(footer, "settings-footer-actions"); AddButton(footerActions, L("settings.back"), Back, "button").name = "settings-back"; AddButton(footerActions, L("settings.apply"), Apply, "primary").name = "settings-apply"; if (settings.ConfirmingDisplay) { header.SetEnabled(false); body.SetEnabled(false); footer.SetEnabled(false); var dialog = Dialog("settings.display.confirm"); countdown = Text(dialog, L("settings.display.countdown", settings.ConfirmationSeconds), "body"); FocusAfterDispatch(AddButton(dialog, L("settings.display.keep"), ConfirmDisplay, "primary")); AddButton(dialog, L("settings.display.revert"), RevertDisplay, "button"); } else if (capture != null) { header.SetEnabled(false); body.SetEnabled(false); footer.SetEnabled(false); var dialog = Dialog("settings.bind.waiting"); Text(dialog, L("settings.bind.prompt", L(capture.LabelKey)), "body"); var cancel = AddButton(dialog, L("settings.bind.cancel"), settings.Bindings.Cancel, "button"); cancel.name = "settings-capture-cancel"; FocusAfterDispatch(cancel); } else FocusAfterDispatch(navigation.Q