Search Unity

EditorGUILayout.EnumMaskField doesn't use Enum's values

Discussion in 'Scripting' started by Bananenfraese, Mar 10, 2014.

  1. Bananenfraese

    Bananenfraese

    Joined:
    Mar 15, 2013
    Posts:
    2
    Hi,

    I just discovered, that the EnumMaskField-Method of EditorGUILayout does not use the values of the given enum, but instead generates the values based on the order in the enum definition,

    In my case I have an enum that has discrete values assigned like this

    Code (csharp):
    1. public enum SomeEnum
    2. {
    3.   None=0,
    4.   FirstItem=1,
    5.   SecondItem=4,
    6.   SomeFlag=31,
    7.   SomeValueWithAdditionalFlag = 8 | SomeFlag
    8. }
    But when I use the EnumMaskField, Unity treats the values like they were like this:

    Code (csharp):
    1. public enum SomeEnum
    2. {
    3.   None=1,
    4.   FirstItem=2,
    5.   SecondItem=4,
    6.   SomeFlag=8,
    7.   SomeValueWithAdditionalFlag = 16
    8. }
    I'm not sure if it's meant to be like this and if that's just a limitation of the EnumMaskField or if there is a workaround for this...
    or maybe I'm using the whole thing the wrong way. For the time being I'm using generated Toggle-Inputs, but these take up a lot of space and don't really contribute to a clean Editor-Layout.

    Any help is appreciated...
     
  2. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
  3. Bananenfraese

    Bananenfraese

    Joined:
    Mar 15, 2013
    Posts:
    2
    Hi,

    thanks a lot for the answer! I understand the requirements for an enum, that can be used as bit-combinable flags. I know my example was a bit of a special case, but even an enum which is built completely following the guidlines for the FlagsAttribute will not work correctly with EnumMaskField.

    Code (csharp):
    1. public enum SomeEnum
    2. {
    3.   None = 0,
    4.   FirstFlag = 1,
    5.   SecondFlag = 2
    6. }
    ... is treated like this when used EnumMaskField:

    Code (csharp):
    1. public enum SomEnum
    2. {
    3.   None = 1,
    4.  FirstFlag = 2,
    5.  SecondFlag = 4
    6. }
    So even following the guidlines, which explicitely state that there should be value defined for "None", won't work. If I won't get it working the way I want it I'll just create my own input-method, which may not look that slick, but hopfully do what I need it to do ;-)

    For enums like the one above with the additional "None"-value I currently work around the problem by shifting the value one bit to the left before feeding it to the Editor-Method and shifting it back one bit to the right when assigning it back to the property. But for enums with non-continuous values or special flags like the one in my first example the solution is much more of a hassle, since you always have map each and every value of the mask between the the two representations.

    Any other ideas?
     
    Last edited: Mar 10, 2014
  4. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Can't you just get rid of the None value? I don't think it's required.

    Also, your first example doesn't make much sense. 1, 2, 4, 8, 16 . . . is required if you wish to use the enum as a bit mask. If you need a flag that is "31" or 8 | SomeFlag, then create an integer to store that flag (perhaps a constant?).

    I don't believe it makes sense to include these flags in your enum definition.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Hrmmm... so this throws a wrench in something I wrote before.

    So I noticed that all my enum entries were showing up in the dropdown and I wrote this to deal with it:

    In my 'SPEditorGUI' class:
    Code (csharp):
    1.  
    2.         public static int EnumFlagField(Rect position, System.Type enumType, GUIContent label, int value)
    3.         {
    4.             var names = (from e in ObjUtil.GetUniqueEnumFlags(enumType) select e.ToString()).ToArray();
    5.             return EditorGUI.MaskField(position, label, value, names);
    6.         }
    7.  
    8.         public static System.Enum EnumFlagField(Rect position, GUIContent label, System.Enum value)
    9.         {
    10.             if (value == null) throw new System.ArgumentException("Enum value must be non-null.", "value");
    11.  
    12.             var enumType = value.GetType();
    13.             int i = EnumFlagField(position, enumType, label, System.Convert.ToInt32(value));
    14.             return System.Enum.ToObject(enumType, i) as System.Enum;
    15.         }
    16.  
    And this is the 'GetUniqueEnumFlags' in 'ObjUtil':
    Code (csharp):
    1.  
    2.         public static IEnumerable<System.Enum> GetUniqueEnumFlags(System.Type enumType)
    3.         {
    4.             if (enumType == null) throw new System.ArgumentNullException("enumType");
    5.             if (!enumType.IsEnum) throw new System.ArgumentException("Type must be an enum.", "enumType");
    6.  
    7.             foreach(var e in System.Enum.GetValues(enumType))
    8.             {
    9.                 var d = System.Convert.ToDecimal(e);
    10.                 if (d > 0 && MathUtil.IsPowerOfTwo(System.Convert.ToUInt64(d))) yield return e as System.Enum;
    11.             }
    12.         }
    13.  
    But yeah, I just realized that if the order is out of order, it won't work correctly.

    And yeah, the unity EnumMaskField basically just uses the MaskField like I'm doing here, but it just uses ALL the values in the enum, not the unique ones like I do.

    I'ma update this code so it works correctly with out of order values, and missing flag values. Because I use 'MaskField' though, any missing entries will have to be blank, yet still exist in the dropdown. But selecting it will result in nothing as I'll clean the int before returning it. I just have to include a blank entry, because how MaskField lines up the returned int mask with the indices of the array of names passed in.

    The other option is to write my own drop down graphics... which I really don't feel like doing.
     
  6. alice_funo

    alice_funo

    Joined:
    Mar 9, 2016
    Posts:
    14
    This is super useful, since I have random values for ease of testing for certain mask in other places as well. Thanks a bunch!
     
  7. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    812
    This problem still exists, so I'm sharing my workaround. It makes the enum mask field behave exactly like you'd expect it to.

    Usage:

    Code (CSharp):
    1. newValue = LudiqGUI.EnumMaskField(position, oldValue);
    Implementation:

    Code (CSharp):
    1.  
    2.         public static Enum EnumMaskField(Rect position, Enum value)
    3.         {
    4.             return OrderToEnumMask(EditorGUI.EnumMaskField(position, EnumToOrderMask(value, false)));
    5.         }
    6.  
    7.         public static Enum EnumToOrderMask(Enum enumMaskEnum, bool allowCompound)
    8.         {
    9.             var enumType = enumMaskEnum.GetType();
    10.             var enumValues = Enum.GetValues(enumType);
    11.             var enumMask = Convert.ToInt64(enumMaskEnum);
    12.  
    13.             var orderMask = 0L;
    14.  
    15.             for (int i = 0; i < enumValues.Length; i++)
    16.             {
    17.                 var enumValue = enumValues.GetValue(i);
    18.                 var enumValueMask = Convert.ToInt64(enumValue);
    19.  
    20.                 // Skip none values, because they'd always be included
    21.                 if (enumValueMask == 0)
    22.                 {
    23.                     continue;
    24.                 }
    25.  
    26.                 // Skip compound values, which can be useful for properly
    27.                 // separating items in the inspector and avoiding a deadlock
    28.                 if (!allowCompound && !Mathf.IsPowerOfTwo((int)enumValueMask))
    29.                 {
    30.                     continue;
    31.                 }
    32.  
    33.                 if ((enumMask & enumValueMask) == enumValueMask)
    34.                 {
    35.                     var indexMask = 1L << i;
    36.                     orderMask |= indexMask;
    37.                 }
    38.             }
    39.  
    40.             return (Enum)Enum.ToObject(enumType, orderMask);
    41.         }
    42.  
    43.         public static Enum OrderToEnumMask(Enum orderMaskEnum)
    44.         {
    45.             var enumType = orderMaskEnum.GetType();
    46.             var enumValues = Enum.GetValues(enumType);
    47.             var orderMask = Convert.ToInt64(orderMaskEnum);
    48.  
    49.             var enumMask = 0L;
    50.  
    51.             for (int i = 0; i < enumValues.Length; i++)
    52.             {
    53.                 var indexMask = 1L << i;
    54.  
    55.                 if ((orderMask & indexMask) == indexMask)
    56.                 {
    57.                     var enumValue = enumValues.GetValue(i);
    58.                     var enumValueMask = Convert.ToInt64(enumValue);
    59.                    
    60.                     enumMask |= enumValueMask;
    61.                 }
    62.             }
    63.  
    64.             return (Enum)Enum.ToObject(enumType, enumMask);
    65.         }
     
    Catsoft-Studios and Duffer123 like this.
  8. Duffer123

    Duffer123

    Joined:
    May 24, 2015
    Posts:
    1,215
    @Ludiq ,

    Can this be used to facilitate true 64 bit enums (and enum masks) within Unity?