using System; using UnityEngine; using UnityEngine.UIElements; namespace ProjectM.Client { /// /// Code-built UI Toolkit settings panel (Graphics + Audio) shared by the main menu and the in-game pause /// overlay. Binds to : edits a working copy, previews audio live, and persists /// + applies on Apply/Back. Enum settings use compact "< value >" cycle rows (robust, no dropdown-popup /// theming); volumes use sliders with a live percentage readout. /// public static class SettingsScreen { public static VisualElement Build(Action onBack) { var working = SettingsService.Current; var root = MenuUi.FullScreenRoot(true); var card = MenuUi.Card("SETTINGS"); card.style.minWidth = 480; root.Add(card); // ---------------- Graphics ---------------- card.Add(MenuUi.Caption("— GRAPHICS —")); var resolutions = Screen.resolutions; int resIndex = FindResIndex(resolutions, working.ResWidth, working.ResHeight); card.Add(CycleRow("Resolution", () => resolutions.Length > 0 ? ResLabel(resolutions[Mathf.Clamp(resIndex, 0, resolutions.Length - 1)]) : $"{working.ResWidth}x{working.ResHeight}", dir => { if (resolutions.Length == 0) return; resIndex = Wrap(resIndex + dir, resolutions.Length); var r = resolutions[resIndex]; working.ResWidth = r.width; working.ResHeight = r.height; working.RefreshHz = Mathf.RoundToInt((float)r.refreshRateRatio.value); })); string[] modes = { "Exclusive", "Borderless", "Maximized", "Windowed" }; card.Add(CycleRow("Display Mode", () => modes[Mathf.Clamp(working.FullScreenMode, 0, 3)], dir => working.FullScreenMode = Wrap(working.FullScreenMode + dir, 4))); var qnames = QualitySettings.names; card.Add(CycleRow("Quality", () => qnames.Length > 0 ? qnames[Mathf.Clamp(working.QualityLevel, 0, qnames.Length - 1)] : "—", dir => { if (qnames.Length > 0) working.QualityLevel = Wrap(working.QualityLevel + dir, qnames.Length); })); string[] vsync = { "Off", "On", "Half" }; card.Add(CycleRow("V-Sync", () => vsync[Mathf.Clamp(working.VSync, 0, 2)], dir => working.VSync = Wrap(working.VSync + dir, 3))); // ---------------- Audio ---------------- card.Add(MenuUi.Caption("— AUDIO —")); card.Add(VolumeRow("Master", working.Master, v => { working.Master = v; AudioListener.volume = v; })); card.Add(VolumeRow("Music", working.Music, v => { working.Music = v; GameVolume.Music = v; })); card.Add(VolumeRow("SFX", working.Sfx, v => { working.Sfx = v; GameVolume.Sfx = v; })); // ---------------- Buttons ---------------- var apply = MenuUi.Button("Apply", () => SettingsService.SaveAndApply(working)); apply.style.backgroundColor = new Color(0.12f, 0.30f, 0.22f, 1f); card.Add(apply); card.Add(MenuUi.Button("Back", () => { SettingsService.SaveAndApply(working); onBack?.Invoke(); })); return root; } static VisualElement CycleRow(string name, Func getValue, Action onCycle) { var row = Row(name); var val = new Label(getValue()); val.style.flexGrow = 1; val.style.unityTextAlign = TextAnchor.MiddleCenter; val.style.color = MenuUi.TextCol; val.style.fontSize = 15; var dec = MenuUi.SmallButton("<", () => { onCycle(-1); val.text = getValue(); }); var inc = MenuUi.SmallButton(">", () => { onCycle(+1); val.text = getValue(); }); row.Add(dec); row.Add(val); row.Add(inc); return row; } static VisualElement VolumeRow(string name, float initial, Action onChange) { var row = Row(name); var slider = new Slider(0f, 1f) { value = initial }; slider.style.flexGrow = 1; var pct = new Label(Pct(initial)); pct.style.minWidth = 52; pct.style.unityTextAlign = TextAnchor.MiddleRight; pct.style.color = MenuUi.TextCol; slider.RegisterValueChangedCallback(evt => { onChange(evt.newValue); pct.text = Pct(evt.newValue); }); row.Add(slider); row.Add(pct); return row; } static VisualElement Row(string name) { var row = new VisualElement(); row.style.flexDirection = FlexDirection.Row; row.style.alignItems = Align.Center; row.style.marginTop = 4; row.style.marginBottom = 4; var label = new Label(name); label.style.minWidth = 120; label.style.color = MenuUi.SubCol; label.style.fontSize = 14; row.Add(label); return row; } static string ResLabel(Resolution r) => $"{r.width} x {r.height} @ {Mathf.RoundToInt((float)r.refreshRateRatio.value)}"; static string Pct(float v) => Mathf.RoundToInt(v * 100f) + "%"; static int Wrap(int i, int n) => n <= 0 ? 0 : ((i % n) + n) % n; static int FindResIndex(Resolution[] all, int w, int h) { for (int i = 0; i < all.Length; i++) if (all[i].width == w && all[i].height == h) return i; return Mathf.Max(0, all.Length - 1); // default to the highest available } } }