summaryrefslogtreecommitdiff
path: root/Assets/SerializableDictionary/Editor/SerializableDictionaryPropertyDrawer.cs
diff options
context:
space:
mode:
authorchai <chaifix@163.com>2021-07-27 09:40:26 +0800
committerchai <chaifix@163.com>2021-07-27 09:40:26 +0800
commit8bbc03542340b4ea7ca1e2beec2f11ff335851e8 (patch)
tree15a4bdebe7b8d9448c476e0f67b94a5a50bc2ec5 /Assets/SerializableDictionary/Editor/SerializableDictionaryPropertyDrawer.cs
parent4ceee84cd45e4e3ec40ebd888e41bd47a938c2d5 (diff)
*mic
Diffstat (limited to 'Assets/SerializableDictionary/Editor/SerializableDictionaryPropertyDrawer.cs')
-rw-r--r--Assets/SerializableDictionary/Editor/SerializableDictionaryPropertyDrawer.cs565
1 files changed, 565 insertions, 0 deletions
diff --git a/Assets/SerializableDictionary/Editor/SerializableDictionaryPropertyDrawer.cs b/Assets/SerializableDictionary/Editor/SerializableDictionaryPropertyDrawer.cs
new file mode 100644
index 00000000..f631a96a
--- /dev/null
+++ b/Assets/SerializableDictionary/Editor/SerializableDictionaryPropertyDrawer.cs
@@ -0,0 +1,565 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+using System.Reflection;
+using System;
+
+public class SerializableDictionaryPropertyDrawer : PropertyDrawer
+{
+ const string KeysFieldName = "m_keys";
+ const string ValuesFieldName = "m_values";
+ protected const float IndentWidth = 15f;
+
+ static GUIContent s_iconPlus = IconContent ("Toolbar Plus", "Add entry");
+ static GUIContent s_iconMinus = IconContent ("Toolbar Minus", "Remove entry");
+ static GUIContent s_warningIconConflict = IconContent ("console.warnicon.sml", "Conflicting key, this entry will be lost");
+ static GUIContent s_warningIconOther = IconContent ("console.infoicon.sml", "Conflicting key");
+ static GUIContent s_warningIconNull = IconContent ("console.warnicon.sml", "Null key, this entry will be lost");
+ static GUIStyle s_buttonStyle = GUIStyle.none;
+ static GUIContent s_tempContent = new GUIContent();
+
+
+ class ConflictState
+ {
+ public object conflictKey = null;
+ public object conflictValue = null;
+ public int conflictIndex = -1 ;
+ public int conflictOtherIndex = -1 ;
+ public bool conflictKeyPropertyExpanded = false;
+ public bool conflictValuePropertyExpanded = false;
+ public float conflictLineHeight = 0f;
+ }
+
+ struct PropertyIdentity
+ {
+ public PropertyIdentity(SerializedProperty property)
+ {
+ this.instance = property.serializedObject.targetObject;
+ this.propertyPath = property.propertyPath;
+ }
+
+ public UnityEngine.Object instance;
+ public string propertyPath;
+ }
+
+ static Dictionary<PropertyIdentity, ConflictState> s_conflictStateDict = new Dictionary<PropertyIdentity, ConflictState>();
+
+ enum Action
+ {
+ None,
+ Add,
+ Remove
+ }
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ label = EditorGUI.BeginProperty(position, label, property);
+
+ Action buttonAction = Action.None;
+ int buttonActionIndex = 0;
+
+ var keyArrayProperty = property.FindPropertyRelative(KeysFieldName);
+ var valueArrayProperty = property.FindPropertyRelative(ValuesFieldName);
+
+ ConflictState conflictState = GetConflictState(property);
+
+ if(conflictState.conflictIndex != -1)
+ {
+ keyArrayProperty.InsertArrayElementAtIndex(conflictState.conflictIndex);
+ var keyProperty = keyArrayProperty.GetArrayElementAtIndex(conflictState.conflictIndex);
+ SetPropertyValue(keyProperty, conflictState.conflictKey);
+ keyProperty.isExpanded = conflictState.conflictKeyPropertyExpanded;
+
+ valueArrayProperty.InsertArrayElementAtIndex(conflictState.conflictIndex);
+ var valueProperty = valueArrayProperty.GetArrayElementAtIndex(conflictState.conflictIndex);
+ SetPropertyValue(valueProperty, conflictState.conflictValue);
+ valueProperty.isExpanded = conflictState.conflictValuePropertyExpanded;
+ }
+
+ var buttonWidth = s_buttonStyle.CalcSize(s_iconPlus).x;
+
+ var labelPosition = position;
+ labelPosition.height = EditorGUIUtility.singleLineHeight;
+ if (property.isExpanded)
+ labelPosition.xMax -= s_buttonStyle.CalcSize(s_iconPlus).x;
+
+ EditorGUI.PropertyField(labelPosition, property, label, false);
+ // property.isExpanded = EditorGUI.Foldout(labelPosition, property.isExpanded, label);
+ if (property.isExpanded)
+ {
+ var buttonPosition = position;
+ buttonPosition.xMin = buttonPosition.xMax - buttonWidth;
+ buttonPosition.height = EditorGUIUtility.singleLineHeight;
+ EditorGUI.BeginDisabledGroup(conflictState.conflictIndex != -1);
+ if(GUI.Button(buttonPosition, s_iconPlus, s_buttonStyle))
+ {
+ buttonAction = Action.Add;
+ buttonActionIndex = keyArrayProperty.arraySize;
+ }
+ EditorGUI.EndDisabledGroup();
+
+ EditorGUI.indentLevel++;
+ var linePosition = position;
+ linePosition.y += EditorGUIUtility.singleLineHeight;
+ linePosition.xMax -= buttonWidth;
+
+ foreach(var entry in EnumerateEntries(keyArrayProperty, valueArrayProperty))
+ {
+ var keyProperty = entry.keyProperty;
+ var valueProperty = entry.valueProperty;
+ int i = entry.index;
+
+ float lineHeight = DrawKeyValueLine(keyProperty, valueProperty, linePosition, i);
+
+ buttonPosition = linePosition;
+ buttonPosition.x = linePosition.xMax;
+ buttonPosition.height = EditorGUIUtility.singleLineHeight;
+ if(GUI.Button(buttonPosition, s_iconMinus, s_buttonStyle))
+ {
+ buttonAction = Action.Remove;
+ buttonActionIndex = i;
+ }
+
+ if(i == conflictState.conflictIndex && conflictState.conflictOtherIndex == -1)
+ {
+ var iconPosition = linePosition;
+ iconPosition.size = s_buttonStyle.CalcSize(s_warningIconNull);
+ GUI.Label(iconPosition, s_warningIconNull);
+ }
+ else if(i == conflictState.conflictIndex)
+ {
+ var iconPosition = linePosition;
+ iconPosition.size = s_buttonStyle.CalcSize(s_warningIconConflict);
+ GUI.Label(iconPosition, s_warningIconConflict);
+ }
+ else if(i == conflictState.conflictOtherIndex)
+ {
+ var iconPosition = linePosition;
+ iconPosition.size = s_buttonStyle.CalcSize(s_warningIconOther);
+ GUI.Label(iconPosition, s_warningIconOther);
+ }
+
+
+ linePosition.y += lineHeight;
+ }
+
+ EditorGUI.indentLevel--;
+ }
+
+ if(buttonAction == Action.Add)
+ {
+ keyArrayProperty.InsertArrayElementAtIndex(buttonActionIndex);
+ valueArrayProperty.InsertArrayElementAtIndex(buttonActionIndex);
+ }
+ else if(buttonAction == Action.Remove)
+ {
+ DeleteArrayElementAtIndex(keyArrayProperty, buttonActionIndex);
+ DeleteArrayElementAtIndex(valueArrayProperty, buttonActionIndex);
+ }
+
+ conflictState.conflictKey = null;
+ conflictState.conflictValue = null;
+ conflictState.conflictIndex = -1;
+ conflictState.conflictOtherIndex = -1;
+ conflictState.conflictLineHeight = 0f;
+ conflictState.conflictKeyPropertyExpanded = false;
+ conflictState.conflictValuePropertyExpanded = false;
+
+ foreach(var entry1 in EnumerateEntries(keyArrayProperty, valueArrayProperty))
+ {
+ var keyProperty1 = entry1.keyProperty;
+ int i = entry1.index;
+ object keyProperty1Value = GetPropertyValue(keyProperty1);
+
+ if(keyProperty1Value == null)
+ {
+ var valueProperty1 = entry1.valueProperty;
+ SaveProperty(keyProperty1, valueProperty1, i, -1, conflictState);
+ DeleteArrayElementAtIndex(valueArrayProperty, i);
+ DeleteArrayElementAtIndex(keyArrayProperty, i);
+
+ break;
+ }
+
+
+ foreach(var entry2 in EnumerateEntries(keyArrayProperty, valueArrayProperty, i + 1))
+ {
+ var keyProperty2 = entry2.keyProperty;
+ int j = entry2.index;
+ object keyProperty2Value = GetPropertyValue(keyProperty2);
+
+ if(ComparePropertyValues(keyProperty1Value, keyProperty2Value))
+ {
+ var valueProperty2 = entry2.valueProperty;
+ SaveProperty(keyProperty2, valueProperty2, j, i, conflictState);
+ DeleteArrayElementAtIndex(keyArrayProperty, j);
+ DeleteArrayElementAtIndex(valueArrayProperty, j);
+
+ goto breakLoops;
+ }
+ }
+ }
+ breakLoops:
+
+ EditorGUI.EndProperty();
+ }
+
+ static float DrawKeyValueLine(SerializedProperty keyProperty, SerializedProperty valueProperty, Rect linePosition, int index)
+ {
+ bool keyCanBeExpanded = CanPropertyBeExpanded(keyProperty);
+ bool valueCanBeExpanded = CanPropertyBeExpanded(valueProperty);
+
+ if(!keyCanBeExpanded && valueCanBeExpanded)
+ {
+ return DrawKeyValueLineExpand(keyProperty, valueProperty, linePosition);
+ }
+ else
+ {
+ var keyLabel = keyCanBeExpanded ? ("Key " + index.ToString()) : "";
+ var valueLabel = valueCanBeExpanded ? ("Value " + index.ToString()) : "";
+ return DrawKeyValueLineSimple(keyProperty, valueProperty, keyLabel, valueLabel, linePosition);
+ }
+ }
+
+ static float DrawKeyValueLineSimple(SerializedProperty keyProperty, SerializedProperty valueProperty, string keyLabel, string valueLabel, Rect linePosition)
+ {
+ float labelWidth = EditorGUIUtility.labelWidth;
+ float labelWidthRelative = labelWidth / linePosition.width;
+
+ float keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
+ var keyPosition = linePosition;
+ keyPosition.height = keyPropertyHeight;
+ keyPosition.width = labelWidth - IndentWidth;
+ EditorGUIUtility.labelWidth = keyPosition.width * labelWidthRelative;
+ EditorGUI.PropertyField(keyPosition, keyProperty, TempContent(keyLabel), true);
+
+ float valuePropertyHeight = EditorGUI.GetPropertyHeight(valueProperty);
+ var valuePosition = linePosition;
+ valuePosition.height = valuePropertyHeight;
+ valuePosition.xMin += labelWidth;
+ EditorGUIUtility.labelWidth = valuePosition.width * labelWidthRelative;
+ EditorGUI.indentLevel--;
+ EditorGUI.PropertyField(valuePosition, valueProperty, TempContent(valueLabel), true);
+ EditorGUI.indentLevel++;
+
+ EditorGUIUtility.labelWidth = labelWidth;
+
+ return Mathf.Max(keyPropertyHeight, valuePropertyHeight);
+ }
+
+ static float DrawKeyValueLineExpand(SerializedProperty keyProperty, SerializedProperty valueProperty, Rect linePosition)
+ {
+ float labelWidth = EditorGUIUtility.labelWidth;
+
+ float keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
+ var keyPosition = linePosition;
+ keyPosition.height = keyPropertyHeight;
+ keyPosition.width = labelWidth - IndentWidth;
+ EditorGUI.PropertyField(keyPosition, keyProperty, GUIContent.none, true);
+
+ float valuePropertyHeight = EditorGUI.GetPropertyHeight(valueProperty);
+ var valuePosition = linePosition;
+ valuePosition.height = valuePropertyHeight;
+ EditorGUI.PropertyField(valuePosition, valueProperty, GUIContent.none, true);
+
+ EditorGUIUtility.labelWidth = labelWidth;
+
+ return Mathf.Max(keyPropertyHeight, valuePropertyHeight);
+ }
+
+ static bool CanPropertyBeExpanded(SerializedProperty property)
+ {
+ switch(property.propertyType)
+ {
+ case SerializedPropertyType.Generic:
+ case SerializedPropertyType.Vector4:
+ case SerializedPropertyType.Quaternion:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static void SaveProperty(SerializedProperty keyProperty, SerializedProperty valueProperty, int index, int otherIndex, ConflictState conflictState)
+ {
+ conflictState.conflictKey = GetPropertyValue(keyProperty);
+ conflictState.conflictValue = GetPropertyValue(valueProperty);
+ float keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
+ float valuePropertyHeight = EditorGUI.GetPropertyHeight(valueProperty);
+ float lineHeight = Mathf.Max(keyPropertyHeight, valuePropertyHeight);
+ conflictState.conflictLineHeight = lineHeight;
+ conflictState.conflictIndex = index;
+ conflictState.conflictOtherIndex = otherIndex;
+ conflictState.conflictKeyPropertyExpanded = keyProperty.isExpanded;
+ conflictState.conflictValuePropertyExpanded = valueProperty.isExpanded;
+ }
+
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ float propertyHeight = EditorGUIUtility.singleLineHeight;
+
+ if (property.isExpanded)
+ {
+ var keysProperty = property.FindPropertyRelative(KeysFieldName);
+ var valuesProperty = property.FindPropertyRelative(ValuesFieldName);
+
+ foreach(var entry in EnumerateEntries(keysProperty, valuesProperty))
+ {
+ var keyProperty = entry.keyProperty;
+ var valueProperty = entry.valueProperty;
+ float keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
+ float valuePropertyHeight = EditorGUI.GetPropertyHeight(valueProperty);
+ float lineHeight = Mathf.Max(keyPropertyHeight, valuePropertyHeight);
+ propertyHeight += lineHeight;
+ }
+
+ ConflictState conflictState = GetConflictState(property);
+
+ if(conflictState.conflictIndex != -1)
+ {
+ propertyHeight += conflictState.conflictLineHeight;
+ }
+ }
+
+ return propertyHeight;
+ }
+
+ static ConflictState GetConflictState(SerializedProperty property)
+ {
+ ConflictState conflictState;
+ PropertyIdentity propId = new PropertyIdentity(property);
+ if(!s_conflictStateDict.TryGetValue(propId, out conflictState))
+ {
+ conflictState = new ConflictState();
+ s_conflictStateDict.Add(propId, conflictState);
+ }
+ return conflictState;
+ }
+
+ static Dictionary<SerializedPropertyType, PropertyInfo> s_serializedPropertyValueAccessorsDict;
+
+ static SerializableDictionaryPropertyDrawer()
+ {
+ Dictionary<SerializedPropertyType, string> serializedPropertyValueAccessorsNameDict = new Dictionary<SerializedPropertyType, string>() {
+ { SerializedPropertyType.Integer, "intValue" },
+ { SerializedPropertyType.Boolean, "boolValue" },
+ { SerializedPropertyType.Float, "floatValue" },
+ { SerializedPropertyType.String, "stringValue" },
+ { SerializedPropertyType.Color, "colorValue" },
+ { SerializedPropertyType.ObjectReference, "objectReferenceValue" },
+ { SerializedPropertyType.LayerMask, "intValue" },
+ { SerializedPropertyType.Enum, "intValue" },
+ { SerializedPropertyType.Vector2, "vector2Value" },
+ { SerializedPropertyType.Vector3, "vector3Value" },
+ { SerializedPropertyType.Vector4, "vector4Value" },
+ { SerializedPropertyType.Rect, "rectValue" },
+ { SerializedPropertyType.ArraySize, "intValue" },
+ { SerializedPropertyType.Character, "intValue" },
+ { SerializedPropertyType.AnimationCurve, "animationCurveValue" },
+ { SerializedPropertyType.Bounds, "boundsValue" },
+ { SerializedPropertyType.Quaternion, "quaternionValue" },
+ };
+ Type serializedPropertyType = typeof(SerializedProperty);
+
+ s_serializedPropertyValueAccessorsDict = new Dictionary<SerializedPropertyType, PropertyInfo>();
+ BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
+
+ foreach(var kvp in serializedPropertyValueAccessorsNameDict)
+ {
+ PropertyInfo propertyInfo = serializedPropertyType.GetProperty(kvp.Value, flags);
+ s_serializedPropertyValueAccessorsDict.Add(kvp.Key, propertyInfo);
+ }
+ }
+
+ static GUIContent IconContent(string name, string tooltip)
+ {
+ var builtinIcon = EditorGUIUtility.IconContent (name);
+ return new GUIContent(builtinIcon.image, tooltip);
+ }
+
+ static GUIContent TempContent(string text)
+ {
+ s_tempContent.text = text;
+ return s_tempContent;
+ }
+
+ static void DeleteArrayElementAtIndex(SerializedProperty arrayProperty, int index)
+ {
+ var property = arrayProperty.GetArrayElementAtIndex(index);
+ // if(arrayProperty.arrayElementType.StartsWith("PPtr<$"))
+ if(property.propertyType == SerializedPropertyType.ObjectReference)
+ {
+ property.objectReferenceValue = null;
+ }
+
+ arrayProperty.DeleteArrayElementAtIndex(index);
+ }
+
+ public static object GetPropertyValue(SerializedProperty p)
+ {
+ PropertyInfo propertyInfo;
+ if(s_serializedPropertyValueAccessorsDict.TryGetValue(p.propertyType, out propertyInfo))
+ {
+ return propertyInfo.GetValue(p, null);
+ }
+ else
+ {
+ if(p.isArray)
+ return GetPropertyValueArray(p);
+ else
+ return GetPropertyValueGeneric(p);
+ }
+ }
+
+ static void SetPropertyValue(SerializedProperty p, object v)
+ {
+ PropertyInfo propertyInfo;
+ if(s_serializedPropertyValueAccessorsDict.TryGetValue(p.propertyType, out propertyInfo))
+ {
+ propertyInfo.SetValue(p, v, null);
+ }
+ else
+ {
+ if(p.isArray)
+ SetPropertyValueArray(p, v);
+ else
+ SetPropertyValueGeneric(p, v);
+ }
+ }
+
+ static object GetPropertyValueArray(SerializedProperty property)
+ {
+ object[] array = new object[property.arraySize];
+ for(int i = 0; i < property.arraySize; i++)
+ {
+ SerializedProperty item = property.GetArrayElementAtIndex(i);
+ array[i] = GetPropertyValue(item);
+ }
+ return array;
+ }
+
+ static object GetPropertyValueGeneric(SerializedProperty property)
+ {
+ Dictionary<string, object> dict = new Dictionary<string, object>();
+ var iterator = property.Copy();
+ if(iterator.Next(true))
+ {
+ var end = property.GetEndProperty();
+ do
+ {
+ string name = iterator.name;
+ object value = GetPropertyValue(iterator);
+ dict.Add(name, value);
+ } while(iterator.Next(false) && iterator.propertyPath != end.propertyPath);
+ }
+ return dict;
+ }
+
+ static void SetPropertyValueArray(SerializedProperty property, object v)
+ {
+ object[] array = (object[]) v;
+ property.arraySize = array.Length;
+ for(int i = 0; i < property.arraySize; i++)
+ {
+ SerializedProperty item = property.GetArrayElementAtIndex(i);
+ SetPropertyValue(item, array[i]);
+ }
+ }
+
+ static void SetPropertyValueGeneric(SerializedProperty property, object v)
+ {
+ Dictionary<string, object> dict = (Dictionary<string, object>) v;
+ var iterator = property.Copy();
+ if(iterator.Next(true))
+ {
+ var end = property.GetEndProperty();
+ do
+ {
+ string name = iterator.name;
+ SetPropertyValue(iterator, dict[name]);
+ } while(iterator.Next(false) && iterator.propertyPath != end.propertyPath);
+ }
+ }
+
+ static bool ComparePropertyValues(object value1, object value2)
+ {
+ if(value1 is Dictionary<string, object> && value2 is Dictionary<string, object>)
+ {
+ var dict1 = (Dictionary<string, object>)value1;
+ var dict2 = (Dictionary<string, object>)value2;
+ return CompareDictionaries(dict1, dict2);
+ }
+ else
+ {
+ return object.Equals(value1, value2);
+ }
+ }
+
+ static bool CompareDictionaries(Dictionary<string, object> dict1, Dictionary<string, object> dict2)
+ {
+ if(dict1.Count != dict2.Count)
+ return false;
+
+ foreach(var kvp1 in dict1)
+ {
+ var key1 = kvp1.Key;
+ object value1 = kvp1.Value;
+
+ object value2;
+ if(!dict2.TryGetValue(key1, out value2))
+ return false;
+
+ if(!ComparePropertyValues(value1, value2))
+ return false;
+ }
+
+ return true;
+ }
+
+ struct EnumerationEntry
+ {
+ public SerializedProperty keyProperty;
+ public SerializedProperty valueProperty;
+ public int index;
+
+ public EnumerationEntry(SerializedProperty keyProperty, SerializedProperty valueProperty, int index)
+ {
+ this.keyProperty = keyProperty;
+ this.valueProperty = valueProperty;
+ this.index = index;
+ }
+ }
+
+ static IEnumerable<EnumerationEntry> EnumerateEntries(SerializedProperty keyArrayProperty, SerializedProperty valueArrayProperty, int startIndex = 0)
+ {
+ if(keyArrayProperty.arraySize > startIndex)
+ {
+ int index = startIndex;
+ var keyProperty = keyArrayProperty.GetArrayElementAtIndex(startIndex);
+ var valueProperty = valueArrayProperty.GetArrayElementAtIndex(startIndex);
+ var endProperty = keyArrayProperty.GetEndProperty();
+
+ do
+ {
+ yield return new EnumerationEntry(keyProperty, valueProperty, index);
+ index++;
+ } while(keyProperty.Next(false) && valueProperty.Next(false) && !SerializedProperty.EqualContents(keyProperty, endProperty));
+ }
+ }
+}
+
+public class SerializableDictionaryStoragePropertyDrawer : PropertyDrawer
+{
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ property.Next(true);
+ EditorGUI.PropertyField(position, property, label, true);
+ }
+
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ property.Next(true);
+ return EditorGUI.GetPropertyHeight(property);
+ }
+}