Search code examples
c#unity-game-engineunity3d-editorunity-editor

How to select elements in nested ReorderableList in a CustomEditor?


I have a ReorderableList in my CustomEditor script. In the drawElementCallback I added a second nested ReorderableList. Everything works fine and I can add elements to both lists like here

enter image description here

BUT as you can see for some reason I can not select the elements of the inner ReorderableLists so I also can not remove items.

How can I select items in the inner list?


Here the classes broken down to the basic example

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

[Serializable]
public class SomeClass
{
    public string Name;
    public List<SomeClass> InnerList;
}

[CreateAssetMenu(menuName = "Example", fileName = "new Example Asset")]
public class Example : ScriptableObject
{
    public List<SomeClass> SomeClasses;

    [CustomEditor(typeof(Example))]
    private class ModuleDrawer : Editor
    {
        private SerializedProperty SomeClasses;
        private ReorderableList list;

        private void OnEnable()
        {
            SomeClasses = serializedObject.FindProperty("SomeClasses");

            // setupt the outer list
            list = new ReorderableList(serializedObject, SomeClasses)
            {
                displayAdd = true,
                displayRemove = true,
                draggable = true,

                drawHeaderCallback = rect =>
                {
                    EditorGUI.LabelField(rect, "Outer List");
                },

                drawElementCallback = (rect, index, a, h) =>
                {
                    // get outer element
                    var element = SomeClasses.GetArrayElementAtIndex(index);

                    var InnerList = element.FindPropertyRelative("InnerList");

                    // Setup the inner list
                    var innerReorderableList = new ReorderableList(element.serializedObject, InnerList)
                    {
                        displayAdd = true,
                        displayRemove = true,
                        draggable = true,

                        drawHeaderCallback = innerRect =>
                        {
                            EditorGUI.LabelField(innerRect, "Inner List");
                        },

                        drawElementCallback = (innerRect, innerIndex, innerA, innerH) =>
                        {
                            // Get element of inner list
                            var innerElement = InnerList.GetArrayElementAtIndex(innerIndex);

                            var name = innerElement.FindPropertyRelative("Name");

                            EditorGUI.PropertyField(innerRect, name);
                        }
                    };

                    var height = (InnerList.arraySize + 3) * EditorGUIUtility.singleLineHeight;
                    innerReorderableList.DoList(new Rect(rect.x, rect.y, rect.width, height));
                },

                elementHeightCallback = index =>
                {
                    var element = SomeClasses.GetArrayElementAtIndex(index);

                    var innerList = element.FindPropertyRelative("InnerList");

                    return (innerList.arraySize + 4) * EditorGUIUtility.singleLineHeight;
                }
            };
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            list.DoLayoutList();

            serializedObject.ApplyModifiedProperties();
        }
    }
}

Update

The same apprantly also doesn't work if I have a ReorderableList within a CustomPropertyDrawer.

But to my suprise if I have a UnityEvent both works: Having it inside a CustomPropertyDrawer or within a ReorderableList. I can add and remove items as expected.

As you can see here I added a UnityEvent field by adding

[Serializable]
public class SomeClass
{
    public string Name;
    public List<SomeClass> InnerList;
}

and using

//...
innerReorderableList.DoList(new Rect(rect.x, rect.y, rect.width, height));

var InnerEvent = element.FindPropertyRelative("InnerEvent");
var pers = InnerEvent.FindPropertyRelative("m_PersistentCalls.m_Calls");
var evHeight = (Mathf.Max(1, pers.arraySize) * 2 + 3) * EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, evHeight), InnerEvent);

and adjusting the elementHeightCallback to

elementHeightCallback = index =>
{
    var element = SomeClasses.GetArrayElementAtIndex(index);

    var innerList = element.FindPropertyRelative("InnerList");
    var InnerEvent = element.FindPropertyRelative("InnerEvent");
    var pers = InnerEvent.FindPropertyRelative("m_PersistentCalls.m_Calls");

    return (Mathf.Max(1, innerList.arraySize) + 4 + Mathf.Max(1, pers.arraySize) * 2 + 4) * EditorGUIUtility.singleLineHeight;
}

I can fully interact with it as expected and also select and remove entries.

enter image description here

So what are they doing different?


Solution

  • It looks like you are creating the inner lists over and over without storing them anywhere. I modified your code to store the reorderable lists in a dictionary with the element.propertyPath as a key. Hope this helps.

    using System;
    using System.Collections.Generic;
    using UnityEditor;
    using UnityEditorInternal;
    using UnityEngine;
    
    [Serializable]
    public class SomeClass
    {
        public string Name;
        public List<SomeClass> InnerList;
    }
    
    [CreateAssetMenu(menuName = "Example", fileName = "new Example Asset")]
    public class Example : ScriptableObject
    {
        public List<SomeClass> SomeClasses;
    
        [CustomEditor(typeof(Example))]
        private class ModuleDrawer : Editor
        {
            private SerializedProperty SomeClasses;
            private ReorderableList list;
    
            private Dictionary<string, ReorderableList> innerListDict = new Dictionary<string, ReorderableList>();
    
            private void OnEnable()
            {
                SomeClasses = serializedObject.FindProperty("SomeClasses");
    
                // setupt the outer list
                list = new ReorderableList(serializedObject, SomeClasses)
                {
                    displayAdd = true,
                    displayRemove = true,
                    draggable = true,
    
                    drawHeaderCallback = rect =>
                    {
                        EditorGUI.LabelField(rect, "Outer List");
                    },
    
                    drawElementCallback = (rect, index, a, h) =>
                    {
                        // get outer element
                        var element = SomeClasses.GetArrayElementAtIndex(index);
    
                        var InnerList = element.FindPropertyRelative("InnerList");
    
                        string listKey = element.propertyPath;
    
                        ReorderableList innerReorderableList;
    
                        if (innerListDict.ContainsKey(listKey))
                        {
                            // fetch the reorderable list in dict
                            innerReorderableList = innerListDict[listKey];
                        }
                        else
                        {
                            // create reorderabl list and store it in dict
                            innerReorderableList = new ReorderableList(element.serializedObject, InnerList)
                            {
                                displayAdd = true,
                                displayRemove = true,
                                draggable = true,
    
                                drawHeaderCallback = innerRect =>
                                {
                                    EditorGUI.LabelField(innerRect, "Inner List");
                                },
    
                                drawElementCallback = (innerRect, innerIndex, innerA, innerH) =>
                                {
                                    // Get element of inner list
                                    var innerElement = InnerList.GetArrayElementAtIndex(innerIndex);
    
                                    var name = innerElement.FindPropertyRelative("Name");
    
                                    EditorGUI.PropertyField(innerRect, name);
                                }
                            };
                            innerListDict[listKey] = innerReorderableList;
                        }
    
                        // Setup the inner list
                        var height = (InnerList.arraySize + 3) * EditorGUIUtility.singleLineHeight;
                        innerReorderableList.DoList(new Rect(rect.x, rect.y, rect.width, height));
                    },
    
                    elementHeightCallback = index =>
                    {
                        var element = SomeClasses.GetArrayElementAtIndex(index);
    
                        var innerList = element.FindPropertyRelative("InnerList");
    
                        return (innerList.arraySize + 4) * EditorGUIUtility.singleLineHeight;
                    }
                };
            }
    
            public override void OnInspectorGUI()
            {
                serializedObject.Update();
    
                list.DoLayoutList();
    
                serializedObject.ApplyModifiedProperties();
            }
        }
    }