NodeEditorAction.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using XNode;
  7. using XNodeEditor.Internal;
  8. namespace XNodeEditor
  9. {
  10. public partial class NodeEditorWindow
  11. {
  12. public enum NodeActivity
  13. {
  14. Idle,
  15. HoldNode,
  16. DragNode,
  17. HoldGrid,
  18. DragGrid
  19. }
  20. public static NodeActivity currentActivity = NodeActivity.Idle;
  21. public static bool isPanning { get; private set; }
  22. public static Vector2[] dragOffset;
  23. public static XNode.Node[] copyBuffer = null;
  24. private bool IsDraggingPort
  25. {
  26. get { return draggedOutput != null; }
  27. }
  28. private bool IsHoveringPort
  29. {
  30. get { return hoveredPort != null; }
  31. }
  32. private bool IsHoveringNode
  33. {
  34. get { return hoveredNode != null; }
  35. }
  36. private bool IsHoveringReroute
  37. {
  38. get { return hoveredReroute.port != null; }
  39. }
  40. private XNode.Node hoveredNode = null;
  41. [NonSerialized] public XNode.NodePort hoveredPort = null;
  42. [NonSerialized] private XNode.NodePort draggedOutput = null;
  43. [NonSerialized] private XNode.NodePort draggedOutputTarget = null;
  44. [NonSerialized] private XNode.NodePort autoConnectOutput = null;
  45. [NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();
  46. private RerouteReference hoveredReroute = new RerouteReference();
  47. public List<RerouteReference> selectedReroutes = new List<RerouteReference>();
  48. private Vector2 dragBoxStart;
  49. private List<Node> preBoxSelection;
  50. private RerouteReference[] preBoxSelectionReroute;
  51. private Rect selectionBox;
  52. private bool isDoubleClick = false;
  53. private Vector2 lastMousePosition;
  54. private float dragThreshold = 1f;
  55. public void Controls()
  56. {
  57. wantsMouseMove = true;
  58. Event e = Event.current;
  59. switch (e.type)
  60. {
  61. case EventType.DragUpdated:
  62. case EventType.DragPerform:
  63. DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
  64. if (e.type == EventType.DragPerform)
  65. {
  66. DragAndDrop.AcceptDrag();
  67. graphEditor.OnDropObjects(DragAndDrop.objectReferences);
  68. }
  69. break;
  70. case EventType.MouseMove:
  71. //Keyboard commands will not get correct mouse position from Event
  72. lastMousePosition = e.mousePosition;
  73. break;
  74. case EventType.ScrollWheel:
  75. float oldZoom = zoom;
  76. if (e.delta.y > 0) zoom += 0.1f * zoom;
  77. else zoom -= 0.1f * zoom;
  78. if (NodeEditorPreferences.GetSettings().zoomToMouse)
  79. panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset);
  80. break;
  81. case EventType.MouseDrag:
  82. if (e.button == 0)
  83. {
  84. if (IsDraggingPort)
  85. {
  86. // Set target even if we can't connect, so as to prevent auto-conn menu from opening erroneously
  87. if (IsHoveringPort && hoveredPort.IsInput && !draggedOutput.IsConnectedTo(hoveredPort))
  88. {
  89. draggedOutputTarget = hoveredPort;
  90. }
  91. else
  92. {
  93. draggedOutputTarget = null;
  94. }
  95. Repaint();
  96. }
  97. else if (currentActivity == NodeActivity.HoldNode)
  98. {
  99. RecalculateDragOffsets(e);
  100. currentActivity = NodeActivity.DragNode;
  101. Repaint();
  102. }
  103. if (currentActivity == NodeActivity.DragNode)
  104. {
  105. // Holding ctrl inverts grid snap
  106. bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
  107. if (e.control) gridSnap = !gridSnap;
  108. Vector2 mousePos = WindowToGridPosition(e.mousePosition);
  109. // Move selected nodes with offset
  110. for (int i = 0; i < _selectedNodes.Count; i++)
  111. {
  112. XNode.Node node = _selectedNodes[i];
  113. if (node == null)
  114. continue;
  115. Undo.RecordObject(node, "Moved Node");
  116. Vector2 initial = node.position;
  117. try
  118. {
  119. node.position = mousePos + dragOffset[i];
  120. }
  121. catch (Exception exception)
  122. {
  123. Console.WriteLine(exception);
  124. throw;
  125. }
  126. if (gridSnap)
  127. {
  128. node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
  129. node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;
  130. }
  131. // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame.
  132. Vector2 offset = node.position - initial;
  133. if (offset.sqrMagnitude > 0)
  134. {
  135. foreach (XNode.NodePort output in node.Outputs)
  136. {
  137. Rect rect;
  138. if (portConnectionPoints.TryGetValue(output, out rect))
  139. {
  140. rect.position += offset;
  141. portConnectionPoints[output] = rect;
  142. }
  143. }
  144. foreach (XNode.NodePort input in node.Inputs)
  145. {
  146. Rect rect;
  147. if (portConnectionPoints.TryGetValue(input, out rect))
  148. {
  149. rect.position += offset;
  150. portConnectionPoints[input] = rect;
  151. }
  152. }
  153. }
  154. }
  155. // Move selected reroutes with offset
  156. for (int i = 0; i < selectedReroutes.Count; i++)
  157. {
  158. Vector2 pos = mousePos + dragOffset[_selectedNodes.Count + i];
  159. if (gridSnap)
  160. {
  161. pos.x = (Mathf.Round(pos.x / 16) * 16);
  162. pos.y = (Mathf.Round(pos.y / 16) * 16);
  163. }
  164. selectedReroutes[i].SetPoint(pos);
  165. }
  166. Repaint();
  167. }
  168. else if (currentActivity == NodeActivity.HoldGrid)
  169. {
  170. currentActivity = NodeActivity.DragGrid;
  171. preBoxSelection = _selectedNodes;
  172. preBoxSelectionReroute = selectedReroutes.ToArray();
  173. dragBoxStart = WindowToGridPosition(e.mousePosition);
  174. Repaint();
  175. }
  176. else if (currentActivity == NodeActivity.DragGrid)
  177. {
  178. Vector2 boxStartPos = GridToWindowPosition(dragBoxStart);
  179. Vector2 boxSize = e.mousePosition - boxStartPos;
  180. if (boxSize.x < 0)
  181. {
  182. boxStartPos.x += boxSize.x;
  183. boxSize.x = Mathf.Abs(boxSize.x);
  184. }
  185. if (boxSize.y < 0)
  186. {
  187. boxStartPos.y += boxSize.y;
  188. boxSize.y = Mathf.Abs(boxSize.y);
  189. }
  190. selectionBox = new Rect(boxStartPos, boxSize);
  191. Repaint();
  192. }
  193. }
  194. else if (e.button == 1 || e.button == 2)
  195. {
  196. //check drag threshold for larger screens
  197. if (e.delta.magnitude > dragThreshold)
  198. {
  199. panOffset += e.delta * zoom;
  200. isPanning = true;
  201. }
  202. }
  203. break;
  204. case EventType.MouseDown:
  205. Repaint();
  206. if (e.button == 0)
  207. {
  208. draggedOutputReroutes.Clear();
  209. if (IsHoveringPort)
  210. {
  211. if (hoveredPort.IsOutput)
  212. {
  213. draggedOutput = hoveredPort;
  214. autoConnectOutput = hoveredPort;
  215. }
  216. else
  217. {
  218. hoveredPort.VerifyConnections();
  219. autoConnectOutput = null;
  220. if (hoveredPort.IsConnected)
  221. {
  222. XNode.Node node = hoveredPort.node;
  223. XNode.NodePort output = hoveredPort.Connection;
  224. int outputConnectionIndex = output.GetConnectionIndex(hoveredPort);
  225. draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex);
  226. hoveredPort.Disconnect(output);
  227. draggedOutput = output;
  228. draggedOutputTarget = hoveredPort;
  229. if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
  230. }
  231. }
  232. }
  233. else if (IsHoveringNode && IsHoveringTitle(hoveredNode))
  234. {
  235. // If mousedown on node header, select or deselect
  236. if (!_selectedNodes.Contains(hoveredNode))
  237. {
  238. if (e.control || e.shift)
  239. {
  240. SelectNode(hoveredNode, true);
  241. }
  242. else
  243. {
  244. SelectNode(hoveredNode, false);
  245. }
  246. if (!e.control && !e.shift) selectedReroutes.Clear();
  247. }
  248. // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.
  249. isDoubleClick = (e.clickCount == 2);
  250. e.Use();
  251. currentActivity = NodeActivity.HoldNode;
  252. }
  253. else if (IsHoveringReroute)
  254. {
  255. // If reroute isn't selected
  256. if (!selectedReroutes.Contains(hoveredReroute))
  257. {
  258. // Add it
  259. if (e.control || e.shift) selectedReroutes.Add(hoveredReroute);
  260. // Select it
  261. else
  262. {
  263. selectedReroutes = new List<RerouteReference>() { hoveredReroute };
  264. Selection.activeObject = null;
  265. }
  266. }
  267. // Deselect
  268. else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute);
  269. e.Use();
  270. currentActivity = NodeActivity.HoldNode;
  271. }
  272. // If mousedown on grid background, deselect all
  273. else if (!IsHoveringNode)
  274. {
  275. // 暂时屏蔽以前的逻辑了
  276. currentActivity = NodeActivity.HoldGrid;
  277. if (!e.control && !e.shift)
  278. {
  279. selectedReroutes.Clear();
  280. _selectedNodes.Clear();
  281. }
  282. }
  283. }
  284. break;
  285. case EventType.MouseUp:
  286. if (e.button == 0)
  287. {
  288. //Port drag release
  289. if (IsDraggingPort)
  290. {
  291. // If connection is valid, save it
  292. if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget))
  293. {
  294. XNode.Node node = draggedOutputTarget.node;
  295. if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget);
  296. // ConnectionIndex can be -1 if the connection is removed instantly after creation
  297. int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);
  298. if (connectionIndex != -1)
  299. {
  300. draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);
  301. if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
  302. EditorUtility.SetDirty(graph);
  303. }
  304. }
  305. // Open context menu for auto-connection if there is no target node
  306. else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate &&
  307. autoConnectOutput != null)
  308. {
  309. GenericMenu menu = new GenericMenu();
  310. graphEditor.AddContextMenuItems(menu);
  311. menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
  312. }
  313. //Release dragged connection
  314. draggedOutput = null;
  315. draggedOutputTarget = null;
  316. EditorUtility.SetDirty(graph);
  317. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  318. }
  319. else if (currentActivity == NodeActivity.DragNode)
  320. {
  321. IEnumerable<XNode.Node> nodes = _selectedNodes;
  322. foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node);
  323. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  324. }
  325. else if (!IsHoveringNode)
  326. {
  327. // If click outside node, release field focus
  328. if (!isPanning)
  329. {
  330. EditorGUI.FocusTextInControl(null);
  331. EditorGUIUtility.editingTextField = false;
  332. }
  333. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  334. }
  335. // If click node header, select it.
  336. if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift))
  337. {
  338. selectedReroutes.Clear();
  339. SelectNode(hoveredNode, false);
  340. // Double click to center node
  341. if (isDoubleClick)
  342. {
  343. Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode)
  344. ? nodeSizes[hoveredNode] / 2
  345. : Vector2.zero;
  346. panOffset = -hoveredNode.position - nodeDimension;
  347. }
  348. }
  349. // If click reroute, select it.
  350. if (IsHoveringReroute && !(e.control || e.shift))
  351. {
  352. selectedReroutes = new List<RerouteReference>() { hoveredReroute };
  353. _selectedNodes.Clear();
  354. }
  355. Repaint();
  356. currentActivity = NodeActivity.Idle;
  357. }
  358. else if (e.button == 1 || e.button == 2)
  359. {
  360. if (!isPanning)
  361. {
  362. if (IsDraggingPort)
  363. {
  364. draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition));
  365. }
  366. else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null &&
  367. selectedReroutes.Count == 1)
  368. {
  369. selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint());
  370. selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port,
  371. selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1);
  372. }
  373. else if (IsHoveringReroute)
  374. {
  375. ShowRerouteContextMenu(hoveredReroute);
  376. }
  377. else if (IsHoveringPort)
  378. {
  379. ShowPortContextMenu(hoveredPort);
  380. }
  381. else if (IsHoveringNode && IsHoveringTitle(hoveredNode))
  382. {
  383. if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false);
  384. autoConnectOutput = null;
  385. GenericMenu menu = new GenericMenu();
  386. NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);
  387. menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
  388. 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.
  389. }
  390. else if (!IsHoveringNode)
  391. {
  392. autoConnectOutput = null;
  393. GenericMenu menu = new GenericMenu();
  394. graphEditor.AddContextMenuItems(menu);
  395. menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
  396. }
  397. }
  398. isPanning = false;
  399. }
  400. // Reset DoubleClick
  401. isDoubleClick = false;
  402. break;
  403. case EventType.KeyDown:
  404. // if (EditorGUIUtility.editingTextField) break;
  405. // else if (e.keyCode == KeyCode.F) Home();
  406. // if (NodeEditorUtilities.IsMac())
  407. // {
  408. // if (e.keyCode == KeyCode.Return) RenameSelectedNode();
  409. // }
  410. // else
  411. // {
  412. // if (e.keyCode == KeyCode.F2) RenameSelectedNode();
  413. // }
  414. //
  415. // if (e.keyCode == KeyCode.A)
  416. // {
  417. // if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node)))
  418. // {
  419. // foreach (XNode.Node node in graph.nodes)
  420. // {
  421. // DeselectNode(node);
  422. // }
  423. // }
  424. // else
  425. // {
  426. // foreach (XNode.Node node in graph.nodes)
  427. // {
  428. // SelectNode(node, true);
  429. // }
  430. // }
  431. //
  432. // Repaint();
  433. // }
  434. //
  435. // break;
  436. case EventType.ValidateCommand:
  437. case EventType.ExecuteCommand:
  438. if (e.commandName == "SoftDelete")
  439. {
  440. if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
  441. e.Use();
  442. }
  443. else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete")
  444. {
  445. if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
  446. e.Use();
  447. }
  448. else if (e.commandName == "Duplicate")
  449. {
  450. if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();
  451. e.Use();
  452. }
  453. else if (e.commandName == "Copy")
  454. {
  455. if (e.type == EventType.ExecuteCommand) CopySelectedNodes();
  456. e.Use();
  457. }
  458. else if (e.commandName == "Paste")
  459. {
  460. if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));
  461. e.Use();
  462. }
  463. Repaint();
  464. break;
  465. case EventType.Ignore:
  466. // If release mouse outside window
  467. if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid)
  468. {
  469. Repaint();
  470. currentActivity = NodeActivity.Idle;
  471. }
  472. break;
  473. }
  474. }
  475. private void RecalculateDragOffsets(Event current)
  476. {
  477. dragOffset = new Vector2[_selectedNodes.Count + selectedReroutes.Count];
  478. // Selected nodes
  479. for (int i = 0; i < _selectedNodes.Count; i++)
  480. {
  481. Node node = _selectedNodes[i];
  482. dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);
  483. }
  484. // Selected reroutes
  485. for (int i = 0; i < selectedReroutes.Count; i++)
  486. {
  487. dragOffset[_selectedNodes.Count + i] =
  488. selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition);
  489. }
  490. }
  491. /// <summary> Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin </summary>
  492. public void Home()
  493. {
  494. if (_selectedNodes.Count > 0)
  495. {
  496. Vector2 minPos = _selectedNodes.Select(x => x.position)
  497. .Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
  498. Vector2 maxPos = _selectedNodes
  499. .Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero))
  500. .Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y)));
  501. panOffset = -(minPos + (maxPos - minPos) / 2f);
  502. }
  503. else
  504. {
  505. zoom = 2;
  506. panOffset = Vector2.zero;
  507. }
  508. }
  509. /// <summary> Remove nodes in the graph in _selectedNodes</summary>
  510. public void RemoveSelectedNodes()
  511. {
  512. // We need to delete reroutes starting at the highest point index to avoid shifting indices
  513. selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList();
  514. for (int i = 0; i < selectedReroutes.Count; i++)
  515. {
  516. selectedReroutes[i].RemovePoint();
  517. }
  518. selectedReroutes.Clear();
  519. foreach (var node in _selectedNodes)
  520. {
  521. if (!node.IsRemove())
  522. {
  523. continue;
  524. }
  525. graphEditor.RemoveNode(node);
  526. }
  527. }
  528. /// <summary> Initiate a rename on the currently selected node </summary>
  529. public void RenameSelectedNode()
  530. {
  531. if (_selectedNodes.Count == 1 && Selection.activeObject is XNode.Node)
  532. {
  533. XNode.Node node = Selection.activeObject as XNode.Node;
  534. Vector2 size;
  535. if (nodeSizes.TryGetValue(node, out size))
  536. {
  537. RenamePopup.Show(Selection.activeObject, size.x);
  538. }
  539. else
  540. {
  541. RenamePopup.Show(Selection.activeObject);
  542. }
  543. }
  544. }
  545. /// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary>
  546. public void MoveNodeToTop(XNode.Node node)
  547. {
  548. int index;
  549. while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1)
  550. {
  551. graph.nodes[index] = graph.nodes[index + 1];
  552. graph.nodes[index + 1] = node;
  553. }
  554. }
  555. /// <summary> Duplicate selected nodes and select the duplicates </summary>
  556. public void DuplicateSelectedNodes()
  557. {
  558. // Get selected nodes which are part of this graph
  559. XNode.Node[] selectedNodes = _selectedNodes.Select(x => x as XNode.Node)
  560. .Where(x => x != null && x.graph == graph).ToArray();
  561. if (selectedNodes == null || selectedNodes.Length == 0) return;
  562. // Get top left node position
  563. Vector2 topLeftNode = selectedNodes.Select(x => x.position)
  564. .Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
  565. InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30));
  566. }
  567. public void CopySelectedNodes()
  568. {
  569. copyBuffer = _selectedNodes.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph)
  570. .ToArray();
  571. }
  572. public void PasteNodes(Vector2 pos)
  573. {
  574. InsertDuplicateNodes(copyBuffer, pos);
  575. }
  576. private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft)
  577. {
  578. if (nodes == null || nodes.Length == 0) return;
  579. // Get top-left node
  580. Vector2 topLeftNode = nodes.Select(x => x.position)
  581. .Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
  582. Vector2 offset = topLeft - topLeftNode;
  583. List<Node> newNodes = new List<Node>();
  584. Dictionary<XNode.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>();
  585. for (int i = 0; i < nodes.Length; i++)
  586. {
  587. XNode.Node srcNode = nodes[i];
  588. if (srcNode == null) continue;
  589. // Check if user is allowed to add more of given node type
  590. XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
  591. Type nodeType = srcNode.GetType();
  592. if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib))
  593. {
  594. int typeCount = graph.nodes.Count(x => x.GetType() == nodeType);
  595. if (typeCount >= disallowAttrib.max) continue;
  596. }
  597. XNode.Node newNode = graphEditor.CopyNode(srcNode);
  598. substitutes.Add(srcNode, newNode);
  599. newNode.position = srcNode.position + offset;
  600. newNode.ResetUniqueID();
  601. newNodes.Add(newNode);
  602. }
  603. // Walk through the selected nodes again, recreate connections, using the new nodes
  604. for (int i = 0; i < nodes.Length; i++)
  605. {
  606. XNode.Node srcNode = nodes[i];
  607. if (srcNode == null) continue;
  608. foreach (XNode.NodePort port in srcNode.Ports)
  609. {
  610. for (int c = 0; c < port.ConnectionCount; c++)
  611. {
  612. XNode.NodePort inputPort =
  613. port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c);
  614. XNode.NodePort outputPort =
  615. port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c);
  616. XNode.Node newNodeIn, newNodeOut;
  617. if (substitutes.TryGetValue(inputPort.node, out newNodeIn) &&
  618. substitutes.TryGetValue(outputPort.node, out newNodeOut))
  619. {
  620. newNodeIn.UpdatePorts();
  621. newNodeOut.UpdatePorts();
  622. inputPort = newNodeIn.GetInputPort(inputPort.fieldName);
  623. outputPort = newNodeOut.GetOutputPort(outputPort.fieldName);
  624. }
  625. if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort);
  626. }
  627. }
  628. }
  629. // Select the new nodes
  630. _selectedNodes = newNodes;
  631. }
  632. /// <summary> Draw a connection as we are dragging it </summary>
  633. public void DrawDraggedConnection()
  634. {
  635. if (IsDraggingPort)
  636. {
  637. Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null);
  638. float thickness = graphEditor.GetNoodleThickness(draggedOutput, null);
  639. NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null);
  640. NoodleStroke stroke = graphEditor.GetNoodleStroke(draggedOutput, null);
  641. Rect fromRect;
  642. if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
  643. List<Vector2> gridPoints = new List<Vector2>();
  644. gridPoints.Add(fromRect.center);
  645. for (int i = 0; i < draggedOutputReroutes.Count; i++)
  646. {
  647. gridPoints.Add(draggedOutputReroutes[i]);
  648. }
  649. if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);
  650. else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition));
  651. DrawNoodle(gradient, path, stroke, thickness, gridPoints);
  652. Color bgcol = Color.black;
  653. Color frcol = gradient.colorKeys[0].color;
  654. bgcol.a = 0.6f;
  655. frcol.a = 0.6f;
  656. // Loop through reroute points again and draw the points
  657. for (int i = 0; i < draggedOutputReroutes.Count; i++)
  658. {
  659. // Draw reroute point at position
  660. Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));
  661. rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
  662. rect = GridToWindowRect(rect);
  663. NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol);
  664. }
  665. }
  666. }
  667. bool IsHoveringTitle(XNode.Node node)
  668. {
  669. Vector2 mousePos = Event.current.mousePosition;
  670. //Get node position
  671. Vector2 nodePos = GridToWindowPosition(node.position);
  672. float width;
  673. Vector2 size;
  674. if (nodeSizes.TryGetValue(node, out size)) width = size.x;
  675. else width = 200;
  676. Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom));
  677. return windowRect.Contains(mousePos);
  678. }
  679. /// <summary> Attempt to connect dragged output to target node </summary>
  680. public void AutoConnect(XNode.Node node)
  681. {
  682. if (autoConnectOutput == null) return;
  683. // Find input port of same type
  684. XNode.NodePort inputPort =
  685. node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType);
  686. // Fallback to input port
  687. if (inputPort == null) inputPort = node.Ports.FirstOrDefault(x => x.IsInput);
  688. // Autoconnect if connection is compatible
  689. if (inputPort != null && inputPort.CanConnectTo(autoConnectOutput)) autoConnectOutput.Connect(inputPort);
  690. // Save changes
  691. EditorUtility.SetDirty(graph);
  692. if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
  693. autoConnectOutput = null;
  694. }
  695. }
  696. }