- Home /
custom inspector multi-select enum dropdown?
I want to make a drop-down in a custom inspector that allows me to select multiple enums from a popup, exactly like the popups you get when selecting a camera's culling mask, a projector's "ignore" layers, or a layermask. (see picture)
Am I missing a simple way to do this? What would the returned result of a change be? A List or array of FooEnum elements? I can jury-rig an equivalent alternative, but since this GUI element does exactly what I want, I'd love to use one if I could find out how!
Nice simple solution with PropertyDrawer + $$anonymous$$askField here: http://answers.unity3d.com/questions/486694/default-editor-enum-as-flags-.html
Answer by Bunny83 · Feb 05, 2013 at 03:24 PM
I've written a "wrapper" function, mainly for a general PropertyDrawer, which actual uses the enum values. However, this changes the Nothing and Everything behaviour, but all enum values work fine, even combined values.
//C#
// Have to be defined somewhere in a runtime script file
public class BitMaskAttribute : PropertyAttribute
{
public System.Type propType;
public BitMaskAttribute(System.Type aType)
{
propType = aType;
}
}
This should go into an editor script:
using UnityEngine;
using UnityEditor;
using System.Collections;
public static class EditorExtension
{
public static int DrawBitMaskField (Rect aPosition, int aMask, System.Type aType, GUIContent aLabel)
{
var itemNames = System.Enum.GetNames(aType);
var itemValues = System.Enum.GetValues(aType) as int[];
int val = aMask;
int maskVal = 0;
for(int i = 0; i < itemValues.Length; i++)
{
if (itemValues[i] != 0)
{
if ((val & itemValues[i]) == itemValues[i])
maskVal |= 1 << i;
}
else if (val == 0)
maskVal |= 1 << i;
}
int newMaskVal = EditorGUI.MaskField(aPosition, aLabel, maskVal, itemNames);
int changes = maskVal ^ newMaskVal;
for(int i = 0; i < itemValues.Length; i++)
{
if ((changes & (1 << i)) != 0) // has this list item changed?
{
if ((newMaskVal & (1 << i)) != 0) // has it been set?
{
if (itemValues[i] == 0) // special case: if "0" is set, just set the val to 0
{
val = 0;
break;
}
else
val |= itemValues[i];
}
else // it has been reset
{
val &= ~itemValues[i];
}
}
}
return val;
}
}
[CustomPropertyDrawer(typeof(BitMaskAttribute))]
public class EnumBitMaskPropertyDrawer : PropertyDrawer
{
public override void OnGUI (Rect position, SerializedProperty prop, GUIContent label)
{
var typeAttr = attribute as BitMaskAttribute;
// Add the actual int value behind the field name
label.text = label.text + "("+prop.intValue+")";
prop.intValue = EditorExtension.DrawBitMaskField(position, prop.intValue, typeAttr.propType, label);
}
}
Now just imagine this example:
public enum EMyEnum
{
None = 0x00,
Cow = 0x01,
Chicken = 0x02,
Cat = 0x04,
Dog = 0x08,
CowChicken = 0x03,
CatDog = 0x0C,
All = 0x0F,
}
public class Example : MonoBehaviour
{
[BitMask(typeof(EMyEnum))]
public EMyEnum someMask;
}
This will automatically draw my modified enum-mask field.
unfortunately there is no way (i know) to deter$$anonymous$$e the actual type of the SerializedProperty, so i have to pass the type as attribute parameter.
$$anonymous$$y solution for achieving this functionality would've been much more amateurish. This one's a keeper, many thanks for sharing your code!
Thanks Bunny, incredibly useful! Bookmarked and will thumbs up once I find a few questions to answer and get 15 points :)
Rob
Just to let you know. In Unity 4.3 it is now possible to get the serialized property type. Just use the fieldInfo.FieldType property. You don't need to pass the type in the attribute's constructor anymore. See : http://docs.unity3d.com/Documentation/ScriptReference/PropertyDrawer-fieldInfo.html
@vexe: you can use and modify this helper method as you like, since it's posted here on UA. I'll keep the int + Type version since its more flexible especially in combination with a PropertyDrawer.
That way you can easily add another method like this:
public static T DrawBit$$anonymous$$askField<T> (Rect aPosition, T aValue, GUIContent aLabel) where T : System.Enum
{
return (T)DrawBit$$anonymous$$askField ( aPosition, (int)aValue, aValue.GetType(), aLabel);
}
Answer by whydoidoit · Feb 05, 2013 at 09:10 AM
You need to use EnumMaskField and make sure your enum is decorated with the [Flags] attribute.
Does the Flags attribute even change anything? The implementation of Enum$$anonymous$$askField is horrible. It doesn't care about the actual enum values, it just treats the first value as 1 the second as 2 the third as 4 ...
Yeah - I'm thinking of the one that I wrote and googling the wrong name :S
Thanks so much! $$anonymous$$nowing what to research helped me learn something new. :)
Note that applying [Flags] doesn't automatically make the enum values into powers of 2, you need to do that yourself. I like to use a shift operator, so it's clear I'm getting one bit each time, like so:
[Flags]
enum $$anonymous$$yEnum
{
First = (1 << 0),
Second = (1 << 1),
Third = (1 << 2),
etc.
}
And yeah, it's a pity Enum$$anonymous$$askField isn't used automatically when [Flags] attribute is present :-p
Answer by phobos2077 · Sep 08, 2018 at 05:27 PM
@AlwaysSunny @Bunny83 @whydoidoit @Aqibsadiq :
A clean solution for current versions of Unity.
EnumFlagAttributePropertyDrawer.cs
using System;
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(EnumFlagAttribute))]
class EnumFlagAttributePropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
label = EditorGUI.BeginProperty(position, label, property);
var oldValue = (Enum)fieldInfo.GetValue(property.serializedObject.targetObject);
var newValue = EditorGUI.EnumFlagsField(position, label, oldValue);
if (!newValue.Equals(oldValue))
{
property.intValue = (int)Convert.ChangeType(newValue, fieldInfo.FieldType);
}
EditorGUI.EndProperty();
}
}
EnumFlagAttribute.cs
using UnityEngine;
/// <summary>
/// Display multi-select popup for Flags enum correctly.
/// </summary>
public class EnumFlagAttribute : PropertyAttribute
{
}
Usage example:
public class MyFlagHolder : MonoBehaviour
{
[SerializeField, EnumFlag]
private MyFlag flags;
}
[Flags]
public enum MyFlag
{
Flag1 = 1,
Flag2 = 2,
Flag3 = 4,
Flag4 = 8
}
This solution doesn't seem to solve the issue where all flags option (label=Everything) return -1, whereas the solution offered by @Bunny83, however convoluted, does.
Answer by Aqibsadiq · Nov 20, 2017 at 07:41 AM
@Bunny83 @AlwaysSunny @whydoidoit
Here is a simpler way
Simple 4 Steps
Step 1 : Make a new Script "EnumFlagsAttribute"
using UnityEngine;
using System.Collections;
public class EnumFlagsAttribute : PropertyAttribute
{
public EnumFlagsAttribute() { }
}
Step 2 : Make another Script "EnumFlagsAttributeDrawer"
using UnityEngine;
using System.Collections;
using System;
using UnityEditor;
[CustomPropertyDrawer(typeof(EnumFlagsAttribute))]
public class EnumFlagsAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
{
_property.intValue = EditorGUI.MaskField( _position, _label, _property.intValue, _property.enumNames );
}
}
Step 3: Enum Declaration
[System.Flags]
public enum FurnitureType
{
None , SofaPrefab , Curtains , Table , Chairs
}
[EnumFlagsAttribute]
public FurnitureType enumType;
Step 4: Getting Selected Elements
List<int> ReturnSelectedElements()
{
List<int> selectedElements = new List<int>();
for (int i = 0; i < System.Enum.GetValues(typeof(FurnitureType)).Length; i++)
{
int layer = 1 << i;
if (((int) enumType & layer) != 0)
{
selectedElements.Add(i);
}
}
return selectedElements;
}
Well, that's not really a solution. As you might see I'm also using EditorGUI.$$anonymous$$askField in my solution. The problem is that the $$anonymous$$askField doesn't care about the actual bitmask values.
In your example your enum does not represent a bit mask at all. Your enum values are:
public enum FurnitureType
{
None = 0,
SofaPrefab = 1,
Curtains = 2,
Table = 3,
Chairs = 4
}
So looking at the bit values here your "Table" would be a Sofa combined with Curtains ^^.
You don't seem to actually be intrested in a bitflag field. I'm not sure you really understand why we use a bitmask. We can represent up to 32 individual items in a single integer value. The main point of using a bitflag is that is't way faster and doesn't require memory on the heap for managing an array of elements. We have 32 boolean values in a single integer which we can easily test if they are set or not.
The way you use the $$anonymous$$askField is the way Unity had in $$anonymous$$d, but it doesn't fit for mask enums. You basically do the same that "Enum$$anonymous$$askField" does wrong. You expect the mask value for each enum member to be in order and continous. Have a look at my "E$$anonymous$$yEnum" example. It has 4 distinct members ( Cow, Chicken, Cat, Dog ) and some combinations of those. Your solution artifically gives each enum member a different bit value which you reverse in your "ReturnSelectedElements" method. However the point was to actually have an enum that represents a bitmask.
okay you are right i am not interested in bitflag field To me the Question was how to create a custom inspector multi-select enum dropdown and i just did that and its working fine
Ironically Unity has an internal method called "Do$$anonymous$$askField" which takes seperate values and names arrays. This is internally used by $$anonymous$$askField. However the array with the values is automatically generated like this:
int[] array = new int[flagNames.Length];
for (int i = 0; i < array.Length; i++)
{
array[i] = 1 << i;
}
If we had access to the internal Do$$anonymous$$askField the fix would be much easier as you just had to pass the arrays we get from "Enum.GetNames" and "Enum.GetValues" and would get the correct result.
I think that's about the 5th time that a really good feature is burried in the engine without either not being accessible or not being documented
Your answer
Follow this Question
Related Questions
How to change array orders in the inspector 1 Answer
Should I use Inspector or custom editor window? 1 Answer
Using EditorGUI.FloatField With Custom Class Inspectors? 1 Answer
Custom inspector curves similar to the 'Audio Source' volume/pan/spread Curves 0 Answers
Prefix label greyed out when following component disabled 1 Answer