Last active
April 24, 2024 10:50
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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