diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs
index dd49a06..315f23b 100644
--- a/Scripts/Editor/NodeEditorAction.cs
+++ b/Scripts/Editor/NodeEditorAction.cs
@@ -60,7 +60,7 @@ namespace XNodeEditor {
case EventType.MouseDrag:
if (e.button == 0) {
if (IsDraggingPort) {
- if (IsHoveringPort && hoveredPort.IsInput) {
+ if (IsHoveringPort && hoveredPort.IsInput && draggedOutput.CanConnectTo(hoveredPort)) {
if (!draggedOutput.IsConnectedTo(hoveredPort)) {
draggedOutputTarget = hoveredPort;
}
@@ -422,7 +422,7 @@ namespace XNodeEditor {
Rect fromRect;
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
Vector2 from = fromRect.center;
- col.a = 0.6f;
+ col.a = draggedOutputTarget != null ? 1.0f : 0.6f;
Vector2 to = Vector2.zero;
for (int i = 0; i < draggedOutputReroutes.Count; i++) {
to = draggedOutputReroutes[i];
diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs
index 629c594..b136dc9 100644
--- a/Scripts/Editor/NodeEditorGUILayout.cs
+++ b/Scripts/Editor/NodeEditorGUILayout.cs
@@ -260,7 +260,7 @@ namespace XNodeEditor {
/// The serializedObject of the node
/// Connection type of added instance ports
/// Called on the list on creation. Use this if you want to customize the created ReorderableList
- public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, Action onCreation = null) {
+ 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) {
XNode.Node node = serializedObject.targetObject as XNode.Node;
Predicate isMatchingInstancePort =
@@ -279,7 +279,7 @@ namespace XNodeEditor {
// If a ReorderableList isn't cached for this array, do so.
if (list == null) {
SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
- list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, onCreation);
+ list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list);
else reorderableListCache.Add(serializedObject.targetObject, new Dictionary() { { fieldName, list } });
}
@@ -287,7 +287,7 @@ namespace XNodeEditor {
list.DoLayoutList();
}
- private static ReorderableList CreateReorderableList(string fieldName, List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, Action onCreation) {
+ private static ReorderableList CreateReorderableList(string fieldName, List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) {
bool hasArrayData = arrayData != null && arrayData.isArray;
int arraySize = hasArrayData ? arrayData.arraySize : 0;
XNode.Node node = serializedObject.targetObject as XNode.Node;
@@ -375,8 +375,8 @@ namespace XNodeEditor {
int i = 0;
while (node.HasPort(newName)) newName = fieldName + " " + (++i);
- if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, newName);
- else node.AddInstanceInput(type, connectionType, newName);
+ if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName);
+ else node.AddInstanceInput(type, connectionType, typeConstraint, newName);
serializedObject.Update();
EditorUtility.SetDirty(node);
if (hasArrayData) arrayData.InsertArrayElementAtIndex(arraySize);
@@ -422,8 +422,8 @@ namespace XNodeEditor {
string newName = arrayData.name + " 0";
int i = 0;
while (node.HasPort(newName)) newName = arrayData.name + " " + (++i);
- if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, newName);
- else node.AddInstanceInput(type, connectionType, newName);
+ if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, typeConstraint, newName);
+ else node.AddInstanceInput(type, connectionType, typeConstraint, newName);
EditorUtility.SetDirty(node);
instancePortCount++;
}
diff --git a/Scripts/Node.cs b/Scripts/Node.cs
index 43d716e..7c06f92 100644
--- a/Scripts/Node.cs
+++ b/Scripts/Node.cs
@@ -41,6 +41,16 @@ namespace XNode {
Override,
}
+ /// Tells which types of input to allow
+ public enum TypeConstraint {
+ /// Allow all types of input
+ None,
+ /// Allow similar and inherited types
+ Inherited,
+ /// Allow only similar types
+ Strict,
+ }
+
/// Iterate over all ports on this node.
public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
/// Iterate over all outputs on this node.
@@ -87,21 +97,21 @@ namespace XNode {
/// Convenience function.
///
///
- public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) {
- return AddInstancePort(type, NodePort.IO.Input, connectionType, fieldName);
+ public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ return AddInstancePort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
}
/// Convenience function.
///
///
- public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) {
- return AddInstancePort(type, NodePort.IO.Output, connectionType, fieldName);
+ public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ return AddInstancePort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
}
/// Add a dynamic, serialized port to this node.
///
///
- private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) {
+ private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
if (fieldName == null) {
fieldName = "instanceInput_0";
int i = 0;
@@ -110,7 +120,7 @@ namespace XNode {
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
return ports[fieldName];
}
- NodePort port = new NodePort(fieldName, type, direction, connectionType, this);
+ NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
ports.Add(fieldName, port);
return port;
}
@@ -216,14 +226,18 @@ namespace XNode {
public ShowBackingValue backingValue;
public ConnectionType connectionType;
public bool instancePortList;
+ public TypeConstraint typeConstraint;
/// Mark a serializable field as an input port. You can access this through
/// Should we display the backing value for this port as an editor field?
/// Should we allow multiple connections?
- public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) {
+ /// Constrains which input connections can be made to this port
+ /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays
+ public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool instancePortList = false) {
this.backingValue = backingValue;
this.connectionType = connectionType;
this.instancePortList = instancePortList;
+ this.typeConstraint = typeConstraint;
}
}
@@ -237,6 +251,7 @@ namespace XNode {
/// Mark a serializable field as an output port. You can access this through
/// Should we display the backing value for this port as an editor field?
/// Should we allow multiple connections?
+ /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays
public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) {
this.backingValue = backingValue;
this.connectionType = connectionType;
diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs
index 8d96146..434ffc5 100644
--- a/Scripts/NodeDataCache.cs
+++ b/Scripts/NodeDataCache.cs
@@ -30,7 +30,7 @@ namespace XNode {
NodePort staticPort;
if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
// If port exists but with wrong settings, remove it. Re-add it later.
- if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName);
+ if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction || port.typeConstraint != staticPort.typeConstraint) ports.Remove(port.fieldName);
else port.ValueType = staticPort.ValueType;
}
// If port doesn't exist anymore, remove it
diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs
index 3f55795..24e4941 100644
--- a/Scripts/NodePort.cs
+++ b/Scripts/NodePort.cs
@@ -21,6 +21,7 @@ namespace XNode {
public IO direction { get { return _direction; } }
public Node.ConnectionType connectionType { get { return _connectionType; } }
+ public Node.TypeConstraint typeConstraint { get { return _typeConstraint; } }
/// Is this port connected to anytihng?
public bool IsConnected { get { return connections.Count != 0; } }
@@ -49,6 +50,7 @@ namespace XNode {
[SerializeField] private List connections = new List();
[SerializeField] private IO _direction;
[SerializeField] private Node.ConnectionType _connectionType;
+ [SerializeField] private Node.TypeConstraint _typeConstraint;
[SerializeField] private bool _dynamic;
/// Construct a static targetless nodeport. Used as a template.
@@ -61,6 +63,7 @@ namespace XNode {
if (attribs[i] is Node.InputAttribute) {
_direction = IO.Input;
_connectionType = (attribs[i] as Node.InputAttribute).connectionType;
+ _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint;
} else if (attribs[i] is Node.OutputAttribute) {
_direction = IO.Output;
_connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
@@ -75,17 +78,19 @@ namespace XNode {
_direction = nodePort.direction;
_dynamic = nodePort._dynamic;
_connectionType = nodePort._connectionType;
+ _typeConstraint = nodePort._typeConstraint;
_node = node;
}
/// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports.
- public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node node) {
+ public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) {
_fieldName = fieldName;
this.ValueType = type;
_direction = direction;
_node = node;
_dynamic = true;
_connectionType = connectionType;
+ _typeConstraint = typeConstraint;
}
/// Checks all connections for invalid references, and removes them.
@@ -240,6 +245,23 @@ namespace XNode {
return false;
}
+ /// Returns true if this port can connect to specified port
+ public bool CanConnectTo(NodePort port) {
+ // Figure out which is input and which is output
+ NodePort input = null, output = null;
+ if (IsInput) input = this;
+ else output = this;
+ if (port.IsInput) input = port;
+ else output = port;
+ // If there isn't one of each, they can't connect
+ if (input == null || output == null) return false;
+ // Check type constraints
+ 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;
+ // Success
+ return true;
+ }
+
/// Disconnect this port from another port
public void Disconnect(NodePort port) {
// Remove this ports connection to the other