Files
Nitrox/NitroxClient/Debuggers/BaseDebugger.cs
2025-07-06 00:23:46 +02:00

271 lines
9.2 KiB
C#

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;
/// <summary>
/// Currently active tab. This is the index used with <see cref="tabs"/>.
/// </summary>
public DebuggerTab ActiveTab;
public bool CanDragWindow = true;
public virtual bool Enabled { get; set; }
public Rect WindowRect;
/// <summary>
/// Optional rendered tabs of the current debugger.
/// </summary>
/// <remarks>
/// <see cref="ActiveTab"/> gives the index of the currently selected tab.
/// </remarks>
private readonly Dictionary<string, DebuggerTab> tabs = new Dictionary<string, DebuggerTab>();
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
{
/// <summary>
/// Uses the NitroxDebug skin.
/// </summary>
DEFAULT,
/// <summary>
/// Creates a copy of the default Unity IMGUI skin and sets the copied skin as render skin.
/// </summary>
UNITYCOPY,
/// <summary>
/// Creates a copy based on the NitroxDebug skin and sets the copied skin as render skin.
/// </summary>
DERIVEDCOPY
}
protected DebuggerTab AddTab(string name, Action render)
{
DebuggerTab tab = new(name, render);
tabs.Add(tab.Name, tab);
return tab;
}
protected Optional<DebuggerTab> 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
}
/// <summary>
/// Call this inside a <see cref="MonoBehaviour.OnGUI"/> method.
/// </summary>
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));
});
}
/// <summary>
/// Optionally adjust the skin that is used during render.
/// </summary>
/// <remarks>
/// Set <see cref="SkinCreationOptions"/> on <see cref="GUISkinCreationOptions.UNITYCOPY"/> or <see cref="GUISkinCreationOptions.DERIVEDCOPY"/> in constructor before using this method.
/// </remarks>
/// <param name="skin">Skin that is being used during <see cref="Render(int)"/>.</param>
protected virtual void OnSetSkin(GUISkin skin)
{
}
/// <summary>
/// Optionally use a custom render solution for the debugger by overriding this method.
/// </summary>
protected virtual void Render()
{
ActiveTab?.Render();
}
/// <summary>
/// Gets (a copy of) a skin specified by <see cref="GUISkinCreationOptions"/>.
/// </summary>
/// <returns>A reference to an existing or copied skin.</returns>
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; }
}
}
}