How to apply PropertyAttributes only on a specific type appearing in the variable it is used on?

I have a generic custom class SerializableMatrix that I made a custom property drawer for so that I can display 2D arrays as matrices in the inspector. Like this: [197683-matrixdisplay.png*|197683]

Now, I also want to implement an attribute BigBoolAttribute, that makes the booleans more clear in the inspector by making them slightly bigger and toggle green color. This way it would be easier to fill out many arrays of booleans instead of misclicking tiny checkboxes. I want to be able to get the following result:
[197684-howiwantit.png*|197684]
where clicking a square will turn it green. This by using the code:

[BigBool(40)]
public SerializableMatrix<bool> toDisplay = new SerializableMatrix<bool>(4,4);

The problem

When using an attribute it overrides the propertydrawer of SerializableMatrix, causing the matrix not to be drawn in the inspector, since the attribute works on the whole SerializableMatrix instead of the booleans in this SerializableMatrix. Instead I just get one of my custom boolean squares.

I would love for there to be a way to make the attribute basically act as if it was applied to just booleans that come along the way instead of the variable applied on: Use the original way of drawing the property, but for every property of type boolean we find in this class, we can change the drawing.


Attempted and considered solutions

  • Instead of making an attribute, I can change the property drawer for bool types. This way, it indeed finds the booleans appearing in SerializableMatrix and replaces them with the custom way of drawing without messing up how the 2D arrays are drawn, so the way I want it. However, this will then be applied to all booleans, and I would need a NormalBoolAttribute everytime I want a normally drawn boolean which is not preferred. Furthermore, I can not pass a size parameter anymore as is possible with the attribute.
  • I can not put the attribute in the SerializableMatrix class, because this is a generic class and it might be that there are no booleans used, causing errors (unless I could somehow put a check on the type before applying the attribute, however you can not put attributes in if statements)
  • Decoration drawers can use multiple drawers in different orders. However as I understand it it does not give access to the property itself so I can not change the things needed to draw the boolean as I want since I will need the button presses to change the value of the boolean property.
  • Making a separate class for SerializableBoolMatrix and write a slightly different propertydrawer. This would work however I don’t think it is a very clean solution.
  • Trying to see if EditorGUI.PropertyField() can be changed in behaviour for certain types. However I could not find any documentation about this.
  • Would there be some way to use [CustomPropertyDrawer(typeof())] pass the combination of being a boolean and having the attribute?

Thank you for your time and any reactions, I inserted the code below if this could be of help in clarifying the setup that I am using.

I am also open to alternative ways that get a result that works differently than what I have in mind, as long as it makes it easier filling out many 2D boolean matrices and spotting potential mistakes.


SerializableMatrix.cs

[System.Serializable]
public class SerializableMatrix<TValue>
{
    public RowData[] rows;

    [System.Serializable]
    public class RowData
    {
        public TValue[] columns;
        public RowData(int y)
        {
            columns = new TValue[y];
        }
    }

    public SerializableMatrix(int x, int y)
    {
        rows = new RowData[x];
        for (int i = 0; i < x; i++)
        {
            rows *= new RowData(y);*

}
}
}
SerializableMatrixPropertyDrawer.cs
[CustomPropertyDrawer(typeof(SerializableMatrix<>))]
public class SerializableMatrixPropertyDrawer : PropertyDrawer
{
private SerializedProperty rows;
private SerializedProperty columns;

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (rows == null)
{
rows = property.FindPropertyRelative(“rows”);
}

float height = EditorGUIUtility.singleLineHeight;
if (property.isExpanded)
{
for (int i = 0; i < rows.arraySize; i++)
{
height += EditorGUI.GetPropertyHeight(rows.GetArrayElementAtIndex(i).FindPropertyRelative(“columns”).GetArrayElementAtIndex(0));
}
}

return height;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (rows == null)
{
rows = property.FindPropertyRelative(“rows”);
}

EditorGUI.BeginProperty(position, label, property);

Rect foldoutRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);

EditorGUI.indentLevel++;
if (property.isExpanded)
{
float addY = EditorGUIUtility.singleLineHeight;
for (int i = 0; i < rows.arraySize; i++)
{
columns = rows.GetArrayElementAtIndex(i).FindPropertyRelative(“columns”);
float addX = 0;

//EditorGUILayout.BeginHorizontal();
for (int j = 0; j < columns.arraySize; j++)
{
Rect rect = new Rect(position.x + addX, position.y + addY, EditorGUIUtility.fieldWidth, EditorGUI.GetPropertyHeight(columns.GetArrayElementAtIndex(j)));
EditorGUI.PropertyField(rect, columns.GetArrayElementAtIndex(j), GUIContent.none);
addX += rect.width;
}
//EditorGUILayout.EndHorizontal();

addY += EditorGUI.GetPropertyHeight(columns.GetArrayElementAtIndex(0));
}
}
EditorGUI.indentLevel–;

EditorGUI.EndProperty();
}
}
BigBoolAttribute.cs
public class BigBoolAttribute : PropertyAttribute
{
public readonly int size;

public BigBoolAttribute(int size)
{
this.size = size;
}
}
BigBoolDrawer.cs
[CustomPropertyDrawer(typeof(BigBoolAttribute))]
public class BigBoolDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return 60; //change this to retrieve size from attribute
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if(GUI.Button(position, “”))
{
Debug.Log(“Button pressed”);
}
}
}
*
*

For anyone finding this thread in the future, here is what I ended up doing:

While it does not exactly do what I had hoped for, depending on the application it might be just as usable (at least for me it is)

I made the BigBoolDrawer inherit from the SerializableMatrixPropertyDrawer and made two virtual functions for getting an element height and drawing the property field which are overriden by the BigBoolDrawer. Some typechecking is used to make sure the attribute is only applied to SerializableMatrix Types. It is easier to maintain than a whole separate class, and also for expansion with other types.

The only drawback is that the BigBool attribute can only be used on a SerializableMatrix, For future classes with a custom property drawer that I want these clear booleans, I would thus have to write a new attribute, however I think it is unlikely that this is really a necessity as these big booleans are to me mostly handy when there is many near each other.

Though if someone has a solution that is cleaner, please still share! :slight_smile:


Result

The following lines of code give the following result:

    [BigBool(80)]
    public SerializableMatrix<bool> floorTiles = new SerializableMatrix<bool>(2, 2);
    [BigBool(40)]
    public SerializableMatrix<bool> wallTiles = new SerializableMatrix<bool>(4, 4);
    // Without attribute
    public SerializableMatrix<bool> trackTiles = new SerializableMatrix<bool>(4, 4);
    // When applying to the wrong data structure
    [BigBool(40)]
    public bool test;

Link to picture because the site is broken and pictures can’t be uploaded:

Result.png


Code

BigBoolDrawer.cs

[CustomPropertyDrawer(typeof(BigBoolAttribute))]
public class BigBoolDrawer : SerializableMatrixPropertyDrawer
{
    public override Rect DrawProperty(Rect position, float addX, float addY, SerializedProperty property, GUIContent label)
    {
        if (property.propertyType == SerializedPropertyType.Boolean)
        {
            BigBoolAttribute bigBool = (BigBoolAttribute)attribute;

            var oldColor = GUI.backgroundColor;
            if (property.boolValue == true)
            {
                GUI.backgroundColor = Color.green;
            } 
            Rect rect = new Rect(position.x + addX, position.y + addY, bigBool.size, bigBool.size);
            if (GUI.Button(rect, ""))
            {
                property.boolValue = !property.boolValue;
            }
            GUI.backgroundColor = oldColor;
            return rect;
        }
        else
        {
            BigBoolAttribute bigBool = (BigBoolAttribute)attribute;
            Rect rect = new Rect(position.x + addX, position.y + addY, EditorGUIUtility.fieldWidth, bigBool.size);
            EditorGUI.LabelField(position, "Use the attribute [BigBool()] with the SerializableMatrix<bool> class");
            return rect;
        }
        
    }

    public override float ElementHeight(SerializedProperty property)
    {
        BigBoolAttribute bigBool = (BigBoolAttribute)attribute;
        return bigBool.size;
    }
}

SerializableMatrixPropertyDrawer.cs

[CustomPropertyDrawer(typeof(SerializableMatrix<>))]
public class SerializableMatrixPropertyDrawer : PropertyDrawer
{
private SerializedProperty rows;
private SerializedProperty columns;

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
    if (rows == null)
    {
        rows = property.FindPropertyRelative("rows");
    }
    
    float height = EditorGUIUtility.singleLineHeight;
    if (property.isExpanded)
    {
        for (int i = 0; i < rows.arraySize; i++)
        {
            height += ElementHeight(rows.GetArrayElementAtIndex(i).FindPropertyRelative("columns").GetArrayElementAtIndex(0));
        }
    }

    return height;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    if (property.FindPropertyRelative("isOfSerializableMatrixType") != null)
    {
        if (rows == null)
        {
            rows = property.FindPropertyRelative("rows");
        }

        EditorGUI.BeginProperty(position, label, property);

        Rect foldoutRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);        
        property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);
    
        EditorGUI.indentLevel++;
        if (property.isExpanded)
        {
            float addY = EditorGUIUtility.singleLineHeight;
            for (int i = 0; i < rows.arraySize; i++)
            {
                columns = rows.GetArrayElementAtIndex(i).FindPropertyRelative("columns");
                float addX = 0;

                for (int j = 0; j < columns.arraySize; j++)
                {
                    addX += DrawProperty(position, addX, addY, columns.GetArrayElementAtIndex(j), GUIContent.none).width;
                }

                addY += ElementHeight(columns.GetArrayElementAtIndex(0));
            }
        }
        EditorGUI.indentLevel--;
    
        EditorGUI.EndProperty();
    } 
    else 
    {
        EditorGUI.LabelField(position, "Use the attribute [" + attribute.ToString() + "] with the SerializableMatrix<> class");
    }
}

public virtual Rect DrawProperty(Rect position, float addX, float addY, SerializedProperty property, GUIContent label)
{
    Rect rect = new Rect(position.x + addX, position.y + addY, EditorGUIUtility.fieldWidth, ElementHeight(property));
    EditorGUI.PropertyField(rect, property, label);
    return rect;
}

public virtual float ElementHeight(SerializedProperty property)
{
    return EditorGUI.GetPropertyHeight(property);
}

}