如何在检查器中的列表中使用ReorderableList并添加新的空项目和折叠子项? [英] How can I use ReorderableList with a List in the Inspector and adding new empty items and collapse childs?

查看:988
本文介绍了如何在检查器中的列表中使用ReorderableList并添加新的空项目和折叠子项?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这就是我声明列表的方式:

This is how I declared the List :

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

public class ConversationTrigger : MonoBehaviour
{
    public List<Conversation> conversations = new List<Conversation>();

这是使用此列表的编辑器脚本:

This is the editor script using this List :

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

[CustomEditor(typeof(ConversationTrigger))]
public class ConversationTriggerEditor : Editor
{
    private ReorderableList ConversationsList;

    private void OnEnable()
    {
        ConversationTrigger conversationtrigger = (ConversationTrigger)target;
        ConversationsList = new ReorderableList(serializedObject, serializedObject.FindProperty("conversations"), true, true, true, true); 
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        ConversationsList.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
        EditorUtility.SetDirty(target);
    }
}

这是使用ReorderableList之前检查器的屏幕截图.我可以单击一个对话"项并将其折叠,然后在每个孩子上再次将其折叠,依此类推:

This is a screenshot of the Inspector before using the ReorderableList. I could click on a Conversation item and collapse it and then on each child to collapse it again and so on :

此屏幕截图是在使用ReorderableList时:

And this screenshot is when using the ReorderableList :

现在,我只能拖动项目并更改位置,单击+时,它将添加最后一项的重复项.

Now I can only drag the items and change places and when clicking on + it will add a duplication of the last item.

我现在想做的事情至少可以想到三件事:

What I want to do for now I can think of at least 3 things :

  1. 在选择现有项目(例如开幕")时,如果单击+,则在开幕"下添加一个新的空对话"项目.如果我选择了魔术",则在末尾添加一个空的新项;如果选择中间",则在其后的锁间"中添加一个空项.想法是在所选项目之后添加空项目.

  1. When selecting existing item for example Opening and then if clicking on the + add a new empty Conversation item under the Opening. If I selected the Magic add empty new one to the end and if selected the one in the middle Locked Room add empty item after it. The idea is to add empty item after the selected item.

当删除项目时,所选项目相同

When removing item/s same the selected one/s

当双击其中一项或使箭头折叠将折叠并显示所有子项(如我的问题"和其他")中的屏幕快照时,我如何才能做到这一点?

How can I make that when clicking double click on one of the items or making a arrow that will collapse the items and show all the childs like in my screenshot in my question like Opening and others ?

推荐答案

花了我一段时间,但我喜欢EditorScript:D

It took me quite a while but I love EditorScripting :D

假设您的数据结构如下

public class ConversationTrigger : MonoBehaviour
{
    public List<Conversation> conversations;

    public void SaveConversations() { }

    public void LoadConversations() { }
}

[Serializable]
public class Conversation
{
    public string Name;
    public bool Foldout;
    public List<Dialogue> Dialogues;
}

[Serializable]
public class Dialogue
{
    public string Name;
    public bool Foldout;
    public List<string> Sentences;
}

我想出了以下EditorScript.该想法基于> How在嵌套的reordeablelist中选择元素.

I came up with the following EditorScript. The idea is based on a former question of mine on How to select elements in nested reordeablelist.

与EditorScript一样,它具有相当的灵活性.我试图发表很多评论,但如果不清楚,请在评论中问我.

It got quite compley as usual for EditorScript. I tried to comment a lot but if anything is unclear just ask me in the comments.

非常遗憾的是ReorderableList不是文档功能,因为它是如此强大和有用...

It is a huge pity that the ReorderableList is not a documented features since it is so extremly powerful and useful ...

您必须重写多项内容:

  • drawHeaderCallback
  • drawElementCallback
  • elementHeightCallback
  • onAddCallback
  • drawHeaderCallback
  • drawElementCallback
  • elementHeightCallback
  • onAddCallback

并且为了能够与它们嵌套使用,在字典中存储了不同的ReorderableList:

and in order to be able to ineract with them nested store the different ReorderableLists in dictionaries:

[CustomEditor(typeof(ConversationTrigger))]
public class ConversationTriggerEditor : Editor
{
    private ConversationTrigger _conversationTrigger;

    [SerializeField] private ReorderableList conversationsList;

    private SerializedProperty _conversations;

    private int _currentlySelectedConversationIndex = -1;

    private readonly Dictionary<string, ReorderableList> _dialoguesListDict = new Dictionary<string, ReorderableList>();
    private readonly Dictionary<string, ReorderableList> _sentencesListDict = new Dictionary<string, ReorderableList>();

    private void OnEnable()
    {
        _conversationTrigger = (ConversationTrigger)target;
        _conversations = serializedObject.FindProperty("conversations");

        conversationsList = new ReorderableList(serializedObject, _conversations)
        {
            displayAdd = true,
            displayRemove = true,
            draggable = true,

            drawHeaderCallback = DrawConversationsHeader,

            drawElementCallback = DrawConversationsElement,

            onAddCallback = (list) =>
            {
                SerializedProperty addedElement;
                // if something is selected add after that element otherwise on the end
                if (_currentlySelectedConversationIndex >= 0)
                {
                    list.serializedProperty.InsertArrayElementAtIndex(_currentlySelectedConversationIndex + 1);
                    addedElement = list.serializedProperty.GetArrayElementAtIndex(_currentlySelectedConversationIndex + 1);
                }
                else
                {
                    list.serializedProperty.arraySize++;
                    addedElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);
                }

                var name = addedElement.FindPropertyRelative("Name");
                var foldout = addedElement.FindPropertyRelative("Foldout");
                var dialogues = addedElement.FindPropertyRelative("Dialogues");

                name.stringValue = "";
                foldout.boolValue = true;
                dialogues.arraySize = 0;
            },

            elementHeightCallback = (index) =>
            {
                return GetConversationHeight(_conversations.GetArrayElementAtIndex(index));
            }
        };
    }

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

        // if there are no elements reset _currentlySelectedConversationIndex
        if (conversationsList.serializedProperty.arraySize - 1 < _currentlySelectedConversationIndex) _currentlySelectedConversationIndex = -1;

        conversationsList.DoLayoutList();

        if (GUILayout.Button("Save Conversations"))
        {
            _conversationTrigger.SaveConversations();
        }

        if (GUILayout.Button("Load Conversations"))
        {
            Undo.RecordObject(_conversationTrigger, "Loaded conversations from JSON");
            _conversationTrigger.LoadConversations();
        }

        serializedObject.ApplyModifiedProperties();
    }

    #region Drawers

    #region List Headers

    private void DrawConversationsHeader(Rect rect)
    {
        EditorGUI.LabelField(rect, "Conversations");
    }

    private void DrawDialoguesHeader(Rect rect)
    {
        EditorGUI.LabelField(rect, "Dialogues");
    }

    private void DrawSentencesHeader(Rect rect)
    {
        EditorGUI.LabelField(rect, "Sentences");
    }

    #endregion List Headers

    #region Elements

    private void DrawConversationsElement(Rect rect, int index, bool isActive, bool isFocused)
    {
        if (isActive) _currentlySelectedConversationIndex = index;

        var conversation = _conversations.GetArrayElementAtIndex(index);

        var position = new Rect(rect);

        var name = conversation.FindPropertyRelative("Name");
        var foldout = conversation.FindPropertyRelative("Foldout");
        var dialogues = conversation.FindPropertyRelative("Dialogues");
        string dialoguesListKey = conversation.propertyPath;

        EditorGUI.indentLevel++;
        {
            // make the label be a foldout
            foldout.boolValue = EditorGUI.Foldout(new Rect(position.x, position.y, 10, EditorGUIUtility.singleLineHeight), foldout.boolValue, foldout.boolValue ? "" : name.stringValue);

            if (foldout.boolValue)
            {
                // draw the name field
                name.stringValue = EditorGUI.TextField(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight), name.stringValue);
                position.y += EditorGUIUtility.singleLineHeight;

                if (!_dialoguesListDict.ContainsKey(dialoguesListKey))
                {
                    // create reorderabl list and store it in dict
                    var dialoguesList = new ReorderableList(conversation.serializedObject, dialogues)
                    {
                        displayAdd = true,
                        displayRemove = true,
                        draggable = true,

                        drawHeaderCallback = DrawDialoguesHeader,

                        drawElementCallback = (convRect, convIndex, convActive, convFocused) => { DrawDialoguesElement(_dialoguesListDict[dialoguesListKey], convRect, convIndex, convActive, convFocused); },

                        elementHeightCallback = (dialogIndex) =>
                        {
                            return GetDialogueHeight(_dialoguesListDict[dialoguesListKey].serializedProperty.GetArrayElementAtIndex(dialogIndex));
                        },

                        onAddCallback = (list) =>
                        {
                            list.serializedProperty.arraySize++;
                            var addedElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);

                            var newDialoguesName = addedElement.FindPropertyRelative("Name");
                            var newDialoguesFoldout = addedElement.FindPropertyRelative("Foldout");
                            var sentences = addedElement.FindPropertyRelative("Sentences");

                            newDialoguesName.stringValue = "";
                            newDialoguesFoldout.boolValue = true;
                            sentences.arraySize = 0;
                        }
                    };
                    _dialoguesListDict[dialoguesListKey] = dialoguesList;
                }

                _dialoguesListDict[dialoguesListKey].DoList(new Rect(position.x, position.y, position.width, position.height - EditorGUIUtility.singleLineHeight));
            }

        }
        EditorGUI.indentLevel--;
    }

    private void DrawDialoguesElement(ReorderableList list, Rect rect, int index, bool isActive, bool isFocused)
    {
        if (list == null) return;

        var dialog = list.serializedProperty.GetArrayElementAtIndex(index);

        var position = new Rect(rect);

        var foldout = dialog.FindPropertyRelative("Foldout");
        var name = dialog.FindPropertyRelative("Name");

        {
            // make the label be a foldout
            foldout.boolValue = EditorGUI.Foldout(new Rect(position.x, position.y, 10, EditorGUIUtility.singleLineHeight), foldout.boolValue, foldout.boolValue ? "" : name.stringValue);

            var sentencesListKey = dialog.propertyPath;
            var sentences = dialog.FindPropertyRelative("Sentences");

            if (foldout.boolValue)
            {
                // draw the name field
                name.stringValue = EditorGUI.TextField(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight), name.stringValue);
                position.y += EditorGUIUtility.singleLineHeight;

                if (!_sentencesListDict.ContainsKey(sentencesListKey))
                {
                    // create reorderabl list and store it in dict
                    var sentencesList = new ReorderableList(sentences.serializedObject, sentences)
                    {
                        displayAdd = true,
                        displayRemove = true,
                        draggable = true,

                        // header for the dialog list
                        drawHeaderCallback = DrawSentencesHeader,

                        // how a sentence is displayed
                        drawElementCallback = (sentenceRect, sentenceIndex, sentenceIsActive, sentenceIsFocused) =>
                        {
                            var sentence = sentences.GetArrayElementAtIndex(sentenceIndex);

                            // draw simple textArea for sentence
                            sentence.stringValue = EditorGUI.TextArea(sentenceRect, sentence.stringValue);
                        },

                        // Sentences have simply a fixed height of 2 lines
                        elementHeight = EditorGUIUtility.singleLineHeight * 2,

                        // when a sentence is added
                        onAddCallback = (sentList) =>
                        {
                            sentList.serializedProperty.arraySize++;
                            var addedElement = sentList.serializedProperty.GetArrayElementAtIndex(sentList.serializedProperty.arraySize - 1);      

                            addedElement.stringValue = "";
                        }
                    };

                    // store the created ReorderableList
                    _sentencesListDict[sentencesListKey] = sentencesList;
                }

                // Draw the list
                _sentencesListDict[sentencesListKey].DoList(new Rect(position.x, position.y, position.width, position.height - EditorGUIUtility.singleLineHeight));
            }
        }
    }

    #endregion Elements

    #endregion Drawers


    #region Helpers

    #region HeightGetter

    /// <summary>
    /// Returns the height of given Conversation property
    /// </summary>
    /// <param name="conversation"></param>
    /// <returns>height of given Conversation property</returns>
    private float GetConversationHeight(SerializedProperty conversation)
    {
        var foldout = conversation.FindPropertyRelative("Foldout");

        // if not foldout the height is simply 1 line
        var height = EditorGUIUtility.singleLineHeight;

        // otherwise we sum up every controls and child heights
        if (foldout.boolValue)
        {
            // we need some more lines:
            //  for the Name field,
            // the list header,
            // the list buttons and a bit buffer
            height += EditorGUIUtility.singleLineHeight * 5;


            var dialogues = conversation.FindPropertyRelative("Dialogues");

            for (var d = 0; d < dialogues.arraySize; d++)
            {
                var dialog = dialogues.GetArrayElementAtIndex(d);
                height += GetDialogueHeight(dialog);
            }
        }

        return height;
    }

    /// <summary>
    /// Returns the height of given Dialogue property
    /// </summary>
    /// <param name="dialog"></param>
    /// <returns>height of given Dialogue property</returns>
    private float GetDialogueHeight(SerializedProperty dialog)
    {
        var foldout = dialog.FindPropertyRelative("Foldout");

        // same game for the dialog if not foldout it is only a single line
        var height = EditorGUIUtility.singleLineHeight;

        // otherwise sum up controls and child heights
        if (foldout.boolValue)
        {
            // we need some more lines:
            //  for the Name field,
            // the list header,
            // the list buttons and a bit buffer
            height += EditorGUIUtility.singleLineHeight * 4;

            var sentences = dialog.FindPropertyRelative("Sentences");

            // the sentences are easier since they always have the same height
            // in this example 2 lines so simply do
            // at least have space for 1 sentences even if there is none
            height += EditorGUIUtility.singleLineHeight * Mathf.Max(1, sentences.arraySize) * 2;
        }

        return height;
    }

    #endregion

    #endregion Helpers
}


添加新的会话,为其命名,然后对其进行折叠.如果选择了对话,请在其后添加新对话


Adding new Conversations, give them names, fold them. If a conversation is selected add the new conversation after it

添加对话和句子,仍然能够重新排列和(展开)所有内容

Adding dialogues and sentences and still being able to rearrange and (un)fold everything

这篇关于如何在检查器中的列表中使用ReorderableList并添加新的空项目和折叠子项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆