Enum Flags as Toggle Buttons

So like many before me, I wanted enum flags available in the editor. The best solutions I found were using a custom Attribute and PropertyDrawer to make it look like similar to the LayerMask dropdown. While that’s neat, it comes with the disadvantage that you have no real overview once multiple flags are set. You need to open the dropdown to see the actual state.

That didn’t work for me, so I built a solution using Toggle Buttons:

You can get the code from here: http://www.sharkbombs.com/2015/02/17/unity-editor-enum-flags-as-toggle-buttons/

And: If anyone has any useful feedback, I’d love to hear it!

Oh and: I’ve uploaded an upgraded version of this to the asset store here:

2 Likes

Hi @Democide
I just tried popping this in, and I get a single row of absolutelly tiny buttons. Is there a trick to getting 3 nicely formatted rows as you have in the gif above? Thanks!

These are three separate enums. The issue is, that you have an enum with a lot of options. There’s currently no code that detects this and does any linebreaks. I didn’t need it and didn’t really want to bother. It’s not terribly difficult but I just was done with it at that point.

If you do upgrade it to contain that functionality, let me know though. I’d be happy to update the script.

1 Like

Hey Martin, Got some help from a friend today modifying it. Here’s the new code for the EnumFlagDrawer.cs:

using System;
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(EnumFlagAttribute))]
public class EnumFlagsAttributeDrawer : PropertyDrawer
{
    const float mininumWidth = 80.0f;

    public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
    {
        int buttonsIntValue = 0;
        int enumLength = _property.enumNames.Length;

        float enumWidth = (EditorGUIUtility.currentViewWidth - EditorGUIUtility.labelWidth - 30);

        int buttonsPerRow = Mathf.FloorToInt(enumWidth / mininumWidth);
        int numRows = Mathf.CeilToInt((float)enumLength / (float)buttonsPerRow);

       
        bool[] buttonPressed = new bool[enumLength];

        float buttonWidth = enumWidth / Mathf.Min(buttonsPerRow, enumLength);

        EditorGUI.LabelField(new Rect(_position.x, _position.y, EditorGUIUtility.labelWidth, _position.height), _label);

        EditorGUI.BeginChangeCheck();

       
        for (int row = 0; row < numRows; row++)
        {
            for (int button = 0; button < buttonsPerRow; button++)
            {
                int i = button + row * buttonsPerRow;

                if (i >= enumLength) { break; }

                // Check if the button is/was pressed
                if ((_property.intValue & (1 << i)) == 1 << i)
                {
                    buttonPressed[i] = true;
                }

                Rect buttonPos = new Rect(_position.x + EditorGUIUtility.labelWidth + buttonWidth * button, _position.y + row * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing), buttonWidth, EditorGUIUtility.singleLineHeight);

                buttonPressed[i] = GUI.Toggle(buttonPos, buttonPressed[i], _property.enumNames[i], EditorStyles.toolbarButton);

                if (buttonPressed[i])
                    buttonsIntValue += 1 << i;
            }
        }

        if (EditorGUI.EndChangeCheck())
        {
            _property.intValue = buttonsIntValue;
        }
    }
   
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        int enumLength = property.enumNames.Length;

        float enumWidth = (EditorGUIUtility.currentViewWidth - EditorGUIUtility.labelWidth - 30);

        int buttonsPerRow = Mathf.FloorToInt(enumWidth / mininumWidth);
        int numRows = Mathf.CeilToInt((float)enumLength / (float)buttonsPerRow);


        return numRows * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
    }
}
1 Like

Sweet. I cleaned it up some more.

using System;
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(EnumFlagAttribute))]
public class EnumFlagsAttributeDrawer : PropertyDrawer
{
    const float mininumWidth = 60.0f;

    int enumLength;
    float enumWidth;

    int numBtns;
    int numRows;

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        SetDimensions(property);
        return numRows * EditorGUIUtility.singleLineHeight + (numRows - 1) * EditorGUIUtility.standardVerticalSpacing;
    }

    void SetDimensions(SerializedProperty property) {
        enumLength = property.enumNames.Length;
        enumWidth = (EditorGUIUtility.currentViewWidth - EditorGUIUtility.labelWidth - 20 );

        numBtns = Mathf.FloorToInt(enumWidth / mininumWidth);
        numRows = Mathf.CeilToInt((float)enumLength / (float)numBtns);
    }

    public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
    {
        SetDimensions(_property);

        int buttonsIntValue = 0;
        bool[] buttonPressed = new bool[enumLength];
        float buttonWidth = enumWidth / Mathf.Min(numBtns, enumLength);

        EditorGUI.LabelField(new Rect(_position.x, _position.y, EditorGUIUtility.labelWidth, _position.height), _label);

        EditorGUI.BeginChangeCheck ();

        for (int row = 0; row < numRows; row++) {
            for (int btn = 0; btn < numBtns; btn++) {
                int i = btn + row * numBtns;

                if (i >= enumLength) {
                    break;
                }

                // Check if the button is/was pressed
                if ((_property.intValue & (1 << i)) == 1 << i) {
                    buttonPressed[i] = true;
                }

                Rect buttonPos = new Rect(_position.x + EditorGUIUtility.labelWidth + buttonWidth * btn, _position.y + row * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing), buttonWidth, EditorGUIUtility.singleLineHeight);
                buttonPressed[i] = GUI.Toggle(buttonPos, buttonPressed[i], _property.enumNames[i], EditorStyles.toolbarButton);

                if (buttonPressed[i])
                    buttonsIntValue += 1 << i;
            }
        }

        if (EditorGUI.EndChangeCheck()) {
            _property.intValue = buttonsIntValue;
        }
    }
}
1 Like

This is fantastic, exactly what I was looking for. I know that it has been a while but. Do you have any idea why I am getting a type is not a enum value in the SetDimensions at these two lines?

enumLength = property.enumNames.Length;
enumWidth = (EditorGUIUtility.currentViewWidth - EditorGUIUtility.labelWidth - 20 );

EnumFlagsAttributeDrawer

using System;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(EnumFlagAttribute))]
public class EnumFlagsAttributeDrawer : PropertyDrawer
{
    const float mininumWidth = 60.0f;
    int enumLength;
    float enumWidth;
    int numBtns;
    int numRows;
   
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        SetDimensions(property);
        return numRows * EditorGUIUtility.singleLineHeight + (numRows - 1) * EditorGUIUtility.standardVerticalSpacing;
    }
    void SetDimensions(SerializedProperty property) {
        enumLength = property.enumNames.Length;
        enumWidth = (EditorGUIUtility.currentViewWidth - EditorGUIUtility.labelWidth - 20 );
        numBtns = Mathf.FloorToInt(enumWidth / mininumWidth);
        numRows = Mathf.CeilToInt((float)enumLength / (float)numBtns);
    }
    public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
    {
        SetDimensions(_property);
        int buttonsIntValue = 0;
        bool[] buttonPressed = new bool[enumLength];
        float buttonWidth = enumWidth / Mathf.Min(numBtns, enumLength);
        EditorGUI.LabelField(new Rect(_position.x, _position.y, EditorGUIUtility.labelWidth, _position.height), _label);
        EditorGUI.BeginChangeCheck ();
        for (int row = 0; row < numRows; row++) {
            for (int btn = 0; btn < numBtns; btn++) {
                int i = btn + row * numBtns;
                if (i >= enumLength) {
                    break;
                }
                // Check if the button is/was pressed
                if ((_property.intValue & (1 << i)) == 1 << i) {
                    buttonPressed[i] = true;
                }
                Rect buttonPos = new Rect(_position.x + EditorGUIUtility.labelWidth + buttonWidth * btn, _position.y + row * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing), buttonWidth, EditorGUIUtility.singleLineHeight);
                buttonPressed[i] = GUI.Toggle(buttonPos, buttonPressed[i], _property.enumNames[i], EditorStyles.toolbarButton);
                if (buttonPressed[i])
                    buttonsIntValue += 1 << i;
            }
        }
        if (EditorGUI.EndChangeCheck()) {
            _property.intValue = buttonsIntValue;
        }
    }
}

Please show your usage of this attribute as well.
Make sure you put it on the Enum field, marked with [Flags] attribute

Here is my enum

public enum EPlayMode { a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z }

Here is where I am trying to access the data.

public class Object : MonoBehaviour
{
    [EnumFlag]     public EPlayMode test;
   
    private void Update() {
        Debug.Log("a" + !test.Equals(EPlayMode.FirstOnly));
        Debug.Log("b" + !test.Equals(0));
    }
}

Eventually, I would like to write if statements that check what values are selected and do something specific depending on what is selected. What I am want to do with this is have a bunch of different ambient sounds that I can select on an object. If crows are selected then you will hear crows, if crows and windy trees are selected then you will hear crows and trees blowing in the wind. This may not be the best way of doing this architecturally but its the idea that is in my head.

Try to add [Flags] attribute to enum, like this

[Flags]
public enum EPlayMode { a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z }

Whatever the problem is in the original post, this is (presumably) not going to fix it, because a bit field enumeration behaves differently than a constant value enumeration. You an find more about this in the official Microsoft Docs, under FlagsAttribute Class. There are also examples and guidelines on how to use this attribute.

EDIT: Looked a bit deeper into the posted code, yes, you are right, they use enumeration as a bit field, so the attribute should be used.

Other than that, it is a great way to achieve OP’s need, in particular this:

Things like Enum.HasFlag(Enum) Method (Microsoft Docs) are very handy in this case.