first commit

This commit is contained in:
2026-05-08 18:21:51 +01:00
commit 00f9448329
31 changed files with 556 additions and 0 deletions
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using Godot;
using Tutorials.addons.custom_inspector_tools.InspectorPlugins;
namespace Tutorials.addons.custom_inspector_tools;
#if TOOLS
[Tool]
public partial class CustomInspectorPlugin : EditorPlugin
{
private readonly List<EditorInspectorPlugin> _customPlugins = new();
private bool _pluginsBuilt = false;
private bool _inspectorPluginsAdded = false;
private EditorSelection _selection;
private bool _refreshingInspector = false;
public override void _EnterTree()
{
base._EnterTree();
if (!_pluginsBuilt)
{
BuildPlugins();
_pluginsBuilt = true;
}
_selection = EditorInterface.Singleton.GetSelection();
_selection.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged()
{
if (!_inspectorPluginsAdded)
{
return;
}
RefreshInspectorForCurrentSelection();
}
private void RefreshInspectorForCurrentSelection()
{
if (_refreshingInspector)
{
return;
}
_refreshingInspector = true;
try
{
GodotObject objToInspect = null;
foreach (var node in _selection.GetSelectedNodes())
{
if (node is not null)
{
objToInspect = node;
break;
}
}
// Drop old inspector UI, then rebuild for the selected object.
EditorInterface.Singleton.InspectObject(null);
if (objToInspect is not null)
{
EditorInterface.Singleton.InspectObject(objToInspect);
}
}
finally
{
_refreshingInspector = false;
}
}
private void BuildPlugins()
{
_customPlugins.Clear();
// Add new plugins here as you create them.
_customPlugins.Add(new TestBoxInspectorPlugin());
}
public override void _EnablePlugin()
{
base._EnablePlugin();
try
{
if (_inspectorPluginsAdded)
return;
foreach (var plugin in _customPlugins)
{
AddInspectorPlugin(plugin);
GD.Print($"Added inspector plugin: {plugin.GetType().Name}");
}
_inspectorPluginsAdded = true;
RefreshInspectorForCurrentSelection();
}
catch (Exception ex)
{
GD.PrintErr($"Error enabling CustomInspectorPlugin: {ex.Message}");
}
}
public override void _DisablePlugin()
{
try
{
RemoveAllInspectorPlugins();
EditorInterface.Singleton.InspectObject(null);
RefreshInspectorForCurrentSelection();
}
catch (Exception ex)
{
GD.PrintErr($"Error disabling CustomInspectorPlugin: {ex.Message}");
}
base._DisablePlugin();
}
private void RemoveAllInspectorPlugins()
{
if (!_inspectorPluginsAdded)
return;
foreach (var plugin in _customPlugins)
{
RemoveInspectorPlugin(plugin);
GD.Print($"Removed inspector plugin: {plugin.GetType().Name}");
}
_inspectorPluginsAdded = false;
}
}
#endif
@@ -0,0 +1 @@
uid://dxmsbkgfqf1kh
@@ -0,0 +1,16 @@
using System;
namespace Tutorials.addons.custom_inspector_tools.InspectorPlugins;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class TestBoxAttribute : Attribute
{
public string Identifier { get; }
public string Text { get; }
public TestBoxAttribute(string identifier, string text)
{
Identifier = identifier;
Text = text;
}
}
@@ -0,0 +1 @@
uid://c0rv1g5ikjgrm
@@ -0,0 +1,153 @@
#if TOOLS
using System.Collections.Generic;
using System.Reflection;
using Godot;
using Microsoft.VisualBasic.CompilerServices;
namespace Tutorials.addons.custom_inspector_tools.InspectorPlugins;
[Tool]
public partial class TestBoxInspectorPlugin : EditorInspectorPlugin
{
private const bool DEBUG_LOGS = true;
// What object are we looking at?
private ulong _currentObjectId = 0;
private readonly HashSet<string> _identifiersInjectedForCurrentObject = new();
public override bool _CanHandle(GodotObject @object)
{
if (@object is null)
{
return false;
}
var objectId = @object.GetInstanceId();
if (DEBUG_LOGS)
{
GD.Print($"[TestBoxInspectorPlugin] _CanHandle called for object with ID {objectId}");
}
var type = @object.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var property in properties)
{
if (property.GetCustomAttribute<TestBoxAttribute>() is not null)
{
if (DEBUG_LOGS)
{
GD.Print($"[TestBoxInspectorPlugin] Found property with TestBoxAttribute: {property.Name}");
}
return true;
}
}
foreach (var field in fields)
{
if (field.GetCustomAttribute<TestBoxAttribute>() is not null)
{
if (DEBUG_LOGS)
{
GD.Print($"[TestBoxInspectorPlugin] Found field with TestBoxAttribute: {field.Name}");
}
return true;
}
}
return false;
}
public override bool _ParseProperty(GodotObject @object, Variant.Type type, string name, PropertyHint hintType, string hintString,
PropertyUsageFlags usageFlags, bool wide)
{
if (@object is null)
{
return false;
}
var objectId = @object.GetInstanceId();
if (_currentObjectId != objectId)
{
_currentObjectId = objectId;
_identifiersInjectedForCurrentObject.Clear();
if (DEBUG_LOGS)
{
GD.Print($"[TestBoxInspectorPlugin] New object selected with ID {_currentObjectId}. Cleared injected identifiers.");
}
}
var objectType = @object.GetType();
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
var property = objectType.GetProperty(name, flags);
var field = objectType.GetField(name, flags);
if (property is null && field is null && name.Contains("_"))
{
// Godot mangles property names with underscores, so we need to check for that.
var unmangledName = name.Replace("_", "");
property = objectType.GetProperty(unmangledName, flags);
field = objectType.GetField(unmangledName, flags);
}
var member = (MemberInfo)property ?? field;
if (member is null)
{
return false;
}
var testBoxAttribute = member.GetCustomAttribute<TestBoxAttribute>();
if (testBoxAttribute is null)
{
return false;
}
if (_identifiersInjectedForCurrentObject.Add(testBoxAttribute.Identifier))
{
if (DEBUG_LOGS)
{
GD.Print($"[TestBoxInspectorPlugin] Injecting TestBox for identifier '{testBoxAttribute.Identifier}' on object ID {_currentObjectId}");
}
AddCustomControl(TestBoxControl.Create(testBoxAttribute.Text));
}
else
{
if (DEBUG_LOGS)
{
GD.Print($"[TestBoxInspectorPlugin] Already injected TestBox for identifier '{testBoxAttribute.Identifier}' on object ID {_currentObjectId}. Skipping.");
}
}
return false;
}
}
[Tool]
public partial class TestBoxControl : PanelContainer
{
public static TestBoxControl Create(string text)
{
var control = new TestBoxControl();
control.SizeFlagsHorizontal = SizeFlags.ExpandFill;
var label = new RichTextLabel()
{
Text = text,
FitContent = true,
ScrollActive = false,
BbcodeEnabled = true,
SizeFlagsHorizontal = SizeFlags.ExpandFill
};
control.AddChild(label);
return control;
}
}
#endif
@@ -0,0 +1 @@
uid://t0xwv6w6ue8v
+6
View File
@@ -0,0 +1,6 @@
[plugin]
name="Custom Inspector Tools"
description="Custom Inspector Tools"
author="omegaleo"
version="1.0"
script="CustomInspectorPlugin.cs"