/// <summary> /// Adds a fold-out list GUI from a generic list of any serialized object type /// </summary> /// <param name="list">A generic List</param> /// <param name="expanded">A bool to determine the state of the primary fold-out</param> public static bool FoldOutTextList(string label, List <string> list, bool expanded) { // Store the previous indent and return the flow to it at the end int indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; // A copy of toolbarButton with left alignment for foldouts var foldoutStyle = new GUIStyle(EditorStyles.toolbarButton); foldoutStyle.alignment = TextAnchor.MiddleLeft; expanded = AddFoldOutListHeader <string>(label, list, expanded, indent); // START. Will consist of one row with two columns. // The second column has the content EditorGUILayout.BeginHorizontal(); // SPACER COLUMN / INDENT EditorGUILayout.BeginVertical(); EditorGUILayout.BeginHorizontal(GUILayout.MinWidth((indent + 3) * 9)); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); // CONTENT COLUMN... EditorGUILayout.BeginVertical(); // Use a for, instead of foreach, to avoid the iterator since we will be // be changing the loop in place when buttons are pressed. Even legal // changes can throw an error when changes are detected for (int i = 0; i < list.Count; i++) { string item = list[i]; EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); // FIELD... if (item == null) { item = ""; } list[i] = EditorGUILayout.TextField(item); LIST_BUTTONS listButtonPressed = AddFoldOutListItemButtons(); EditorGUILayout.EndHorizontal(); GUILayout.Space(2); UpdateFoldOutListOnButtonPressed <string>(list, i, listButtonPressed); } EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel = indent; return(expanded); }
/// <summary> /// Adds a fold-out list GUI from a generic list of any serialized object type. /// Uses System.Reflection to add all fields for a passed serialized object /// instance. Handles most basic types including automatic naming like the /// inspector does by default /// /// Adds collapseBools (see docs below) /// </summary> /// <param name="label"> The field label</param> /// <param name="list">A generic List</param> /// <param name="expanded">A bool to determine the state of the primary fold-out</param> /// <param name="foldOutStates">Dictionary<object, bool> used to track list item states</param> /// <param name="collapseBools"> /// If true, bools on list items will collapse fields which follow them /// </param> /// <returns>The new foldout state from user input. Just like Unity's foldout</returns> public static bool SerializedObjFoldOutList <T>(string label, List <T> list, bool expanded, ref Dictionary <object, bool> foldOutStates, bool collapseBools) where T : new() { // Store the previous indent and return the flow to it at the end int indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; int buttonSpacer = 6; #region Header Foldout // Use a Horizanal space or the toolbar will extend to the left no matter what EditorGUILayout.BeginHorizontal(); EditorGUI.indentLevel = 0; // Space will handle this for the header GUILayout.Space(indent * 6); // Matches the content indent EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); expanded = Foldout(expanded, label); if (!expanded) { // Don't add the '+' button when the contents are collapsed. Just quit. EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel = indent; // Return to the last indent return(expanded); } // BUTTONS... EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(100)); // Add expand/collapse buttons if there are items in the list bool masterCollapse = false; bool masterExpand = false; if (list.Count > 0) { GUIContent content; var collapseIcon = '\u2261'.ToString(); content = new GUIContent(collapseIcon, "Click to collapse all"); masterCollapse = GUILayout.Button(content, EditorStyles.toolbarButton); var expandIcon = '\u25A1'.ToString(); content = new GUIContent(expandIcon, "Click to expand all"); masterExpand = GUILayout.Button(content, EditorStyles.toolbarButton); } else { GUILayout.FlexibleSpace(); } EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(50)); // A little space between button groups GUILayout.Space(buttonSpacer); // Main Add button if (GUILayout.Button(new GUIContent("+", "Click to add"), EditorStyles.toolbarButton)) { list.Add(new T()); } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); #endregion Header Foldout #region List Items // Use a for, instead of foreach, to avoid the iterator since we will be // be changing the loop in place when buttons are pressed. Even legal // changes can throw an error when changes are detected for (int i = 0; i < list.Count; i++) { T item = list[i]; #region Section Header // If there is a field with the name 'name' use it for our label string itemLabel = PGEditorUtils.GetSerializedObjFieldName <T>(item); if (itemLabel == "") { itemLabel = string.Format("Element {0}", i); } // Get the foldout state. // If this item is new, add it too (singleton) // Singleton works better than multiple Add() calls because we can do // it all at once, and in one place. bool foldOutState; if (!foldOutStates.TryGetValue(item, out foldOutState)) { foldOutStates[item] = true; foldOutState = true; } // Force states if master buttons were pressed if (masterCollapse) { foldOutState = false; } if (masterExpand) { foldOutState = true; } // Use a Horizanal space or the toolbar will extend to the start no matter what EditorGUILayout.BeginHorizontal(); EditorGUI.indentLevel = 0; // Space will handle this for the header GUILayout.Space((indent + 3) * 6); // Matches the content indent EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); // Display foldout with current state foldOutState = Foldout(foldOutState, itemLabel); foldOutStates[item] = foldOutState; // Used again below LIST_BUTTONS listButtonPressed = AddFoldOutListItemButtons(); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); #endregion Section Header // If folded out, display all serialized fields if (foldOutState == true) { EditorGUI.indentLevel = indent + 3; // Display Fields for the list instance PGEditorUtils.SerializedObjectFields <T>(item, collapseBools); GUILayout.Space(2); } #region Process List Changes // Don't allow 'up' presses for the first list item switch (listButtonPressed) { case LIST_BUTTONS.None: // Nothing was pressed, do nothing break; case LIST_BUTTONS.Up: if (i > 0) { T shiftItem = list[i]; list.RemoveAt(i); list.Insert(i - 1, shiftItem); } break; case LIST_BUTTONS.Down: // Don't allow 'down' presses for the last list item if (i + 1 < list.Count) { T shiftItem = list[i]; list.RemoveAt(i); list.Insert(i + 1, shiftItem); } break; case LIST_BUTTONS.Remove: list.RemoveAt(i); foldOutStates.Remove(item); // Clean-up break; case LIST_BUTTONS.Add: list.Insert(i, new T()); break; } #endregion Process List Changes } #endregion List Items EditorGUI.indentLevel = indent; return(expanded); }
/// <summary> /// Adds a fold-out list GUI from a generic list of any serialized object type /// </summary> /// <param name="list">A generic List</param> /// <param name="expanded">A bool to determine the state of the primary fold-out</param> public static bool FoldOutObjList <T>(string label, List <T> list, bool expanded) where T : UnityEngine.Object { // Store the previous indent and return the flow to it at the end int indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; // Space will handle this for the header // A copy of toolbarButton with left alignment for foldouts var foldoutStyle = new GUIStyle(EditorStyles.toolbarButton); foldoutStyle.alignment = TextAnchor.MiddleLeft; if (!AddFoldOutListHeader <T>(label, list, expanded, indent)) { return(false); } // Use a for, instead of foreach, to avoid the iterator since we will be // be changing the loop in place when buttons are pressed. Even legal // changes can throw an error when changes are detected for (int i = 0; i < list.Count; i++) { T item = list[i]; EditorGUILayout.BeginHorizontal(); GUILayout.Space((indent + 3) * 6); // Matches the content indent // OBJECT FIELD... // Count is always in sync bec T fieldVal = (T)EditorGUILayout.ObjectField(item, typeof(T), true); // This is weird but have to replace the item with the new value, can't // find a way to set in-place in a more stable way list.RemoveAt(i); list.Insert(i, fieldVal); LIST_BUTTONS listButtonPressed = AddFoldOutListItemButtons(); EditorGUILayout.EndHorizontal(); GUILayout.Space(2); #region Process List Changes // Don't allow 'up' presses for the first list item switch (listButtonPressed) { case LIST_BUTTONS.None: // Nothing was pressed, do nothing break; case LIST_BUTTONS.Up: if (i > 0) { T shiftItem = list[i]; list.RemoveAt(i); list.Insert(i - 1, shiftItem); } break; case LIST_BUTTONS.Down: // Don't allow 'down' presses for the last list item if (i + 1 < list.Count) { T shiftItem = list[i]; list.RemoveAt(i); list.Insert(i + 1, shiftItem); } break; case LIST_BUTTONS.Remove: list.RemoveAt(i); break; case LIST_BUTTONS.Add: list.Insert(i, null); break; } #endregion Process List Changes } EditorGUI.indentLevel = indent; return(true); }
/// <summary> /// Used by basic foldout lists to process any list item button presses which will alter /// the order or members of the ist /// </summary> /// <param name="listButtonPressed"></param> private static void UpdateFoldOutListOnButtonPressed <T>(List <T> list, int currentIndex, LIST_BUTTONS listButtonPressed) { // Don't allow 'up' presses for the first list item switch (listButtonPressed) { case LIST_BUTTONS.None: // Nothing was pressed, do nothing break; case LIST_BUTTONS.Up: if (currentIndex > 0) { T shiftItem = list[currentIndex]; list.RemoveAt(currentIndex); list.Insert(currentIndex - 1, shiftItem); } break; case LIST_BUTTONS.Down: // Don't allow 'down' presses for the last list item if (currentIndex + 1 < list.Count) { T shiftItem = list[currentIndex]; list.RemoveAt(currentIndex); list.Insert(currentIndex + 1, shiftItem); } break; case LIST_BUTTONS.Remove: list.RemoveAt(currentIndex); break; case LIST_BUTTONS.Add: list.Insert(currentIndex, default(T)); break; } }