Skip to content

Instantly share code, notes, and snippets.

@kraj0t
Last active April 24, 2024 10:50
Show Gist options
  • Save kraj0t/aa65f15372befd2d5860e9e2d189a719 to your computer and use it in GitHub Desktop.
Save kraj0t/aa65f15372befd2d5860e9e2d189a719 to your computer and use it in GitHub Desktop.
Extend or modify built-in private Editor classes that are not exposed in the API, such as MeshRendererInspector or TransformInspector
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
/// <summary><para>Use this class to override a built-in Editor that is not publicly exposed in the API.</para>
///
/// <para>Instead of inheriting from the built-in class, which is impossible because it is private, this class uses reflection to create an instance of the
/// original editor and override its virtual methods and its Unity messages (AKA callbacks) such as OnEnable.</para>
///
/// <para>Only the most typically needed methods have been exposed as virtual. For example, UseDefaultMargins just calls the original editor's UseDefaultMargins
/// method, because you will very rarely need to extend it. In case you do need it, you can always access the OriginalEditor property, or write reflection code
/// yourself.</para></summary>
public abstract class InternalClassReflectionExtendedEditor : Editor
{
private class ReflectionData
{
public Type builtInEditorType;
public MethodInfo _Awake;
public MethodInfo _OnEnable;
public MethodInfo _OnDisable;
public MethodInfo _ShouldHideOpenButton;
public MethodInfo _OnHeaderGUI;
public MethodInfo _Reset;
public MethodInfo _OnValidate;
public MethodInfo _OnPreSceneGUI;
public MethodInfo _OnSceneGUI;
public MethodInfo _OnSceneDrag;
public MethodInfo _HasFrameBounds;
public MethodInfo _OnGetFrameBounds;
}
private static readonly Dictionary<string, ReflectionData> cachedReflectionData = new();
private ReflectionData reflectionData;
/// <summary>The name of the built-in Editor type that you want to override, as returned by GetType().Name</summary>
protected abstract string EditorTypeName { get; }
public Editor OriginalEditor { get; private set; }
#region Override and seal methods that you will typically want to extend, calling a new custom virtual method.
public sealed override void OnInspectorGUI()
{
DoOnInspectorGUI();
}
public override VisualElement CreateInspectorGUI()
{
return DoCreateInspectorGUI();
}
public sealed override void DrawPreview(Rect previewArea)
{
DoDrawPreview(previewArea);
}
protected void OnSceneGUI()
{
DoOnSceneGUI();
}
#endregion
#region Implement the Unity messages and call the custom virtual methods, declaring them as non-virtual to avoid accidental overriding.
protected void Awake()
{
CacheReflectionDataIfNeeded();
CreateOriginalEditorIfNeeded();
reflectionData._Awake?.Invoke(OriginalEditor, null);
DoAwake();
}
protected void OnEnable()
{
CacheReflectionDataIfNeeded();
CreateOriginalEditorIfNeeded();
reflectionData._OnEnable?.Invoke(OriginalEditor, null);
DoOnEnable();
}
protected void OnDisable()
{
DoOnDisable();
reflectionData._OnDisable?.Invoke(OriginalEditor, null);
}
protected void OnDestroy()
{
DoOnDestroy();
// Note: no need to call the original editor's OnDestroy(), as it will be called by Unity.
DestroyImmediate(OriginalEditor);
OriginalEditor = null;
}
protected void Reset()
{
CacheReflectionDataIfNeeded();
CreateOriginalEditorIfNeeded();
reflectionData._Reset?.Invoke(OriginalEditor, null);
}
protected void OnValidate()
{
CacheReflectionDataIfNeeded();
CreateOriginalEditorIfNeeded();
reflectionData._OnValidate?.Invoke(OriginalEditor, null);
}
protected void OnPreSceneGUI()
{
reflectionData._OnPreSceneGUI?.Invoke(OriginalEditor, null);
}
protected void OnSceneDrag(SceneView sceneView, int index)
{
reflectionData._OnSceneDrag?.Invoke(OriginalEditor, new object[] {sceneView, index});
}
protected bool HasFrameBounds()
{
if (reflectionData._HasFrameBounds == null)
return false;
return (bool)reflectionData._HasFrameBounds.Invoke(OriginalEditor, null);
}
protected Bounds OnGetFrameBounds()
{
if (reflectionData._OnGetFrameBounds == null)
return default(Bounds);
return (Bounds)reflectionData._OnGetFrameBounds?.Invoke(OriginalEditor, null)!;
}
#endregion
#region Create new virtual methods to extend the original Editor's functionality
protected virtual void DoAwake()
{
}
protected virtual void DoOnEnable()
{
}
protected virtual void DoOnDisable()
{
}
protected virtual void DoOnDestroy()
{
}
protected virtual void DoOnInspectorGUI()
{
OriginalEditor.OnInspectorGUI();
}
protected virtual VisualElement DoCreateInspectorGUI()
{
return OriginalEditor.CreateInspectorGUI();
}
protected virtual void DoDrawPreview(Rect previewArea)
{
OriginalEditor.DrawPreview(previewArea);
}
protected virtual void DoOnSceneGUI()
{
reflectionData._OnSceneGUI?.Invoke(OriginalEditor, null);
}
#endregion
#region Override certain methods without changing their behavior. These will rarely need extension or modification.
public override void OnPreviewSettings()
{
OriginalEditor.OnPreviewSettings();
}
public override string GetInfoString()
{
return OriginalEditor.GetInfoString();
}
public override GUIContent GetPreviewTitle()
{
return OriginalEditor.GetPreviewTitle();
}
public override bool UseDefaultMargins()
{
return OriginalEditor.UseDefaultMargins();
}
public override bool RequiresConstantRepaint()
{
return OriginalEditor.RequiresConstantRepaint();
}
protected override bool ShouldHideOpenButton()
{
// Note: using ! to supress warning, as this invocation will never return null, because it is implemented in the Editor class.
return (bool)reflectionData._ShouldHideOpenButton?.Invoke(OriginalEditor, null)!;
}
public override void SaveChanges()
{
OriginalEditor.SaveChanges();
}
public override void DiscardChanges()
{
OriginalEditor.DiscardChanges();
}
public override bool HasPreviewGUI()
{
return OriginalEditor.HasPreviewGUI();
}
protected override void OnHeaderGUI()
{
reflectionData._OnHeaderGUI?.Invoke(OriginalEditor, null);
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
OriginalEditor.OnPreviewGUI(r, background);
}
public override void ReloadPreviewInstances()
{
OriginalEditor.ReloadPreviewInstances();
}
public override Texture2D RenderStaticPreview(string assetPath, Object[] subAssets, int width, int height)
{
return OriginalEditor.RenderStaticPreview(assetPath, subAssets, width, height);
}
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
{
OriginalEditor.OnInteractivePreviewGUI(r, background);
}
#endregion
private void CacheReflectionDataIfNeeded()
{
if (reflectionData != null)
return;
if (!cachedReflectionData.TryGetValue(EditorTypeName, out reflectionData))
{
reflectionData = new ReflectionData();
var t = Type.GetType(EditorTypeName);
reflectionData.builtInEditorType = t;
reflectionData._Awake = t?.GetMethod("Awake", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
reflectionData._OnEnable = t?.GetMethod("OnEnable", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
reflectionData._OnDisable = t?.GetMethod("OnDisable", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
reflectionData._ShouldHideOpenButton = t?.GetMethod("ShouldHideOpenButton", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._OnHeaderGUI = t?.GetMethod("OnHeaderGUI", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._Reset = t?.GetMethod("Reset", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._OnValidate = t?.GetMethod("OnValidate", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._OnPreSceneGUI = t?.GetMethod("OnPreSceneGUI", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._OnSceneGUI = t?.GetMethod("OnSceneGUI", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._OnSceneDrag = t?.GetMethod("OnSceneDrag", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._HasFrameBounds = t?.GetMethod("HasFrameBounds", BindingFlags.Instance | BindingFlags.NonPublic);
reflectionData._OnGetFrameBounds = t?.GetMethod("OnGetFrameBounds", BindingFlags.Instance | BindingFlags.NonPublic);
cachedReflectionData.Add(EditorTypeName, reflectionData);
}
}
private void CreateOriginalEditorIfNeeded()
{
if (!OriginalEditor)
{
OriginalEditor = Editor.CreateEditor(targets, reflectionData.builtInEditorType);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment