using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures.Util; using NitroxModel.Helper; using UnityEngine; namespace NitroxClient.Debuggers { [ExcludeFromCodeCoverage] public abstract class BaseDebugger { public readonly string DebuggerName; public readonly KeyCode Hotkey; public readonly bool HotkeyAltRequired; public readonly bool HotkeyControlRequired; public readonly bool HotkeyShiftRequired; public readonly string HotkeyString; public readonly GUISkinCreationOptions SkinCreationOptions; /// /// Currently active tab. This is the index used with . /// public DebuggerTab ActiveTab; public bool CanDragWindow = true; public virtual bool Enabled { get; set; } public Rect WindowRect; /// /// Optional rendered tabs of the current debugger. /// /// /// gives the index of the currently selected tab. /// private readonly Dictionary tabs = new Dictionary(); private float maxHeight; protected BaseDebugger(float desiredWidth, string debuggerName = null, KeyCode hotkey = KeyCode.None, bool control = false, bool shift = false, bool alt = false, GUISkinCreationOptions skinOptions = GUISkinCreationOptions.DEFAULT, float maxHeight = 1000f) { this.maxHeight = maxHeight; if (desiredWidth < 200) { desiredWidth = 200; } WindowRect = new Rect(Screen.width / 2 - (desiredWidth / 2), Screen.height * 0.1f, desiredWidth, Math.Min(Screen.height * 0.8f, maxHeight)); // Default position in center of screen. Hotkey = hotkey; HotkeyAltRequired = alt; HotkeyShiftRequired = shift; HotkeyControlRequired = control; HotkeyString = Hotkey == KeyCode.None ? "None" : $"{(HotkeyControlRequired ? "CTRL+" : "")}{(HotkeyAltRequired ? "ALT+" : "")}{(HotkeyShiftRequired ? "SHIFT+" : "")}{Hotkey}"; SkinCreationOptions = skinOptions; if (string.IsNullOrEmpty(debuggerName)) { string name = GetType().Name; DebuggerName = name.Substring(0, name.IndexOf("Debugger", StringComparison.Ordinal)); } else { DebuggerName = debuggerName; } } public enum GUISkinCreationOptions { /// /// Uses the NitroxDebug skin. /// DEFAULT, /// /// Creates a copy of the default Unity IMGUI skin and sets the copied skin as render skin. /// UNITYCOPY, /// /// Creates a copy based on the NitroxDebug skin and sets the copied skin as render skin. /// DERIVEDCOPY } protected DebuggerTab AddTab(string name, Action render) { DebuggerTab tab = new(name, render); tabs.Add(tab.Name, tab); return tab; } protected Optional GetTab(string name) { Validate.NotNull(name); tabs.TryGetValue(name, out DebuggerTab tab); return Optional.OfNullable(tab); } public virtual void Update() { // Defaults to a no-op but can be overriden } /// /// Call this inside a method. /// public virtual void OnGUI() { if (!Enabled) { return; } GUISkin skin = GetSkin(); GUISkinUtils.RenderWithSkin(skin, () => { WindowRect = GUILayout.Window(GUIUtility.GetControlID(FocusType.Keyboard), WindowRect, RenderInternal, $"[DEBUGGER] {DebuggerName}", GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); }); } /// /// Optionally adjust the skin that is used during render. /// /// /// Set on or in constructor before using this method. /// /// Skin that is being used during . protected virtual void OnSetSkin(GUISkin skin) { } /// /// Optionally use a custom render solution for the debugger by overriding this method. /// protected virtual void Render() { ActiveTab?.Render(); } /// /// Gets (a copy of) a skin specified by . /// /// A reference to an existing or copied skin. private GUISkin GetSkin() { GUISkin skin = GUI.skin; string skinName = GetSkinName(); switch (SkinCreationOptions) { case GUISkinCreationOptions.DEFAULT: skin = GUISkinUtils.RegisterDerivedOnce("debuggers.default", s => { SetBaseStyle(s); OnSetSkinImpl(s); }); break; case GUISkinCreationOptions.UNITYCOPY: skin = GUISkinUtils.RegisterDerivedOnce(skinName, OnSetSkinImpl); break; case GUISkinCreationOptions.DERIVEDCOPY: GUISkin baseSkin = GUISkinUtils.RegisterDerivedOnce("debuggers.default", SetBaseStyle); skin = GUISkinUtils.RegisterDerivedOnce(skinName, OnSetSkinImpl, baseSkin); break; } return skin; } private string GetSkinName() { string name = GetType().Name; return $"debuggers.{name.Substring(0, name.IndexOf("Debugger")).ToLowerInvariant()}"; } private void OnSetSkinImpl(GUISkin skin) { if (SkinCreationOptions == GUISkinCreationOptions.DEFAULT) { Enabled = false; throw new NotSupportedException($"Cannot change {nameof(GUISkin)} for {GetType().FullName} when accessing the default skin. Change {nameof(SkinCreationOptions)} to something else than {nameof(GUISkinCreationOptions.DEFAULT)}."); } OnSetSkin(skin); } private void RenderInternal(int windowId) { using (new GUILayout.HorizontalScope("Box")) { if (tabs.Count == 1) { GUILayout.Label(tabs.First().Key, "tabActive"); } else { foreach (DebuggerTab tab in tabs.Values) { if (GUILayout.Button(tab.Name, ActiveTab == tab ? "tabActive" : "tab")) { ActiveTab = tab; } } } } Render(); if (CanDragWindow) { GUI.DragWindow(); } } private void SetBaseStyle(GUISkin skin) { skin.label.alignment = TextAnchor.MiddleLeft; skin.label.margin = new RectOffset(); skin.label.padding = new RectOffset(); skin.SetCustomStyle("header", skin.label, s => { s.margin.top = 10; s.margin.bottom = 10; s.alignment = TextAnchor.MiddleCenter; s.fontSize = 16; s.fontStyle = FontStyle.Bold; }); skin.SetCustomStyle("tab", skin.button, s => { s.fontSize = 16; s.margin = new RectOffset(5, 5, 5, 5); }); skin.SetCustomStyle("tabActive", skin.button, s => { s.fontStyle = FontStyle.Bold; s.fontSize = 16; }); } public virtual void ResetWindowPosition() { WindowRect = new Rect(Screen.width / 2f - (WindowRect.width / 2), Screen.height / 2f - (WindowRect.height / 2), WindowRect.width, Math.Min(Screen.height * 0.8f, maxHeight)); //Reset position of debuggers because SN sometimes throws the windows from planet 4546B } public class DebuggerTab { public DebuggerTab(string name, Action render) { Validate.NotNull(name, $"Expected a name for the {nameof(DebuggerTab)}"); Validate.NotNull(render, $"Expected an action for the {nameof(DebuggerTab)}"); Name = name; Render = render; } public string Name { get; } public Action Render { get; } } } }