public void Save() { if (AssetDatabase.Contains(graph)) { EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } else { SaveAs(); } }
/// <summary> Draw a connection as we are dragging it </summary> public void DrawDraggedConnection() { if (IsDraggingPort) { if (!_portConnectionPoints.ContainsKey(draggedOutput)) { return; } Vector2 from = _portConnectionPoints[draggedOutput].center; Vector2 to = draggedOutputTarget != null ? portConnectionPoints[draggedOutputTarget].center : WindowToGridPosition(Event.current.mousePosition); Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType); col.a = 0.6f; DrawConnection(from, to, col); } }
/// <summary> Returns color for target node </summary> public virtual Color GetTint() { // Try get color from [NodeTint] attribute Type type = target.GetType(); Color color; if (type.TryGetAttributeTint(out color)) { return(color); } // Return default color (grey) else { return(NodeEditorPreferences.GetSettings().tintColor); } }
/// <summary> Create a node and save it in the graph asset </summary> public virtual XNode.Node CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); node.position = position; if (node.name == null || node.name.Trim() == "") { node.name = NodeEditorUtilities.NodeDefaultName(type); } AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } NodeEditorWindow.RepaintAll(); return(node); }
private void DrawTooltip() { if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips&& graphEditor != null) { string tooltip = graphEditor.GetPortTooltip(hoveredPort); if (string.IsNullOrEmpty(tooltip)) { return; } GUIContent content = new GUIContent(tooltip); Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); Rect rect = new Rect(Event.current.mousePosition - (size), size); EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); Repaint(); } }
void OnFocus() { current = this; ValidateGraphEditor(); var set = NodeEditorPreferences.GetSettings(); if (graphEditor != null && set != null && set.autoSave) { try { AssetDatabase.SaveAssets(); }catch (Exception ex) { Debug.LogWarning(ex.Message); } } }
/// <summary> Draw a connection as we are dragging it </summary> public void DrawDraggedConnection() { if (IsDraggingPort) { Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType); col.a = draggedOutputTarget != null ? 1.0f : 0.6f; Rect fromRect; if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) { return; } List <Vector2> gridPoints = new List <Vector2>(); gridPoints.Add(fromRect.center); for (int i = 0; i < draggedOutputReroutes.Count; i++) { gridPoints.Add(draggedOutputReroutes[i]); } if (draggedOutputTarget != null) { gridPoints.Add(portConnectionPoints[draggedOutputTarget].center); } else { gridPoints.Add(WindowToGridPosition(Event.current.mousePosition)); } DrawNoodle(col, gridPoints); Color bgcol = Color.black; Color frcol = col; bgcol.a = 0.6f; frcol.a = 0.6f; // Loop through reroute points again and draw the points for (int i = 0; i < draggedOutputReroutes.Count; i++) { // Draw reroute point at position Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16)); rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); rect = GridToWindowRect(rect); NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol); } } }
/// <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> Attempt to connect dragged output to target node </summary> public void AutoConnect(XNode.Node node) { if (autoConnectOutput != null) { // Find input port of same type XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType); // Fallback to input port if (inputPort == null) { inputPort = node.Ports.FirstOrDefault(x => x.IsInput); } // Autoconnect if (inputPort != null) { autoConnectOutput.Connect(inputPort); } // Save changes EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } autoConnectOutput = null; } if (autoConnectOutputLink != null) { var linkType = autoConnectOutputLink.Link.LinkType; var inputLinks = XNode.NodeDataCache.GetInputLinks(node.GetType()); var inputPort = inputLinks.FirstOrDefault(x => x.LinkType == linkType); inputPort = inputPort ?? inputLinks.FirstOrDefault(x => x.LinkType.IsAssignableFrom(linkType)); if (inputPort != null) { XNode.NodeLink link = autoConnectOutputLink.Connect(new XNode.NodeLinkPort(node, inputPort)); SelectObject(link, false); EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } autoConnectOutputLink = null; } }
private void DrawTooltip() { if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips) { Type type = hoveredPort.ValueType; GUIContent content = new GUIContent(); content.text = type.PrettyName(); if (hoveredPort.IsOutput) { object obj = hoveredPort.node.GetValue(hoveredPort); content.text += " = " + (obj != null ? obj.ToString() : "null"); } Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); Rect rect = new Rect(Event.current.mousePosition - (size), size); EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); Repaint(); } }
/// <summary> Safely remove a node and all its connections. </summary> public virtual void RemoveNode(XNode.Node 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 void CreateNode(Type type, Vector2 position) { XNode.Node node = graph.AddNode(type); node.position = position; node.name = UnityEditor.ObjectNames.NicifyVariableName(type.Name); //PT if (node is PTNode) { (node as PTNode).UpdatePreview(); } AssetDatabase.AddObjectToAsset(node, graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } Repaint(); }
private void OnDisable() { if (NodeEditorPreferences.GetSettings().autoSave) { AutoSave(); } // Cache portConnectionPoints before serialization starts int count = portConnectionPoints.Count; _references = new NodePortReference[count]; _rects = new Rect[count]; int index = 0; foreach (var portConnectionPoint in portConnectionPoints) { _references[index] = new NodePortReference(portConnectionPoint.Key); _rects[index] = portConnectionPoint.Value; index++; } }
/// <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> Create a node and save it in the graph asset </summary> public virtual void CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); node.position = position; if (string.IsNullOrEmpty(node.name)) { // Automatically remove redundant 'Node' postfix string typeName = type.Name; if (typeName.EndsWith("Node")) { typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); } node.name = UnityEditor.ObjectNames.NicifyVariableName(typeName); } AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } NodeEditorWindow.RepaintAll(); }
/// <summary> Draw a connection as we are dragging it </summary> public void DrawDraggedConnection() { if (IsDraggingPort) { Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType); Rect fromRect; if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) { return; } Vector2 from = fromRect.center; col.a = draggedOutputTarget != null ? 1.0f : 0.6f; Vector2 to = Vector2.zero; for (int i = 0; i < draggedOutputReroutes.Count; i++) { to = draggedOutputReroutes[i]; DrawConnection(from, to, col); from = to; } to = draggedOutputTarget != null ? portConnectionPoints[draggedOutputTarget].center : WindowToGridPosition(Event.current.mousePosition); DrawConnection(from, to, col); Color bgcol = Color.black; Color frcol = col; bgcol.a = 0.6f; frcol.a = 0.6f; // Loop through reroute points again and draw the points for (int i = 0; i < draggedOutputReroutes.Count; i++) { // Draw reroute point at position Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16)); rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); rect = GridToWindowRect(rect); NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol); } } }
/// <summary> Draws all connections </summary> public void DrawConnections() { foreach (Node node in 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; } foreach (NodePort output in node.Outputs) { //Needs cleanup. Null checks are ugly if (!portConnectionPoints.ContainsKey(output)) { continue; } Vector2 from = _portConnectionPoints[output].center; for (int k = 0; k < output.ConnectionCount; k++) { NodePort input = output.GetConnection(k); 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 (!_portConnectionPoints.ContainsKey(input)) { continue; } Vector2 to = _portConnectionPoints[input].center; DrawConnection(from, to, NodeEditorPreferences.GetTypeColor(output.ValueType)); } } } }
/// <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); }
public void SaveAs() { string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); if (string.IsNullOrEmpty(path)) { return; } else { XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath <XNode.NodeGraph>(path); if (existingGraph != null) { AssetDatabase.DeleteAsset(path); } AssetDatabase.CreateAsset(graph, path); EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } }
private void OnDisable() { // Cache portConnectionPoints before serialization starts int count = portConnectionPoints.Count; _references = new NodePortReference[count]; _rects = new Rect[count]; int index = 0; foreach (var portConnectionPoint in portConnectionPoints) { _references[index] = new NodePortReference(portConnectionPoint.Key); _rects[index] = portConnectionPoint.Value; index++; } if (!NodeEditorPreferences.GetSettings().autoSave) { if (EditorUtility.DisplayDialog("Save?", "Save asset?", "Save")) { AssetDatabase.SaveAssets(); } } }
public void Controls() { wantsMouseMove = true; Event e = Event.current; switch (e.type) { case EventType.DragUpdated: case EventType.DragPerform: DragAndDrop.visualMode = DragAndDropVisualMode.Generic; if (e.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); graphEditor.OnDropObjects(DragAndDrop.objectReferences); } break; case EventType.MouseMove: //Keyboard commands will not get correct mouse position from Event lastMousePosition = e.mousePosition; break; case EventType.ScrollWheel: float oldZoom = zoom; if (e.delta.y > 0) { zoom += 0.1f * zoom; } else { zoom -= 0.1f * zoom; } if (NodeEditorPreferences.GetSettings().zoomToMouse) { panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset); } break; case EventType.MouseDrag: if (e.button == 0) { if (IsDraggingPort) { if (IsHoveringPort && hoveredPort.IsInput && draggedOutput.CanConnectTo(hoveredPort)) { if (!draggedOutput.IsConnectedTo(hoveredPort)) { draggedOutputTarget = hoveredPort; } } else { draggedOutputTarget = null; } Repaint(); } else if (currentActivity == NodeActivity.HoldNode) { RecalculateDragOffsets(e); currentActivity = NodeActivity.DragNode; Repaint(); } if (currentActivity == NodeActivity.DragNode) { // Holding ctrl inverts grid snap bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; if (e.control) { gridSnap = !gridSnap; } Vector2 mousePos = WindowToGridPosition(e.mousePosition); // Move selected nodes with offset 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; Vector2 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. Vector2 offset = node.position - initial; if (offset.sqrMagnitude > 0) { foreach (XNode.NodePort output in node.Outputs) { Rect rect; if (portConnectionPoints.TryGetValue(output, out rect)) { rect.position += offset; portConnectionPoints[output] = rect; } } foreach (XNode.NodePort input in node.Inputs) { Rect rect; if (portConnectionPoints.TryGetValue(input, out rect)) { rect.position += offset; portConnectionPoints[input] = rect; } } } } } // Move selected reroutes with offset for (int i = 0; i < selectedReroutes.Count; i++) { Vector2 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); } selectedReroutes[i].SetPoint(pos); } Repaint(); } else if (currentActivity == NodeActivity.HoldGrid) { currentActivity = NodeActivity.DragGrid; preBoxSelection = Selection.objects; preBoxSelectionReroute = selectedReroutes.ToArray(); dragBoxStart = WindowToGridPosition(e.mousePosition); Repaint(); } else if (currentActivity == NodeActivity.DragGrid) { Vector2 boxStartPos = GridToWindowPosition(dragBoxStart); Vector2 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); } selectionBox = new Rect(boxStartPos, boxSize); Repaint(); } } else if (e.button == 1 || e.button == 2) { panOffset += e.delta * zoom; isPanning = true; } break; case EventType.MouseDown: Repaint(); if (e.button == 0) { draggedOutputReroutes.Clear(); if (IsHoveringPort) { if (hoveredPort.IsOutput) { draggedOutput = hoveredPort; autoConnectOutput = hoveredPort; } else { hoveredPort.VerifyConnections(); autoConnectOutput = null; if (hoveredPort.IsConnected) { XNode.Node node = hoveredPort.node; XNode.NodePort output = hoveredPort.Connection; int outputConnectionIndex = output.GetConnectionIndex(hoveredPort); draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex); hoveredPort.Disconnect(output); draggedOutput = output; draggedOutputTarget = hoveredPort; if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } } } } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { // If mousedown on node header, select or deselect if (!Selection.Contains(hoveredNode)) { SelectNode(hoveredNode, e.control || e.shift); if (!e.control && !e.shift) { selectedReroutes.Clear(); } } else if (e.control || e.shift) { DeselectNode(hoveredNode); } // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown. isDoubleClick = (e.clickCount == 2); e.Use(); currentActivity = NodeActivity.HoldNode; } else if (IsHoveringReroute) { // If reroute isn't selected if (!selectedReroutes.Contains(hoveredReroute)) { // Add it if (e.control || e.shift) { selectedReroutes.Add(hoveredReroute); } // Select it else { selectedReroutes = new List <RerouteReference>() { hoveredReroute }; Selection.activeObject = null; } } // Deselect else if (e.control || e.shift) { selectedReroutes.Remove(hoveredReroute); } e.Use(); currentActivity = NodeActivity.HoldNode; } // If mousedown on grid background, deselect all else if (!IsHoveringNode) { currentActivity = NodeActivity.HoldGrid; if (!e.control && !e.shift) { selectedReroutes.Clear(); Selection.activeObject = null; } } } break; case EventType.MouseUp: if (e.button == 0) { //Port drag release if (IsDraggingPort) { //If connection is valid, save it if (draggedOutputTarget != null) { XNode.Node node = draggedOutputTarget.node; if (graph.nodes.Count != 0) { draggedOutput.Connect(draggedOutputTarget); } // ConnectionIndex can be -1 if the connection is removed instantly after creation int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget); if (connectionIndex != -1) { draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes); if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } EditorUtility.SetDirty(graph); } } // Open context menu for auto-connection else if (NodeEditorPreferences.GetSettings().dragToCreate&& autoConnectOutput != null) { GenericMenu menu = new GenericMenu(); graphEditor.AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } //Release dragged connection draggedOutput = null; draggedOutputTarget = null; EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } else if (currentActivity == NodeActivity.DragNode) { IEnumerable <XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node); foreach (XNode.Node node in nodes) { EditorUtility.SetDirty(node); } if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } else if (!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)) { selectedReroutes.Clear(); SelectNode(hoveredNode, false); // Double click to center node if (isDoubleClick) { Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero; panOffset = -hoveredNode.position - nodeDimension; } } // If click reroute, select it. if (IsHoveringReroute && !(e.control || e.shift)) { selectedReroutes = new List <RerouteReference>() { hoveredReroute }; Selection.activeObject = null; } Repaint(); currentActivity = NodeActivity.Idle; } else if (e.button == 1 || e.button == 2) { if (!isPanning) { if (IsDraggingPort) { draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition)); } else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1) { selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint()); selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1); } else if (IsHoveringReroute) { ShowRerouteContextMenu(hoveredReroute); } else if (IsHoveringPort) { ShowPortContextMenu(hoveredPort); } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (!Selection.Contains(hoveredNode)) { SelectNode(hoveredNode, false); } autoConnectOutput = null; GenericMenu menu = new GenericMenu(); NodeEditor.GetEditor(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 (!IsHoveringNode) { autoConnectOutput = null; GenericMenu menu = new GenericMenu(); graphEditor.AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } } isPanning = false; } // Reset DoubleClick isDoubleClick = false; break; case EventType.KeyDown: if (EditorGUIUtility.editingTextField) { break; } else if (e.keyCode == KeyCode.F) { Home(); } if (NodeEditorUtilities.IsMac()) { if (e.keyCode == KeyCode.Return) { RenameSelectedNode(); } } else { if (e.keyCode == KeyCode.F2) { RenameSelectedNode(); } } if (e.keyCode == KeyCode.A) { if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) { foreach (XNode.Node node in graph.nodes) { DeselectNode(node); } } else { foreach (XNode.Node node in graph.nodes) { SelectNode(node, true); } } Repaint(); } break; case EventType.ValidateCommand: case EventType.ExecuteCommand: if (e.commandName == "SoftDelete") { if (e.type == EventType.ExecuteCommand) { RemoveSelectedNodes(); } e.Use(); } else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") { if (e.type == EventType.ExecuteCommand) { RemoveSelectedNodes(); } e.Use(); } else if (e.commandName == "Duplicate") { if (e.type == EventType.ExecuteCommand) { DuplicateSelectedNodes(); } e.Use(); } else if (e.commandName == "Copy") { if (e.type == EventType.ExecuteCommand) { CopySelectedNodes(); } e.Use(); } else if (e.commandName == "Paste") { if (e.type == EventType.ExecuteCommand) { PasteNodes(WindowToGridPosition(lastMousePosition)); } e.Use(); } Repaint(); break; case EventType.Ignore: // If release mouse outside window if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) { Repaint(); currentActivity = NodeActivity.Idle; } break; } }
private void DrawNodes() { Event e = Event.current; if (e.type == EventType.Layout) { selectionCache = new List <UnityEngine.Object>(Selection.objects); } if (e.type == EventType.Repaint) { portConnectionPoints.Clear(); nodeWidths.Clear(); } //Active node is hashed before and after node GUI to detect changes int nodeHash = 0; System.Reflection.MethodInfo onValidate = null; if (Selection.activeObject != null && Selection.activeObject is Siccity.XNode.Node) { onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); if (onValidate != null) { nodeHash = Selection.activeObject.GetHashCode(); } } BeginZoomed(position, zoom); Vector2 mousePos = Event.current.mousePosition; if (e.type != EventType.Layout) { hoveredNode = null; hoveredPort = null; } List <UnityEngine.Object> preSelection = preBoxSelection != null ? new List <UnityEngine.Object>(preBoxSelection) : new List <UnityEngine.Object>(); //Save guiColor so we can revert it Color guiColor = GUI.color; for (int n = 0; n < graph.nodes.Count; n++) { // Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable. if (graph.nodes[n] == null) { continue; } if (n >= graph.nodes.Count) { return; } Siccity.XNode.Node node = graph.nodes[n]; NodeEditor nodeEditor = NodeEditor.GetEditor(node); NodeEditor.portPositions = new Dictionary <Siccity.XNode.NodePort, Vector2>(); //Get node position Vector2 nodePos = GridToWindowPositionNoClipped(node.position); GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); bool selected = selectionCache.Contains(graph.nodes[n]); if (selected) { GUIStyle style = new GUIStyle(NodeEditorResources.styles.nodeBody); GUIStyle highlightStyle = new GUIStyle(NodeEditorResources.styles.nodeHighlight); highlightStyle.padding = style.padding; style.padding = new RectOffset(); GUI.color = nodeEditor.GetTint(); GUILayout.BeginVertical(new GUIStyle(style)); GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUILayout.BeginVertical(new GUIStyle(highlightStyle)); } else { GUIStyle style = NodeEditorResources.styles.nodeBody; GUI.color = nodeEditor.GetTint(); GUILayout.BeginVertical(new GUIStyle(style)); } GUI.color = guiColor; EditorGUI.BeginChangeCheck(); //Draw node contents nodeEditor.OnNodeGUI(); //Apply nodeEditor.serializedObject.ApplyModifiedProperties(); //If user changed a value, notify other scripts through onUpdateNode if (EditorGUI.EndChangeCheck()) { if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } } if (e.type == EventType.Repaint) { nodeWidths.Add(node, nodeEditor.GetWidth()); foreach (var kvp in NodeEditor.portPositions) { Vector2 portHandlePos = kvp.Value; portHandlePos += node.position; Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); portConnectionPoints.Add(kvp.Key, rect); } } GUILayout.EndVertical(); if (selected) { GUILayout.EndVertical(); } if (e.type != EventType.Layout) { //Check if we are hovering this node Vector2 nodeSize = GUILayoutUtility.GetLastRect().size; Rect windowRect = new Rect(nodePos, nodeSize); if (windowRect.Contains(mousePos)) { hoveredNode = node; } //If dragging a selection box, add nodes inside to selection if (currentActivity == NodeActivity.DragGrid) { Vector2 startPos = GridToWindowPositionNoClipped(dragBoxStart); Vector2 size = mousePos - startPos; if (size.x < 0) { startPos.x += size.x; size.x = Mathf.Abs(size.x); } if (size.y < 0) { startPos.y += size.y; size.y = Mathf.Abs(size.y); } Rect r = new Rect(startPos, size); if (windowRect.Overlaps(r)) { preSelection.Add(node); } } //Check if we are hovering any of this nodes ports //Check input ports foreach (Siccity.XNode.NodePort input in node.Inputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(input)) { continue; } Rect r = GridToWindowRect(portConnectionPoints[input]); if (r.Contains(mousePos)) { hoveredPort = input; } } //Check all output ports foreach (Siccity.XNode.NodePort output in node.Outputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(output)) { continue; } Rect r = GridToWindowRect(portConnectionPoints[output]); if (r.Contains(mousePos)) { hoveredPort = output; } } } GUILayout.EndArea(); } if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) { Selection.objects = preSelection.ToArray(); } EndZoomed(position, zoom); //If a change in hash is detected in the selected node, call OnValidate method. //This is done through reflection because OnValidate is only relevant in editor, //and thus, the code should not be included in build. if (nodeHash != 0) { if (onValidate != null && nodeHash != Selection.activeObject.GetHashCode()) { onValidate.Invoke(Selection.activeObject, null); } } }
/// <summary> Draw a bezier from startpoint to endpoint, both in grid coordinates </summary> public void DrawConnection(Vector2 startPoint, Vector2 endPoint, Color col) { startPoint = GridToWindowPosition(startPoint); endPoint = GridToWindowPosition(endPoint); switch (NodeEditorPreferences.GetSettings().noodleType) { case NodeEditorPreferences.NoodleType.Curve: Vector2 startTangent = startPoint; if (startPoint.x < endPoint.x) { startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, 0.7f); } else { startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, -0.7f); } Vector2 endTangent = endPoint; if (startPoint.x > endPoint.x) { endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, -0.7f); } else { endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, 0.7f); } Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, col, null, 4); break; case NodeEditorPreferences.NoodleType.Line: Handles.color = col; Handles.DrawAAPolyLine(5, startPoint, endPoint); break; case NodeEditorPreferences.NoodleType.Angled: Handles.color = col; if (startPoint.x <= endPoint.x - (50 / zoom)) { float midpoint = (startPoint.x + endPoint.x) * 0.5f; Vector2 start_1 = startPoint; Vector2 end_1 = endPoint; start_1.x = midpoint; end_1.x = midpoint; Handles.DrawAAPolyLine(5, startPoint, start_1); Handles.DrawAAPolyLine(5, start_1, end_1); Handles.DrawAAPolyLine(5, end_1, endPoint); } else { float midpoint = (startPoint.y + endPoint.y) * 0.5f; Vector2 start_1 = startPoint; Vector2 end_1 = endPoint; start_1.x += 25 / zoom; end_1.x -= 25 / zoom; Vector2 start_2 = start_1; Vector2 end_2 = end_1; start_2.y = midpoint; end_2.y = midpoint; Handles.DrawAAPolyLine(5, startPoint, start_1); Handles.DrawAAPolyLine(5, start_1, start_2); Handles.DrawAAPolyLine(5, start_2, end_2); Handles.DrawAAPolyLine(5, end_2, end_1); Handles.DrawAAPolyLine(5, end_1, endPoint); } break; } }
/// <summary> Draws all connections </summary> public void DrawConnections() { Vector2 mousePos = Event.current.mousePosition; List <RerouteReference> selection = preBoxSelectionReroute != null ? new List <RerouteReference>(preBoxSelectionReroute) : new List <RerouteReference>(); hoveredReroute = new RerouteReference(); Color col = GUI.color; foreach (XNode.Node node in 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 Rect fromRect; if (!_portConnectionPoints.TryGetValue(output, out fromRect)) { continue; } Color connectionColor = graphEditor.GetTypeColor(output.ValueType); for (int k = 0; k < output.ConnectionCount; k++) { XNode.NodePort input = output.GetConnection(k); // 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); } Rect toRect; if (!_portConnectionPoints.TryGetValue(input, out toRect)) { continue; } Vector2 from = fromRect.center; Vector2 to = Vector2.zero; List <Vector2> reroutePoints = output.GetReroutePoints(k); // Loop through reroute points and draw the path for (int i = 0; i < reroutePoints.Count; i++) { to = reroutePoints[i]; DrawConnection(from, to, connectionColor); from = to; } to = toRect.center; DrawConnection(from, to, connectionColor); // 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 = GridToWindowRect(rect); // Draw selected reroute points with an outline if (selectedReroutes.Contains(rerouteRef)) { GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUI.DrawTexture(rect, NodeEditorResources.dotOuter); } GUI.color = connectionColor; GUI.DrawTexture(rect, NodeEditorResources.dot); if (rect.Overlaps(selectionBox)) { selection.Add(rerouteRef); } if (rect.Contains(mousePos)) { hoveredReroute = rerouteRef; } } } } } GUI.color = col; if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) { selectedReroutes = selection; } }
private void DrawNodes() { Event e = Event.current; if (e.type == EventType.Layout) { selectionCache = new List <UnityEngine.Object>(Selection.objects); } System.Reflection.MethodInfo onValidate = null; if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); if (onValidate != null) { EditorGUI.BeginChangeCheck(); } } BeginZoomed(position, zoom, topPadding); Vector2 mousePos = Event.current.mousePosition; if (e.type != EventType.Layout) { hoveredNode = null; hoveredPort = null; } List <UnityEngine.Object> preSelection = preBoxSelection != null ? new List <UnityEngine.Object>(preBoxSelection) : new List <UnityEngine.Object>(); // Selection box stuff Vector2 boxStartPos = GridToWindowPositionNoClipped(dragBoxStart); Vector2 boxSize = mousePos - 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); } Rect selectionBox = new Rect(boxStartPos, boxSize); //Save guiColor so we can revert it Color guiColor = GUI.color; List <XNode.NodePort> removeEntries = new List <XNode.NodePort>(); if (e.type == EventType.Layout) { culledNodes = new List <XNode.Node>(); } for (int n = 0; n < graph.nodes.Count; n++) { // Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable. if (graph.nodes[n] == null) { continue; } if (n >= graph.nodes.Count) { return; } XNode.Node node = graph.nodes[n]; // Culling if (e.type == EventType.Layout) { // Cull unselected nodes outside view if (!Selection.Contains(node) && ShouldBeCulled(node)) { culledNodes.Add(node); continue; } } else if (culledNodes.Contains(node)) { continue; } if (e.type == EventType.Repaint) { removeEntries.Clear(); foreach (var kvp in _portConnectionPoints) { if (kvp.Key.node == node) { removeEntries.Add(kvp.Key); } } foreach (var k in removeEntries) { _portConnectionPoints.Remove(k); } } NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); NodeEditor.portPositions.Clear(); // Set default label width. This is potentially overridden in OnBodyGUI EditorGUIUtility.labelWidth = 84; //Get node position Vector2 nodePos = GridToWindowPositionNoClipped(node.position); GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); bool selected = selectionCache.Contains(graph.nodes[n]); if (selected) { GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); GUIStyle highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle()); highlightStyle.padding = style.padding; style.padding = new RectOffset(); GUI.color = nodeEditor.GetTint(); GUILayout.BeginVertical(style); GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUILayout.BeginVertical(new GUIStyle(highlightStyle)); } else { GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); GUI.color = nodeEditor.GetTint(); GUILayout.BeginVertical(style); } GUI.color = guiColor; EditorGUI.BeginChangeCheck(); //Draw node contents nodeEditor.OnHeaderGUI(); //if (zoom < 2) //{ nodeEditor.OnBodyGUI(); //} //If user changed a value, notify other scripts through onUpdateNode if (EditorGUI.EndChangeCheck()) { if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } EditorUtility.SetDirty(node); nodeEditor.serializedObject.ApplyModifiedProperties(); } GUILayout.EndVertical(); //Cache data about the node for next frame if (e.type == EventType.Repaint) { Vector2 size = GUILayoutUtility.GetLastRect().size; if (nodeSizes.ContainsKey(node)) { nodeSizes[node] = size; } else { nodeSizes.Add(node, size); } foreach (var kvp in NodeEditor.portPositions) { Vector2 portHandlePos = kvp.Value; portHandlePos += node.position; Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); portConnectionPoints[kvp.Key] = rect; } } if (selected) { GUILayout.EndVertical(); } if (e.type != EventType.Layout) { //Check if we are hovering this node Vector2 nodeSize = GUILayoutUtility.GetLastRect().size; Rect windowRect = new Rect(nodePos, nodeSize); if (windowRect.Contains(mousePos)) { hoveredNode = node; } //If dragging a selection box, add nodes inside to selection if (currentActivity == NodeActivity.DragGrid) { if (windowRect.Overlaps(selectionBox)) { preSelection.Add(node); } } //Check if we are hovering any of this nodes ports //Check input ports foreach (XNode.NodePort input in node.Inputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(input)) { continue; } Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]); if (r.Contains(mousePos)) { hoveredPort = input; } } //Check all output ports foreach (XNode.NodePort output in node.Outputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(output)) { continue; } Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); if (r.Contains(mousePos)) { hoveredPort = output; } } } GUILayout.EndArea(); } if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) { Selection.objects = preSelection.ToArray(); } EndZoomed(position, zoom, topPadding); //If a change in is detected in the selected node, call OnValidate method. //This is done through reflection because OnValidate is only relevant in editor, //and thus, the code should not be included in build. if (onValidate != null && EditorGUI.EndChangeCheck()) { onValidate.Invoke(Selection.activeObject, null); } }
public void Controls() { wantsMouseMove = true; Event e = Event.current; switch (e.type) { case EventType.MouseMove: break; case EventType.ScrollWheel: if (e.delta.y > 0) { zoom += 0.1f * zoom; } else { zoom -= 0.1f * zoom; } break; case EventType.MouseDrag: if (e.button == 0) { if (IsDraggingPort) { if (IsHoveringPort && hoveredPort.IsInput) { if (!draggedOutput.IsConnectedTo(hoveredPort)) { draggedOutputTarget = hoveredPort; } } else { draggedOutputTarget = null; } Repaint(); } else if (currentActivity == NodeActivity.HoldHeader || currentActivity == NodeActivity.DragHeader) { 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; node.position = WindowToGridPosition(e.mousePosition) + dragOffset[i]; bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; if (e.control) { gridSnap = !gridSnap; } 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; } } } currentActivity = NodeActivity.DragHeader; Repaint(); } else if (currentActivity == NodeActivity.HoldGrid) { currentActivity = NodeActivity.DragGrid; preBoxSelection = Selection.objects; dragBoxStart = WindowToGridPosition(e.mousePosition); Repaint(); } else if (currentActivity == NodeActivity.DragGrid) { foreach (XNode.Node node in graph.nodes) { } Repaint(); } } else if (e.button == 1 || e.button == 2) { Vector2 tempOffset = panOffset; tempOffset += e.delta * zoom; // Round value to increase crispyness of UI text tempOffset.x = Mathf.Round(tempOffset.x); tempOffset.y = Mathf.Round(tempOffset.y); panOffset = tempOffset; isPanning = true; } break; case EventType.MouseDown: Repaint(); if (e.button == 0) { if (IsHoveringPort) { if (hoveredPort.IsOutput) { draggedOutput = hoveredPort; } else { hoveredPort.VerifyConnections(); if (hoveredPort.IsConnected) { XNode.Node node = hoveredPort.node; XNode.NodePort output = hoveredPort.Connection; hoveredPort.Disconnect(output); draggedOutput = output; draggedOutputTarget = hoveredPort; if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } } } } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { // If mousedown on node header, select or deselect if (!Selection.Contains(hoveredNode)) { SelectNode(hoveredNode, e.control || e.shift); } else if (e.control || e.shift) { DeselectNode(hoveredNode); } e.Use(); currentActivity = NodeActivity.HoldHeader; dragOffset = new Vector2[Selection.objects.Length]; for (int i = 0; i < dragOffset.Length; i++) { if (Selection.objects[i] is XNode.Node) { XNode.Node node = Selection.objects[i] as XNode.Node; dragOffset[i] = node.position - WindowToGridPosition(e.mousePosition); } } } // If mousedown on grid background, deselect all else if (!IsHoveringNode) { currentActivity = NodeActivity.HoldGrid; if (!e.control && !e.shift) { Selection.activeObject = null; } } } break; case EventType.MouseUp: if (e.button == 0) { //Port drag release if (IsDraggingPort) { //If connection is valid, save it if (draggedOutputTarget != null) { XNode.Node node = draggedOutputTarget.node; if (graph.nodes.Count != 0) { draggedOutput.Connect(draggedOutputTarget); } if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } EditorUtility.SetDirty(graph); } //Release dragged connection draggedOutput = null; draggedOutputTarget = null; EditorUtility.SetDirty(graph); AssetDatabase.SaveAssets(); } else if (currentActivity == NodeActivity.DragHeader) { AssetDatabase.SaveAssets(); } else if (!IsHoveringNode) { // If click outside node, release field focus if (!isPanning) { // I've got no idea which of these do what, so we'll just reset all of it. GUIUtility.hotControl = 0; GUIUtility.keyboardControl = 0; EditorGUIUtility.editingTextField = false; EditorGUIUtility.keyboardControl = 0; EditorGUIUtility.hotControl = 0; } AssetDatabase.SaveAssets(); } // If click node header, select single node. if (currentActivity == NodeActivity.HoldHeader && !(e.control || e.shift)) { SelectNode(hoveredNode, false); } Repaint(); currentActivity = NodeActivity.Idle; } else if (e.button == 1) { if (!isPanning) { if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (!Selection.Contains(hoveredNode)) { SelectNode(hoveredNode, false); } ShowNodeContextMenu(); } else if (!IsHoveringNode) { ShowGraphContextMenu(); } } isPanning = false; } break; case EventType.KeyDown: if (EditorGUIUtility.editingTextField) { break; } else if (e.keyCode == KeyCode.F) { Home(); } break; case EventType.ValidateCommand: if (e.commandName == "SoftDelete") { RemoveSelectedNodes(); } else if (e.commandName == "Duplicate") { DublicateSelectedNodes(); } Repaint(); break; case EventType.Ignore: // If release mouse outside window if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) { Repaint(); currentActivity = NodeActivity.Idle; } break; } }
public virtual Color GetTypeColor(Type type) { return(NodeEditorPreferences.GetTypeColor(type)); }
public virtual Texture2D GetSecondaryGridTexture() { return(NodeEditorPreferences.GetSettings().crossTexture); }
public virtual Texture2D GetGridTexture() { return(NodeEditorPreferences.GetSettings().gridTexture); }
public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) { return(NodeEditorPreferences.GetSettings().noodleStroke); }