using System;
using System.Collections;
using System.Collections.Generic;
using BansheeEngine;
namespace BansheeEditor
{
///
/// Base class for objects that display GUI for a modifyable dictionary of elements. Elements can be added, modified or
/// removed.
///
public abstract class GUIDictionaryFieldBase
{
private const int IndentAmount = 5;
protected List rows = new List();
protected GUILayoutX guiChildLayout;
protected GUILayoutX guiTitleLayout;
protected GUILayoutY guiContentLayout;
protected bool isExpanded;
protected int depth;
///
/// Constructs a new GUI dictionary.
///
protected GUIDictionaryFieldBase()
{ }
///
/// Updates the GUI dictionary 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 dictionary elements.
/// Label to display on the dictionary GUI title.
/// Should the created field represent a null object.
/// Number of rows to create GUI for. Only matters for a non-null dictionary.
/// 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 : GUIDictionaryFieldRow, 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;
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(guiClearBtn);
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++)
{
GUIDictionaryFieldRow 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 dictionary 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.
///
/// Key of the element whose value to retrieve.
/// Value of the list element at the specified key.
protected internal abstract object GetValue(object key);
///
/// Sets a value of an element at the specified index in the list.
///
/// Key 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(object key, object value);
///
/// Checks does the element with the specified key exist in the dictionary.
///
/// Key of the element to check for existence.
/// True if the key exists in the dictionary, false otherwise.
protected internal abstract bool Contains(object key);
///
/// 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 dictionary with zero
/// elements in the place of the current dictionary.
///
protected abstract void OnCreateButtonClicked();
///
/// Triggered when the user clicks on the clear button on the title bar. Deletes the current dictionary object.
///
protected abstract void OnClearButtonClicked();
///
/// Triggered when the user clicks on the delete button next to a dictionary entry. Deletes an element in the
/// dictionary.
///
/// Key of the element to remove.
protected internal abstract void OnDeleteButtonClicked(object key);
///
/// Triggered when the user clicks on the clone button next to a dictionary entry. Clones an element and
/// adds the clone to the dictionary.
///
/// Key of the element to clone.
protected internal abstract void OnCloneButtonClicked(object key);
///
/// Triggered when user clicks the edit or apply (depending on state) button next to the dictionary entry. Starts
/// edit mode for the element, if not already in edit mode. Applies edit mode changes if already in edit mode.
///
/// Key of the element to edit.
protected internal virtual void OnEditButtonClicked(object key)
{
// TODO
}
}
///
/// 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 dictionary elements.
///
/// Type of key used by the dictionary.
/// Type of value stored in the dictionary.
public class GUIDictionaryField : GUIDictionaryFieldBase
{
///
/// 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 Dictionary Dictionary { get { return dictionary; } }
protected Dictionary dictionary;
///
/// Constructs a new empty dictionary GUI.
///
public GUIDictionaryField()
{ }
///
/// Updates the GUI dictionary 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 dictionary elements.
/// Label to display on the list GUI title.
/// Object containing the 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, Dictionary dictionary,
GUILayout layout, int depth = 0)
where RowType : GUIDictionaryFieldRow, new()
{
this.dictionary = dictionary;
if (dictionary != null)
base.Update(title, false, dictionary.Count, layout, depth);
else
base.Update(title, true, 0, layout, depth);
}
///
protected internal override object GetValue(object key)
{
return dictionary[(Key)key];
}
///
protected internal override void SetValue(object key, object value)
{
dictionary[(Key)key] = (Value)value;
if (OnValueChanged != null)
OnValueChanged((Key)key);
}
///
protected internal override bool Contains(object key)
{
return dictionary.ContainsKey((Key)key); ;
}
///
protected override void OnCreateButtonClicked()
{
dictionary = new Dictionary();
if (OnChanged != null)
OnChanged(dictionary);
}
///
protected override void OnClearButtonClicked()
{
dictionary = null;
if (OnChanged != null)
OnChanged(dictionary);
}
///
protected internal override void OnDeleteButtonClicked(object key)
{
dictionary.Remove((Key)key);
if (OnValueChanged != null)
OnValueChanged((Key)key);
}
///
protected internal override void OnCloneButtonClicked(object key)
{
// TODO - Not implemented
//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);
}
}
///
/// Contains GUI elements for a single entry in a dictionary.
///
public abstract class GUIDictionaryFieldRow
{
private GUILayoutY rowLayout;
private GUILayoutX keyRowLayout;
private GUILayoutY keyLayout;
private GUILayoutY valueLayout;
private GUILayoutX titleLayout;
private bool localTitleLayout;
private GUIDictionaryFieldBase parent;
protected object key;
protected int depth;
///
/// Constructs a new dictionary row object.
///
protected GUIDictionaryFieldRow()
{
}
///
/// (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.
/// Key of the element to create GUI for.
/// Determines the depth at which the element is rendered.
public void BuildGUI(GUIDictionaryFieldBase parent, GUILayout parentLayout, object key, int depth)
{
this.parent = parent;
this.key = key;
this.depth = depth;
if (rowLayout == null)
rowLayout = parentLayout.AddLayoutY();
if (keyRowLayout == null)
keyRowLayout = rowLayout.AddLayoutX();
if (keyLayout == null)
keyLayout = keyRowLayout.AddLayoutY();
if (valueLayout == null)
valueLayout = rowLayout.AddLayoutY();
GUILayoutX externalTitleLayout = CreateKeyGUI(keyLayout);
CreateValueGUI(valueLayout);
if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout))
return;
if (externalTitleLayout != null)
{
localTitleLayout = false;
titleLayout = externalTitleLayout;
}
else
{
GUILayoutY buttonCenter = keyRowLayout.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 editIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Edit));
GUIButton cloneBtn = new GUIButton(cloneIcon, GUIOption.FixedWidth(30));
GUIButton deleteBtn = new GUIButton(deleteIcon, GUIOption.FixedWidth(30));
GUIButton editBtn = new GUIButton(editIcon, GUIOption.FixedWidth(30));
cloneBtn.OnClick += () => parent.OnCloneButtonClicked(key);
deleteBtn.OnClick += () => parent.OnDeleteButtonClicked(key);
editBtn.OnClick += () => parent.OnEditButtonClicked(key);
titleLayout.AddElement(cloneBtn);
titleLayout.AddElement(deleteBtn);
titleLayout.AddSpace(10);
titleLayout.AddElement(editBtn);
}
///
/// Creates GUI elements specific to type in the key portion of a dictionary entry.
///
/// Layout to insert the row GUI elements to.
/// An optional title bar layout that the standard dictionary buttons will be appended to.
protected abstract GUILayoutX CreateKeyGUI(GUILayoutY layout);
///
/// Creates GUI elements specific to type in the key portion of a dictionary entry.
///
/// Layout to insert the row GUI elements to.
protected abstract void CreateValueGUI(GUILayoutY layout);
///
/// Refreshes the GUI for the dictionary 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 dictionary's row.
///
/// Type of the value. Must match the dictionary's element type.
/// Value in this dictionary's row.
protected T GetValue()
{
return (T)parent.GetValue(key);
}
///
/// Sets the value contained in this dictionary's row.
///
/// Type of the value. Must match the dictionary's element type.
/// Value to set.
protected void SetValue(T value)
{
parent.SetValue(key, value);
}
///
/// Destroys all row GUI elements.
///
public void Destroy()
{
rowLayout.Destroy();
rowLayout = null;
}
}
}