Search Unity

[SOLVED] PropertyDrawer on Array elements with different heights?

Discussion in 'Immediate Mode GUI (IMGUI)' started by Eldoir, Jul 25, 2018.

  1. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    60
    Hello there,
    I've been struggling with this for an hour now.

    I have an array of GrassTexture elements,
    And I want each GrassTexture to be drawn with a PropertyDrawer.
    There is a variable in GrassTexture, "useDifferentSettings", that shows other variables of the class if its toggle is checked.
    So, I will have an array of GrassTextures, and each GrassTexture will have its own height, depending on this toggle.

    But I've read Unity uses only ONE instance of the drawer to draw all my elements, so I guess this is a problem for me.
    I've overriden GetPropertyHeight, but it seems it can only return a single value for every element, because of that "one instance" thing.

    Let's say I have 3 elements in my array, respectively of height 34, 50 and 86 (depending on boolean values, for example).
    It seems my PropertyDrawer will draw each element with a height of 86, which is the height of the last element.

    Some code and screens below:

    My Drawer:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomPropertyDrawer(typeof(GrassTexture))]
    5. public class GrassTextureDrawer : PropertyDrawer
    6. {
    7.     private Rect position;
    8.     private const int lineHeight = 16;
    9.     private int totalPropertyHeight = 0;
    10.     private int marginBetweenFields;
    11.  
    12.  
    13.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    14.     {
    15.         this.position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), GUIContent.none);
    16.         this.position.height = lineHeight;
    17.         totalPropertyHeight = lineHeight;
    18.  
    19.         marginBetweenFields = (int)EditorGUIUtility.standardVerticalSpacing;
    20.  
    21.         SerializedProperty newTexture = property.FindPropertyRelative("newTexture");
    22.         SerializedProperty useDifferentSettings = property.FindPropertyRelative("useDifferentSettings");
    23.         SerializedProperty minWidth = property.FindPropertyRelative("minWidth");
    24.         SerializedProperty maxWidth = property.FindPropertyRelative("maxWidth");
    25.         SerializedProperty minHeight = property.FindPropertyRelative("minHeight");
    26.         SerializedProperty maxHeight = property.FindPropertyRelative("maxHeight");
    27.         SerializedProperty noiseSpread = property.FindPropertyRelative("noiseSpread");
    28.         SerializedProperty healthyColor = property.FindPropertyRelative("healthyColor");
    29.         SerializedProperty dryColor = property.FindPropertyRelative("dryColor");
    30.         SerializedProperty billboard = property.FindPropertyRelative("billboard");
    31.  
    32.         int indentWidth = 15 * EditorGUI.indentLevel;
    33.         this.position.x -= indentWidth;
    34.         this.position.width += indentWidth;
    35.  
    36.         DisplayPropertyField(newTexture);
    37.         DisplayPropertyField(useDifferentSettings, spaceBelow: false);
    38.  
    39.         if (useDifferentSettings.boolValue)
    40.         {
    41.             AddToPositionY(lineHeight + 8);
    42.  
    43.             DisplayPropertyField(minWidth);
    44.             DisplayPropertyField(maxWidth);
    45.             DisplayPropertyField(minHeight);
    46.             DisplayPropertyField(maxHeight);
    47.             DisplayPropertyField(noiseSpread);
    48.             DisplayPropertyField(healthyColor);
    49.             DisplayPropertyField(dryColor);
    50.             DisplayPropertyField(billboard, spaceBelow: false);
    51.         }
    52.     }
    53.  
    54.     private void DisplayPropertyField(SerializedProperty property, bool spaceBelow = true)
    55.     {
    56.         EditorGUI.PropertyField(position, property);
    57.  
    58.         if (spaceBelow)
    59.         {
    60.             AddToPositionY(lineHeight + marginBetweenFields);
    61.         }
    62.     }
    63.  
    64.     private void AddToPositionY(int addY)
    65.     {
    66.         position.y += addY;
    67.         totalPropertyHeight += addY;
    68.     }
    69.  
    70.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    71.     {
    72.         return totalPropertyHeight;
    73.     }
    74. }
    My GrassTexture class:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [System.Serializable]
    4. public class GrassTexture
    5. {
    6.     [SerializeField]
    7.     private Texture2D newTexture = null;
    8.  
    9.     [SerializeField]
    10.     private bool useDifferentSettings;
    11.  
    12.     [SerializeField]
    13.     private float minWidth = 0.5f;
    14.  
    15.     [SerializeField]
    16.     private float maxWidth = 1f;
    17.  
    18.     [SerializeField]
    19.     private float minHeight = 0.5f;
    20.  
    21.     [SerializeField]
    22.     private float maxHeight = 1f;
    23.  
    24.     [SerializeField]
    25.     private float noiseSpread = 0.1f;
    26.  
    27.     [SerializeField]
    28.     private Color healthyColor = Color.gray;
    29.  
    30.     [SerializeField]
    31.     private Color dryColor = Color.yellow;
    32.  
    33.     [SerializeField]
    34.     private bool billboard = true;
    35. }
     

    Attached Files:

  2. Estecka

    Estecka

    Joined:
    Oct 11, 2013
    Posts:
    62
    I think your use of `GetPropertyHeigt()` is wrong.

    The way it works, for each time the propertydrawer is drawn, `GetPropertyHeight` is called BEFORE `OnGUI`. But the way you you built it, you are calculating the height in OnGUI (too late), and then have GetPropertyHeight return it.

    As a result, your GetPropertyHeigh is returning the height of the previously drawn property instead of the current one.

    In order to fix that, you need `GetPropertyHeight()` to search for the relevant propertyrelatives and calculate the height there, instead of relying on OnGUI
     
    mr_holymood likes this.
  3. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    60
    Hello Estecka,
    Thanks for your reply! :)
    I tried what you suggested with the following code.
    Basically, I just count the SerializedProperties I have to display, in order to calculate the height dynamically this time.
    The fix is dirty, as I just wanted to test if it worked.
    And it does not, unfortunately :( What did I do wrong?
    P.S : I'm sorry for the long lines of code, I didn't find any option to show/hide it.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomPropertyDrawer(typeof(GrassTexture))]
    5. public class GrassTextureDrawer : PropertyDrawer
    6. {
    7.     private Rect position;
    8.     private const int lineHeight = 16;
    9.     private int totalPropertyHeight = 0;
    10.     private int marginBetweenFields;
    11.  
    12.     SerializedProperty newTexture, useDifferentSettings, minWidth,
    13.      maxWidth, minHeight, maxHeight, noiseSpread, healthyColor, dryColor, billboard;
    14.  
    15.  
    16.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    17.     {
    18.         this.position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), GUIContent.none);
    19.         this.position.height = lineHeight;
    20.         totalPropertyHeight = lineHeight;
    21.  
    22.         marginBetweenFields = (int)EditorGUIUtility.standardVerticalSpacing;
    23.  
    24.         newTexture = property.FindPropertyRelative("newTexture");
    25.         useDifferentSettings = property.FindPropertyRelative("useDifferentSettings");
    26.         minWidth = property.FindPropertyRelative("minWidth");
    27.         maxWidth = property.FindPropertyRelative("maxWidth");
    28.         minHeight = property.FindPropertyRelative("minHeight");
    29.         maxHeight = property.FindPropertyRelative("maxHeight");
    30.         noiseSpread = property.FindPropertyRelative("noiseSpread");
    31.         healthyColor = property.FindPropertyRelative("healthyColor");
    32.         dryColor = property.FindPropertyRelative("dryColor");
    33.         billboard = property.FindPropertyRelative("billboard");
    34.  
    35.         int indentWidth = 15 * EditorGUI.indentLevel;
    36.         this.position.x -= indentWidth;
    37.         this.position.width += indentWidth;
    38.  
    39.         DisplayPropertyField(newTexture);
    40.         DisplayPropertyField(useDifferentSettings, spaceBelow: false);
    41.  
    42.         if (useDifferentSettings.boolValue)
    43.         {
    44.             AddToPositionY(lineHeight + 8);
    45.  
    46.             DisplayPropertyField(minWidth);
    47.             DisplayPropertyField(maxWidth);
    48.             DisplayPropertyField(minHeight);
    49.             DisplayPropertyField(maxHeight);
    50.             DisplayPropertyField(noiseSpread);
    51.             DisplayPropertyField(healthyColor);
    52.             DisplayPropertyField(dryColor);
    53.             DisplayPropertyField(billboard, spaceBelow: false);
    54.         }
    55.     }
    56.  
    57.     private float GetHeight()
    58.     {
    59.         return (lineHeight + marginBetweenFields) * (useDifferentSettings.boolValue ? 11 : 3);
    60.     }
    61.  
    62.     private void DisplayPropertyField(SerializedProperty property, bool spaceBelow = true)
    63.     {
    64.         EditorGUI.PropertyField(position, property);
    65.  
    66.         if (spaceBelow)
    67.         {
    68.             AddToPositionY(lineHeight + marginBetweenFields);
    69.         }
    70.     }
    71.  
    72.     private void AddToPositionY(int addY)
    73.     {
    74.         position.y += addY;
    75.         totalPropertyHeight += addY;
    76.     }
    77.  
    78.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    79.     {
    80.         if (useDifferentSettings == null) return 0f;
    81.         return GetHeight();
    82.     }
    83. }
     
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    You aren't setting useDifferentSettings.
     
  5. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    60
    Hello @brownboot67,
    Thanks for your reply :)
    In fact, since Estecka told me that GetPropertyHeight was called before OnGUI, I simply made a nullcheck at line 80, and I was thinking that the next time the drawer would call GetPropertyHeight, useDifferentSettings would have been initialized in OnGUI, and I could be able to call GetHeight().
    Unfortunately, that last code I posted has the same result as my first try.
     
  6. Estecka

    Estecka

    Joined:
    Oct 11, 2013
    Posts:
    62
    You still have the exact same problem as before in the new code; GetPropertyHeight is still using data from the previous draw instead of the current one :
    You're using a SerializedProperty to figure out the height, but this SerializedProperty is still assigned in OnGUI -after the relevant GetPropertyHeight has been called.

    I've only read your code in diagonal, but I think a quick fix would be along these lines. :
    Code (CSharp):
    1.  public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    2.     {
    3.         useDifferentSettings = property.FindPropertyRelative("useDifferentSettings") // Assign it here, not then
    4.         return GetHeight();
    5.     }
    The bottom line is : GetPropertyHeight should not use anything that is assigned in OnGUI, have GetPropertyHeight assign everything by itself.
     
    Last edited: Jul 27, 2018
    Atomiz2002 and Eldoir like this.
  7. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    60
  8. unity_5jYQAkUl6nE_JQ

    unity_5jYQAkUl6nE_JQ

    Joined:
    Apr 13, 2018
    Posts:
    1
    I was stuck on this (and another issue) all day. -my main issue was that I was returning a height of 0f from GetPropertyHeight(), causing my editor to freeze.

    but to solve the dynamic property size within an array, I did the following;

    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. public class DemoPropertyDemo : MonoBehaviour
    6. {
    7.     public DemoProperty[] DemoProperties;
    8. }
    9.  
    10.  
    11. [System.Serializable]
    12. public class DemoProperty
    13. {
    14.     public float totalHeight = 20;
    15.  
    16.     public int variableHeight = 1; // Number of rows (try adding several different variables here instead;
    17.     // each variable requires
    18.     // - EditorGUI.PropertyField
    19.     // - position.y += *height of the variable line*
    20.     // - add that *height of the variable line*, or keep an count or lines if they are all the same height.
    21.     // I suggest creating a local Rect if you want to change the height, x position or width of the property field
    22. }
    23.  
    24.  
    25. [CustomPropertyDrawer(typeof(DemoProperty))]
    26. public class DemoPropertyDrawer : PropertyDrawer
    27. {
    28.     float lineHeight = 20; // Height of each row
    29.  
    30.     // SerializedProperty variableHeight, and other Serialized properties can be stored here
    31.                                         //if you have many and want to break into smaller methods
    32.  
    33.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    34.     {
    35.         // Getting this here AND in GetPropertyHeight solved my dynamic height issue
    36.         SerializedProperty totalHeight = property.FindPropertyRelative("totalHeight");
    37.         SerializedProperty variableHeight = property.FindPropertyRelative("variableHeight");
    38.  
    39.         int lines = 1; // alternatively, keep a float total of the height as you go, if you're using many different line heights
    40.         position.y += lineHeight;
    41.  
    42.         Rect labelRect = position;
    43.         labelRect.height = lineHeight;
    44.         EditorGUI.PropertyField(labelRect, variableHeight, new GUIContent());
    45.         position.y += lineHeight;
    46.         lines++;
    47.  
    48.         for (int i = 0; i < variableHeight.intValue; i++)
    49.         {
    50.             labelRect = position;
    51.             labelRect.height = lineHeight; // only required if different from previous setting
    52.             EditorGUI.LabelField(labelRect, "Variable Line number: " + (i + 1).ToString());
    53.             position.y += lineHeight;
    54.             lines++;
    55.         }
    56.  
    57.         float newHeight = lineHeight * lines;
    58.         if (totalHeight.floatValue != newHeight)
    59.             totalHeight.floatValue = newHeight;
    60.     }
    61.  
    62.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    63.     {
    64.         SerializedProperty totalHeight = property.FindPropertyRelative("totalHeight");
    65.  
    66.         if (totalHeight != null)
    67.         {
    68.             float height = Mathf.Max(20, totalHeight.floatValue); // Height of 0 will freeze the editor
    69.  
    70.             return height;
    71.         }
    72.  
    73.         return base.GetPropertyHeight(property, label);
    74.     }
    75. }
    76.  
     
  9. Kazko

    Kazko

    Joined:
    Apr 2, 2014
    Posts:
    82
    This sentence should be in bold at the top of the manual for this method! Thank you for releasing my mind from a three day struggle!
     
  10. Nxs

    Nxs

    Joined:
    Aug 29, 2019
    Posts:
    4
    OMFG.
    I can't describe how many days I've been googling this stuff. Almost every source online shows examples of people setting variables during OnGUI() and it's been driving me insane recently. You single-handedly revived my sanity.