From 846f7f30b179a89dc278ddab48e9abf8f21e1936 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 21 Sep 2017 13:22:34 +0200 Subject: [PATCH] Exposed fields and custom editors --- Examples/Nodes/BaseNode.cs | 7 +- Examples/Nodes/Editor.meta | 9 ++ Examples/Nodes/Editor/MathNodeEditor.cs | 12 +++ Examples/Nodes/Editor/MathNodeEditor.cs.meta | 12 +++ Examples/Nodes/{AddNode.cs => MathNode.cs} | 4 +- .../{AddNode.cs.meta => MathNode.cs.meta} | 0 Scripts/Editor/NodeEditor.cs | 99 ++++++++++++++++++- Scripts/Editor/NodeEditorGUI.cs | 4 +- Scripts/Editor/NodeEditorReflection.cs | 42 ++++++-- Scripts/Editor/NodeEditorToolbar.cs | 9 +- Scripts/Editor/NodeEditorWindow.cs | 7 -- Scripts/NodePort.cs | 4 +- 12 files changed, 187 insertions(+), 22 deletions(-) create mode 100644 Examples/Nodes/Editor.meta create mode 100644 Examples/Nodes/Editor/MathNodeEditor.cs create mode 100644 Examples/Nodes/Editor/MathNodeEditor.cs.meta rename Examples/Nodes/{AddNode.cs => MathNode.cs} (70%) rename Examples/Nodes/{AddNode.cs.meta => MathNode.cs.meta} (100%) diff --git a/Examples/Nodes/BaseNode.cs b/Examples/Nodes/BaseNode.cs index 6ee4621..bd34ef1 100644 --- a/Examples/Nodes/BaseNode.cs +++ b/Examples/Nodes/BaseNode.cs @@ -4,7 +4,12 @@ public class BaseNode : Node { public bool concat; - + [TextArea] + public string SomeString; + [Header("New stuff")] + public Color col; + public AnimationCurve anim; + public Vector3 vec; protected override void Init() { inputs = new NodePort[2]; inputs[0] = CreateNodeInput("IntInput", typeof(int)); diff --git a/Examples/Nodes/Editor.meta b/Examples/Nodes/Editor.meta new file mode 100644 index 0000000..40beab9 --- /dev/null +++ b/Examples/Nodes/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 0d2300267781fed46a6d964565309cbf +folderAsset: yes +timeCreated: 1505987971 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/Nodes/Editor/MathNodeEditor.cs b/Examples/Nodes/Editor/MathNodeEditor.cs new file mode 100644 index 0000000..6dbf0f2 --- /dev/null +++ b/Examples/Nodes/Editor/MathNodeEditor.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +[CustomNodeEditor(typeof(MathNode))] +public class AddNodeEditor : NodeEditor { + + public override void OnNodeGUI() { + GUILayout.Label("YEAH CUSTOM"); + base.OnNodeGUI(); + } +} diff --git a/Examples/Nodes/Editor/MathNodeEditor.cs.meta b/Examples/Nodes/Editor/MathNodeEditor.cs.meta new file mode 100644 index 0000000..57c1490 --- /dev/null +++ b/Examples/Nodes/Editor/MathNodeEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c4cc83d08877562419d86874dd3587e2 +timeCreated: 1505987983 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Examples/Nodes/AddNode.cs b/Examples/Nodes/MathNode.cs similarity index 70% rename from Examples/Nodes/AddNode.cs rename to Examples/Nodes/MathNode.cs index fc6de57..adf593c 100644 --- a/Examples/Nodes/AddNode.cs +++ b/Examples/Nodes/MathNode.cs @@ -1,9 +1,11 @@ using UnityEngine; [System.Serializable] -public class AddNode : Node { +public class MathNode : Node { public int someValue; + public enum MathType { Add, Subtract, Multiply, Divide} + public MathType mathType = MathType.Add; protected override void Init() { inputs = new NodePort[2]; diff --git a/Examples/Nodes/AddNode.cs.meta b/Examples/Nodes/MathNode.cs.meta similarity index 100% rename from Examples/Nodes/AddNode.cs.meta rename to Examples/Nodes/MathNode.cs.meta diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 22a73f5..8ef0317 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -2,11 +2,102 @@ using System.Collections.Generic; using UnityEngine; using UnityEditor; +using System.Reflection; +using System.Linq; +using System; /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. -public class NodeEditor : Editor { - - public override void OnInspectorGUI() { - base.OnInspectorGUI(); +[CustomNodeEditor(typeof(Node))] +public class NodeEditor { + + public Node target; + + public virtual void OnNodeGUI() { + DrawDefaultNodeGUI(); + } + + public void DrawDefaultNodeGUI() { + FieldInfo[] fields = GetInspectorFields(target); + for (int i = 0; i < fields.Length; i++) { + Type fieldType = fields[i].FieldType; + string fieldName = fields[i].Name; + object fieldValue = fields[i].GetValue(target); + object[] fieldAttribs = fields[i].GetCustomAttributes(false); + + HeaderAttribute headerAttrib; + if (GetAttrib(fieldAttribs, out headerAttrib)) { + EditorGUILayout.LabelField(headerAttrib.header); + } + + EditorGUI.BeginChangeCheck(); + if (fieldType == typeof(int)) { + fieldValue = EditorGUILayout.IntField(fieldName, (int)fieldValue); + } + else if (fieldType == typeof(bool)) { + fieldValue = EditorGUILayout.Toggle(fieldName, (bool)fieldValue); + } + else if (fieldType.IsEnum) { + fieldValue = EditorGUILayout.EnumPopup(fieldName, (Enum)fieldValue); + } + else if (fieldType == typeof(string)) { + TextAreaAttribute textAreaAttrib; + if (GetAttrib(fieldAttribs, out textAreaAttrib)) { + fieldValue = EditorGUILayout.TextArea(fieldValue != null ? (string)fieldValue : ""); + } + else + fieldValue = EditorGUILayout.TextField(fieldName, fieldValue != null ? (string)fieldValue : ""); + } + else if (fieldType == typeof(Rect)) { + if (fieldName == "position") continue; + fieldValue = EditorGUILayout.RectField(fieldName, (Rect)fieldValue); + } + else if (fieldType == typeof(float)) { + fieldValue = EditorGUILayout.FloatField(fieldName, (float)fieldValue); + } + else if (fieldType == typeof(Vector2)) { + fieldValue = EditorGUILayout.Vector2Field(fieldName, (Vector2)fieldValue); + } + else if (fieldType == typeof(Vector3)) { + fieldValue = EditorGUILayout.Vector2Field(fieldName, (Vector3)fieldValue); + } + else if (fieldType == typeof(Vector4)) { + fieldValue = EditorGUILayout.Vector2Field(fieldName, (Vector4)fieldValue); + } + else if (fieldType == typeof(Color)) { + fieldValue = EditorGUILayout.ColorField(fieldName, (Color)fieldValue); + } + else if (fieldType == typeof(AnimationCurve)) { + fieldValue = EditorGUILayout.CurveField(fieldName, fieldValue != null ? (AnimationCurve)fieldValue : new AnimationCurve()); + } + else if (fieldType.IsSubclassOf(typeof(UnityEngine.Object)) || fieldType == typeof(UnityEngine.Object)) { + fieldValue = EditorGUILayout.ObjectField(fieldName, (UnityEngine.Object)fieldValue, fieldType, true); + } + + if (EditorGUI.EndChangeCheck()) { + fields[i].SetValue(target, fieldValue); + } + } + } + private static FieldInfo[] GetInspectorFields(Node node) { + return node.GetType().GetFields().Where(f => f.IsPublic).ToArray(); + } + + private static bool GetAttrib(object[] attribs, out T attribOut) where T : Attribute { + for (int i = 0; i < attribs.Length; i++) { + if (attribs[i].GetType() == typeof(T)) { + attribOut = attribs[i] as T; + return true; + } + } + attribOut = null; + return false; + } +} + +[AttributeUsage(AttributeTargets.Class)] +public class CustomNodeEditorAttribute : Attribute { + public Type inspectedType; + public CustomNodeEditorAttribute(Type inspectedType) { + this.inspectedType = inspectedType; } } diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 70db270..e036aa9 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -168,7 +168,9 @@ public partial class NodeEditorWindow { GUILayout.EndHorizontal(); - // GUI + NodeEditor nodeEditor = GetNodeEditor(node.GetType()); + nodeEditor.target = node; + nodeEditor.OnNodeGUI(); GUILayout.EndArea(); diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 1334bba..ce7e7e8 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -1,20 +1,50 @@ -using System.Reflection; +using System.Collections.Generic; +using System.Reflection; using System.Linq; using System; +using UnityEngine; /// Contains reflection-related info public partial class NodeEditorWindow { + private static Dictionary customNodeEditor; public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } } private static Type[] _nodeTypes; + public static NodeEditor GetNodeEditor(Type node) { + if (customNodeEditor == null) CacheCustomNodeEditors(); + if (customNodeEditor.ContainsKey(node)) return customNodeEditor[node]; + return customNodeEditor[typeof(Node)]; + } + public static Type[] GetNodeTypes() { //Get all classes deriving from Node via reflection - Type derivedType = typeof(Node); - Assembly assembly = Assembly.GetAssembly(derivedType); - return assembly.GetTypes().Where(t => - t != derivedType && - derivedType.IsAssignableFrom(t) + return GetDerivedTypes(typeof(Node)); + } + + public static void CacheCustomNodeEditors(){ + customNodeEditor = new Dictionary(); + customNodeEditor.Add(typeof(Node), new NodeEditor()); + //Get all classes deriving from NodeEditor via reflection + Type[] nodeEditors = GetDerivedTypes(typeof(NodeEditor)); + for (int i = 0; i < nodeEditors.Length; i++) { + var attribs = nodeEditors[i].GetCustomAttributes(typeof(CustomNodeEditorAttribute), false); + if (attribs == null || attribs.Length == 0) continue; + CustomNodeEditorAttribute attrib = attribs[0] as CustomNodeEditorAttribute; + customNodeEditor.Add(attrib.inspectedType, Activator.CreateInstance(nodeEditors[i]) as NodeEditor); + } + } + + public static Type[] GetDerivedTypes(Type baseType) { + //Get all classes deriving from baseType via reflection + Assembly assembly = Assembly.GetAssembly(baseType); + return assembly.GetTypes().Where(t => + t != baseType && + baseType.IsAssignableFrom(t) ).ToArray(); } + + public static object ObjectFromType(Type type) { + return Activator.CreateInstance(type); + } } diff --git a/Scripts/Editor/NodeEditorToolbar.cs b/Scripts/Editor/NodeEditorToolbar.cs index 2ea2857..19bc890 100644 --- a/Scripts/Editor/NodeEditorToolbar.cs +++ b/Scripts/Editor/NodeEditorToolbar.cs @@ -12,7 +12,7 @@ public partial class NodeEditorWindow { if (DropdownButton("Edit", 50)) EditContextMenu(); if (DropdownButton("View", 50)) { } if (DropdownButton("Settings", 70)) { } - if (DropdownButton("Tools", 50)) { } + if (DropdownButton("Tools", 50)) ToolsContextMenu(); if (IsHoveringNode) { GUILayout.Space(20); string hoverInfo = hoveredNode.GetType().ToString(); @@ -44,4 +44,11 @@ public partial class NodeEditorWindow { contextMenu.DropDown(new Rect(5f, 17f, 0f, 0f)); } + + public void ToolsContextMenu() { + GenericMenu contextMenu = new GenericMenu(); + contextMenu.AddItem(new GUIContent("Debug Custom Node Editors"), false, () => CacheCustomNodeEditors()); + + contextMenu.DropDown(new Rect(5f, 17f, 0f, 0f)); + } } diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 13eaaa2..9e09791 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -43,13 +43,6 @@ public partial class NodeEditorWindow : EditorWindow { GUI.DragWindow(); } - /*public byte[] ProtoSerialize(T value) { - using (var ms = new MemoryStream()) { - ProtoBuf.Serializer.Serialize(ms, value); - return ms.ToArray(); - } - }*/ - public Vector2 WindowToGridPosition(Vector2 windowPosition) { return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom; } diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 0ac5359..2cea5de 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -41,8 +41,10 @@ public class NodePort :ISerializationCallbackReceiver{ } public void Connect(NodePort port) { + if (connections == null) connections = new List(); + if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } if (port == this) { Debug.LogWarning("Attempting to connect port to self."); return; } - if (connections.Contains(port)) { Debug.LogWarning("Port already connected."); return; } + if (connections.Contains(port)) { Debug.LogWarning("Port already connected. "); return; } if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } connections.Add(port); port.connections.Add(this);