//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System;
using System.Collections;
using System.Collections.Generic;
using bs;
namespace bs.Editor
{
/** @addtogroup GUI-Editor
* @{
*/
///
/// 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 Dictionary rows = new Dictionary();
protected GUIDictionaryFieldRow editRow;
protected GUILayoutY guiLayout;
protected GUILayoutX guiChildLayout;
protected GUILayoutX guiTitleLayout;
protected GUILayoutX guiInternalTitleLayout;
protected GUILayoutY guiContentLayout;
protected GUIToggle guiFoldout;
protected bool isExpanded;
protected int depth;
protected LocString title;
private int editRowIdx = -1;
private object editKey;
private object editValue;
private object editOriginalKey;
private State state;
private bool isModified;
///
/// Expands or collapses the entries of the dictionary.
///
public bool IsExpanded
{
get { return isExpanded; }
set
{
if (isExpanded != value)
ToggleFoldout(value, true);
}
}
///
/// Event that triggers when the list foldout is expanded or collapsed (rows are shown or hidden).
///
public Action OnExpand;
///
/// Constructs a new GUI dictionary.
///
/// Label to display on the dictionary GUI title.
/// 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 GUIDictionaryFieldBase(LocString title, GUILayout layout, int depth = 0)
{
this.title = title;
this.depth = depth;
guiLayout = layout.AddLayoutY();
guiTitleLayout = guiLayout.AddLayoutX();
}
///
/// Completely rebuilds the dictionary GUI elements.
///
public void BuildGUI()
{
editKey = CreateKey();
editValue = CreateValue();
UpdateHeaderGUI();
foreach (var KVP in rows)
KVP.Value.Destroy();
rows.Clear();
if (editRow != null)
{
editRow.Destroy();
editRow = null;
}
if (!IsNull())
{
// Hidden dependency: BuildGUI must be called after all elements are
// in the dictionary so we do it in two steps
int numRows = GetNumRows();
for (int i = 0; i < numRows; i++)
{
GUIDictionaryFieldRow newRow = CreateRow();
rows.Add(i, newRow);
}
editRow = CreateRow();
editRow.Initialize(this, guiContentLayout, numRows, depth + 1);
editRow.Enabled = false;
for (int i = 0; i < numRows; i++)
rows[i].Initialize(this, guiContentLayout, i, depth + 1);
}
}
///
/// Rebuilds the GUI dictionary header if needed.
///
protected void UpdateHeaderGUI()
{
Action BuildEmptyGUI = () =>
{
guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
guiInternalTitleLayout.AddElement(new GUILabel(title));
guiInternalTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create),
new LocEdString("Create"));
GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
createBtn.OnClick += OnCreateButtonClicked;
guiInternalTitleLayout.AddElement(createBtn);
};
Action BuildFilledGUI = () =>
{
guiInternalTitleLayout = guiTitleLayout.InsertLayoutX(0);
guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
guiFoldout.Value = isExpanded;
guiFoldout.AcceptsKeyFocus = false;
guiFoldout.OnToggled += x => ToggleFoldout(x, false);
GUIContent clearIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clear),
new LocEdString("Clear"));
GUIButton guiClearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(30));
guiClearBtn.OnClick += OnClearButtonClicked;
GUIContent addIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Add),
new LocEdString("Add"));
GUIButton guiAddBtn = new GUIButton(addIcon, GUIOption.FixedWidth(30));
guiAddBtn.OnClick += OnAddButtonClicked;
guiInternalTitleLayout.AddElement(guiFoldout);
guiInternalTitleLayout.AddElement(guiAddBtn);
guiInternalTitleLayout.AddElement(guiClearBtn);
guiChildLayout = guiLayout.AddLayoutX();
guiChildLayout.AddSpace(IndentAmount);
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
? EditorStylesInternal.InspectorContentBgAlternate
: EditorStylesInternal.InspectorContentBg;
GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
backgroundPanel.AddElement(inspectorContentBg);
ToggleFoldout(isExpanded, false);
};
if (state == State.None)
{
if (!IsNull())
{
BuildFilledGUI();
state = State.Filled;
}
else
{
BuildEmptyGUI();
state = State.Empty;
}
}
else if (state == State.Empty)
{
if (!IsNull())
{
guiInternalTitleLayout.Destroy();
BuildFilledGUI();
state = State.Filled;
}
}
else if (state == State.Filled)
{
if (IsNull())
{
guiInternalTitleLayout.Destroy();
guiChildLayout.Destroy();
BuildEmptyGUI();
state = State.Empty;
}
}
}
///
/// Destroys all rows and clears the row list.
///
private void ClearRows()
{
foreach (var KVP in rows)
KVP.Value.Destroy();
editRow.Destroy();
rows.Clear();
}
///
/// 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.
///
/// State representing was anything modified between two last calls to .
public virtual InspectableState Refresh()
{
InspectableState state = InspectableState.NotModified;
for (int i = 0; i < rows.Count; i++)
state |= rows[i].Refresh();
if (editRow != null && editRow.Enabled)
state |= editRow.Refresh();
if (isModified)
{
state |= InspectableState.Modified;
isModified = false;
}
return state;
}
///
/// Destroys the GUI elements.
///
public void Destroy()
{
if (guiLayout != null)
{
guiLayout.Destroy();
guiLayout = null;
}
guiLayout = null;
guiTitleLayout = null;
guiChildLayout = null;
for (int i = 0; i < rows.Count; i++)
rows[i].Destroy();
rows.Clear();
if (editRow != null)
editRow.Destroy();
editRow = null;
}
///
/// Checks is the specified row index the temporary edit row.
///
/// Sequential index of the row to check.
/// True if the index is of an edit row.
private bool IsTemporaryRow(int rowIdx)
{
return rowIdx == rows.Count;
}
///
/// Checks is any row being currently edited.
///
/// True if a row is being edited, false otherwise.
private bool IsEditInProgress()
{
return editRowIdx != -1;
}
///
/// Returns the number of rows in the dictionary.
///
/// Number of rows in the dictionary.
protected abstract int GetNumRows();
///
/// Checks is the dictionary instance not assigned.
///
/// True if there is not a dictionary instance.
protected abstract bool IsNull();
///
/// Gets a value of an element at the specified index in the list. Also handles temporary edit fields.
///
/// Sequential index of the row to set the value for.
/// Value of the list element at the specified key.
protected internal virtual object GetValueInternal(int rowIdx)
{
if (rowIdx == editRowIdx || IsTemporaryRow(rowIdx))
return editValue;
else
return GetValue(GetKey(rowIdx));
}
///
/// Sets a value of an element at the specified index in the list. Also handles temporary edit fields.
///
/// Sequential index of the row to set the value for.
/// Value to assign to the element. Caller must ensure it is of valid type.
protected internal virtual void SetValueInternal(int rowIdx, object value)
{
if (rowIdx == editRowIdx || IsTemporaryRow(rowIdx))
editValue = value;
else
SetValue(GetKey(rowIdx), value);
}
///
/// Changes the value of the key of the specified row.
///
/// Sequential index of the row to set the key for.
/// Key to assign to the specified row.
protected internal void SetKey(int rowIdx, object key)
{
if (editRowIdx != rowIdx)
{
Debug.LogError("Cannot change a dictionary row that is not in edit state.");
return;
}
editKey = key;
}
///
/// Gets a key for a row at the specified index. Handles the special case for the currently edited row.
///
/// Sequential index of the row for which to retrieve the key.
/// Key for a row at the specified index.
protected internal object GetKeyInternal(int rowIdx)
{
if (editRowIdx == rowIdx || IsTemporaryRow(rowIdx))
return editKey;
return GetKey(rowIdx);
}
///
/// Creates a new dictionary row GUI.
///
/// Object containing the dictionary row GUI.
protected abstract GUIDictionaryFieldRow CreateRow();
///
/// Gets a key for a row at the specified index.
///
/// Sequential index of the row for which to retrieve the key.
/// Key for a row at the specified index.
protected internal abstract object GetKey(int rowIdx);
///
/// Gets a value of an element at the specified index in the list.
///
/// Key of the element whose value to retrieve.
/// Value of the dictionary entry for 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);
///
/// Updates both key and value of an existing entry.
///
/// Original key of the entry.
/// New key of the entry.
/// New value of the entry.
protected internal abstract void EditEntry(object oldKey, object newKey, object value);
///
/// Adds a new entry to the dictionary.
///
/// Key of the entry to add.
/// Value of the entry to add.
protected internal abstract void AddEntry(object key, object value);
///
/// Removes the specified entry from the dictionary.
///
/// Key of the entry to remove.
protected internal abstract void RemoveEntry(object key);
///
/// Creates a new empty key object of a valid type that can be used in the dictionary.
///
/// New empty key object.
protected internal abstract object CreateKey();
///
/// Creates a new empty value object of a valid type that can be used in the dictionary.
///
/// New empty value object.
protected internal abstract object CreateValue();
///
/// 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);
///
/// Clones the specified dictionary element.
///
/// Sequential index of the element in the dictionary to clone.
protected internal abstract KeyValuePair