/// <summary> Draws an input and an output port on the same line </summary> public static void PortPair(xNode.NodePort input, xNode.NodePort output) { GUILayout.BeginHorizontal(); NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0)); NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0)); GUILayout.EndHorizontal(); }
/// <summary> Returned gradient is used to color noodles </summary> /// <param name="output"> The output this noodle comes from. Never null. </param> /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> public virtual Gradient GetNoodleGradient(xNode.NodePort output, xNode.NodePort input) { Gradient grad = new Gradient(); // If dragging the noodle, draw solid, slightly transparent if (input == null) { Color a = GetTypeColor(output.ValueType); grad.SetKeys( new GradientColorKey[] { new GradientColorKey(a, 0f) }, new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f) } ); } // If normal, draw gradient fading from one input color to the other else { Color a = GetTypeColor(output.ValueType); Color b = GetTypeColor(input.ValueType); // If any port is hovered, tint white if (window.hoveredPort == output || window.hoveredPort == input) { a = Color.Lerp(a, Color.white, 0.8f); b = Color.Lerp(b, Color.white, 0.8f); } grad.SetKeys( new GradientColorKey[] { new GradientColorKey(a, 0f), new GradientColorKey(b, 1f) }, new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) } ); } return(grad); }
/// <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> Add a port field to previous layout element. </summary> public static void AddPortField(xNode.NodePort port) { if (port == null) { return; } Rect rect = new Rect(); // 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(); float paddingLeft = NodeEditorResources.styles.inputPort.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 += NodeEditorResources.styles.outputPort.padding.right; rect.position = rect.position + new Vector2(rect.width, 0); } rect.size = new Vector2(16, 16); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); // Register the handle position Vector2 portPos = rect.center; NodeEditor.portPositions[port] = portPos; }
/// <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> Override to display custom tooltips </summary> public virtual string GetPortTooltip(xNode.NodePort port) { Type portType = port.ValueType; string tooltip = ""; tooltip = portType.PrettyName(); if (port.IsOutput) { object obj = port.node.GetValue(port); tooltip += " = " + (obj != null ? obj.ToString() : "null"); } return(tooltip); }
private void OnEnable() { // Reload portConnectionPoints if there are any int length = _references.Length; if (length == _rects.Length) { for (int i = 0; i < length; i++) { xNode.NodePort nodePort = _references[i].GetNodePort(); if (nodePort != null) { _portConnectionPoints.Add(nodePort, _rects[i]); } } } }
/// <summary> Show right-click context menu for hovered port </summary> 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($"Disconnect({name})"), false, () => hoveredPort.Disconnect(index)); } contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections()); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } }
/// <summary> Is this port part of a DynamicPortList? </summary> public static bool IsDynamicPortListPort(xNode.NodePort port) { string[] parts = port.fieldName.Split(' '); if (parts.Length != 2) { return(false); } Dictionary <string, ReorderableList> cache; if (reorderableListCache.TryGetValue(port.node, out cache)) { ReorderableList list; if (cache.TryGetValue(parts[0], out list)) { return(true); } } return(false); }
/// <summary> Make a simple port field. </summary> public static void PortField(Vector2 position, xNode.NodePort port) { if (port == null) { return; } Rect rect = new Rect(position, new Vector2(16, 16)); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); // Register the handle position Vector2 portPos = rect.center; NodeEditor.portPositions[port] = portPos; }
/// <summary> Make a simple port field. </summary> public static void PortField(GUIContent label, xNode.NodePort port, params GUILayoutOption[] options) { if (port == null) { return; } if (options == null) { options = new GUILayoutOption[] { GUILayout.MinWidth(30) } } ; Vector2 position = Vector3.zero; GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName)); // 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) { // Display a label EditorGUILayout.LabelField(content, options); Rect rect = GUILayoutUtility.GetLastRect(); float paddingLeft = NodeEditorResources.styles.inputPort.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) { // Display a label EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options); Rect rect = GUILayoutUtility.GetLastRect(); rect.width += NodeEditorResources.styles.outputPort.padding.right; position = rect.position + new Vector2(rect.width, 0); } PortField(position, port); }
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); }
public static bool IsInstancePortListPort(xNode.NodePort port) { return(IsDynamicPortListPort(port)); }
/// <summary> Returned color is used to color ports </summary> public virtual Color GetPortColor(xNode.NodePort port) { return(GetTypeColor(port.ValueType)); }
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; }
/// <summary> Make a simple port field. </summary> public static void PortField(xNode.NodePort port, params GUILayoutOption[] options) { PortField(null, port, options); }
/// <summary> The returned color is used to color the background of the door. /// Usually used for outer edge effect </summary> public virtual Color GetPortBackgroundColor(xNode.NodePort port) { return(Color.gray); }
/// <summary> Draws all connections </summary> public void DrawConnections() { Vector2 mousePos = Event.current.mousePosition; List <RerouteReference> selection = this.preBoxSelectionReroute != null ? new List <RerouteReference>(this.preBoxSelectionReroute) : new List <RerouteReference>(); this.hoveredReroute = new RerouteReference(); List <Vector2> gridPoints = new List <Vector2>(2); Color col = GUI.color; foreach (xNode.Node node in this.graph.nodes) { //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. if (node == null) { continue; } // Draw full connections and output > reroute foreach (xNode.NodePort output in node.Outputs) { //Needs cleanup. Null checks are ugly if (!this._portConnectionPoints.TryGetValue(output, out var fromRect)) { continue; } Color portColor = this.graphEditor.GetPortColor(output); for (int k = 0; k < output.ConnectionCount; k++) { xNode.NodePort input = output.GetConnection(k); Gradient noodleGradient = this.graphEditor.GetNoodleGradient(output, input); float noodleThickness = this.graphEditor.GetNoodleThickness(output, input); NoodlePath noodlePath = this.graphEditor.GetNoodlePath(output, input); NoodleStroke noodleStroke = this.graphEditor.GetNoodleStroke(output, input); // Error handling if (input == null) { continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return. } if (!input.IsConnectedTo(output)) { input.Connect(output); } if (!this._portConnectionPoints.TryGetValue(input, out var toRect)) { continue; } List <Vector2> reroutePoints = output.GetReroutePoints(k); gridPoints.Clear(); gridPoints.Add(fromRect.center); gridPoints.AddRange(reroutePoints); gridPoints.Add(toRect.center); this.DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints); // Loop through reroute points again and draw the points for (int i = 0; i < reroutePoints.Count; i++) { RerouteReference rerouteRef = new RerouteReference(output, k, i); // Draw reroute point at position Rect rect = new Rect(reroutePoints[i], new Vector2(12, 12)); rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6); rect = this.GridToWindowRect(rect); // Draw selected reroute points with an outline if (this.selectedReroutes.Contains(rerouteRef)) { GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUI.DrawTexture(rect, NodeEditorResources.dotOuter); } GUI.color = portColor; GUI.DrawTexture(rect, NodeEditorResources.dot); if (rect.Overlaps(this.selectionBox)) { selection.Add(rerouteRef); } if (rect.Contains(mousePos)) { this.hoveredReroute = rerouteRef; } } } } } GUI.color = col; if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) { this.selectedReroutes = selection; } }
/// <summary> Make a field for a serialized property. Manual node port override. </summary> public static void PropertyField(SerializedProperty property, xNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { PropertyField(property, null, port, includeChildren, options); }
public void Controls() { this.wantsMouseMove = true; var e = Event.current; switch (e.type) { case EventType.DragUpdated: case EventType.DragPerform: DragAndDrop.visualMode = DragAndDropVisualMode.Generic; if (e.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); this.graphEditor.OnDropObjects(DragAndDrop.objectReferences); } break; case EventType.MouseMove: //Keyboard commands will not get correct mouse position from Event this.lastMousePosition = e.mousePosition; break; case EventType.ScrollWheel: var oldZoom = this.zoom; if (e.delta.y > 0) { this.zoom += 0.1f * this.zoom; } else { this.zoom -= 0.1f * this.zoom; } if (NodeEditorPreferences.GetSettings().zoomToMouse) { this.panOffset += (1 - oldZoom / this.zoom) * (this.WindowToGridPosition(e.mousePosition) + this.panOffset); } break; case EventType.MouseDrag: switch (e.button) { case 0: { if (this.IsDraggingPort) { // Set target even if we can't connect, so as to prevent auto-conn menu from opening erroneously if (this.IsHoveringPort && this.hoveredPort.IsInput && !this.draggedOutput.IsConnectedTo(this.hoveredPort)) { this.draggedOutputTarget = this.hoveredPort; } else { this.draggedOutputTarget = null; } this.Repaint(); } else if (currentActivity == NodeActivity.HoldNode) { this.RecalculateDragOffsets(e); currentActivity = NodeActivity.DragNode; this.Repaint(); } switch (currentActivity) { case NodeActivity.DragNode: { // Holding ctrl inverts grid snap var gridSnap = NodeEditorPreferences.GetSettings().gridSnap; if (e.control) { gridSnap = !gridSnap; } var mousePos = this.WindowToGridPosition(e.mousePosition); // Move selected nodes with offset for (var i = 0; i < Selection.objects.Length; i++) { if (!(Selection.objects[i] is xNode.Node node)) { continue; } Undo.RecordObject(node, "Moved Node"); var initial = node.position; node.position = mousePos + dragOffset[i]; if (gridSnap) { node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8; node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8; } // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame. var offset = node.position - initial; if (!(offset.sqrMagnitude > 0)) { continue; } foreach (var output in node.Outputs) { if (this.portConnectionPoints.TryGetValue(output, out var rect)) { rect.position += offset; this.portConnectionPoints[output] = rect; } } foreach (var input in node.Inputs) { if (this.portConnectionPoints.TryGetValue(input, out var rect)) { rect.position += offset; this.portConnectionPoints[input] = rect; } } } // Move selected reroutes with offset for (var i = 0; i < this.selectedReroutes.Count; i++) { var pos = mousePos + dragOffset[Selection.objects.Length + i]; if (gridSnap) { pos.x = (Mathf.Round(pos.x / 16) * 16); pos.y = (Mathf.Round(pos.y / 16) * 16); } this.selectedReroutes[i].SetPoint(pos); } this.Repaint(); break; } case NodeActivity.HoldGrid: currentActivity = NodeActivity.DragGrid; this.preBoxSelection = Selection.objects; this.preBoxSelectionReroute = this.selectedReroutes.ToArray(); this.dragBoxStart = this.WindowToGridPosition(e.mousePosition); this.Repaint(); break; case NodeActivity.DragGrid: { var boxStartPos = this.GridToWindowPosition(this.dragBoxStart); var boxSize = e.mousePosition - boxStartPos; if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } this.selectionBox = new Rect(boxStartPos, boxSize); this.Repaint(); break; } } break; } case 1: case 2: { //check drag threshold for larger screens if (e.delta.magnitude > this.dragThreshold) { this.panOffset += e.delta * this.zoom; isPanning = true; } break; } } break; case EventType.MouseDown: this.Repaint(); if (e.button == 0) { this.draggedOutputReroutes.Clear(); if (this.IsHoveringPort) { if (this.hoveredPort.IsOutput) { this.draggedOutput = this.hoveredPort; this.autoConnectOutput = this.hoveredPort; } else { this.hoveredPort.VerifyConnections(); this.autoConnectOutput = null; if (this.hoveredPort.IsConnected) { var node = this.hoveredPort.node; var output = this.hoveredPort.Connection; var outputConnectionIndex = output.GetConnectionIndex(this.hoveredPort); this.draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex); this.hoveredPort.Disconnect(output); this.draggedOutput = output; this.draggedOutputTarget = this.hoveredPort; if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } } } } else if (this.IsHoveringNode && this.IsHoveringTitle(this.hoveredNode)) { // If mousedown on node header, select or deselect if (!Selection.Contains(this.hoveredNode)) { this.SelectNode(this.hoveredNode, e.control || e.shift); if (!e.control && !e.shift) { this.selectedReroutes.Clear(); } } else if (e.control || e.shift) { this.DeselectNode(this.hoveredNode); } // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown. this.isDoubleClick = (e.clickCount == 2); e.Use(); currentActivity = NodeActivity.HoldNode; } else if (this.IsHoveringReroute) { // If reroute isn't selected if (!this.selectedReroutes.Contains(this.hoveredReroute)) { // Add it if (e.control || e.shift) { this.selectedReroutes.Add(this.hoveredReroute); } // Select it else { this.selectedReroutes = new List <RerouteReference>() { this.hoveredReroute }; Selection.activeObject = null; } } // Deselect else if (e.control || e.shift) { this.selectedReroutes.Remove(this.hoveredReroute); } e.Use(); currentActivity = NodeActivity.HoldNode; } // If mousedown on grid background, deselect all else if (!this.IsHoveringNode) { currentActivity = NodeActivity.HoldGrid; if (!e.control && !e.shift) { this.selectedReroutes.Clear(); Selection.activeObject = null; } } } break; case EventType.MouseUp: if (e.button == 0) { //Port drag release if (this.IsDraggingPort) { // If connection is valid, save it if (this.draggedOutputTarget != null && this.draggedOutput.CanConnectTo(this.draggedOutputTarget)) { var node = this.draggedOutputTarget.node; if (this.graph.nodes.Count != 0) { this.draggedOutput.Connect(this.draggedOutputTarget); } // ConnectionIndex can be -1 if the connection is removed instantly after creation var connectionIndex = this.draggedOutput.GetConnectionIndex(this.draggedOutputTarget); if (connectionIndex != -1) { this.draggedOutput.GetReroutePoints(connectionIndex).AddRange(this.draggedOutputReroutes); if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } EditorUtility.SetDirty(this.graph); } } // Open context menu for auto-connection if there is no target node else if (this.draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate&& this.autoConnectOutput != null) { var menu = new GenericMenu(); this.graphEditor.AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } //Release dragged connection this.draggedOutput = null; this.draggedOutputTarget = null; EditorUtility.SetDirty(this.graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } else if (currentActivity == NodeActivity.DragNode) { var nodes = Selection.objects.Where(x => x is xNode.Node).Select(x => x as xNode.Node); foreach (var node in nodes) { EditorUtility.SetDirty(node); } if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } else if (!this.IsHoveringNode) { // If click outside node, release field focus if (!isPanning) { EditorGUI.FocusTextInControl(null); EditorGUIUtility.editingTextField = false; } if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } // If click node header, select it. if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) { this.selectedReroutes.Clear(); this.SelectNode(this.hoveredNode, false); // Double click to center node if (this.isDoubleClick) { var nodeDimension = this.nodeSizes.ContainsKey(this.hoveredNode) ? this.nodeSizes[this.hoveredNode] / 2 : Vector2.zero; this.panOffset = -this.hoveredNode.position - nodeDimension; } } // If click reroute, select it. if (this.IsHoveringReroute && !(e.control || e.shift)) { this.selectedReroutes = new List <RerouteReference>() { this.hoveredReroute }; Selection.activeObject = null; } this.Repaint(); currentActivity = NodeActivity.Idle; } else if (e.button == 1 || e.button == 2) { if (!isPanning) { if (this.IsDraggingPort) { this.draggedOutputReroutes.Add(this.WindowToGridPosition(e.mousePosition)); } else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && this.selectedReroutes.Count == 1) { this.selectedReroutes[0].InsertPoint(this.selectedReroutes[0].GetPoint()); this.selectedReroutes[0] = new RerouteReference(this.selectedReroutes[0].port, this.selectedReroutes[0].connectionIndex, this.selectedReroutes[0].pointIndex + 1); } else if (this.IsHoveringReroute) { this.ShowRerouteContextMenu(this.hoveredReroute); } else if (this.IsHoveringPort) { this.ShowPortContextMenu(this.hoveredPort); } else if (this.IsHoveringNode && this.IsHoveringTitle(this.hoveredNode)) { if (!Selection.Contains(this.hoveredNode)) { this.SelectNode(this.hoveredNode, false); } this.autoConnectOutput = null; var menu = new GenericMenu(); NodeEditor.GetEditor(this.hoveredNode, this).AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places. } else if (!this.IsHoveringNode) { this.autoConnectOutput = null; var menu = new GenericMenu(); this.graphEditor.AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } } isPanning = false; } // Reset DoubleClick this.isDoubleClick = false; break; case EventType.KeyDown: if (EditorGUIUtility.editingTextField) { break; } else if (e.keyCode == KeyCode.F) { this.Home(); } if (NodeEditorUtilities.IsMac()) { if (e.keyCode == KeyCode.Return) { this.RenameSelectedNode(); } } else { if (e.keyCode == KeyCode.F2) { this.RenameSelectedNode(); } } if (e.keyCode == KeyCode.A) { if (Selection.objects.Any(x => this.graph.nodes.Contains(x as xNode.Node))) { foreach (var node in this.graph.nodes) { this.DeselectNode(node); } } else { foreach (var node in this.graph.nodes) { this.SelectNode(node, true); } } this.Repaint(); } break; case EventType.ValidateCommand: case EventType.ExecuteCommand: if (e.commandName == "SoftDelete") { if (e.type == EventType.ExecuteCommand) { this.RemoveSelectedNodes(); } e.Use(); } else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") { if (e.type == EventType.ExecuteCommand) { this.RemoveSelectedNodes(); } e.Use(); } else if (e.commandName == "Duplicate") { if (e.type == EventType.ExecuteCommand) { this.DuplicateSelectedNodes(); } e.Use(); } else if (e.commandName == "Copy") { if (e.type == EventType.ExecuteCommand) { this.CopySelectedNodes(); } e.Use(); } else if (e.commandName == "Paste") { if (e.type == EventType.ExecuteCommand) { this.PasteNodes(this.WindowToGridPosition(this.lastMousePosition)); } e.Use(); } this.Repaint(); break; case EventType.Ignore: // If release mouse outside window if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) { this.Repaint(); currentActivity = NodeActivity.Idle; } break; } }
/// <summary> Returned float is used for noodle thickness </summary> /// <param name="output"> The output this noodle comes from. Never null. </param> /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> public virtual float GetNoodleThickness(xNode.NodePort output, xNode.NodePort input) { return(NodeEditorPreferences.GetSettings().noodleThickness); }
public NodePortReference(xNode.NodePort nodePort) { _node = nodePort.node; _name = nodePort.fieldName; }
/// <summary> Make a field for a serialized property. Manual node port override. </summary> public static void PropertyField(SerializedProperty property, GUIContent label, xNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { if (property == null) { throw new NullReferenceException(); } // If property is not a port, display a regular property field if (port == null) { EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); } else { Rect rect = new Rect(); List <PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name); // 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) { // Get data from [Input] attribute xNode.Node.ShowBackingValue showBacking = xNode.Node.ShowBackingValue.Unconnected; xNode.Node.InputAttribute inputAttribute; bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) { dynamicPortList = inputAttribute.dynamicPortList; showBacking = inputAttribute.backingValue; } bool usePropertyAttributes = dynamicPortList || showBacking == xNode.Node.ShowBackingValue.Never || (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); } else { spacePadding += (attr as SpaceAttribute).height; } } else if (attr is HeaderAttribute) { if (usePropertyAttributes) { //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; 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; } } if (dynamicPortList) { Type type = GetType(property); xNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : xNode.Node.ConnectionType.Multiple; DynamicPortList(property.name, type, property.serializedObject, port.Direction, connectionType); return; } 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, 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, tooltip)); break; case xNode.Node.ShowBackingValue.Always: // Display an editable property field EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; } rect = GUILayoutUtility.GetLastRect(); float paddingLeft = NodeEditorResources.styles.inputPort.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 xNode.Node.ShowBackingValue showBacking = xNode.Node.ShowBackingValue.Unconnected; xNode.Node.OutputAttribute outputAttribute; bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) { dynamicPortList = outputAttribute.dynamicPortList; showBacking = outputAttribute.backingValue; } bool usePropertyAttributes = dynamicPortList || showBacking == xNode.Node.ShowBackingValue.Never || (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); } else { spacePadding += (attr as SpaceAttribute).height; } } else if (attr is HeaderAttribute) { if (usePropertyAttributes) { //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; 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; } } if (dynamicPortList) { Type type = GetType(property); xNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : xNode.Node.ConnectionType.Multiple; DynamicPortList(property.name, type, property.serializedObject, port.Direction, connectionType); return; } 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, 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, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); break; case xNode.Node.ShowBackingValue.Always: // Display an editable property field EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; } rect = GUILayoutUtility.GetLastRect(); rect.width += NodeEditorResources.styles.outputPort.padding.right; rect.position = rect.position + new Vector2(rect.width, spacePadding); } rect.size = new Vector2(16, 16); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); // Register the handle position Vector2 portPos = rect.center; NodeEditor.portPositions[port] = portPos; } }
public RerouteReference(xNode.NodePort port, int connectionIndex, int pointIndex) { this.port = port; this.connectionIndex = connectionIndex; this.pointIndex = pointIndex; }
public virtual NoodleStroke GetNoodleStroke(xNode.NodePort output, xNode.NodePort input) { return(NodeEditorPreferences.GetSettings().noodleStroke); }