/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using Leap.Unity.Query; using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace Leap.Unity { public class CustomEditorBase : CustomEditorBase where T : UnityEngine.Object { protected new T target; protected new T[] targets; protected override void OnEnable() { base.OnEnable(); target = base.target as T; targets = base.targets.Query(). Where(t => t != null). OfType(). ToArray(); } } public class CustomEditorBase : Editor { protected Dictionary> _specifiedDrawers; protected Dictionary>> _specifiedDecorators; protected Dictionary>> _specifiedPostDecorators; protected Dictionary>> _conditionalProperties; protected Dictionary> _foldoutProperties; protected Dictionary _foldoutStates; protected List _deferredProperties; protected bool _showScriptField = true; private bool _canCallSpecifyFunctions = false; private GUIStyle _boldFoldoutStyle; protected List _modifiedProperties = new List(); protected void dontShowScriptField() { _showScriptField = false; } /// /// Specify a callback to be used to draw a specific named property. Should be called in OnEnable. /// /// /// protected void specifyCustomDrawer(string propertyName, Action propertyDrawer) { throwIfNotInOnEnable("specifyCustomDrawer"); if (!validateProperty(propertyName)) { return; } _specifiedDrawers[propertyName] = propertyDrawer; } /// /// Specify a callback to be used to draw a decorator for a specific named property. Should be called in OnEnable. /// protected void specifyCustomDecorator(string propertyName, Action decoratorDrawer) { throwIfNotInOnEnable("specifyCustomDecorator"); if (!validateProperty(propertyName)) { return; } List> list; if (!_specifiedDecorators.TryGetValue(propertyName, out list)) { list = new List>(); _specifiedDecorators[propertyName] = list; } list.Add(decoratorDrawer); } /// /// Specify a callback to be used to draw a decorator AFTER a specific named property. /// /// Should be called in OnEnable. /// protected void specifyCustomPostDecorator(string propertyName, Action decoratorDrawer) { throwIfNotInOnEnable("specifyCustomPostDecorator"); if (!validateProperty(propertyName)) { return; } List> list; if (!_specifiedPostDecorators.TryGetValue(propertyName, out list)) { list = new List>(); _specifiedPostDecorators[propertyName] = list; } list.Add(decoratorDrawer); } /// /// Specify a list of properties that should only be displayed if the conditional property has a value of true. /// Should be called in OnEnable. /// /// /// protected void specifyConditionalDrawing(string conditionalName, params string[] dependantProperties) { throwIfNotInOnEnable("specifyConditionalDrawing"); if (!validateProperty(conditionalName)) { return; } SerializedProperty conditionalProp = serializedObject.FindProperty(conditionalName); specifyConditionalDrawing(() => { if (conditionalProp.hasMultipleDifferentValues) { return false; } else { return conditionalProp.boolValue; } }, dependantProperties); } protected void specifyConditionalDrawing(string enumName, int enumValue, params string[] dependantProperties) { throwIfNotInOnEnable("specifyConditionalDrawing"); if (!validateProperty(enumName)) { return; } SerializedProperty enumProp = serializedObject.FindProperty(enumName); specifyConditionalDrawing(() => { if (enumProp.hasMultipleDifferentValues) { return false; } else { return enumProp.intValue == enumValue; } }, dependantProperties); } protected void hideField(string propertyName) { throwIfNotInOnEnable("hideField"); specifyConditionalDrawing(() => false, propertyName); } protected void specifyConditionalDrawing(Func conditional, params string[] dependantProperties) { throwIfNotInOnEnable("specifyConditionalDrawing"); for (int i = 0; i < dependantProperties.Length; i++) { string dependant = dependantProperties[i]; if (!validateProperty(dependant)) { continue; } List> list; if (!_conditionalProperties.TryGetValue(dependant, out list)) { list = new List>(); _conditionalProperties[dependant] = list; } list.Add(conditional); } } /// /// Defer rendering of a property until the end of the inspector. Deferred properties /// are drawn in the REVERSE order they are deferred! NOT by the order they appear in the /// serialized object! /// protected void deferProperty(string propertyName) { throwIfNotInOnEnable("deferProperty"); if (!validateProperty(propertyName)) { return; } _deferredProperties.Insert(0, propertyName); } /// /// Condition the drawing of a property based on the status of a foldout drop-down. /// protected void addPropertyToFoldout(string propertyName, string foldoutName, bool foldoutStartOpen = false) { throwIfNotInOnEnable("addPropertyToFoldout"); if (!validateProperty(propertyName)) { return; } List list; if (!_foldoutProperties.TryGetValue(foldoutName, out list)) { list = new List(); _foldoutProperties[foldoutName] = list; } _foldoutProperties[foldoutName].Add(propertyName); _foldoutStates[foldoutName] = foldoutStartOpen; } /// /// Check whether a property is inside of a foldout drop-down. /// protected bool isInFoldout(string propertyName) { bool isInFoldout = false; foreach (var foldout in _foldoutProperties) { foreach (string property in foldout.Value) { if (property.Equals(propertyName)) { isInFoldout = true; break; } } if (isInFoldout) { break; } } return isInFoldout; } protected void drawScriptField(bool disable = true) { var scriptProp = serializedObject.FindProperty("m_Script"); EditorGUI.BeginDisabledGroup(disable); EditorGUILayout.PropertyField(scriptProp); EditorGUI.EndDisabledGroup(); } protected virtual void OnEnable() { try { if (serializedObject == null) { } } catch (NullReferenceException) { DestroyImmediate(this); throw new Exception("Cleaning up an editor of type " + GetType() + ". Make sure to always destroy your editors when you are done with them!"); } _specifiedDrawers = new Dictionary>(); _specifiedDecorators = new Dictionary>>(); _specifiedPostDecorators = new Dictionary>>(); _conditionalProperties = new Dictionary>>(); _foldoutProperties = new Dictionary>(); _foldoutStates = new Dictionary(); _deferredProperties = new List(); _canCallSpecifyFunctions = true; } protected bool validateProperty(string propertyName) { if (serializedObject.FindProperty(propertyName) == null) { Debug.LogWarning("Property " + propertyName + " does not exist, was it removed or renamed?"); return false; } return true; } /* * This method draws all visible properties, mirroring the default behavior of OnInspectorGUI. * Individual properties can be specified to have custom drawers. */ public override void OnInspectorGUI() { // OnInspectorGUI is the first time "EditorStyles" can be accessed if (_boldFoldoutStyle == null) { _boldFoldoutStyle = new GUIStyle(EditorStyles.foldout); _boldFoldoutStyle.fontStyle = FontStyle.Bold; } _canCallSpecifyFunctions = false; _modifiedProperties.Clear(); SerializedProperty iterator = serializedObject.GetIterator(); bool isFirst = true; while (iterator.NextVisible(isFirst)) { if (isFirst && !_showScriptField) { isFirst = false; continue; } if (_deferredProperties.Contains(iterator.name) || isInFoldout(iterator.name)) { continue; } using (new EditorGUI.DisabledGroupScope(isFirst)) { drawProperty(iterator); } isFirst = false; } foreach (var deferredProperty in _deferredProperties) { if (!isInFoldout(deferredProperty)) { drawProperty(serializedObject.FindProperty(deferredProperty)); } } foreach (var foldout in _foldoutProperties) { _foldoutStates[foldout.Key] = EditorGUILayout.Foldout(_foldoutStates[foldout.Key], foldout.Key, _boldFoldoutStyle); if (_foldoutStates[foldout.Key]) { // Draw normal priority properties first foreach (var property in foldout.Value) { if (!_deferredProperties.Contains(property)) { drawProperty(serializedObject.FindProperty(property)); } } // Draw deferred properties second foreach (var property in foldout.Value) { if (_deferredProperties.Contains(property)) { drawProperty(serializedObject.FindProperty(property)); } } } } serializedObject.ApplyModifiedProperties(); } private void drawProperty(SerializedProperty property) { List> conditionalList; if (_conditionalProperties.TryGetValue(property.name, out conditionalList)) { bool allTrue = true; for (int i = 0; i < conditionalList.Count; i++) { allTrue &= conditionalList[i](); } if (!allTrue) { return; } } Action customDrawer; List> decoratorList; if (_specifiedDecorators.TryGetValue(property.name, out decoratorList)) { for (int i = 0; i < decoratorList.Count; i++) { decoratorList[i](property); } } EditorGUI.BeginChangeCheck(); if (_specifiedDrawers.TryGetValue(property.name, out customDrawer)) { customDrawer(property); } else { EditorGUILayout.PropertyField(property, true); } if (EditorGUI.EndChangeCheck()) { _modifiedProperties.Add(property.Copy()); } List> postDecoratorList; if (_specifiedPostDecorators.TryGetValue(property.name, out postDecoratorList)) { for (int i = 0; i < postDecoratorList.Count; i++) { postDecoratorList[i](property); } } } private void throwIfNotInOnEnable(string methodName) { if (!_canCallSpecifyFunctions) { throw new InvalidOperationException("Cannot call " + methodName + " from within any other function but OnEnable. Make sure you also call base.OnEnable as well!"); } } } }