Search Unity

Simple compact property drawer

Discussion in 'Editor & General Support' started by JoeStrout, Apr 11, 2016.

  1. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Whew! I just spent most of an hour wrestling with this, so I thought I'd share.

    My goal seemed simple: I have a simple struct with a couple of int fields, and I want this to display in a compact one-line format similar to Vector2 and Vector3. PropertyDrawer to the rescue, right?

    But the docs only show an example of drawing sub-fields without labels. As soon as I included labels, the display was horrible: Unity would allocate ridiculous amounts of space to the labels, shrinking the actual fields down to the point where they were unusable or even overlapped other things. And much searching on the internets turned up only more of the same: no labels in the field area, nothing like Vector2/Vector3.

    After much wrestling and experimentation, I found that if you specify a label to your call to EditorGUI.PropertyField (or EditorGUI.IntField, etc.), then it will take the rectangle you give it, and divide this (stupidly) between the actual field and the label. There appears to be no way to tell it how wide the label should be. The only way I could find to get it to draw decently was to specify GUIContent.none for the label, and draw the label myself with a separate call to GUI.Label.

    So, with no further ado, here's my sample code, for a type called AttackStats.Damage, containing two fields called "min" and "max"...

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomPropertyDrawer(typeof(AttackStats.Damage))]
    5. public class AttackStatsDamageDrawer : PropertyDrawer {
    6.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    7.         // Using BeginProperty / EndProperty on the parent property means that
    8.         // prefab override logic works on the entire property.
    9.         EditorGUI.BeginProperty(position, label, property);
    10.        
    11.         // Draw label (and adjust our position to the content area)
    12.         position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
    13.        
    14.         // Calculate rects
    15.         Rect minRect = new Rect(position.x, position.y, position.width/2 - 8, position.height);
    16.         Rect maxRect = new Rect(minRect.xMax + 8, position.y, position.width/2, position.height);
    17.        
    18.         // Draw subfields, using our handy dandy helper method
    19.         Subfield(property, "min", minRect);
    20.         Subfield(property, "max", maxRect);
    21.        
    22.         // Does NOT work, because Unity calculates the label & field widths stupidly:
    23.         //EditorGUI.PropertyField(minRect, property.FindPropertyRelative("min"));
    24.         //EditorGUI.PropertyField(maxRect, property.FindPropertyRelative("max"), new GUIContent("Max"));
    25.        
    26.         EditorGUI.EndProperty();
    27.     }
    28.    
    29.     void Subfield(SerializedProperty property, string fieldName, Rect position, float labelWidth=50) {
    30.         SerializedProperty subprop = property.FindPropertyRelative(fieldName);
    31.         Rect labelR = new Rect(position.x, position.y, labelWidth, position.height);
    32.         Rect fieldR = new Rect(position.x+labelWidth, position.y, position.width-labelWidth, position.height);
    33.         GUI.Label(labelR, subprop.displayName);
    34.         EditorGUI.PropertyField(fieldR, subprop, GUIContent.none);
    35.     }
    36. }
    So I hope this saves the next poor schmuck some time. And though I'm now content with this solution, if I am myself doing something dumb, or there is some key insight I'm still missing, please point it out. I love learning new things!

    Cheers,
    - Joe
     
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    FRACTIONS! Why does posting publicly cause my IQ to jump 20 points?

    Not 30 seconds after posting this, I stumbled upon this key insight. The stupid label width that was giving me so much grief is something you can simply set. And once you do, the simply EditorGUI.PropertyField methods work just fine.

    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(AttackStats.Damage))]
    2. public class AttackStatsDamageDrawer : PropertyDrawer {
    3.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    4.         // Using BeginProperty / EndProperty on the parent property means that
    5.         // prefab override logic works on the entire property.
    6.         EditorGUI.BeginProperty(position, label, property);
    7.        
    8.         // Draw label (and adjust our position to the content area)
    9.         position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
    10.        
    11.         // Calculate rects
    12.         Rect minRect = new Rect(position.x, position.y, position.width/2 - 8, position.height);
    13.         Rect maxRect = new Rect(minRect.xMax + 8, position.y, position.width/2, position.height);
    14.        
    15.          EditorGUIUtility.labelWidth = 40;
    16.         EditorGUI.PropertyField(minRect, property.FindPropertyRelative("min"));
    17.         EditorGUI.PropertyField(maxRect, property.FindPropertyRelative("max"), new GUIContent("Max"));
    18.        
    19.         EditorGUI.EndProperty();
    20.     }
    21. }
    Oh well, live and learn... :)
     
    robot-ink and _ParadisE_ like this.