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
+}