/// <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); }
public bool SerializedObjFoldOutList(string label, List <CharactersActionData> list, bool expanded, ref Dictionary <object, bool> foldOutStates, bool collapseBools, Transform tf) { // 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 = PGEditorUtils.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 CharactersActionData()); } 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++) { CharactersActionData item = list[i]; #region Section Header // If there is a field with the name 'name' use it for our label string itemLabel = PGEditorUtils.GetSerializedObjFieldName <CharactersActionData>(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 = PGEditorUtils.Foldout(foldOutState, itemLabel); foldOutStates[item] = foldOutState; // Used again below PGEditorUtils.LIST_BUTTONS listButtonPressed = PGEditorUtils.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); System.Type type = typeof(CharactersActionData); // System.Reflection.FieldInfo[] fields = type.GetFields(); // Display Fields Dynamically item.actionType = PGEditorUtils.EnumPopup <Config.ColliderAction>("actionType", item.actionType); item.condition = PGEditorUtils.EnumPopup <Config.ActionTriggerCondition>("triggerCondition", item.condition); PGEditorUtils.FieldInfoField <CharactersActionData>(item, type.GetField("loop")); switch (item.actionType) { case Config.ColliderAction.Movement: drawRegion(x => item.startVal = x, tf.localPosition, x => tf.localPosition = x, item.startVal, item, "startVal"); drawRegion(x => item.endVal = x, tf.localPosition, x => tf.localPosition = x, item.endVal, item, "endVal"); break; case Config.ColliderAction.Rotation: drawRegion(x => item.startVal = x, tf.localRotation.eulerAngles, x => tf.localRotation = Quaternion.Euler(item.startVal), item.startVal, item, "startVal"); drawRegion(x => item.endVal = x, tf.localRotation.eulerAngles, x => tf.localRotation = x, Quaternion.Euler(item.endVal), item, "endVal"); break; case Config.ColliderAction.Scale: drawRegion(x => item.startVal = x, tf.localScale, x => tf.localScale = x, item.startVal, item, "startVal"); drawRegion(x => item.endVal = x, tf.localScale, x => tf.localScale = x, item.endVal, item, "endVal"); break; case Config.ColliderAction.Alpha: PGEditorUtils.FieldInfoField <CharactersActionData>(item, type.GetField("startAlpha")); PGEditorUtils.FieldInfoField <CharactersActionData>(item, type.GetField("endAlpha")); break; } PGEditorUtils.FieldInfoField <CharactersActionData>(item, type.GetField("duration")); if (item.loop > 1 || item.loop < 0) { item.loopType = PGEditorUtils.EnumPopup <DG.Tweening.LoopType>("loopType", item.loopType); PGEditorUtils.FieldInfoField <CharactersActionData>(item, type.GetField("pauseTime")); } PGEditorUtils.FieldInfoField <CharactersActionData>(item, type.GetField("delay")); GUILayout.Space(2); } #region Process List Changes // Don't allow 'up' presses for the first list item switch (listButtonPressed) { case PGEditorUtils.LIST_BUTTONS.None: // Nothing was pressed, do nothing break; case PGEditorUtils.LIST_BUTTONS.Up: if (i > 0) { CharactersActionData shiftItem = list[i]; list.RemoveAt(i); list.Insert(i - 1, shiftItem); } break; case PGEditorUtils.LIST_BUTTONS.Down: // Don't allow 'down' presses for the last list item if (i + 1 < list.Count) { CharactersActionData shiftItem = list[i]; list.RemoveAt(i); list.Insert(i + 1, shiftItem); } break; case PGEditorUtils.LIST_BUTTONS.Remove: list.RemoveAt(i); foldOutStates.Remove(item); // Clean-up break; case PGEditorUtils.LIST_BUTTONS.Add: list.Insert(i, new CharactersActionData()); break; } #endregion Process List Changes } #endregion List Items EditorGUI.indentLevel = indent; return(expanded); }