// ===================================================================== // Copyright 2013-2017 Fluffy Underware // All rights reserved // // http://www.fluffyunderware.com // ===================================================================== using UnityEngine; using System.Collections.Generic; using UnityEditor; using FluffyUnderware.DevToolsEditor.Extensions; using FluffyUnderware.DevTools; using System; using UnityEditorInternal; using UnityEditor.AnimatedValues; using UnityEngine.Events; using Object = UnityEngine.Object; namespace FluffyUnderware.DevToolsEditor { public class DTEditor : Editor where T : UnityEngine.Object { #region ### Public Properties ### /// /// Target Script /// public virtual T Target { get { return (target != null) ? target as T : null; } } /// /// Whether the target is currently selected or not /// public bool TargetIsActive { get { if (target is MonoBehaviour) return (target != null && ((MonoBehaviour)target).transform == Selection.activeTransform) ? true : false; else return true; } } /// /// Whether the target is part of a prefab /// /// Has issues with the new prefab system. See the comments in the code that assigns IsPrefab's value to know more about this // TODO Has issues with the new prefab system. See the comments in the code that assigns IsPrefab's value to know more about this [Obsolete("Will get removed in the next major version. Use Unity's PrefabUtility to know if this.Target is part of a prefab")] public bool IsPrefab { get; private set; } public bool IsInsideInspector { get; private set; } /// /// The Root node of all inspector fields /// public DTGroupNode Node { get { return mRootNode; } } /// /// The renderer used to render inspector fields /// public IDTInspectorNodeRenderer NodeRenderer { get { return mNodeRenderer; } set { mNodeRenderer = value; } } /// /// Whether the inspector needs a repaint /// public bool NeedRepaint { get; set; } #endregion DTGroupNode mRootNode; IDTInspectorNodeRenderer mNodeRenderer = new DTInspectorNodeDefaultRenderer(); bool mEnterChildren; #region ### Public Methods ### #endregion #region ### Protected Methods (override to change inspector appearance) ### protected virtual void OnEnable() { if (mRootNode == null) mRootNode = new DTGroupNode("Root"); Undo.undoRedoPerformed -= OnUndoRedo; Undo.undoRedoPerformed += OnUndoRedo; #pragma warning disable 618 if (target != null) { PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType(target); //BUG in the new prefab system, an instantiated prefab will have IsPrefab == true, while the documentation of the PrefabUtility.GetPrefabAssetType says otherwise. But is fixing this worth it knowing that IsPrefab is not used in Curvy when using the new prefab system? IsPrefab = prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant; } else IsPrefab = false; #pragma warning restore 618 } protected virtual void OnDisable() { if (mRootNode != null) mRootNode.Clear(); Undo.undoRedoPerformed -= OnUndoRedo; } protected virtual void OnReadNodes() { } protected virtual void OnSceneGUI() { } public override void OnInspectorGUI() { RenderGUI(false); } public void RenderGUI(bool embedded = false) { if (Target == null) return; #if UNITY_5_6_OR_NEWER serializedObject.UpdateIfRequiredOrScript(); #else serializedObject.UpdateIfDirtyOrScript(); #endif if (Node.Count == 0) ReadNodes(); NeedRepaint = false; if (mRootNode) { DTInspectorNode.IsInsideInspector = !embedded; IsInsideInspector = !embedded; OnCustomInspectorGUIBefore(); renderNode(mRootNode); OnCustomInspectorGUI(); } if (serializedObject.ApplyModifiedProperties()) OnModified(); GUI.SetNextControlName(""); if (NeedRepaint) Repaint(); } protected virtual void OnModified() { } // /// Add custom GUI code here, rendered before the default inspector /// protected virtual void OnCustomInspectorGUIBefore() { } /// /// Add custom GUI code here, rendered after the default inspector /// protected virtual void OnCustomInspectorGUI() { } /// /// Called to initialize a ReorderableList. Override to add custom behaviour /// /// field node /// ArrayEx attribute of the field protected virtual void SetupArrayEx(DTFieldNode node, ArrayExAttribute attribute) { // Defaults if (attribute.ShowHeader) { node.ArrayEx.drawHeaderCallback = (Rect r) => { EditorGUI.LabelField(r, node.GUIContent); if (attribute.DropTarget) { Event ev = Event.current; switch (ev.type) { case EventType.DragUpdated: if (r.Contains(ev.mousePosition)) { Type fieldType = node.serializedProperty.GetFieldType(); //bug? this code is called when dragging a game object over the list of Input Spots, but not the list of Input Game Objects. Why? Both have the ArrayExAttribute with ShowHeader being true bool allowed = DragAndDrop.objectReferences.Length > 0 && DTEditorUtility.DragDropTypeMatch(fieldType); DragAndDrop.visualMode = allowed ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; } break; case EventType.DragPerform: if (r.Contains(ev.mousePosition)) { Object[] objs = DTEditorUtility.DragDropGetObjectsOfType(node.serializedProperty.GetFieldType()); foreach (Object o in objs) { int idx = node.serializedProperty.arraySize; node.serializedProperty.InsertArrayElementAtIndex(idx); node.serializedProperty.GetArrayElementAtIndex(idx).objectReferenceValue = o; } node.serializedObject.ApplyModifiedProperties(); } break; } } }; } node.ArrayEx.drawElementCallback = (Rect r, int index, bool isActive, bool isFocused) => { SerializedProperty e = node.ArrayEx.serializedProperty.GetArrayElementAtIndex(index); if (e != null) EditorGUI.PropertyField(r, e); }; } /// /// Called when UndoRedo occured /// public virtual void OnUndoRedo() { } #endregion /// /// builds node tree and process parsing attributes /// public void ReadNodes() { DTGroupNode._serializedObject = serializedObject; SerializedProperty iterator = serializedObject.GetIterator(); mRootNode.Clear(); mEnterChildren = true; DTGroupNode baseNode = mRootNode; DTGroupNode parentNode = baseNode; Stack propertyPathStack = new Stack(); Stack baseNodeStack = new Stack(); bool resetParent = false; while (iterator.NextVisible(mEnterChildren)) { mEnterChildren = false; if (iterator.name != "m_Script" && iterator.name != "InspectorFoldout") { // handle baseNode resetting (AsGroup etc...) while (propertyPathStack.Count > 0 && !iterator.propertyPath.StartsWith(propertyPathStack.Peek())) { propertyPathStack.Pop(); baseNode = baseNodeStack.Pop(); parentNode = baseNode; } DTFieldNode fieldNode = new DTFieldNode(iterator); // get group parsing attributes List groupParseAttribs = iterator.GetAttributes(typeof(IDTGroupParsingAttribute)); groupParseAttribs.Sort(); // get field parsing attributes List parsingAttributes = iterator.GetAttributes(typeof(IDTFieldParsingAttribute)); foreach (IDTGroupParsingAttribute ga in groupParseAttribs) { if (ga is TabAttribute) { TabAttribute tabA = (TabAttribute)ga; parentNode = baseNode.EnsurePath(tabA.Path, false); if (!string.IsNullOrEmpty(tabA.TabBarName) && !string.IsNullOrEmpty(tabA.TabName)) { if (!parentNode[tabA.TabBarName]) parentNode = (DTGroupNode)parentNode.Add(new DTGroupNode(tabA.TabBarName, null, DTInspectorNode.RenderAsEnum.TabBar)); else parentNode = (DTGroupNode)parentNode[tabA.TabBarName]; if (!parentNode[tabA.TabName]) parentNode = (DTGroupNode)parentNode.Add(new DTGroupNode(tabA.TabName, iterator, DTInspectorNode.RenderAsEnum.Tab)); else parentNode = (DTGroupNode)parentNode[tabA.TabName]; if (tabA.Sort != 100) parentNode.SortOrder = tabA.Sort; } else DTLog.LogWarningFormat("[DevTools] Skipping [Tab] on '{0}' because Path is missing TabBar or Tab!", iterator.propertyPath); } else if (ga is SectionAttribute) { SectionAttribute sectionA = (SectionAttribute)ga; parentNode = createGroup(baseNode, sectionA.Path, iterator); if (sectionA.Sort != 100) parentNode.SortOrder = sectionA.Sort; } else if (ga is AsGroupAttribute) { AsGroupAttribute asGroupA = (AsGroupAttribute)ga; propertyPathStack.Push(fieldNode.SerializedPropertyPath); baseNodeStack.Push(baseNode); parentNode = createGroup((asGroupA.PathIsAbsolute) ? baseNode : parentNode, (asGroupA.Path == null) ? fieldNode.Name : asGroupA.Path + "/" + fieldNode.Name, iterator); baseNode = parentNode; } else if (ga is GroupAttribute) { GroupAttribute groupA = (GroupAttribute)ga; parentNode = createGroup(baseNode, groupA.Path, iterator); if (groupA.Sort != 100) parentNode.SortOrder = groupA.Sort; resetParent = true; } } foreach (IDTFieldParsingAttribute pa in parsingAttributes) { if (pa is Hide) { fieldNode.Visible = false; fieldNode.ContentVisible = false; mEnterChildren = false; } else if (pa is AsGroupAttribute || pa is Inline) { fieldNode.Visible = false; fieldNode.ContentVisible = false; mEnterChildren = true; } else if (pa is ArrayExAttribute) { ArrayExAttribute arrayA = (ArrayExAttribute)pa; fieldNode.ArrayEx = new ReorderableList(serializedObject, iterator, arrayA.Draggable, arrayA.ShowHeader, arrayA.ShowAdd, arrayA.ShowDelete); SetupArrayEx(fieldNode, arrayA); } else if (pa is SortOrderAttribute) { fieldNode.SortOrder = ((SortOrderAttribute)pa).Sort; } } parentNode.Add(fieldNode); if (resetParent) { parentNode = parentNode.Parent as DTGroupNode; resetParent = false; } } } OnReadNodes(); Node.Sort(); } /// /// Renders the node tree and process rendering attributes /// /// void renderNode(DTInspectorNode node) { if (serializedObject == null) return; bool guistate = GUI.enabled; DTInspectorNode item; for (int i = 0; i < node.Items.Count; i++) { item = node[i]; item.serializedObject = serializedObject; if (item.Disabled) GUI.enabled = false; if (item is DTFieldNode) { DTFieldNode field = (DTFieldNode)item; field.serializedProperty = serializedObject.FindProperty(field.SerializedPropertyPath); if (field.serializedProperty == null) return; field.Calculate(); if (field.Visible) { foreach (ActionAttribute act in item.Actions) if (act.Position == ActionAttribute.ActionPositionEnum.Above) NodeRenderer.RenderAction(item, act, this, Target); if (field.ArrayEx != null) { field.ArrayEx.serializedProperty = field.serializedProperty; field.ArrayEx.DoLayoutList(); } else { NodeRenderer.RenderField(field); field.raiseOnRender(); } foreach (ActionAttribute act in item.Actions) if (act.Position == ActionAttribute.ActionPositionEnum.Below) NodeRenderer.RenderAction(item, act, this, Target); } } else if (item is DTGroupNode) { DTGroupNode group = (DTGroupNode)item; group.Calculate(); if (group.Visible) { foreach (ActionAttribute act in item.Actions) if (act.Position == ActionAttribute.ActionPositionEnum.Above) NodeRenderer.RenderAction(item, act, this, Target); if (group.Disabled) GUI.enabled = false; switch (item.RenderAs) { case DTInspectorNode.RenderAsEnum.Section: NodeRenderer.RenderSectionHeader(group); if (group.ContentVisible) { renderNode(group); group.raiseOnRender(); } NodeRenderer.RenderSectionFooter(group); break; case DTInspectorNode.RenderAsEnum.TabBar: NodeRenderer.RenderTabBarHeader(group, (group.MaxItemsPerRow == -1) ? group.Items.Count : group.MaxItemsPerRow); if (group.SelectedIndex > -1) { renderNode(group[group.SelectedIndex]); group[group.SelectedIndex].raiseOnRender(); } NodeRenderer.RenderTabBarFooter(group); break; default: if (group.ContentVisible) renderNode(group); break; } foreach (ActionAttribute act in item.Actions) if (act.Position == ActionAttribute.ActionPositionEnum.Below) NodeRenderer.RenderAction(item, act, this, Target); } } GUI.enabled = guistate; if (item.NeedRepaint) { item.NeedRepaint = false; NeedRepaint = true; } } } /// /// Creates a parent group for a field, creating the path if neccessary /// /// "root" the path is applied to /// node path /// field property the parent node is for /// DTGroupNode createGroup(DTGroupNode baseNode, string path, SerializedProperty forProperty) { DTGroupNode node = baseNode.EnsurePath(path, false, forProperty); return node; } } }