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.

Revisions

  1. kraj0t revised this gist Apr 24, 2024. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions InternalClassReflectionExtendedEditor.cs
    Original file line number Diff line number Diff line change
    @@ -116,6 +116,9 @@ protected void Reset()

    protected void OnValidate()
    {
    CacheReflectionDataIfNeeded();
    CreateOriginalEditorIfNeeded();

    reflectionData._OnValidate?.Invoke(OriginalEditor, null);
    }

  2. kraj0t created this gist Apr 14, 2024.
    296 changes: 296 additions & 0 deletions InternalClassReflectionExtendedEditor.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,296 @@
    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()
    {
    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);
    }
    }
    }