Jelajahi Sumber

Added ability to register custom inspectors for managed types

BearishSun 10 tahun lalu
induk
melakukan
29ddb80a56
30 mengubah file dengan 366 tambahan dan 93 penghapusan
  1. 7 2
      BansheeMono/Include/BsMonoClass.h
  2. 26 3
      BansheeMono/Source/BsMonoClass.cpp
  3. 13 1
      MBansheeEditor/Inspector/CustomInspector.cs
  4. 2 5
      MBansheeEditor/Inspector/GenericInspector.cs
  5. 4 4
      MBansheeEditor/Inspector/InspectableArray.cs
  6. 1 1
      MBansheeEditor/Inspector/InspectableBool.cs
  7. 1 1
      MBansheeEditor/Inspector/InspectableColor.cs
  8. 16 18
      MBansheeEditor/Inspector/InspectableField.cs
  9. 1 1
      MBansheeEditor/Inspector/InspectableFloat.cs
  10. 1 1
      MBansheeEditor/Inspector/InspectableGameObjectRef.cs
  11. 1 1
      MBansheeEditor/Inspector/InspectableInt.cs
  12. 4 4
      MBansheeEditor/Inspector/InspectableList.cs
  13. 2 5
      MBansheeEditor/Inspector/InspectableObject.cs
  14. 1 1
      MBansheeEditor/Inspector/InspectableResourceRef.cs
  15. 1 1
      MBansheeEditor/Inspector/InspectableString.cs
  16. 1 1
      MBansheeEditor/Inspector/InspectableVector2.cs
  17. 1 1
      MBansheeEditor/Inspector/InspectableVector3.cs
  18. 1 1
      MBansheeEditor/Inspector/InspectableVector4.cs
  19. 37 0
      MBansheeEditor/Inspector/InspectorUtility.cs
  20. 3 11
      MBansheeEditor/Inspector/InspectorWindow.cs
  21. 2 1
      MBansheeEditor/MBansheeEditor.csproj
  22. 1 1
      MBansheeEditor/ProjectWindow.cs
  23. 9 10
      MBansheeEngine/GUI/GUILayout.cs
  24. 0 17
      MBansheeEngine/SerializableField.cs
  25. 52 0
      SBansheeEditor/Include/BsScriptInspectorUtility.h
  26. 2 0
      SBansheeEditor/SBansheeEditor.vcxproj
  27. 6 0
      SBansheeEditor/SBansheeEditor.vcxproj.filters
  28. 3 0
      SBansheeEditor/Source/BsEditorScriptManager.cpp
  29. 165 0
      SBansheeEditor/Source/BsScriptInspectorUtility.cpp
  30. 2 1
      TODO.txt

+ 7 - 2
BansheeMono/Include/BsMonoClass.h

@@ -98,14 +98,19 @@ namespace BansheeEngine
 		 *
 		 * @note	Be aware this will not include the fields of any base classes.
 		 */
-		const Vector<MonoField*> getAllFields() const;
+		const Vector<MonoField*>& getAllFields() const;
 
 		/**
 		 * @brief	Returns all methods belonging to this class.
 		 *
 		 * @note	Be aware this will not include the methods of any base classes.
 		 */
-		const Vector<MonoMethod*> getAllMethods() const;
+		const Vector<MonoMethod*>& getAllMethods() const;
+
+		/**
+		 * @brief	Gets all attributes applied to this class.
+		 */
+		Vector<MonoClass*> getAllAttributes() const;
 
 		/**
 		 * @brief	Check if this class has an attribute of the type "monoClass".

+ 26 - 3
BansheeMono/Source/BsMonoClass.cpp

@@ -158,7 +158,7 @@ namespace BansheeEngine
 		return *newProperty;
 	}
 
-	const Vector<MonoField*> MonoClass::getAllFields() const
+	const Vector<MonoField*>& MonoClass::getAllFields() const
 	{
 		if(mHasCachedFields)
 			return mCachedFieldList;
@@ -181,7 +181,7 @@ namespace BansheeEngine
 		return mCachedFieldList;
 	}
 
-	const Vector<MonoMethod*> MonoClass::getAllMethods() const
+	const Vector<MonoMethod*>& MonoClass::getAllMethods() const
 	{
 		if (mHasCachedMethods)
 			return mCachedMethodList;
@@ -207,6 +207,29 @@ namespace BansheeEngine
 		return mCachedMethodList;
 	}
 
+	Vector<MonoClass*> MonoClass::getAllAttributes() const
+	{
+		// TODO - Consider caching custom attributes or just initializing them all at load
+		Vector<MonoClass*> attributes;
+
+		MonoCustomAttrInfo* attrInfo = mono_custom_attrs_from_class(mClass);
+		if (attrInfo == nullptr)
+			return attributes;
+
+		for (INT32 i = 0; i < attrInfo->num_attrs; i++)
+		{
+			::MonoClass* attribClass = mono_method_get_class(attrInfo->attrs[i].ctor);
+			MonoClass* klass = MonoManager::instance().findClass(attribClass);
+
+			if (klass != nullptr)
+				attributes.push_back(klass);
+		}
+
+		mono_custom_attrs_free(attrInfo);
+
+		return attributes;
+	}
+
 	MonoObject* MonoClass::invokeMethod(const String& name, MonoObject* instance, void** params, UINT32 numParams)
 	{
 		return getMethod(name, numParams)->invoke(instance, params);
@@ -251,7 +274,7 @@ namespace BansheeEngine
 		MonoCustomAttrInfo* attrInfo = mono_custom_attrs_from_class(mClass);
 		if(attrInfo == nullptr)
 			return false;
-
+	
 		bool hasAttr = mono_custom_attrs_has_attr(attrInfo, monoClass->_getInternalClass()) != 0;
 
 		mono_custom_attrs_free(attrInfo);

+ 13 - 1
MBansheeEditor/Inspector/CustomInspector.cs

@@ -1,12 +1,24 @@
 using System;
+using BansheeEngine;
 
 namespace BansheeEditor
 {
-    [AttributeUsage(AttributeTargets.Class)]
+    /// <summary>
+    /// Specifies that a class or a struct uses a custom inspector for GUI display. This attribute can be placed on an 
+    /// implementation of <see cref="Inspector"/> in which case the type must reference a <see cref="Component"/> or a
+    /// <see cref="ManagedResource"/>. Or it can be placed on an implementation of <see cref="InspectableField"/> in which
+    /// case they type must reference any serializable class/struct, but may also reference an attribute in which case
+    /// any serializable type with that attribute applied with have a custom inspector.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
     public sealed class CustomInspector : Attribute
     {
         private Type type;
 
+        /// <summary>
+        /// Creates a new custom inspector attribute.
+        /// </summary>
+        /// <param name="type">Type for which the custom inspector should be displayed.</param>
         public CustomInspector(Type type)
         {
             this.type = type;

+ 2 - 5
MBansheeEditor/Inspector/GenericInspector.cs

@@ -10,7 +10,7 @@ namespace BansheeEditor
     internal sealed class GenericInspector : Inspector
     {
         private bool isInitialized;
-        private List<InspectableObjectBase> inspectableFields = new List<InspectableObjectBase>();
+        private List<InspectableField> inspectableFields = new List<InspectableField>();
 
         private void Initialize()
         {
@@ -23,10 +23,7 @@ namespace BansheeEditor
                     if (!field.Inspectable)
                         continue;
 
-                    if (field.HasCustomInspector)
-                        inspectableFields.Add(InspectableObjectBase.CreateCustomInspectable(field.CustomInspectorType, field.Name, new InspectableFieldLayout(layout), field.GetProperty()));
-                    else
-                        inspectableFields.Add(InspectableObjectBase.CreateDefaultInspectable(field.Name, new InspectableFieldLayout(layout), field.GetProperty()));
+                    inspectableFields.Add(InspectableField.CreateInspectable(field.Name, new InspectableFieldLayout(layout), field.GetProperty()));
                 }
             }
 

+ 4 - 4
MBansheeEditor/Inspector/InspectableArray.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableArray : InspectableObjectBase
+    public class InspectableArray : InspectableField
     {
         private class EntryRow
         {
@@ -22,7 +22,7 @@ namespace BansheeEditor
                 contentLayout = rowLayout.AddLayoutY();
             }
 
-            public void Refresh(InspectableObjectBase child, int seqIndex, InspectableArray parent)
+            public void Refresh(InspectableField child, int seqIndex, InspectableArray parent)
             {
                 if (ownsTitleLayout || (titleLayout != null && titleLayout == child.GetTitleLayout()))
                     return;
@@ -115,7 +115,7 @@ namespace BansheeEditor
 
             for (int i = 0; i < GetChildCount(); i++)
             {
-                InspectableObjectBase child = GetChild(i);
+                InspectableField child = GetChild(i);
                 bool childModified = child.Refresh(0);
 
                 if (childModified)
@@ -196,7 +196,7 @@ namespace BansheeEditor
                         EntryRow newRow = new EntryRow(guiContentLayout);
                         rows.Add(newRow);
 
-                        InspectableObjectBase childObj = CreateDefaultInspectable(i + ".", new InspectableFieldLayout(newRow.contentLayout), array.GetProperty(i));
+                        InspectableField childObj = CreateInspectable(i + ".", new InspectableFieldLayout(newRow.contentLayout), array.GetProperty(i));
                         AddChild(childObj);
 
                         childObj.Refresh(0);

+ 1 - 1
MBansheeEditor/Inspector/InspectableBool.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableBool : InspectableObjectBase
+    public class InspectableBool : InspectableField
     {
         private bool propertyValue;
         private GUIToggleField guiField;

+ 1 - 1
MBansheeEditor/Inspector/InspectableColor.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableColor : InspectableObjectBase
+    public class InspectableColor : InspectableField
     {
         private Color propertyValue;
         private GUIColorField guiField;

+ 16 - 18
MBansheeEditor/Inspector/InspectableObjectBase.cs → MBansheeEditor/Inspector/InspectableField.cs

@@ -7,23 +7,23 @@ using BansheeEngine;
  
 namespace BansheeEditor
 {
-    public class InspectableObjectBase
+    public class InspectableField
     {
-        private List<InspectableObjectBase> children = new List<InspectableObjectBase>();
-        private InspectableObjectBase parent;
+        private List<InspectableField> children = new List<InspectableField>();
+        private InspectableField parent;
         
         protected InspectableFieldLayout layout;
         protected SerializableProperty property;
         protected string title;
 
-        public InspectableObjectBase(string title, InspectableFieldLayout layout, SerializableProperty property)
+        public InspectableField(string title, InspectableFieldLayout layout, SerializableProperty property)
         {
             this.layout = layout;
             this.title = title;
             this.property = property;
         }
 
-        protected void AddChild(InspectableObjectBase child)
+        protected void AddChild(InspectableField child)
         {
             if (child.parent == this)
                 return;
@@ -35,7 +35,7 @@ namespace BansheeEditor
             child.parent = this;
         }
 
-        protected void RemoveChild(InspectableObjectBase child)
+        protected void RemoveChild(InspectableField child)
         {
             children.Remove(child);
             child.parent = null;
@@ -79,7 +79,7 @@ namespace BansheeEditor
         protected virtual void Update(int layoutIndex)
         {
             // Destroy all children as we expect update to rebuild them
-            InspectableObjectBase[] childrenCopy = children.ToArray();
+            InspectableField[] childrenCopy = children.ToArray();
             for (int i = 0; i < childrenCopy.Length; i++)
             {
                 childrenCopy[i].Destroy();
@@ -88,7 +88,7 @@ namespace BansheeEditor
             children.Clear();
         }
 
-        protected InspectableObjectBase GetChild(int index)
+        protected InspectableField GetChild(int index)
         {
             return children[index];
         }
@@ -102,7 +102,7 @@ namespace BansheeEditor
         {
             layout.DestroyElements();
 
-            InspectableObjectBase[] childrenCopy = children.ToArray();
+            InspectableField[] childrenCopy = children.ToArray();
             for (int i = 0; i < childrenCopy.Length; i++)
                 childrenCopy[i].Destroy();
 
@@ -112,8 +112,14 @@ namespace BansheeEditor
                 parent.RemoveChild(this);
         }
 
-        public static InspectableObjectBase CreateDefaultInspectable(string title, InspectableFieldLayout layout, SerializableProperty property)
+        public static InspectableField CreateInspectable(string title, InspectableFieldLayout layout, SerializableProperty property)
         {
+            Type customInspectable = InspectorUtility.GetCustomInspectable(property.InternalType);
+            if (customInspectable != null)
+            {
+                return (InspectableField)Activator.CreateInstance(customInspectable, title, property);
+            }
+
             switch (property.Type)
             {
                 case SerializableProperty.FieldType.Int:
@@ -146,13 +152,5 @@ namespace BansheeEditor
 
             throw new Exception("No inspector exists for the provided field type.");
         }
-
-        public static InspectableObjectBase CreateCustomInspectable(Type inspectableType, string title, InspectableFieldLayout layout, SerializableProperty property)
-        {
-            if (!inspectableType.IsSubclassOf(typeof (InspectableObjectBase)))
-                throw new Exception("Invalid inspector type.");
-
-            return (InspectableObjectBase)Activator.CreateInstance(inspectableType, title, property);
-        }
     }
 }

+ 1 - 1
MBansheeEditor/Inspector/InspectableFloat.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableFloat : InspectableObjectBase
+    public class InspectableFloat : InspectableField
     {
         private float propertyValue;
         private GUIFloatField guiFloatField;

+ 1 - 1
MBansheeEditor/Inspector/InspectableGameObjectRef.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableGameObjectRef : InspectableObjectBase
+    public class InspectableGameObjectRef : InspectableField
     {
         private GameObject propertyValue;
         private GUIGameObjectField guiField;

+ 1 - 1
MBansheeEditor/Inspector/InspectableInt.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableInt : InspectableObjectBase
+    public class InspectableInt : InspectableField
     {
         private int propertyValue;
         private GUIIntField guiIntField;

+ 4 - 4
MBansheeEditor/Inspector/InspectableList.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableList : InspectableObjectBase
+    public class InspectableList : InspectableField
     {
         private class EntryRow
         {
@@ -22,7 +22,7 @@ namespace BansheeEditor
                 contentLayout = rowLayout.AddLayoutY();
             }
 
-            public void Refresh(InspectableObjectBase child, int seqIndex, InspectableList parent)
+            public void Refresh(InspectableField child, int seqIndex, InspectableList parent)
             {
                 if (ownsTitleLayout || (titleLayout != null && titleLayout == child.GetTitleLayout()))
                     return;
@@ -115,7 +115,7 @@ namespace BansheeEditor
 
             for (int i = 0; i < GetChildCount(); i++)
             {
-                InspectableObjectBase child = GetChild(i);
+                InspectableField child = GetChild(i);
                 bool childModified = child.Refresh(0);
 
                 if (childModified)
@@ -196,7 +196,7 @@ namespace BansheeEditor
                         EntryRow newRow = new EntryRow(guiContentLayout);
                         rows.Add(newRow);
 
-                        InspectableObjectBase childObj = CreateDefaultInspectable(i + ".", new InspectableFieldLayout(newRow.contentLayout), list.GetProperty(i));
+                        InspectableField childObj = CreateInspectable(i + ".", new InspectableFieldLayout(newRow.contentLayout), list.GetProperty(i));
                         AddChild(childObj);
 
                         childObj.Refresh(0);

+ 2 - 5
MBansheeEditor/Inspector/InspectableObject.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableObject : InspectableObjectBase
+    public class InspectableObject : InspectableField
     {
         private const int IndentAmount = 15;
 
@@ -99,10 +99,7 @@ namespace BansheeEditor
                         if (!field.Inspectable)
                             continue;
 
-                        if (field.HasCustomInspector)
-                            AddChild(CreateCustomInspectable(field.CustomInspectorType, field.Name, new InspectableFieldLayout(guiContentLayout), field.GetProperty()));
-                        else
-                            AddChild(CreateDefaultInspectable(field.Name, new InspectableFieldLayout(guiContentLayout), field.GetProperty()));
+                        AddChild(CreateInspectable(field.Name, new InspectableFieldLayout(guiContentLayout), field.GetProperty()));
                     }
                 }
                 else

+ 1 - 1
MBansheeEditor/Inspector/InspectableResourceRef.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableResourceRef : InspectableObjectBase
+    public class InspectableResourceRef : InspectableField
     {
         private Resource propertyValue;
         private GUIResourceField guiField;

+ 1 - 1
MBansheeEditor/Inspector/InspectableString.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableString : InspectableObjectBase
+    public class InspectableString : InspectableField
     {
         private string propertyValue;
         private GUITextField guiField;

+ 1 - 1
MBansheeEditor/Inspector/InspectableVector2.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableVector2 : InspectableObjectBase
+    public class InspectableVector2 : InspectableField
     {
         private Vector2 propertyValue;
         private GUIVector2Field guiField;

+ 1 - 1
MBansheeEditor/Inspector/InspectableVector3.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableVector3 : InspectableObjectBase
+    public class InspectableVector3 : InspectableField
     {
         private Vector3 propertyValue;
         private GUIVector3Field guiField;

+ 1 - 1
MBansheeEditor/Inspector/InspectableVector4.cs

@@ -7,7 +7,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class InspectableVector4 : InspectableObjectBase
+    public class InspectableVector4 : InspectableField
     {
         private Vector4 propertyValue;
         private GUIVector4Field guiField;

+ 37 - 0
MBansheeEditor/Inspector/InspectorUtility.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    public class InspectorUtility
+    {
+        public static Inspector GetInspector(Type type)
+        {
+            Inspector customInspector = Internal_GetCustomInspector(type);
+            if (customInspector != null)
+                return customInspector;
+
+            return new GenericInspector();
+        }
+
+        public static Type GetCustomInspectable(Type type)
+        {
+            Type customInspectable = Internal_GetCustomInspectable(type);
+            if (customInspectable != null)
+                return customInspectable;
+
+            return null;
+        }
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern Inspector Internal_GetCustomInspector(Type type);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern Type Internal_GetCustomInspectable(Type type);
+    }
+}

+ 3 - 11
MBansheeEditor/Inspector/InspectorWindow.cs

@@ -88,7 +88,7 @@ namespace BansheeEditor
             inspectorResource = new InspectorResource();
             inspectorResource.panel = inspectorLayout.AddPanel();
 
-            inspectorResource.inspector = GetInspector(activeResource.GetType());
+            inspectorResource.inspector = InspectorUtility.GetInspector(activeResource.GetType());
             inspectorResource.inspector.Initialize(this, inspectorResource.panel, activeResource);
             inspectorResource.inspector.Refresh();
 
@@ -127,7 +127,7 @@ namespace BansheeEditor
                 inspectorLayout.AddElement(data.foldout);
                 data.panel = inspectorLayout.AddPanel();
                 
-                data.inspector = GetInspector(allComponents[i].GetType());
+                data.inspector = InspectorUtility.GetInspector(allComponents[i].GetType());
                 data.inspector.Initialize(this, data.panel, allComponents[i]);
 
                 data.foldout.SetExpanded(true);
@@ -255,7 +255,7 @@ namespace BansheeEditor
             bool hasPrefab = PrefabUtility.HasPrefabLink(activeSO);
             if (soHasPrefab != hasPrefab || forceUpdate)
             {
-                int numChildren = soPrefabLayout.GetNumChildren();
+                int numChildren = soPrefabLayout.ChildCount;
                 for (int i = 0; i < numChildren; i++)
                     soPrefabLayout.GetChild(0).Destroy();
 
@@ -604,13 +604,5 @@ namespace BansheeEditor
             if(titleBg != null)
                 titleBg.Bounds = GetTitleBounds();
         }
-
-        private Inspector GetInspector(Type type)
-        {
-            // TODO - Check if type has a custom inspector
-            // and return the custom inspector, otherwise create a generic one
-
-            return new GenericInspector();
-        }
     }
 }

+ 2 - 1
MBansheeEditor/MBansheeEditor.csproj

@@ -59,6 +59,7 @@
     <Compile Include="GUI\GUISceneTreeView.cs" />
     <Compile Include="GUI\TextureField.cs" />
     <Compile Include="HierarchyWindow.cs" />
+    <Compile Include="Inspector\InspectorUtility.cs" />
     <Compile Include="LocEdString.cs" />
     <Compile Include="PrefabUtility.cs" />
     <Compile Include="LibraryDropTarget.cs" />
@@ -92,7 +93,7 @@
     <Compile Include="Inspector\InspectableGameObjectRef.cs" />
     <Compile Include="Inspector\InspectableList.cs" />
     <Compile Include="Inspector\InspectableObject.cs" />
-    <Compile Include="Inspector\InspectableObjectBase.cs" />
+    <Compile Include="Inspector\InspectableField.cs" />
     <Compile Include="Inspector\InspectableInt.cs" />
     <Compile Include="Inspector\InspectableResourceRef.cs" />
     <Compile Include="Inspector\InspectableString.cs" />

+ 1 - 1
MBansheeEditor/ProjectWindow.cs

@@ -184,7 +184,7 @@ namespace BansheeEditor
         private void RefreshRecentProjects()
         {
             GUILayout scrollLayout = recentProjectsArea.Layout;
-            while(scrollLayout.GetNumChildren() > 0)
+            while(scrollLayout.ChildCount > 0)
                 scrollLayout.GetChild(0).Destroy();
 
             RecentProject[] recentProjects = EditorSettings.RecentProjects;

+ 9 - 10
MBansheeEngine/GUI/GUILayout.cs

@@ -11,6 +11,14 @@ namespace BansheeEngine
     /// </summary>
     public abstract class GUILayout : GUIElement
     {
+        /// <summary>
+        /// Returns number of child elements in the layout.
+        /// </summary>
+        public int ChildCount
+        {
+            get { return Internal_GetChildCount(mCachedPtr); }
+        }
+
         /// <summary>
         /// Adds a new element to the layout after all existing elements.
         /// </summary>
@@ -28,22 +36,13 @@ namespace BansheeEngine
         /// <param name="element">GUI element to insert.</param>
         public void InsertElement(int index, GUIElement element)
         {
-            if(index < 0 || index > GetNumChildren())
+            if(index < 0 || index > ChildCount)
                 throw new ArgumentOutOfRangeException("index", index, "Index out of range.");
 
             if (element != null)
                 Internal_InsertElement(mCachedPtr, index, element.mCachedPtr);
         }
 
-        /// <summary>
-        /// Returns number of child elements in the layout.
-        /// </summary>
-        /// <returns>Number of child elements in the layout.</returns>
-        public int GetNumChildren()
-        {
-            return Internal_GetChildCount(mCachedPtr);
-        }
-
         /// <summary>
         /// Gets a child elements at the specified index in the layout.
         /// </summary>

+ 0 - 17
MBansheeEngine/SerializableField.cs

@@ -41,23 +41,6 @@ namespace BansheeEngine
             get { return type; }
         }
 
-        /// <summary>
-        /// Returns true if the field has custom inspector GUI.
-        /// </summary>
-        public bool HasCustomInspector
-        {
-            get { return false; } // TODO - Add [UseCustomInspector(typeof(InspecableType))] attribute and parse it
-        }
-
-        /// <summary>
-        /// Returns a type deriving from Inspector, that is used for displaying the custom inspector for this element.
-        /// Only relevant if <see cref="HasCustomInspector"/> is true.
-        /// </summary>
-        public Type CustomInspectorType
-        {
-            get { return null; } // TODO - See above. Return type from UseCustomInspector attribute
-        }
-
         /// <summary>
         /// Returns the name of the field.
         /// </summary>

+ 52 - 0
SBansheeEditor/Include/BsScriptInspectorUtility.h

@@ -0,0 +1,52 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsScriptObject.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Interop class between C++ & CLR that deals with custom inspectors. Custom inspectors
+	 *			allow the developer to control exactly how certain types are displayed in the inspector
+	 *			window in the editor.
+	 */
+	class BS_SCR_BED_EXPORT ScriptInspectorUtility : public ScriptObject<ScriptInspectorUtility>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "InspectorUtility");
+
+		/**
+		 * @brief	Hooks up domain reload callback. Must be called on library load.
+		 */
+		static void startUp();
+
+		/**
+		 * @brief	Destroys domain reload callback. Must be called before library is unloaded.
+		 */
+		static void shutDown();
+
+
+	private:
+		ScriptInspectorUtility(MonoObject* instance);
+
+		/**
+		 * @brief	Reloads all assembly types and attempts to find uses of CustomInspector attribute. Old
+		 *			data cleared and replaced with new.
+		 */
+		static void reloadAssemblyData();
+
+		static MonoClass* mCustomInspectorAtribute;
+		static MonoField* mTypeField;
+
+		static UnorderedMap<MonoClass*, MonoClass*> mInspectorTypes;
+		static UnorderedMap<MonoClass*, MonoClass*> mInspectableFieldTypes;
+
+		static HEvent mDomainLoadedConn;
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static MonoObject* internal_GetCustomInspector(MonoReflectionType* reflType);
+		static MonoReflectionType* internal_GetCustomInspectable(MonoReflectionType* reflType);
+	};
+}

+ 2 - 0
SBansheeEditor/SBansheeEditor.vcxproj

@@ -248,6 +248,7 @@
     <ClInclude Include="Include\BsScriptFolderMonitor.h" />
     <ClInclude Include="Include\BsScriptGUISceneTreeView.h" />
     <ClInclude Include="Include\BsScriptGUITextureField.h" />
+    <ClInclude Include="Include\BsScriptInspectorUtility.h" />
     <ClInclude Include="Include\BsScriptOSDropTarget.h" />
     <ClInclude Include="Include\BsScriptEditorApplication.h" />
     <ClInclude Include="Include\BsScriptEditorBuiltin.h" />
@@ -304,6 +305,7 @@
     <ClCompile Include="Source\BsScriptFolderMonitor.cpp" />
     <ClCompile Include="Source\BsScriptGUISceneTreeView.cpp" />
     <ClCompile Include="Source\BsScriptGUITextureField.cpp" />
+    <ClCompile Include="Source\BsScriptInspectorUtility.cpp" />
     <ClCompile Include="Source\BsScriptOSDropTarget.cpp" />
     <ClCompile Include="Source\BsScriptEditorApplication.cpp" />
     <ClCompile Include="Source\BsScriptEditorBuiltin.cpp" />

+ 6 - 0
SBansheeEditor/SBansheeEditor.vcxproj.filters

@@ -177,6 +177,9 @@
     <ClInclude Include="Include\BsEditorScriptLibrary.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsScriptInspectorUtility.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
@@ -341,5 +344,8 @@
     <ClCompile Include="Source\BsEditorScriptLibrary.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsScriptInspectorUtility.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 3 - 0
SBansheeEditor/Source/BsEditorScriptManager.cpp

@@ -21,6 +21,7 @@
 #include "BsEditorResourceLoader.h"
 #include "BsScriptManager.h"
 #include "BsScriptEditorApplication.h"
+#include "BsScriptInspectorUtility.h"
 
 namespace BansheeEngine
 {
@@ -44,6 +45,7 @@ namespace BansheeEngine
 		MenuItemManager::startUp(ScriptAssemblyManager::instance());
 		ScriptFolderMonitorManager::startUp();
 		ScriptSelection::startUp();
+		ScriptInspectorUtility::startUp();
 
 		mOnDomainLoadConn = ScriptObjectManager::instance().onRefreshDomainLoaded.connect(std::bind(&EditorScriptManager::loadMonoTypes, this));
 		mOnAssemblyRefreshDoneConn = ScriptObjectManager::instance().onRefreshComplete.connect(std::bind(&EditorScriptManager::onAssemblyRefreshDone, this));
@@ -70,6 +72,7 @@ namespace BansheeEngine
 		mOnDomainLoadConn.disconnect();
 		mOnAssemblyRefreshDoneConn.disconnect();
 
+		ScriptInspectorUtility::shutDown();
 		ScriptSelection::shutDown();
 		ScriptFolderMonitorManager::shutDown();
 		MenuItemManager::shutDown();

+ 165 - 0
SBansheeEditor/Source/BsScriptInspectorUtility.cpp

@@ -0,0 +1,165 @@
+#include "BsScriptInspectorUtility.h"
+#include "BsScriptMeta.h"
+#include "BsMonoClass.h"
+#include "BsMonoMethod.h"
+#include "BsMonoAssembly.h"
+#include "BsMonoUtil.h"
+#include "BsScriptAssemblyManager.h"
+#include "BsScriptObjectManager.h"
+#include "BsScriptManagedResource.h"
+#include "BsScriptComponent.h"
+
+namespace BansheeEngine
+{
+	MonoClass* ScriptInspectorUtility::mCustomInspectorAtribute = nullptr;
+	MonoField* ScriptInspectorUtility::mTypeField = nullptr;
+
+	UnorderedMap<MonoClass*, MonoClass*> ScriptInspectorUtility::mInspectorTypes;
+	UnorderedMap<MonoClass*, MonoClass*> ScriptInspectorUtility::mInspectableFieldTypes;
+
+	HEvent ScriptInspectorUtility::mDomainLoadedConn;
+
+	ScriptInspectorUtility::ScriptInspectorUtility(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptInspectorUtility::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_GetCustomInspector", &ScriptInspectorUtility::internal_GetCustomInspector);
+		metaData.scriptClass->addInternalCall("Internal_GetCustomInspectable", &ScriptInspectorUtility::internal_GetCustomInspectable);
+	}
+
+	void ScriptInspectorUtility::startUp()
+	{
+		mDomainLoadedConn = ScriptObjectManager::instance().onRefreshDomainLoaded.connect(&ScriptInspectorUtility::reloadAssemblyData);
+		reloadAssemblyData();
+	}
+
+	void ScriptInspectorUtility::shutDown()
+	{
+		mDomainLoadedConn.disconnect();
+	}
+
+	void ScriptInspectorUtility::reloadAssemblyData()
+	{
+		mInspectorTypes.clear();
+		mInspectableFieldTypes.clear();
+
+		// Reload MenuItem attribute from editor assembly
+		MonoAssembly* editorAssembly = MonoManager::instance().getAssembly(EDITOR_ASSEMBLY);
+		mCustomInspectorAtribute = editorAssembly->getClass("BansheeEditor", "CustomInspector");
+		if (mCustomInspectorAtribute == nullptr)
+			BS_EXCEPT(InvalidStateException, "Cannot find CustomInspector managed class.");
+
+		MonoClass* inspectorClass = editorAssembly->getClass("BansheeEditor", "Inspector");
+		if (inspectorClass == nullptr)
+			BS_EXCEPT(InvalidStateException, "Cannot find Inspector managed class.");
+
+		MonoClass* inspectableFieldClass = editorAssembly->getClass("BansheeEditor", "InspectableField");
+		if (inspectableFieldClass == nullptr)
+			BS_EXCEPT(InvalidStateException, "Cannot find InspectableField managed class.");
+
+		mTypeField = mCustomInspectorAtribute->getField("type");
+
+		ScriptAssemblyManager& sam = ScriptAssemblyManager::instance();
+
+		Vector<String> scriptAssemblyNames = sam.getScriptAssemblies();
+		for (auto& assemblyName : scriptAssemblyNames)
+		{
+			MonoAssembly* assembly = MonoManager::instance().getAssembly(assemblyName);
+
+			// Find new classes/structs with the custom inspector attribute
+			const Vector<MonoClass*>& allClasses = assembly->getAllClasses();
+			for (auto curClass : allClasses)
+			{
+				MonoObject* attrib = curClass->getAttribute(mCustomInspectorAtribute);
+				if (attrib == nullptr)
+					continue;
+
+				// Check if the attribute references a valid class
+				MonoReflectionType* referencedReflType = nullptr;
+				mTypeField->getValue(attrib, &referencedReflType);
+
+				MonoType* referencedType = mono_reflection_type_get_type(referencedReflType);
+				::MonoClass* referencedMonoClass = mono_class_from_mono_type(referencedType);
+
+				MonoClass* referencedClass = MonoManager::instance().findClass(referencedMonoClass);
+				if (referencedClass == nullptr)
+					continue;
+
+				if (curClass->isSubClassOf(inspectorClass))
+				{
+					bool isValidInspectorType = referencedClass->isSubClassOf(ScriptManagedResource::getMetaData()->scriptClass) ||
+						referencedClass->isSubClassOf(ScriptComponent::getMetaData()->scriptClass);
+
+					if (!isValidInspectorType)
+						continue;
+
+					mInspectorTypes[referencedClass] = curClass;
+				}
+				else if (curClass->isSubClassOf(inspectableFieldClass))
+				{
+					mInspectorTypes[referencedClass] = curClass;
+				}
+			}
+		}
+	}
+
+	MonoObject* ScriptInspectorUtility::internal_GetCustomInspector(MonoReflectionType* reflType)
+	{
+		if (reflType == nullptr)
+			return nullptr;
+
+		MonoType* type = mono_reflection_type_get_type(reflType);
+		::MonoClass* monoClass = mono_class_from_mono_type(type);
+
+		MonoClass* klass = MonoManager::instance().findClass(monoClass);
+		if (klass == nullptr)
+			return nullptr;
+
+		auto iterFind = mInspectorTypes.find(klass);
+		if (iterFind != mInspectorTypes.end())
+			return iterFind->second->createInstance();
+
+		return nullptr;
+	}
+
+	MonoReflectionType* ScriptInspectorUtility::internal_GetCustomInspectable(MonoReflectionType* reflType)
+	{
+		if (reflType == nullptr)
+			return nullptr;
+
+		MonoType* type = mono_reflection_type_get_type(reflType);
+		::MonoClass* monoClass = mono_class_from_mono_type(type);
+
+		MonoClass* klass = MonoManager::instance().findClass(monoClass);
+		if (klass == nullptr)
+			return nullptr;
+
+		// Try to find an inspectable field implementation directly referencing the class
+		auto iterFind = mInspectableFieldTypes.find(klass);
+		if (iterFind != mInspectableFieldTypes.end())
+		{
+			::MonoClass* outMonoClass = iterFind->second->_getInternalClass();
+			MonoType* outMonoType = mono_class_get_type(outMonoClass);
+
+			return mono_type_get_object(MonoManager::instance().getDomain(), outMonoType);
+		}
+
+		// Try to find an inspectable field implementation referencing any of the class' attributes
+		Vector<MonoClass*> attribs = klass->getAllAttributes();
+		for (auto attrib : attribs)
+		{
+			auto iterFind = mInspectableFieldTypes.find(attrib);
+			if (iterFind != mInspectableFieldTypes.end())
+			{
+				::MonoClass* outMonoClass = iterFind->second->_getInternalClass();
+				MonoType* outMonoType = mono_class_get_type(outMonoClass);
+
+				return mono_type_get_object(MonoManager::instance().getDomain(), outMonoType);
+			}
+		}
+
+		return nullptr;
+	}
+}

+ 2 - 1
TODO.txt

@@ -100,7 +100,8 @@ Optional:
   - There should be a CmdRecordSO equivalent for resources (probably)
   - Add commands for breaking or reverting a scene object 
   - Test & finalize undo/redo system
- - Update GUISlider so it works with the new style (and to have min/max limits, plus step size)
+ - Update GUISlider so has min/max limits, plus step size
+  - Add Range[] attribute to C# that forces a float/int to be displayed as a slider
  - Add "focus on object" key (F) - animate it: rotate camera towards then speed towards while zooming in
  - Ortographic camera views (+ gizmo in scene view corner that shows camera orientation)
  - Drag to select in scene view