From f16858ffcd006d89df2d58184b558b3eb087b6c1 Mon Sep 17 00:00:00 2001 From: Jeiel Aranal Date: Thu, 6 Jul 2017 01:47:27 +0800 Subject: [PATCH] Added option to edit ScriptableObjects inline in inspector --- EditScriptableAttribute.cs | 34 ++++++ Editor/ReorderableArrayInspector.cs | 178 +++++++++++++++++++++++----- 2 files changed, 183 insertions(+), 29 deletions(-) create mode 100644 EditScriptableAttribute.cs diff --git a/EditScriptableAttribute.cs b/EditScriptableAttribute.cs new file mode 100644 index 0000000..6db4237 --- /dev/null +++ b/EditScriptableAttribute.cs @@ -0,0 +1,34 @@ +/*MIT License + +Copyright(c) 2017 Jeiel Aranal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.*/ + +using UnityEngine; + +namespace SubjectNerd.Utilities +{ + /// + /// Display a ScriptableObject field with an inline editor + /// + public class EditScriptableAttribute : PropertyAttribute + { + + } +} \ No newline at end of file diff --git a/Editor/ReorderableArrayInspector.cs b/Editor/ReorderableArrayInspector.cs index bb024ef..139de08 100644 --- a/Editor/ReorderableArrayInspector.cs +++ b/Editor/ReorderableArrayInspector.cs @@ -20,6 +20,12 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ +// Uncomment the line below to turn all arrays into reorderable lists +//#define LIST_ALL_ARRAYS + +// Uncomment the line below to make all ScriptableObject fields editable +//#define EDIT_ALL_SCRIPTABLES + using System; using System.Collections.Generic; using System.Linq; @@ -218,11 +224,20 @@ public void SetDropHandler(SerializedProperty property, Action listIndex = new List(); + private readonly Dictionary editableIndex = new Dictionary(); protected bool alwaysDrawInspector = false; protected bool isInitialized = false; protected bool hasSortableArrays = false; + protected bool hasEditable = false; protected struct ContextMenuData { @@ -243,8 +258,10 @@ public ContextMenuData(string item) ~ReorderableArrayInspector() { listIndex.Clear(); + //hasSortableArrays = false; + editableIndex.Clear(); + //hasEditable = false; isInitialized = false; - hasSortableArrays = false; } #region Initialization @@ -265,13 +282,15 @@ protected virtual void InitInspector() if (isInitialized && FORCE_INIT == false) return; - FindSortableArrays(); + FindTargetProperties(); FindContextMenu(); } - protected void FindSortableArrays() + protected void FindTargetProperties() { listIndex.Clear(); + editableIndex.Clear(); + Type typeScriptable = typeof (ScriptableObject); SerializedProperty iterProp = serializedObject.GetIterator(); // This iterator goes through all the child serialized properties, looking @@ -282,19 +301,48 @@ protected void FindSortableArrays() { if (iterProp.isArray && iterProp.propertyType != SerializedPropertyType.String) { - bool canTurnToList = true; - // If not going to list all arrays - // Use SerializedPropExtension to check for attribute - if (LIST_ALL_ARRAYS == false) - { - canTurnToList = iterProp.HasAttribute(); - } - +#if LIST_ALL_ARRAYS + bool canTurnToList = true +#else + bool canTurnToList = iterProp.HasAttribute(); +#endif if (canTurnToList) { hasSortableArrays = true; CreateListData(serializedObject.FindProperty(iterProp.propertyPath)); + } + } + + if (iterProp.propertyType == SerializedPropertyType.ObjectReference) + { + Type propType = iterProp.GetTypeReflection(); + if (propType == null) + continue; + + bool isScriptable = propType.IsSubclassOf(typeScriptable); + if (isScriptable) + { +#if EDIT_ALL_SCRIPTABLES + bool makeEditable = true; +#else + bool makeEditable = iterProp.HasAttribute(); +#endif + if (makeEditable) + { + Editor scriptableEditor = null; + if (iterProp.objectReferenceValue != null) + { + CreateCachedEditorWithContext(iterProp.objectReferenceValue, + serializedObject.targetObject, null, + ref scriptableEditor); + var reorderable = scriptableEditor as ReorderableArrayInspector; + if (reorderable != null) + reorderable.isSubEditor = true; + } + editableIndex.Add(iterProp.propertyPath, scriptableEditor); + hasEditable = true; + } } } } while (iterProp.NextVisible(true)); @@ -313,7 +361,7 @@ private IEnumerable GetAllMethods(Type t) return Enumerable.Empty(); var binding = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; return t.GetMethods(binding).Concat(GetAllMethods(t.BaseType)); - } + } private void FindContextMenu() { @@ -421,6 +469,11 @@ private void HandleReorderableOptions(ReorderableAttribute arrayAttr, Serialized } } + /// + /// Given a SerializedProperty, return the automatic ReorderableList assigned to it if any + /// + /// + /// protected ReorderableList GetSortableList(SerializedProperty property) { if (listIndex.Count == 0) @@ -435,6 +488,12 @@ protected ReorderableList GetSortableList(SerializedProperty property) return data.GetPropertyList(property); } + /// + /// Set a drag and drop handler function on a SerializedObject's ReorderableList, if any + /// + /// + /// + /// protected bool SetDragDropHandler(SerializedProperty property, Action handler) { if (listIndex.Count == 0) @@ -449,17 +508,26 @@ protected bool SetDragDropHandler(SerializedProperty property, Action 0) listData = listIndex.Find(data => property.propertyPath.StartsWith(data.Parent)); + Editor scriptableEditor; + bool isScriptableEditor = editableIndex.TryGetValue(property.propertyPath, out scriptableEditor); + if (listData != null) { // Try to show the list @@ -536,22 +609,69 @@ protected void DrawPropertySortableArray(SerializedProperty property) } } + else if (isScriptableEditor) + { + if (scriptableEditor == null) + { + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.PropertyField(property, uiExpandWidth); + if (GUILayout.Button(labelBtnCreate, EditorStyles.miniButton, uiWidth50)) + { + Type propType = property.GetTypeReflection(); + var createdAsset = CreateAssetWithSavePrompt(propType, "Assets"); + if (createdAsset != null) + property.objectReferenceValue = createdAsset; + } + } + } + else + { + EditorGUILayout.PropertyField(property); + Rect rectFoldout = GUILayoutUtility.GetLastRect(); + rectFoldout.width = 20; + property.isExpanded = EditorGUI.Foldout(rectFoldout, property.isExpanded, GUIContent.none); + + if (property.isExpanded) + { + EditorGUI.indentLevel++; + scriptableEditor.OnInspectorGUI(); + GUILayout.Box(GUIContent.none, uiHeight2, uiExpandWidth); + EditorGUI.indentLevel--; + } + } + } else { SerializedProperty targetProp = serializedObject.FindProperty(property.propertyPath); - bool restoreEnable = GUI.enabled; - if (targetProp.propertyPath.StartsWith("m_")) - GUI.enabled = false; - EditorGUILayout.PropertyField(targetProp, targetProp.isExpanded); - GUI.enabled = restoreEnable; + + bool isStartProp = targetProp.propertyPath.StartsWith("m_"); + using (new EditorGUI.DisabledScope(isStartProp)) + { + EditorGUILayout.PropertyField(targetProp, targetProp.isExpanded); + } } } + // Creates a new ScriptableObject via the default Save File panel + private ScriptableObject CreateAssetWithSavePrompt(Type type, string path) + { + path = EditorUtility.SaveFilePanelInProject("Save ScriptableObject", "New " + type.Name + ".asset", "asset", "Enter a file name for the ScriptableObject.", path); + if (path == "") return null; + ScriptableObject asset = ScriptableObject.CreateInstance(type); + AssetDatabase.CreateAsset(asset, path); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); + EditorGUIUtility.PingObject(asset); + return asset; + } + #region Helper functions /// /// Draw the default inspector, with the sortable arrays /// - public void DrawDefaultSortable() + public void DrawPropertiesAll() { SerializedProperty iterProp = serializedObject.GetIterator(); IterateSerializedProp(iterProp); @@ -561,7 +681,7 @@ public void DrawDefaultSortable() /// Draw the default inspector, except for the given property names /// /// - public void DrawSortableExcept(params string[] propertyNames) + public void DrawPropertiesExcept(params string[] propertyNames) { SerializedProperty iterProp = serializedObject.GetIterator(); if (iterProp.NextVisible(true)) @@ -616,7 +736,7 @@ public void DrawPropertiesUpTo(string propertyStop) } while (iterProp.NextVisible(false)); } } - + /// /// Draw the default inspector, starting from a given property to a stopping property /// @@ -665,6 +785,6 @@ public void DrawContextMenuButtons() GUI.enabled = enabledState; } } - #endregion +#endregion } } \ No newline at end of file