You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
20 KiB
511 lines
20 KiB
// This code is part of the Fungus library (http://fungusgames.com) maintained by Chris Gregan (http://twitter.com/gofungus). |
|
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE) |
|
|
|
using UnityEditor; |
|
using UnityEditorInternal; |
|
using UnityEngine; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Reflection; |
|
using UnityEditor.Callbacks; |
|
|
|
namespace Fungus |
|
{ |
|
[CustomEditor (typeof(LuaBindings))] |
|
public class LuaBindingsEditor : Editor |
|
{ |
|
protected ReorderableList boundObjectsList; |
|
|
|
protected SerializedProperty allLuaEnvironmentsProp; |
|
protected SerializedProperty luaEnvironmentProp; |
|
protected SerializedProperty tableNameProp; |
|
protected SerializedProperty registerTypesProp; |
|
protected SerializedProperty boundObjectsProp; |
|
protected SerializedProperty showInheritedProp; |
|
|
|
protected string bindingHelpItem = ""; |
|
protected string bindingHelpDetail = ""; |
|
|
|
protected virtual void OnEnable() |
|
{ |
|
allLuaEnvironmentsProp = serializedObject.FindProperty("allEnvironments"); |
|
luaEnvironmentProp = serializedObject.FindProperty("luaEnvironment"); |
|
tableNameProp = serializedObject.FindProperty("tableName"); |
|
registerTypesProp = serializedObject.FindProperty("registerTypes"); |
|
boundObjectsProp = serializedObject.FindProperty("boundObjects"); |
|
showInheritedProp = serializedObject.FindProperty("showInherited"); |
|
CreateBoundObjectsList(); |
|
} |
|
|
|
protected void CreateBoundObjectsList() |
|
{ |
|
boundObjectsList = new ReorderableList(serializedObject, boundObjectsProp, true, true, true, true); |
|
|
|
boundObjectsList.drawElementCallback = |
|
(Rect rect, int index, bool isActive, bool isFocused) => { |
|
var element = boundObjectsList.serializedProperty.GetArrayElementAtIndex(index); |
|
rect.y += 2; |
|
|
|
float widthA = rect.width * 0.25f; |
|
float widthB = rect.width * 0.75f * 0.5f; |
|
float widthC = rect.width * 0.75f * 0.5f; |
|
|
|
SerializedProperty keyProp = element.FindPropertyRelative("key"); |
|
SerializedProperty objectProp = element.FindPropertyRelative("obj"); |
|
|
|
EditorGUI.BeginChangeCheck(); |
|
|
|
EditorGUI.PropertyField( |
|
new Rect(rect.x, rect.y, widthA - 5, EditorGUIUtility.singleLineHeight), |
|
keyProp, GUIContent.none); |
|
|
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
// Force the key to be a valid Lua variable name |
|
LuaBindings luaBindings = target as LuaBindings; |
|
keyProp.stringValue = GetUniqueKey(luaBindings, keyProp.stringValue, index); |
|
} |
|
|
|
EditorGUI.BeginChangeCheck(); |
|
|
|
EditorGUI.PropertyField( |
|
new Rect(rect.x + widthA, rect.y, widthB - 5, EditorGUIUtility.singleLineHeight), |
|
objectProp, GUIContent.none); |
|
|
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
// Use the object name as the key |
|
string keyName = objectProp.objectReferenceValue.name; |
|
LuaBindings luaBindings = target as LuaBindings; |
|
element.FindPropertyRelative("key").stringValue = GetUniqueKey(luaBindings, keyName.ToLower(), index); |
|
|
|
// Auto select any Flowchart component in the object |
|
GameObject go = objectProp.objectReferenceValue as GameObject; |
|
if (go != null) |
|
{ |
|
Component flowchart = go.GetComponent("Fungus.Flowchart"); |
|
if (flowchart != null) |
|
{ |
|
SerializedProperty componentProp = element.FindPropertyRelative("component"); |
|
componentProp.objectReferenceValue = flowchart; |
|
} |
|
} |
|
} |
|
|
|
if (objectProp.objectReferenceValue != null) |
|
{ |
|
GameObject go = objectProp.objectReferenceValue as GameObject; |
|
if (go != null) |
|
{ |
|
SerializedProperty componentProp = element.FindPropertyRelative("component"); |
|
|
|
int selected = 0; |
|
List<string> options = new List<string>(); |
|
options.Add("<GameObject>"); |
|
|
|
int count = 1; |
|
Component[] componentList = go.GetComponents<Component>(); |
|
foreach (Component component in componentList) |
|
{ |
|
if (componentProp.objectReferenceValue == component) |
|
{ |
|
selected = count; |
|
} |
|
|
|
if (component == null || |
|
component.GetType() == null) |
|
{ |
|
// Missing script? |
|
continue; |
|
} |
|
|
|
string componentName = component.GetType().ToString().Replace("UnityEngine.", ""); |
|
options.Add(componentName); |
|
|
|
count++; |
|
} |
|
|
|
int i = EditorGUI.Popup( |
|
new Rect(rect.x + widthA + widthB, rect.y, widthC, EditorGUIUtility.singleLineHeight), |
|
selected, |
|
options.ToArray()); |
|
if (i == 0) |
|
{ |
|
componentProp.objectReferenceValue = null; |
|
} |
|
else |
|
{ |
|
componentProp.objectReferenceValue = componentList[i - 1]; |
|
} |
|
} |
|
} |
|
|
|
boundObjectsList.onAddCallback = (ReorderableList l) => { |
|
// Add a new item. This copies last item in the list, so clear new items values. |
|
boundObjectsProp.InsertArrayElementAtIndex(boundObjectsProp.arraySize); |
|
SerializedProperty newItem = boundObjectsProp.GetArrayElementAtIndex(boundObjectsProp.arraySize - 1); |
|
newItem.FindPropertyRelative("key").stringValue = ""; |
|
newItem.FindPropertyRelative("obj").objectReferenceValue = null; |
|
newItem.FindPropertyRelative("component").objectReferenceValue = null; |
|
}; |
|
}; |
|
|
|
boundObjectsList.drawHeaderCallback = (Rect rect) => { |
|
|
|
float widthA = rect.width * 0.25f; |
|
float widthB = rect.width * 0.75f * 0.5f; |
|
float widthC = rect.width * 0.75f * 0.5f; |
|
|
|
EditorGUI.LabelField(new Rect(rect.x+ 12, rect.y, widthA, EditorGUIUtility.singleLineHeight), "Key"); |
|
EditorGUI.LabelField(new Rect(rect.x + widthA + 12, rect.y, widthB, EditorGUIUtility.singleLineHeight), "Object"); |
|
EditorGUI.LabelField(new Rect(rect.x + widthA + widthB + 6, rect.y, widthC, EditorGUIUtility.singleLineHeight), "Component"); |
|
}; |
|
} |
|
|
|
public override void OnInspectorGUI() |
|
{ |
|
serializedObject.Update(); |
|
|
|
EditorGUILayout.PropertyField(allLuaEnvironmentsProp); |
|
if (!allLuaEnvironmentsProp.boolValue) |
|
{ |
|
EditorGUILayout.PropertyField(luaEnvironmentProp); |
|
} |
|
|
|
EditorGUILayout.PropertyField(tableNameProp); |
|
EditorGUILayout.PropertyField(registerTypesProp); |
|
|
|
EditorGUILayout.LabelField("Object Bindings"); |
|
|
|
boundObjectsList.DoLayoutList(); |
|
EditorGUILayout.Space(); |
|
|
|
ShowBindingMemberInfo(); |
|
|
|
// Update the bound types on every tick to make sure they're up to date. |
|
// This could be a bit heavy on performance if the bound object list is long, but |
|
// I couldn't get it to work reliably by only updating when the list has changed. |
|
// This only happens when inspecting a Fungus Bindings component so I think it'll be ok. |
|
PopulateBoundTypes(target as LuaBindings, serializedObject); |
|
|
|
serializedObject.ApplyModifiedProperties(); |
|
} |
|
|
|
protected virtual void ShowBindingMemberInfo() |
|
{ |
|
List<string> items = new List<string>(); |
|
items.Add("<Select a binding member>"); |
|
|
|
List<string> details = new List<string>(); |
|
details.Add(""); |
|
|
|
LuaBindings luaBindings = target as LuaBindings; |
|
foreach (BoundObject boundObject in luaBindings.BoundObjects) |
|
{ |
|
UnityEngine.Object inspectObject = boundObject.obj; |
|
if (boundObject.component != null) |
|
{ |
|
inspectObject = boundObject.component; |
|
} |
|
|
|
// Ignore empty entries |
|
if (inspectObject == null || |
|
boundObject.key.Length == 0) |
|
{ |
|
continue; |
|
} |
|
|
|
BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public; |
|
if (!showInheritedProp.boolValue) |
|
{ |
|
flags |= BindingFlags.DeclaredOnly; |
|
} |
|
|
|
// Show info for fields and properties |
|
MemberInfo[] memberInfos = inspectObject.GetType().GetMembers(flags); |
|
foreach (MemberInfo memberInfo in memberInfos) |
|
{ |
|
if (memberInfo.MemberType != MemberTypes.Field && |
|
memberInfo.MemberType != MemberTypes.Property) |
|
{ |
|
continue; |
|
} |
|
|
|
string item = boundObject.key + "/" + memberInfo.Name; |
|
items.Add(item); |
|
|
|
string detail = ""; |
|
detail += "Binding Key: " + boundObject.key + "\n"; |
|
detail += "Bound Object: " + boundObject.obj.name + "\n"; |
|
if (boundObject.component != null) |
|
{ |
|
detail += "Bound Component: " + boundObject.component.GetType() + "\n"; |
|
} |
|
|
|
if (memberInfo.MemberType == MemberTypes.Field) |
|
{ |
|
detail += "Field Name: " + memberInfo.Name + "\n"; |
|
detail += "Field Type: " + (memberInfo as FieldInfo).FieldType; |
|
} |
|
else if (memberInfo.MemberType == MemberTypes.Property) |
|
{ |
|
detail += "Property Name: " + memberInfo.Name + "\n"; |
|
detail += "Property Type: " + (memberInfo as PropertyInfo).PropertyType; |
|
} |
|
|
|
details.Add(detail); |
|
} |
|
|
|
// Show info for methods |
|
foreach (MemberInfo memberInfo in memberInfos) |
|
{ |
|
if (memberInfo.MemberType != MemberTypes.Method) |
|
{ |
|
continue; |
|
} |
|
|
|
MethodInfo methodInfo = memberInfo as MethodInfo; |
|
if (methodInfo.IsGenericMethod) |
|
{ |
|
continue; |
|
} |
|
|
|
// Build list of parameters |
|
string param = ""; |
|
foreach (ParameterInfo pi in methodInfo.GetParameters() ) |
|
{ |
|
if (param == "") |
|
{ |
|
param += pi.Name; |
|
} |
|
else |
|
{ |
|
param += ", " + pi.Name; |
|
} |
|
} |
|
param = "(" + param + ")"; |
|
|
|
string item = boundObject.key + "/" + methodInfo.Name + param; |
|
items.Add(item); |
|
|
|
string detail = ""; |
|
detail += "Binding Key: " + boundObject.key + "\n"; |
|
detail += "Bound Object: " + boundObject.obj.name + "\n"; |
|
if (boundObject.component != null) |
|
{ |
|
detail += "Bound Component: " + boundObject.component.GetType() + "\n"; |
|
} |
|
|
|
detail += "Method Name: " + methodInfo.Name + "\n"; |
|
foreach (ParameterInfo pi in methodInfo.GetParameters() ) |
|
{ |
|
detail += "Parameter: " + pi.Name + " (" + pi.ParameterType.ToString() + ")\n"; |
|
} |
|
detail += "Return Type : " + methodInfo.ReturnType.ToString(); |
|
|
|
details.Add(detail); |
|
} |
|
} |
|
|
|
EditorGUILayout.BeginHorizontal(); |
|
|
|
EditorGUILayout.PrefixLabel(new GUIContent("Member Info", "Display member info for the selected binding")); |
|
|
|
GUILayout.FlexibleSpace(); |
|
|
|
// Display the popup list of bound members |
|
int selectedIndex = EditorGUILayout.Popup(0, items.ToArray()); |
|
if (selectedIndex > 0) |
|
{ |
|
bindingHelpItem = items[selectedIndex].Replace("/", "."); |
|
bindingHelpDetail = details[selectedIndex]; |
|
} |
|
|
|
EditorGUILayout.EndHorizontal(); |
|
|
|
EditorGUILayout.PropertyField(showInheritedProp); |
|
|
|
// Show help info for currently selected item |
|
if (bindingHelpItem != "") |
|
{ |
|
EditorGUILayout.HelpBox(bindingHelpDetail, MessageType.Info); |
|
|
|
EditorGUILayout.PrefixLabel(new GUIContent("Lua Usage", "A copyable example of how this item could be used in a Lua script.")); |
|
|
|
GUIStyle textAreaStyle = EditorStyles.textField; |
|
textAreaStyle.wordWrap = true; |
|
|
|
EditorGUILayout.TextArea(bindingHelpItem, textAreaStyle, GUILayout.MinHeight(EditorGUIUtility.singleLineHeight * 3)); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Returns a new binding key that is guaranteed to be a valid Lua variable name and |
|
/// not to clash with any existing binding in the list. |
|
/// </summary> |
|
protected virtual string GetUniqueKey(LuaBindings luaBindings, string originalKey, int ignoreIndex = -1) |
|
{ |
|
string baseKey = originalKey; |
|
|
|
// Only letters and digits allowed |
|
char[] arr = baseKey.Where(c => (char.IsLetterOrDigit(c) || c == '_')).ToArray(); |
|
baseKey = new string(arr); |
|
|
|
// No leading digits allowed |
|
baseKey = baseKey.TrimStart('0','1','2','3','4','5','6','7','8','9'); |
|
|
|
// No empty keys allowed |
|
if (baseKey.Length == 0) |
|
{ |
|
baseKey = "object"; |
|
} |
|
|
|
// Build a hash of all keys currently in use |
|
HashSet<string> keyhash = new HashSet<string>(); |
|
for (int i = 0; i < luaBindings.BoundObjects.Count; ++i) |
|
{ |
|
if (i == ignoreIndex) |
|
{ |
|
continue; |
|
} |
|
|
|
keyhash.Add(luaBindings.BoundObjects[i].key); |
|
} |
|
|
|
// Append a suffix to make the key unique |
|
string key = baseKey; |
|
int suffix = 0; |
|
while (keyhash.Contains(key)) |
|
{ |
|
suffix++; |
|
key = baseKey + suffix; |
|
} |
|
|
|
return key; |
|
} |
|
|
|
[DidReloadScripts()] |
|
protected static void DidReloadScripts() |
|
{ |
|
LuaBindings[] luaBindingsList = GameObject.FindObjectsOfType<LuaBindings>(); |
|
foreach (LuaBindings luaBindings in luaBindingsList) |
|
{ |
|
SerializedObject so = new SerializedObject(luaBindings); |
|
so.Update(); |
|
|
|
PopulateBoundTypes(luaBindings, so); |
|
|
|
so.ApplyModifiedProperties(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Update the list of bound types on the LuaBindings object. |
|
/// </summary> |
|
protected static void PopulateBoundTypes(LuaBindings luaBindings, SerializedObject so) |
|
{ |
|
// Use a temp HashSet to store the list of types. |
|
// The final list is stored as a list of type strings. |
|
HashSet<System.Type> typeSet = new HashSet<System.Type>(); |
|
foreach (BoundObject boundObject in luaBindings.BoundObjects) |
|
{ |
|
if (boundObject.obj == null) |
|
{ |
|
continue; |
|
} |
|
|
|
AddAllSubTypes(typeSet, boundObject.obj.GetType()); |
|
|
|
if (boundObject.component != null) |
|
{ |
|
AddAllSubTypes(typeSet, boundObject.component.GetType()); |
|
} |
|
} |
|
|
|
// Store the final list of types in the luaBindings object |
|
SerializedProperty boundTypesProp = so.FindProperty("boundTypes"); |
|
boundTypesProp.ClearArray(); |
|
int index = 0; |
|
foreach (System.Type t in typeSet) |
|
{ |
|
boundTypesProp.InsertArrayElementAtIndex(index); |
|
SerializedProperty element = boundTypesProp.GetArrayElementAtIndex(index); |
|
element.stringValue = t.AssemblyQualifiedName; |
|
index++; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Adds the type to the set of types, and then uses reflection to add |
|
/// all public fields, properties and methods to the set of types. |
|
/// </summary> |
|
protected static void AddAllSubTypes(HashSet<System.Type> typeSet, System.Type t) |
|
{ |
|
AddSubType(typeSet, t); |
|
|
|
MemberInfo[] memberInfos = t.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); |
|
foreach (MemberInfo memberInfo in memberInfos) |
|
{ |
|
if (memberInfo.MemberType == MemberTypes.Field) |
|
{ |
|
FieldInfo fieldInfo = memberInfo as FieldInfo; |
|
AddSubType(typeSet, fieldInfo.FieldType); |
|
} |
|
else if (memberInfo.MemberType == MemberTypes.Property) |
|
{ |
|
PropertyInfo propertyInfo = memberInfo as PropertyInfo; |
|
AddSubType(typeSet, propertyInfo.PropertyType); |
|
} |
|
else if (memberInfo.MemberType == MemberTypes.Method) |
|
{ |
|
MethodInfo methodInfo = memberInfo as MethodInfo; |
|
if (methodInfo.IsGenericMethod) |
|
{ |
|
continue; |
|
} |
|
|
|
if (methodInfo.ReturnType != typeof(void)) |
|
{ |
|
AddSubType(typeSet, methodInfo.ReturnType); |
|
} |
|
|
|
foreach (ParameterInfo pi in methodInfo.GetParameters() ) |
|
{ |
|
AddSubType(typeSet, pi.ParameterType); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Adds a single type to the type set. |
|
/// IEnumerable and IEnumerator types are handled specially. |
|
/// </summary> |
|
protected static void AddSubType(HashSet<System.Type> typeSet, System.Type t) |
|
{ |
|
// MoonSharp handles IEnumerator and IEnumerable types automatically, so just |
|
// register the generic type used. |
|
if (typeof(IEnumerable).IsAssignableFrom(t) || |
|
typeof(IEnumerator).IsAssignableFrom(t)) |
|
{ |
|
System.Type[] genericArguments = t.GetGenericArguments(); |
|
if (genericArguments.Count() == 1) |
|
{ |
|
System.Type containedType = genericArguments[0]; |
|
|
|
// The generic type could itself be an IEnumerator or IEnumerable, so |
|
// recursively check for this case. |
|
AddSubType(typeSet, containedType); |
|
} |
|
} |
|
else if (t != typeof(System.Object)) |
|
{ |
|
// Non-IEnumerable/IEnumerator types will be registered. |
|
typeSet.Add(t); |
|
} |
|
} |
|
|
|
} |
|
|
|
} |