using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using BansheeEngine; namespace BansheeEditor { /// /// Base class for objects that display GUI for a modifyable list of elements. Elements can be added, removed and moved. /// public abstract class GUIListFieldBase { private const int IndentAmount = 5; protected List rows = new List(); protected GUIIntField guiSizeField; protected GUILayoutX guiChildLayout; protected GUILayoutX guiTitleLayout; protected GUILayoutY guiContentLayout; protected bool isExpanded; /// /// Constructs a new GUI list. /// protected GUIListFieldBase() { } /// /// Constructs a new GUI list with the specified row types. Must be called right after the constructor. /// /// Type of rows that are used to handle GUI for individual list elements. /// Label to display on the list GUI title. /// Should the created field represent a null object. /// Number of rows to create GUI for. Only matters for a non-empty list. /// Layout to which to append the list GUI elements to. protected void Construct(LocString title, bool empty, int numRows, GUILayout layout) where T : GUIListFieldRow, new() { if (empty) { guiChildLayout = null; guiContentLayout = null; guiTitleLayout = layout.AddLayoutX(); guiTitleLayout.AddElement(new GUILabel(title)); guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100))); GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create)); GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30)); createBtn.OnClick += OnCreateButtonClicked; guiTitleLayout.AddElement(createBtn); } else { GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout); guiFoldout.Value = isExpanded; guiFoldout.OnToggled += OnFoldoutToggled; guiSizeField = new GUIIntField("", GUIOption.FixedWidth(50)); guiSizeField.SetRange(0, int.MaxValue); GUIContent resizeIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Resize)); GUIButton guiResizeBtn = new GUIButton(resizeIcon, GUIOption.FixedWidth(30)); guiResizeBtn.OnClick += OnResizeButtonClicked; GUIContent clearIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clear)); GUIButton guiClearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(30)); guiClearBtn.OnClick += OnClearButtonClicked; guiTitleLayout = layout.AddLayoutX(); guiTitleLayout.AddElement(guiFoldout); guiTitleLayout.AddElement(guiSizeField); guiTitleLayout.AddElement(guiResizeBtn); guiTitleLayout.AddElement(guiClearBtn); guiSizeField.Value = numRows; if (numRows > 0) { guiChildLayout = layout.AddLayoutX(); guiChildLayout.AddSpace(IndentAmount); guiChildLayout.Enabled = isExpanded; GUIPanel guiContentPanel = guiChildLayout.AddPanel(); GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX(); guiIndentLayoutX.AddSpace(IndentAmount); GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY(); guiIndentLayoutY.AddSpace(IndentAmount); guiContentLayout = guiIndentLayoutY.AddLayoutY(); guiIndentLayoutY.AddSpace(IndentAmount); guiIndentLayoutX.AddSpace(IndentAmount); guiChildLayout.AddSpace(IndentAmount); GUIPanel backgroundPanel = guiContentPanel.AddPanel(Inspector.START_BACKGROUND_DEPTH); GUITexture inspectorContentBg = new GUITexture(null, EditorStyles.InspectorContentBg); backgroundPanel.AddElement(inspectorContentBg); for (int i = 0; i < numRows; i++) { GUIListFieldRow newRow = new T(); newRow.BuildGUI(this, guiContentLayout, i); rows.Add(newRow); } } } } /// /// Returns the layout that is used for positioning the elements in the title bar. /// /// Horizontal layout for positioning the title bar elements. public GUILayoutX GetTitleLayout() { return guiTitleLayout; } /// /// Refreshes contents of all list rows and checks if anything was modified. /// /// True if any entry in the list was modified, false otherwise. public bool Refresh() { bool anythingModified = false; for (int i = 0; i < rows.Count; i++) { bool updateGUI; anythingModified |= rows[i].Refresh(out updateGUI); if (updateGUI) rows[i].BuildGUI(this, guiContentLayout, i); } return anythingModified; } /// /// Destroys the GUI elements. /// public void Destroy() { if (guiTitleLayout != null) { guiTitleLayout.Destroy(); guiTitleLayout = null; } if (guiChildLayout != null) { guiChildLayout.Destroy(); guiChildLayout = null; } for (int i = 0; i < rows.Count; i++) rows[i].Destroy(); rows.Clear(); } /// /// Gets a value of an element at the specified index in the list. /// /// Sequential index of the element whose value to retrieve. /// Value of the list element at the specified index. protected internal abstract object GetValue(int seqIndex); /// /// Sets a value of an element at the specified index in the list. /// /// Sequential index of the element whose value to set. /// Value to assign to the element. Caller must ensure it is of valid type. protected internal abstract void SetValue(int seqIndex, object value); /// /// Triggered when the user clicks on the expand/collapse toggle in the title bar. /// /// Determines whether the contents were expanded or collapsed. private void OnFoldoutToggled(bool expanded) { isExpanded = expanded; if (guiChildLayout != null) guiChildLayout.Enabled = isExpanded; } /// /// Triggered when the user clicks on the create button on the title bar. Creates a brand new list with zero /// elements in the place of the current list. /// protected abstract void OnCreateButtonClicked(); /// /// Triggered when the user clicks on the resize button on the title bar. Changes the size of the list while /// preserving existing contents. /// protected abstract void OnResizeButtonClicked(); /// /// Triggered when the user clicks on the clear button on the title bar. Deletes the current list object. /// protected abstract void OnClearButtonClicked(); /// /// Triggered when the user clicks on the delete button next to the list entry. Deletes an element in the list. /// /// Sequential index of the element in the list to remove. protected internal abstract void OnDeleteButtonClicked(int index); /// /// Triggered when the user clicks on the clone button next to the list entry. Clones an element in the list and /// adds the clone to the back of the list. Non-value types must implement the interface /// in order to be cloned. If it doesn't the clone will point to a null reference. /// /// Sequential index of the element in the list to clone. protected internal abstract void OnCloneButtonClicked(int index); /// /// Triggered when the user clicks on the move up button next to the list entry. Moves an element from the current /// list index to the one right before it, if not at zero. /// /// Sequential index of the element in the list to move. protected internal abstract void OnMoveUpButtonClicked(int index); /// /// Triggered when the user clicks on the move down button next to the list entry. Moves an element from the current /// list index to the one right after it, if the element isn't already the last element. /// /// Sequential index of the element in the list to move. protected internal abstract void OnMoveDownButtonClicked(int index); } /// /// Creates GUI elements that allow viewing and manipulation of a . When constructing the /// object user can provide a custom type that manages GUI for individual array elements. /// public class GUIArrayField : GUIListFieldBase { /// /// Triggered when the reference array has been changed. This does not include changes that only happen to its /// internal elements. /// public Action OnChanged; /// /// Triggered when an element in the list has been changed. /// public Action OnValueChanged; /// /// Array object whose contents are displayed. /// public Array Array { get { return array; } } protected Array array; protected Type arrayType; /// /// Constructs a new GUI array. /// private GUIArrayField() { } /// /// Creates a new GUI array. /// /// Type of rows that are used to handle GUI for individual list elements. /// Type of elements stored in the array. /// Label to display on the list GUI title. /// Object containing the list data. Cannot be null. /// Layout to which to append the list GUI elements to. public static GUIArrayField Create(LocString title, ElementType[] array, GUILayout layout) where RowType : GUIListFieldRow, new() { GUIArrayField newArrayField = new GUIArrayField(); if (array != null) newArrayField.Construct(title, false, array.Length, layout); else newArrayField.Construct(title, true, 0, layout); newArrayField.arrayType = typeof (ElementType[]); newArrayField.array = array; return newArrayField; } /// protected internal override object GetValue(int seqIndex) { return array.GetValue(seqIndex); } /// protected internal override void SetValue(int seqIndex, object value) { array.SetValue(value, seqIndex); if (OnValueChanged != null) OnValueChanged(); } /// protected override void OnCreateButtonClicked() { array = Array.CreateInstance(arrayType.GetElementType(), 0); if (OnChanged != null) OnChanged(array); } /// protected override void OnResizeButtonClicked() { int size = guiSizeField.Value; Array newArray = Array.CreateInstance(arrayType.GetElementType(), size); int maxSize = MathEx.Min(size, array.GetLength(0)); for (int i = 0; i < maxSize; i++) newArray.SetValue(array.GetValue(i), i); array = newArray; if(OnChanged != null) OnChanged(array); } /// protected override void OnClearButtonClicked() { array = null; if (OnChanged != null) OnChanged(array); } /// protected internal override void OnDeleteButtonClicked(int index) { int size = MathEx.Max(0, array.GetLength(0) - 1); Array newArray = Array.CreateInstance(arrayType.GetElementType(), size); int destIdx = 0; for (int i = 0; i < array.GetLength(0); i++) { if (i == index) continue; newArray.SetValue(array.GetValue(i), destIdx); destIdx++; } array = newArray; if (OnChanged != null) OnChanged(array); } /// protected internal override void OnCloneButtonClicked(int index) { int size = array.GetLength(0) + 1; Array newArray = Array.CreateInstance(arrayType.GetElementType(), size); object clonedEntry = null; for (int i = 0; i < array.GetLength(0); i++) { object value = array.GetValue(i); newArray.SetValue(value, i); if (i == index) { if (value == null) clonedEntry = null; else { ValueType valueType = value as ValueType; if (valueType != null) clonedEntry = valueType; else { ICloneable cloneable = value as ICloneable; if (cloneable != null) clonedEntry = cloneable.Clone(); else clonedEntry = null; } } } } newArray.SetValue(clonedEntry, size - 1); array = newArray; if (OnChanged != null) OnChanged(array); } /// protected internal override void OnMoveUpButtonClicked(int index) { if ((index - 1) >= 0) { object previousEntry = array.GetValue(index - 1); array.SetValue(array.GetValue(index), index - 1); array.SetValue(previousEntry, index); if (OnValueChanged != null) OnValueChanged(); } } /// protected internal override void OnMoveDownButtonClicked(int index) { if ((index + 1) < array.GetLength(0)) { object nextEntry = array.GetValue(index + 1); array.SetValue(array.GetValue(index), index + 1); array.SetValue(nextEntry, index); if (OnValueChanged != null) OnValueChanged(); } } } /// /// Contains GUI elements for a single entry in a list. /// public abstract class GUIListFieldRow { private GUILayoutX rowLayout; private GUIListFieldBase parent; protected int seqIndex; /// /// Constructs a new array row object. /// protected GUIListFieldRow() { } /// /// (Re)creates all row GUI elements. /// /// Parent array GUI object that the entry is contained in. /// Parent layout that row GUI elements will be added to. /// Sequential index of the array entry. public void BuildGUI(GUIListFieldBase parent, GUILayout parentLayout, int seqIndex) { this.parent = parent; this.seqIndex = seqIndex; if (rowLayout == null) rowLayout = parentLayout.AddLayoutX(); else rowLayout.Clear(); GUILayoutY contentLayout = rowLayout.AddLayoutY(); GUILayoutX titleLayout = CreateGUI(contentLayout); if (titleLayout == null) { GUILayoutY buttonCenter = rowLayout.AddLayoutY(); buttonCenter.AddFlexibleSpace(); titleLayout = buttonCenter.AddLayoutX(); buttonCenter.AddFlexibleSpace(); } GUIContent cloneIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clone)); GUIContent deleteIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Delete)); GUIContent moveUp = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.MoveUp)); GUIContent moveDown = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.MoveDown)); GUIButton cloneBtn = new GUIButton(cloneIcon, GUIOption.FixedWidth(30)); GUIButton deleteBtn = new GUIButton(deleteIcon, GUIOption.FixedWidth(30)); GUIButton moveUpBtn = new GUIButton(moveUp, GUIOption.FixedWidth(30)); GUIButton moveDownBtn = new GUIButton(moveDown, GUIOption.FixedWidth(30)); cloneBtn.OnClick += () => parent.OnCloneButtonClicked(seqIndex); deleteBtn.OnClick += () => parent.OnDeleteButtonClicked(seqIndex); moveUpBtn.OnClick += () => parent.OnMoveUpButtonClicked(seqIndex); moveDownBtn.OnClick += () => parent.OnMoveDownButtonClicked(seqIndex); titleLayout.AddElement(cloneBtn); titleLayout.AddElement(deleteBtn); titleLayout.AddElement(moveUpBtn); titleLayout.AddElement(moveDownBtn); } /// /// Creates GUI elements specific to type in the array row. /// /// Layout to insert the row GUI elements to. /// An optional title bar layout that the standard array buttons will be appended to. protected abstract GUILayoutX CreateGUI(GUILayoutY layout); /// /// Refreshes the GUI for the list row and checks if anything was modified. /// /// Determines should the field's GUI elements be updated due to modifications. /// True if any modifications were made, false otherwise. internal protected virtual bool Refresh(out bool rebuildGUI) { rebuildGUI = false; return false; } /// /// Gets the value contained in this list row. /// /// Type of the value. Must match the list's element type. /// Value in this list row. protected T GetValue() { return (T)parent.GetValue(seqIndex); } /// /// Sets the value contained in this list row. /// /// Type of the value. Must match the list's element type. /// Value to set. protected void SetValue(T value) { parent.SetValue(seqIndex, value); } /// /// Destroys all row GUI elements. /// public void Destroy() { rowLayout.Destroy(); rowLayout = null; } } }