Instance indicator colors

Added instance colors to better indicate what is being displayed.
Code refactor.
This commit is contained in:
max 2021-12-24 03:19:19 +01:00
parent 91648de3d9
commit 22fee290ce
4 changed files with 256 additions and 237 deletions

View File

@ -1,6 +1,8 @@
# Change Log: # Change Log:
## 1.0.0 ## 1.1.1
- Base project. - Instance indicator colors.
- Code refactor.
## 1.1.0 ## 1.1.0
- ExstendableScriptableObjectDrawer. - ExstendableScriptableObjectDrawer.
## 1.0.0
- Base project.

View File

@ -1,218 +1,154 @@
// References: // References:
// https://gist.github.com/tomkail/ba4136e6aa990f4dc94e0d39ec6a058c // https://gist.github.com/tomkail/ba4136e6aa990f4dc94e0d39ec6a058c
// Developed by Tom Kail at Inkle // Developed by Tom Kail at Inkle
// Released under the MIT Licence as held at https://opensource.org/licenses/MIT // Released under the MIT Licence as held at https://opensource.org/licenses/MIT
using System; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
using Object = UnityEngine.Object;
namespace ScriptableData.Editor namespace ScriptableData.Editor
{ {
[CustomPropertyDrawer(typeof(ScriptableObject), true)] [CustomPropertyDrawer(typeof(ScriptableObject), true)]
public class ExtendedScriptableObjectDrawer : PropertyDrawer public class ExtendedScriptableObjectDrawer : PropertyDrawer
{ {
private static readonly string[] ignoreClassFullNames = new string[] { "TMPro.TMP_FontAsset" }; // Permamently exclude classes from being affected by the drawer.
private const int buttonWidth = 20; private static readonly string[] ignoredFullClassNames = new string[] { "TMPro.TMP_FontAsset" };
private const int buttonWidth = 20;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{ {
float totalHeight = EditorGUIUtility.singleLineHeight; float totalHeight = EditorGUIUtility.singleLineHeight;
if (property.objectReferenceValue == null || !HasVisableSubProperties(property) || fieldInfo.HasAttribute<NonExtendableAttribute>())
{
return totalHeight;
}
if (property.isExpanded) SerializedObject serializedObject = null;
{ if (property.objectReferenceValue is ScriptableObject scriptableObject)
ScriptableObject data = property.objectReferenceValue as ScriptableObject; {
serializedObject = new SerializedObject(scriptableObject);
}
if (data == null) if (serializedObject == null || !serializedObject.HasVisableSubProperties() || fieldInfo.HasAttribute<NonExtendableAttribute>())
{ {
return EditorGUIUtility.singleLineHeight; return totalHeight;
} }
SerializedObject serializedObject = new SerializedObject(data); if (property.isExpanded)
SerializedProperty prop = serializedObject.GetIterator(); {
if (prop.NextVisible(true)) // Iterate over all the values and draw them.
{ SerializedProperty p = serializedObject.GetIterator();
do
{
if (prop.name == "m_Script")
{
continue;
}
SerializedProperty subProp = serializedObject.FindProperty(prop.name); if (p.NextVisible(true))
float height = EditorGUI.GetPropertyHeight(subProp, null, true) + EditorGUIUtility.standardVerticalSpacing; {
totalHeight += height; do
} {
while (prop.NextVisible(false)); // Skip drawing the class file.
} if (EditorUtils.IsIgnoredProperty(p))
// Add a tiny bit of height if open for the background {
totalHeight += EditorGUIUtility.standardVerticalSpacing; continue;
} }
return totalHeight; float height = EditorGUI.GetPropertyHeight(p, null, true) + EditorGUIUtility.standardVerticalSpacing;
} totalHeight += height;
}
while (p.NextVisible(false));
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) // Add a tiny bit of height if open for the background
{ totalHeight += EditorGUIUtility.standardVerticalSpacing;
EditorGUI.BeginProperty(position, label, property); }
return totalHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
Type fieldType = EditorUtils.GetFieldType(fieldInfo); Type fieldType = EditorUtils.GetFieldType(fieldInfo);
if (fieldType == null || HasIgnoredClassName(fieldType) || fieldInfo.HasAttribute<NonExtendableAttribute>()) // Draw with default drawer.
{ if (fieldType == null || HasIgnoredClassName(fieldType) || fieldInfo.HasAttribute<NonExtendableAttribute>())
EditorGUI.PropertyField(position, property, label); {
EditorGUI.EndProperty(); EditorGUI.PropertyField(position, property, label);
return; EditorGUI.EndProperty();
} return;
}
ScriptableObject propertySO = null;
if (!property.hasMultipleDifferentValues && property.serializedObject.targetObject != null && property.serializedObject.targetObject is ScriptableObject)
{
propertySO = property.serializedObject.targetObject as ScriptableObject;
}
GUIContent guiContent = new GUIContent(property.displayName);
Rect foldoutRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth, EditorGUIUtility.singleLineHeight); Rect foldoutRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth, EditorGUIUtility.singleLineHeight);
if (property.objectReferenceValue != null && HasVisableSubProperties(property))
{
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, guiContent, true);
}
else
{
foldoutRect.x += 10;
EditorGUI.Foldout(foldoutRect, property.isExpanded, guiContent, true, EditorStyles.label);
}
Rect indentedPosition = EditorGUI.IndentedRect(position); Rect indentedPosition = EditorGUI.IndentedRect(position);
float indentOffset = indentedPosition.x - position.x; float indentOffset = indentedPosition.x - position.x;
Rect propertyRect = new Rect(position.x + (EditorGUIUtility.labelWidth - indentOffset + 2), position.y, position.width - (EditorGUIUtility.labelWidth - indentOffset + 2), EditorGUIUtility.singleLineHeight); Rect propertyRect = new Rect(position.x + (EditorGUIUtility.labelWidth - indentOffset + 2), position.y, position.width - (EditorGUIUtility.labelWidth - indentOffset + 2), EditorGUIUtility.singleLineHeight);
if (propertySO != null || property.objectReferenceValue == null) SerializedObject serializedObject = null;
{ if(property.objectReferenceValue is ScriptableObject scriptableObject)
propertyRect.width -= buttonWidth; {
} serializedObject = new SerializedObject(scriptableObject);
}
EditorGUI.BeginChangeCheck(); // Check for sub properties and if the property can be expanded.
if (serializedObject != null && serializedObject.HasVisableSubProperties())
{
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, new GUIContent(property.displayName), true);
Object assignedObject = EditorGUI.ObjectField(propertyRect, GUIContent.none, property.objectReferenceValue, fieldType, false); if (property.isExpanded)
{
// Draw sub properties.
serializedObject.Draw(position);
}
}
else
{
EditorGUI.LabelField(foldoutRect, new GUIContent(property.displayName));
if (EditorGUI.EndChangeCheck()) if (property.objectReferenceValue == null)
{ {
property.objectReferenceValue = assignedObject; propertyRect.width -= buttonWidth;
property.serializedObject.ApplyModifiedProperties();
}
Rect buttonRect = new Rect(position.x + position.width - buttonWidth, position.y, buttonWidth, EditorGUIUtility.singleLineHeight); // Draw create button.
Rect buttonRect = new Rect(position.x + position.width - buttonWidth, position.y, buttonWidth, EditorGUIUtility.singleLineHeight);
DrawScriptableObjectCreateButton(buttonRect, property, fieldType);
}
}
if (property.propertyType == SerializedPropertyType.ObjectReference && property.objectReferenceValue != null) EditorGUI.BeginChangeCheck();
{ // Draw object field.
if (property.isExpanded) EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
{
DrawScriptableObjectChildFields(position, property);
}
}
else
{
DrawScriptableObjectCreateButton(buttonRect, property, fieldType);
}
property.serializedObject.ApplyModifiedProperties(); if (EditorGUI.EndChangeCheck())
EditorGUI.EndProperty(); {
} property.serializedObject.ApplyModifiedProperties();
}
private void DrawScriptableObjectChildFields(Rect position, SerializedProperty property) property.serializedObject.ApplyModifiedProperties();
EditorGUI.EndProperty();
}
private static void DrawScriptableObjectCreateButton(Rect position, SerializedProperty property, Type type)
{ {
ScriptableObject scriptableObject = (ScriptableObject)property.objectReferenceValue; if (GUI.Button(position, "+"))
{
GenericMenu typeChooser = new GenericMenu();
IEnumerable<Type> types = type.Assembly.GetTypes().Where(t => type.IsAssignableFrom(t));
foreach (Type t in types)
{
if (t.IsAbstract)
{
continue;
}
if (scriptableObject == null) typeChooser.AddItem(new GUIContent(t.Name), false, (o) => {
{ property.objectReferenceValue = EditorUtils.CreateAssetWithSavePrompt(o as Type, EditorUtils.GetSelectedAssetPath(property));
return; property.serializedObject.ApplyModifiedProperties();
} }, t);
}
typeChooser.ShowAsContext();
}
}
// Draw a background that shows us clearly which fields are part of the ScriptableObject private static bool HasIgnoredClassName(Type type)
GUI.Box(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing - 1, position.width, position.height - EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing), "");
EditorGUI.indentLevel++;
SerializedObject serializedObject = new SerializedObject(scriptableObject);
// Iterate over all the values and draw them
SerializedProperty prop = serializedObject.GetIterator();
float y = position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.BeginChangeCheck();
if (prop.NextVisible(true))
{
do
{
// Don't bother drawing the class file
if (prop.name == "m_Script") continue;
float height = EditorGUI.GetPropertyHeight(prop, new GUIContent(prop.displayName), true);
EditorGUI.PropertyField(new Rect(position.x, y, position.width /*- buttonWidth*/, height), prop, true);
y += height + EditorGUIUtility.standardVerticalSpacing;
}
while (prop.NextVisible(false));
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
EditorGUI.indentLevel--;
}
private void DrawScriptableObjectCreateButton(Rect position, SerializedProperty property, Type type)
{ {
if (GUI.Button(position, "+")) return ignoredFullClassNames.Contains(type.FullName);
{ }
if (type.IsAbstract) }
{
GenericMenu typeChooser = new GenericMenu();
foreach (var elem in type.Assembly.GetTypes().Where(t => type.IsAssignableFrom(t)))
{
if (elem.IsAbstract) continue;
typeChooser.AddItem(new GUIContent(elem.Name), false, (elem) => {
property.objectReferenceValue = EditorUtils.CreateAssetWithSavePrompt(elem as Type, EditorUtils.GetSelectedAssetPath(property));
property.serializedObject.ApplyModifiedProperties();
}, elem);
}
typeChooser.ShowAsContext();
}
else
{
property.objectReferenceValue = EditorUtils.CreateAssetWithSavePrompt(type, EditorUtils.GetSelectedAssetPath(property));
}
}
}
private bool HasIgnoredClassName(Type type)
{
return ignoreClassFullNames.Contains(type.FullName);
}
private bool HasVisableSubProperties(SerializedProperty property)
{
ScriptableObject scriptableObject = (ScriptableObject)property.objectReferenceValue;
if (scriptableObject != null)
{
SerializedObject serializedObject = new SerializedObject(scriptableObject);
SerializedProperty prop = serializedObject.GetIterator();
while (prop.NextVisible(true))
{
if (prop.name == "m_Script") continue;
return true; //if theres any visible property other than m_script
}
}
return false;
}
}
} }

View File

@ -3,68 +3,149 @@ using System.Reflection;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
using Random = UnityEngine.Random;
namespace ScriptableData.Editor namespace ScriptableData.Editor
{ {
public static class EditorUtils public static class EditorUtils
{ {
public static string GetSelectedAssetPath(SerializedProperty property) public static string GetSelectedAssetPath(SerializedProperty property)
{ {
string selectedAssetPath = "Assets"; string selectedAssetPath = "Assets";
if (property.serializedObject.targetObject is MonoBehaviour behaviour) if (property.serializedObject.targetObject is MonoBehaviour behaviour)
{
MonoScript ms = MonoScript.FromMonoBehaviour(behaviour);
selectedAssetPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(ms));
}
return selectedAssetPath;
}
public static ScriptableObject CreateAssetWithSavePrompt(Type type, string path)
{
path = EditorUtility.SaveFilePanelInProject("Save ScriptableObject", type.Name + ".asset", "asset", "Enter a file name for the ScriptableObject.", path);
if (string.IsNullOrWhiteSpace(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;
}
public static bool HasAttribute<T>(this FieldInfo fieldInfo)
{
Attribute[] attributes = Attribute.GetCustomAttributes(fieldInfo);
foreach (Attribute a in attributes)
{ {
if (a is T) MonoScript ms = MonoScript.FromMonoBehaviour(behaviour);
{ selectedAssetPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(ms));
return true;
}
} }
return false; return selectedAssetPath;
} }
public static Type GetFieldType(FieldInfo fieldInfo) public static ScriptableObject CreateAssetWithSavePrompt(Type type, string path)
{ {
Type type = fieldInfo.FieldType; path = EditorUtility.SaveFilePanelInProject("Save ScriptableObject", type.Name + ".asset", "asset", "Enter a file name for the ScriptableObject.", path);
if (string.IsNullOrWhiteSpace(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;
}
if (type.IsArray) public static bool HasAttribute<T>(this FieldInfo fieldInfo)
{ {
type = type.GetElementType(); Attribute[] attributes = Attribute.GetCustomAttributes(fieldInfo);
}
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
type = type.GetGenericArguments()[0];
}
return type; foreach (Attribute a in attributes)
} {
} if (a is T)
{
return true;
}
}
return false;
}
public static Type GetFieldType(FieldInfo fieldInfo)
{
Type type = fieldInfo.FieldType;
if (type.IsArray)
{
type = type.GetElementType();
}
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
type = type.GetGenericArguments()[0];
}
return type;
}
public static void Draw(this SerializedObject serializedObject, Rect position)
{
// Get position with indentation.
Rect indentedPosition = EditorGUI.IndentedRect(position);
EditorGUI.indentLevel++;
// Draw a background that shows us clearly which fields are part of the ScriptableObject.
GUI.Box(new Rect(indentedPosition.x, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing - 1, position.width, position.height - EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing), "");
// Draw color coded side to indicate the scriptable object instance.
if (serializedObject.targetObject != null)
{
Random.InitState(serializedObject.targetObject.GetInstanceID());
EditorGUI.DrawRect(new Rect(indentedPosition.x - EditorGUIUtility.standardVerticalSpacing, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing - 1, EditorGUIUtility.standardVerticalSpacing, position.height - EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing), Random.ColorHSV(0f, 1f, 1f, 1f, 0.75f, 0.75f));
}
Rect pRect = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing, position.width, 0);
// Iterate over all the values and draw them.
SerializedProperty p = serializedObject.GetIterator();
EditorGUI.BeginChangeCheck();
if (p.NextVisible(true))
{
do
{
if (IsIgnoredProperty(p))
{
continue;
}
pRect.height = EditorGUI.GetPropertyHeight(p, new GUIContent(p.displayName), true);
// Draw property.
EditorGUI.PropertyField(pRect, p, true);
pRect.y += pRect.height + EditorGUIUtility.standardVerticalSpacing;
}
while (p.NextVisible(false));
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
EditorGUI.indentLevel--;
}
public static bool HasVisableSubProperties(this SerializedObject serializedObject)
{
SerializedProperty p = serializedObject.GetIterator();
if (p.NextVisible(true))
{
do
{
if (IsIgnoredProperty(p))
{
continue;
}
return true;
}
while (p.NextVisible(false));
}
return false;
}
public static bool IsIgnoredProperty(SerializedProperty property)
{
// Skip drawing the class file.
if (property.name == "m_Script")
{
return true;
}
return false;
}
}
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "com.vertexcolor.scriptabledata", "name": "com.vertexcolor.scriptabledata",
"displayName": "ScriptableData", "displayName": "ScriptableData",
"version": "1.1.0", "version": "1.1.1",
"unity": "2019.1", "unity": "2019.1",
"description": "ScriptableData code base for ScriptableObject workflow.", "description": "ScriptableData code base for ScriptableObject workflow.",
"category": "Tool", "category": "Tool",