using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
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"), ("ambient_occlusion", "Umgebungsverdeckung (SSAO)") })
{
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 if (key == "ambient_occlusion")
LunePreferences.AmbientOcclusion = 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);
_checks["ambient_occlusion"].SetValueWithoutNotify(LunePreferences.AmbientOcclusion);
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, full screen and ambient occlusion take effect at once.
public static void Apply()
{
AudioListener.volume = LunePreferences.Muted ? 0f : LunePreferences.Volume;
ApplyAmbientOcclusion(LunePreferences.AmbientOcclusion);
if (Application.isBatchMode)
return;
if (Screen.fullScreen != LunePreferences.Fullscreen)
Screen.fullScreen = LunePreferences.Fullscreen;
}
///
/// Switches the SSAO feature of the URP renderer (U5.4). Only in the player: in the editor
/// the renderer is a project asset, and switching it there would change the file.
///
static void ApplyAmbientOcclusion(bool on)
{
if (Application.isEditor)
return;
ScriptableRendererFeature feature = AmbientOcclusionFeature();
if (feature != null && feature.isActive != on)
feature.SetActive(on);
}
/// The SSAO feature of the active URP renderer, or null (player self-test).
public static ScriptableRendererFeature AmbientOcclusionFeature()
{
if (!(GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset pipeline))
return null;
foreach (ScriptableRendererData data in pipeline.rendererDataList)
{
if (data == null)
continue;
foreach (ScriptableRendererFeature feature in data.rendererFeatures)
{
if (feature is ScreenSpaceAmbientOcclusion)
return feature;
}
}
return null;
}
}
/// 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)
Click();
});
}
/// Text of the switch (self-test lookup).
public string Text { get { return this.Q