//********************************** 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 System.Runtime.CompilerServices;
using System.Text;
using bs;
namespace bs.Editor
{
/** @addtogroup Inspector
* @{
*/
///
/// Displays GUI for a serializable property containing a dictionary. Dictionary contents are displayed as rows of
/// entries that can be shown, hidden or manipulated.
///
public class InspectableDictionary : InspectableField
{
private InspectableDictionaryGUI dictionaryGUIField;
///
/// Creates a new inspectable dictionary GUI for the specified property.
///
/// Context shared by all inspectable fields created by the same parent.
/// Name of the property, or some other value to set as the title.
/// Full path to this property (includes name of this property and all parent properties).
/// 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 dictionary whose contents to display.
public InspectableDictionary(InspectableContext context, string title, string path, int depth, InspectableFieldLayout layout,
SerializableProperty property)
: base(context, title, path, SerializableProperty.FieldType.Dictionary, depth, layout, property)
{
}
///
public override GUILayoutX GetTitleLayout()
{
return dictionaryGUIField.GetTitleLayout();
}
///
public override InspectableState Refresh(int layoutIndex)
{
return dictionaryGUIField.Refresh();
}
///
protected internal override void Initialize(int layoutIndex)
{
GUILayout dictionaryLayout = layout.AddLayoutY(layoutIndex);
dictionaryGUIField = InspectableDictionaryGUI.Create(context, title, path, property, dictionaryLayout, depth);
dictionaryGUIField.IsExpanded = context.Persistent.GetBool(path + "_Expanded");
dictionaryGUIField.OnExpand += x => context.Persistent.SetBool(path + "_Expanded", x);
}
///
public override InspectableField FindPath(string path)
{
string subPath = GetSubPath(path, depth + 1);
if (string.IsNullOrEmpty(subPath))
return null;
int lastLeftIdx = subPath.LastIndexOf("Key[");
int lastRightIdx = -1;
bool isKey;
if (lastLeftIdx != -1)
{
lastRightIdx = subPath.LastIndexOf(']', lastLeftIdx);
isKey = true;
}
else
{
lastLeftIdx = subPath.LastIndexOf("Value[");
lastRightIdx = subPath.LastIndexOf(']', lastLeftIdx);
isKey = false;
}
if (lastLeftIdx == -1 || lastRightIdx == -1)
return null;
int count = lastRightIdx - 1 - lastLeftIdx;
if (count <= 0)
return null;
string arrayIdxStr = subPath.Substring(lastLeftIdx, count);
if (!int.TryParse(arrayIdxStr, out int idx))
return null;;
if (idx >= dictionaryGUIField.NumRows)
return null;
InspectableDictionaryGUIRow row = dictionaryGUIField.GetRow(idx);
InspectableField field = null;
if (isKey)
field = row?.FieldKey;
else
field = row?.FieldValue;
if (field != null)
{
if (field.Path == path)
return field;
return field.FindPath(path);
}
return null;
}
///
/// Creates GUI elements that allow viewing and manipulation of a referenced
/// by a serializable property.
///
private class InspectableDictionaryGUI : GUIDictionaryFieldBase
{
private SerializableProperty property;
private IDictionary dictionary;
private int numElements;
private InspectableContext context;
private string path;
private List orderedKeys = new List();
///
/// Context shared by all inspectable fields created by the same parent.
///
public InspectableContext Context
{
get { return context; }
}
///
/// Returns a property path to the array field (name of the array field and all parent object fields).
///
public string Path
{
get { return path; }
}
///
/// Returns the number of rows in the array.
///
public int NumRows
{
get { return GetNumRows(); }
}
///
/// Constructs a new dictionary GUI.
///
/// Context shared by all inspectable fields created by the same parent.
/// Label to display on the list GUI title.
/// Full path to this property (includes name of this property and all parent properties).
///
/// Serializable property referencing a 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 InspectableDictionaryGUI(InspectableContext context, LocString title, string path, SerializableProperty property,
GUILayout layout, int depth = 0)
: base(title, layout, depth)
{
this.property = property;
this.context = context;
this.path = path;
dictionary = property.GetValue();
if (dictionary != null)
numElements = dictionary.Count;
UpdateKeys();
}
///
/// Builds the inspectable dictionary GUI elements. Must be called at least once in order for the contents to
/// be populated.
///
/// Context shared by all inspectable fields created by the same parent.
/// Label to display on the list GUI title.
/// Full path to this property (includes name of this property and all parent properties).
///
/// Serializable property referencing a 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.
public static InspectableDictionaryGUI Create(InspectableContext context, LocString title, string path,
SerializableProperty property, GUILayout layout, int depth = 0)
{
InspectableDictionaryGUI guiDictionary = new InspectableDictionaryGUI(context, title, path, property,
layout, depth);
guiDictionary.BuildGUI();
return guiDictionary;
}
///
/// Returns an array row at the specified index.
///
/// Index of the row.
/// Array row representation or null if index is out of range.
public InspectableDictionaryGUIRow GetRow(int idx)
{
if (idx < GetNumRows())
return (InspectableDictionaryGUIRow)rows[idx];
return null;
}
///
public override InspectableState Refresh()
{
// Check if any modifications to the array were made outside the inspector
IDictionary newDict = property.GetValue();
if (dictionary == null && newDict != null)
{
dictionary = newDict;
numElements = dictionary.Count;
BuildGUI();
}
else if (newDict == null && dictionary != null)
{
dictionary = null;
numElements = 0;
BuildGUI();
}
else
{
if (dictionary != null)
{
if (numElements != dictionary.Count)
{
numElements = dictionary.Count;
BuildGUI();
}
}
}
return base.Refresh();
}
///
/// Updates the ordered set of keys used for mapping sequential indexes to keys. Should be called whenever a
/// dictionary key changes.
///
private void UpdateKeys()
{
orderedKeys.Clear();
if (dictionary != null)
{
SerializableDictionary dict = property.GetDictionary();
foreach (var key in dictionary.Keys)
orderedKeys.Add(dict.GetProperty(key).Key);
}
}
///
protected override GUIDictionaryFieldRow CreateRow()
{
return new InspectableDictionaryGUIRow();
}
///
protected override int GetNumRows()
{
if (dictionary != null)
return dictionary.Count;
return 0;
}
///
protected override bool IsNull()
{
return dictionary == null;
}
///
protected internal override object GetKey(int rowIdx)
{
return orderedKeys[rowIdx];
}
///
protected internal override object GetValue(object key)
{
SerializableProperty keyProperty = (SerializableProperty)key;
SerializableDictionary dictionary = property.GetDictionary();
return dictionary.GetProperty(keyProperty.GetValue