0

I can't find how to add an array of gameObjects to an Editor window in the new Unity UI ToolKit like in the picture:

enter image description here

I don't find an array component in the UI Builder library and the ListView does not seem to be the right choice for me.

derHugo
  • 83,094
  • 9
  • 75
  • 115
ouzari
  • 397
  • 3
  • 12
  • Does a `List` not work when serialized with `[SerializeField] List prefabs`?, thats the standard way to expose a list into the inspector (same can be done with an array) – akaBase May 08 '23 at 08:59
  • [SerializeField] List prefabs works in the inspector but I want the same result in an Editor window – ouzari May 08 '23 at 09:17
  • As an [EditorWindow](https://docs.unity3d.com/ScriptReference/EditorWindow.html) basically is derived from `ScriptableObject` you should actually be able to do exactly the same as you would for an Inspector of e.g. a `MonoBehaviour` or `ScriptableObject` ... – derHugo May 08 '23 at 12:46
  • As I said, I am using the new Unity UI Toolkit, and I did not find an example of adding an array (or list) to an Editor window. Can give me an example please? – ouzari May 08 '23 at 13:18

2 Answers2

2

I'm not very familiar with the "new" UIToolkit (still not getting warm with it ^^) but this seems to work for me

public class TEstEditorWindow : EditorWindow
{
    private List<GameObject> prefabs;
    private ListView list;

    [MenuItem("TEEST/Testitest")]
    public static void Open()
    {
        var window = GetWindow<TEstEditorWindow>();
        window.Show();
    }

    private void CreateGUI()
    {
        DrawReorderableList(prefabs, rootVisualElement);
    }

    // jut making this static so it can be generalized and used everywhere
    public static void DrawReorderableList<T>(List<T> sourceList, VisualElement rootVisualElement, bool allowSceneObjects = true) where T : UnityEngine.Object
    {
        var list = new ListView(sourceList)
        {
            virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight,
            showFoldoutHeader = true,
            headerTitle = "Prefabs",
            showAddRemoveFooter = true,
            reorderMode = ListViewReorderMode.Animated,
            makeItem = () => new ObjectField
            {
                objectType = typeof(T),
                allowSceneObjects = allowSceneObjects
            },
            bindItem = (element, i) =>
            {
                ((ObjectField)element).value = sourceList[i];
                ((ObjectField)element).RegisterValueChangedCallback((value) =>
                {
                    sourceList[i] = (T)value.newValue;
                });
            }
        };

        rootVisualElement.Add(list);
    }
}

I sure hope there is a better an more stable way to do this though ^^

enter image description here

See also

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Thank you very much, that's what I needed, even if I have a problem of keeping the state of the list when adding objects but I will try to find the solution. – ouzari May 08 '23 at 14:09
  • @ouzari yeah I also got some exceptions when re-ordering stuff .. probably have to play around with the bindings a little more – derHugo May 08 '23 at 14:13
  • @ouzari alternatively look at bottom ... you can just go through the "old" way for this one property which uses the default drawer for lists => works with any type of list and doesn't have any weird edge cases we would first have to cover in the upper implementation ^^ – derHugo May 08 '23 at 14:20
1

Easier way via ProperyField directly

public class TEstEditorWindow : EditorWindow
{
    [SerializeField] private List<GameObject> prefabs;

    [MenuItem("TEEST/Testitest")]
    public static void Open()
    {
        var window = GetWindow<TEstEditorWindow>();
        window.Show();
    }

    private void CreateGUI()
    {
        var serializedObject = new SerializedObject(this);
        var property = serializedObject.FindProperty(nameof(prefabs));

        var field = new PropertyField(property);
        field.Bind(serializedObject);

        rootVisualElement.Add(field);
    }
}

Alternatively

Just for this property you could go through IMGUI and do

public class TEstEditorWindow : EditorWindow
{
    [SerializeField] private List<GameObject> prefabs;

    [MenuItem("TEEST/Testitest")]
    public static void Open()
    {
        var window = GetWindow<TstEditorWindow>();
        window.Show();
    }

    private void CreateGUI()
    {
        var container = new IMGUIContainer(() =>
        {
            var serializedObject = new SerializedObject(this);
            var property = serializedObject.FindProperty(nameof(prefabs));
            serializedObject.Update();
            EditorGUILayout.PropertyField(property);
            serializedObject.ApplyModifiedProperties();
        });

        rootVisualElement.Add(container);
    }
}

until there is better support for such lists.

This simply uses the default list drawer and works for any type of list (as long as type is serializable of course) and is more stable then re-inventing it via the ListView

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Thank you again. I tried this method but I can't choose a gameObject or prefab and when I close the Editor window and open it again, it does not keep the empty list elements. – ouzari May 08 '23 at 14:36
  • @ouzari and EditorWindow is not meant to maintain objects no ... for that you would need to actually create and store values e.g. into an actual `ScriptableObject` instance (that's e.g. how some of the project settings work -> https://docs.unity3d.com/ScriptReference/SettingsProvider.html) – derHugo May 08 '23 at 14:37
  • OK, I will see that. Thanks. – ouzari May 08 '23 at 14:40
  • @ouzari found yet an easier way ^^ see top of this answer now ... but yeah in general every time you open an editor window you open a new instance of it with a new empty list – derHugo May 08 '23 at 14:51