using System; using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using Object = UnityEngine.Object; /// Use this class to override a built-in Editor that is not publicly exposed in the API. /// /// 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. /// /// 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. 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 cachedReflectionData = new(); private ReflectionData reflectionData; /// The name of the built-in Editor type that you want to override, as returned by GetType().Name 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() { 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); } } }