Files
project-reset/Assets/Scripts/Core/Tools/DebugOverlayDrawer.cs
2025-08-26 13:50:57 -04:00

240 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UIElements;
// ReSharper disable UsageOfDefaultStructEquality, to disable the warnings about IndexOf(currentPage). It's fine.
namespace Reset.Core.Tools{
public class DebugOverlayDrawer : MonoBehaviour{
[Header("Asset References")]
[BoxGroup("References")] public VisualTreeAsset valueTemplate;
[BoxGroup("References")] public VisualTreeAsset pageBaseTemplate;
[BoxGroup("References")] public VisualTreeAsset pageButtonTemplate;
[Header("Scene References")]
[BoxGroup("References")][ShowInInspector] public UIDocument root;
[BoxGroup("References")]public GameObject canvasRootGameObject;
[Space(10)]
[ShowInInspector]
public Dictionary<GameObject, List<string>> Pages = new(); // Page and value names
[Space(5)]
[ShowInInspector]
public Dictionary<object, Label> values = new(); // Value name and value value (lol)
[Space]
public string pageNamePrefix = "Debug Page: ";
public static bool Locked;
private static float defaultLabelAlpha;
private static float defaultValueAlpha;
// Private variables
[ShowInInspector]
public KeyValuePair<GameObject, List<string>> currentPage;
public enum DebugOverlayAnchor{
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Center
}
void Awake(){
if (Instance != null) {
Destroy(this);
}
Instance = this;
}
void Start(){
SetCurrentPageVisible();
Debug.Log(valueTemplate.Instantiate().resolvedStyle.backgroundColor);
defaultLabelAlpha = valueTemplate.Instantiate().Query<VisualElement>("LabelBackground").First().resolvedStyle.backgroundColor.a;
defaultValueAlpha = valueTemplate.Instantiate().Query<VisualElement>("ValueBackground").First().resolvedStyle.backgroundColor.a;
}
public static DebugOverlayDrawer Instance;
public static void AddOnOverlay(string pageName, string sourceName, Vector3 position){
// Make sure the page exists. If it does, use it. If not, create it
GameObject thisPageObject = null;
bool alreadyExisted = false;
// This checks for a gameobject with the page name. Sets it if it finds it
foreach (var page in Instance.Pages) {
if (page.Key.name == $"{Instance.pageNamePrefix}{pageName}") {
thisPageObject = page.Key;
alreadyExisted = true;
break;
}
}
// Otherwise, this will make a new one and add it
UIDocument thisDocument;
if (thisPageObject == null) {
// Create a new object
thisPageObject = new GameObject(name: $"{Instance.pageNamePrefix}{pageName}");
// Add it to the list of page objects and set it's transform
KeyValuePair<GameObject, List<string>> newPage = new(thisPageObject, new List<string>());
Instance.Pages.Add(newPage.Key, newPage.Value);
thisPageObject.transform.SetParent(Instance.canvasRootGameObject.transform);
// Add a UI Document for it, give it the base template
thisDocument = thisPageObject.AddComponent<UIDocument>();
thisDocument.visualTreeAsset = Instance.pageBaseTemplate;
// Then add a button/header to the main container
VisualElement newPageButton = Instance.pageButtonTemplate.Instantiate();
Instance.root.rootVisualElement.Query<VisualElement>("Pages").First().Add(newPageButton);
((Label)newPageButton.Query<VisualElement>("PageLabel").First()).text = pageName;
} else {
thisDocument = thisPageObject.GetComponent<UIDocument>();
}
// If this is the only page, set it to the current page
if (Instance.Pages.Count == 1) {
ChangeToPage(pageName);
SetCurrentPageVisible();
}
// Warning for the page being null for whatever reason by this point
if (thisPageObject == null) {
Debug.LogError(
$"Debug Overlay Manager never found a page named: {sourceName}. It failed to create one and aborted.");
return;
}
// Now, iterate through this page and see if the variable is already tracked
if (alreadyExisted && Instance.Pages[thisPageObject].Contains(sourceName)) {
return;
}
// Since it doesn't exist, add it to the list of tracked variables
Instance.Pages[thisPageObject].Add(sourceName);
// Then create a new on-screen element for this variable
VisualElement thisElement = (Instance.valueTemplate.Instantiate());
thisDocument.rootVisualElement.Query<VisualElement>("ValuesContainer").First().Add(thisElement);
// Set it's label
((Label)thisElement.Query<VisualElement>("Label").First()).text = sourceName;
Instance.values.Add($"{pageName}/{sourceName}", (Label)thisElement.Query<VisualElement>("Value"));
}
// Publicly accessible method to change the value
public static void ChangeValue(string pageName, string sourceName, string newValue){
Instance.values[$"{pageName}/{sourceName}"].text = newValue;
}
// Publicly accessible method to change the color
public static void ChangeColor(string pageName, string sourceName, Color newColor){
VisualElement labelBG = Instance.values[$"{pageName}/{sourceName}"].parent.parent.Query<VisualElement>("LabelBackground").First();
VisualElement valueBG = Instance.values[$"{pageName}/{sourceName}"].parent.parent.Query<VisualElement>("ValueBackground").First();
labelBG.style.backgroundColor = ((newColor / 1.2f + Color.gray * .3f)* .7f).Alpha(defaultLabelAlpha);
valueBG.style.backgroundColor = ((newColor / 1.5f + Color.gray * .4f) * .6f) .Alpha(defaultValueAlpha);
}
// Change to a specific page
public static void ChangeToPage(string pageName){
if (Locked) {
Debug.LogWarning($"Can't switch to page {pageName}: Page is currently locked");
return;
}
try {
Instance.currentPage = Instance.Pages.Where(pair => pair.Key.name == Instance.pageNamePrefix + pageName).First();
} catch (Exception e) {
Debug.LogError($"Couldn't switch page to {pageName}: {e.Message}");
throw;
}
}
// Back a page
public static void PreviousPage(){
// Do nothing if there's only one page
if (Instance.Pages.Count == 1) {
return;
}
// Lock check
if (Locked) {
Debug.LogWarning($"Attempted to switch to a new page but the page is locked.");
return;
}
// Get the index of the current page
int currentIndex = Instance.Pages.ToList().IndexOf(Instance.currentPage);
// Change the page with wrapping
if (Instance.Pages.First().Key == Instance.currentPage.Key) {
Instance.currentPage = Instance.Pages.Last();
} else {
Instance.currentPage = Instance.Pages.ElementAt(currentIndex - 1);
}
// Set visibility
SetCurrentPageVisible();
}
public static void NextPage(){
// Do nothing if there's only one page
if (Instance.Pages.Count == 1) {
return;
}
// Lock check
if (Locked) {
Debug.LogWarning($"Attempted to switch to a new page but the page is locked.");
return;
}
// Get the index of the current page
int currentIndex = Instance.Pages.ToList().IndexOf(Instance.currentPage);
// Change the page with wrapping
if (Instance.Pages.Last().Key == Instance.currentPage.Key) {
Instance.currentPage = Instance.Pages.First();
} else {
Instance.currentPage = Instance.Pages.ElementAt(currentIndex + 1);
}
// Set visibility
SetCurrentPageVisible();
}
static void SetCurrentPageVisible(){
// Set the display property for the page to enable/disable them
foreach (var page in Instance.Pages) {
if (page.Key == Instance.currentPage.Key) {
page.Key.GetComponent<UIDocument>().rootVisualElement.style.display = new StyleEnum<DisplayStyle>(DisplayStyle.Flex);
} else {
page.Key.GetComponent<UIDocument>().rootVisualElement.style.display = new StyleEnum<DisplayStyle>(DisplayStyle.None);
}
}
// Turn all the page labels into a list
var pageButtons = Instance.root.rootVisualElement.Query<Label>("PageLabel").ToList();
// Iterate through said list and set the opacity of the parent (so everything including the label gets turned down)
foreach (Label page in pageButtons) {
page.parent.style.opacity = .4f;
if (Instance.currentPage.Key.name == $"{Instance.pageNamePrefix}{page.text}") {
page.parent.style.opacity = 1f;
}
}
}
}
}