need help on ReorderableList

Hi lets first look at what i have :
I have a serializable class that has different type of fields (SerializeField), lets call this InlineValue, then I have another class that is serializable and has a List as public field lets call this one NodeValue.
Now, i will use NodeValue as field in other classes like public NodeValue value;

[Serializable]
    public class InlineValue : ICopy<InlineValue>
    {
        [SerializeField] public string name;
        [SerializeField] public bool boolValue;
        [SerializeField] public int intValue;
        [SerializeField] public float floatValue;
        [SerializeField] public string stringValue;
        [SerializeField] public Rect rectValue;
        [SerializeField] public Vector2 vector2Value;
        [SerializeField] public Vector3 vector3Value;
        [SerializeField] public Vector4 vector4Value;
        [SerializeField] public Quaternion quaternionValue;
        [SerializeField] public Color colorValue;
        [SerializeField] public AnimationCurve animationCurveValue;
        [SerializeField] public DataType dataType;
}
[Serializable]
    public class Values
    {
        public List<InlineValue> Value = new List<InlineValue>();
    }
public class Test : MonoBehaviour
    {
        public Values value = new Values();
    }

I would like to draw this list inside the class as a ReorderableList for that test script’s inspector (later on inside another editor window, just using the inspector for faster feedback on when i am tweaking stuff.)
I tried making a custom editor for the Values class, that didn’t show up at all, well, i can understand it because i am adding the test class to game object not the Values class.
so then i tried the property drawer, but i faild because i got tons of errors when i used the property.serializedObject as the first element of the ReorderableList constructor and property as the second param :

[CustomPropertyDrawer(typeof(Values))]
public class PropertyDrawer : UnityEditor.PropertyDrawer
{
    private ReorderableList list;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var serializedObject = property.serializedObject;
        list = new ReorderableList(serializedObject, property, true, true, true, true);
        serializedObject.Update();
        list.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
    }
}

Am i derping here or did i miss something? how to make the list of a class used as a field inside another class use the reorderable editor?

Actually I know what is the mess all about in property drawer take a look at the first error :

Error

Input elements should be an Array SerializedProperty
UnityEngine.Debug:LogError(Object)
UnityEditorInternal.ReorderableList:InitList(SerializedObject, SerializedProperty, IList, Boolean, Boolean, Boolean, Boolean) (at C:/buildslave/unity/build/Editor/Mono/GUI/ReorderableList.cs:259)
UnityEditorInternal.ReorderableList:.ctor(SerializedObject, SerializedProperty, Boolean, Boolean, Boolean, Boolean) (at C:/buildslave/unity/build/Editor/Mono/GUI/ReorderableList.cs:241)
PropertyDrawer:OnGUI(Rect, SerializedProperty, GUIContent) (at Assets/TNP/Common/Editor/PropertyDrawer.cs:15)
UnityEditor.DockArea:OnGUI()

It’s clear as day that the property in the method call for drawer’s OnGUI is not an array and the reorderable list needs obviously, an array or a list.

so how to approach this?

Edit : Now I am really confused, when i say the type of property drawer is Values, or InlineValue (in both cases) , when i look into the available options in intellisense for property inside OnGUI, i see the fields for InlineValue !!! when i say the type of is Value, shouldn’t i see the list instead of a single instance of the base class?

Never mind found my answer somewhere in forums, for those who are wondering here is the whole solution :
This is the very base InlineValue Class :

[Serializable]
    public class InlineValue : ICopy<InlineValue>
    {
        [SerializeField] public string name;
        [SerializeField] public bool boolValue;
        [SerializeField] public int intValue;
        [SerializeField] public float floatValue;
        [SerializeField] public string stringValue;
        [SerializeField] public Rect rectValue;
        [SerializeField] public Vector2 vector2Value;
        [SerializeField] public Vector3 vector3Value;
        [SerializeField] public Vector4 vector4Value;
        [SerializeField] public Quaternion quaternionValue;
        [SerializeField] public Color colorValue;
        [SerializeField] public AnimationCurve animationCurveValue;
        [SerializeField] public DataType dataType;
}

Then i have the Values class :

[Serializable]
public class Values
{
    public List<InlineValue> List = new List<InlineValue>();
}

Then the property drawer for it (Thanks to PrefabEvolution, I just tweaked it a little bit to separate elements a little more)

[CustomPropertyDrawer(typeof(Values))]
    public class ValuesListDrawer : PropertyDrawer
    {
        private ReorderableList list;

        private ReorderableList getList(SerializedProperty property)
        {
            if (list == null)
            {
                list = new ReorderableList(property.serializedObject, property, true, true, true, true);
                list.drawHeaderCallback = (Rect rect) =>
                {
                    EditorGUI.LabelField(rect, "Values");
                };
                list.drawElementCallback = (Rect Rect, int index, bool isActive, bool isFocused) =>
                {
                    var element = list.serializedProperty.GetArrayElementAtIndex(index);
                    //Rect.y += 2;

                    float height = EditorGUIUtility.singleLineHeight;
                    var type = element.FindPropertyRelative("dataType").intValue;
                    EditorGUI.PropertyField(new Rect(Rect.x, Rect.y, 110, height),
                        element.FindPropertyRelative("dataType"), GUIContent.none);
                    float boxSize = ((Rect.width - 120)/4) - 13.1f;
                    switch (type)
                    {
                        case 0: // Bool
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("boolValue"), GUIContent.none);
                            break;
                        case 1: // Int
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("intValue"), GUIContent.none);
                            break;
                        case 2: // Float
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("floatValue"), GUIContent.none);
                            break;
                        case 3: // String
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("stringValue"), GUIContent.none);
                            break;
                        case 4: // Rect
                            EditorGUI.LabelField(new Rect(Rect.x + 110, Rect.y, 13, height), "X");
                            EditorGUI.PropertyField(new Rect(Rect.x + 124, Rect.y, boxSize, height),
                                element.FindPropertyRelative("rectValue.x"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 126 + boxSize, Rect.y, 13, height), "Y");
                            EditorGUI.PropertyField(new Rect(Rect.x + 140 + boxSize, Rect.y, boxSize, height),
                                element.FindPropertyRelative("rectValue.y"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 141 + (2*boxSize), Rect.y, 14, height), "W");
                            EditorGUI.PropertyField(new Rect(Rect.x + 157 + (2*boxSize), Rect.y, boxSize, height),
                                element.FindPropertyRelative("rectValue.width"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 158 + (3*boxSize), Rect.y, 13, height), "H");
                            EditorGUI.PropertyField(new Rect(Rect.x + 172 + (3*boxSize), Rect.y, boxSize, height),
                                element.FindPropertyRelative("rectValue.height"), GUIContent.none);
                            break;
                        case 5: // Vector 2
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("vector2Value"), GUIContent.none);
                            break;
                        case 6: // Vector 3
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("vector3Value"), GUIContent.none);
                            break;
                        case 7: // Vector 4
                            EditorGUI.LabelField(new Rect(Rect.x + 110, Rect.y, 13, height), "X");
                            EditorGUI.PropertyField(new Rect(Rect.x + 124, Rect.y, boxSize, height),
                                element.FindPropertyRelative("vector4Value.x"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 126 + boxSize, Rect.y, 13, height), "Y");
                            EditorGUI.PropertyField(new Rect(Rect.x + 140 + boxSize, Rect.y, boxSize, height),
                                element.FindPropertyRelative("vector4Value.y"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 141 + (2*boxSize), Rect.y, 13, height), "Z");
                            EditorGUI.PropertyField(new Rect(Rect.x + 155 + (2*boxSize), Rect.y, boxSize, height),
                                element.FindPropertyRelative("vector4Value.z"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 156 + (3*boxSize), Rect.y, 14, height), "W");
                            EditorGUI.PropertyField(new Rect(Rect.x + 172 + (3*boxSize), Rect.y, boxSize, height),
                                element.FindPropertyRelative("vector4Value.w"), GUIContent.none);
                            break;
                        case 8: // Quaternion
                            EditorGUI.LabelField(new Rect(Rect.x + 110, Rect.y, 13, height), "X");
                            EditorGUI.PropertyField(new Rect(Rect.x + 124, Rect.y, boxSize, height),
                                element.FindPropertyRelative("quaternionValue.x"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 126 + boxSize, Rect.y, 13, height), "Y");
                            EditorGUI.PropertyField(new Rect(Rect.x + 140 + boxSize, Rect.y, boxSize, height),
                                element.FindPropertyRelative("quaternionValue.y"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 141 + (2*boxSize), Rect.y, 13, height), "Z");
                            EditorGUI.PropertyField(new Rect(Rect.x + 155 + (2*boxSize), Rect.y, boxSize, height),
                                element.FindPropertyRelative("quaternionValue.z"), GUIContent.none);
                            EditorGUI.LabelField(new Rect(Rect.x + 156 + (3*boxSize), Rect.y, 14, height), "W");
                            EditorGUI.PropertyField(new Rect(Rect.x + 172 + (3*boxSize), Rect.y, boxSize, height),
                                element.FindPropertyRelative("quaternionValue.w"), GUIContent.none);
                            break;
                        case 9: // Color
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("colorValue"), GUIContent.none);
                            break;
                        case 10: // Animation Curve
                            EditorGUI.PropertyField(new Rect(Rect.x + 110, Rect.y, Rect.width - 110, height),
                                element.FindPropertyRelative("animationCurveValue"), GUIContent.none);
                            break;
                    }
                };
            }
            return list;
        }

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return getList(property.FindPropertyRelative("List")).GetHeight();
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            var listProperty = property.FindPropertyRelative("List");
            var list = getList(listProperty);
            var height = 0f;
            for (int i = 0; i < listProperty.arraySize; i++)
                height = Mathf.Max(height, EditorGUI.GetPropertyHeight(listProperty.GetArrayElementAtIndex(i))) + 1.5f;
            list.elementHeight = height;
            list.DoList(position);
        }
    }

And Finally the usage :

public class Test : MonoBehaviour
    {
        [SerializeField] private Values Value = new Values();
    }

And now i have my reorderable list that sits nicely not only in inspector but anywhere else that I use that Values class :smile:

1 Like

Thanks for reposting your findings! That helped me get these ReorderableLists working on CustomProperties.

i’m glad it was helful, honestly, I would like if I could go for reflection but the more I think about it, the more it will become a nightmare to use reflection on custom input drawing, oh well, I will see what will happen in future

sigh, can anyone help me figure how to draw this damn thing on a custom editor window instead of inspector? (same class above)

ok, let me explain what is going on, i have a class as inlineValue (first post), and a class for values (first post) that contains a list of inline values, it serializes ok,
now, i have a custom editor window for a node graph, the graph is a scriptable object and nodes are scriptable object too, both marked as serializer too.
here is the problem, in baseNode class (scriptable object) that other nodes will inherit from, i want to use that Values class for meta data, so i have this :

public Values MetaData;

obviously i instantiate it later in the constructor to a new instance of the class. now, i have a method to show the properties of the node inside part of that custom inspector, this method is being called from part of the editor window class :

public virtual void DrawNodeProperties()
        {
            EditorGUILayout.LabelField(Id);

        }

the problem is that i want to use that property drawer we wrote (works in inspector like charm). i know that EditorGUILayout.PropertyField() should be the way, but a big problem is that i don’t know how get access to the serialized property, i can’t use a new instance of serialized property simply because it breaks the connection to the metaData field i have in the class. can anyone help?

Don’t know if this might help but I managed to actually get it to draw on the window by using the EditorGUILayout.PropertyField.

I tried to convert my class into a SerializedObject but that didn’t work, so instead what I did is I made a SerializedObject of the class which holds the property I wanted to draw, the class in this case was a ScriptableObject. After doing so I found the property by using FindProperty() and then drew it on Editor Window using EditorGUILayout.PropertyField(property, true);

//This class uses a custom property drawer in the inspector
[System.Serializable]
public class ItemReader
{
}

public LootTable : ScriptableObject
{
    public List<ItemReader> items;
}

//Editor Window On GUI
//lootTable is the scriptable object i'm looking at or want to draw
SerializedObject so = new SerializedObject(lootTable);
so.Update();
SerializedProperty sp = so.FindProperty("items");
EditorGUILayout.PropertyField(sp, true);
so.ApplyModifierProperties();

I use true in the PropertyField simply because it’s a list and that was the only way to get it showing, seems to work fine hope it works out for you.