///
/// MIT License - Copyright(c) 2019 Ugo Belfiore
///
using hedCommon.editor.editorWindow;
using hedCommon.extension.runtime;
using Sirenix.OdinInspector.Editor;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace ExtUnityComponents
{
public abstract class DecoratorComponentsEditor : Editor
{
///
/// all the virtual function at your disposal
///
#region virtual functions
///
/// called when the inspector is enabled
///
public virtual void OnCustomEnable() { }
///
/// called when the inspector is disabled
///
public virtual void OnCustomDisable() { }
///
/// called every OnInspectorGUI inside the herited class
///
protected virtual void OnCustomInspectorGUI() { }
///
/// called at the first OnInspectorGUI inside the herited class
///
protected virtual void InitOnFirstInspectorGUI() { }
///
/// called at the first OnSceneGUI inside the herited class
///
/// current drawing scene view
protected virtual void CustomOnSceneGUI(SceneView sceneview) { }
///
/// called at the first OnSceneGUI inside the herited class
///
/// current drawing scene view
protected virtual void InitOnFirstOnSceneGUI(SceneView sceneview) { }
///
/// called every editor Update inside the herited class
///
protected virtual void OnEditorUpdate() { }
///
/// called at the first Editor Update inside the herited class
///
protected virtual void InitOnFirstEditorUpdate() { }
public virtual void ShowTinyEditorContent() { }
protected virtual void OnHierarchyChanged() { }
#endregion
///
/// some private variable
///
#region private variables
/// EditorPref key for save the open/close extension
private const string KEY_EXPAND_EDITOR_EXTENSION = "KEY_EXPAND_EDITOR_EXTENSION";
private bool _openExtensions = false; //is this extension currently open or close ? save this for every gameObjects's components
protected bool IsExtensionOpened() => _openExtensions;
private readonly object[] EMPTY_ARRAY = new object[0]; //empty array for invoking methods using reflection
private System.Type _decoratedEditorType = null; //Type object for the internally used (decorated) editor.
private System.Type _editedObjectType; //Type object for the object that is edited by this editor.
private Editor _editorInstance; //urrent editorInstance
private bool _isCustomEditor = false;
private string _nameCustomEditor;
private bool _hasInitSceneGUI = false;
private bool _hasInitEditorUpdate = false;
private bool _hasInitInspector = false;
private bool _showExtension = true;
private bool _showTinyEditor = false;
public TinyEditorWindowSceneView TinyEditorWindow;
private readonly string KEY_EDITOR_PREF_EDITOR_WINDOW = "KEY_EDITOR_PREF_EDITOR_WINDOW";
private string _tinyEditorName = "tiny Editor";
public void ChangeNameEditor(string newName)
{
_tinyEditorName = newName;
if (TinyEditorWindow != null)
{
TinyEditorWindow.NameEditorWindow = newName;
}
}
protected Component ConcretTarget;
protected Component[] ConcretTargets;
/// reflexion cache
private Dictionary _decoratedMethods = new Dictionary();
private Assembly _editorAssembly = Assembly.GetAssembly(typeof(Editor));
///
/// get editor instance
///
/// editor instance
protected Editor GetEditorInstance()
{
return _editorInstance;
}
#endregion
///
/// all the required setup and initialisation
///
#region initialisation editor
public DecoratorComponentsEditor(bool showExtension = false, string tinyEditorName = "")
{
SetupDecoratorSettings(showExtension, tinyEditorName, true, "");
}
///
/// constructor of the class: here we initialize reflexion variables
///
/// name of the component we inspect
public DecoratorComponentsEditor(BUILT_IN_EDITOR_COMPONENTS editorTypeName, bool showExtension = true, string tinyEditorName = "")
{
SetupDecoratorSettings(showExtension, tinyEditorName, isCustomEditor: false, editorTypeName.ToString());
SetupCustomEditor(editorTypeName);
}
private void SetupDecoratorSettings(bool showExtension, string tinyEditorName, bool isCustomEditor, string nameEditor)
{
_showExtension = showExtension;
_showTinyEditor = !string.IsNullOrEmpty(tinyEditorName);
_isCustomEditor = isCustomEditor;
_nameCustomEditor = nameEditor;
_tinyEditorName = tinyEditorName;
}
private void InitTinyEditorWindow(string tinyEditorName)
{
TinyEditorWindow = new TinyEditorWindowSceneView();
string key = KEY_EDITOR_PREF_EDITOR_WINDOW + this.GetType().ToString();
TinyEditorWindow.TinyInit(key, tinyEditorName, TinyEditorWindowSceneView.DEFAULT_POSITION.UP_LEFT);
TinyEditorWindow.IsClosable = false;
TinyEditorWindow.IsMinimisable = true;
}
private void SetupCustomEditor(BUILT_IN_EDITOR_COMPONENTS editorTypeName)
{
_decoratedEditorType = _editorAssembly.GetTypes().Where(t => t.Name == editorTypeName.ToString()).FirstOrDefault();
Init();
// Check CustomEditor types.
System.Type originalEditedType = GetCustomEditorType(_decoratedEditorType);
if (originalEditedType != _editedObjectType)
{
throw new System.ArgumentException(
string.Format("Type {0} does not match the editor {1} type {2}",
_editedObjectType, editorTypeName, originalEditedType));
}
}
///
/// need to be called here to initialize the default editor
///
private void OnEnable()
{
SetupEditor();
InitTargets();
OnCustomEnable();
}
///
/// need to destroy the customEditor when quitting !
///
private new void OnDisable()
{
if (_editorInstance != null)
{
DestroyImmediate(_editorInstance);
}
_hasInitSceneGUI = false;
_hasInitEditorUpdate = false;
_hasInitInspector = false;
SceneView.duringSceneGui -= OwnOnSceneGUI;
EditorApplication.update -= CustomEditorApplicationUpdate;
EditorApplication.hierarchyChanged -= OnHierarchyChanged;
OnCustomDisable();
}
public T GetTarget() where T : Component
{
if (ConcretTarget == null)
{
return (default(T));
}
return ((T)ConcretTarget);
}
public GameObject GetGameObject()
{
return (ConcretTarget.gameObject);
}
public GameObject[] GetGameObjects()
{
if (ConcretTargets == null)
{
return (null);
}
GameObject[] gameObjects = new GameObject[ConcretTargets.Length];
for (int i = 0; i < ConcretTargets.Length; i++)
{
gameObjects[i] = ConcretTargets[i].gameObject;
}
return (gameObjects);
}
public T[] GetTargets() where T : Component
{
if (ConcretTarget == null)
{
return (default(T[]));
}
T[] array = new T[ConcretTargets.Length];
for (int i = 0; i < ConcretTargets.Length; i++)
{
array[i] = (T)ConcretTargets[i];
}
return (array);
}
public T GetTargetIndex(int index) where T : Component
{
if (ConcretTargets == null
|| ConcretTargets.Length == 0
|| index < 0
|| index >= ConcretTargets.Length)
{
return (default(T));
}
return ((T)ConcretTargets[index]);
}
///
/// return true if we have still the reference of the editor, but no more target...
///
///
public bool HaveWeLostTargets()
{
bool targetLost = ConcretTarget != null && target == null;
bool concretTargetLost = ConcretTarget == null;
bool contextLost = ConcretTarget != null && !Selection.gameObjects.Contains(ConcretTarget.gameObject);
return (/*_isCustomEditor && */(targetLost || concretTargetLost || contextLost));
}
///
/// Need to destroy the instance even on Destroy !
/// I don't know why. Unity generate leaks after each compilation
/// because of the CreateEditor
///
private void OnDestroy()
{
if (_editorInstance != null)
{
DestroyImmediate(_editorInstance);
}
//SceneView.duringSceneGui -= OwnOnSceneGUI;
//EditorApplication.update -= OnEditorApplicationUpdate;
}
///
/// here create the editor instance, and open / close the extension panel if needed
///
private void SetupEditor()
{
if (_editorInstance != null)
{
return;
}
_hasInitSceneGUI = false;
_hasInitEditorUpdate = false;
_hasInitInspector = false;
CreateDefaultInstance();
OpenOrCloseExtension();
}
///
/// here create the editor instance
///
private void CreateDefaultInstance()
{
if (_decoratedEditorType == null || targets == null)
{
return;
}
_editorInstance = Editor.CreateEditor(targets, _decoratedEditorType);
}
///
/// manage if the extensions are opened or closed, and save this setting in EditorPrefs
///
private void OpenOrCloseExtension()
{
if (EditorPrefs.HasKey(KEY_EXPAND_EDITOR_EXTENSION + _nameCustomEditor))
{
_openExtensions = EditorPrefs.GetBool(KEY_EXPAND_EDITOR_EXTENSION + _nameCustomEditor);
}
else
{
_openExtensions = false;
EditorPrefs.SetBool(KEY_EXPAND_EDITOR_EXTENSION + _nameCustomEditor, _openExtensions);
}
}
///
/// iniitalisation of reflexion variables
///
private void Init()
{
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var attributes = this.GetType().GetCustomAttributes(typeof(CustomEditor), true) as CustomEditor[];
var field = attributes.Select(editor => editor.GetType().GetField("m_InspectedType", flags)).First();
_editedObjectType = field.GetValue(attributes[0]) as System.Type;
}
///
/// from a given type of editor, get his CustomEditor as type
///
///
///
private System.Type GetCustomEditorType(System.Type type)
{
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var attributes = type.GetCustomAttributes(typeof(CustomEditor), true) as CustomEditor[];
var field = attributes.Select(editor => editor.GetType().GetField("m_InspectedType", flags)).First();
return field.GetValue(attributes[0]) as System.Type;
}
///
/// From a given name of class, invoke it using reflexion
///
protected void CallMethodUsingReflexion(string methodName)
{
MethodInfo method = null;
// Add MethodInfo to cache
if (!_decoratedMethods.ContainsKey(methodName))
{
var flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
method = _decoratedEditorType.GetMethod(methodName, flags);
if (method != null)
{
_decoratedMethods[methodName] = method;
}
else
{
//Debug.LogError(string.Format("Could not find method {0}", method));
}
}
else
{
method = _decoratedMethods[methodName];
}
if (method != null)
{
method.Invoke(GetEditorInstance(), EMPTY_ARRAY);
}
}
#endregion
///
/// The core loop function for display
/// everything
///
#region Display Extension
///
/// here the main InspectorGUI.
/// It's here that we call the base OnInspecctorGUI.
/// Then we show the GUI --------- Extension [+]
/// then if it's open, we call the abstract class CustomOnInspectorGUI()
///
public override void OnInspectorGUI()
{
if (!_isCustomEditor)
{
ManageUnityInspector();
}
else
{
ManageCustomNewInspector();
}
}
private void ManageUnityInspector()
{
if (GetEditorInstance() == null)
{
return;
}
if (HaveWeLostTargets())
{
DestroyImmediate(GetEditorInstance());
return;
}
GetEditorInstance().OnInspectorGUI();
CommonExtenstionInspectorGUI();
}
private void ManageCustomNewInspector()
{
base.OnInspectorGUI();
CommonExtenstionInspectorGUI();
}
///
/// called whatever this editor is inherited from a custom unity component,
/// or just a standar sutom editor
///
private void CommonExtenstionInspectorGUI()
{
TryToInitOnFirstInspectorGUI();
if (_showExtension
&& ConcretTarget != null
&& !IsInPrefabsStage()
&& CanShowExpandFromInterfaceSettings()
&& ExpendBehavior())
{
OnCustomInspectorGUI();
}
}
///
/// this function first check if the target have a IHaveDecoratorOnIt interface on it
/// if we don't have one, return automaticly true
/// (not every components have this decorator, like internal components or out own without the interface)
///
/// then return the settings we want
///
///
private bool CanShowExpandFromInterfaceSettings()
{
if (!HaveInterfaceSettings(out IHaveDecoratorOnIt haveDecorator))
{
return (true);
}
return (haveDecorator.ShowExtension);
}
///
/// this function first check if the target have a IHaveDecoratorOnIt interface on it
/// if we don't have one, return automaticly true
/// (not every components have this decorator, like internal components or out own without the interface)
///
/// then return the settings we want
///
///
private bool CanShowTinyEditorFromInterfaceSettings()
{
if (!HaveInterfaceSettings(out IHaveDecoratorOnIt haveDecorator))
{
return (true);
}
return (haveDecorator.ShowTinyEditorWindow);
}
///
/// this function check if the target have a IHaveDecoratorOnIt interface on it
/// if we don't have one, return automaticly true
/// (not every components have this decorator, like internal components or out own without the interface)
///
/// then return the settings we want
///
///
private bool HaveInterfaceSettings(out IHaveDecoratorOnIt haveDecorator)
{
haveDecorator = null;
if (!_isCustomEditor)
{
return (false);
}
if (ConcretTarget == null)
{
return (false);
}
IHaveDecoratorOnIt[] AllInterfaceDecorator = ConcretTarget.GetComponents();
for (int i = 0; i < AllInterfaceDecorator.Length; i++)
{
if (AllInterfaceDecorator[i] == null)
{
continue;
}
if (AllInterfaceDecorator[i].GetReferenceDecoratorsComponent() == ConcretTarget)
{
haveDecorator = AllInterfaceDecorator[i];
bool targetHaveInterface = haveDecorator != null;
return (targetHaveInterface);
}
}
return (false);
}
private void InitTargets()
{
if (ConcretTargets == null)
{
ConcretTarget = (Component)target;
ConcretTargets = new Component[targets.Length];
for (int i = 0; i < targets.Length; i++)
{
ConcretTargets[i] = (Component)targets[i];
}
//_meshFilterHiddedTools = _concretTarget.GetOrAddComponent();
SceneView.duringSceneGui -= OwnOnSceneGUI;
SceneView.duringSceneGui += OwnOnSceneGUI;
EditorApplication.update -= CustomEditorApplicationUpdate;
EditorApplication.update += CustomEditorApplicationUpdate;
EditorApplication.hierarchyChanged -= OnHierarchyChanged;
EditorApplication.hierarchyChanged += OnHierarchyChanged;
}
}
///
/// init the targets inspected and store them.
/// This is called at the first OnInspectorGUI()
/// Not in the constructor ! we don't have targets information in the constructor
///
private void TryToInitOnFirstInspectorGUI()
{
if (!_hasInitInspector)
{
InitOnFirstInspectorGUI();
_hasInitInspector = true;
}
}
private void TryToInitOnFirstOnEditorUpdate()
{
if (!_hasInitEditorUpdate)
{
InitOnFirstEditorUpdate();
_hasInitEditorUpdate = true;
}
}
///
/// editor update loop
///
private void CustomEditorApplicationUpdate()
{
if (!_isCustomEditor)
{
if (GetEditorInstance() == null)
{
return;
}
}
if (HaveWeLostTargets())
{
DestroyImmediate(GetEditorInstance());
return;
}
if (!IsExtensionOpened())
{
return;
}
TryToInitOnFirstOnEditorUpdate();
OnEditorUpdate();
}
///
/// This function is called at each OnSceneGUI to try to initialize it
///
private void TryToInitOnFirstOnSceneGUI(SceneView sceneview)
{
if (!_hasInitSceneGUI)
{
if (_showTinyEditor)
{
InitTinyEditorWindow(_tinyEditorName);
}
InitOnFirstOnSceneGUI(sceneview);
_hasInitSceneGUI = true;
}
}
///
/// return true if we are in a prefabs editing mode
///
///
private bool IsInPrefabsStage()
{
return (ExtPrefabs.IsEditingInPrefabMode(ConcretTarget.gameObject));
}
public void OwnOnSceneGUI(SceneView sceneview)
{
if (!_isCustomEditor)
{
if (GetEditorInstance() == null)
{
return;
}
}
if (HaveWeLostTargets())
{
DestroyImmediate(GetEditorInstance());
return;
}
TryToInitOnFirstOnSceneGUI(sceneview);
if (_showTinyEditor
&& !IsInPrefabsStage()
&& CanShowTinyEditorFromInterfaceSettings())
{
TinyEditorWindow.ShowEditorWindow(ShowTinyEditorContent, SceneView.currentDrawingSceneView, Event.current);
}
if (!IsExtensionOpened())
{
return;
}
CustomOnSceneGUI(sceneview);
}
///
/// display the Expand behavior of the component editor
///
private bool ExpendBehavior()
{
GUIStyle lineStyle = new GUIStyle();
lineStyle.normal.background = EditorGUIUtility.whiteTexture;
lineStyle.stretchWidth = true;
lineStyle.margin = new RectOffset(0, 0, 12, 18);
var c = GUI.color;
var p = GUILayoutUtility.GetRect(GUIContent.none, lineStyle, GUILayout.Height(1));
p.width -= 120;
if (Event.current.type == EventType.Repaint)
{
GUI.color = EditorGUIUtility.isProSkin ?
new Color(0.157f, 0.157f, 0.157f) : new Color(0.5f, 0.5f, 0.5f);
lineStyle.Draw(p, false, false, false, false);
}
EditorGUI.LabelField(new Rect(p.xMax + 30, p.y - 7, 70, 25), "Extensions");
GUI.color = Color.white;
GUI.backgroundColor = Color.white;
if (GUI.Button(new Rect(p.xMax + 100, p.y - 7, 18, 16), (_openExtensions ? " -" : " +")))
{
_openExtensions = !_openExtensions;
EditorPrefs.SetBool(KEY_EXPAND_EDITOR_EXTENSION + _nameCustomEditor, _openExtensions);
}
GUI.backgroundColor = Color.white;
GUI.color = c;
return (_openExtensions);
}
#endregion
///
/// other basic unity fonction we have to manage
///
#region Unity Fonctions
public void OnSceneGUI()
{
if (GetEditorInstance() == null)
{
return;
}
CallMethodUsingReflexion("OnSceneGUI");
}
protected override void OnHeaderGUI()
{
if (GetEditorInstance() == null)
{
return;
}
CallMethodUsingReflexion("OnHeaderGUI");
}
public override void DrawPreview(Rect previewArea)
{
if (GetEditorInstance() == null)
{
return;
}
GetEditorInstance().DrawPreview(previewArea);
}
public override string GetInfoString()
{
if (GetEditorInstance() == null)
{
return ("");
}
return GetEditorInstance().GetInfoString();
}
public override GUIContent GetPreviewTitle()
{
if (GetEditorInstance() == null)
{
return (null);
}
return GetEditorInstance().GetPreviewTitle();
}
public override bool HasPreviewGUI()
{
if (GetEditorInstance() == null)
{
return (false);
}
return GetEditorInstance().HasPreviewGUI();
}
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
{
if (GetEditorInstance() == null)
{
return;
}
GetEditorInstance().OnInteractivePreviewGUI(r, background);
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
if (GetEditorInstance() == null)
{
return;
}
GetEditorInstance().OnPreviewGUI(r, background);
}
public override void OnPreviewSettings()
{
if (GetEditorInstance() == null)
{
return;
}
GetEditorInstance().OnPreviewSettings();
}
public override void ReloadPreviewInstances()
{
if (GetEditorInstance() == null)
{
return;
}
GetEditorInstance().ReloadPreviewInstances();
}
public override Texture2D RenderStaticPreview(string assetPath, Object[] subAssets, int width, int height)
{
if (GetEditorInstance() == null)
{
return (null);
}
return GetEditorInstance().RenderStaticPreview(assetPath, subAssets, width, height);
}
public override bool RequiresConstantRepaint()
{
if (GetEditorInstance() == null)
{
return (false);
}
return GetEditorInstance().RequiresConstantRepaint();
}
public override bool UseDefaultMargins()
{
if (GetEditorInstance() == null)
{
return (true);
}
return GetEditorInstance().UseDefaultMargins();
}
#endregion
}
}