//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); }
/// <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; } }
/// <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); }
/// <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; }