diff --git a/README.md b/README.md index 0207ad3..3eadd3e 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo * [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph * [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects +### Installation +
Instructions + ### Installing with Unity Package Manager ***Via Git URL*** *(Requires Unity version 2018.3.0b7 or above)* @@ -56,6 +59,21 @@ The package is available on the [openupm registry](https://openupm.com). It's re openupm add com.github.siccity.xnode ``` +### Installing with git +***Via Git Submodule*** + +To add xNode as a [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) in your existing git project, +run the following git command from your project root: + +``` +git submodule add git@github.com:Siccity/xNode.git Assets/Submodules/xNode +``` + +### Installing 'the old way' +If no source control or package manager is available to you, you can simply copy/paste the source files into your assets folder. + +
+ ### Node example: ```csharp // public classes deriving from Node are registered as nodes for use within a graph @@ -92,5 +110,10 @@ public class MathNode : Node { } ``` +### Plugins +Plugins are repositories that add functionality to xNode +* [xNodeGroups](https://github.com/Siccity/xNodeGroups): adds resizable groups + +### Community Join the [Discord](https://discord.gg/qgPrHv4 "Join Discord server") server to leave feedback or get support. Feel free to also leave suggestions/requests in the [issues](https://github.com/Siccity/xNode/issues "Go to Issues") page. diff --git a/Scripts/Editor/AdvancedGenericMenu.cs b/Scripts/Editor/AdvancedGenericMenu.cs new file mode 100644 index 0000000..a22fa83 --- /dev/null +++ b/Scripts/Editor/AdvancedGenericMenu.cs @@ -0,0 +1,213 @@ +#if UNITY_2019_1_OR_NEWER +using System.Collections.Generic; +using System.Linq; +using UnityEditor.IMGUI.Controls; +using UnityEngine; +using static UnityEditor.GenericMenu; + +namespace XNodeEditor +{ + public class AdvancedGenericMenu : AdvancedDropdown + { + public static float? DefaultMinWidth = 200f; + public static float? DefaultMaxWidth = 300f; + + private class AdvancedGenericMenuItem : AdvancedDropdownItem + { + private MenuFunction func; + + private MenuFunction2 func2; + private object userData; + + public AdvancedGenericMenuItem( string name ) : base( name ) + { + } + + public AdvancedGenericMenuItem( string name, bool enabled, Texture2D icon, MenuFunction func ) : base( name ) + { + Set( enabled, icon, func ); + } + + public AdvancedGenericMenuItem( string name, bool enabled, Texture2D icon, MenuFunction2 func, object userData ) : base( name ) + { + Set( enabled, icon, func, userData ); + } + + public void Set( bool enabled, Texture2D icon, MenuFunction func ) + { + this.enabled = enabled; + this.icon = icon; + this.func = func; + } + + public void Set( bool enabled, Texture2D icon, MenuFunction2 func, object userData ) + { + this.enabled = enabled; + this.icon = icon; + this.func2 = func; + this.userData = userData; + } + + public void Run() + { + if ( func2 != null ) + func2( userData ); + else if ( func != null ) + func(); + } + } + + private List items = new List(); + + private AdvancedGenericMenuItem FindOrCreateItem( string name, AdvancedGenericMenuItem currentRoot = null ) + { + if ( string.IsNullOrWhiteSpace( name ) ) + return null; + + AdvancedGenericMenuItem item = null; + + string[] paths = name.Split( '/' ); + if ( currentRoot == null ) + { + item = items.FirstOrDefault( x => x != null && x.name == paths[0] ); + if ( item == null ) + items.Add( item = new AdvancedGenericMenuItem( paths[0] ) ); + } + else + { + item = currentRoot.children.OfType().FirstOrDefault( x => x.name == paths[0] ); + if ( item == null ) + currentRoot.AddChild( item = new AdvancedGenericMenuItem( paths[0] ) ); + } + + if ( paths.Length > 1 ) + return FindOrCreateItem( string.Join( "/", paths, 1, paths.Length - 1 ), item ); + + return item; + } + + private AdvancedGenericMenuItem FindParent( string name ) + { + string[] paths = name.Split( '/' ); + return FindOrCreateItem( string.Join( "/", paths, 0, paths.Length - 1 ) ); + } + + private string Name { get; set; } + + public AdvancedGenericMenu() : base( new AdvancedDropdownState() ) + { + Name = ""; + } + + public AdvancedGenericMenu( string name, AdvancedDropdownState state ) : base( state ) + { + Name = name; + } + + // + // Summary: + // Add a disabled item to the menu. + // + // Parameters: + // content: + // The GUIContent to display as a disabled menu item. + public void AddDisabledItem( GUIContent content ) + { + //var parent = FindParent( content.text ); + var item = FindOrCreateItem( content.text ); + item.Set( false, null, null ); + } + + // + // Summary: + // Add a disabled item to the menu. + // + // Parameters: + // content: + // The GUIContent to display as a disabled menu item. + // + // on: + // Specifies whether to show that the item is currently activated (i.e. a tick next + // to the item in the menu). + public void AddDisabledItem( GUIContent content, bool on ) + { + } + + public void AddItem( string name, bool on, MenuFunction func ) + { + AddItem( new GUIContent( name ), on, func ); + } + + public void AddItem( GUIContent content, bool on, MenuFunction func ) + { + //var parent = FindParent( content.text ); + var item = FindOrCreateItem( content.text ); + item.Set( true/*on*/, null, func ); + } + + public void AddItem( string name, bool on, MenuFunction2 func, object userData ) + { + AddItem( new GUIContent( name ), on, func, userData ); + } + + public void AddItem( GUIContent content, bool on, MenuFunction2 func, object userData ) + { + //var parent = FindParent( content.text ); + var item = FindOrCreateItem( content.text ); + item.Set( true/*on*/, null, func, userData ); + } + + // + // Summary: + // Add a seperator item to the menu. + // + // Parameters: + // path: + // The path to the submenu, if adding a separator to a submenu. When adding a separator + // to the top level of a menu, use an empty string as the path. + public void AddSeparator( string path = null ) + { + var parent = string.IsNullOrWhiteSpace( path ) ? null : FindParent( path ); + if ( parent == null ) + items.Add( null ); + else + parent.AddSeparator(); + } + + // + // Summary: + // Show the menu at the given screen rect. + // + // Parameters: + // position: + // The position at which to show the menu. + public void DropDown( Rect position ) + { + position.width = Mathf.Clamp( position.width, DefaultMinWidth.HasValue ? DefaultMinWidth.Value : 1f, DefaultMaxWidth.HasValue ? DefaultMaxWidth.Value : Screen.width ); + + Show( position ); + } + + protected override AdvancedDropdownItem BuildRoot() + { + var root = new AdvancedDropdownItem( Name ); + + foreach ( var m in items ) + { + if ( m == null ) + root.AddSeparator(); + else + root.AddChild( m ); + } + + return root; + } + + protected override void ItemSelected( AdvancedDropdownItem item ) + { + if ( item is AdvancedGenericMenuItem gmItem ) + gmItem.Run(); + } + } +} +#endif \ No newline at end of file diff --git a/Scripts/Editor/AdvancedGenericMenu.cs.meta b/Scripts/Editor/AdvancedGenericMenu.cs.meta new file mode 100644 index 0000000..2c5c440 --- /dev/null +++ b/Scripts/Editor/AdvancedGenericMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ddde711109af02e42bfe8eb006577081 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 36c3a6e..8522fc0 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -8,14 +8,15 @@ using Sirenix.OdinInspector.Editor; using Sirenix.Utilities; using Sirenix.Utilities.Editor; #endif +#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU +using GenericMenu = XNodeEditor.AdvancedGenericMenu; +#endif namespace XNodeEditor { /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. [CustomNodeEditor(typeof(XNode.Node))] public class NodeEditor : XNodeEditor.Internal.NodeEditorBase { - private readonly Color DEFAULTCOLOR = new Color32(90, 97, 105, 255); - /// Fires every whenever a node was modified through the editor public static Action onUpdateNode; public readonly static Dictionary portPositions = new Dictionary(); @@ -41,10 +42,32 @@ namespace XNodeEditor { string[] excludes = { "m_Script", "graph", "position", "ports" }; #if ODIN_INSPECTOR - InspectorUtilities.BeginDrawPropertyTree(objectTree, true); - GUIHelper.PushLabelWidth(84); - objectTree.Draw(true); + try + { +#if ODIN_INSPECTOR_3 + objectTree.BeginDraw( true ); +#else + InspectorUtilities.BeginDrawPropertyTree(objectTree, true); +#endif + } + catch ( ArgumentNullException ) + { +#if ODIN_INSPECTOR_3 + objectTree.EndDraw(); +#else + InspectorUtilities.EndDrawPropertyTree(objectTree); +#endif + NodeEditor.DestroyEditor(this.target); + return; + } + + GUIHelper.PushLabelWidth( 84 ); + objectTree.Draw( true ); +#if ODIN_INSPECTOR_3 + objectTree.EndDraw(); +#else InspectorUtilities.EndDrawPropertyTree(objectTree); +#endif GUIHelper.PopLabelWidth(); #else @@ -93,7 +116,7 @@ namespace XNodeEditor { Color color; if (type.TryGetAttributeTint(out color)) return color; // Return default color (grey) - else return DEFAULTCOLOR; + else return NodeEditorPreferences.GetSettings().tintColor; } public virtual GUIStyle GetBodyStyle() { @@ -104,6 +127,11 @@ namespace XNodeEditor { return NodeEditorResources.styles.nodeHighlight; } + /// Override to display custom node header tooltips + public virtual string GetHeaderTooltip() { + return null; + } + /// Add items for the context menu when right-clicking this node. Override to add custom menu items. public virtual void AddContextMenuItems(GenericMenu menu) { bool canRemove = true; diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index b112732..5d8d32b 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -4,6 +4,9 @@ using System.Linq; using UnityEditor; using UnityEngine; using XNodeEditor.Internal; +#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU +using GenericMenu = XNodeEditor.AdvancedGenericMenu; +#endif namespace XNodeEditor { public partial class NodeEditorWindow { @@ -14,16 +17,25 @@ namespace XNodeEditor { public static XNode.Node[] copyBuffer = null; - private bool IsDraggingPort { get { return draggedOutput != null; } } - private bool IsHoveringPort { get { return hoveredPort != null; } } - private bool IsHoveringNode { get { return hoveredNode != null; } } - private bool IsHoveringReroute { get { return hoveredReroute.port != null; } } + public bool IsDraggingPort { get { return draggedOutput != null; } } + public bool IsHoveringPort { get { return hoveredPort != null; } } + public bool IsHoveringNode { get { return hoveredNode != null; } } + public bool IsHoveringReroute { get { return hoveredReroute.port != null; } } + + /// Return the dragged port or null if not exist + public XNode.NodePort DraggedOutputPort { get { XNode.NodePort result = draggedOutput; return result; } } + /// Return the Hovered port or null if not exist + public XNode.NodePort HoveredPort { get { XNode.NodePort result = hoveredPort; return result; } } + /// Return the Hovered node or null if not exist + public XNode.Node HoveredNode { get { XNode.Node result = hoveredNode; return result; } } + private XNode.Node hoveredNode = null; [NonSerialized] public XNode.NodePort hoveredPort = null; [NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null; [NonSerialized] private XNode.NodePort autoConnectOutput = null; [NonSerialized] private List draggedOutputReroutes = new List(); + private RerouteReference hoveredReroute = new RerouteReference(); public List selectedReroutes = new List(); private Vector2 dragBoxStart; @@ -224,7 +236,7 @@ namespace XNodeEditor { // Open context menu for auto-connection if there is no target node else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) { GenericMenu menu = new GenericMenu(); - graphEditor.AddContextMenuItems(menu); + graphEditor.AddContextMenuItems(menu, draggedOutput.ValueType); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } //Release dragged connection @@ -296,7 +308,7 @@ namespace XNodeEditor { isDoubleClick = false; break; case EventType.KeyDown: - if (EditorGUIUtility.editingTextField) break; + if (EditorGUIUtility.editingTextField || GUIUtility.keyboardControl != 0) break; else if (e.keyCode == KeyCode.F) Home(); if (NodeEditorUtilities.IsMac()) { if (e.keyCode == KeyCode.Return) RenameSelectedNode(); @@ -328,11 +340,15 @@ namespace XNodeEditor { if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes(); e.Use(); } else if (e.commandName == "Copy") { - if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); - e.Use(); + if (!EditorGUIUtility.editingTextField) { + if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); + e.Use(); + } } else if (e.commandName == "Paste") { - if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); - e.Use(); + if (!EditorGUIUtility.editingTextField) { + if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); + e.Use(); + } } Repaint(); break; @@ -478,6 +494,7 @@ namespace XNodeEditor { } } } + EditorUtility.SetDirty(graph); // Select the new nodes Selection.objects = newNodes; } @@ -502,6 +519,7 @@ namespace XNodeEditor { DrawNoodle(gradient, path, stroke, thickness, gridPoints); + GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput); Color bgcol = Color.black; Color frcol = gradient.colorKeys[0].color; bgcol.a = 0.6f; @@ -514,7 +532,7 @@ namespace XNodeEditor { rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); rect = GridToWindowRect(rect); - NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol); + NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol, portStyle.normal.background, portStyle.active.background); } } } @@ -548,4 +566,4 @@ namespace XNodeEditor { autoConnectOutput = null; } } -} +} \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorBase.cs b/Scripts/Editor/NodeEditorBase.cs index 1fc28c7..e556a10 100644 --- a/Scripts/Editor/NodeEditorBase.cs +++ b/Scripts/Editor/NodeEditorBase.cs @@ -24,7 +24,7 @@ namespace XNodeEditor.Internal { private PropertyTree _objectTree; public PropertyTree objectTree { get { - if (this._objectTree == null) { + if (this._objectTree == null){ try { bool wasInEditor = NodeEditor.inNodeEditor; NodeEditor.inNodeEditor = true; @@ -58,6 +58,16 @@ namespace XNodeEditor.Internal { return editor; } + public static void DestroyEditor( K target ) + { + if ( target == null ) return; + T editor; + if ( editors.TryGetValue( target, out editor ) ) + { + editors.Remove( target ); + } + } + private static Type GetEditorType(Type type) { if (type == null) return null; if (editorTypes == null) CacheCustomEditors(); diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 99cdecf..35b2e2a 100755 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -4,6 +4,9 @@ using System.Linq; using UnityEditor; using UnityEngine; using XNodeEditor.Internal; +#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU +using GenericMenu = XNodeEditor.AdvancedGenericMenu; +#endif namespace XNodeEditor { /// Contains GUI methods @@ -112,7 +115,21 @@ namespace XNodeEditor { /// Show right-click context menu for hovered port void ShowPortContextMenu(XNode.NodePort hoveredPort) { GenericMenu contextMenu = new GenericMenu(); + foreach (var port in hoveredPort.GetConnections()) { + var name = port.node.name; + var index = hoveredPort.GetConnectionIndex(port); + contextMenu.AddItem(new GUIContent(string.Format("Disconnect({0})", name)), false, () => hoveredPort.Disconnect(index)); + } contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections()); + //Get compatible nodes with this port + if (NodeEditorPreferences.GetSettings().createFilter) { + contextMenu.AddSeparator(""); + + if (hoveredPort.direction == XNode.NodePort.IO.Input) + graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Output); + else + graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Input); + } contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } @@ -329,6 +346,8 @@ namespace XNodeEditor { if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; Color portColor = graphEditor.GetPortColor(output); + GUIStyle portStyle = graphEditor.GetPortStyle(output); + for (int k = 0; k < output.ConnectionCount; k++) { XNode.NodePort input = output.GetConnection(k); @@ -362,11 +381,11 @@ namespace XNodeEditor { // Draw selected reroute points with an outline if (selectedReroutes.Contains(rerouteRef)) { GUI.color = NodeEditorPreferences.GetSettings().highlightColor; - GUI.DrawTexture(rect, NodeEditorResources.dotOuter); + GUI.DrawTexture(rect, portStyle.normal.background); } GUI.color = portColor; - GUI.DrawTexture(rect, NodeEditorResources.dot); + GUI.DrawTexture(rect, portStyle.active.background); if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; @@ -531,8 +550,8 @@ namespace XNodeEditor { if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); EndZoomed(position, zoom, topPadding); - //If a change in is detected in the selected node, call OnValidate method. - //This is done through reflection because OnValidate is only relevant in editor, + //If a change in is detected in the selected node, call OnValidate method. + //This is done through reflection because OnValidate is only relevant in editor, //and thus, the code should not be included in build. if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null); } @@ -551,16 +570,21 @@ namespace XNodeEditor { } private void DrawTooltip() { - if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips && graphEditor != null) { - string tooltip = graphEditor.GetPortTooltip(hoveredPort); - if (string.IsNullOrEmpty(tooltip)) return; - GUIContent content = new GUIContent(tooltip); - Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); - size.x += 8; - Rect rect = new Rect(Event.current.mousePosition - (size), size); - EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); - Repaint(); + if (!NodeEditorPreferences.GetSettings().portTooltips || graphEditor == null) + return; + string tooltip = null; + if (hoveredPort != null) { + tooltip = graphEditor.GetPortTooltip(hoveredPort); + } else if (hoveredNode != null && IsHoveringNode && IsHoveringTitle(hoveredNode)) { + tooltip = NodeEditor.GetEditor(hoveredNode, this).GetHeaderTooltip(); } + if (string.IsNullOrEmpty(tooltip)) return; + GUIContent content = new GUIContent(tooltip); + Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); + size.x += 8; + Rect rect = new Rect(Event.current.mousePosition - (size), size); + EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); + Repaint(); } } } diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 3574ace..a0e3358 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -16,7 +16,7 @@ namespace XNodeEditor { /// Make a field for a serialized property. Automatically displays relevant node port. public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options) { - PropertyField(property, (GUIContent) null, includeChildren, options); + PropertyField(property, (GUIContent)null, includeChildren, options); } /// Make a field for a serialized property. Automatically displays relevant node port. @@ -59,6 +59,7 @@ namespace XNodeEditor { (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); float spacePadding = 0; + string tooltip = null; foreach (var attr in propertyAttributes) { if (attr is SpaceAttribute) { if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); @@ -71,6 +72,8 @@ namespace XNodeEditor { position = EditorGUI.IndentedRect(position); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; + } else if (attr is TooltipAttribute) { + tooltip = (attr as TooltipAttribute).tooltip; } } @@ -83,13 +86,13 @@ namespace XNodeEditor { switch (showBacking) { case XNode.Node.ShowBackingValue.Unconnected: // Display a label if port is connected - if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); + if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip)); // Display an editable property field if port is not connected else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; case XNode.Node.ShowBackingValue.Never: // Display a label - EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); + EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip)); break; case XNode.Node.ShowBackingValue.Always: // Display an editable property field @@ -98,7 +101,8 @@ namespace XNodeEditor { } rect = GUILayoutUtility.GetLastRect(); - rect.position = rect.position - new Vector2(16, -spacePadding); + float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left; + rect.position = rect.position - new Vector2(16 + paddingLeft, -spacePadding); // If property is an output, display a text label and put a port handle on the right side } else if (port.direction == XNode.NodePort.IO.Output) { // Get data from [Output] attribute @@ -115,6 +119,7 @@ namespace XNodeEditor { (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); float spacePadding = 0; + string tooltip = null; foreach (var attr in propertyAttributes) { if (attr is SpaceAttribute) { if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); @@ -127,6 +132,8 @@ namespace XNodeEditor { position = EditorGUI.IndentedRect(position); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; + } else if (attr is TooltipAttribute) { + tooltip = (attr as TooltipAttribute).tooltip; } } @@ -139,13 +146,13 @@ namespace XNodeEditor { switch (showBacking) { case XNode.Node.ShowBackingValue.Unconnected: // Display a label if port is connected - if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); + if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); // Display an editable property field if port is not connected else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; case XNode.Node.ShowBackingValue.Never: // Display a label - EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); + EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); break; case XNode.Node.ShowBackingValue.Always: // Display an editable property field @@ -154,15 +161,16 @@ namespace XNodeEditor { } rect = GUILayoutUtility.GetLastRect(); + rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right; rect.position = rect.position + new Vector2(rect.width, spacePadding); } rect.size = new Vector2(16, 16); - NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); - Color backgroundColor = editor.GetTint(); + Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); - DrawPortHandle(rect, backgroundColor, col); + GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port); + DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background); // Register the handle position Vector2 portPos = rect.center; @@ -194,7 +202,8 @@ namespace XNodeEditor { EditorGUILayout.LabelField(content, options); Rect rect = GUILayoutUtility.GetLastRect(); - position = rect.position - new Vector2(16, 0); + float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left; + position = rect.position - new Vector2(16 + paddingLeft, 0); } // If property is an output, display a text label and put a port handle on the right side else if (port.direction == XNode.NodePort.IO.Output) { @@ -202,6 +211,7 @@ namespace XNodeEditor { EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options); Rect rect = GUILayoutUtility.GetLastRect(); + rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right; position = rect.position + new Vector2(rect.width, 0); } PortField(position, port); @@ -213,10 +223,11 @@ namespace XNodeEditor { Rect rect = new Rect(position, new Vector2(16, 16)); - NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); - Color backgroundColor = editor.GetTint(); + Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); - DrawPortHandle(rect, backgroundColor, col); + GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port); + + DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background); // Register the handle position Vector2 portPos = rect.center; @@ -231,19 +242,22 @@ namespace XNodeEditor { // If property is an input, display a regular property field and put a port handle on the left side if (port.direction == XNode.NodePort.IO.Input) { rect = GUILayoutUtility.GetLastRect(); - rect.position = rect.position - new Vector2(16, 0); + float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left; + rect.position = rect.position - new Vector2(16 + paddingLeft, 0); // If property is an output, display a text label and put a port handle on the right side } else if (port.direction == XNode.NodePort.IO.Output) { rect = GUILayoutUtility.GetLastRect(); + rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right; rect.position = rect.position + new Vector2(rect.width, 0); } rect.size = new Vector2(16, 16); - NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); - Color backgroundColor = editor.GetTint(); + Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); - DrawPortHandle(rect, backgroundColor, col); + GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port); + + DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background); // Register the handle position Vector2 portPos = rect.center; @@ -258,16 +272,25 @@ namespace XNodeEditor { GUILayout.EndHorizontal(); } - public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { + /// + /// Draw the port + /// + /// position and size + /// color for background texture of the port. Normaly used to Border + /// + /// texture for border of the dot port + /// texture for the dot port + public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor, Texture2D border, Texture2D dot) { Color col = GUI.color; GUI.color = backgroundColor; - GUI.DrawTexture(rect, NodeEditorResources.dotOuter); + GUI.DrawTexture(rect, border); GUI.color = typeColor; - GUI.DrawTexture(rect, NodeEditorResources.dot); + GUI.DrawTexture(rect, dot); GUI.color = col; } -#region Obsolete + + #region Obsolete [Obsolete("Use IsDynamicPortListPort instead")] public static bool IsInstancePortListPort(XNode.NodePort port) { return IsDynamicPortListPort(port); @@ -277,7 +300,7 @@ namespace XNodeEditor { public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation); } -#endregion + #endregion /// Is this port part of a DynamicPortList? public static bool IsDynamicPortListPort(XNode.NodePort port) { @@ -308,12 +331,12 @@ namespace XNodeEditor { return new { index = i, port = x }; } } - return new { index = -1, port = (XNode.NodePort) null }; + return new { index = -1, port = (XNode.NodePort)null }; }).Where(x => x.port != null); List dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); node.UpdatePorts(); - + ReorderableList list = null; Dictionary rlc; if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) { @@ -328,7 +351,7 @@ namespace XNodeEditor { } list.list = dynamicPorts; list.DoLayoutList(); - + } private static ReorderableList CreateReorderableList(string fieldName, List dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) { @@ -349,7 +372,7 @@ namespace XNodeEditor { EditorGUI.PropertyField(rect, itemData, true); } else EditorGUI.LabelField(rect, port != null ? port.fieldName : ""); if (port != null) { - Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); + Vector2 pos = rect.position + (port.IsOutput ? new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); NodeEditorGUILayout.PortField(pos, port); } }; @@ -371,6 +394,7 @@ namespace XNodeEditor { }; list.onReorderCallback = (ReorderableList rl) => { + serializedObject.Update(); bool hasRect = false; bool hasNewRect = false; Rect rect = Rect.zero; @@ -385,8 +409,8 @@ namespace XNodeEditor { // Swap cached positions to mitigate twitching hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect); hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect); - NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect?newRect:rect; - NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect?rect:newRect; + NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect; + NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect; } } // Move down @@ -399,8 +423,8 @@ namespace XNodeEditor { // Swap cached positions to mitigate twitching hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect); hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect); - NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect?newRect:rect; - NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect?rect:newRect; + NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect; + NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect; } } // Apply changes @@ -445,7 +469,7 @@ namespace XNodeEditor { return new { index = i, port = x }; } } - return new { index = -1, port = (XNode.NodePort) null }; + return new { index = -1, port = (XNode.NodePort)null }; }).Where(x => x.port != null); dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); @@ -514,4 +538,4 @@ namespace XNodeEditor { return list; } } -} +} \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 72eb8aa..73dfe96 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -20,10 +20,10 @@ namespace XNodeEditor { [System.Serializable] public class Settings : ISerializationCallbackReceiver { - [SerializeField] private Color32 _gridLineColor = new Color(0.45f, 0.45f, 0.45f); + [SerializeField] private Color32 _gridLineColor = new Color(.23f, .23f, .23f); public Color32 gridLineColor { get { return _gridLineColor; } set { _gridLineColor = value; _gridTexture = null; _crossTexture = null; } } - [SerializeField] private Color32 _gridBgColor = new Color(0.18f, 0.18f, 0.18f); + [SerializeField] private Color32 _gridBgColor = new Color(.19f, .19f, .19f); public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } } [Obsolete("Use maxZoom instead")] @@ -32,15 +32,20 @@ namespace XNodeEditor { [UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")] public float maxZoom = 5f; public float minZoom = 1f; + public Color32 tintColor = new Color32(90, 97, 105, 255); public Color32 highlightColor = new Color32(255, 255, 255, 255); public bool gridSnap = true; public bool autoSave = true; + public bool openOnCreate = true; public bool dragToCreate = true; + public bool createFilter = true; public bool zoomToMouse = true; public bool portTooltips = true; [SerializeField] private string typeColorsData = ""; [NonSerialized] public Dictionary typeColors = new Dictionary(); [FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy; + public float noodleThickness = 2f; + public NoodleStroke noodleStroke = NoodleStroke.Full; private Texture2D _gridTexture; @@ -149,6 +154,7 @@ namespace XNodeEditor { //Label EditorGUILayout.LabelField("System", EditorStyles.boldLabel); settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave); + settings.openOnCreate = EditorGUILayout.Toggle(new GUIContent("Open Editor on Create", "Disable to prevent openening the editor when creating a new graph"), settings.openOnCreate); if (GUI.changed) SavePrefs(key, settings); EditorGUILayout.Space(); } @@ -156,11 +162,16 @@ namespace XNodeEditor { private static void NodeSettingsGUI(string key, Settings settings) { //Label EditorGUILayout.LabelField("Node", EditorStyles.boldLabel); + settings.tintColor = EditorGUILayout.ColorField("Tint", settings.tintColor); settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor); settings.noodlePath = (NoodlePath) EditorGUILayout.EnumPopup("Noodle path", (Enum) settings.noodlePath); + settings.noodleThickness = EditorGUILayout.FloatField(new GUIContent("Noodle thickness", "Noodle Thickness of the node connections"), settings.noodleThickness); settings.noodleStroke = (NoodleStroke) EditorGUILayout.EnumPopup("Noodle stroke", (Enum) settings.noodleStroke); settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips); settings.dragToCreate = EditorGUILayout.Toggle(new GUIContent("Drag to Create", "Drag a port connection anywhere on the grid to create and connect a node"), settings.dragToCreate); + settings.createFilter = EditorGUILayout.Toggle(new GUIContent("Create Filter", "Only show nodes that are compatible with the selected port"), settings.createFilter); + + //END if (GUI.changed) { SavePrefs(key, settings); NodeEditorWindow.RepaintAll(); diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 0a0a36a..d401139 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -5,6 +5,9 @@ using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; +#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU +using GenericMenu = XNodeEditor.AdvancedGenericMenu; +#endif namespace XNodeEditor { /// Contains reflection-related extensions built for xNode diff --git a/Scripts/Editor/NodeEditorResources.cs b/Scripts/Editor/NodeEditorResources.cs index 0a84e0a..26a79ce 100644 --- a/Scripts/Editor/NodeEditorResources.cs +++ b/Scripts/Editor/NodeEditorResources.cs @@ -18,7 +18,7 @@ namespace XNodeEditor { public static Styles _styles = null; public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } } public class Styles { - public GUIStyle inputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; + public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; public Styles() { GUIStyle baseStyle = new GUIStyle("Label"); @@ -26,7 +26,15 @@ namespace XNodeEditor { inputPort = new GUIStyle(baseStyle); inputPort.alignment = TextAnchor.UpperLeft; - inputPort.padding.left = 10; + inputPort.padding.left = 0; + inputPort.active.background = dot; + inputPort.normal.background = dotOuter; + + outputPort = new GUIStyle(baseStyle); + outputPort.alignment = TextAnchor.UpperRight; + outputPort.padding.right = 0; + outputPort.active.background = dot; + outputPort.normal.background = dotOuter; nodeHeader = new GUIStyle(); nodeHeader.alignment = TextAnchor.MiddleCenter; diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index ac12e33..753973b 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -127,6 +127,57 @@ namespace XNodeEditor { return methods.Count() > 0; } + /// + /// Looking for ports with value Type compatible with a given type. + /// + /// Node to search + /// Type to find compatiblities + /// + /// True if NodeType has some port with value type compatible + public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { + Type findType = typeof(XNode.Node.InputAttribute); + if (direction == XNode.NodePort.IO.Output) + findType = typeof(XNode.Node.OutputAttribute); + + //Get All fields from node type and we go filter only field with portAttribute. + //This way is possible to know the values of the all ports and if have some with compatible value tue + foreach (FieldInfo f in XNode.NodeDataCache.GetNodeFields(nodeType)) { + var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault(); + if (portAttribute != null) { + if (IsCastableTo(f.FieldType, compatibleType)) { + return true; + } + } + } + + return false; + } + + /// + /// Filter only node types that contains some port value type compatible with an given type + /// + /// List with all nodes type to filter + /// Compatible Type to Filter + /// Return Only Node Types with ports compatible, or an empty list + public static List GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { + //Result List + List filteredTypes = new List(); + + //Return empty list + if (nodeTypes == null) { return filteredTypes; } + if (compatibleType == null) { return filteredTypes; } + + //Find compatiblity + foreach (Type findType in nodeTypes) { + if (HasCompatiblePortType(findType, compatibleType, direction)) { + filteredTypes.Add(findType); + } + } + + return filteredTypes; + } + + /// Return a prettiefied type name. public static string PrettyName(this Type type) { if (type == null) return "null"; diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 4f0a102..a7ec96b 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -99,7 +99,7 @@ namespace XNodeEditor { private static void OnSelectionChanged() { XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { - Open(nodeGraph); + if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph); } } diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 01de70e..a304c2c 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -1,8 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; +#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU +using GenericMenu = XNodeEditor.AdvancedGenericMenu; +#endif namespace XNodeEditor { /// Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. @@ -17,7 +19,7 @@ namespace XNodeEditor { /// Called when opened by NodeEditorWindow public virtual void OnOpen() { } - + /// Called when NodeEditorWindow gains focus public virtual void OnWindowFocus() { } @@ -57,10 +59,24 @@ namespace XNodeEditor { return 0; } - /// Add items for the context menu when right-clicking this node. Override to add custom menu items. - public virtual void AddContextMenuItems(GenericMenu menu) { + /// + /// Add items for the context menu when right-clicking this node. + /// Override to add custom menu items. + /// + /// + /// Use it to filter only nodes with ports value type, compatible with this type + /// Direction of the compatiblity + public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); - var nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(type => GetNodeMenuOrder(type)).ToArray(); + + Type[] nodeTypes; + + if (compatibleType != null && NodeEditorPreferences.GetSettings().createFilter) { + nodeTypes = NodeEditorUtilities.GetCompatibleNodesTypes(NodeEditorReflection.nodeTypes, compatibleType, direction).OrderBy(GetNodeMenuOrder).ToArray(); + } else { + nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(GetNodeMenuOrder).ToArray(); + } + for (int i = 0; i < nodeTypes.Length; i++) { Type type = nodeTypes[i]; @@ -125,7 +141,7 @@ namespace XNodeEditor { /// The output this noodle comes from. Never null. /// The output this noodle comes from. Can be null if we are dragging the noodle. public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) { - return 5f; + return NodeEditorPreferences.GetSettings().noodleThickness; } public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) { @@ -141,6 +157,29 @@ namespace XNodeEditor { return GetTypeColor(port.ValueType); } + /// + /// The returned Style is used to configure the paddings and icon texture of the ports. + /// Use these properties to customize your port style. + /// + /// The properties used is: + /// [Left and Right], [Background] = border texture, + /// and [Background] = dot texture; + /// + /// the owner of the style + /// + public virtual GUIStyle GetPortStyle(XNode.NodePort port) { + if (port.direction == XNode.NodePort.IO.Input) + return NodeEditorResources.styles.inputPort; + + return NodeEditorResources.styles.outputPort; + } + + /// The returned color is used to color the background of the door. + /// Usually used for outer edge effect + public virtual Color GetPortBackgroundColor(XNode.NodePort port) { + return Color.gray; + } + /// Returns generated color for a type. This color is editable in preferences public virtual Color GetTypeColor(Type type) { return NodeEditorPreferences.GetTypeColor(type); @@ -234,4 +273,4 @@ namespace XNodeEditor { } } } -} +} \ No newline at end of file diff --git a/Scripts/Editor/SceneGraphEditor.cs b/Scripts/Editor/SceneGraphEditor.cs index 9fb1c67..dd290a8 100644 --- a/Scripts/Editor/SceneGraphEditor.cs +++ b/Scripts/Editor/SceneGraphEditor.cs @@ -53,6 +53,7 @@ namespace XNodeEditor { GUI.color = Color.white; } } + DrawDefaultInspector(); } private void OnEnable() { diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 6744cc5..704e99d 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -51,6 +51,8 @@ namespace XNode { Strict, /// Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject) InheritedInverse, + /// Allow connections where output value type is assignable from input value or input value type is assignable from output value type + InheritedAny } #region Obsolete diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index ba52e1b..f865ab2 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -7,6 +7,7 @@ namespace XNode { /// Precaches reflection data in editor so we won't have to do it runtime public static class NodeDataCache { private static PortDataCache portDataCache; + private static Dictionary> formerlySerializedAsCache; private static bool Initialized { get { return portDataCache != null; } } /// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. @@ -17,6 +18,9 @@ namespace XNode { Dictionary> removedPorts = new Dictionary>(); System.Type nodeType = node.GetType(); + Dictionary formerlySerializedAs = null; + if (formerlySerializedAsCache != null) formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs); + List dynamicListPorts = new List(); List typePortCache; @@ -43,6 +47,11 @@ namespace XNode { } // If port doesn't exist anymore, remove it else if (port.IsStatic) { + //See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts + // so it can be reconnected in missing ports stage. + string newName = null; + if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) removedPorts.Add(newName, port.GetConnections()); + port.ClearConnections(); ports.Remove(port.fieldName); } @@ -177,6 +186,7 @@ namespace XNode { object[] attribs = fieldInfo[i].GetCustomAttributes(true); Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute; Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute; + UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute; if (inputAttrib == null && outputAttrib == null) continue; @@ -185,6 +195,14 @@ namespace XNode { if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List()); portDataCache[nodeType].Add(new NodePort(fieldInfo[i])); } + + if(formerlySerializedAsAttribute != null) { + if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary>(); + if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary()); + + if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node."); + else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name); + } } } diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index b2f1ad1..6bcc638 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -273,10 +273,12 @@ namespace XNode { if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; // Check output type constraints if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; // Success return true; } @@ -294,12 +296,13 @@ namespace XNode { for (int i = 0; i < port.connections.Count; i++) { if (port.connections[i].Port == this) { port.connections.RemoveAt(i); + // Trigger OnRemoveConnection from this side port + port.node.OnRemoveConnection(port); } } } // Trigger OnRemoveConnection node.OnRemoveConnection(this); - if (port != null) port.node.OnRemoveConnection(port); } /// Disconnect this port from another port @@ -307,11 +310,7 @@ namespace XNode { // Remove the other ports connection to this port NodePort otherPort = connections[i].Port; if (otherPort != null) { - for (int k = 0; k < otherPort.connections.Count; k++) { - if (otherPort.connections[k].Port == this) { - otherPort.connections.RemoveAt(i); - } - } + otherPort.connections.RemoveAll(it => { return it.Port == this; }); } // Remove this ports connection to the other connections.RemoveAt(i); @@ -415,4 +414,4 @@ namespace XNode { } } } -} \ No newline at end of file +}