Ejemplo n.º 1
0
            //PUBLIC

            /// <summary>
            /// Find the height of a persistent callback object
            /// </summary>
            /// <param name="property">The property that is to have the height calculated for</param>
            /// <returns>Returns the height in pixels that should be reserved for this element</returns>
            public static float GetElementHeight(SerializedProperty property)
            {
                //Get the target objects of the property
                PersistentCallback[] callbacks;
                property.GetPropertyValues(out callbacks);

                //Check that there are values to check
                if (callbacks.Length == 0)
                {
                    return(0f);
                }

                //Get the height that is used for the object/method selection elements
                float height = EditorGUIUtility.singleLineHeight * 2 + (BUFFER_SPACE * 2f);

                //Get the method that is in use by the primary (first) callback
                PersistentMethod primary = PersistentOptionsUtility.GetPersistentMethodFromCallback(callbacks[0]);

                //Check if this method is the same across all selections
                bool isDifferent = false;

                for (int i = 1; i < callbacks.Length; i++)
                {
                    if (primary != PersistentOptionsUtility.GetPersistentMethodFromCallback(callbacks[i]))
                    {
                        isDifferent = true;
                        break;
                    }
                }

                //If the methods are the same then the parameters can be shown
                if (!isDifferent && !callbacks[0].IsDynamic && primary != null)
                {
                    //Calculate the height required for each drawer
                    int ind = 0;
                    foreach (var param in primary.Parameters)
                    {
                        //Get the drawer for the type
                        AParameterDrawer drawer = ParameterDrawers.GetDrawer(primary.ParameterSignature[ind], param.Attribute);

                        //Collate the parameter cache's that are needed for this callback
                        PersistentParameterCache[] current = new PersistentParameterCache[callbacks.Length];
                        for (int i = 0; i < callbacks.Length; i++)
                        {
                            current[i] = callbacks[i].ParameterInfo[ind].ParameterCache;
                        }

                        //Retrieve the height for the drawer
                        height += drawer.GetDrawerHeight(current, param.DisplayLabel) + BUFFER_SPACE;

                        //Increment the progress
                        ++ind;
                    }
                }

                //Return the final completed height
                return(height);
            }
        //PUBLIC

        /// <summary>
        /// Retrieve all usable methods that belong to the supplied object type
        /// </summary>
        /// <param name="obj">The object that is to have its methods returned</param>
        /// <returns>Returns an array of the objects that describe the usable methods of the object</returns>
        public static PersistentMethod[] RetrieveObjectMethods(UnityEngine.Object obj)
        {
            //Get the type of the object to process
            RaiseableType key = obj.GetType();

            //Check if the key has already been cached
            if (!objectMethods.ContainsKey(key))
            {
                ProcessObjectType(key);
            }

            //Make a copy buffer for the methods that are retrieved
            PersistentMethod[] buffer = new PersistentMethod[objectMethods[key].Count];
            objectMethods[key].Values.CopyTo(buffer, 0);

            //Return the identified methods
            return(buffer);
        }
Ejemplo n.º 3
0
            /// <summary>
            /// Assign the supplied persistent method to the supplied callback object
            /// </summary>
            /// <param name="callbacks">The persistent callback objects that will be populated with the method</param>
            /// <param name="target">The object that is being targetted by the method invocation</param>
            /// <param name="method">Information about the method that will be raised by the </param>
            /// <param name="isDynamic">Flags if this callback is dynamic, receiving values from called event</param>
            private static void AssignPersistentCallback(PersistentCallback[] callbacks, UnityEngine.Object target, PersistentMethod method, bool isDynamic)
            {
                //Loop through the callback objects and assign the method values
                for (int i = 0; i < callbacks.Length; i++)
                {
                    //Setup the default values for the parameters that can be raised
                    int ind = 0;
                    PersistentCallback.PersistentCallbackParameterInfo[] parameterInfos = new PersistentCallback.PersistentCallbackParameterInfo[method.Parameters.Length];
                    foreach (PersistentParameter param in method.Parameters)
                    {
                        //Store the default value that will be assigned to the parameter
                        object defaultValue = null;

                        //Check if there is a default value for this entry
                        if (param.DefaultValue is DBNull)
                        {
                            defaultValue = method.ParameterSignature[ind].GetDefaultValue();
                        }
                        else
                        {
                            defaultValue = param.DefaultValue;
                        }

                        //Assign the values to the parameter info object
                        parameterInfos[ind] = new PersistentCallback.PersistentCallbackParameterInfo();
                        parameterInfos[ind].ParameterType = method.ParameterSignature[ind];
                        parameterInfos[ind].ParameterCache.SetValue(defaultValue, method.ParameterSignature[ind]);

                        //Increase the progress
                        ++ind;
                    }


                    //Assign the new values
                    callbacks[i].Target        = target;
                    callbacks[i].MethodName    = method.MethodName;
                    callbacks[i].ParameterInfo = parameterInfos;
                    callbacks[i].IsDynamic     = isDynamic;
                }
            }
Ejemplo n.º 4
0
            /// <summary>
            /// Display the elements of the property within the designated area on the inspector area
            /// </summary>
            /// <param name="position">The position within the inspector that the property should be drawn to</param>
            /// <param name="property">The property that is to be displayed within the inspector</param>
            /// <param name="dynamicTypes">Defines the types that are designated as dynamic types for operation</param>
            /// <param name="forceDirty">An action that can be raised from the generic menus callback to force a dirty of the current elements</param>
            /// <returns>Returns true if an event occurred that caused changes that need to be saved</returns>
            public static bool DrawLayoutElements(Rect position, SerializedProperty property, Type[] dynamicTypes, Action forceDirty)
            {
                //Get the target objects of the property
                PersistentCallback[] callbacks;

                //Check that there are values to check
                if (!property.GetPropertyValues(out callbacks) || callbacks.Length == 0)
                {
                    return(false);
                }

                //Flag if the height of this object needs to be recalculated
                bool elementsModified = false;

                {
                    //Check if the event state is different for the contained callbacks
                    bool isDifferent = false;
                    for (int i = 1; i < callbacks.Length; i++)
                    {
                        if (callbacks[0].EventState != callbacks[i].EventState)
                        {
                            isDifferent = true;
                            break;
                        }
                    }

                    //Display an option to change the event state of the options
                    using (GUIMixer.PushSegment(isDifferent)) {
                        //Begin checking for UI changes
                        EditorGUI.BeginChangeCheck();

                        //Present the option for changing the event state
                        ERuntimeEventState newState = (ERuntimeEventState)EditorGUI.EnumPopup(GetLineOffsetPosition(position, 0, 1f), GUIContent.none, callbacks[0].EventState);

                        //If the state has changed, apply it to all of the callbacks
                        if (EditorGUI.EndChangeCheck())
                        {
                            for (int i = 0; i < callbacks.Length; i++)
                            {
                                callbacks[i].EventState = newState;
                            }
                        }
                    }
                }

                {
                    //Check if the target object is different for the contained callbacks
                    bool isDifferent = false;

                    //Check there are multiple objects to modify
                    if (callbacks.Length > 1)
                    {
                        //Get the target of the initial object
                        UnityEngine.Object baseTarget = GetBaseTarget(callbacks[0].Target);

                        //Compare the base target against the others
                        for (int i = 1; i < callbacks.Length; i++)
                        {
                            if (baseTarget != GetBaseTarget(callbacks[i].Target))
                            {
                                isDifferent = true;
                                break;
                            }
                        }
                    }

                    //Display a target field for the callback operations
                    using (GUIMixer.PushSegment(isDifferent)) {
                        //Begin checking for UI changes
                        EditorGUI.BeginChangeCheck();

                        //Present the option for changing the target object
                        UnityEngine.Object newTarget = EditorGUI.ObjectField(GetLineOffsetPosition(position, 1, .4f), GUIContent.none, callbacks[0].Target, typeof(UnityEngine.Object), true);

                        //If the target changed
                        if (EditorGUI.EndChangeCheck())
                        {
                            //Apply the new target to all contained elements
                            for (int i = 0; i < callbacks.Length; i++)
                            {
                                callbacks[i].Target = newTarget;
                            }

                            //That can cause massive changes
                            elementsModified = true;
                        }
                    }
                }

                //Store the persistent method that is assigned to the primary
                PersistentMethod primaryMethod = PersistentOptionsUtility.GetPersistentMethodFromCallback(callbacks[0]);

                //Store a flag that indicates if the callbacks have different methods
                bool methodsAreDifferent = false;

                //Allow for the modification of selected function if there is a target set
                using (GUILocker.PushSegment(callbacks[0].Target)) {
                    //Retrieve the Content that will be displayed for the function selection selected option
                    GUIContent selectedDisplay;

                    //If a method match could be found, use its display label
                    if (primaryMethod != null)
                    {
                        selectedDisplay = primaryMethod.DisplayLabel;
                    }

                    //Otherwise, method may be missing
                    else
                    {
                        //Try to retrieve the label for the callback
                        string generatedLabel = PersistentOptionsUtility.GetPersistentMethodLabelFromCallback(callbacks[0]);

                        //If the string is empty, no function has been set yet
                        selectedDisplay = (string.IsNullOrEmpty(generatedLabel) ?
                                           NO_SELECTION_LABEL :
                                           new GUIContent(generatedLabel + " " + MISSING_LABEL)
                                           );
                    }

                    //Check there are multiple callbacks to compare against
                    if (callbacks.Length > 1)
                    {
                        //Check if the selected callbacks for the other callbacks is different
                        for (int i = 1; i < callbacks.Length; i++)
                        {
                            if (primaryMethod != PersistentOptionsUtility.GetPersistentMethodFromCallback(callbacks[i]))
                            {
                                methodsAreDifferent = true;
                                break;
                            }
                        }
                    }

                    //Display the dropdown button for selecting the active callback
                    using (GUIMixer.PushSegment(methodsAreDifferent)) {
                        if (EditorGUI.DropdownButton(GetLineOffsetPosition(position, 1, .6f, .4f), selectedDisplay, FocusType.Passive))
                        {
                            //Retrieve the constructed menu
                            GenericMenu optionsMenu = CreateOptionsSelectionMenu(callbacks[0].Target, callbacks[0].IsValid, callbacks[0].IsDynamic, primaryMethod, dynamicTypes,
                                                                                 //Reset function
                                                                                 () => {
                                //Loop through and reset all of the callback methods
                                for (int i = 0; i < callbacks.Length; i++)
                                {
                                    callbacks[i].ResetMethod();
                                }

                                //Reset the primary method
                                primaryMethod       = null;
                                methodsAreDifferent = false;

                                //Values modified, force the calling object to re-calculate this element
                                forceDirty();
                            },
                                                                                 //Assign method function
                                                                                 (target, method, isDynamic) => {
                                //Apply the new method to the option
                                AssignPersistentCallback(callbacks, target, method, isDynamic);

                                //Methods are now the same
                                primaryMethod       = method;
                                methodsAreDifferent = false;

                                //Values modified, force the calling object to re-calculate this element
                                forceDirty();
                            });

                            //Display the various options
                            optionsMenu.DropDown(GetLineOffsetPosition(position, 1, .6f, .4f));
                        }
                    }
                }

                //If the primary method is consistent across the multiple objects, display the values
                if (!methodsAreDifferent && !callbacks[0].IsDynamic && primaryMethod != null)
                {
                    //Store the rect of the last point
                    Rect displayRect = GetLineOffsetPosition(position, 1, 1f);

                    //Loop through all of the parameters for this method
                    int ind = 0;
                    foreach (var param in primaryMethod.Parameters)
                    {
                        //Retrieve the drawer for this parameter
                        AParameterDrawer drawer = ParameterDrawers.GetDrawer(primaryMethod.ParameterSignature[ind], param.Attribute);

                        //Collate the parameter cache's that are needed for this callback
                        PersistentParameterCache[] current = new PersistentParameterCache[callbacks.Length];
                        for (int i = 0; i < callbacks.Length; i++)
                        {
                            current[i] = callbacks[i].ParameterInfo[ind].ParameterCache;
                        }

                        //Setup the display rect for displaying values correctly given the values
                        displayRect.y     += displayRect.height + BUFFER_SPACE;
                        displayRect.height = drawer.GetDrawerHeight(current, param.DisplayLabel);

                        //Draw the elements to the inspector
                        if (drawer.DisplayParameterValue(displayRect, current, param.DisplayLabel) && !elementsModified)
                        {
                            elementsModified = true;
                        }

                        //Increment the current progress through the parameters
                        ++ind;
                    }
                }

                //Return the re-calculate flag
                return(elementsModified);
            }
Ejemplo n.º 5
0
            /// <summary>
            /// Populate a Generic Menu with the available persistent methods on the supplied target
            /// </summary>
            /// <param name="target">The target object that should be scanned for raisable methods</param>
            /// <param name="currentIsValid">Flags if the current callback is valid</param>
            /// <param name="currentIsDynamic">Flags if the current persistent callback is a dynamic one</param>
            /// <param name="selectedMethod">The previous method that has been selected for use</param>
            /// <param name="dynamicTypes">The types that are able to be dynamic in their raising of the callback</param>
            /// <param name="resetCallback">A callback that is raised when the no function option is selected for callback(s)</param>
            /// <param name="assignCallback">The callback that will be used to assign the callback values to the callback object</param>
            /// <returns>Returns a Generic Menu that can be displayed to list the available options</returns>
            private static GenericMenu CreateOptionsSelectionMenu(UnityEngine.Object target, bool currentIsValid, bool currentIsDynamic, PersistentMethod selectedMethod, Type[] dynamicTypes, GenericMenu.MenuFunction resetCallback, AssignCallbackDel assignCallback)
            {
                //Create a generic menu that can be used to display possible options
                GenericMenu optionsMenu = new GenericMenu();

                #if UNITY_2018_2_OR_NEWER
                //Toggle the option to allow for the method to be raised on specific components if there are multiple of the same type
                optionsMenu.allowDuplicateNames = true;
                #endif

                //Store a list of the elements that are to have their values displayed
                List <UnityEngine.Object> searchTargets = new List <UnityEngine.Object>(1);

                //Store a reference to the Game Object to show options for
                GameObject searchObject = GetBaseTarget(target) as GameObject;

                //If there is a search object, use that
                if (searchObject != null)
                {
                    //Add the search object itself
                    searchTargets.Add(searchObject);

                    //Grab all of the components attached to the object
                    searchTargets.AddRange(searchObject.GetComponents <Component>());
                }

                //Otherwise, just use the target (Scriptable Assets etc.)
                else
                {
                    searchTargets.Add(target);
                }

                //Add a label for the target object
                optionsMenu.AddDisabledItem(new GUIContent(searchTargets[0].name + " "));
                optionsMenu.AddSeparator(string.Empty);

                //Add the clear option to the menu
                optionsMenu.AddItem(NO_SELECTION_LABEL, !currentIsValid, resetCallback);
                optionsMenu.AddSeparator(string.Empty);

                //Store the signature string for dynamic elements
                string dynamicSignature = (dynamicTypes.Length > 0 ? PersistentOptionsUtility.GenerateSignatureLabelString(dynamicTypes) : string.Empty);

                //Process all of the elements to be searched
                foreach (UnityEngine.Object search in searchTargets)
                {
                    //Retrieve the methods that can be used at this level
                    PersistentMethod[] methods = PersistentOptionsUtility.RetrieveObjectMethods(search);

                    //If there are no methods, skip
                    if (methods.Length == 0)
                    {
                        continue;
                    }

                    //Sort the methods based on their display labels
                    Array.Sort(methods, (left, right) => {
                        //If the property flag differs, sort based on that
                        if (left.IsProperty != right.IsProperty)
                        {
                            return(right.IsProperty.CompareTo(left.IsProperty));
                        }

                        //Otherwise, go alphabetical
                        return(left.DisplayLabel.text.CompareTo(right.DisplayLabel.text));
                    });

                    //Store the starting directory label that will be used for creating the sub directories of options
                    string labelPrefix = search.GetType().Name + "/";

                    //Use a string builder to construct the display names for the different method options
                    StringBuilder sb = new StringBuilder();

                    //Store the target that will be operated on if this option is selected
                    UnityEngine.Object objTarget = search;

                    //Loop through the options to add the dynamic methods
                    if (dynamicTypes.Length > 0)
                    {
                        //Flag if an option was found
                        bool foundOne = false;

                        //Check if any of the options can be used as dynamic types
                        for (int i = 0; i < methods.Length; i++)
                        {
                            //Check the parameter length is the same
                            if (methods[i].ParameterSignature.Length != dynamicTypes.Length)
                            {
                                continue;
                            }

                            //Flag if this is a valid candidate
                            bool valid = true;
                            for (int j = 0; j < dynamicTypes.Length; j++)
                            {
                                if (methods[i].ParameterSignature[j] != dynamicTypes[j])
                                {
                                    valid = false;
                                    break;
                                }
                            }

                            //If the method isn't valid, don't bother
                            if (!valid)
                            {
                                continue;
                            }

                            //Check if this is the first valid option found
                            if (!foundOne)
                            {
                                //Add the initial header element
                                optionsMenu.AddDisabledItem(new GUIContent(labelPrefix + "Dynamic Methods " + dynamicSignature));

                                //Flag one as found
                                foundOne = true;
                            }

                            //Store a collective index for the lambda
                            int ind = i;

                            //Add the option to the menu
                            optionsMenu.AddItem(
                                new GUIContent(
                                    labelPrefix + (methods[i].IsProperty ?
                                                   methods[i].MethodName.Substring(PersistentOptionsUtility.SETTER_PROPERTY_PREFIX.Length) :
                                                   methods[i].MethodName) +
                                    " ",
                                    methods[i].DisplayLabel.tooltip
                                    ),
                                currentIsDynamic && methods[i] == selectedMethod,
                                () => assignCallback(objTarget, methods[ind], true)
                                );
                        }

                        //If an option was found add the ending buffer elements
                        if (foundOne)
                        {
                            optionsMenu.AddDisabledItem(new GUIContent(labelPrefix + " "));
                            optionsMenu.AddDisabledItem(new GUIContent(labelPrefix + "Constant Methods"));
                        }
                    }

                    //Loop through the options to add the persistent options
                    for (int i = 0; i < methods.Length; i++)
                    {
                        //Store a collectible index for the lambda
                        int ind = i;

                        //Clear out the previous string value
                        sb.Length = 0;

                        //Add the current prefix to the entry
                        sb.Append(labelPrefix);

                        //Add the display text to the entry
                        sb.Append(methods[i].DisplayLabel.text);

                        //Check to see if all of the contained entries have drawers
                        bool found = false;

                        //Check if a missing drawer was found
                        for (int j = 0; j < methods[i].Parameters.Length; j++)
                        {
                            if (!ParameterDrawers.HasDrawer(methods[i].ParameterSignature[j], methods[i].Parameters[j].Attribute))
                            {
                                found = true;
                                break;
                            }
                        }

                        //Check for a missing drawer
                        if (found)
                        {
                            sb.Append("\t*");
                        }

                        //Add the option to the menu
                        optionsMenu.AddItem(
                            new GUIContent(sb.ToString(), methods[i].DisplayLabel.tooltip),
                            !currentIsDynamic && methods[i] == selectedMethod,
                            () => assignCallback(objTarget, methods[ind], false)
                            );
                    }
                }

                //Return the constructed menu
                return(optionsMenu);
            }
        /// <summary>
        /// Process the supplied type to establish the usable methods that are contained for it
        /// </summary>
        /// <param name="key">The type that is to be processed to identify usable methods</param>
        private static void ProcessObjectType(Type key)
        {
            //Find all the methods that belong to the type with the specified flags
            List <MethodInfo> baseMethods = new List <MethodInfo>(key.GetMethods(PersistentCallbackUtility.SEARCH_FLAGS));

            //Remove any method that is not valid
            baseMethods.RemoveAll(method => {
                //If this contains generic parameters, not gonna use it
                if (method.ContainsGenericParameters)
                {
                    return(true);
                }

                //If this is a property (With a special name) only allow setters
                if (method.IsSpecialName && !method.Name.StartsWith(SETTER_PROPERTY_PREFIX))
                {
                    return(true);
                }

                //Check if there is an exclusion attribute
                if (method.GetFirstCustomAttributeOf <OmitRuntimeEventAttribute>() != null)
                {
                    return(true);
                }

                //Retrieve the parameters of the object
                ParameterInfo[] parameters = method.GetParameters();

                //Check that each of the parameter types has a supported type
                foreach (ParameterInfo parm in parameters)
                {
                    if (!GenericSerialisation.CanProcess(parm.ParameterType))
                    {
                        return(true);
                    }
                }

                //If gotten this far, not going to be used
                return(false);
            });

            //Process each of the stored entries for inclusion in the lookup
            Dictionary <MethodHash, PersistentMethod> typeLookup = new Dictionary <MethodHash, PersistentMethod>(baseMethods.Count);

            //Process all of the stored methods
            foreach (MethodInfo method in baseMethods)
            {
                //Get all of the parameters belonging to this method that needs to processed
                ParameterInfo[] parameters = method.GetParameters();

                //Create an array for all of the parameters within this method
                PersistentParameter[] paramsInfo = new PersistentParameter[parameters.Length];

                //Store the type signature for this method
                Type[] signature = new Type[parameters.Length];

                //Store the collection of objects that will be used to create this Methods Hash
                List <object> hashable = new List <object>(parameters.Length + 1);
                hashable.Add(method.Name);

                //Process each of the parameter signature values
                for (int i = 0; i < parameters.Length; i++)
                {
                    //Store the type for the entry
                    signature[i] = parameters[i].ParameterType;

                    //Add the type to the hashable list
                    hashable.Add(parameters[i].ParameterType);

                    //Look for a description attribute that is attached to this parameter
                    DescriptionAttribute paramDesc = parameters[i].GetFirstCustomAttributeOf <DescriptionAttribute>();

                    //Create the information object for this parameter
                    paramsInfo[i] = new PersistentParameter(
                        new GUIContent(
                            ObjectNames.NicifyVariableName(parameters[i].Name),
                            paramDesc != null ? paramDesc.Description : string.Empty
                            ),
                        parameters[i].GetFirstCustomAttributeOf <ParameterAttribute>(),
                        parameters[i].DefaultValue
                        );
                }

                //Look for a description attribute that is attached to this method
                DescriptionAttribute methodDesc = method.GetFirstCustomAttributeOf <DescriptionAttribute>();

                //Create the information object for this method
                PersistentMethod persistentMethod = new PersistentMethod(
                    new GUIContent(
                        GenerateLabelFromSignature(
                            (method.IsSpecialName ?
                             method.Name.Substring(SETTER_PROPERTY_PREFIX.Length) :
                             method.Name
                            ),
                            signature
                            ),
                        methodDesc != null ? methodDesc.Description : string.Empty
                        ),
                    method.Name,
                    signature,
                    paramsInfo,
                    method.IsSpecialName
                    );

                //Get the hash key for this method
                MethodHash hashKey = HashUtitlity.GetCombinedHash(hashable.ToArray());

                //Warn on a key conflict (Ideally, shouldn't happen ever)
                if (typeLookup.ContainsKey(hashKey))
                {
                    Debug.LogWarningFormat("Generated hash key for the method '{0}' (For Type: {1}) is identical to the method '{2}'. This method will overrite the previous", persistentMethod.DisplayLabel.text, key.FullName, typeLookup[hashKey].DisplayLabel.text);
                }

                //Add the method to the lookup
                typeLookup[hashKey] = persistentMethod;
            }

            //Stash the values in the general lookup
            objectMethods[key] = typeLookup;
        }