9

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?

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • I suppose ModulesManager is the class Example? What is AvailableModules? (trying to play around with it) – AVAVT Feb 13 '19 at 12:58
  • @AVAVT sorry updated the code ^^ you assumed correctly and `AvailableModules` here is `SomeClasses` – derHugo Feb 13 '19 at 13:27
  • I'm checking it out, but why are you using this... structure though? It reeks of infinite recursion, and you're using only 1 property at each level? – AVAVT Feb 13 '19 at 14:18
  • I only wnat one level because (therefor the original variable names) it is kind of a modules manager. A module has a name and a List of other Modules it depends on. So actually I only display the name but add a Module including its own dependencies etc. I do this using a PopupField with the available names and than get the according module by index :) I'm only stuck on this nested list problem that I can't select the entries once added. – derHugo Feb 13 '19 at 14:52

1 Answers1

13

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();
        }
    }
}
Tobias K.
  • 561
  • 4
  • 6
  • Man you are awesome ^^ How long I have been sitting on this now ... not seeing that it could have been so simple! Take your earned reward ;) – derHugo Feb 14 '19 at 14:40
  • Haha, well we all know that feeling. :D – Tobias K. Feb 14 '19 at 14:49
  • Just for others, For me it was problematic inside a property drawer. As stated in @Tobias K. answer, I just had to store the reference to the ReorderedList in a dict, to ensure I wasn't recreating it every update. that solved the problem. Thanks! – Adam B Sep 11 '20 at 02:27