-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathDictionaryDrawer.cs
435 lines (381 loc) · 18.4 KB
/
DictionaryDrawer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
using System.Reflection;
public abstract class DictionaryDrawer<TK, TV> : PropertyDrawer
{
private Dictionary<TK, TV> _Dictionary;
private bool _Foldout;
private const float kButtonWidth = 18f;
private static float lineHeight = EditorGUIUtility.singleLineHeight + 4;
private float spacing = 12f;
private float fieldPadding = 1f;
private GUIStyle addEntryStyle;
private GUIContent addEntryContent;
private GUIStyle clearDictionaryStyle;
private GUIContent clearDictionaryContent;
//reuses clearDictionaryStyle. I am adding it for readability
private GUIStyle removeEntryStyle;
private GUIContent removeEntryContent;
private GUIStyle HeaderStyle;
private Rect buttonRect;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
CheckInitialize(property, label);
if (_Foldout)
{
//Height of the main Header and the two column headers + height of all the drawn dictionary entries + a little padding on the bottom.
return (GetDictionaryElementsHeight() + (lineHeight * 2)) + 14f;
}
return lineHeight+ 4f;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
CheckInitialize(property, label);
position.height = 20f;
DrawHeader(position, property, label);
if (!_Foldout)
return;
position.y += 5f + lineHeight * 2;
foreach (var item in _Dictionary)
{
var key = item.Key;
var value = item.Value;
var keyRect = position;
keyRect.width /= 3;
keyRect.x += 10;
//Apply vertical padding
keyRect.y += fieldPadding;
keyRect.height -= fieldPadding * 2;
EditorGUI.BeginChangeCheck();
var newKey = DoField(keyRect, typeof(TK), (TK)key);
if (EditorGUI.EndChangeCheck())
{
try
{
_Dictionary.Remove(key);
_Dictionary.Add(newKey, value);
}
catch (Exception e)
{
_Dictionary.Remove(key);
Debug.Log(e.Message);
}
break;
}
var valueRect = position;
valueRect.x = keyRect.xMax + spacing;
valueRect.y += fieldPadding;
//Apply vertical padding
valueRect.height -= fieldPadding * 2;
valueRect.width = (position.width - keyRect.width) - ((kButtonWidth + 2) * 2f) - valueRect.size.y - (spacing* 2.5f);
EditorGUI.BeginChangeCheck();
value = DoField(valueRect, typeof(TV), (TV)value);
Rect changeValueRect = new Rect(new Vector2(buttonRect.x - 2f, valueRect.position.y), new Vector2(kButtonWidth, valueRect.size.y));
value = ChangeValueType(changeValueRect, key, value);
if (EditorGUI.EndChangeCheck())
{
_Dictionary[key] = value;
break;
}
EditorGUIUtility.AddCursorRect(changeValueRect, MouseCursor.Link);
var removeRect = valueRect;
removeRect.x = buttonRect.x + kButtonWidth;
removeRect.width = kButtonWidth;
if (GUI.Button(removeRect, removeEntryContent, removeEntryStyle))
{
RemoveItem(key);
break;
}
EditorGUIUtility.AddCursorRect(removeRect, MouseCursor.Link);
position.y += Mathf.Max(GetEntryHeight(key) ,GetEntryHeight(value));
}
}
/// <summary>
/// Gets the combined height of all dictionary elements
/// </summary>
/// <returns></returns>
private float GetDictionaryElementsHeight()
{
float height = 0;
foreach(var item in _Dictionary)
{
var key = item.Key;
var value = item.Value;
height += Mathf.Max(GetEntryHeight(key), GetEntryHeight(value));
}
return height;
}
private void DrawColumn(Rect position, GUIStyle style)
{
Rect columnRect = new Rect(position.x, position.yMax - 1, position.width, GetDictionaryElementsHeight() + 12f);
GUI.Box(columnRect, GUIContent.none, style);
}
private void DrawHeader(Rect position, SerializedProperty property, GUIContent label)
{
Rect headerRect = new Rect(position.position, new Vector2(position.size.x - kButtonWidth * 1.5f, lineHeight));
GUI.Box(headerRect, GUIContent.none, HeaderStyle);
var foldoutRect = position;
foldoutRect.x += 4f;
foldoutRect.width -= 2 * kButtonWidth;
EditorGUI.BeginChangeCheck();
if(_Dictionary.Count > 0)
{
_Foldout = EditorGUI.Foldout(foldoutRect, _Foldout, label, true);
}
else
{
foldoutRect.x += 4f;
EditorGUI.LabelField(foldoutRect, label);
_Foldout = false;
}
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetBool(label.text, _Foldout);
}
//Draw the Add Item Button
buttonRect = position;
buttonRect.x = position.width - 20 - kButtonWidth + position.x + 1;
buttonRect.width = kButtonWidth;
GUIStyle headerButtonStyle = new GUIStyle(HeaderStyle);
headerButtonStyle.padding = new RectOffset(0, 0, 0, 0);
Rect headerButtonRect = new Rect(buttonRect.position, new Vector2(kButtonWidth * 1.5f, lineHeight));
if (GUI.Button(headerButtonRect, addEntryContent, headerButtonStyle))
{
AddNewItem();
}
EditorGUIUtility.AddCursorRect(headerButtonRect, MouseCursor.Link);
buttonRect.x -= kButtonWidth;
//Draw the Item count label
GUIStyle headerItemCountLabelStyle = new GUIStyle("MiniLabel");
GUIContent headerItemCountLabelContent = new GUIContent();
if(_Dictionary.Count == 0)
{
headerItemCountLabelContent = new GUIContent("Empty");
}
else
{
headerItemCountLabelContent = new GUIContent($"{_Dictionary.Count} Item{(_Dictionary.Count == 1 ? "" : "s")}");
}
GUI.Label(new Rect(buttonRect.x - 30f, buttonRect.y, 50f, headerRect.height), headerItemCountLabelContent, headerItemCountLabelStyle);
//Draw the header labels (Keys - Values)
if(_Foldout)
{
//Draw "Keys" header
position.y += headerRect.height;
Rect keyHeaderRect = new Rect(position.x, position.y - 1, position.width /3f + kButtonWidth - 1, headerRect.height);
GUIStyle columnHeaderStyle = new GUIStyle("GroupBox");
columnHeaderStyle.padding = new RectOffset(0, 0, 0, 0);
columnHeaderStyle.contentOffset = new Vector2(0, 3f);
GUI.Box(keyHeaderRect, new GUIContent("Keys"), columnHeaderStyle);
//Draw "Values" header
Rect valuesHeaderRect = new Rect(keyHeaderRect.xMax - 1, keyHeaderRect.y, (position.width - keyHeaderRect.width - kButtonWidth * 0.5f), keyHeaderRect.height);
GUI.Box(valuesHeaderRect, new GUIContent("Values"), columnHeaderStyle);
//Draw the Columns for the keys and values.
DrawColumn(keyHeaderRect, columnHeaderStyle);
DrawColumn(valuesHeaderRect, columnHeaderStyle);
position.y += headerRect.height;
}
/*
if (GUI.Button(buttonRect, clearDictionaryContent, clearDictionaryStyle))
{
ClearDictionary();
}
*/
}
#region TypeControls
private static float GetEntryHeight<T>(T value)
{
switch (value)
{
case Bounds: return lineHeight * 2;
case BoundsInt: return lineHeight * 2;
case Rect: return lineHeight * 2;
case RectInt: return lineHeight * 2;
default: return lineHeight;
}
}
private static T DoField<T>(Rect rect, Type type, T value)
{
if (typeof(UnityObject).IsAssignableFrom(type))
return (T)(object)EditorGUI.ObjectField(rect, (UnityObject)(object)value, type, true);
switch (value)
{
case null: EditorGUI.LabelField(rect, "null"); return value;
case long: return (T)(object)EditorGUI.LongField(rect, (long)(object)value);
case int: return (T)(object)EditorGUI.IntField(rect, (int)(object)value);
case float: return (T)(object)EditorGUI.FloatField(rect, (float)(object)value);
case double: return (T)(object)EditorGUI.DoubleField(rect, (double)(object)value);
case string: return (T)(object)EditorGUI.TextField(rect, (string)(object)value);
case bool: return (T)(object)EditorGUI.Toggle(rect, (bool)(object)value);
case Vector2Int: return (T)(object)EditorGUI.Vector2IntField(rect, GUIContent.none, (Vector2Int)(object)value);
case Vector3Int: return (T)(object)EditorGUI.Vector3IntField(rect, GUIContent.none, (Vector3Int)(object)value);
case Vector2: return (T)(object)EditorGUI.Vector2Field(rect, GUIContent.none, (Vector2)(object)value);
case Vector3: return (T)(object)EditorGUI.Vector3Field(rect, GUIContent.none, (Vector3)(object)value);
case Vector4: return (T)(object)EditorGUI.Vector4Field(rect, GUIContent.none, (Vector4)(object)value);
case BoundsInt: return (T)(object)EditorGUI.BoundsIntField(rect, (BoundsInt)(object)value);
case Bounds: return (T)(object)EditorGUI.BoundsField(rect, (Bounds)(object)value);
case RectInt: return (T)(object)EditorGUI.RectIntField(rect, (RectInt)(object)value);
case Rect: return (T)(object)EditorGUI.RectField(rect, (Rect)(object)value);
case Color: return (T)(object)EditorGUI.ColorField(rect, (Color)(object)value);
case AnimationCurve: return (T)(object)EditorGUI.CurveField(rect, (AnimationCurve)(object)value);
case Gradient: return (T)(object)EditorGUI.GradientField(rect, (Gradient)(object)value);
case UnityObject: return (T)(object)EditorGUI.ObjectField(rect, (UnityObject)(object)value, type, true);
}
if (value.GetType().IsEnum)
{
if (Enum.TryParse(value.GetType(), value.ToString(), out object enumValue))
{
return (T)(object)EditorGUI.EnumPopup(rect, (Enum)enumValue);
}
}
//Setup GUIStyle and GUIContent for the "Clear Dictionary" button
GUIStyle style = new GUIStyle(EditorStyles.miniButton);
style.padding = new RectOffset(2, 2, 2, 2);
GUIContent content = new GUIContent(EditorGUIUtility.IconContent("d_UnityEditor.ConsoleWindow@2x"));
content.tooltip = "Debug Values";
Type fieldType = value.GetType();
bool isStruct = fieldType.IsValueType && !fieldType.IsEnum;
EditorGUI.LabelField(rect, $"{fieldType.ToString().Replace("+", ".")} {(isStruct ? "struct" : "class")} instance");
if( GUI.Button(new Rect(rect.xMax - kButtonWidth, rect.y, kButtonWidth, kButtonWidth), content, style))
{
Debug.Log(JsonUtility.ToJson(value));
}
//DrawSerializableObject(rect, value);
return value;
}
//Unfinished
/*
public static void DrawSerializableObject(Rect rect, object obj)
{
if (obj == null)
{
Console.WriteLine("Object is null.");
return;
}
Type type = obj.GetType();
FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (FieldInfo field in fields)
{
object value = field.GetValue(obj);
rect.y += GetEntryHeight(value);
Debug.Log($"{field.Name}: {value}");
if(value != null)
{
DoField(rect, value.GetType(), value);
}
}
}
*/
private TV ChangeValueType(Rect rect, TK key, TV value)
{
GUIContent content = EditorGUIUtility.IconContent("_Popup");
content.tooltip = "Change Value Type";
GUIStyle changeItemStyle = new GUIStyle(EditorStyles.miniButton);
changeItemStyle.padding = new RectOffset(2, 2, 2, 2);
if (GUI.Button(rect, content, changeItemStyle))
{
GenericMenu genericMenu = new GenericMenu();
genericMenu.AddItem(new GUIContent("Numbers/int"), value is int, () => { _Dictionary[key] = (TV)(object)default(int); });
genericMenu.AddItem(new GUIContent("Numbers/float"), value is float, () => { _Dictionary[key] = (TV)(object)default(float); });
genericMenu.AddItem(new GUIContent("Numbers/double"), value is double, () => { _Dictionary[key] = (TV)(object)default(double); });
genericMenu.AddItem(new GUIContent("Numbers/long"), value is long, () => { _Dictionary[key] = (TV)(object)default(long); });
genericMenu.AddItem(new GUIContent("Vectors/Vector2"), (value is Vector2 && !(value is Vector2Int)), () => { _Dictionary[key] = (TV)(object)default(Vector2); });
genericMenu.AddItem(new GUIContent("Vectors/Vector3"), (value is Vector3 && !(value is Vector3Int)), () => { _Dictionary[key] = (TV)(object)default(Vector3); });
genericMenu.AddItem(new GUIContent("Vectors/Vector4"), value is Vector4, () => { _Dictionary[key] = (TV)(object)default(Vector4); });
genericMenu.AddItem(new GUIContent("Vectors/Vector2Int"), value is Vector2Int, () => { _Dictionary[key] = (TV)(object)default(Vector2Int); });
genericMenu.AddItem(new GUIContent("Vectors/Vector3Int"), value is Vector3Int, () => { _Dictionary[key] = (TV)(object)default(Vector3Int); });
genericMenu.AddItem(new GUIContent("Bounds/Bounds"), value is Bounds && value is not BoundsInt, () => { _Dictionary[key] = (TV)(object)default(Bounds); });
genericMenu.AddItem(new GUIContent("Bounds/BoundsInt"), value is BoundsInt, () => { _Dictionary[key] = (TV)(object)default(BoundsInt); });
genericMenu.AddItem(new GUIContent("Rects/Rect"), value is Rect && value is not RectInt, () => { _Dictionary[key] = (TV)(object)default(Rect); });
genericMenu.AddItem(new GUIContent("Rects/RectInt"), value is RectInt, () => { _Dictionary[key] = (TV)(object)default(RectInt); });
genericMenu.AddItem(new GUIContent("string"), value is string, () => { _Dictionary[key] = (TV)(object)""; });
genericMenu.AddItem(new GUIContent("bool"), value is bool, () => { _Dictionary[key] = (TV)(object)default(bool); });
genericMenu.AddItem(new GUIContent("Color"), value is Color, () => { _Dictionary[key] = (TV)(object)default(Color); });
genericMenu.AddItem(new GUIContent("AnimationCurve"), value is AnimationCurve, () => { _Dictionary[key] = (TV)(object)(new AnimationCurve()); });
genericMenu.AddItem(new GUIContent("Gradient"), value is Gradient, () => { _Dictionary[key] = (TV)(object)(new Gradient()); });
genericMenu.AddItem(new GUIContent("Unity Object"), value is UnityObject, () => { _Dictionary[key] = (TV)(object)(new UnityObject()); });
genericMenu.ShowAsContext();
}
return (TV)value;
}
#endregion
private void RemoveItem(TK key)
{
_Dictionary.Remove(key);
}
private void CheckInitialize(SerializedProperty property, GUIContent label)
{
if (_Dictionary == null)
{
SetupStyles();
var target = property.serializedObject.targetObject;
_Dictionary = fieldInfo.GetValue(target) as Dictionary<TK, TV>;
if (_Dictionary == null)
{
_Dictionary = new Dictionary<TK, TV>();
fieldInfo.SetValue(target, _Dictionary);
}
_Foldout = EditorPrefs.GetBool(label.text);
}
}
private void SetupStyles()
{
//Setup GUIStyle and GUIContent for the "Add Item" button
addEntryStyle = new GUIStyle(EditorStyles.miniButton);
addEntryStyle.padding = new RectOffset(3, 3, 3, 3);
addEntryContent = new GUIContent(EditorGUIUtility.IconContent("d_CreateAddNew@2x"));
addEntryContent.tooltip = "Add Item";
//Setup GUIStyle and GUIContent for the "Clear Dictionary" button
clearDictionaryStyle = new GUIStyle(EditorStyles.miniButton);
clearDictionaryStyle.padding = new RectOffset(2, 2, 2, 2);
clearDictionaryContent = new GUIContent(EditorGUIUtility.IconContent("d_winbtn_win_close@2x"));
clearDictionaryContent.tooltip = "Clear dictionary";
removeEntryContent = new GUIContent(EditorGUIUtility.IconContent("d_winbtn_win_close@2x"));
removeEntryContent.tooltip = "Remove Item";
removeEntryStyle = new GUIStyle(clearDictionaryStyle);
HeaderStyle = new GUIStyle("MiniToolbarButton");
HeaderStyle.fixedHeight = 0;
HeaderStyle.fixedWidth = 0;
HeaderStyle.padding = new RectOffset(2,2,2,2);
}
private void ClearDictionary()
{
_Dictionary.Clear();
}
private void AddNewItem()
{
TK key;
if (typeof(TK) == typeof(string))
key = (TK)(object)"";
else key = default(TK);
if (typeof(TV) == typeof(object))
{
var value = (TV)(object)1;
try
{
_Dictionary.Add(key, value);
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}
else
{
var value = default(TV);
try
{
_Dictionary.Add(key, value);
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}
}
}
[CustomPropertyDrawer(typeof(Blackboard))]
public class BlackboardDrawer : DictionaryDrawer<string, object> { }