NodeEditorAction.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using XNodeEditor.Internal;
  7. namespace XNodeEditor {
  8. public partial class NodeEditorWindow {
  9. public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid }
  10. public static NodeActivity currentActivity = NodeActivity.Idle;
  11. public static bool isPanning { get; private set; }
  12. public static Vector2[] dragOffset;
  13. public static XNode.Node[] copyBuffer = null;
  14. private bool IsDraggingPort { get { return draggedOutput != null; } }
  15. private bool IsHoveringPort { get { return hoveredPort != null; } }
  16. private bool IsHoveringNode { get { return hoveredNode != null; } }
  17. private bool IsHoveringReroute { get { return hoveredReroute.port != null; } }
  18. private XNode.Node hoveredNode = null;
  19. [NonSerialized] public XNode.NodePort hoveredPort = null;
  20. [NonSerialized] private XNode.NodePort draggedOutput = null;
  21. [NonSerialized] private XNode.NodePort draggedOutputTarget = null;
  22. [NonSerialized] private XNode.NodePort autoConnectOutput = null;
  23. [NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();
  24. private RerouteReference hoveredReroute = new RerouteReference();
  25. private List<RerouteReference> selectedReroutes = new List<RerouteReference>();
  26. private Vector2 dragBoxStart;
  27. private UnityEngine.Object[] preBoxSelection;
  28. private RerouteReference[] preBoxSelectionReroute;
  29. private Rect selectionBox;
  30. private bool isDoubleClick = false;
  31. private Vector2 lastMousePosition;
  32. public void Controls() {
  33. wantsMouseMove = true;
  34. Event e = Event.current;
  35. switch (e.type) {
  36. case EventType.DragUpdated:
  37. case EventType.DragPerform:
  38. DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
  39. if (e.type == EventType.DragPerform) {
  40. DragAndDrop.AcceptDrag();
  41. graphEditor.OnDropObjects(DragAndDrop.objectReferences);
  42. }
  43. break;
  44. case EventType.MouseMove:
  45. //Keyboard commands will not get correct mouse position from Event
  46. lastMousePosition = e.mousePosition;
  47. break;
  48. case EventType.ScrollWheel:
  49. float oldZoom = zoom;
  50. if (e.delta.y > 0) zoom += 0.1f * zoom;
  51. else zoom -= 0.1f * zoom;
  52. if (NodeEditorPreferences.GetSettings().zoomToMouse) panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset);
  53. break;
  54. case EventType.MouseDrag:
  55. if (e.button == 0) {
  56. if (IsDraggingPort) {
  57. if (IsHoveringPort && hoveredPort.IsInput && draggedOutput.CanConnectTo(hoveredPort)) {
  58. if (!draggedOutput.IsConnectedTo(hoveredPort)) {
  59. draggedOutputTarget = hoveredPort;
  60. }
  61. } else {
  62. draggedOutputTarget = null;
  63. }
  64. Repaint();
  65. } else if (currentActivity == NodeActivity.HoldNode) {
  66. RecalculateDragOffsets(e);
  67. currentActivity = NodeActivity.DragNode;
  68. Repaint();
  69. }
  70. if (currentActivity == NodeActivity.DragNode) {
  71. // Holding ctrl inverts grid snap
  72. bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
  73. if (e.control) gridSnap = !gridSnap;
  74. Vector2 mousePos = WindowToGridPosition(e.mousePosition);
  75. // Move selected nodes with offset
  76. for (int i = 0; i < Selection.objects.Length; i++) {
  77. if (Selection.objects[i] is XNode.Node) {
  78. XNode.Node node = Selection.objects[i] as XNode.Node;
  79. Undo.RecordObject(node, "Moved Node");
  80. Vector2 initial = node.position;
  81. node.position = mousePos + dragOffset[i];
  82. if (gridSnap) {
  83. node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
  84. node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;
  85. }
  86. // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame.
  87. Vector2 offset = node.position - initial;
  88. if (offset.sqrMagnitude > 0) {
  89. foreach (XNode.NodePort output in node.Outputs) {
  90. Rect rect;
  91. if (portConnectionPoints.TryGetValue(output, out rect)) {
  92. rect.position += offset;
  93. portConnectionPoints[output] = rect;
  94. }
  95. }
  96. foreach (XNode.NodePort input in node.Inputs) {
  97. Rect rect;
  98. if (portConnectionPoints.TryGetValue(input, out rect)) {
  99. rect.position += offset;
  100. portConnectionPoints[input] = rect;
  101. }
  102. }
  103. }
  104. }
  105. }
  106. // Move selected reroutes with offset
  107. for (int i = 0; i < selectedReroutes.Count; i++) {
  108. Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i];
  109. if (gridSnap) {
  110. pos.x = (Mathf.Round(pos.x / 16) * 16);
  111. pos.y = (Mathf.Round(pos.y / 16) * 16);
  112. }
  113. selectedReroutes[i].SetPoint(pos);
  114. }
  115. Repaint();
  116. } else if (currentActivity == NodeActivity.HoldGrid) {
  117. currentActivity = NodeActivity.DragGrid;
  118. preBoxSelection = Selection.objects;
  119. preBoxSelectionReroute = selectedReroutes.ToArray();
  120. dragBoxStart = WindowToGridPosition(e.mousePosition);
  121. Repaint();
  122. } else if (currentActivity == NodeActivity.DragGrid) {
  123. Vector2 boxStartPos = GridToWindowPosition(dragBoxStart);
  124. Vector2 boxSize = e.mousePosition - boxStartPos;
  125. if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
  126. if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); }
  127. selectionBox = new Rect(boxStartPos, boxSize);
  128. Repaint();
  129. }
  130. } else if (e.button == 1 || e.button == 2) {
  131. panOffset += e.delta * zoom;
  132. isPanning = true;
  133. }
  134. break;
  135. case EventType.MouseDown:
  136. Repaint();
  137. if (e.button == 0) {
  138. draggedOutputReroutes.Clear();
  139. if (IsHoveringPort) {
  140. if (hoveredPort.IsOutput) {
  141. draggedOutput = hoveredPort;
  142. autoConnectOutput = hoveredPort;
  143. } else {
  144. hoveredPort.VerifyConnections();
  145. autoConnectOutput = null;
  146. if (hoveredPort.IsConnected) {
  147. XNode.Node node = hoveredPort.node;
  148. XNode.NodePort output = hoveredPort.Connection;
  149. int outputConnectionIndex = output.GetConnectionIndex(hoveredPort);
  150. draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex);
  151. hoveredPort.Disconnect(output);
  152. draggedOutput = output;
  153. draggedOutputTarget = hoveredPort;
  154. if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
  155. }
  156. }
  157. } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
  158. // If mousedown on node header, select or deselect
  159. if (!Selection.Contains(hoveredNode)) {
  160. SelectNode(hoveredNode, e.control || e.shift);
  161. if (!e.control && !e.shift) selectedReroutes.Clear();
  162. } else if (e.control || e.shift) DeselectNode(hoveredNode);
  163. // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.
  164. isDoubleClick = (e.clickCount == 2);
  165. e.Use();
  166. currentActivity = NodeActivity.HoldNode;
  167. } else if (IsHoveringReroute) {
  168. // If reroute isn't selected
  169. if (!selectedReroutes.Contains(hoveredReroute)) {
  170. // Add it
  171. if (e.control || e.shift) selectedReroutes.Add(hoveredReroute);
  172. // Select it
  173. else {
  174. selectedReroutes = new List<RerouteReference>() { hoveredReroute };
  175. Selection.activeObject = null;
  176. }
  177. }
  178. // Deselect
  179. else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute);
  180. e.Use();
  181. currentActivity = NodeActivity.HoldNode;
  182. }
  183. // If mousedown on grid background, deselect all
  184. else if (!IsHoveringNode) {
  185. currentActivity = NodeActivity.HoldGrid;
  186. if (!e.control && !e.shift) {
  187. selectedReroutes.Clear();
  188. Selection.activeObject = null;
  189. }
  190. }
  191. }
  192. break;
  193. case EventType.MouseUp:
  194. if (e.button == 0) {
  195. //Port drag release
  196. if (IsDraggingPort) {
  197. //If connection is valid, save it
  198. if (draggedOutputTarget != null) {
  199. XNode.Node node = draggedOutputTarget.node;
  200. if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget);
  201. // ConnectionIndex can be -1 if the connection is removed instantly after creation
  202. int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);
  203. if (connectionIndex != -1) {
  204. draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);
  205. if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
  206. EditorUtility.SetDirty(graph);
  207. }
  208. }
  209. // Open context menu for auto-connection
  210. else if (NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) {
  211. GenericMenu menu = new GenericMenu();
  212. graphEditor.AddContextMenuItems(menu);
  213. menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
  214. }
  215. //Release dragged connection
  216. draggedOutput = null;
  217. draggedOutputTarget = null;
  218. EditorUtility.SetDirty(graph);
  219. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  220. } else if (currentActivity == NodeActivity.DragNode) {
  221. IEnumerable<XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node);
  222. foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node);
  223. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  224. } else if (!IsHoveringNode) {
  225. // If click outside node, release field focus
  226. if (!isPanning) {
  227. EditorGUI.FocusTextInControl(null);
  228. EditorGUIUtility.editingTextField = false;
  229. }
  230. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  231. }
  232. // If click node header, select it.
  233. if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) {
  234. selectedReroutes.Clear();
  235. SelectNode(hoveredNode, false);
  236. // Double click to center node
  237. if (isDoubleClick) {
  238. Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero;
  239. panOffset = -hoveredNode.position - nodeDimension;
  240. }
  241. }
  242. // If click reroute, select it.
  243. if (IsHoveringReroute && !(e.control || e.shift)) {
  244. selectedReroutes = new List<RerouteReference>() { hoveredReroute };
  245. Selection.activeObject = null;
  246. }
  247. Repaint();
  248. currentActivity = NodeActivity.Idle;
  249. } else if (e.button == 1 || e.button == 2) {
  250. if (!isPanning) {
  251. if (IsDraggingPort) {
  252. draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition));
  253. } else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1) {
  254. selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint());
  255. selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1);
  256. } else if (IsHoveringReroute) {
  257. ShowRerouteContextMenu(hoveredReroute);
  258. } else if (IsHoveringPort) {
  259. ShowPortContextMenu(hoveredPort);
  260. } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
  261. if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false);
  262. autoConnectOutput = null;
  263. GenericMenu menu = new GenericMenu();
  264. NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);
  265. menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
  266. 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.
  267. } else if (!IsHoveringNode) {
  268. autoConnectOutput = null;
  269. GenericMenu menu = new GenericMenu();
  270. graphEditor.AddContextMenuItems(menu);
  271. menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
  272. }
  273. }
  274. isPanning = false;
  275. }
  276. // Reset DoubleClick
  277. isDoubleClick = false;
  278. break;
  279. case EventType.KeyDown:
  280. if (EditorGUIUtility.editingTextField) break;
  281. else if (e.keyCode == KeyCode.F) Home();
  282. if (NodeEditorUtilities.IsMac()) {
  283. if (e.keyCode == KeyCode.Return) RenameSelectedNode();
  284. } else {
  285. if (e.keyCode == KeyCode.F2) RenameSelectedNode();
  286. }
  287. if (e.keyCode == KeyCode.A) {
  288. if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) {
  289. foreach (XNode.Node node in graph.nodes) {
  290. DeselectNode(node);
  291. }
  292. } else {
  293. foreach (XNode.Node node in graph.nodes) {
  294. SelectNode(node, true);
  295. }
  296. }
  297. Repaint();
  298. }
  299. break;
  300. case EventType.ValidateCommand:
  301. case EventType.ExecuteCommand:
  302. if (e.commandName == "SoftDelete") {
  303. if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
  304. e.Use();
  305. } else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") {
  306. if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
  307. e.Use();
  308. } else if (e.commandName == "Duplicate") {
  309. if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();
  310. e.Use();
  311. } else if (e.commandName == "Copy") {
  312. if (e.type == EventType.ExecuteCommand) CopySelectedNodes();
  313. e.Use();
  314. } else if (e.commandName == "Paste") {
  315. if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));
  316. e.Use();
  317. }
  318. Repaint();
  319. break;
  320. case EventType.Ignore:
  321. // If release mouse outside window
  322. if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) {
  323. Repaint();
  324. currentActivity = NodeActivity.Idle;
  325. }
  326. break;
  327. }
  328. }
  329. private void RecalculateDragOffsets(Event current) {
  330. dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
  331. // Selected nodes
  332. for (int i = 0; i < Selection.objects.Length; i++) {
  333. if (Selection.objects[i] is XNode.Node) {
  334. XNode.Node node = Selection.objects[i] as XNode.Node;
  335. dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);
  336. }
  337. }
  338. // Selected reroutes
  339. for (int i = 0; i < selectedReroutes.Count; i++) {
  340. dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition);
  341. }
  342. }
  343. /// <summary> Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin </summary>
  344. public void Home() {
  345. var nodes = Selection.objects.Where(o => o is XNode.Node).Cast<XNode.Node>().ToList();
  346. if (nodes.Count > 0) {
  347. Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
  348. Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y)));
  349. panOffset = -(minPos + (maxPos - minPos) / 2f);
  350. } else {
  351. zoom = 2;
  352. panOffset = Vector2.zero;
  353. }
  354. }
  355. /// <summary> Remove nodes in the graph in Selection.objects</summary>
  356. public void RemoveSelectedNodes() {
  357. // We need to delete reroutes starting at the highest point index to avoid shifting indices
  358. selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList();
  359. for (int i = 0; i < selectedReroutes.Count; i++) {
  360. selectedReroutes[i].RemovePoint();
  361. }
  362. selectedReroutes.Clear();
  363. foreach (UnityEngine.Object item in Selection.objects) {
  364. if (item is XNode.Node) {
  365. XNode.Node node = item as XNode.Node;
  366. graphEditor.RemoveNode(node);
  367. }
  368. }
  369. }
  370. /// <summary> Initiate a rename on the currently selected node </summary>
  371. public void RenameSelectedNode() {
  372. if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
  373. XNode.Node node = Selection.activeObject as XNode.Node;
  374. Vector2 size;
  375. if (nodeSizes.TryGetValue(node, out size)) {
  376. RenamePopup.Show(Selection.activeObject, size.x);
  377. } else {
  378. RenamePopup.Show(Selection.activeObject);
  379. }
  380. }
  381. }
  382. /// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary>
  383. public void MoveNodeToTop(XNode.Node node) {
  384. int index;
  385. while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) {
  386. graph.nodes[index] = graph.nodes[index + 1];
  387. graph.nodes[index + 1] = node;
  388. }
  389. }
  390. /// <summary> Duplicate selected nodes and select the duplicates </summary>
  391. public void DuplicateSelectedNodes() {
  392. // Get selected nodes which are part of this graph
  393. XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
  394. if (selectedNodes == null || selectedNodes.Length == 0) return;
  395. // Get top left node position
  396. Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
  397. InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30));
  398. }
  399. public void CopySelectedNodes() {
  400. copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
  401. }
  402. public void PasteNodes(Vector2 pos) {
  403. InsertDuplicateNodes(copyBuffer, pos);
  404. }
  405. private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) {
  406. if (nodes == null || nodes.Length == 0) return;
  407. // Get top-left node
  408. Vector2 topLeftNode = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
  409. Vector2 offset = topLeft - topLeftNode;
  410. UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length];
  411. Dictionary<XNode.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>();
  412. for (int i = 0; i < nodes.Length; i++) {
  413. XNode.Node srcNode = nodes[i];
  414. if (srcNode == null) continue;
  415. XNode.Node newNode = graphEditor.CopyNode(srcNode);
  416. substitutes.Add(srcNode, newNode);
  417. newNode.position = srcNode.position + offset;
  418. newNodes[i] = newNode;
  419. }
  420. // Walk through the selected nodes again, recreate connections, using the new nodes
  421. for (int i = 0; i < nodes.Length; i++) {
  422. XNode.Node srcNode = nodes[i];
  423. if (srcNode == null) continue;
  424. foreach (XNode.NodePort port in srcNode.Ports) {
  425. for (int c = 0; c < port.ConnectionCount; c++) {
  426. XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c);
  427. XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c);
  428. XNode.Node newNodeIn, newNodeOut;
  429. if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) {
  430. newNodeIn.UpdateStaticPorts();
  431. newNodeOut.UpdateStaticPorts();
  432. inputPort = newNodeIn.GetInputPort(inputPort.fieldName);
  433. outputPort = newNodeOut.GetOutputPort(outputPort.fieldName);
  434. }
  435. if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort);
  436. }
  437. }
  438. }
  439. // Select the new nodes
  440. Selection.objects = newNodes;
  441. }
  442. /// <summary> Draw a connection as we are dragging it </summary>
  443. public void DrawDraggedConnection() {
  444. if (IsDraggingPort) {
  445. Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null);
  446. float thickness = graphEditor.GetNoodleThickness(draggedOutput, null);
  447. NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null);
  448. NoodleStroke stroke = graphEditor.GetNoodleStroke(draggedOutput, null);
  449. Rect fromRect;
  450. if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
  451. List<Vector2> gridPoints = new List<Vector2>();
  452. gridPoints.Add(fromRect.center);
  453. for (int i = 0; i < draggedOutputReroutes.Count; i++) {
  454. gridPoints.Add(draggedOutputReroutes[i]);
  455. }
  456. if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);
  457. else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition));
  458. DrawNoodle(gradient, path, stroke, thickness, gridPoints);
  459. Color bgcol = Color.black;
  460. Color frcol = gradient.colorKeys[0].color;
  461. bgcol.a = 0.6f;
  462. frcol.a = 0.6f;
  463. // Loop through reroute points again and draw the points
  464. for (int i = 0; i < draggedOutputReroutes.Count; i++) {
  465. // Draw reroute point at position
  466. Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));
  467. rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
  468. rect = GridToWindowRect(rect);
  469. NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol);
  470. }
  471. }
  472. }
  473. bool IsHoveringTitle(XNode.Node node) {
  474. Vector2 mousePos = Event.current.mousePosition;
  475. //Get node position
  476. Vector2 nodePos = GridToWindowPosition(node.position);
  477. float width;
  478. Vector2 size;
  479. if (nodeSizes.TryGetValue(node, out size)) width = size.x;
  480. else width = 200;
  481. Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom));
  482. return windowRect.Contains(mousePos);
  483. }
  484. /// <summary> Attempt to connect dragged output to target node </summary>
  485. public void AutoConnect(XNode.Node node) {
  486. if (autoConnectOutput == null) return;
  487. // Find input port of same type
  488. XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType);
  489. // Fallback to input port
  490. if (inputPort == null) inputPort = node.Ports.FirstOrDefault(x => x.IsInput);
  491. // Autoconnect
  492. if (inputPort != null) autoConnectOutput.Connect(inputPort);
  493. // Save changes
  494. EditorUtility.SetDirty(graph);
  495. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  496. autoConnectOutput = null;
  497. }
  498. }
  499. }