private void RenderArray(SerializedProperty prop, string changedCallback) { var formatted = Regex.Split(prop.name, @"(?<!^)(?=[A-Z])"); formatted[0] = formatted[0].Substring(0, 1).ToUpper() + formatted[0].Substring(1); var disabledString = propDisabled ? "[Read Only]" : ""; prop.isExpanded = UTStyles.FoldoutHeader($"{String.Join(" ", formatted)} [{prop.arraySize}] {disabledString}", prop.isExpanded); var foldoutRect = GUILayoutUtility.GetLastRect(); if (!propDisabled) { HandleDragAndDrop(foldoutRect, prop); if (droppedObjects) { return; } } if (!prop.isExpanded) { return; } EditorGUI.BeginDisabledGroup(propDisabled); var popup = UTUtils.GetPropertyAttribute <PopupAttribute>(prop); for (int i = 0; i < prop.arraySize; i++) { EditorGUILayout.BeginHorizontal(); if (RenderPositionControls(i, new[] { prop })) { break; } EditorGUI.BeginChangeCheck(); if (popup == null) { EditorGUILayout.PropertyField(prop.GetArrayElementAtIndex(i), new GUIContent()); } else { var options = UTUtils.GetPopupOptions(prop.GetArrayElementAtIndex(i), null, popup, out var selectedIndex); selectedIndex = EditorGUILayout.Popup(selectedIndex, options); prop.GetArrayElementAtIndex(i).stringValue = options[selectedIndex]; } if (EditorGUI.EndChangeCheck()) { HandleChangeCallback(t, changedCallback, prop, null, new object[] { prop.GetArrayElementAtIndex(i), i }); } if (RenderRemoveControls(i, new[] { prop })) { HandleChangeCallback(t, changedCallback, prop, null, new object[] { null, i }); break; } EditorGUILayout.EndHorizontal(); } if (!propDisabled) { EditorGUILayout.BeginHorizontal(); if (RenderAddControls(new[] { prop }, "Add Element", null)) { HandleChangeCallback(t, changedCallback, prop, null, new object[] { prop.GetArrayElementAtIndex(prop.arraySize - 1), prop.arraySize - 1 }); } if (GUILayout.Button("Clear", GUILayout.MaxWidth(60))) { prop.arraySize = 0; HandleChangeCallback(t, changedCallback, prop, null, new object[] { null, 0 }); } EditorGUILayout.EndHorizontal(); } EditorGUI.EndDisabledGroup(); }
private void RenderStackedArray(string name, SerializedProperty prop, SerializedProperty otherProp, PopupAttribute leftPopup, PopupAttribute rightPopup, string addMethod, string addText, string changedCallback) { var disabledString = propDisabled ? "[Read Only]" : ""; prop.isExpanded = UTStyles.FoldoutHeader($"{name} [{prop.arraySize}] {disabledString}", prop.isExpanded); var foldoutRect = GUILayoutUtility.GetLastRect(); if (!propDisabled) { HandleDragAndDrop(foldoutRect, prop, otherProp); if (droppedObjects) { return; } } if (!prop.isExpanded) { return; } EditorGUI.BeginDisabledGroup(propDisabled); for (int i = 0; i < prop.arraySize; i++) { EditorGUILayout.BeginHorizontal(); if (RenderPositionControls(i, new[] { prop, otherProp })) { break; } // this code is very similar to PopupAttribute itself // but we have to handle it here directly because we are connecting two different props together // should probably refactor at some point EditorGUI.BeginChangeCheck(); // handle arrays of different lengths var lLength = prop.arraySize; var rLength = otherProp.arraySize; if (lLength != rLength) { prop.arraySize = Math.Max(lLength, rLength); otherProp.arraySize = Math.Max(lLength, rLength); } // Left Field if (leftPopup == null) { EditorGUILayout.PropertyField(prop.GetArrayElementAtIndex(i), new GUIContent()); } else { var source = otherProp.GetArrayElementAtIndex(i); var options = UTUtils.GetPopupOptions(prop.GetArrayElementAtIndex(i), source, leftPopup, out var selectedIndex); selectedIndex = EditorGUILayout.Popup(selectedIndex, options); prop.GetArrayElementAtIndex(i).stringValue = options[selectedIndex]; } if (rightPopup == null) { EditorGUILayout.PropertyField(otherProp.GetArrayElementAtIndex(i), new GUIContent()); } else { var source = prop.GetArrayElementAtIndex(i); var options = UTUtils.GetPopupOptions(otherProp.GetArrayElementAtIndex(i), source, rightPopup, out var selectedIndex); selectedIndex = EditorGUILayout.Popup(selectedIndex, options); otherProp.GetArrayElementAtIndex(i).stringValue = options[selectedIndex]; } if (EditorGUI.EndChangeCheck()) { HandleChangeCallback(t, changedCallback, prop, otherProp, new object[] { prop.GetArrayElementAtIndex(i), otherProp.GetArrayElementAtIndex(i), i }); } if (RenderRemoveControls(i, new[] { prop, otherProp })) { HandleChangeCallback(t, changedCallback, prop, otherProp, new object[] { null, null, i }); break; } EditorGUILayout.EndHorizontal(); } if (!propDisabled) { EditorGUILayout.BeginHorizontal(); if (RenderAddControls(new[] { prop, otherProp }, addText, addMethod)) { HandleChangeCallback(t, changedCallback, prop, otherProp, new object[] { prop.GetArrayElementAtIndex(prop.arraySize - 1), otherProp.GetArrayElementAtIndex(otherProp.arraySize - 1), prop.arraySize - 1 }); } if (GUILayout.Button("Clear", GUILayout.MaxWidth(60))) { prop.arraySize = 0; otherProp.arraySize = 0; HandleChangeCallback(t, changedCallback, prop, otherProp, new object[] { null, null, 0 }); } EditorGUILayout.EndHorizontal(); } EditorGUI.EndDisabledGroup(); }
private void DrawFieldElement(UTField field, SerializedProperty prop) { var propPath = prop.propertyPath; var arrIndex = Convert.ToInt32(field.isArray ? propPath.Substring(propPath.LastIndexOf("[") + 1, propPath.LastIndexOf("]") - propPath.LastIndexOf("[") - 1) : null); var customLabel = field.attributes.Find(i => i is ShowLabelAttribute) as ShowLabelAttribute; var uiAttrs = field.uiAttrs; var uiOverride = uiAttrs.Where(i => i.GetType().GetMethod("OnGUI")?.DeclaringType == i.GetType()).ToArray(); EditorGUI.BeginChangeCheck(); switch (prop.type) { case "bool": if (uiOverride.Any()) { uiOverride.First().OnGUI(prop); break; } if (customLabel == null) { EditorGUILayout.PropertyField(prop, new GUIContent(), GUILayout.MaxWidth(30)); } else { EditorGUILayout.PropertyField(prop, new GUIContent(customLabel.label ?? prop.displayName)); } break; default: // for list views we handle the UI separately due to how popup targeting works if (uiOverride.Any() && !field.isInListView) { uiOverride.First().OnGUI(prop); break; } var popupAttr = field.attributes.Find(i => i is PopupAttribute) as PopupAttribute; if (popupAttr != null) { var sourceProp = UTUtils.GetPropThroughAttribute(serializedObject, popupAttr.methodName); // I do not like this handling // need a way to determine if target is a part of the list view or not without breaking the bank var source = sourceProp == null ? null : !field.isInListView ? sourceProp : sourceProp.isArray && sourceProp.arraySize > arrIndex ? sourceProp.GetArrayElementAtIndex(arrIndex) : null; var options = UTUtils.GetPopupOptions(prop, source, popupAttr, out var selectedIndex); selectedIndex = EditorGUILayout.Popup(selectedIndex, options); // we force reserialize for cases of default values if (prop.type == "int") { if (prop.intValue != selectedIndex) { shouldReserialize = true; } prop.intValue = selectedIndex; } else { if (prop.stringValue != options[selectedIndex]) { shouldReserialize = true; } prop.stringValue = options[selectedIndex]; } break; } // if there are no popup attributes - still allow gui override if (uiOverride.Any(i => !(i is PopupAttribute))) { uiOverride.First(i => !(i is PopupAttribute)).OnGUI(prop); break; } if (customLabel == null) { EditorGUILayout.PropertyField(prop, new GUIContent()); } else { EditorGUILayout.PropertyField(prop, new GUIContent(customLabel.label ?? prop.displayName)); } break; } if (EditorGUI.EndChangeCheck() && field.onValueChaged != null) { if (!field.isInListView) { field.onValueChaged.Invoke(t, new object[] { prop }); return; } HandleFieldChangeArray(field, prop, arrIndex); } }