using System;
using System.Collections;
using System.Collections.Generic;
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;
protected int depth;
///
/// Constructs a new GUI list.
///
protected GUIListFieldBase()
{ }
///
/// Updates the GUI list contents. Must be called at least once in order for the contents to be populated.
///
/// 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-null list.
/// Layout to which to append the list GUI elements to.
/// Determines at which depth to render the background. Useful when you have multiple
/// nested containers whose backgrounds are overlaping. Also determines background style,
/// depths divisible by two will use an alternate style.
protected void Update(LocString title, bool empty, int numRows, GUILayout layout,
int depth = 0) where T : GUIListFieldRow, new()
{
Destroy();
this.depth = depth;
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);
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 < numRows; i++)
{
GUIListFieldRow newRow = new T();
newRow.BuildGUI(this, guiContentLayout, i, depth);
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, depth);
}
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 a 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 a list entry. Clones the element and adds the clone
/// to the back of the list.
///
/// 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 a 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 a 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.
///
/// Type of elements stored in the array.
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 array has been changed.
///
public Action OnValueChanged;
///
/// Array object whose contents are displayed.
///
public ElementType[] Array { get { return array; } }
protected ElementType[] array;
///
/// Constructs a new empty GUI array.
///
public GUIArrayField()
{ }
///
/// Updates the GUI array contents. Must be called at least once in order for the contents to be populated.
///
/// Type of rows that are used to handle GUI for individual array elements.
/// Label to display on the array GUI title.
/// Object containing the array data. Can be null.
/// Layout to which to append the array GUI elements to.
/// Determines at which depth to render the background. Useful when you have multiple
/// nested containers whose backgrounds are overlaping. Also determines background style,
/// depths divisible by two will use an alternate style.
public void Update(LocString title, ElementType[] array, GUILayout layout, int depth = 0)
where RowType : GUIListFieldRow, new()
{
this.array = array;
if (array != null)
base.Update(title, false, array.Length, layout, depth);
else
base.Update(title, true, 0, layout, depth);
}
///
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 = new ElementType[0];
if (OnChanged != null)
OnChanged(array);
}
///
protected override void OnResizeButtonClicked()
{
int size = guiSizeField.Value;
ElementType[] newArray = new ElementType[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);
ElementType[] newArray = new ElementType[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;
ElementType[] newArray = new ElementType[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
clonedEntry = SerializableUtility.Clone(value);
}
}
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();
}
}
}
///
/// 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 list elements.
///
/// Type of elements stored in the list.
public class GUIListField : GUIListFieldBase
{
///
/// Triggered when the reference list 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;
///
/// List object whose contents are displayed.
///
public List List { get { return list; } }
protected List list;
///
/// Constructs a new empty GUI array.
///
public GUIListField()
{ }
///
/// Updates the GUI list contents. Must be called at least once in order for the contents to be populated.
///
/// 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.
/// Layout to which to append the list GUI elements to.
/// Determines at which depth to render the background. Useful when you have multiple
/// nested containers whose backgrounds are overlaping. Also determines background style,
/// depths divisible by two will use an alternate style.
public void Update(LocString title, List list, GUILayout layout, int depth = 0)
where RowType : GUIListFieldRow, new()
{
this.list = list;
if (list != null)
base.Update(title, false, list.Count, layout, depth);
else
base.Update(title, true, 0, layout, depth);
}
///
protected internal override object GetValue(int seqIndex)
{
return list[seqIndex];
}
///
protected internal override void SetValue(int seqIndex, object value)
{
list[seqIndex] = (ElementType)value;
if (OnValueChanged != null)
OnValueChanged();
}
///
protected override void OnCreateButtonClicked()
{
list = new List();
if (OnChanged != null)
OnChanged(list);
}
///
protected override void OnResizeButtonClicked()
{
int size = guiSizeField.Value;
if(size == list.Count)
return;
if (size < list.Count)
list.RemoveRange(size, list.Count - size);
else
{
ElementType[] extraElements = new ElementType[size - list.Count];
list.AddRange(extraElements);
}
if (OnChanged != null)
OnValueChanged();
}
///
protected override void OnClearButtonClicked()
{
list = null;
if (OnChanged != null)
OnChanged(list);
}
///
protected internal override void OnDeleteButtonClicked(int index)
{
list.RemoveAt(index);
if (OnChanged != null)
OnChanged(list);
}
///
protected internal override void OnCloneButtonClicked(int index)
{
object clonedEntry = null;
if (list[index] != null)
clonedEntry = SerializableUtility.Clone(list[index]);
list.Add((ElementType)clonedEntry);
if (OnChanged != null)
OnChanged(list);
}
///
protected internal override void OnMoveUpButtonClicked(int index)
{
if ((index - 1) >= 0)
{
ElementType 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)
{
ElementType 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 GUIListFieldRow
{
private GUILayoutX rowLayout;
private GUILayoutY contentLayout;
private GUILayoutX titleLayout;
private bool localTitleLayout;
private GUIListFieldBase parent;
protected int seqIndex;
protected int depth;
///
/// Constructs a new list 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.
/// Determines the depth at which the element is rendered.
internal void BuildGUI(GUIListFieldBase parent, GUILayout parentLayout, int seqIndex, int depth)
{
this.parent = parent;
this.seqIndex = seqIndex;
this.depth = depth;
if (rowLayout == null)
rowLayout = parentLayout.AddLayoutX();
if (contentLayout == null)
contentLayout = rowLayout.AddLayoutY();
GUILayoutX externalTitleLayout = CreateGUI(contentLayout);
if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout))
return;
if (externalTitleLayout != null)
{
localTitleLayout = false;
titleLayout = externalTitleLayout;
}
else
{
GUILayoutY buttonCenter = rowLayout.AddLayoutY();
buttonCenter.AddFlexibleSpace();
titleLayout = buttonCenter.AddLayoutX();
buttonCenter.AddFlexibleSpace();
localTitleLayout = 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);
}
///
/// 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;
}
}
}