//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System;
using System.Text;
using bs;
namespace bs.Editor
{
/** @addtogroup Inspector
* @{
*/
///
/// 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
{
private InspectableArrayGUI arrayGUIField;
private InspectableFieldStyleInfo style;
///
/// Style applied to the elements of the array and the array itself.
///
internal InspectableFieldStyleInfo Style
{
get { return style; }
}
///
/// Creates a new inspectable array 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 array whose contents to display.
/// Information that can be used for customizing field rendering and behaviour.
public InspectableArray(InspectableContext context, string title, string path, int depth, InspectableFieldLayout layout,
SerializableProperty property, InspectableFieldStyleInfo style)
: base(context, title, path, SerializableProperty.FieldType.Array, depth, layout, property)
{
this.style = style;
}
///
public override GUILayoutX GetTitleLayout()
{
return arrayGUIField.GetTitleLayout();
}
///
public override InspectableState Refresh(int layoutIndex)
{
return arrayGUIField.Refresh();
}
///
protected internal override void Initialize(int layoutIndex)
{
GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
arrayGUIField = InspectableArrayGUI.Create(context, title, path, property, arrayLayout, depth, style);
arrayGUIField.IsExpanded = context.Persistent.GetBool(path + "_Expanded");
arrayGUIField.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('[');
int lastRightIdx = subPath.LastIndexOf(']', lastLeftIdx);
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 >= arrayGUIField.NumRows)
return null;
InspectableArrayGUIRow row = arrayGUIField.GetRow(idx);
InspectableField field = row?.Field;
if (field != null)
{
if (field.Path == path)
return field;
return field.FindPath(path);
}
return null;
}
///
/// Handles creation of GUI elements for a GUI list field that displays a object.
///
private class InspectableArrayGUI : GUIListFieldBase
{
private Array array;
private int numElements;
private InspectableContext context;
private SerializableProperty property;
private string path;
private InspectableFieldStyleInfo style;
///
/// 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; }
}
///
/// Style applied to the elements of the array and the array itself.
///
public InspectableFieldStyleInfo Style
{
get { return style; }
}
///
/// Returns the number of rows in the array.
///
public int NumRows
{
get { return GetNumRows(); }
}
///
/// Constructs a new inspectable array 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 single-dimensional array.
/// 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.
/// Information that can be used for customizing field rendering and behaviour.
public InspectableArrayGUI(InspectableContext context, LocString title, string path, SerializableProperty property,
GUILayout layout, int depth, InspectableFieldStyleInfo style)
: base(title, layout, depth)
{
this.property = property;
this.context = context;
this.path = path;
this.style = style;
array = property.GetValue();
if (array != null)
numElements = array.Length;
}
///
/// Creates a new inspectable array GUI object that displays the contents of the provided serializable property.
///
/// 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 single-dimensional array.
/// 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.
/// Information that can be used for customizing field rendering and behaviour.
public static InspectableArrayGUI Create(InspectableContext context, LocString title, string path,
SerializableProperty property, GUILayout layout, int depth, InspectableFieldStyleInfo style)
{
InspectableArrayGUI guiArray = new InspectableArrayGUI(context, title, path, property, layout, depth, style);
guiArray.BuildGUI();
return guiArray;
}
///
/// Returns an array row at the specified index.
///
/// Index of the row.
/// Array row representation or null if index is out of range.
public InspectableArrayGUIRow GetRow(int idx)
{
if (idx < GetNumRows())
return (InspectableArrayGUIRow)rows[idx];
return null;
}
///
public override InspectableState Refresh()
{
// Check if any modifications to the array were made outside the inspector
Array newArray = property.GetValue();
if (array == null && newArray != null)
{
array = newArray;
numElements = array.Length;
BuildGUI();
}
else if (newArray == null && array != null)
{
array = null;
numElements = 0;
BuildGUI();
}
else
{
if (array != null)
{
if (numElements != array.Length)
{
numElements = array.Length;
BuildGUI();
}
}
}
return base.Refresh();
}
///
protected override GUIListFieldRow CreateRow()
{
return new InspectableArrayGUIRow();
}
///
protected override bool IsNull()
{
return array == null;
}
///
protected override int GetNumRows()
{
if (array != null)
return array.Length;
return 0;
}
///
protected internal override object GetValue(int seqIndex)
{
SerializableArray serzArray = property.GetArray();
// Create a property wrapper for native arrays so we get notified when the array values change
if (style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NativeWrapper))
{
SerializableProperty.Getter getter = () =>
{
Array array = property.GetValue();
if (array != null)
return array.GetValue(seqIndex);
else
return null;
};
SerializableProperty.Setter setter = (object value) =>
{
Array array = property.GetValue();
if (array != null)
{
array.SetValue(value, seqIndex);
property.SetValue(array);
}
};
return new SerializableProperty(serzArray.ElementPropertyType, serzArray.ElementType, getter, setter);
}
else
return serzArray.GetProperty(seqIndex);
}
///
protected internal override void SetValue(int seqIndex, object value)
{
// Setting the value should be done through the property
throw new InvalidOperationException();
}
///
protected override void CreateList()
{
StartUndo();
array = property.CreateArrayInstance(new int[1] { 0 });
property.SetValue(array);
numElements = 0;
EndUndo();
}
///
protected override void ResizeList()
{
StartUndo();
int size = guiSizeField.Value; // TODO - Support multi-rank arrays
Array newArray = property.CreateArrayInstance(new int[] { size });
int maxSize = MathEx.Min(size, array.Length);
for (int i = 0; i < maxSize; i++)
newArray.SetValue(array.GetValue(i), i);
property.SetValue(newArray);
array = newArray;
numElements = size;
EndUndo();
}
///
protected override void ClearList()
{
// Native arrays cannot be set to null, so just clear their size to zero instead
if (style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NativeWrapper))
CreateList();
else
{
StartUndo();
property.SetValue