private void HandleListView(List <UTField> listViewFields, KeyValuePair <string, UTFieldType> fieldEntry) { List <SerializedProperty> propsList = new List <SerializedProperty>(); foreach (var field in listViewFields) { propsList.Add(serializedObject.FindProperty(field.name)); } // draw header var parentProp = serializedObject.FindProperty(listViewFields[0].name); var uiAttrs = listViewFields[0].uiAttrs; var isVisible = true; foreach (var uiAttr in uiAttrs) { if (!uiAttr.GetVisible(parentProp)) { isVisible = false; break; } } if (!isVisible) { return; } foreach (var uiAttr in uiAttrs) { uiAttr.BeforeGUI(propsList[0]); } var propDisabled = listViewFields.Exists(i => i.isDisabled); var page = listPaginations[fieldEntry.Key]; var pageCount = Mathf.CeilToInt(parentProp.arraySize / 30f); if (pageCount > 1 && page > pageCount - 1) { page = pageCount - 1; listPaginations[fieldEntry.Key] = page; } var utilsShownVal = utilsShown[fieldEntry.Key]; parentProp.isExpanded = UTStyles.FoldoutHeader( GetListViewHeaderText(fieldEntry.Key, propDisabled, parentProp.isExpanded, page, pageCount, parentProp.arraySize), parentProp.isExpanded, ref utilsShownVal); utilsShown[fieldEntry.Key] = utilsShownVal; var foldoutRect = GUILayoutUtility.GetLastRect(); if (!propDisabled && UTEditorArray.HandleDragAndDrop(foldoutRect, serializedObject, propsList)) { droppedObjects = true; if (listViewFields[0].onValueChaged == null) { return; } HandleFieldChangeArray(listViewFields[0], parentProp, parentProp.arraySize - 1); return; } if (droppedObjects) { return; } if (!parentProp.isExpanded) { return; } EditorGUI.BeginDisabledGroup(propDisabled); // List View Utils if (utilsShownVal) { CreateListViewUtils(propsList, fieldEntry); } for (int i = 30 * page; i < Mathf.Min(parentProp.arraySize, 30 * (page + 1)); i++) { Rect headerRect = default; Rect[] fieldRects = new Rect[listViewFields.Count]; // get a react to draw a header later if (listViewFields.Count > 1 && i == 30 * page) { headerRect = EditorGUILayout.GetControlRect(); } EditorGUILayout.BeginHorizontal(); // position controls if (UTEditorArray.RenderPositionControls(i, propsList, out var newIndex)) { HandleFieldChangeArray(listViewFields[0], parentProp.GetArrayElementAtIndex(newIndex), newIndex); break; } var fieldIndex = 0; foreach (var field in listViewFields) { var prop = serializedObject.FindProperty(field.name); if (prop.arraySize != parentProp.arraySize) { prop.arraySize = parentProp.arraySize; } DrawFieldElement(field, prop.GetArrayElementAtIndex(i)); // saved the field width to reuse in the header later if (listViewFields.Count > 1 && i == 30 * page) { fieldRects[fieldIndex] = GUILayoutUtility.GetLastRect(); } fieldIndex++; } // removal controls if (UTEditorArray.RenderRemoveControls(i, propsList)) { if (listViewFields[0].onValueChaged != null) { HandleFieldChangeArray(listViewFields[0], null, i); } break; } EditorGUILayout.EndHorizontal(); // draw a header with saved field widths // we only draw a header for cases where there are multiple elements if (listViewFields.Count > 1 && i == 30 * page) { GUI.Box(headerRect, "", new GUIStyle("helpBox")); for (int j = 0; j < listViewFields.Count; j++) { var adjustedRect = fieldRects[j]; if (j == 0) { adjustedRect.xMin = headerRect.xMin + 2; } adjustedRect.yMin = headerRect.yMin; adjustedRect.yMax = headerRect.yMax; EditorGUI.LabelField(adjustedRect, listViewFields[j].listViewColumnName ?? serializedObject.FindProperty(listViewFields[j].name).displayName); } } } EditorGUI.EndDisabledGroup(); if (pageCount > 1) { EditorGUILayout.BeginHorizontal(); EditorGUI.BeginDisabledGroup(page != pageCount - 1); } if (GUILayout.Button(listViewFields[0].listViewAddTitle ?? "Add Element")) { var insertAt = parentProp.arraySize; if (listViewFields[0].listViewAddMethod != null) { listViewFields[0].listViewAddMethod.Invoke(t, new object[] { serializedObject }); } else { foreach (var field in listViewFields) { var prop = serializedObject.FindProperty(field.name); prop.InsertArrayElementAtIndex(insertAt); } } if (listViewFields[0].onValueChaged != null) { HandleFieldChangeArray(listViewFields[0], parentProp.GetArrayElementAtIndex(insertAt), insertAt); } } if (pageCount > 1) { EditorGUI.EndDisabledGroup(); EditorGUI.BeginDisabledGroup(page <= 0); if (GUILayout.Button(UTStyles.ArrowL, UTStyles.MiniArrowLeft)) { listPaginations[fieldEntry.Key]--; } EditorGUI.EndDisabledGroup(); EditorGUI.BeginDisabledGroup(page >= pageCount - 1); if (GUILayout.Button(UTStyles.ArrowR, UTStyles.MiniArrowRight)) { listPaginations[fieldEntry.Key]++; } EditorGUI.EndDisabledGroup(); EditorGUILayout.EndHorizontal(); } foreach (var uiAttr in uiAttrs) { uiAttr.AfterGUI(parentProp); } }
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 RenderStackedArray(string name, SerializedProperty prop, SerializedProperty otherProp, 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; } 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); } EditorGUILayout.PropertyField(prop.GetArrayElementAtIndex(i), new GUIContent()); EditorGUILayout.PropertyField(otherProp.GetArrayElementAtIndex(i), new GUIContent()); 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(40))) { prop.arraySize = 0; otherProp.arraySize = 0; HandleChangeCallback(t, changedCallback, prop, otherProp, new object[] { null, null, 0 }); } EditorGUILayout.EndHorizontal(); } EditorGUI.EndDisabledGroup(); }
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(); }
public override void OnInspectorGUI() { t = (UdonSharpBehaviour)target; showUdonSettings = (bool)(UTUtils.GetUTSetting("showUdonSettings", UTUtils.UTSettingType.Bool) ?? false); // we force-disable collision transfer for toolkit driven behaviours as it is not applicable if (!nonUBChecked) { nonUBMode = t.gameObject.GetComponent <UdonBehaviour>() == null; nonUBChecked = true; } if (!nonUBMode) { if (UdonSharpEditorUtility.GetBackingUdonBehaviour(t).AllowCollisionOwnershipTransfer) { UdonSharpEditorUtility.GetBackingUdonBehaviour(t).AllowCollisionOwnershipTransfer = false; } } var headerExited = false; EditorGUI.BeginChangeCheck(); showUdonSettings = GUILayout.Toggle(showUdonSettings, "Udon Settings", UTStyles.smallButton); if (EditorGUI.EndChangeCheck()) { UTUtils.SetUTSetting("showUdonSettings", UTUtils.UTSettingType.Bool, showUdonSettings); } if (showUdonSettings) { headerExited = UdonSharpGUI.DrawDefaultUdonSharpBehaviourHeader(t, true); } if (headerExited) { return; } #region Caching if (!cacheBuilt) { tT = t.GetType(); programAsset = UdonSharpEditorUtility.GetUdonSharpProgramAsset(t); behInfo = new UTBehaviourInfo(t); var prop = serializedObject.GetIterator(); var next = prop.NextVisible(true); if (next) { do { if (prop.name == "m_Script") { continue; } if (!fieldCache.ContainsKey(prop.name)) { var newField = new UTField(prop); fieldCache.Add(prop.name, newField); if (newField.isInTabGroup) { if (!tabs.ContainsKey(newField.tabGroupName)) { tabs.Add(newField.tabGroupName, new Dictionary <string, UTFieldType>()); // we only support one tab group per behaviour right now if (!tabsExist) { fieldOrder.Add(newField.tabGroupName, UTFieldType.Tab); tabsExist = true; } } // since tabs can host foldouts - we need to explicitly retarget them // this logic could be generalized if i ever want to make all of this recursive if (newField.isInFoldout) { if (!foldouts.ContainsKey(newField.foldoutName)) { foldouts.Add(newField.foldoutName, new Dictionary <string, UTFieldType>()); tabs[newField.tabGroupName].Add(newField.foldoutName, UTFieldType.Foldout); } AddToFieldOrder(newField, prop, true); continue; } AddToFieldOrder(newField, prop, addToTabGroup: true); continue; } if (newField.isInFoldout) { if (!foldouts.ContainsKey(newField.foldoutName)) { foldouts.Add(newField.foldoutName, new Dictionary <string, UTFieldType>()); fieldOrder.Add(newField.foldoutName, UTFieldType.Foldout); } AddToFieldOrder(newField, prop, true); continue; } AddToFieldOrder(newField, prop); } } while (prop.NextVisible(false)); } cacheBuilt = true; return; } var e = Event.current; if (e.type == EventType.Repaint) { if (firstRepaint) { firstRepaint = false; return; } } #endregion if (behInfo.customName != null || behInfo.helpUrl != null) { EditorGUILayout.BeginHorizontal(); UTStyles.RenderHeader(behInfo.customName != null ? behInfo.customName : behInfo.name); if (behInfo.helpUrl != null) { if (GUILayout.Button("?", GUILayout.Height(26), GUILayout.Width(26))) { Application.OpenURL(behInfo.helpUrl); } } EditorGUILayout.EndHorizontal(); } if (behInfo.helpMsg != null) { UTStyles.RenderNote(behInfo.helpMsg); } if (behInfo.onBeforeEditor != null) { behInfo.onBeforeEditor.Invoke(t, new object[] { serializedObject }); } // handle jagged arrays Undo.RecordObject(t, "Change Properties"); EditorGUI.BeginChangeCheck(); droppedObjects = false; // this method is overrideable by custom code DrawGUI(); if (EditorGUI.EndChangeCheck() || droppedObjects || shouldReserialize) { if (behInfo.onValuesChanged != null) { behInfo.onValuesChanged.Invoke(t, new object[] { serializedObject }); } serializedObject.ApplyModifiedProperties(); } if (droppedObjects) { return; } if (behInfo.onAfterEditor != null) { behInfo.onAfterEditor.Invoke(t, new object[] { serializedObject }); } #region Buttons if (behInfo.buttons != null && !Application.isPlaying && behInfo.buttons.Length > 0) { buttonsExpanded = UTStyles.FoldoutHeader("Editor Methods", buttonsExpanded); if (buttonsExpanded) { EditorGUILayout.BeginVertical(new GUIStyle("helpBox")); foreach (var button in behInfo.buttons) { if (GUILayout.Button(button.Name)) { button.Invoke(t, new object[] {}); } } EditorGUILayout.EndVertical(); } } if (Application.isPlaying && behInfo.udonCustomEvents.Length > 0 && !nonUBMode) { methodsExpanded = UTStyles.FoldoutHeader("Udon Events", methodsExpanded); if (methodsExpanded) { EditorGUILayout.BeginVertical(new GUIStyle("helpBox")); var rowBreak = Mathf.Max(1, Mathf.Min(3, behInfo.udonCustomEvents.Length - 1)); var rowEndI = -100; foreach (var(button, i) in behInfo.udonCustomEvents.WithIndex()) { if (i == rowEndI && i != behInfo.udonCustomEvents.Length - 1) { EditorGUILayout.EndHorizontal(); } if (i % rowBreak == 0 && i != behInfo.udonCustomEvents.Length - 1) { EditorGUILayout.BeginHorizontal(); rowEndI = Math.Min(i + rowBreak, behInfo.udonCustomEvents.Length - 1); } if (GUILayout.Button(button)) { UdonSharpEditorUtility.GetBackingUdonBehaviour(t).SendCustomEvent(button); UdonSharpEditorUtility.CopyUdonToProxy(t); } if (i == behInfo.udonCustomEvents.Length - 1 && rowEndI != -100) { EditorGUILayout.EndHorizontal(); } } EditorGUILayout.EndVertical(); } } #endregion }
private void HandleFields(Dictionary <string, UTFieldType> fields) { foreach (var fieldEntry in fields) { if (droppedObjects) { break; } switch (fieldEntry.Value) { case UTFieldType.Regular: { var prop = serializedObject.FindProperty(fieldEntry.Key); DrawField(fieldCache[fieldEntry.Key], prop); break; } case UTFieldType.Tab: { var tabNames = tabs.Select(i => i.Key).ToArray(); var tabSaveTarget = fieldCache[tabs[tabNames[0]].Keys.ToArray()[0]].tabSaveTarget; if (!String.IsNullOrEmpty(tabSaveTarget)) { var targetProp = serializedObject.FindProperty(tabSaveTarget); tabOpen = targetProp.intValue; EditorGUI.BeginChangeCheck(); tabOpen = GUILayout.Toolbar(tabOpen, tabNames); if (EditorGUI.EndChangeCheck()) { targetProp.intValue = tabOpen; if (fieldCache.ContainsKey(tabSaveTarget)) { var fieldChange = fieldCache?[tabSaveTarget].onValueChaged; if (fieldChange != null) { fieldChange.Invoke(t, new object[] { targetProp }); } } } } else { tabOpen = GUILayout.Toolbar(tabOpen, tabNames); } HandleFields(tabs[tabNames[tabOpen]]); break; } case UTFieldType.Foldout: { var foldout = foldouts[fieldEntry.Key].First(); UTField field; switch (foldout.Value) { case UTFieldType.Horizontal: { field = horizontalViews[foldout.Key].First(); break; } case UTFieldType.ListView: { field = listViews[foldout.Key].First(); break; } default: { field = fieldCache[foldout.Key]; break; } } var parentProp = serializedObject.FindProperty(field.name); var foldoutName = field.foldoutName; parentProp.isExpanded = UTStyles.FoldoutHeader(foldoutName, parentProp.isExpanded); if (!parentProp.isExpanded) { break; } EditorGUILayout.BeginVertical(new GUIStyle("helpBox")); HandleFields(foldouts[fieldEntry.Key]); EditorGUILayout.EndVertical(); break; } case UTFieldType.ListView: { var listViewFields = listViews[fieldEntry.Key]; HandleListView(listViewFields, fieldEntry); break; } case UTFieldType.Horizontal: { if (droppedObjects) { break; } var horizontalFields = horizontalViews[fieldEntry.Key]; List <SerializedProperty> propsList = new List <SerializedProperty>(); foreach (var field in horizontalFields) { propsList.Add(serializedObject.FindProperty(field.name)); } var uiAttrs = horizontalFields[0].uiAttrs; var isVisible = true; foreach (var uiAttr in uiAttrs) { if (!uiAttr.GetVisible(propsList[0])) { isVisible = false; break; } } if (!isVisible) { break; } foreach (var uiAttr in uiAttrs) { uiAttr.BeforeGUI(propsList[0]); } var horizontalAttr = horizontalFields[0].attributes.Find(i => i is HorizontalAttribute) as HorizontalAttribute; if (horizontalAttr.showHeader) { // header frame and label for the group var oldColor = GUI.backgroundColor; var headerRect = EditorGUILayout.GetControlRect(); GUI.backgroundColor = new Color(oldColor.r, oldColor.g, oldColor.b, 0.5f); GUI.Box(headerRect, "", new GUIStyle("helpBox")); GUI.backgroundColor = oldColor; EditorGUI.LabelField(headerRect, fieldEntry.Key); } EditorGUILayout.BeginHorizontal(); for (int i = 0; i < propsList.Count; i++) { DrawFieldElement(horizontalFields[i], propsList[i]); } EditorGUILayout.EndHorizontal(); foreach (var uiAttr in uiAttrs) { uiAttr.AfterGUI(propsList[0]); } break; } } } }