using System; using UnityEngine; namespace S3.Modules.Popout; // Renders the Map Popout settings: hotkey, behaviour toggles, theme preset buttons, // a live per-element color editor for the Custom theme, and overlay opacity sliders. static class PopoutSettingsUI { private static bool _capturing; // --------------------------------------------------------------------------- // Color editor state — one hex string per theme element, kept in sync with // the sliders. Reset by InitColorEditor when a preset is copied or on first draw. // --------------------------------------------------------------------------- private static bool _colorEditorInit; private static string _hexWBg = "1F1F1F"; private static string _hexAcc = "4296FA"; private static string _hexTxt = "FFFFFF"; private static string _hexPop = "141414"; private static string _hexMap = "FFFFFF"; private static string _hexCmp = "161616"; private static string _hexNN = "CD3232"; private static string _hexNS = "CDCDCD"; private static string _hexMapBg = "151F28"; private static bool _showColorEditor = true; // expanded by default private static readonly string[] s_themeNames = { "S3 Dark", "Classic", "Night", "Hi-Vis", "Retro", "Custom" }; private static readonly string[] s_presetNames = { "S3 Dark", "Classic", "Night", "Hi-Vis", "Retro" }; private const float kLabelW = 92f; private const float kThemeW = 68f; // wide enough for "S3 Dark" / "Classic" private const float kHexW = 60f; private const float kSwatchW = 20f; private const float kChanL = 12f; private const float kSliderW = 52f; private const float kValW = 26f; // --------------------------------------------------------------------------- public static void Draw() { PopoutSettings s = PopoutModule.Settings; // ── Hotkey ────────────────────────────────────────────────────────────── GUILayout.BeginHorizontal(); GUILayout.Label("Pop-out hotkey:", GUILayout.Width(110f)); if (_capturing) { GUILayout.Label("Press a key… (Esc to cancel)"); Event e = Event.current; if (e.type == EventType.KeyDown) { if (e.keyCode != KeyCode.Escape && e.keyCode != KeyCode.None) { s.hotkeyKeyCode = (int)e.keyCode; s.hotkeyModifiers = (e.shift ? 1 : 0) | (e.control ? 2 : 0) | (e.alt ? 4 : 0); PopoutModule.RebuildHotkey(); PopoutModule.Persist(); } _capturing = false; e.Use(); } } else { if (GUILayout.Button($"{HotkeyLabel(s)} (click to change)", GUILayout.Width(220f))) _capturing = true; } GUILayout.EndHorizontal(); GUILayout.Space(8f); // ── Behaviour ─────────────────────────────────────────────────────────── bool pan = GUILayout.Toggle(s.panDisablesFollow, " Dragging the map cancels follow mode"); if (pan != s.panDisablesFollow) { s.panDisablesFollow = pan; PopoutModule.Persist(); } GUILayout.Space(8f); // ── Theme preset quick-switch ──────────────────────────────────────────── GUILayout.BeginHorizontal(); GUILayout.Label("Theme:", GUILayout.Width(kLabelW)); for (int i = 0; i < s_themeNames.Length; i++) { GUI.color = s.mapTheme == i ? new Color(0.5f, 0.9f, 1.0f) : Color.white; if (GUILayout.Button(s_themeNames[i], GUILayout.Width(kThemeW))) MapThemes.Apply((MapTheme)i); } GUI.color = Color.white; GUILayout.EndHorizontal(); GUILayout.Space(4f); // ── Overlay opacity ────────────────────────────────────────────────────── OpacityRow("Window opacity:", ref s.overlayAlpha, 0.1f, 1.0f, Native.RRPOPOUT_SetOverlayAlpha); OpacityRow("Map opacity:", ref s.overlayMapAlpha, 0.0f, 1.0f, Native.RRPOPOUT_SetOverlayMapAlpha); GUILayout.Space(8f); // ── Status + detach button ────────────────────────────────────────────── bool det = PopoutModule.IsDetached; GUILayout.Label(det ? "Status: Detached" : "Status: Docked"); if (GUILayout.Button(det ? "Re-attach map" : "Detach map", GUILayout.Width(160f))) PopoutModule.Toggle(); GUILayout.Space(8f); // ── Custom color editor (collapsible) ──────────────────────────────────── string editorHeader = _showColorEditor ? "▲ Custom Colors (click to collapse)" : "▼ Custom Colors (click to expand)"; if (GUILayout.Button(editorHeader, GUILayout.Width(300f))) _showColorEditor = !_showColorEditor; if (_showColorEditor) { GUILayout.Space(4f); GUILayout.Label("Hex = RRGGBB (no #). Drag sliders to tune live; changes switch to Custom."); // "Copy from:" seeds the editor from a named preset and applies it as Custom GUILayout.BeginHorizontal(); GUILayout.Label("Copy from:", GUILayout.Width(kLabelW)); for (int i = 0; i < s_presetNames.Length; i++) { if (GUILayout.Button(s_presetNames[i], GUILayout.Width(kThemeW))) { MapThemeData pd = MapThemes.GetPresetData((MapTheme)i); s.customTheme = pd; InitColorEditor(ref s.customTheme); MapThemes.Apply(MapTheme.Custom); } } GUILayout.EndHorizontal(); GUILayout.Space(4f); if (!_colorEditorInit) InitColorEditor(ref s.customTheme); // Each row writes directly through ref to the struct field on the heap-allocated // PopoutSettings class, so no copy-modify-write boilerplate is needed. bool anyChanged = false; anyChanged |= ColorRow("Window BG", ref s.customTheme.wBgR, ref s.customTheme.wBgG, ref s.customTheme.wBgB, ref s.customTheme.wBgA, showAlpha: true, ref _hexWBg); anyChanged |= ColorRow("Accent", ref s.customTheme.accR, ref s.customTheme.accG, ref s.customTheme.accB, ref s.customTheme.accA, showAlpha: false, ref _hexAcc); anyChanged |= ColorRow("Text", ref s.customTheme.txtR, ref s.customTheme.txtG, ref s.customTheme.txtB, ref s.customTheme.txtA, showAlpha: false, ref _hexTxt); anyChanged |= ColorRow("Popup BG", ref s.customTheme.popR, ref s.customTheme.popG, ref s.customTheme.popB, ref s.customTheme.popA, showAlpha: true, ref _hexPop); anyChanged |= ColorRow("Map Tint", ref s.customTheme.mapR, ref s.customTheme.mapG, ref s.customTheme.mapB, ref s.customTheme.mapA, showAlpha: true, ref _hexMap); anyChanged |= ColorRow("Compass BG", ref s.customTheme.cmpR, ref s.customTheme.cmpG, ref s.customTheme.cmpB, ref s.customTheme.cmpA, showAlpha: true, ref _hexCmp); anyChanged |= ColorRow("N Needle", ref s.customTheme.nNR, ref s.customTheme.nNG, ref s.customTheme.nNB, ref s.customTheme.nNA, showAlpha: false, ref _hexNN); anyChanged |= ColorRow("S Needle", ref s.customTheme.nSR, ref s.customTheme.nSG, ref s.customTheme.nSB, ref s.customTheme.nSA, showAlpha: false, ref _hexNS); anyChanged |= ColorRow("Map Camera", ref s.customTheme.mapBgR, ref s.customTheme.mapBgG, ref s.customTheme.mapBgB, ref s.customTheme.mapBgA, showAlpha: false, ref _hexMapBg); if (anyChanged) MapThemes.Apply(MapTheme.Custom); } } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- private static void OpacityRow(string label, ref float field, float min, float max, Action setNative) { GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(110f)); float nv = GUILayout.HorizontalSlider(field, min, max, GUILayout.Width(180f)); GUILayout.Label($"{field:P0}", GUILayout.Width(44f)); GUILayout.EndHorizontal(); if (Mathf.Abs(nv - field) > 0.001f) { field = nv; setNative(nv); PopoutModule.Persist(); } } // Syncs all nine hex strings from the given theme data snapshot. // Call after "Copy from preset" or on first draw. private static void InitColorEditor(ref MapThemeData t) { _hexWBg = ToHex(t.wBgR, t.wBgG, t.wBgB); _hexAcc = ToHex(t.accR, t.accG, t.accB); _hexTxt = ToHex(t.txtR, t.txtG, t.txtB); _hexPop = ToHex(t.popR, t.popG, t.popB); _hexMap = ToHex(t.mapR, t.mapG, t.mapB); _hexCmp = ToHex(t.cmpR, t.cmpG, t.cmpB); _hexNN = ToHex(t.nNR, t.nNG, t.nNB); _hexNS = ToHex(t.nSR, t.nSG, t.nSB); _hexMapBg = ToHex(t.mapBgR, t.mapBgG, t.mapBgB); _colorEditorInit = true; } // Renders one theme element row and returns true if any value changed. // hexStr shows RRGGBB (no #). Typing a valid 6-char hex updates R/G/B; // moving a slider updates R/G/B and also rebuilds hexStr. private static bool ColorRow( string label, ref float r, ref float g, ref float b, ref float a, bool showAlpha, ref string hexStr) { bool changed = false; GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(kLabelW)); // Hex input string newHex = GUILayout.TextField(hexStr, 6, GUILayout.Width(kHexW)); if (newHex != hexStr) { hexStr = newHex; if (newHex.Length == 6 && ColorUtility.TryParseHtmlString("#" + newHex, out Color hc)) { r = hc.r; g = hc.g; b = hc.b; changed = true; } } // Live color swatch GUI.color = new Color(r, g, b, 1f); GUILayout.Box("", GUILayout.Width(kSwatchW), GUILayout.Height(16f)); GUI.color = Color.white; // R, G, B sliders; rebuild hex if any change bool rC = ChanSlider("R", ref r); bool gC = ChanSlider("G", ref g); bool bC = ChanSlider("B", ref b); if (rC || gC || bC) { hexStr = ToHex(r, g, b); changed = true; } // Optional alpha slider (no hex involvement) if (showAlpha && ChanSlider("A", ref a)) changed = true; GUILayout.EndHorizontal(); return changed; } // Renders a single channel: letter label + slider (0-1) + integer value (0-255). // Returns true if the value moved more than the movement threshold. private static bool ChanSlider(string label, ref float v) { GUILayout.Label(label, GUILayout.Width(kChanL)); float nv = GUILayout.HorizontalSlider(v, 0f, 1f, GUILayout.Width(kSliderW)); GUILayout.Label(Mathf.RoundToInt(nv * 255).ToString(), GUILayout.Width(kValW)); if (Mathf.Abs(nv - v) > 0.002f) { v = nv; return true; } return false; } private static string ToHex(float r, float g, float b) => $"{Mathf.RoundToInt(r * 255):X2}{Mathf.RoundToInt(g * 255):X2}{Mathf.RoundToInt(b * 255):X2}"; private static string HotkeyLabel(PopoutSettings s) { string prefix = ""; if ((s.hotkeyModifiers & 2) != 0) prefix += "Ctrl+"; if ((s.hotkeyModifiers & 1) != 0) prefix += "Shift+"; if ((s.hotkeyModifiers & 4) != 0) prefix += "Alt+"; return prefix + (KeyCode)s.hotkeyKeyCode; } }