Search Unity

Mixed value for Property Attribute and Drawer in multi edit mode

Discussion in 'Editor & General Support' started by a-t-hellboy, May 19, 2019.

  1. a-t-hellboy

    a-t-hellboy

    Joined:
    Dec 6, 2013
    Posts:
    180
    Hey guys, I've written a property attribute and drawer for Scriptable Object dropdown.
    The problem which I have is multi editing part. When I select multiple objects which has that property attribute and each of them has different selected item in dropdown, all of them change to the first selected object, selected item in dropdown.

    I read by using EditorGUI.showMixedValue I can use Mixed Value in multi editing but it doesn't work for me and I don't know.

    Could you tell me what I did wrong ?

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEditor;
    5. using UnityEngine;
    6. namespace Infrastructure.ScriptableObjectDropdown
    7. {
    8.     /// <summary>
    9.     /// Indicates how selectable scriptableObjects should be collated in drop-down menu.
    10.     /// </summary>
    11.     public enum ScriptableObjectGrouping
    12.     {
    13.         /// <summary>
    14.         /// No grouping, just show type names in a list; for instance, "MainFolder > NestedFolder > SpecialScriptableObject".
    15.         /// </summary>
    16.         None,
    17.         /// <summary>
    18.         /// Group classes by namespace and show foldout menus for nested namespaces; for
    19.         /// instance, "MainFolder >> NestedFolder >> SpecialScriptableObject".
    20.         /// </summary>
    21.         ByFolder,
    22.         /// <summary>
    23.         /// Group scriptableObjects by folder; for instance, "MainFolder > NestedFolder >> SpecialScriptableObject".
    24.         /// </summary>
    25.         ByFolderFlat
    26.     }
    27.     [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
    28.     public class ScriptableObjectDropdownAttribute : PropertyAttribute
    29.     {
    30.         public ScriptableObjectGrouping grouping = ScriptableObjectGrouping.None;
    31.         public ScriptableObjectDropdownAttribute() { }
    32.     }
    33. }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System.Reflection;
    6. using System;
    7. using System.Linq;
    8. namespace Infrastructure.ScriptableObjectDropdown.Editor
    9. {
    10.     [CustomPropertyDrawer(typeof(ScriptableObjectDropdownAttribute))]
    11.     public class ScriptableObjectDropdownDrawer : PropertyDrawer
    12.     {
    13.         private static ScriptableObject[] _scriptableObjects;
    14.         private static ScriptableObject _selectedScriptableObject;
    15.         private static readonly int _controlHint = typeof(ScriptableObjectDropdownAttribute).GetHashCode();
    16.         private static GUIContent _guiContent = new GUIContent();
    17.         private static int _selectionControlID;
    18.         private static readonly GenericMenu.MenuFunction2 _onSelectedScriptableObject = OnSelectedScriptableObject;
    19.         public static Type GetPropertyType(SerializedProperty property)
    20.         {
    21.             Type parentType = property.serializedObject.targetObject.GetType();
    22.             FieldInfo fieldInfo = parentType.GetField(property.propertyPath);
    23.             return fieldInfo.FieldType;
    24.         }
    25.         private static ScriptableObject[] GetScriptableObjects(SerializedProperty property)
    26.         {
    27.             Type genericClassType = typeof(Resources);
    28.             MethodInfo[] methodsInfo = genericClassType.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
    29.             MethodInfo methodInfo = methodsInfo.Where(_ => _.IsGenericMethod && _.Name.Equals("LoadAll") && _.GetParameters().FirstOrDefault().ParameterType == typeof(string))
    30.                                     .FirstOrDefault();
    31.             Type[] genericArguments = new Type[] { GetPropertyType(property) };
    32.             MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);
    33.             _scriptableObjects = genericMethodInfo.Invoke(null, new object[] { "" }) as ScriptableObject[];
    34.             return _scriptableObjects;
    35.         }
    36.         private static UnityEngine.Object DrawScriptableObjectSelectionControl(Rect position, GUIContent label,
    37.             ScriptableObject scriptableObject, SerializedProperty property, ScriptableObjectDropdownAttribute attribute)
    38.         {
    39.             if (label != null && label != GUIContent.none)
    40.                 position = EditorGUI.PrefixLabel(position, label);
    41.             int controlID = GUIUtility.GetControlID(_controlHint, FocusType.Keyboard, position);
    42.             bool triggerDropDown = false;
    43.             switch (Event.current.GetTypeForControl(controlID))
    44.             {
    45.                 case EventType.ExecuteCommand:
    46.                     if (Event.current.commandName == "ScriptableObjectReferenceUpdated")
    47.                     {
    48.                         if (_selectionControlID == controlID)
    49.                         {
    50.                             if (scriptableObject != _selectedScriptableObject)
    51.                             {
    52.                                 scriptableObject = _selectedScriptableObject;
    53.                                 GUI.changed = true;
    54.                             }
    55.                             _selectionControlID = 0;
    56.                             _selectedScriptableObject = null;
    57.                         }
    58.                     }
    59.                     break;
    60.                 case EventType.MouseDown:
    61.                     if (GUI.enabled && position.Contains(Event.current.mousePosition))
    62.                     {
    63.                         GUIUtility.keyboardControl = controlID;
    64.                         triggerDropDown = true;
    65.                         Event.current.Use();
    66.                     }
    67.                     break;
    68.                 case EventType.KeyDown:
    69.                     if (GUI.enabled && GUIUtility.keyboardControl == controlID)
    70.                     {
    71.                         if (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Space)
    72.                         {
    73.                             triggerDropDown = true;
    74.                             Event.current.Use();
    75.                         }
    76.                     }
    77.                     break;
    78.                 case EventType.Repaint:
    79.                     if (scriptableObject == null)
    80.                     {
    81.                         _guiContent.text = "Nothing";
    82.                     }
    83.                     else
    84.                     {
    85.                         _guiContent.text = scriptableObject.name;
    86.                     }
    87.                     EditorStyles.popup.Draw(position, _guiContent, controlID);
    88.                     break;
    89.             }
    90.             if (triggerDropDown)
    91.             {
    92.                 _selectionControlID = controlID;
    93.                 _selectedScriptableObject = scriptableObject;
    94.                 DisplayDropDown(position, scriptableObject, attribute.grouping);
    95.             }
    96.             return scriptableObject;
    97.         }
    98.         private static void DrawScriptableObjectSelectionControl(Rect position, GUIContent label,
    99.             SerializedProperty property, ScriptableObjectDropdownAttribute attribute)
    100.         {
    101.             bool restoreShowMixedValue = EditorGUI.showMixedValue;
    102.             EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
    103.             property.objectReferenceValue = DrawScriptableObjectSelectionControl(position, label,
    104.                 property.objectReferenceValue as ScriptableObject, property, attribute);
    105.             EditorGUI.showMixedValue = restoreShowMixedValue;
    106.         }
    107.         private static void DisplayDropDown(Rect position, ScriptableObject selectedScriptableObject, ScriptableObjectGrouping grouping)
    108.         {
    109.             var menu = new GenericMenu();
    110.             menu.AddItem(new GUIContent("Nothing"), selectedScriptableObject == null, _onSelectedScriptableObject, null);
    111.             menu.AddSeparator("");
    112.             for (int i = 0; i < _scriptableObjects.Length; ++i)
    113.             {
    114.                 var scriptableObject = _scriptableObjects[i];
    115.                 string menuLabel = FormatGroupedScriptableObject(scriptableObject, grouping);
    116.                 if (string.IsNullOrEmpty(menuLabel))
    117.                     continue;
    118.                 var content = new GUIContent(menuLabel);
    119.                 menu.AddItem(content, scriptableObject == selectedScriptableObject, _onSelectedScriptableObject, scriptableObject);
    120.             }
    121.             menu.DropDown(position);
    122.         }
    123.         private static string FindPath(ScriptableObject scriptableObject)
    124.         {
    125.             string path = AssetDatabase.GetAssetPath(scriptableObject);
    126.             path = path.Replace("Assets/Resources/", "");
    127.             path = path.Replace(".asset", "");
    128.             return path;
    129.         }
    130.         private static string FormatGroupedScriptableObject(ScriptableObject scriptableObject, ScriptableObjectGrouping grouping)
    131.         {
    132.             string path = FindPath(scriptableObject);
    133.             switch (grouping)
    134.             {
    135.                 default:
    136.                 case ScriptableObjectGrouping.None:
    137.                     path = path.Replace("/", " > ");
    138.                     return path;
    139.                 case ScriptableObjectGrouping.ByFolder:
    140.                     return path;
    141.                 case ScriptableObjectGrouping.ByFolderFlat:
    142.                     int last = path.LastIndexOf('/');
    143.                     string part1 = path.Substring(0, last);
    144.                     string part2 = path.Substring(last);
    145.                     path = part1.Replace("/", " > ") + part2;
    146.                     return path;
    147.             }
    148.         }
    149.         private static void OnSelectedScriptableObject(object userData)
    150.         {
    151.             _selectedScriptableObject = userData as ScriptableObject;
    152.             var scriptableObjectReferenceUpdatedEvent = EditorGUIUtility.CommandEvent("ScriptableObjectReferenceUpdated");
    153.             EditorWindow.focusedWindow.SendEvent(scriptableObjectReferenceUpdatedEvent);
    154.         }
    155.         public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    156.         {
    157.             return EditorStyles.popup.CalcHeight(GUIContent.none, 0);
    158.         }
    159.         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    160.         {
    161.             GetScriptableObjects(property);
    162.             DrawScriptableObjectSelectionControl(position, label, property, attribute as ScriptableObjectDropdownAttribute);
    163.         }
    164.     }
    165. }
    Edit:
    Instruction:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [CreateAssetMenu(menuName = "Create Resource")]
    6. public class ResourceStats : ScriptableObject
    7. {
    8.    public string name;
    9. }
    You should put those scriptable objects in Resources folder.

    How to use the attribute:

    Code (CSharp):
    1. [ScriptableObjectDropdown] public ResourceStats _stats;
    Or

    Code (CSharp):
    1. [ScriptableObjectDropdown(grouping = ScriptableObjectGrouping.ByFolder)] public ResourceStats _stats;
     
    Last edited: May 20, 2019
  2. Buckslice

    Buckslice

    Joined:
    Sep 12, 2014
    Posts:
    5
    I am having the same exact problem, did you ever figure this out? ... Thanks my fellow.
     
  3. a-t-hellboy

    a-t-hellboy

    Joined:
    Dec 6, 2013
    Posts:
    180
    I've not solved the problem yet (Actually I've not worked on this problem after creating this topic)