Exemplo n.º 1
0
    /// <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.");
        }
    }
Exemplo n.º 2
0
    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
    }
Exemplo n.º 3
0
    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.
        }
    }