/// <summary> /// Removes a binding from a ReorderableList. /// </summary> /// <param name="list"></param> /// <param name="minfo"></param> public void RemoveBinding(ReorderableList list, MemberInfo minfo, MemberType type) { if (list.count <= 0) { return; } //Find the highest index of a binding with the correct MemberType. EZConsoleBindingEditor lastebind = null; for (int i = list.list.Count - 1; i >= 0; i--) { EZConsoleBindingEditor ebind = (EZConsoleBindingEditor)list.list[i]; MemberType ebindtype = (MemberType)ebind.SerialBinding.FindPropertyRelative("TargetType").intValue; if (ebindtype == type) { lastebind = ebind; break; } } if (lastebind == null) { return; } //EZConsoleBindingEditor lastebind = (EZConsoleBindingEditor)list.list[list.list.Count - 1]; //Find the index of lastebind's serializedproperty in _bindingsSerial bool foundit = false; //For error reporting for (int i = 0; i < _bindingsSerial.arraySize; i++) { //Make sure the properties are identical. (We can't compare them directly, it'll always fail.) SerializedProperty proptodelete = _bindingsSerial.GetArrayElementAtIndex(i); if (proptodelete.FindPropertyRelative("TargetMethodName").stringValue == minfo.Name && proptodelete.FindPropertyRelative("ControlObject").objectReferenceValue == lastebind.ControlObject && proptodelete.FindPropertyRelative("ControlComponent").objectReferenceValue == lastebind.ControlComponent && proptodelete.FindPropertyRelative("ControlDelegateName").stringValue == lastebind.ControlDelegateName) { _bindingsSerial.DeleteArrayElementAtIndex(i); //This works because it doesn't get applied until next frame. foundit = true; break; } } if (foundit == false) //Report error if we couldn't find it. { Debug.LogError("Tried to delete " + lastebind.ControlDelegateName + " binding but couldn't find it in the serialized list."); } }
public void DrawElement(Rect rect, int index, bool isActive, bool isFocused, List <EZConsoleBindingEditor> bindingslist, Type deltype, MemberType type) { //The list will try to draw newly-deleted objects for one frame. Prevent errors from being thrown. if (_bindingsSerial.arraySize <= index || _bindingsSerial.GetArrayElementAtIndex(index) == null) { return; } //Setup editor binding and serialized properties EZConsoleBindingEditor ebind = bindingslist[index]; SerializedProperty targettype = ebind.SerialBinding.FindPropertyRelative("TargetType"); if ((MemberType)targettype.intValue != type) { return; } SerializedProperty controlobjectserial = ebind.SerialBinding.FindPropertyRelative("ControlObject"); SerializedProperty controlcomponentserial = ebind.SerialBinding.FindPropertyRelative("ControlComponent"); SerializedProperty delegatenameserial = ebind.SerialBinding.FindPropertyRelative("TargetDelegateName"); //Control object Rect controlobjectrect = new Rect(rect.x, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight); ebind.ControlObject = (GameObject)EditorGUI.ObjectField(controlobjectrect, ebind.ControlObject, typeof(UnityEngine.Object), true); controlobjectserial.objectReferenceValue = ebind.ControlObject; #region Component/Delegate Drop-Down //Drop-down button for choosing the specific component/delegate. Similar to assigning events to Unity's Button UI. EditorGUI.BeginDisabledGroup(ebind.ControlObject == null); //Set the name you see on the non-expanded version be either the target delegate name or "No Function." string dropdowntext; if (ebind.ControlDelegateName == "" || ebind.ControlDelegateName == null) { dropdowntext = "No Function"; } else { dropdowntext = ebind.ControlDelegateName; } GUIContent dropdowncontent = new GUIContent(dropdowntext); Rect dropdownrect = new Rect(rect.x + rect.width / 2, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight); if (EditorGUI.DropdownButton(dropdownrect, dropdowncontent, FocusType.Keyboard)) { GenericMenu menu = new GenericMenu(); //Add the "No Component" option MenuSelectComponent emptymsc = new MenuSelectComponent() { binding = ebind, bindobject = ebind.ControlObject, component = null, delegatename = null }; menu.AddItem(new GUIContent("None"), ebind.ControlComponent == null, SelectFunction, emptymsc); menu.AddSeparator(""); //List all components Component[] components = ebind.ControlObject.GetComponents <Component>(); for (int j = 0; j < components.Length; j++) { //List all delegates in the control, whihc includes actions, functions, etc. List <FieldInfo> fieldinfos = components[j].GetType().GetFields(BindingFlags.Public | BindingFlags.Instance) .Where(t => t.FieldType.IsAssignableFrom(deltype)) .ToList(); foreach (FieldInfo finfo in fieldinfos) { MenuSelectComponent msc = new MenuSelectComponent() { binding = ebind, component = components[j], bindobject = ebind.ControlObject, delegatename = finfo.Name }; string path = components[j].GetType().Name + "/" + finfo.Name; menu.AddItem(new GUIContent(path), ebind.ControlComponent == components[j], SelectFunction, msc); } } menu.ShowAsContext(); } EditorGUI.EndDisabledGroup(); #endregion }
public override void OnInspectorGUI() { //DrawDefaultInspector(); GUILayoutOption[] layoutoptions = new GUILayoutOption[2] { GUILayout.MaxWidth(EditorGUIUtility.currentViewWidth / 2), GUILayout.MaxHeight(EditorGUIUtility.singleLineHeight) }; #region Target Script Labels/Fields //Target monoscript (compile-time script) object field //Don't allow changing the target monoscript during runtime EditorGUI.BeginDisabledGroup(Application.isPlaying); EditorGUILayout.BeginHorizontal(); GUILayout.Label("Target Monoscript: ", EditorStyles.boldLabel, layoutoptions); EditorGUILayout.ObjectField(_compileScriptSerial, typeof(MonoScript), GUIContent.none, layoutoptions); _consoleSerial.ApplyModifiedProperties(); EditorGUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); //Target component (runtime script) object field if (_script != null) { EditorGUILayout.BeginHorizontal(); GUILayout.Label("Runtime Component: ", EditorStyles.boldLabel, layoutoptions); Component oldcomponent = _console.RuntimeScript; //Cache this so we can run ChangeRuntimeScript if needed EditorGUILayout.ObjectField(_runtimeScriptSerial, _scriptType, GUIContent.none, layoutoptions); _consoleSerial.ApplyModifiedProperties(); if (Application.isPlaying && oldcomponent != _console.RuntimeScript) { _console.ChangeRuntimeScript(oldcomponent, _console.RuntimeScript); } EditorGUILayout.EndHorizontal(); } #endregion EditorGUILayout.Separator(); GUILayout.Label("Bindable Methods: ", EditorStyles.boldLabel, layoutoptions); //Create a list for each target if (_script != null) { //Make the dictionary of editorbindings, which represents the lists only in the editor. Dictionary <MemberInfo, List <EZConsoleBindingEditor> > editorbindings = new Dictionary <MemberInfo, List <EZConsoleBindingEditor> >(); #region Populate Editor Bindings Dictionary //Get list of methods using reflection. //Yes, it's calling this reflection every frame, but it's ONE inspector window at a time so you'd need like 100k+ methods to notice it. //Alternatives would offer little performance gain in exchange for lots of script complexity. MethodInfo[] methods = _scriptType.GetMethods(BindingFlags.Instance | BindingFlags.Public) .Where(x => !x.DeclaringType.IsAssignableFrom(typeof(MonoBehaviour))) .ToArray(); FieldInfo[] fields = _scriptType.GetFields(BindingFlags.Instance | BindingFlags.Public) .Where(x => !x.DeclaringType.IsAssignableFrom(typeof(MonoBehaviour))) .ToArray(); //Make lists for methods and fields for (int i = 0; i < methods.Length; i++) { editorbindings.Add(methods[i], new List <EZConsoleBindingEditor>()); } for (int i = 0; i < fields.Length; i++) { editorbindings.Add(fields[i], new List <EZConsoleBindingEditor>()); } //Iterate through the serialized bindings list and make an EZConsoleBindingEditor item for each one. for (int i = 0; i < _bindingsSerial.arraySize; i++) { SerializedProperty sprop = _bindingsSerial.GetArrayElementAtIndex(i); string methodname = sprop.FindPropertyRelative("TargetMethodName").stringValue; MemberInfo minfokey = editorbindings.Keys.FirstOrDefault(m => m.Name == methodname); //MemberType type = (MemberType) Enum.Parse(typeof(MemberType), sprop.FindPropertyRelative("TargetType").stringValue); if (minfokey != null) //Make sure we have a list available. { //We do. Make a new editor binding and add it. EZConsoleBindingEditor ebind = new EZConsoleBindingEditor() { ControlComponent = (Component)sprop.FindPropertyRelative("ControlComponent").objectReferenceValue, ControlObject = (GameObject)sprop.FindPropertyRelative("ControlObject").objectReferenceValue, ControlDelegateName = sprop.FindPropertyRelative("ControlDelegateName").stringValue, SerialBinding = sprop }; editorbindings[minfokey].Add(ebind); } else { Debug.Log("Couldn't find a method in the list called " + methodname); } } #endregion #region Draw ReorderableLists //Now we've got a dictionary of editor bindings, and each binding points to a serialized property. Time to draw the lists. foreach (MemberInfo minfo in editorbindings.Keys) { Type returntype; Type[] paramtypes; if (minfo.MemberType == MemberTypes.Method) { MethodInfo methinfo = (MethodInfo)minfo; //Find the parameters and return type of the target method returntype = methinfo.ReturnType; ParameterInfo[] paraminfos = methinfo.GetParameters(); //Make an array of types that include parameter types, with one at the end that represents the return type. //This will get used to find the delegate type we'll need, and follows the format required by Func. paramtypes = new Type[paraminfos.Length]; for (int p = 0; p < paraminfos.Length; p++) { paramtypes[p] = paraminfos[p].ParameterType; } //Get the type of delegate that we'll need to assign to this. Type deltype; if (returntype == typeof(void)) //Action type { deltype = Expression.GetActionType(paramtypes); } else //Func type { Array.Resize(ref paramtypes, paramtypes.Length + 1); paramtypes[paramtypes.Length - 1] = returntype; deltype = Expression.GetFuncType(paramtypes); } //Make the list ReorderableList reorderablelist = new ReorderableList(editorbindings[minfo], typeof(EZConsoleBindingEditor), true, true, true, true); //Now we assign to all the ReorderableList's callbacks that we need. Unity handles when they're fired. reorderablelist.drawHeaderCallback = (Rect rect) => DrawHeader(rect, minfo.Name, deltype, returntype, paramtypes); //Add label to the header reorderablelist.onCanRemoveCallback = (list) => { return(list.list.Count > 0); }; //We can always press the remove button for now because it's glitchy as hell in Unity. reorderablelist.onAddCallback = (list) => AddBinding(list, minfo, MemberType.Method); //Add a new binding. reorderablelist.onRemoveCallback = (list) => RemoveBinding(list, minfo, MemberType.Method); //Removes the last binding on the list. reorderablelist.drawElementCallback = (rect, index, isActive, isFocused) => DrawElement(rect, index, isActive, isFocused, editorbindings[minfo], deltype, MemberType.Method); //Draw the entire list. reorderablelist.index = reorderablelist.count - 1; //We need this for the removal button to work, because the index isn't set in any sane or observable way in Unity. reorderablelist.DoLayoutList(); //Display the list. } else if (minfo.MemberType == MemberTypes.Field) { FieldInfo fieldinfo = (FieldInfo)minfo; returntype = fieldinfo.FieldType; paramtypes = new Type[1] { fieldinfo.FieldType }; //Works for both func and action! So it can be reused for both the "getter" and "setter." //Make a list of "getters" for the field. ReorderableList getreorderablelist = new ReorderableList(editorbindings[minfo], typeof(EZConsoleBindingEditor), true, true, true, true); //Add callbacks. When asked for MemberType, specify GetField. //Type[] funcparams = new Type[1] { returntype }; //The params for a Func that returns returntype but has no actual params. getreorderablelist.drawHeaderCallback = (Rect rect) => DrawHeader(rect, "get_" + minfo.Name, Expression.GetFuncType(paramtypes), returntype, paramtypes); getreorderablelist.onCanRemoveCallback = (list) => { return(true); }; //We can always press the remove button for now because it's glitchy as hell in Unity. getreorderablelist.onAddCallback = (list) => AddBinding(list, minfo, MemberType.GetField); getreorderablelist.onRemoveCallback = (list) => RemoveBinding(list, minfo, MemberType.GetField); getreorderablelist.drawElementCallback = (rect, index, isActive, isFocused) => DrawElement(rect, index, isActive, isFocused, editorbindings[minfo], Expression.GetFuncType(paramtypes), MemberType.GetField); getreorderablelist.index = getreorderablelist.count - 1; getreorderablelist.DoLayoutList(); //Make the "setters" list for the field. ReorderableList setreorderablelist = new ReorderableList(editorbindings[minfo], typeof(EZConsoleBindingEditor), true, true, true, true); //Add callbacks. When asked for MemberType, specify SetField. setreorderablelist.drawHeaderCallback = (Rect rect) => DrawHeader(rect, "set_" + minfo.Name, Expression.GetActionType(paramtypes), typeof(void), paramtypes); setreorderablelist.onCanRemoveCallback = (list) => { return(true); }; //We can always press the remove button for now because it's glitchy as hell in Unity. setreorderablelist.onAddCallback = (list) => AddBinding(list, minfo, MemberType.SetField); setreorderablelist.onRemoveCallback = (list) => RemoveBinding(list, minfo, MemberType.SetField); setreorderablelist.drawElementCallback = (rect, index, isActive, isFocused) => DrawElement(rect, index, isActive, isFocused, editorbindings[minfo], Expression.GetActionType(paramtypes), MemberType.SetField); setreorderablelist.index = getreorderablelist.count - 1; setreorderablelist.DoLayoutList(); } else { returntype = null; paramtypes = null; Debug.LogError("Console created memberinfo that's not a MethodInfo or FieldInfo"); } } #endregion _consoleSerial.ApplyModifiedProperties(); //Updates the SerializedObject that holds all the properties - they don't actually change until you do this. } }