1028 lines
40 KiB
C#
1028 lines
40 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace RotaryHeart.Lib.SerializableDictionary
|
|
{
|
|
[CustomPropertyDrawer(typeof(DrawableDictionary), true)]
|
|
public class DictionaryPropertyDrawer : PropertyDrawer
|
|
{
|
|
#region Fields
|
|
SerializedProperty reqReferences;
|
|
SerializedProperty isExpanded;
|
|
SerializedProperty KeysValues;
|
|
SerializedProperty KeysProp;
|
|
SerializedProperty ValuesProp;
|
|
|
|
readonly GUIContent idContent = new GUIContent("Id");
|
|
readonly GUIContent valueContent = new GUIContent("Value");
|
|
readonly GUIStyle tooTipStyle = new GUIStyle("Tooltip");
|
|
|
|
ReorderableList list;
|
|
|
|
string title;
|
|
|
|
System.Type[] typesNative =
|
|
{
|
|
typeof(bool),
|
|
typeof(byte),
|
|
typeof(float),
|
|
typeof(int),
|
|
typeof(string),
|
|
typeof(Vector2),
|
|
typeof(Vector3),
|
|
typeof(Vector4),
|
|
typeof(Quaternion),
|
|
typeof(Matrix4x4),
|
|
typeof(Color),
|
|
typeof(Rect),
|
|
typeof(LayerMask)
|
|
};
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Used to get the required references, returns the reference to the ReorderableList
|
|
/// </summary>
|
|
/// <param name="property">This property</param>
|
|
private SerializedProperty GetReferences(SerializedProperty property)
|
|
{
|
|
SerializedProperty listProp = property.FindPropertyRelative("reorderableList");
|
|
reqReferences = property.FindPropertyRelative("reqReferences");
|
|
isExpanded = property.FindPropertyRelative("isExpanded");
|
|
|
|
list = GetTargetObjectOfProperty(listProp) as ReorderableList;
|
|
|
|
KeysValues = property.FindPropertyRelative("_keyValues");
|
|
KeysProp = property.FindPropertyRelative("_keys");
|
|
ValuesProp = property.FindPropertyRelative("_values");
|
|
|
|
return listProp;
|
|
}
|
|
|
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
|
{
|
|
GetReferences(property);
|
|
|
|
if (list == null)
|
|
return 0;
|
|
|
|
//Default header height
|
|
float height = EditorGUIUtility.singleLineHeight;
|
|
//Default space between entires
|
|
float verticalSpace = list.verticalSpacing * 2;
|
|
|
|
//Add another line if it has the req reference drawn
|
|
if (KeysProp.arrayElementType.Contains("$"))
|
|
height += EditorGUIUtility.singleLineHeight;
|
|
|
|
if (isExpanded.boolValue)
|
|
{
|
|
//Default height for the bottom section
|
|
height += EditorGUIUtility.singleLineHeight;
|
|
|
|
int keysSize = KeysProp.arraySize;
|
|
|
|
if (Constants.ShowPages)
|
|
{
|
|
//Extra space for top section pages
|
|
height += EditorGUIUtility.singleLineHeight;
|
|
keysSize = Mathf.Min(keysSize, Constants.PageCount);
|
|
}
|
|
|
|
if (keysSize > 0)
|
|
{
|
|
//Iterate through all the keys
|
|
for (int keyIndex = 0; keyIndex < keysSize; keyIndex++)
|
|
{
|
|
//Should only happen with pages
|
|
if (keyIndex >= KeysProp.arraySize)
|
|
break;
|
|
|
|
var keyProp = KeysProp.GetArrayElementAtIndex(keyIndex);
|
|
|
|
//Use the same element height calculations, adding 4 because of the
|
|
height += List_getElementHeightCallback(keyProp, keyIndex) + verticalSpace;
|
|
}
|
|
}
|
|
else
|
|
//Default height for empty list
|
|
height += EditorGUIUtility.singleLineHeight + verticalSpace * 3;
|
|
}
|
|
|
|
return height + verticalSpace;
|
|
}
|
|
|
|
#region Helpers
|
|
|
|
private object GetTargetObjectOfProperty(SerializedProperty prop)
|
|
{
|
|
var path = prop.propertyPath.Replace(".Array.data[", "[");
|
|
object obj = prop.serializedObject.targetObject;
|
|
var elements = path.Split('.');
|
|
foreach (var element in elements)
|
|
{
|
|
if (element.Contains("["))
|
|
{
|
|
var elementName = element.Substring(0, element.IndexOf("["));
|
|
var index = System.Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
|
|
obj = GetValue_Imp(obj, elementName, index);
|
|
}
|
|
else
|
|
{
|
|
obj = GetValue_Imp(obj, element);
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
private void SetTargetObjectOfProperty(SerializedProperty prop, object value, bool custom = false)
|
|
{
|
|
var path = prop.propertyPath.Replace(".Array.data[", "[");
|
|
object obj = prop.serializedObject.targetObject;
|
|
var elements = path.Split('.');
|
|
foreach (var element in elements.Take(elements.Length - 1))
|
|
{
|
|
if (element.Contains("["))
|
|
{
|
|
var elementName = element.Substring(0, element.IndexOf("["));
|
|
var index = System.Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
|
|
obj = GetValue_Imp(obj, elementName, index);
|
|
}
|
|
else
|
|
{
|
|
obj = GetValue_Imp(obj, element);
|
|
}
|
|
}
|
|
|
|
if (Object.ReferenceEquals(obj, null)) return;
|
|
|
|
try
|
|
{
|
|
var element = elements.Last();
|
|
var tp = obj.GetType();
|
|
|
|
if (custom)
|
|
tp = tp.BaseType;
|
|
|
|
if (element.Contains("["))
|
|
{
|
|
var elementName = element.Substring(0, element.IndexOf("["));
|
|
var index = System.Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
|
|
var field = tp.GetField(elementName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
var arr = field.GetValue(obj) as System.Collections.IList;
|
|
arr[index] = value;
|
|
}
|
|
else
|
|
{
|
|
var field = tp.GetField(element, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
if (field != null)
|
|
{
|
|
field.SetValue(obj, value);
|
|
}
|
|
}
|
|
|
|
}
|
|
catch
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
private object GetValue_Imp(object source, string name)
|
|
{
|
|
if (source == null)
|
|
return null;
|
|
var type = source.GetType();
|
|
|
|
while (type != null)
|
|
{
|
|
var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|
if (f != null)
|
|
return f.GetValue(source);
|
|
|
|
var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
|
if (p != null)
|
|
return p.GetValue(source, null);
|
|
|
|
type = type.BaseType;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private object GetValue_Imp(object source, string name, int index)
|
|
{
|
|
var enumerable = GetValue_Imp(source, name) as System.Collections.IEnumerable;
|
|
if (enumerable == null) return null;
|
|
var enm = enumerable.GetEnumerator();
|
|
//while (index-- >= 0)
|
|
// enm.MoveNext();
|
|
//return enm.Current;
|
|
|
|
for (int i = 0; i <= index; i++)
|
|
{
|
|
if (!enm.MoveNext()) return null;
|
|
}
|
|
return enm.Current;
|
|
}
|
|
|
|
private bool IsUnitySerialized(FieldInfo fieldInfo)
|
|
{
|
|
object[] customAttributes = fieldInfo.GetCustomAttributes(true);
|
|
if (customAttributes.Any(x => x is System.NonSerializedAttribute))
|
|
{
|
|
return false;
|
|
}
|
|
if (fieldInfo.IsPrivate && !customAttributes.Any(x => x is SerializeField))
|
|
{
|
|
return false;
|
|
}
|
|
return IsUnitySerialized(fieldInfo.FieldType);
|
|
}
|
|
|
|
private bool IsUnitySerialized(System.Type type)
|
|
{
|
|
if (type.IsGenericType)
|
|
{
|
|
if (type.GetGenericTypeDefinition() == typeof(List<>))
|
|
{
|
|
return IsUnitySerialized(type.GetGenericArguments()[0]);
|
|
}
|
|
return false;
|
|
}
|
|
if (type.IsEnum)
|
|
{
|
|
return true;
|
|
}
|
|
if (type.IsValueType)
|
|
{
|
|
return true;
|
|
}
|
|
if (type.IsAssignableFrom(typeof(Object)))
|
|
{
|
|
return true;
|
|
}
|
|
if (typesNative.Contains(type) || (type.IsArray && typesNative.Contains(type.GetElementType())))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a Vector4 to Quaternion
|
|
/// </summary>
|
|
/// <param name="v4">Vector to convert</param>
|
|
private Quaternion ConvertToQuaternion(Vector4 v4)
|
|
{
|
|
return new Quaternion(v4.x, v4.y, v4.z, v4.w);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a Quaternion to Vector4
|
|
/// </summary>
|
|
/// <param name="q">Quaternion to convert</param>
|
|
private Vector4 QuaternionToVector4(Quaternion q)
|
|
{
|
|
return new Vector4(q.x, q.y, q.z, q.w);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
|
{
|
|
SerializedProperty listProp = GetReferences(property);
|
|
|
|
reqReferences.isExpanded = false;
|
|
property.isExpanded = false;
|
|
|
|
title = label.text;
|
|
|
|
Rect nextRect;
|
|
|
|
string keyType = KeysProp.arrayElementType;
|
|
int offset = 0;
|
|
|
|
//Only draw the required references field for keys that requires a default value
|
|
if (keyType.Contains("$"))
|
|
{
|
|
nextRect = GetNextRect(ref position);
|
|
EditorGUI.PropertyField(nextRect, reqReferences);
|
|
offset = 20;
|
|
}
|
|
|
|
nextRect = GetNextRect(ref position);
|
|
|
|
//Fix values size based on the keys size
|
|
if (ValuesProp.arraySize != KeysProp.arraySize)
|
|
ValuesProp.arraySize = KeysProp.arraySize;
|
|
if (KeysValues.arraySize != KeysProp.arraySize)
|
|
KeysValues.arraySize = KeysProp.arraySize;
|
|
|
|
if (list != null)
|
|
{
|
|
if (!list.HasList)
|
|
{
|
|
list = new ReorderableList(KeysProp, true, true, true);
|
|
|
|
//Required callbacks
|
|
list.onRemoveCallback += List_onRemoveCallback;
|
|
list.onAddCallback += List_onAddCallback;
|
|
list.drawElementCallback += List_drawElementCallback;
|
|
list.drawHeaderCallback += List_drawHeaderCallback;
|
|
list.getElementHeightCallback += List_getElementHeightCallback;
|
|
list.onElementsReorder += List_onElementsReorder;
|
|
list.headerExpand += List_headerExpand;
|
|
|
|
SetTargetObjectOfProperty(listProp, list);
|
|
}
|
|
|
|
list.List = KeysProp;
|
|
list.isExpanded = isExpanded.boolValue;
|
|
|
|
list.DoList(new Rect(nextRect.x, nextRect.y, nextRect.width, GetPropertyHeight(property, label) - offset), label, Constants.ShowPages, Constants.PageCount);
|
|
}
|
|
}
|
|
|
|
private void List_headerExpand(bool expand)
|
|
{
|
|
isExpanded.boolValue = expand;
|
|
|
|
for (int i = 0; i < KeysValues.arraySize; i++)
|
|
{
|
|
KeysProp.GetArrayElementAtIndex(i).isExpanded = expand;
|
|
ValuesProp.GetArrayElementAtIndex(i).isExpanded = expand;
|
|
}
|
|
}
|
|
|
|
private void List_onElementsReorder(int startIndex, int newIndex)
|
|
{
|
|
KeysValues.MoveArrayElement(startIndex, newIndex);
|
|
//KeysProp.MoveArrayElement(startIndex, newIndex);
|
|
ValuesProp.MoveArrayElement(startIndex, newIndex);
|
|
}
|
|
|
|
private void List_drawHeaderCallback(Rect rect, GUIContent label)
|
|
{
|
|
rect.x += 6;
|
|
|
|
isExpanded.boolValue = EditorGUI.Foldout(rect, isExpanded.boolValue, "", true);
|
|
isExpanded.serializedObject.ApplyModifiedProperties();
|
|
EditorGUI.LabelField(rect, title + (Constants.ShowSize ? " [" + KeysValues.arraySize + "]" : ""));
|
|
}
|
|
|
|
private float List_getElementHeightCallback(SerializedProperty element, int index)
|
|
{
|
|
float height;
|
|
|
|
bool containsAttribute = fieldInfo.GetCustomAttributes(typeof(DrawKeyAsPropertyAttribute), true).Any();
|
|
|
|
height = EditorGUI.GetPropertyHeight(element, GUIContent.none, true) + list.verticalSpacing * 4;
|
|
|
|
//Value height
|
|
if (element.isExpanded)
|
|
{
|
|
var valueProp = ValuesProp.GetArrayElementAtIndex(index);
|
|
|
|
height += EditorGUI.GetPropertyHeight(valueProp, GUIContent.none, true) + list.verticalSpacing - (containsAttribute ? EditorGUIUtility.singleLineHeight : 0);
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
private void List_drawElementCallback(Rect rect, SerializedProperty element, GUIContent label, int index, bool selected, bool focused)
|
|
{
|
|
var keyValueProp = KeysValues.GetArrayElementAtIndex(index);
|
|
var keyProp = KeysProp.GetArrayElementAtIndex(index);
|
|
var valueProp = ValuesProp.GetArrayElementAtIndex(index);
|
|
|
|
SerializedProperty keyToUse = keyProp.propertyType == SerializedPropertyType.Generic ? keyProp : keyValueProp;
|
|
|
|
if (keyToUse.propertyType == SerializedPropertyType.Generic)
|
|
{
|
|
rect.x -= 10;
|
|
rect.width += 10;
|
|
}
|
|
|
|
//Only draw the color if this entry is not selected
|
|
if (!selected)
|
|
{
|
|
if (Event.current.type == EventType.Repaint)
|
|
tooTipStyle.Draw(rect, false, false, false, false);
|
|
}
|
|
|
|
rect.height = EditorGUIUtility.singleLineHeight;
|
|
|
|
//Check if it contains the new attribute
|
|
bool containsAttribute = fieldInfo.GetCustomAttributes(typeof(DrawKeyAsPropertyAttribute), true).Any();
|
|
|
|
Rect keyRect = new Rect(rect.x + 50, rect.y + 4, rect.width - 52, rect.height);
|
|
Rect valueRect = new Rect(keyRect);
|
|
|
|
#region Key Field
|
|
|
|
string propName = "";
|
|
if (containsAttribute)
|
|
{
|
|
var field = fieldInfo.FieldType.BaseType.GetField("_keys", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|
var fieldType = field.FieldType;
|
|
var elementType = fieldType.GetGenericArguments()[0];
|
|
foreach (var fi in elementType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
if (IsUnitySerialized(fi))
|
|
{
|
|
propName = fi.Name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Draw only if its not a generic type or can be draw as property
|
|
if ((containsAttribute && !string.IsNullOrEmpty(propName)) || keyToUse.propertyType != SerializedPropertyType.Generic)
|
|
{
|
|
if (containsAttribute)
|
|
keyRect.height = EditorGUI.GetPropertyHeight(keyProp, GUIContent.none, true) - (keyProp.isExpanded ? EditorGUIUtility.singleLineHeight : 0);
|
|
|
|
keyProp.isExpanded = EditorGUI.Foldout(new Rect(rect.x + 15, keyRect.y, 20, rect.height), keyProp.isExpanded, idContent, true);
|
|
}
|
|
|
|
GUI.SetNextControlName("CheckGenericFocus" + index);
|
|
|
|
switch (keyToUse.propertyType)
|
|
{
|
|
case SerializedPropertyType.Quaternion:
|
|
EditorGUI.BeginChangeCheck();
|
|
var newV4 = EditorGUI.Vector4Field(keyRect, GUIContent.none, QuaternionToVector4(keyToUse.quaternionValue));
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
keyToUse.quaternionValue = ConvertToQuaternion(newV4);
|
|
}
|
|
break;
|
|
|
|
case SerializedPropertyType.Enum:
|
|
string[] names = keyToUse.enumDisplayNames;
|
|
if (names.Length <= keyToUse.enumValueIndex || keyToUse.enumValueIndex < 0)
|
|
{
|
|
list.Selected = new[] {index};
|
|
List_onRemoveCallback(list);
|
|
return;
|
|
}
|
|
var selectedVal = names[keyToUse.enumValueIndex];
|
|
|
|
//Draw button with dropdown style
|
|
if (GUI.Button(keyRect, selectedVal, EditorStyles.layerMaskField))
|
|
{
|
|
List<string> usedNames = new List<string>();
|
|
GenericMenu menu = new GenericMenu();
|
|
|
|
//Add all the used values
|
|
for (int i = 0; i < KeysValues.arraySize; i++)
|
|
{
|
|
usedNames.Add(names[KeysValues.GetArrayElementAtIndex(i).enumValueIndex]);
|
|
}
|
|
|
|
//Add all the menu items
|
|
for (int i = 0; i < names.Length; i++)
|
|
{
|
|
int nameIndex = i;
|
|
|
|
//If the value is being used, show it disabled
|
|
if (usedNames.Contains(names[nameIndex]) && !names[nameIndex].Equals(selectedVal))
|
|
{
|
|
menu.AddDisabledItem(new GUIContent(names[nameIndex]));
|
|
}
|
|
else
|
|
{
|
|
menu.AddItem(new GUIContent(names[nameIndex]), selectedVal == names[nameIndex], () =>
|
|
{
|
|
keyValueProp.enumValueIndex = nameIndex;
|
|
keyProp.enumValueIndex = nameIndex;
|
|
keyToUse.serializedObject.ApplyModifiedProperties();
|
|
});
|
|
}
|
|
}
|
|
|
|
//Show menu under mouse position
|
|
menu.ShowAsContext();
|
|
|
|
Event.current.Use();
|
|
}
|
|
break;
|
|
|
|
case SerializedPropertyType.Generic:
|
|
//Only draw as property if values are correct
|
|
if (containsAttribute && !string.IsNullOrEmpty(propName))
|
|
{
|
|
EditorGUI.PropertyField(keyRect, keyToUse.FindPropertyRelative(propName), GUIContent.none, false);
|
|
}
|
|
else
|
|
{
|
|
keyRect.height = EditorGUI.GetPropertyHeight(keyToUse, idContent);
|
|
EditorGUI.PropertyField(new Rect(rect.x + 15, keyRect.y, keyRect.width + 35, keyRect.height), keyToUse, idContent, true);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
EditorGUI.PropertyField(keyRect, keyToUse, GUIContent.none, false);
|
|
break;
|
|
}
|
|
|
|
//Not used for generic type
|
|
if (keyToUse.propertyType != SerializedPropertyType.Generic)
|
|
{
|
|
//Old key value
|
|
var oldId = GetKeyValue(keyProp);
|
|
//New key value
|
|
var newId = GetKeyValue(keyValueProp);
|
|
|
|
//Notify if the key is empty or null
|
|
if ((keyToUse.propertyType == SerializedPropertyType.String && string.IsNullOrEmpty(newId.ToString())) || newId == null)
|
|
{
|
|
GUIContent content = EditorGUIUtility.IconContent("console.warnicon.sml");
|
|
content.tooltip = "ID cannot be left empty";
|
|
|
|
GUI.Button(new Rect(keyRect.x - 15, keyRect.y, 30, 30), content, GUIStyle.none);
|
|
}
|
|
//Check if the key value has been changed
|
|
else if ((oldId == null && newId != null) || !oldId.Equals(newId))
|
|
{
|
|
//Be sure that the dictionary doesn't contain an element with this key
|
|
if (ContainsId(newId, index))
|
|
{
|
|
//Check if this key is still focused
|
|
if (GUI.GetNameOfFocusedControl().Equals("CheckGenericFocus" + index))
|
|
{
|
|
//Notify the user that this key already exists
|
|
GUIContent content = EditorGUIUtility.IconContent("console.erroricon.sml");
|
|
content.tooltip = "Dictionary already has this id, this id cannot be used";
|
|
|
|
GUI.Button(new Rect(keyRect.x - 15, keyRect.y, 30, 30), content, GUIStyle.none);
|
|
}
|
|
else
|
|
{
|
|
//If it's not, set the correct key back. This is to avoid having multiple errors with ids
|
|
SetValue(keyValueProp, oldId);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Set the value
|
|
SetGenericValue(keyProp, valueProp, newId);
|
|
}
|
|
}
|
|
}
|
|
#endregion Fey Field
|
|
|
|
valueRect.y = keyRect.yMax + 3 - (containsAttribute ? 2 : 0);
|
|
valueRect.x -= 20;
|
|
valueRect.width += 20;
|
|
|
|
#region Value Field
|
|
|
|
//Special case for generic types
|
|
if (valueProp.propertyType == SerializedPropertyType.Generic)
|
|
{
|
|
if (keyToUse.propertyType != SerializedPropertyType.Generic)
|
|
valueRect.y -= 3;
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
//Value field
|
|
if (keyProp.isExpanded)
|
|
{
|
|
EditorGUI.BeginProperty(valueRect, GUIContent.none, valueProp);
|
|
|
|
if (valueProp.propertyType == SerializedPropertyType.Quaternion)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
var newV4 = EditorGUI.Vector4Field(valueRect, GUIContent.none, QuaternionToVector4(valueProp.quaternionValue));
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProp.quaternionValue = ConvertToQuaternion(newV4);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.PropertyField(valueRect, valueProp, valueContent, true);
|
|
}
|
|
EditorGUI.EndProperty();
|
|
}
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
//This is used to apply the modified changes
|
|
ValuesProp.serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Value field
|
|
if (keyProp.isExpanded)
|
|
{
|
|
valueRect.x -= 10;
|
|
valueRect.width += 10;
|
|
|
|
EditorGUI.BeginProperty(valueRect, GUIContent.none, valueProp);
|
|
|
|
EditorGUI.PrefixLabel(valueRect, valueContent);
|
|
if (valueProp.propertyType == SerializedPropertyType.Quaternion)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
var newV4 = EditorGUI.Vector4Field(new Rect(valueRect.x + 45, valueRect.y, valueRect.width - 45, valueRect.height), GUIContent.none, QuaternionToVector4(valueProp.quaternionValue));
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProp.quaternionValue = ConvertToQuaternion(newV4);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.PropertyField(new Rect(valueRect.x + 45, valueRect.y, valueRect.width - 45, valueRect.height), valueProp, GUIContent.none, true);
|
|
}
|
|
EditorGUI.EndProperty();
|
|
}
|
|
}
|
|
|
|
#endregion Value Field
|
|
}
|
|
|
|
private void List_onAddCallback(ReorderableList list)
|
|
{
|
|
KeysValues.arraySize = ValuesProp.arraySize = ++KeysProp.arraySize;
|
|
|
|
SetPropertyDefault(KeysValues.GetArrayElementAtIndex(KeysValues.arraySize - 1), KeysValues);
|
|
SetPropertyDefault(KeysProp.GetArrayElementAtIndex(KeysProp.arraySize - 1), KeysProp);
|
|
|
|
KeysValues.serializedObject.ApplyModifiedProperties();
|
|
ValuesProp.serializedObject.ApplyModifiedProperties();
|
|
KeysProp.serializedObject.ApplyModifiedProperties();
|
|
|
|
//SetPropertyDefault(ValuesProp.GetArrayElementAtIndex(ValuesProp.arraySize - 1), null);
|
|
}
|
|
|
|
private void List_onRemoveCallback(ReorderableList list)
|
|
{
|
|
for (int i = list.Selected.Length - 1; i >= 0; i--)
|
|
{
|
|
int index = list.Selected[i];
|
|
|
|
int last = KeysProp.arraySize - 1;
|
|
|
|
KeysValues.MoveArrayElement(index, last);
|
|
KeysProp.MoveArrayElement(index, last);
|
|
ValuesProp.MoveArrayElement(index, last);
|
|
|
|
KeysValues.arraySize--;
|
|
KeysProp.arraySize--;
|
|
ValuesProp.arraySize--;
|
|
}
|
|
|
|
ValuesProp.serializedObject.ApplyModifiedProperties();
|
|
ValuesProp.serializedObject.Update();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the <paramref name="KeysProp"/> contains the id
|
|
/// </summary>
|
|
/// <param name="obj">Id to check</param>
|
|
/// <param name="index">Property index on the array</param>
|
|
/// <returns>True if an element is already using the id; otherwise, false</returns>
|
|
private bool ContainsId(object obj, int index)
|
|
{
|
|
for (int i = 0; i < KeysProp.arraySize; i++)
|
|
{
|
|
if (index == i)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
object val = GetKeyValue(KeysProp.GetArrayElementAtIndex(i));
|
|
|
|
if (val.Equals(obj))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the current property value
|
|
/// </summary>
|
|
/// <param name="prop">Property to check</param>
|
|
/// <returns>object representation of the property value</returns>
|
|
private object GetKeyValue(SerializedProperty prop)
|
|
{
|
|
object obj = null;
|
|
switch (prop.propertyType)
|
|
{
|
|
case SerializedPropertyType.Integer:
|
|
case SerializedPropertyType.LayerMask:
|
|
obj = prop.intValue;
|
|
break;
|
|
case SerializedPropertyType.Boolean:
|
|
obj = prop.boolValue;
|
|
break;
|
|
case SerializedPropertyType.Float:
|
|
obj = prop.floatValue;
|
|
break;
|
|
case SerializedPropertyType.String:
|
|
obj = prop.stringValue;
|
|
break;
|
|
case SerializedPropertyType.Color:
|
|
obj = prop.colorValue;
|
|
break;
|
|
case SerializedPropertyType.ObjectReference:
|
|
obj = prop.objectReferenceValue;
|
|
break;
|
|
case SerializedPropertyType.Enum:
|
|
obj = prop.enumValueIndex;
|
|
break;
|
|
case SerializedPropertyType.Vector2:
|
|
obj = prop.vector2Value;
|
|
break;
|
|
case SerializedPropertyType.Vector3:
|
|
obj = prop.vector3Value;
|
|
break;
|
|
case SerializedPropertyType.Vector4:
|
|
obj = prop.vector4Value;
|
|
break;
|
|
case SerializedPropertyType.Rect:
|
|
obj = prop.rectValue;
|
|
break;
|
|
case SerializedPropertyType.ArraySize:
|
|
obj = prop.arraySize;
|
|
break;
|
|
case SerializedPropertyType.Character:
|
|
obj = (char)prop.intValue;
|
|
break;
|
|
case SerializedPropertyType.AnimationCurve:
|
|
obj = prop.animationCurveValue;
|
|
break;
|
|
case SerializedPropertyType.Bounds:
|
|
obj = prop.boundsValue;
|
|
break;
|
|
case SerializedPropertyType.Gradient:
|
|
obj = GetGradientValue(prop);
|
|
break;
|
|
case SerializedPropertyType.Quaternion:
|
|
obj = prop.quaternionValue;
|
|
break;
|
|
case SerializedPropertyType.Generic:
|
|
obj = GetTargetObjectOfProperty(prop);
|
|
break;
|
|
default:
|
|
Debug.LogError("Key Type not implemented: " + prop.propertyType);
|
|
break;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the property value
|
|
/// </summary>
|
|
/// <param name="prop">Property to modify</param>
|
|
/// <param name="obj">Value</param>
|
|
private void SetValue(SerializedProperty prop, object obj)
|
|
{
|
|
switch (prop.propertyType)
|
|
{
|
|
case SerializedPropertyType.Integer:
|
|
case SerializedPropertyType.LayerMask:
|
|
prop.intValue = (int)obj;
|
|
break;
|
|
case SerializedPropertyType.Boolean:
|
|
prop.boolValue = (bool)obj;
|
|
break;
|
|
case SerializedPropertyType.Float:
|
|
prop.floatValue = (float)obj;
|
|
break;
|
|
case SerializedPropertyType.String:
|
|
prop.stringValue = (string)obj;
|
|
break;
|
|
case SerializedPropertyType.Color:
|
|
prop.colorValue = (Color)obj;
|
|
break;
|
|
case SerializedPropertyType.ObjectReference:
|
|
prop.objectReferenceValue = (Object)obj;
|
|
break;
|
|
case SerializedPropertyType.Enum:
|
|
prop.enumValueIndex = (int)obj;
|
|
break;
|
|
case SerializedPropertyType.Vector2:
|
|
prop.vector2Value = (Vector2)obj;
|
|
break;
|
|
case SerializedPropertyType.Vector3:
|
|
prop.vector3Value = (Vector3)obj;
|
|
break;
|
|
case SerializedPropertyType.Vector4:
|
|
prop.vector4Value = (Vector4)obj;
|
|
break;
|
|
case SerializedPropertyType.Rect:
|
|
prop.rectValue = (Rect)obj;
|
|
break;
|
|
case SerializedPropertyType.ArraySize:
|
|
prop.arraySize = (int)obj;
|
|
break;
|
|
case SerializedPropertyType.Character:
|
|
prop.intValue = (char)obj;
|
|
break;
|
|
case SerializedPropertyType.AnimationCurve:
|
|
prop.animationCurveValue = (AnimationCurve)obj;
|
|
break;
|
|
case SerializedPropertyType.Bounds:
|
|
prop.boundsValue = (Bounds)obj;
|
|
break;
|
|
case SerializedPropertyType.Gradient:
|
|
SetGradientValue(prop, (Gradient)obj);
|
|
break;
|
|
case SerializedPropertyType.Quaternion:
|
|
prop.quaternionValue = (Quaternion)obj;
|
|
break;
|
|
case SerializedPropertyType.Generic:
|
|
SetTargetObjectOfProperty(prop, null);
|
|
break;
|
|
default:
|
|
Debug.Log("Type not implemented: " + prop.propertyType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get the Gradient value of the <paramref name="prop"/> using reflection, if it fails returns null
|
|
/// </summary>
|
|
/// <param name="prop">SerializedProperty to get the value from</param>
|
|
/// <returns>Gradient value, or null if it fails</returns>
|
|
private Gradient GetGradientValue(SerializedProperty prop)
|
|
{
|
|
PropertyInfo propertyInfo = typeof(SerializedProperty).GetProperty("gradientValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
if (propertyInfo == null)
|
|
return null;
|
|
|
|
return propertyInfo.GetValue(prop, null) as Gradient;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to set the Gradient value of the <paramref name="prop"/> using reflection, if it fails nothing is saved
|
|
/// </summary>
|
|
/// <param name="prop">SerializedProperty to get the value from</param>
|
|
/// <param name="gradient">Gradient value to save</param>
|
|
private void SetGradientValue(SerializedProperty prop, Gradient gradient)
|
|
{
|
|
PropertyInfo propertyInfo = typeof(SerializedProperty).GetProperty("gradientValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
if (propertyInfo == null)
|
|
return;
|
|
|
|
propertyInfo.SetValue(prop, gradient, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Special check for a dictionary with a generic value type. This tries to set the key to its id field
|
|
/// </summary>
|
|
/// <param name="keyProp">Key proeprty</param>
|
|
/// <param name="valueProp">Value property</param>
|
|
/// <param name="obj">Key value to set</param>
|
|
private void SetGenericValue(SerializedProperty keyProp, SerializedProperty valueProp, object obj)
|
|
{
|
|
SetValue(keyProp, obj);
|
|
|
|
IDAttribute attribute = System.Attribute.GetCustomAttribute(fieldInfo, typeof(IDAttribute)) as IDAttribute;
|
|
|
|
if (attribute == null)
|
|
{
|
|
//This generic dictionary doesn't contain an id attribute
|
|
return;
|
|
}
|
|
|
|
SerializedProperty id = valueProp.FindPropertyRelative(attribute.Id);
|
|
|
|
if (id == null)
|
|
{
|
|
Debug.LogError("Couldn't find any id field with name '" + attribute.Id + "' on field: " + fieldInfo.Name);
|
|
return;
|
|
}
|
|
|
|
SetValue(id, obj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the next rect forcing the height to be of a single line
|
|
/// </summary>
|
|
/// <param name="position">Position reference</param>
|
|
/// <returns>Next rect</returns>
|
|
private Rect GetNextRect(ref Rect position)
|
|
{
|
|
var h = EditorGUIUtility.singleLineHeight;
|
|
var r = new Rect(position.x, position.y, position.width, h);
|
|
position = new Rect(position.x, position.y + h, position.width, h);
|
|
return r;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the default value based on the property
|
|
/// </summary>
|
|
/// <param name="prop">Property</param>
|
|
private void SetPropertyDefault(SerializedProperty prop, SerializedProperty parentProperty)
|
|
{
|
|
if (prop == null)
|
|
throw new System.ArgumentNullException("prop");
|
|
|
|
switch (prop.propertyType)
|
|
{
|
|
case SerializedPropertyType.Integer:
|
|
prop.intValue = int.MaxValue;
|
|
break;
|
|
case SerializedPropertyType.Boolean:
|
|
prop.boolValue = false;
|
|
break;
|
|
case SerializedPropertyType.Float:
|
|
prop.floatValue = Mathf.Infinity;
|
|
break;
|
|
case SerializedPropertyType.String:
|
|
prop.stringValue = string.Empty;
|
|
break;
|
|
case SerializedPropertyType.Color:
|
|
prop.colorValue = Color.black;
|
|
break;
|
|
case SerializedPropertyType.ObjectReference:
|
|
prop.objectReferenceValue = null;
|
|
break;
|
|
case SerializedPropertyType.LayerMask:
|
|
prop.intValue = -1;
|
|
break;
|
|
case SerializedPropertyType.Enum:
|
|
int index = 0;
|
|
|
|
if (parentProperty != null)
|
|
{
|
|
List<int> numbersUsed = new List<int>();
|
|
|
|
for (int i = 0; i < parentProperty.arraySize; i++)
|
|
numbersUsed.Add(parentProperty.GetArrayElementAtIndex(i).enumValueIndex);
|
|
|
|
while (true)
|
|
{
|
|
if (!numbersUsed.Contains(index))
|
|
{
|
|
break;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
if (index >= prop.enumNames.Length)
|
|
index = 0;
|
|
}
|
|
|
|
prop.enumValueIndex = index;
|
|
break;
|
|
case SerializedPropertyType.Vector2:
|
|
prop.vector2Value = Vector2.zero;
|
|
break;
|
|
case SerializedPropertyType.Vector3:
|
|
prop.vector3Value = Vector3.zero;
|
|
break;
|
|
case SerializedPropertyType.Vector4:
|
|
prop.vector4Value = Vector4.zero;
|
|
break;
|
|
case SerializedPropertyType.Rect:
|
|
prop.rectValue = Rect.zero;
|
|
break;
|
|
case SerializedPropertyType.ArraySize:
|
|
prop.arraySize = 0;
|
|
break;
|
|
case SerializedPropertyType.Character:
|
|
prop.intValue = 0;
|
|
break;
|
|
case SerializedPropertyType.AnimationCurve:
|
|
prop.animationCurveValue = null;
|
|
break;
|
|
case SerializedPropertyType.Bounds:
|
|
prop.boundsValue = default(Bounds);
|
|
break;
|
|
case SerializedPropertyType.Gradient:
|
|
SetGradientValue(prop, new Gradient());
|
|
break;
|
|
case SerializedPropertyType.Generic:
|
|
//Used to initialized all the values on the generic type
|
|
var t = prop.GetEnumerator();
|
|
while (t.MoveNext())
|
|
{
|
|
var val = t.Current;
|
|
SetPropertyDefault((val as SerializedProperty), null);
|
|
}
|
|
break;
|
|
case SerializedPropertyType.Quaternion:
|
|
prop.quaternionValue = Quaternion.identity;
|
|
break;
|
|
default:
|
|
Debug.Log("Type not implemented: " + prop.propertyType);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|