public void DrawMethods(Type componentType, object component, string searchTerm, MethodInfo[] methods) { GUIStyle labelStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleRight }; GUIStyle normalButtonStyle = new GUIStyle(GUI.skin.button) { alignment = TextAnchor.MiddleLeft }; normalButtonStyle.padding = normalButtonStyle.padding.SetLeft(100); List <MethodSetup> expandedMethods = SidekickWindow.Current.PersistentData.ExpandedMethods; GUIStyle expandButtonStyle = new GUIStyle(GUI.skin.button); RectOffset padding = expandButtonStyle.padding; padding.left = 0; padding.right = 1; expandButtonStyle.padding = padding; foreach (MethodInfo method in methods) { if (!SearchMatches(searchTerm, method.Name)) { // Does not match search term, skip it continue; } // object[] customAttributes = method.GetCustomAttributes(false); EditorGUILayout.BeginHorizontal(); ParameterInfo[] parameters = method.GetParameters(); if (method.ReturnType == typeof(void)) { labelStyle.normal.textColor = Color.grey; } else if (method.ReturnType.IsValueType) { labelStyle.normal.textColor = new Color(0, 0, 1); } else { labelStyle.normal.textColor = new Color32(255, 130, 0, 255); } labelStyle.fontSize = 10; var genericArguments = method.GetGenericArguments(); GUIContent buttonLabel = new GUIContent("", "Click to fire with defaults"); if (genericArguments.Length != 0) { string genericArgumentsDisplay = string.Join(", ", genericArguments.Select(item => item.Name)); buttonLabel.text = $"{method.Name} <{genericArgumentsDisplay}> {parameters.Length}"; } else { buttonLabel.text = $"{method.Name} {parameters.Length}"; } using (new EditorGUI.DisabledScope(method.IsGenericMethod)) { bool buttonClicked = GUILayout.Button(buttonLabel, normalButtonStyle); Rect lastRect = GUILayoutUtility.GetLastRect(); lastRect.xMax = normalButtonStyle.padding.left; GUI.Label(lastRect, TypeUtility.NameForType(method.ReturnType), labelStyle); if (buttonClicked) { object[] arguments = null; if (parameters.Length > 0) { arguments = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { arguments[i] = TypeUtility.GetDefaultValue(parameters[i].ParameterType); } } var output = FireMethod(method, component, arguments, null); outputObjects.AddRange(output); opacity = 1f; } } if (parameters.Length > 0 || genericArguments.Length > 0) { string methodIdentifier = TypeUtility.GetMethodIdentifier(method); bool wasExpanded = expandedMethods.Any(item => item.MethodIdentifier == methodIdentifier); string label = wasExpanded ? "▲" : "▼"; bool expanded = GUILayout.Toggle(wasExpanded, label, expandButtonStyle, GUILayout.Width(20)); if (expanded != wasExpanded) { if (expanded) { MethodSetup methodSetup = new MethodSetup() { MethodIdentifier = methodIdentifier, Values = new object[parameters.Length], GenericArguments = new Type[genericArguments.Length], }; expandedMethods.Add(methodSetup); } else { expandedMethods.RemoveAll(item => item.MethodIdentifier == methodIdentifier); } } EditorGUILayout.EndHorizontal(); if (expanded) { MethodSetup methodSetup = expandedMethods.FirstOrDefault(item => item.MethodIdentifier == methodIdentifier); if (methodSetup.Values.Length != parameters.Length) { methodSetup.Values = new object[parameters.Length]; } EditorGUI.indentLevel++; for (var i = 0; i < genericArguments.Length; i++) { Type genericArgument = genericArguments[i]; string displayLabel = genericArgument.Name; Type[] constraints = genericArgument.GetGenericParameterConstraints(); if (constraints.Length != 0) { displayLabel += $" ({string.Join(", ", constraints.Select(item => item.Name))})"; } EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(displayLabel, TypeUtility.NameForType(methodSetup.GenericArguments[i])); var popupRect = GUILayoutUtility.GetLastRect(); popupRect.width = EditorGUIUtility.currentViewWidth; var selectTypeButtonLabel = new GUIContent("Select"); if (GUILayout.Button(selectTypeButtonLabel, EditorStyles.miniButton)) { int index = i; TypeSelectDropdown dropdown = new TypeSelectDropdown(new AdvancedDropdownState(), type => methodSetup.GenericArguments[index] = type, constraints); dropdown.Show(popupRect); } EditorGUILayout.EndHorizontal(); } for (int i = 0; i < parameters.Length; i++) { int index = i; VariablePane.DrawVariable(parameters[i].ParameterType, parameters[i].Name, methodSetup.Values[i], "", VariablePane.VariableAttributes.None, null, false, null, newValue => { methodSetup.Values[index] = newValue; }); } EditorGUI.indentLevel--; EditorGUILayout.BeginHorizontal(); GUILayout.Space(30); bool anyGenericArgumentsMissing = methodSetup.GenericArguments.Any(item => item == null); using (new EditorGUI.DisabledScope(anyGenericArgumentsMissing)) { if (GUILayout.Button("Fire")) { var output = FireMethod(method, component, methodSetup.Values, methodSetup.GenericArguments); outputObjects.AddRange(output); opacity = 1f; } } EditorGUILayout.EndHorizontal(); GUILayout.Space(20); } } else { EditorGUILayout.EndHorizontal(); } } }
private static void DrawIndividualVariable(GUIContent label, Type fieldType, object fieldValue, IEnumerable <Attribute> customAttributes, out bool handled, Action <object> changeCallback) { EditorGUI.BeginChangeCheck(); handled = true; object newValue; RangeAttribute rangeAttribute = null; if (customAttributes != null) { foreach (var customAttribute in customAttributes) { if (customAttribute is RangeAttribute attribute) { rangeAttribute = attribute; } } } if (fieldType == typeof(int)) { if (SidekickSettings.PreferUnityAttributes && rangeAttribute != null) { newValue = EditorGUILayout.IntSlider(label, (int)fieldValue, (int)rangeAttribute.min, (int)rangeAttribute.max); } else { newValue = EditorGUILayout.IntField(label, (int)fieldValue); } } else if (fieldType == typeof(uint)) { long newLong = EditorGUILayout.LongField(label, (uint)fieldValue); // Replicate Unity's built in behaviour newValue = (uint)Mathf.Clamp(newLong, uint.MinValue, uint.MaxValue); } else if (fieldType == typeof(long)) { newValue = EditorGUILayout.LongField(label, (long)fieldValue); } else if (fieldType == typeof(ulong)) { // Note that Unity doesn't have a built in way to handle larger values than long.MaxValue (its inspector // doesn't work correctly with ulong in fact), so display it as a validated text field string newString = EditorGUILayout.TextField(label, ((ulong)fieldValue).ToString()); if (ulong.TryParse(newString, out ulong newULong)) { newValue = newULong; } else { newValue = fieldValue; } } else if (fieldType == typeof(byte)) { int newInt = EditorGUILayout.IntField(label, (byte)fieldValue); // Replicate Unity's built in behaviour newValue = (byte)Mathf.Clamp(newInt, byte.MinValue, byte.MaxValue); } else if (fieldType == typeof(sbyte)) { int newInt = EditorGUILayout.IntField(label, (sbyte)fieldValue); // Replicate Unity's built in behaviour newValue = (sbyte)Mathf.Clamp(newInt, sbyte.MinValue, sbyte.MaxValue); } else if (fieldType == typeof(ushort)) { int newInt = EditorGUILayout.IntField(label, (ushort)fieldValue); // Replicate Unity's built in behaviour newValue = (ushort)Mathf.Clamp(newInt, ushort.MinValue, ushort.MaxValue); } else if (fieldType == typeof(short)) { int newInt = EditorGUILayout.IntField(label, (short)fieldValue); // Replicate Unity's built in behaviour newValue = (short)Mathf.Clamp(newInt, short.MinValue, short.MaxValue); } else if (fieldType == typeof(string)) { newValue = EditorGUILayout.TextField(label, (string)fieldValue); } else if (fieldType == typeof(char)) { string newString = EditorGUILayout.TextField(label, new string((char)fieldValue, 1)); // Replicate Unity's built in behaviour if (newString.Length == 1) { newValue = newString[0]; } else { newValue = fieldValue; } } else if (fieldType == typeof(float)) { if (SidekickSettings.PreferUnityAttributes && rangeAttribute != null) { newValue = EditorGUILayout.Slider(label, (float)fieldValue, rangeAttribute.min, rangeAttribute.max); } else { newValue = EditorGUILayout.FloatField(label, (float)fieldValue); } } else if (fieldType == typeof(double)) { newValue = EditorGUILayout.DoubleField(label, (double)fieldValue); } else if (fieldType == typeof(bool)) { newValue = EditorGUILayout.Toggle(label, (bool)fieldValue); } else if (fieldType == typeof(Vector2)) { newValue = EditorGUILayout.Vector2Field(label, (Vector2)fieldValue); } else if (fieldType == typeof(Vector3)) { newValue = EditorGUILayout.Vector3Field(label, (Vector3)fieldValue); } else if (fieldType == typeof(Vector4)) { newValue = EditorGUILayout.Vector4Field(label, (Vector4)fieldValue); } else if (fieldType == typeof(Vector2Int)) { newValue = EditorGUILayout.Vector2IntField(label, (Vector2Int)fieldValue); } else if (fieldType == typeof(Vector3Int)) { newValue = EditorGUILayout.Vector3IntField(label, (Vector3Int)fieldValue); } else if (fieldType == typeof(Quaternion)) { Quaternion quaternion = (Quaternion)fieldValue; Vector4 vector = new Vector4(quaternion.x, quaternion.y, quaternion.z, quaternion.w); vector = EditorGUILayout.Vector4Field(label, vector); newValue = new Quaternion(vector.x, vector.y, vector.z, vector.z); } #if UNITY_MATH_EXISTS else if (fieldType == typeof(float2)) { newValue = (float2)EditorGUILayout.Vector2Field(label, (float2)fieldValue); } else if (fieldType == typeof(float3)) { newValue = (float3)EditorGUILayout.Vector3Field(label, (float3)fieldValue); } else if (fieldType == typeof(float4)) { newValue = (float4)EditorGUILayout.Vector4Field(label, (float4)fieldValue); } #endif else if (fieldType == typeof(Bounds)) { newValue = EditorGUILayout.BoundsField(label, (Bounds)fieldValue); } else if (fieldType == typeof(BoundsInt)) { newValue = EditorGUILayout.BoundsIntField(label, (BoundsInt)fieldValue); } else if (fieldType == typeof(Color)) { newValue = EditorGUILayout.ColorField(label, (Color)fieldValue); } else if (fieldType == typeof(Color32)) { newValue = (Color32)EditorGUILayout.ColorField(label, (Color32)fieldValue); } else if (fieldType == typeof(Gradient)) { newValue = EditorGUILayout.GradientField(new GUIContent(label), (Gradient)fieldValue); } else if (fieldType == typeof(AnimationCurve)) { newValue = EditorGUILayout.CurveField(label, (AnimationCurve)fieldValue); } else if (fieldType.IsSubclassOf(typeof(Enum))) { newValue = EditorGUILayout.EnumPopup(label, (Enum)fieldValue); Type underlyingType = Enum.GetUnderlyingType(fieldValue.GetType()); // Cast from the enum to the underlying type (e.g. byte) then to int object cast = Convert.ChangeType(newValue, underlyingType); cast = Convert.ChangeType(cast, typeof(int)); // Allow them to edit as an int then cast back newValue = Convert.ChangeType(EditorGUILayout.IntField((int)cast), underlyingType); } else if (fieldType == typeof(Rect)) { newValue = EditorGUILayout.RectField(label, (Rect)fieldValue); } else if (fieldType == typeof(RectInt)) { newValue = EditorGUILayout.RectIntField(label, (RectInt)fieldValue); } else if (fieldType.IsSubclassOf(typeof(UnityEngine.Object))) { newValue = EditorGUILayout.ObjectField(label, (UnityEngine.Object)fieldValue, fieldType, true); } else if (fieldType == typeof(Type)) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(label, new GUIContent(TypeUtility.NameForType((Type)fieldValue))); var popupRect = GUILayoutUtility.GetLastRect(); popupRect.width = EditorGUIUtility.currentViewWidth; var selectTypeButtonLabel = new GUIContent("Select"); if (GUILayout.Button(selectTypeButtonLabel, EditorStyles.miniButton)) { TypeSelectDropdown dropdown = new TypeSelectDropdown(new AdvancedDropdownState(), type => { // Async apply changeCallback?.Invoke(type); }); dropdown.Show(popupRect); } EditorGUILayout.EndHorizontal(); newValue = fieldValue; } else { handled = false; newValue = fieldValue; } if (EditorGUI.EndChangeCheck()) { changeCallback?.Invoke(newValue); } }
void OnGUI() { // Flexible width for the label based on overall width EditorGUIUtility.labelWidth = Mathf.Round(EditorGUIUtility.currentViewWidth * 0.4f); // Use inline controls if there is enough horizontal room EditorGUIUtility.wideMode = EditorGUIUtility.currentViewWidth > 400; // Frame rate tracking if (Event.current.type == EventType.Repaint) { AnimationHelper.UpdateTime(); } current = this; CleanStacks(); DrawToolbar(); Type[] inspectedTypes = null; object[] inspectedContexts = null; ECSContext[] inspectedECSContexts = null; GUILayout.Space(9); string buttonPrefix = ""; #if ECS_EXISTS int selectionWrapWidth = 465; #else int selectionWrapWidth = 400; #endif if (EditorGUIUtility.currentViewWidth > selectionWrapWidth) { EditorGUILayout.BeginHorizontal(); GUILayout.Label("Selection Helpers"); } else { buttonPrefix = "Select "; } var popupRect = GUILayoutUtility.GetLastRect(); popupRect.width = EditorGUIUtility.currentViewWidth; if (GUILayout.Button(new GUIContent(buttonPrefix + "Type From Assembly"), EditorStyles.miniButton)) { TypeSelectDropdown dropdown = new TypeSelectDropdown(new AdvancedDropdownState(), SetSelection); dropdown.Show(popupRect); } if (GUILayout.Button(new GUIContent(buttonPrefix + "Loaded Unity Object"), EditorStyles.miniButton)) { UnityObjectSelectDropdown dropdown = new UnityObjectSelectDropdown(new AdvancedDropdownState(), SetSelection); dropdown.Show(popupRect); } #if ECS_EXISTS if (GUILayout.Button(new GUIContent(buttonPrefix + "ECS System"), EditorStyles.miniButton)) { ECSSystemSelectDropdown dropdown = new ECSSystemSelectDropdown(new AdvancedDropdownState(), SetSelection); dropdown.Show(popupRect); } #endif if (EditorGUIUtility.currentViewWidth > selectionWrapWidth) { EditorGUILayout.EndHorizontal(); } if (activeSelection.IsEmpty) { GUILayout.FlexibleSpace(); GUIStyle style = new GUIStyle(EditorStyles.wordWrappedLabel) { alignment = TextAnchor.MiddleCenter }; GUILayout.Label("No object selected.\n\nSelect something in Unity or use one of the selection helper buttons.", style); GUILayout.FlexibleSpace(); return; } if (activeSelection.Object != null) { if (activeSelection.Object is GameObject selectedGameObject) { List <object> components = selectedGameObject.GetComponents <Component>().Cast <object>().ToList(); components.RemoveAll(item => item == null); components.Insert(0, selectedGameObject); inspectedContexts = components.ToArray(); } #if ECS_EXISTS else if (activeSelection.Object is EntitySelectionProxy entitySelectionProxy) { EntityManager currentEntityManager = entitySelectionProxy.World.EntityManager; string name = currentEntityManager.GetName(entitySelectionProxy.Entity); if (string.IsNullOrEmpty(name)) { name = "Entity " + entitySelectionProxy.Entity.Index; } inspectedContexts = new object [1 + currentEntityManager.GetComponentCount(entitySelectionProxy.Entity)]; inspectedContexts[0] = activeSelection.Object; inspectedECSContexts = new ECSContext[1 + currentEntityManager.GetComponentCount(entitySelectionProxy.Entity)]; inspectedECSContexts[0] = new ECSContext { EntityManager = currentEntityManager, Entity = entitySelectionProxy.Entity }; NativeArray <ComponentType> types = currentEntityManager.GetComponentTypes(entitySelectionProxy.Entity); for (var index = 0; index < types.Length; index++) { object componentData = ECSAccess.GetComponentData(currentEntityManager, entitySelectionProxy.Entity, types[index]); inspectedContexts[1 + index] = componentData; inspectedECSContexts[1 + index] = new ECSContext { EntityManager = currentEntityManager, Entity = entitySelectionProxy.Entity, ComponentType = types[index] }; } types.Dispose(); } #endif else { inspectedContexts = new[] { activeSelection.Object }; } inspectedTypes = inspectedContexts.Select(x => x.GetType()).ToArray(); } else { inspectedTypes = new[] { activeSelection.Type }; inspectedContexts = new Type[] { null }; } if (inspectedECSContexts == null) { inspectedECSContexts = new ECSContext[inspectedContexts.Length]; } GUILayout.Space(5); searchTerm = searchField.OnToolbarGUI(searchTerm); SidekickEditorGUI.BeginLabelHighlight(searchTerm); mode = SidekickUtility.EnumToolbar(mode); GUILayout.Space(5); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); for (int i = 0; i < inspectedTypes.Length; i++) { Type type = inspectedTypes[i]; var inspectedContext = inspectedContexts[i]; var inspectedECSContext = inspectedECSContexts[i]; bool?activeOrEnabled = inspectedContext switch { GameObject gameObject => gameObject.activeSelf, Behaviour behaviour => behaviour.enabled, _ => null }; if (typesHidden.All(row => row.Key != type)) { typesHidden.Add(new KeyValuePair <Type, bool>(type, false)); } int index = typesHidden.FindIndex(row => row.Key == type); string name; if (inspectedContexts[0] != null) { if (activeOrEnabled.HasValue) { name = " " + type.Name; } else { name = " " + type.Name; } if (i == 0 && inspectedContexts[i] is Object unityObject) { name += $" ({unityObject.name})"; } } else { name = type.Name + " (Class)"; } GUIContent content = new GUIContent(name, $"{type.FullName}, {type.Assembly.FullName}"); Rect foldoutRect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.foldoutHeader); Rect toggleRect = foldoutRect; toggleRect.xMin += 36; toggleRect.width = 20; Rect iconRect = foldoutRect; iconRect.xMin += 16; iconRect.yMin += 1; iconRect.height = iconRect.width = 16; // Have to do this before BeginFoldoutHeaderGroup otherwise it'll consume the mouse down event if (activeOrEnabled.HasValue && SidekickEditorGUI.DetectClickInRect(toggleRect)) { switch (inspectedContexts[i]) { case GameObject gameObject: gameObject.SetActive(!gameObject.activeSelf); break; case Behaviour behaviour: behaviour.enabled = !behaviour.enabled; break; } } bool foldout = EditorGUI.BeginFoldoutHeaderGroup(foldoutRect, !typesHidden[index].Value, content, EditorStyles.foldoutHeader, rect => ClassUtilities.GetMenu(inspectedContext, inspectedECSContext).DropDown(rect)); Texture icon = SidekickEditorGUI.GetIcon(inspectedContexts[i], type); if (icon != null) { GUI.DrawTexture(iconRect, icon); } // Right click context menu if (SidekickEditorGUI.DetectClickInRect(foldoutRect, 1)) { ClassUtilities.GetMenu(inspectedContext, inspectedECSContext).DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } if (activeOrEnabled.HasValue) { EditorGUI.Toggle(toggleRect, activeOrEnabled.Value); } EditorGUILayout.EndFoldoutHeaderGroup(); typesHidden[index] = new KeyValuePair <Type, bool>(type, !foldout); if (!typesHidden[index].Value) { SidekickEditorGUI.DrawSplitter(0.5f); EditorGUI.indentLevel++; BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly; if (inspectedContext != null) // Is this an object instance? { bindingFlags |= BindingFlags.Instance; } var typeScope = type; while (typeScope != null) { if (InspectionExclusions.GetExcludedTypes().Contains(typeScope)) { break; } if (typeScope != type) { SidekickEditorGUI.DrawTypeChainHeader(new GUIContent(": " + typeScope.Name)); } FieldInfo[] fields = typeScope.GetFields(bindingFlags); PropertyInfo[] properties = typeScope.GetProperties(bindingFlags); MethodInfo[] methods = typeScope.GetMethods(bindingFlags); // Hide methods and backing fields that have been generated for properties if (SidekickSettings.HideAutoGenerated) { List <MethodInfo> methodList = new List <MethodInfo>(methods.Length); foreach (MethodInfo method in methods) { if (!TypeUtility.IsPropertyMethod(method, typeScope)) { methodList.Add(method); } } methods = methodList.ToArray(); List <FieldInfo> fieldList = new List <FieldInfo>(fields.Length); for (int j = 0; j < fields.Length; j++) { if (!TypeUtility.IsBackingField(fields[j], typeScope)) { fieldList.Add(fields[j]); } } fields = fieldList.ToArray(); } FieldInfo[] events = typeScope.GetFields(bindingFlags); if (mode == InspectorMode.Fields) { fieldPane.DrawFields(inspectedTypes[i], inspectedContexts[i], inspectedECSContext, searchTerm, fields); } else if (mode == InspectorMode.Properties) { propertyPane.DrawProperties(inspectedTypes[i], inspectedContexts[i], searchTerm, properties); } else if (mode == InspectorMode.Methods) { methodPane.DrawMethods(inspectedTypes[i], inspectedContexts[i], searchTerm, methods); } else if (mode == InspectorMode.Events) { eventPane.DrawEvents(inspectedTypes[i], inspectedContexts[i], searchTerm, events); } typeScope = typeScope.BaseType; } EditorGUI.indentLevel--; } SidekickEditorGUI.DrawSplitter(); } EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (inspectedTypes[0] == typeof(GameObject) #if ECS_EXISTS || inspectedTypes[0] == typeof(EntitySelectionProxy) #endif ) { bool pressed = GUILayout.Button("Add Component", GUILayout.Width(230), GUILayout.Height(24)); var popupRect2 = GUILayoutUtility.GetLastRect(); popupRect2.width = EditorGUIUtility.currentViewWidth; if (pressed) { if (inspectedTypes[0] == typeof(GameObject)) { TypeSelectDropdown dropdown = new TypeSelectDropdown(new AdvancedDropdownState(), type => { ((GameObject)inspectedContexts[0]).AddComponent(type); }, new[] { typeof(Component) }); dropdown.Show(popupRect2); } #if ECS_EXISTS else if (inspectedTypes[0] == typeof(EntitySelectionProxy)) { TypeSelectDropdown dropdown = new TypeSelectDropdown(new AdvancedDropdownState(), type => { inspectedECSContexts[0].EntityManager.AddComponent(inspectedECSContexts[0].Entity, ComponentType.ReadWrite(type)); }, null, new [] { typeof(IComponentData) }); dropdown.Show(popupRect2); } #endif } } GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndScrollView(); if (mode == InspectorMode.Methods) { methodPane.PostDraw(); } //if(AnimationHelper.AnimationActive) { // Cause repaint on next frame Repaint(); if (Event.current.type == EventType.Repaint) { //AnimationHelper.ClearAnimationActive(); } } SidekickEditorGUI.EndLabelHighlight(); }