private void AddToFieldOrder(UTField field, SerializedProperty prop, ref Dictionary <string, UTFieldType> targetObj) { // we reuse ListView rendering for the arrays if (field.isArray && !field.isInListView) { listPaginations.Add(prop.displayName, 0); utilsShown.Add(prop.displayName, false); listViews.Add(prop.displayName, new List <UTField> { field }); targetObj.Add(prop.displayName, UTFieldType.ListView); return; } if (field.isInListView) { if (!listViews.ContainsKey(field.listViewName)) { utilsShown.Add(field.listViewName, false); listPaginations.Add(field.listViewName, 0); listViews.Add(field.listViewName, new List <UTField> { field }); targetObj.Add(field.listViewName, UTFieldType.ListView); } else { listViews[field.listViewName].Add(field); } return; } if (field.isInHorizontal) { if (!horizontalViews.ContainsKey(field.horizontalName)) { horizontalViews.Add(field.horizontalName, new List <UTField> { field }); targetObj.Add(field.horizontalName, UTFieldType.Horizontal); } else { horizontalViews[field.horizontalName].Add(field); } return; } else { targetObj.Add(field.name, UTFieldType.Regular); } }
private void AddToFieldOrder(UTField field, SerializedProperty prop, bool addToFoldout = false, bool addToTabGroup = false) { if (!addToFoldout && !addToTabGroup) { AddToFieldOrder(field, prop, ref fieldOrder); return; } if (addToFoldout) { var foldoutTarget = foldouts[field.foldoutName]; AddToFieldOrder(field, prop, ref foldoutTarget); foldouts[field.foldoutName] = foldoutTarget; return; } var tabGroupTarget = tabs[field.tabGroupName]; AddToFieldOrder(field, prop, ref tabGroupTarget); tabs[field.tabGroupName] = tabGroupTarget; return; }
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 HandleFieldChangeArray(UTField field, SerializedProperty prop, int arrIndex = 0) { if (field.isValueChangeAtomic) { if (!field.isInListView) { field.onValueChaged.Invoke(t, new object[] { prop, arrIndex }); return; } var fields = listViews[field.listViewName]; var props = new List <SerializedProperty>(); foreach (var utField in fields) { var parentProp = serializedObject.FindProperty(utField.name); props.Add(parentProp); } field.onValueChaged.Invoke(t, props.ToArray()); return; } if (field.isValueChangedFull) { if (!field.isInListView) { var parentProp = serializedObject.FindProperty(field.name); var props = new SerializedProperty[parentProp.arraySize]; for (int i = 0; i < parentProp.arraySize; i++) { props[i] = parentProp.GetArrayElementAtIndex(i); } field.onValueChaged.Invoke(t, new object[] { props }); return; } { var fields = listViews[field.listViewName]; var props = new List <SerializedProperty[]>(); foreach (var utField in fields) { var parentProp = serializedObject.FindProperty(utField.name); var localProps = new List <SerializedProperty>(); for (int i = 0; i < parentProp.arraySize; i++) { localProps.Add(parentProp.GetArrayElementAtIndex(i)); } props.Add(localProps.ToArray()); } field.onValueChaged.Invoke(t, props.ToArray()); return; } } if (field.isValueChangedWithObject) { if (!field.isInListView) { var parentProp = serializedObject.FindProperty(field.name); var props = new SerializedProperty[parentProp.arraySize]; for (int i = 0; i < parentProp.arraySize; i++) { props[i] = parentProp.GetArrayElementAtIndex(i); } field.onValueChaged.Invoke(t, new object[] { serializedObject, props }); return; } { var fields = listViews[field.listViewName]; var props = new List <SerializedProperty[]>(); foreach (var utField in fields) { var parentProp = serializedObject.FindProperty(utField.name); var localProps = new List <SerializedProperty>(); for (int i = 0; i < parentProp.arraySize; i++) { localProps.Add(parentProp.GetArrayElementAtIndex(i)); } props.Add(localProps.ToArray()); } var appended = new List <object> { serializedObject }; appended.AddRange(props); field.onValueChaged.Invoke(t, appended.ToArray()); return; } } }
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); } }
private void DrawField(UTField field, SerializedProperty prop) { // this is taken directly from U# code var isNonDefault = false; object origValue = null; if (programAsset && !nonUBMode) { origValue = programAsset.GetRealProgram().Heap.GetHeapVariable(programAsset.GetRealProgram().SymbolTable.GetAddressFromSymbol(prop.name)); var fieldVal = tT.GetField(prop.name)?.GetValue(t); isNonDefault = fieldVal != null && origValue != null && !origValue.Equals(fieldVal); } var uiAttrs = field.uiAttrs; if (!uiAttrs.Any()) { EditorGUI.BeginDisabledGroup(field.isDisabled); if (isNonDefault) { PropertyFieldWithUndo(prop, origValue); } else { EditorGUILayout.PropertyField(prop, new GUIContent(prop.displayName)); } EditorGUI.EndDisabledGroup(); return; } var isVisible = true; foreach (var uiAttr in uiAttrs) { if (!uiAttr.GetVisible(prop)) { isVisible = false; break; } } if (!isVisible) { return; } foreach (var uiAttr in uiAttrs) { uiAttr.BeforeGUI(prop); } EditorGUI.BeginChangeCheck(); EditorGUI.BeginDisabledGroup(field.isDisabled); // we can only have a single UI overriding OnGUI that renders the actual prop field var uiOverride = uiAttrs.Where(i => i.GetType().GetMethod("OnGUI")?.DeclaringType == i.GetType()).ToArray(); if (uiOverride.Any()) { uiOverride.First().OnGUI(prop); } else { if (isNonDefault) { PropertyFieldWithUndo(prop, origValue); } else { EditorGUILayout.PropertyField(prop, new GUIContent(prop.displayName)); } } EditorGUI.EndDisabledGroup(); if (EditorGUI.EndChangeCheck() && field.onValueChaged != null) { field.onValueChaged.Invoke(t, new object[] { prop }); } foreach (var uiAttr in uiAttrs) { uiAttr.AfterGUI(prop); } }