using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; namespace Luneraria.UI { /// /// Options and controls (port of scripts/luneraria/ui/lune_settings.gd): volume, mute, full /// screen, reduced motion and the HUD size; the key list is the one place where every shortcut /// is shown. A running combat pauses offline while this window is open (the game controller /// does that on Opened/Closed). /// public sealed class LuneSettingsPanel : VisualElement { public static readonly float[] Scales = { .9f, 1f, 1.15f, 1.3f }; public static readonly (string group, (string key, string action)[] pairs)[] Keys = { ("Planung", new[] { ("1–5", "Truppe kaufen"), ("Leertaste", "Bereit"), ("Klick auf Einheit", "Details, Aufwerten, Einsatz"), ("Esc", "Auswahl oder Fenster schließen") }), ("Kampf", new[] { ("Leertaste / P", "Pause (offline)"), ("Tempo-Knopf", "1× · 2× · 4×") }), ("Kamera", new[] { ("F2 · F3 · F4", "Top · Mid · Bottom"), ("G", "Bossgrotte"), ("B", "eigener Ancient"), ("H", "eigener Held"), ("Tab", "nächste Lane"), ("O", "Übersicht"), ("R", "zurücksetzen"), ("Q / E", "drehen"), ("WASD · Pfeile · mittlere Maustaste", "verschieben"), ("Mausrad", "Zoom"), ("Minimap", "klicken oder ziehen") }), ("Allgemein", new[] { ("F1", "Optionen"), ("M", "Ton an/aus"), ("F11", "Vollbild") }), }; public event Action Opened; public event Action Closed; readonly LuneSlider _volume; readonly Dictionary _checks = new Dictionary(); public readonly List ScaleButtons = new List(); public LuneSettingsPanel() { style.position = Position.Absolute; style.left = 0f; style.top = 0f; style.right = 0f; style.bottom = 0f; style.backgroundColor = new Color(.02f, .03f, .04f, .9f); style.justifyContent = Justify.Center; style.alignItems = Align.Center; RegisterCallback(evt => evt.StopPropagation()); var panel = new LunePanel(LunePanels.Menu); panel.style.width = 900f; Add(panel); VisualElement column = LuneUi.Box(true, 10f, panel); Label heading = LuneUi.Label("Optionen & Steuerung", "HeadingLabel", column); heading.style.alignSelf = Align.Center; LuneUi.Label("Ein laufender Kampf pausiert offline, solange dieses Fenster geöffnet ist.", "MutedLabel", column).style.alignSelf = Align.Center; VisualElement top = LuneUi.Box(false, 30f, column); top.style.alignItems = Align.FlexStart; VisualElement left = LuneUi.Box(true, 8f, top); left.style.flexGrow = 1f; left.style.flexBasis = 0f; LuneUi.Label("LAUTSTÄRKE", "SectionLabel", left); _volume = new LuneSlider(); left.Add(_volume); _volume.Changed += value => { LunePreferences.Volume = value; LunePreferences.Commit(); Apply(); }; foreach ((string key, string text) entry in new[] { ("muted", "Ton stumm [M]"), ("fullscreen", "Vollbild [F11]"), ("reduced_motion", "Bewegung reduzieren") }) { var check = new LuneSwitch(entry.text); left.Add(check); string key = entry.key; check.Changed += value => { if (key == "muted") LunePreferences.Muted = value; else if (key == "fullscreen") LunePreferences.Fullscreen = value; else LunePreferences.ReducedMotion = value; LunePreferences.Commit(); Apply(); }; _checks[key] = check; } VisualElement right = LuneUi.Box(true, 8f, top); right.style.flexGrow = 1f; right.style.flexBasis = 0f; LuneUi.Label("UI-GRÖSSE IM SPIEL", "SectionLabel", right); VisualElement scales = LuneUi.Box(false, 6f, right); foreach (float value in Scales) { var button = new LuneButton(Mathf.RoundToInt(value * 100f) + " %", LuneButtons.HudToggle, scales); button.style.flexGrow = 1f; float captured = value; button.Clicked += () => { LunePreferences.UiScale = captured; LunePreferences.Commit(); Sync(); }; ScaleButtons.Add(button); } LuneUi.Wrap(LuneUi.Label("Größer für hochauflösende oder weit entfernte Bildschirme. Das HUD begrenzt die Größe selbst, sodass nichts aus dem Fenster ragt.", "MutedLabel", right)); var line = new VisualElement { pickingMode = PickingMode.Ignore }; line.style.height = 1f; line.style.marginTop = 7f; line.style.marginBottom = 7f; line.style.backgroundColor = LuneUiTheme.WithAlpha(LuneUiTheme.Bronze, .35f); column.Add(line); LuneUi.Label("STEUERUNG", "SectionLabel", column); VisualElement keys = LuneUi.Box(false, 26f, column); keys.style.alignItems = Align.FlexStart; for (int index = 0; index < 2; index++) { VisualElement list = LuneUi.Box(true, 2f, keys); list.style.flexGrow = 1f; list.style.flexBasis = 0f; var groups = index == 0 ? new[] { Keys[0], Keys[1], Keys[3] } : new[] { Keys[2] }; foreach (var group in groups) { LuneUi.Label(LuneUi.Upper(group.group), "EyebrowLabel", list, 14f).style.marginTop = 6f; foreach (var pair in group.pairs) { VisualElement row = LuneUi.Box(false, 14f, list); Label key = LuneUi.Label(pair.key, "CardFactLabel", row, 15f); key.style.minWidth = 160f; LuneUi.SetColor(key, LuneUiTheme.BronzeLight); LuneUi.Wrap(LuneUi.Label(pair.action, "MutedLabel", row, 15f)).style.flexGrow = 1f; } } } var back = new LuneButton("ZURÜCK [Esc]", LuneButtons.Primary, column); back.style.alignSelf = Align.Center; back.Clicked += Close; LuneUi.Show(this, false); } public bool Open { get { return style.display != DisplayStyle.None; } } public void Show() { Sync(); LuneUi.Show(this, true); BringToFront(); Opened?.Invoke(); } public void Close() { if (!Open) return; LuneUi.Show(this, false); Closed?.Invoke(); } /// Reads the preferences into the controls (Godot sync()). public void Sync() { _volume.SetValueWithoutNotify(LunePreferences.Volume); _checks["muted"].SetValueWithoutNotify(LunePreferences.Muted); _checks["fullscreen"].SetValueWithoutNotify(LunePreferences.Fullscreen); _checks["reduced_motion"].SetValueWithoutNotify(LunePreferences.ReducedMotion); int best = 1; for (int index = 0; index < Scales.Length; index++) { if (Mathf.Abs(Scales[index] - LunePreferences.UiScale) < Mathf.Abs(Scales[best] - LunePreferences.UiScale)) best = index; } for (int index = 0; index < ScaleButtons.Count; index++) ScaleButtons[index].On = index == best; } /// Volume, mute and full screen take effect at once. public static void Apply() { AudioListener.volume = LunePreferences.Muted ? 0f : LunePreferences.Volume; if (Application.isBatchMode) return; if (Screen.fullScreen != LunePreferences.Fullscreen) Screen.fullScreen = LunePreferences.Fullscreen; } } /// Horizontal slider in the HUD look (track, bronze fill, handle); value 0–1. public sealed class LuneSlider : VisualElement { public event Action Changed; public float Value { get; private set; } bool _drag; public LuneSlider() { style.height = 28f; pickingMode = PickingMode.Position; generateVisualContent += Draw; RegisterCallback(evt => { if (evt.button != 0) return; _drag = true; this.CapturePointer(evt.pointerId); Set(evt.localPosition.x); }); RegisterCallback(evt => { if (_drag) Set(evt.localPosition.x); }); RegisterCallback(evt => { _drag = false; this.ReleasePointer(evt.pointerId); }); } void Set(float x) { float value = Mathf.Clamp01((x - 9f) / Mathf.Max(1f, layout.width - 18f)); if (Mathf.Approximately(value, Value)) return; Value = value; MarkDirtyRepaint(); Changed?.Invoke(value); } public void SetValueWithoutNotify(float value) { Value = Mathf.Clamp01(value); MarkDirtyRepaint(); } void Draw(MeshGenerationContext context) { Painter2D p = context.painter2D; float width = layout.width; float y = layout.height * .5f; LunePlates.Line(p, new Vector2(9f, y), new Vector2(width - 9f, y), new Color(0f, 0f, 0f, .55f), 6f); float x = 9f + (width - 18f) * Value; LunePlates.Line(p, new Vector2(9f, y), new Vector2(x, y), LuneUiTheme.Bronze, 6f); p.fillColor = LuneUiTheme.BronzeLight; p.BeginPath(); p.Arc(new Vector2(x, y), 8f, Angle.Degrees(0f), Angle.Degrees(360f)); p.ClosePath(); p.Fill(); } } /// On/off switch with a label (Godot CheckButton with the toggle icons of the menu theme). public sealed class LuneSwitch : VisualElement { public event Action Changed; public bool Value { get; private set; } readonly VisualElement _knob; public LuneSwitch(string text) { style.flexDirection = FlexDirection.Row; style.alignItems = Align.Center; style.height = 34f; pickingMode = PickingMode.Position; Label label = LuneUi.Label(text, "CardFactLabel", this); label.style.flexGrow = 1f; _knob = new VisualElement { pickingMode = PickingMode.Ignore }; _knob.style.width = 44f; _knob.style.height = 22f; _knob.generateVisualContent += Draw; Add(_knob); RegisterCallback(evt => { if (evt.button != 0) return; Value = !Value; _knob.MarkDirtyRepaint(); Changed?.Invoke(Value); }); } public void SetValueWithoutNotify(bool value) { Value = value; _knob.MarkDirtyRepaint(); } void Draw(MeshGenerationContext context) { Painter2D p = context.painter2D; float w = _knob.layout.width; float h = _knob.layout.height; float r = h * .5f; p.fillColor = Value ? LuneUiTheme.WithAlpha(LuneUiTheme.Bronze, .85f) : new Color(0f, 0f, 0f, .5f); p.BeginPath(); p.Arc(new Vector2(r, r), r, Angle.Degrees(90f), Angle.Degrees(270f)); p.LineTo(new Vector2(w - r, 0f)); p.Arc(new Vector2(w - r, r), r, Angle.Degrees(270f), Angle.Degrees(450f)); p.ClosePath(); p.Fill(); p.fillColor = Value ? LuneUiTheme.Parchment : LuneUiTheme.Muted; p.BeginPath(); p.Arc(new Vector2(Value ? w - r : r, r), r - 3f, Angle.Degrees(0f), Angle.Degrees(360f)); p.ClosePath(); p.Fill(); } } }