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 GUIListBase { private const int IndentAmount = 5; protected IList list; protected Type listType; protected List rows = new List(); protected GUIIntField guiSizeField; protected GUILayoutX guiChildLayout; protected GUILayoutX guiTitleLayout; protected bool isExpanded; /// /// Constructs a new GUI list. /// protected GUIListBase() { } /// /// 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. /// Object containing the list data. Can be null. /// Type of the parameter. Needs to be specified in case that /// parameter is null. /// Layout to which to append the list GUI elements to. protected void Construct(LocString title, IList list, Type listType, GUILayout layout) where T : GUIListRow, new() { this.list = list; this.listType = listType; if (list == null) { guiChildLayout = 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 = list.Count; guiChildLayout = layout.AddLayoutX(); guiChildLayout.AddSpace(IndentAmount); guiChildLayout.Visible = isExpanded; GUIPanel guiContentPanel = guiChildLayout.AddPanel(); GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX(); guiIndentLayoutX.AddSpace(IndentAmount); GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY(); guiIndentLayoutY.AddSpace(IndentAmount); GUILayoutY 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 < list.Count; i++) { GUIListRow newRow = new T(); newRow.Update(this, guiContentLayout, i); rows.Add(newRow); } } } /// /// 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.Visible = 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 GUIArray : GUIListBase { /// /// 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 array has been changed. /// public Action OnValueChanged; /// /// Array object whose contents are displayed. /// public Array Array { get { return (Array)list; } } /// /// Constructs a new GUI array. /// private GUIArray() { } /// /// 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 GUIArray Create(LocString title, ElementType[] array, GUILayout layout) where RowType : GUIListRow, new() { GUIArray newArray = new GUIArray(); newArray.Construct(title, array, typeof(ElementType[]), layout); return newArray; } /// /// Refreshes contents of all array rows and checks if anything was modified. /// /// True if any entry in the array was modified, false otherwise. public bool Refresh() { bool anythingModified = false; for (int i = 0; i < rows.Count; i++) anythingModified |= rows[i].Refresh(); 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(); } /// protected override void OnCreateButtonClicked() { list = Array.CreateInstance(listType.GetElementType(), 0); if (OnChanged != null) OnChanged((Array)list); } /// protected override void OnResizeButtonClicked() { int size = guiSizeField.Value; Array newArray = Array.CreateInstance(listType.GetElementType(), size); int maxSize = MathEx.Min(size, list.Count); for (int i = 0; i < maxSize; i++) newArray.SetValue(list[i], i); list = newArray; if(OnChanged != null) OnChanged((Array)list); } /// protected override void OnClearButtonClicked() { list = null; if (OnChanged != null) OnChanged((Array)list); } /// protected internal override object GetValue(int seqIndex) { return list[seqIndex]; } /// protected internal override void SetValue(int seqIndex, object value) { list[seqIndex] = value; if (OnValueChanged != null) OnValueChanged(); } /// protected internal override void OnDeleteButtonClicked(int index) { int size = MathEx.Max(0, list.Count - 1); Array newArray = Array.CreateInstance(listType.GetElementType(), size); int destIdx = 0; for (int i = 0; i < list.Count; i++) { if (i == index) continue; newArray.SetValue(list[i], destIdx); destIdx++; } list = newArray; if (OnChanged != null) OnChanged((Array)list); } /// protected internal override void OnCloneButtonClicked(int index) { int size = list.Count + 1; Array newArray = Array.CreateInstance(listType.GetElementType(), size); object clonedEntry = null; for (int i = 0; i < list.Count; i++) { object value = list[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); list = newArray; if (OnChanged != null) OnChanged((Array)list); } /// protected internal override void OnMoveUpButtonClicked(int index) { if ((index - 1) >= 0) { object previousEntry = list[index - 1]; list[index - 1] = list[index]; list[index] = previousEntry; if (OnValueChanged != null) OnValueChanged(); } } /// protected internal override void OnMoveDownButtonClicked(int index) { if ((index + 1) < list.Count) { object nextEntry = list[index + 1]; list[index + 1] = list[index]; list[index] = nextEntry; if (OnValueChanged != null) OnValueChanged(); } } } /// /// Contains GUI elements for a single entry in a list. /// public abstract class GUIListRow { private GUILayoutX rowLayout; private GUIListBase parent; protected int seqIndex; /// /// Constructs a new array row object. /// protected GUIListRow() { } /// /// Recreates 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 Update(GUIListBase parent, GUILayout parentLayout, int seqIndex) { this.parent = parent; this.seqIndex = seqIndex; if (rowLayout != null) { rowLayout.Destroy(); rowLayout = null; } rowLayout = parentLayout.AddLayoutX(); 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. /// /// True if any modifications were made, false otherwise. internal protected virtual bool Refresh() { 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(); } } }