using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using BansheeEngine; namespace BansheeEditor { /// /// Displays GUI for a serializable property containing an array. Array contents are displayed as rows of entries /// that can be shown, hidden or manipulated. /// public class InspectableArray : InspectableField { /// /// Contains GUI elements for a single entry in the array. /// private class EntryRow { public GUILayoutY contentLayout; private GUILayoutX rowLayout; private GUILayoutX titleLayout; private bool ownsTitleLayout; /// /// Constructs a new entry row object. /// /// Parent layout that row GUI elements will be added to. public EntryRow(GUILayout parentLayout) { rowLayout = parentLayout.AddLayoutX(); contentLayout = rowLayout.AddLayoutY(); } /// /// Recreates all row GUI elements. /// /// Inspectable field of the array entry. /// Sequential index of the array entry. /// Parent array object that the entry is contained in. public void Refresh(InspectableField child, int seqIndex, InspectableArray parent) { if (ownsTitleLayout || (titleLayout != null && titleLayout == child.GetTitleLayout())) return; titleLayout = child.GetTitleLayout(); if (titleLayout == null) { GUILayoutY buttonCenter = rowLayout.AddLayoutY(); buttonCenter.AddFlexibleSpace(); titleLayout = buttonCenter.AddLayoutX(); buttonCenter.AddFlexibleSpace(); ownsTitleLayout = true; } 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); } /// /// Destroys all row GUI elements. /// public void Destroy() { rowLayout.Destroy(); } } private const int IndentAmount = 5; private object propertyValue; // TODO - This will unnecessarily hold references to the object private int numArrayElements; private List rows = new List(); private GUIIntField guiSizeField; private GUILayoutX guiChildLayout; private GUILayoutX guiTitleLayout; private bool isExpanded; private bool forceUpdate = true; /// /// Creates a new inspectable array GUI for the specified property. /// /// Name of the property, or some other value to set as the title. /// Determines how deep within the inspector nesting hierarchy is this field.Some fields may /// contain other fields, in which case you should increase this value by one. /// Parent layout that all the field elements will be added to. /// Serializable property referencing the array whose contents to display. public InspectableArray(string title, int depth, InspectableFieldLayout layout, SerializableProperty property) : base(title, depth, layout, property) { } /// public override GUILayoutX GetTitleLayout() { return guiTitleLayout; } /// protected override bool IsModified() { if (forceUpdate) return true; object newPropertyValue = property.GetValue(); if (propertyValue == null) return newPropertyValue != null; if (newPropertyValue == null) return propertyValue != null; SerializableArray array = property.GetArray(); if (array.GetLength() != numArrayElements) return true; return base.IsModified(); } /// public override bool Refresh(int layoutIndex) { bool anythingModified = false; if (IsModified()) { Update(layoutIndex); anythingModified = true; } for (int i = 0; i < ChildCount; i++) { InspectableField child = GetChild(i); bool childModified = child.Refresh(0); if (childModified) rows[i].Refresh(child, i, this); anythingModified |= childModified; } return anythingModified; } /// protected override void Update(int layoutIndex) { base.Update(layoutIndex); forceUpdate = false; guiTitleLayout = null; if (property.Type != SerializableProperty.FieldType.Array || property.InternalType.GetArrayRank() != 1) // We don't support multirank arrays return; foreach (var row in rows) row.Destroy(); rows.Clear(); layout.DestroyElements(); propertyValue = property.GetValue(); if (propertyValue == null) { guiChildLayout = null; guiTitleLayout = layout.AddLayoutX(layoutIndex); guiTitleLayout.AddElement(new GUILabel(title)); guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100))); if (!property.IsValueType) { GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create)); GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30)); createBtn.OnClick += OnCreateButtonClicked; guiTitleLayout.AddElement(createBtn); } numArrayElements = 0; } 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(layoutIndex); guiTitleLayout.AddElement(guiFoldout); guiTitleLayout.AddElement(guiSizeField); guiTitleLayout.AddElement(guiResizeBtn); guiTitleLayout.AddElement(guiClearBtn); SerializableArray array = property.GetArray(); numArrayElements = array.GetLength(); guiSizeField.Value = numArrayElements; if (isExpanded) { if (numArrayElements > 0) { guiChildLayout = layout.AddLayoutX(layoutIndex); guiChildLayout.AddSpace(IndentAmount); 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); short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1); string bgPanelStyle = depth % 2 == 0 ? EditorStyles.InspectorContentBgAlternate : EditorStyles.InspectorContentBg; GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth); GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle); backgroundPanel.AddElement(inspectorContentBg); for (int i = 0; i < numArrayElements; i++) { EntryRow newRow = new EntryRow(guiContentLayout); rows.Add(newRow); InspectableField childObj = CreateInspectable(i + ".", depth + 1, new InspectableFieldLayout(newRow.contentLayout), array.GetProperty(i)); AddChild(childObj); childObj.Refresh(0); rows[i].Refresh(childObj, i, this); } } } else guiChildLayout = null; } } /// /// 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; forceUpdate = true; } /// /// Triggered when the user clicks on the resize button on the title bar. Changes the size of the array while /// preserving existing contents. /// private void OnResizeButtonClicked() { int size = guiSizeField.Value; // TODO - Support multi-rank arrays Array newArray = property.CreateArrayInstance(new int[] {size}); Array array = property.GetValue(); int maxSize = MathEx.Min(size, array.Length); for (int i = 0; i < maxSize; i++) newArray.SetValue(array.GetValue(i), i); property.SetValue(newArray); } /// /// Triggered when the user clicks on the delete button next to the array entry. Deletes an element in the array. /// /// Sequential index of the element in the array to remove. private void OnDeleteButtonClicked(int index) { Array array = property.GetValue(); int size = MathEx.Max(0, array.Length - 1); Array newArray = property.CreateArrayInstance(new int[] { size }); int destIdx = 0; for (int i = 0; i < array.Length; i++) { if (i == index) continue; newArray.SetValue(array.GetValue(i), destIdx); destIdx++; } property.SetValue(newArray); } /// /// Triggered when the user clicks on the clone button next to the array entry. Clones an element in the array and /// adds the clone to the back of the array. /// /// Sequential index of the element in the array to clone. private void OnCloneButtonClicked(int index) { SerializableArray array = property.GetArray(); int size = array.GetLength() + 1; Array newArray = property.CreateArrayInstance(new int[] { size }); object clonedEntry = null; for (int i = 0; i < array.GetLength(); i++) { object value = array.GetProperty(i).GetValue(); newArray.SetValue(value, i); if (i == index) { clonedEntry = array.GetProperty(i).GetValueCopy(); } } newArray.SetValue(clonedEntry, size - 1); property.SetValue(newArray); } /// /// Triggered when the user clicks on the move up button next to the array entry. Moves an element from the current /// array index to the one right before it, if not at zero. /// /// Sequential index of the element in the array to move. private void OnMoveUpButtonClicked(int index) { Array array = property.GetValue(); if ((index - 1) >= 0) { object previousEntry = array.GetValue(index - 1); array.SetValue(array.GetValue(index), index - 1); array.SetValue(previousEntry, index); } } /// /// Triggered when the user clicks on the move down button next to the array entry. Moves an element from the current /// array index to the one right after it, if the element isn't already the last element. /// /// Sequential index of the element in the array to move. private void OnMoveDownButtonClicked(int index) { Array array = property.GetValue(); if ((index + 1) < array.Length) { object nextEntry = array.GetValue(index + 1); array.SetValue(array.GetValue(index), index + 1); array.SetValue(nextEntry, index); } } /// /// Triggered when the user clicks on the create button on the title bar. Creates a brand new array with zero /// elements in the place of the current array. /// private void OnCreateButtonClicked() { property.SetValue(property.CreateArrayInstance(new int[1] { 0 })); } /// /// Triggered when the user clicks on the clear button on the title bar. Deletes the current array and sets /// the reference to the array in the parent object to null. /// private void OnClearButtonClicked() { property.SetValue(null); } } }