/// <summary> /// Looking for ports with value Type compatible with a given type. /// </summary> /// <param name="nodeType">Node to search</param> /// <param name="compatibleType">Type to find compatiblities</param> /// <param name="direction"></param> /// <returns>True if NodeType has some port with value type compatible</returns> 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); }
/// <summary> Add a port field to previous layout element. </summary> public static void AddPortField(XNode.NodePort port, XNode.NodePort.IO portDirection = XNode.NodePort.IO.Undefined) { if (port == null) { return; } Rect rect = new Rect(); if (portDirection == XNode.NodePort.IO.Undefined) { portDirection = port.direction; } // If property is an input, display a regular property field and put a port handle on the left side if (portDirection == XNode.NodePort.IO.Input) { rect = GUILayoutUtility.GetLastRect(); rect.position = rect.position - new Vector2(16, 0); // If property is an output, display a text label and put a port handle on the right side } else if (portDirection == XNode.NodePort.IO.Output) { rect = GUILayoutUtility.GetLastRect(); rect.position = rect.position + new Vector2(rect.width, 0); } rect.size = new Vector2(16, 16); Color backgroundColor = new Color32(90, 97, 105, 255); Color tint; if (NodeEditorWindow.nodeTint.TryGetValue(port.node.GetType(), out tint)) { backgroundColor *= tint; } Color col = NodeEditorWindow.current.graphEditor.GetTypeColor(port.ValueType); DrawPortHandle(rect, backgroundColor, col); // Register the handle position Vector2 portPos = rect.center; if (NodeEditor.portPositions.ContainsKey(port)) { NodeEditor.portPositions[port] = portPos; } else { NodeEditor.portPositions.Add(port, portPos); } }
private static ReorderableList CreateReorderableList(List <XNode.NodePort> instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, string label, XNode.ConnectionType connectionType = XNode.ConnectionType.Multiple) { bool hasArrayData = arrayData != null && arrayData.isArray; int arraySize = hasArrayData ? arrayData.arraySize : 0; var node = serializedObject.targetObject as XNode.INode; ReorderableList list = new ReorderableList(instancePorts, null, true, true, true, true); list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { XNode.NodePort port = node.GetPort(arrayData.name + " " + index); if (hasArrayData) { if (arrayData.arraySize <= index) { EditorGUI.LabelField(rect, "Invalid element " + index); return; } SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); EditorGUI.PropertyField(rect, itemData); } else { EditorGUI.LabelField(rect, port.fieldName); } Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); NodeEditorGUILayout.PortField(pos, port); }; list.elementHeightCallback = (int index) => { if (hasArrayData) { if (arrayData.arraySize <= index) { return(EditorGUIUtility.singleLineHeight); } SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); return(EditorGUI.GetPropertyHeight(itemData)); } else { return(EditorGUIUtility.singleLineHeight); } }; list.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, label); }; list.onSelectCallback = (ReorderableList rl) => { reorderableListIndex = rl.index; }; list.onReorderCallback = (ReorderableList rl) => { // Move up if (rl.index > reorderableListIndex) { for (int i = reorderableListIndex; i < rl.index; ++i) { XNode.NodePort port = node.GetPort(arrayData.name + " " + i); XNode.NodePort nextPort = node.GetPort(arrayData.name + " " + (i + 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching Rect rect = NodeEditorWindow.current.portConnectionPoints[port]; NodeEditorWindow.current.portConnectionPoints[port] = NodeEditorWindow.current.portConnectionPoints[nextPort]; NodeEditorWindow.current.portConnectionPoints[nextPort] = rect; } } // Move down else { for (int i = reorderableListIndex; i > rl.index; --i) { XNode.NodePort port = node.GetPort(arrayData.name + " " + i); XNode.NodePort nextPort = node.GetPort(arrayData.name + " " + (i - 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching Rect rect = NodeEditorWindow.current.portConnectionPoints[port]; NodeEditorWindow.current.portConnectionPoints[port] = NodeEditorWindow.current.portConnectionPoints[nextPort]; NodeEditorWindow.current.portConnectionPoints[nextPort] = rect; } } // Apply changes serializedObject.ApplyModifiedProperties(); serializedObject.Update(); // Move array data if there is any if (hasArrayData) { arrayData.MoveArrayElement(reorderableListIndex, rl.index); } // Apply changes serializedObject.ApplyModifiedProperties(); serializedObject.Update(); NodeEditorWindow.current.Repaint(); EditorApplication.delayCall += NodeEditorWindow.current.Repaint; }; list.onAddCallback = (ReorderableList rl) => { // Add instance port postfixed with an index number 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); } serializedObject.Update(); EditorUtility.SetDirty(node as UnityEngine.Object); if (hasArrayData) { arrayData.InsertArrayElementAtIndex(arraySize); } serializedObject.ApplyModifiedProperties(); }; list.onRemoveCallback = (ReorderableList rl) => { int index = rl.index; // Clear the removed ports connections instancePorts[index].ClearConnections(); // Move following connections one step up to replace the missing connection for (int k = index + 1; k < instancePorts.Count(); k++) { for (int j = 0; j < instancePorts[k].ConnectionCount; j++) { XNode.NodePort other = instancePorts[k].GetConnection(j); instancePorts[k].Disconnect(other); instancePorts[k - 1].Connect(other); } } // Remove the last instance port, to avoid messing up the indexing node.RemoveInstancePort(instancePorts[instancePorts.Count() - 1].fieldName); serializedObject.Update(); EditorUtility.SetDirty(node as UnityEngine.Object); if (hasArrayData) { arrayData.DeleteArrayElementAtIndex(index); arraySize--; // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues if (instancePorts.Count <= arraySize) { while (instancePorts.Count <= arraySize) { arrayData.DeleteArrayElementAtIndex(--arraySize); } Debug.LogWarning("Array size exceeded instance ports size. Excess items removed."); } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); } }; if (hasArrayData) { int instancePortCount = instancePorts.Count; while (instancePortCount < arraySize) { // Add instance port postfixed with an index number 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); } EditorUtility.SetDirty(node as UnityEngine.Object); instancePortCount++; } while (arraySize < instancePortCount) { arrayData.InsertArrayElementAtIndex(arraySize); arraySize++; } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); } if (onCreateReorderableList != null) { onCreateReorderableList(list); } return(list); }
/// <summary> Draw an editable list of instance ports. Port names are named as "[fieldName] [index]" </summary> /// <param name="fieldName">Supply a list for editable values</param> /// <param name="type">Value type of added instance ports</param> /// <param name="serializedObject">The serializedObject of the node</param> /// <param name="connectionType">Connection type of added instance ports</param> public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.ConnectionType connectionType = XNode.ConnectionType.Multiple) { var node = serializedObject.targetObject as XNode.INode; SerializedProperty arrayData = serializedObject.FindProperty(fieldName); Predicate <string> isMatchingInstancePort = x => { string[] split = x.Split(' '); if (split != null && split.Length == 2) { return(split[0] == fieldName); } else { return(false); } }; List <XNode.NodePort> instancePorts = node.InstancePorts.Where(x => isMatchingInstancePort(x.fieldName)).OrderBy(x => x.fieldName).ToList(); ReorderableList list = null; Dictionary <string, ReorderableList> rlc; if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) { if (!rlc.TryGetValue(fieldName, out list)) { list = null; } } // If a ReorderableList isn't cached for this array, do so. if (list == null) { string label = serializedObject.FindProperty(fieldName).displayName; list = CreateReorderableList(instancePorts, arrayData, type, serializedObject, io, label, connectionType); if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) { rlc.Add(fieldName, list); } else { reorderableListCache.Add(serializedObject.targetObject, new Dictionary <string, ReorderableList>() { { fieldName, list } }); } } list.list = instancePorts; list.DoLayoutList(); }
private static ReorderableList CreateReorderableList(string fieldName, List <XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action <ReorderableList> onCreation) { bool hasArrayData = arrayData != null && arrayData.isArray; XNode.Node node = serializedObject.targetObject as XNode.Node; ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true); string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName); list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { XNode.NodePort port = node.GetPort(fieldName + " " + index); if (hasArrayData) { if (arrayData.arraySize <= index) { EditorGUI.LabelField(rect, "Array[" + index + "] data out of range"); return; } SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); 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)); NodeEditorGUILayout.PortField(pos, port); } }; list.elementHeightCallback = (int index) => { if (hasArrayData) { if (arrayData.arraySize <= index) { return(EditorGUIUtility.singleLineHeight); } SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); return(EditorGUI.GetPropertyHeight(itemData)); } else { return(EditorGUIUtility.singleLineHeight); } }; list.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, label); }; list.onSelectCallback = (ReorderableList rl) => { reorderableListIndex = rl.index; }; list.onReorderCallback = (ReorderableList rl) => { // Move up if (rl.index > reorderableListIndex) { for (int i = reorderableListIndex; i < rl.index; ++i) { XNode.NodePort port = node.GetPort(fieldName + " " + i); XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching Rect rect = NodeEditorWindow.current.portConnectionPoints[port]; NodeEditorWindow.current.portConnectionPoints[port] = NodeEditorWindow.current.portConnectionPoints[nextPort]; NodeEditorWindow.current.portConnectionPoints[nextPort] = rect; } } // Move down else { for (int i = reorderableListIndex; i > rl.index; --i) { XNode.NodePort port = node.GetPort(fieldName + " " + i); XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching Rect rect = NodeEditorWindow.current.portConnectionPoints[port]; NodeEditorWindow.current.portConnectionPoints[port] = NodeEditorWindow.current.portConnectionPoints[nextPort]; NodeEditorWindow.current.portConnectionPoints[nextPort] = rect; } } // Apply changes serializedObject.ApplyModifiedProperties(); serializedObject.Update(); // Move array data if there is any if (hasArrayData) { arrayData.MoveArrayElement(reorderableListIndex, rl.index); } // Apply changes serializedObject.ApplyModifiedProperties(); serializedObject.Update(); NodeEditorWindow.current.Repaint(); EditorApplication.delayCall += NodeEditorWindow.current.Repaint; }; list.onAddCallback = (ReorderableList rl) => { // Add dynamic port postfixed with an index number string newName = fieldName + " 0"; int i = 0; while (node.HasPort(newName)) { newName = fieldName + " " + (++i); } if (io == XNode.NodePort.IO.Output) { node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName); } else { node.AddDynamicInput(type, connectionType, typeConstraint, newName); } serializedObject.Update(); EditorUtility.SetDirty(node); if (hasArrayData) { arrayData.InsertArrayElementAtIndex(arrayData.arraySize); } serializedObject.ApplyModifiedProperties(); }; list.onRemoveCallback = (ReorderableList rl) => { var indexedPorts = node.DynamicPorts.Select(x => { string[] split = x.fieldName.Split(' '); if (split != null && split.Length == 2 && split[0] == fieldName) { int i = -1; if (int.TryParse(split[1], out i)) { return(new { index = i, port = x }); } } return(new { index = -1, port = (XNode.NodePort)null }); }).Where(x => x.port != null); dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); int index = rl.index; if (dynamicPorts[index] == null) { Debug.LogWarning("No port found at index " + index + " - Skipped"); } else if (dynamicPorts.Count <= index) { Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + " - Skipped"); } else { // Clear the removed ports connections dynamicPorts[index].ClearConnections(); // Move following connections one step up to replace the missing connection for (int k = index + 1; k < dynamicPorts.Count(); k++) { for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) { XNode.NodePort other = dynamicPorts[k].GetConnection(j); dynamicPorts[k].Disconnect(other); dynamicPorts[k - 1].Connect(other); } } // Remove the last dynamic port, to avoid messing up the indexing node.RemoveDynamicPort(dynamicPorts[dynamicPorts.Count() - 1].fieldName); serializedObject.Update(); EditorUtility.SetDirty(node); } if (hasArrayData) { if (arrayData.arraySize <= index) { Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped"); Debug.Log(rl.list[0]); return; } arrayData.DeleteArrayElementAtIndex(index); // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues if (dynamicPorts.Count <= arrayData.arraySize) { while (dynamicPorts.Count <= arrayData.arraySize) { arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1); } UnityEngine.Debug.LogWarning("Array size exceeded dynamic ports size. Excess items removed."); } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); } }; if (hasArrayData) { int dynamicPortCount = dynamicPorts.Count; while (dynamicPortCount < arrayData.arraySize) { // Add dynamic port postfixed with an index number string newName = arrayData.name + " 0"; int i = 0; while (node.HasPort(newName)) { newName = arrayData.name + " " + (++i); } if (io == XNode.NodePort.IO.Output) { node.AddDynamicOutput(type, connectionType, typeConstraint, newName); } else { node.AddDynamicInput(type, connectionType, typeConstraint, newName); } EditorUtility.SetDirty(node); dynamicPortCount++; } while (arrayData.arraySize < dynamicPortCount) { arrayData.InsertArrayElementAtIndex(arrayData.arraySize); } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); } if (onCreation != null) { onCreation(list); } return(list); }
/// <summary> Draw an editable list of dynamic ports. Port names are named as "[fieldName] [index]" </summary> /// <param name="fieldName">Supply a list for editable values</param> /// <param name="type">Value type of added dynamic ports</param> /// <param name="serializedObject">The serializedObject of the node</param> /// <param name="connectionType">Connection type of added dynamic ports</param> /// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param> public static void DynamicPortList(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 <ReorderableList> onCreation = null) { XNode.Node node = serializedObject.targetObject as XNode.Node; var indexedPorts = node.DynamicPorts.Select(x => { string[] split = x.fieldName.Split(' '); if (split != null && split.Length == 2 && split[0] == fieldName) { int i = -1; if (int.TryParse(split[1], out i)) { return(new { index = i, port = x }); } } return(new { index = -1, port = (XNode.NodePort)null }); }).Where(x => x.port != null); List <XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); ReorderableList list = null; Dictionary <string, ReorderableList> rlc; if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) { if (!rlc.TryGetValue(fieldName, out list)) { list = null; } } // If a ReorderableList isn't cached for this array, do so. if (list == null) { SerializedProperty arrayData = serializedObject.FindProperty(fieldName); list = CreateReorderableList(fieldName, dynamicPorts, 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 <string, ReorderableList>() { { fieldName, list } }); } } list.list = dynamicPorts; list.DoLayoutList(); }
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 <ReorderableList> onCreation = null) { DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation); }
/// <summary> /// Add items for the context menu when right-clicking this node. /// Override to add custom menu items. /// </summary> /// <param name="menu"></param> /// <param name="compatibleType">Use it to filter only nodes with ports value type, compatible with this type</param> /// <param name="direction">Direction of the compatiblity</param> 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); Type[] nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(type => GetNodeMenuOrder(type)).ToArray(); if (compatibleType != null && NodeEditorPreferences.GetSettings().createFilter) { nodeTypes = NodeEditorUtilities.GetCompatibleNodesTypes(NodeEditorReflection.nodeTypes, compatibleType, direction).ToArray(); } for (int i = 0; i < nodeTypes.Length; i++) { Type type = nodeTypes[i]; //Get node context menu path string path = GetNodeMenuName(type); if (string.IsNullOrEmpty(path)) continue; // Check if user is allowed to add more of given node type XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; bool disallowed = false; if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) { int typeCount = target.nodes.Count(x => x.GetType() == type); if (typeCount >= disallowAttrib.max) disallowed = true; } // Add node entry to context menu if (disallowed) menu.AddItem(new GUIContent(path), false, null); else menu.AddItem(new GUIContent(path), false, () => { XNode.Node node = CreateNode(type, pos); NodeEditorWindow.current.AutoConnect(node); }); } menu.AddSeparator(""); if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos)); else menu.AddDisabledItem(new GUIContent("Paste")); menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences()); menu.AddCustomContextMenuItems(target); }
/// <summary> /// Filter only node types that contains some port value type compatible with an given type /// </summary> /// <param name="nodeTypes">List with all nodes type to filter</param> /// <param name="compatibleType">Compatible Type to Filter</param> /// <returns>Return Only Node Types with ports compatible, or an empty list</returns> public static List <Type> GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { //Result List List <Type> filteredTypes = new List <Type>(); //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); }
public override void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { base.AddContextMenuItems(menu); var graph = target as IBrainGraph; menu.AddSeparator(""); if (graph.IsNodeCollapseModeOn) { menu.AddItem(new GUIContent("Disable Node Collapsing"), false, () => graph.DisableNodeCollapse()); } else { menu.AddItem(new GUIContent("Enable Node Collapsing"), false, () => graph.EnableNodeCollapse()); } }
/// <summary> Draw an editable list of instance ports. Port names are named as "[fieldName] [index]" </summary> /// <param name="fieldName">Supply a list for editable values</param> /// <param name="type">Value type of added instance ports</param> /// <param name="serializedObject">The serializedObject of the node</param> /// <param name="connectionType">Connection type of added instance ports</param> public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple) { XNode.Node node = serializedObject.targetObject as XNode.Node; SerializedProperty arrayData = serializedObject.FindProperty(fieldName); bool hasArrayData = arrayData != null && arrayData.isArray; int arraySize = hasArrayData ? arrayData.arraySize : 0; Predicate <string> isMatchingInstancePort = x => { string[] split = x.Split(' '); if (split != null && split.Length == 2) { return(split[0] == fieldName); } else { return(false); } }; List <XNode.NodePort> instancePorts = node.InstancePorts.Where(x => isMatchingInstancePort(x.fieldName)).OrderBy(x => x.fieldName).ToList(); for (int i = 0; i < instancePorts.Count(); i++) { GUILayout.BeginHorizontal(); // 'Remove' button if (GUILayout.Button("-", GUILayout.Width(20))) { // Clear the removed ports connections instancePorts[i].ClearConnections(); // Move following connections one step up to replace the missing connection for (int k = i + 1; k < instancePorts.Count(); k++) { for (int j = 0; j < instancePorts[k].ConnectionCount; j++) { XNode.NodePort other = instancePorts[k].GetConnection(j); instancePorts[k].Disconnect(other); instancePorts[k - 1].Connect(other); } } // Remove the last instance port, to avoid messing up the indexing node.RemoveInstancePort(instancePorts[instancePorts.Count() - 1].fieldName); serializedObject.Update(); EditorUtility.SetDirty(node); if (hasArrayData) { arrayData.DeleteArrayElementAtIndex(i); arraySize--; // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues if (instancePorts.Count <= arraySize) { while (instancePorts.Count <= arraySize) { arrayData.DeleteArrayElementAtIndex(--arraySize); } Debug.LogWarning("Array size exceeded instance ports size. Excess items removed."); } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); } i--; GUILayout.EndHorizontal(); } else { if (hasArrayData) { if (i < arraySize) { SerializedProperty itemData = arrayData.GetArrayElementAtIndex(i); if (itemData != null) { EditorGUILayout.PropertyField(itemData, new GUIContent(ObjectNames.NicifyVariableName(fieldName) + " " + i), true); } else { EditorGUILayout.LabelField("[Missing array data]"); } } else { EditorGUILayout.LabelField("[Out of bounds]"); } } else { EditorGUILayout.LabelField(instancePorts[i].fieldName); } GUILayout.EndHorizontal(); NodeEditorGUILayout.AddPortField(node.GetPort(instancePorts[i].fieldName)); } // GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); // 'Add' button if (GUILayout.Button("+", GUILayout.Width(20))) { string newName = fieldName + " 0"; 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); } serializedObject.Update(); EditorUtility.SetDirty(node); if (hasArrayData) { arrayData.InsertArrayElementAtIndex(arraySize); } serializedObject.ApplyModifiedProperties(); } GUILayout.EndHorizontal(); }