public void DeselectNode(xNode.Node node) { List <Object> selection = new List <Object>(Selection.objects); selection.Remove(node); Selection.objects = selection.ToArray(); }
private bool ShouldBeCulled(xNode.Node node) { Vector2 nodePos = this.GridToWindowPositionNoClipped(node.position); if (nodePos.x / this._zoom > this.position.width) { return(true); // Right } else if (nodePos.y / this._zoom > this.position.height) { return(true); // Bottom } else if (this.nodeSizes.ContainsKey(node)) { Vector2 size = this.nodeSizes[node]; if (nodePos.x + size.x < 0) { return(true); // Left } else if (nodePos.y + size.y < 0) { return(true); // Top } } return(false); }
/// <summary> Attempt to connect dragged output to target node </summary> public void AutoConnect(xNode.Node node) { if (this.autoConnectOutput == null) { return; } // Find input port of same type xNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == this.autoConnectOutput.ValueType); // Fallback to input port if (inputPort == null) { inputPort = node.Ports.FirstOrDefault(x => x.IsInput); } // Autoconnect if connection is compatible if (inputPort != null && inputPort.CanConnectTo(this.autoConnectOutput)) { this.autoConnectOutput.Connect(inputPort); } // Save changes EditorUtility.SetDirty(this.graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } this.autoConnectOutput = null; }
/// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary> public void MoveNodeToTop(xNode.Node node) { int index; while ((index = this.graph.nodes.IndexOf(node)) != this.graph.nodes.Count - 1) { this.graph.nodes[index] = this.graph.nodes[index + 1]; this.graph.nodes[index + 1] = node; } }
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) { if (property == null) { throw new NullReferenceException(); } xNode.Node node = property.serializedObject.targetObject as xNode.Node; xNode.NodePort port = node.GetPort(property.name); PropertyField(property, label, port, includeChildren); }
/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> public virtual void AddContextMenuItems(GenericMenu menu) { Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); var nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(type => GetNodeMenuOrder(type)).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> Creates a copy of the original node in the graph </summary> public virtual xNode.Node CopyNode(xNode.Node original) { Undo.RecordObject(target, "Duplicate Node"); xNode.Node node = target.CopyNode(original); Undo.RegisterCreatedObjectUndo(node, "Duplicate Node"); node.name = original.name; AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } return(node); }
public void SelectNode(xNode.Node node, bool add) { if (add) { List <Object> selection = new List <Object>(Selection.objects); selection.Add(node); Selection.objects = selection.ToArray(); } else { Selection.objects = new Object[] { node } }; }
/// <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(); node.UpdatePorts(); 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(); }
/// <summary> Automatically delete Node sub-assets before deleting their script. /// This is important to do, because you can't delete null sub assets. /// <para/> For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete </summary> private static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { // Skip processing anything without the .cs extension if (Path.GetExtension(path) != ".cs") { return(AssetDeleteResult.DidNotDelete); } // Get the object that is requested for deletion UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath <UnityEngine.Object> (path); // If we aren't deleting a script, return if (!(obj is UnityEditor.MonoScript)) { return(AssetDeleteResult.DidNotDelete); } // Check script type. Return if deleting a non-node script UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; System.Type scriptType = script.GetClass(); if (scriptType == null || (scriptType != typeof(xNode.Node) && !scriptType.IsSubclassOf(typeof(xNode.Node)))) { return(AssetDeleteResult.DidNotDelete); } // Find all ScriptableObjects using this script string[] guids = AssetDatabase.FindAssets("t:" + scriptType); for (int i = 0; i < guids.Length; i++) { string assetpath = AssetDatabase.GUIDToAssetPath(guids[i]); Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); for (int k = 0; k < objs.Length; k++) { xNode.Node node = objs[k] as xNode.Node; if (node.GetType() == scriptType) { if (node != null && node.graph != null) { // Delete the node and notify the user Debug.LogWarning(node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); node.graph.RemoveNode(node); } } } } // We didn't actually delete the script. Tell the internal system to carry on with normal deletion procedure return(AssetDeleteResult.DidNotDelete); }
/// <summary> Initiate a rename on the currently selected node </summary> public void RenameSelectedNode() { if (Selection.objects.Length == 1 && Selection.activeObject is xNode.Node) { xNode.Node node = Selection.activeObject as xNode.Node; Vector2 size; if (this.nodeSizes.TryGetValue(node, out size)) { RenamePopup.Show(Selection.activeObject, size.x); } else { RenamePopup.Show(Selection.activeObject); } } }
/// <summary> Return false for nodes that can't be removed </summary> public virtual bool CanRemove(xNode.Node node) { // Check graph attributes to see if this node is required Type graphType = target.GetType(); xNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( graphType.GetCustomAttributes(typeof(xNode.NodeGraph.RequireNodeAttribute), true), x => x as xNode.NodeGraph.RequireNodeAttribute); if (attribs.Any(x => x.Requires(node.GetType()))) { if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) { return(false); } } return(true); }
private void RecalculateDragOffsets(Event current) { dragOffset = new Vector2[Selection.objects.Length + this.selectedReroutes.Count]; // Selected nodes for (int i = 0; i < Selection.objects.Length; i++) { if (Selection.objects[i] is xNode.Node) { xNode.Node node = Selection.objects[i] as xNode.Node; dragOffset[i] = node.position - this.WindowToGridPosition(current.mousePosition); } } // Selected reroutes for (int i = 0; i < this.selectedReroutes.Count; i++) { dragOffset[Selection.objects.Length + i] = this.selectedReroutes[i].GetPoint() - this.WindowToGridPosition(current.mousePosition); } }
/// <summary> Remove nodes in the graph in Selection.objects</summary> public void RemoveSelectedNodes() { // We need to delete reroutes starting at the highest point index to avoid shifting indices this.selectedReroutes = this.selectedReroutes.OrderByDescending(x => x.pointIndex).ToList(); for (int i = 0; i < this.selectedReroutes.Count; i++) { this.selectedReroutes[i].RemovePoint(); } this.selectedReroutes.Clear(); foreach (UnityEngine.Object item in Selection.objects) { if (item is xNode.Node) { xNode.Node node = item as xNode.Node; this.graphEditor.RemoveNode(node); } } }
bool IsHoveringTitle(xNode.Node node) { Vector2 mousePos = Event.current.mousePosition; //Get node position Vector2 nodePos = this.GridToWindowPosition(node.position); float width; Vector2 size; if (this.nodeSizes.TryGetValue(node, out size)) { width = size.x; } else { width = 200; } Rect windowRect = new Rect(nodePos, new Vector2(width / this.zoom, 30 / this.zoom)); return(windowRect.Contains(mousePos)); }
/// <summary> Create a node and save it in the graph asset </summary> public virtual xNode.Node CreateNode(Type type, Vector2 position) { Undo.RecordObject(target, "Create Node"); xNode.Node node = target.AddNode(type); Undo.RegisterCreatedObjectUndo(node, "Create Node"); node.position = position; if (node.name == null || node.name.Trim() == "") { node.name = NodeEditorUtilities.NodeDefaultName(type); } if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) { AssetDatabase.AddObjectToAsset(node, target); } if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } NodeEditorWindow.RepaintAll(); return(node); }
/// <summary> Safely remove a node and all its connections. </summary> public virtual void RemoveNode(xNode.Node node) { if (!CanRemove(node)) { return; } // Remove the node Undo.RecordObject(node, "Delete Node"); Undo.RecordObject(target, "Delete Node"); foreach (var port in node.Ports) { foreach (var conn in port.GetConnections()) { Undo.RecordObject(conn.node, "Delete Node"); } } target.RemoveNode(node); Undo.DestroyObjectImmediate(node); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } }
public NodePortReference(xNode.NodePort nodePort) { _node = nodePort.node; _name = nodePort.fieldName; }
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 && arrayData.propertyType != SerializedPropertyType.String) { 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) => { bool hasRect = false; bool hasNewRect = false; Rect rect = Rect.zero; Rect newRect = Rect.zero; // 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 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; } } // 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 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; } } // 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 && arrayData.propertyType != SerializedPropertyType.String) { 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); }
private void InsertDuplicateNodes(xNode.Node[] nodes, Vector2 topLeft) { if (nodes == null || nodes.Length == 0) { return; } // Get top-left node Vector2 topLeftNode = nodes.Select(x => x.position) .Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); Vector2 offset = topLeft - topLeftNode; UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length]; Dictionary <xNode.Node, xNode.Node> substitutes = new Dictionary <xNode.Node, xNode.Node>(); for (int i = 0; i < nodes.Length; i++) { xNode.Node srcNode = nodes[i]; if (srcNode == null) { continue; } // Check if user is allowed to add more of given node type xNode.Node.DisallowMultipleNodesAttribute disallowAttrib; Type nodeType = srcNode.GetType(); if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) { int typeCount = this.graph.nodes.Count(x => x.GetType() == nodeType); if (typeCount >= disallowAttrib.max) { continue; } } xNode.Node newNode = this.graphEditor.CopyNode(srcNode); substitutes.Add(srcNode, newNode); newNode.position = srcNode.position + offset; newNodes[i] = newNode; } // Walk through the selected nodes again, recreate connections, using the new nodes for (int i = 0; i < nodes.Length; i++) { xNode.Node srcNode = nodes[i]; if (srcNode == null) { continue; } foreach (xNode.NodePort port in srcNode.Ports) { for (int c = 0; c < port.ConnectionCount; c++) { xNode.NodePort inputPort = port.Direction == xNode.NodePort.IO.Input ? port : port.GetConnection(c); xNode.NodePort outputPort = port.Direction == xNode.NodePort.IO.Output ? port : port.GetConnection(c); xNode.Node newNodeIn, newNodeOut; if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { newNodeIn.UpdatePorts(); newNodeOut.UpdatePorts(); inputPort = newNodeIn.GetInputPort(inputPort.fieldName); outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); } if (!inputPort.IsConnectedTo(outputPort)) { inputPort.Connect(outputPort); } } } } EditorUtility.SetDirty(this.graph); // Select the new nodes Selection.objects = newNodes; }