//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System;
using System.Collections.Generic;
using BansheeEngine;
namespace BansheeEditor
{
/** @addtogroup AnimationEditor
* @{
*/
///
/// Renders GUI elements that display a Scene Object, its transform, components and child objects, as well as all of
/// their fields. User can then select one of the fields and the class will output a path to the selected field, as
/// well as its parent scene object and component.
///
public class GUIFieldSelector
{
private const int INDENT_AMOUNT = 5;
private SpriteTexture ICON_SO; // TODO
private SpriteTexture ICON_COMPONENT; // TODO
private GUILayoutY mainLayout;
private SceneObject rootSO;
private Element rootElement;
///
/// Stores a single entry in the field hierarchy.
///
private struct Element
{
public Element(SceneObject so, Component comp, string path)
{
this.so = so;
this.comp = comp;
this.path = path;
toggle = null;
childLayout = null;
indentLayout = null;
children = null;
}
public SceneObject so;
public Component comp;
public string path;
public GUIToggle toggle;
public GUILayoutY childLayout;
public GUILayoutX indentLayout;
public Element[] children;
}
///
/// Triggers when the user selects a field. The subscriber will be receive a scene object the field is part of,
/// component the field is part of, and a path to the field, each entry separated by "/". Component can be null
/// in which case it is assumed the field is part of the SceneObject itself.
///
public Action OnElementSelected;
///
/// Creates a new GUIFieldSelector and registers its GUI elements in the provided layout.
///
/// Layout into which to add the selector GUI hierarchy.
/// Scene object to inspect the fields for.
public GUIFieldSelector(GUILayout layout, SceneObject so)
{
rootSO = so;
mainLayout = layout.AddLayoutY();
Rebuild();
}
///
/// Rebuilds all of the selection GUI.
///
private void Rebuild()
{
mainLayout.Clear();
rootElement = new Element();
if (rootSO == null)
return;
rootElement.so = rootSO;
AddSceneObjectRows(rootElement);
}
///
/// Registers a set of rows for all components in a , as well as its transform and
/// child objects.
///
/// Row element under which to create the new rows.
private void AddSceneObjectRows(Element parent)
{
Component[] components = parent.so.GetComponents();
parent.children = new Element[components.Length + 2];
// Add transform
parent.children[0] = AddFoldoutRow(mainLayout, ICON_COMPONENT, "Transform", parent.so,
null, "", ToggleTransformFoldout);
// Add components
for (int i = 0; i < components.Length; i++)
{
Component childComponent = components[i];
Action toggleCallback =
(toggleParent, expand) =>
{
SerializableObject componentObject = new SerializableObject(childComponent.GetType(), null);
ToggleObjectFoldout(toggleParent, componentObject, expand);
};
string name = childComponent.GetType().Name;
parent.children[i + 1] = AddFoldoutRow(mainLayout, ICON_COMPONENT, name, parent.so, childComponent, "",
toggleCallback);
}
// Add children
parent.children[parent.children.Length - 1] = AddFoldoutRow(mainLayout, ICON_SO, "Children", parent.so, null, "",
ToggleChildFoldout);
}
///
/// Registers a set of rows for all child fields of the provided object.
///
/// Parent foldout row to which to append the new elements.
/// Type of the object whose fields to display.
private void AddObjectRows(Element parent, SerializableObject serializableObject)
{
List elements = new List();
foreach (var field in serializableObject.Fields)
{
if (!field.Inspectable)
continue;
string propertyPath = parent.path + "/" + field.Name;
switch (field.Type)
{
case SerializableProperty.FieldType.Bool:
case SerializableProperty.FieldType.Float:
case SerializableProperty.FieldType.Int:
case SerializableProperty.FieldType.Color:
case SerializableProperty.FieldType.Vector2:
case SerializableProperty.FieldType.Vector3:
case SerializableProperty.FieldType.Vector4:
elements.Add(AddFieldRow(parent.childLayout, field.Name, parent.so, parent.comp, propertyPath));
break;
case SerializableProperty.FieldType.Object:
Action toggleCallback =
(toggleParent, expand) =>
{
SerializableObject childObject = new SerializableObject(field.InternalType, null);
ToggleObjectFoldout(toggleParent, childObject, expand);
};
elements.Add(AddFoldoutRow(parent.childLayout, null, field.Name, parent.so, parent.comp,
propertyPath, toggleCallback));
break;
}
}
parent.children = elements.ToArray();
}
///
/// Registers a new row in the layout. The row cannot have any further children and can be selected as the output
/// field.
///
/// Layout to append the row GUI elements to.
/// Name of the field.
/// Parent scene object of the field.
/// Parent component of the field. Can be null if field belongs to .
///
/// Slash separated path to the field from its parent object.
/// Element object storing all information about the added field.
private Element AddFieldRow(GUILayout layout, string name, SceneObject so, Component component, string path)
{
Element element = new Element(so, component, path);
GUILayoutX elementLayout = layout.AddLayoutX();
GUILabel label = new GUILabel(new LocEdString(name));
elementLayout.AddElement(label);
GUIButton selectBtn = new GUIButton(new LocEdString("Select"));
selectBtn.OnClick += () => { DoOnElementSelected(element); };
elementLayout.AddFlexibleSpace();
elementLayout.AddElement(selectBtn);
element.path = path;
return element;
}
///
/// Registers a new row in the layout. The row cannot be selected as the output field, but rather can be expanded
/// so it displays child elements.
///
/// Layout to append the row GUI elements to.
/// Optional icon to display next to the name. Can be null.
/// Name of the field.
/// Parent scene object of the field.
/// Parent component of the field. Can be null if field belongs to .
///
/// Slash separated path to the field from its parent object.
/// Callback to trigger when the user expands or collapses the foldout.
/// Element object storing all information about the added field.
private Element AddFoldoutRow(GUILayout layout, SpriteTexture icon, string name, SceneObject so, Component component,
string path, Action toggleCallback)
{
Element element = new Element(so, component, path);
GUILayoutY elementLayout = layout.AddLayoutY();
GUILayoutX foldoutLayout = elementLayout.AddLayoutX();
element.toggle = new GUIToggle(EditorStyles.Expand);
element.toggle.OnToggled += x => toggleCallback(element, x);
foldoutLayout.AddElement(element.toggle);
if (icon != null)
{
GUITexture guiIcon = new GUITexture(icon, GUIOption.FixedWidth(16), GUIOption.FixedWidth(16));
foldoutLayout.AddElement(guiIcon);
}
GUILabel label = new GUILabel(new LocEdString(name));
foldoutLayout.AddElement(label);
foldoutLayout.AddFlexibleSpace();
element.indentLayout = elementLayout.AddLayoutX();
element.indentLayout.AddSpace(INDENT_AMOUNT);
element.childLayout = element.indentLayout.AddLayoutY();
element.indentLayout.Active = false;
return element;
}
///
/// Expands or collapses the set of rows displaying a 's transform (position, rotation,
/// scale).
///
/// Parent row element whose children to expand/collapse.
/// True to expand, false to collapse.
private void ToggleTransformFoldout(Element parent, bool expand)
{
parent.childLayout.Clear();
parent.children = null;
parent.indentLayout.Active = expand;
if (expand)
{
parent.children = new Element[3];
parent.children[0] = AddFieldRow(parent.childLayout, "Position", parent.so, null, parent.path + "/Position");
parent.children[1] = AddFieldRow(parent.childLayout, "Rotation", parent.so, null, parent.path + "/Rotation");
parent.children[2] = AddFieldRow(parent.childLayout, "Scale", parent.so, null, parent.path + "/Scale");
}
}
///
/// Expands or collapses the set of rows displaying all children of a
/// .
///
/// Parent row element whose children to expand/collapse.
/// True to expand, false to collapse.
private void ToggleChildFoldout(Element parent, bool expand)
{
parent.childLayout.Clear();
parent.children = null;
parent.indentLayout.Active = expand;
if (expand)
{
int numChildren = parent.so.GetNumChildren();
parent.children = new Element[numChildren];
for (int i = 0; i < numChildren; i++)
{
SceneObject child = parent.so.GetChild(i);
parent.children[i] = AddFoldoutRow(parent.childLayout, ICON_SO, child.Name, child, null, "",
ToggleSceneObjectFoldout);
}
}
}
///
/// Expands or collapses the set of rows displaying all children of a . This includes
/// it's components, transform and child scene objects.
///
/// Parent row element whose children to expand/collapse.
/// True to expand, false to collapse.
private void ToggleSceneObjectFoldout(Element parent, bool expand)
{
parent.childLayout.Clear();
parent.children = null;
parent.indentLayout.Active = expand;
if (expand)
{
AddSceneObjectRows(parent);
}
}
///
/// Expands or collapses the set of rows displaying all fields of a serializable object.
///
/// Parent row element whose children to expand/collapse.
/// Object describing the type of the object whose fields to display.
/// True to expand, false to collapse.
private void ToggleObjectFoldout(Element parent, SerializableObject obj,
bool expand)
{
parent.childLayout.Clear();
parent.children = null;
parent.indentLayout.Active = expand;
if (expand)
AddObjectRows(parent, obj);
}
///
/// Triggered when the user selects a field.
///
/// Row element for the field that was selected.
private void DoOnElementSelected(Element element)
{
if (OnElementSelected != null)
OnElementSelected(element.so, element.comp, element.path);
}
}
/** @} */
}