BaseEdgeDragHelper.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. using UnityEditor.Experimental.GraphView;
  2. using UnityEngine.UIElements;
  3. using UnityEngine;
  4. using System.Collections.Generic;
  5. using System;
  6. using System.Linq;
  7. namespace GraphProcessor
  8. {
  9. public class BaseEdgeDragHelper : EdgeDragHelper
  10. {
  11. // https://github.com/Unity-Technologies/UnityCsReference/blob/master/Modules/GraphViewEditor/Manipulators/EdgeDragHelper.cs#L21
  12. internal const int k_PanAreaWidth = 30;
  13. internal const int k_PanSpeed = 4;
  14. internal const int k_PanInterval = 10;
  15. internal const float k_MinSpeedFactor = 0.5f;
  16. internal const float k_MaxSpeedFactor = 7f;
  17. internal const float k_MaxPanSpeed = k_MaxSpeedFactor * k_PanSpeed;
  18. internal const float kPortDetectionWidth = 30;
  19. protected Dictionary<BaseNodeView, List<PortView>> compatiblePorts = new Dictionary<BaseNodeView, List<PortView>>();
  20. private Edge ghostEdge;
  21. protected GraphView graphView;
  22. protected static NodeAdapter nodeAdapter = new NodeAdapter();
  23. protected readonly IEdgeConnectorListener listener;
  24. private IVisualElementScheduledItem panSchedule;
  25. private Vector3 panDiff = Vector3.zero;
  26. private bool wasPanned;
  27. public bool resetPositionOnPan { get; set; }
  28. public BaseEdgeDragHelper(IEdgeConnectorListener listener)
  29. {
  30. this.listener = listener;
  31. resetPositionOnPan = true;
  32. Reset();
  33. }
  34. public override Edge edgeCandidate { get; set; }
  35. public override Port draggedPort { get; set; }
  36. public override void Reset(bool didConnect = false)
  37. {
  38. if (compatiblePorts != null && graphView != null)
  39. {
  40. // Reset the highlights.
  41. graphView.ports.ForEach((p) => {
  42. p.OnStopEdgeDragging();
  43. });
  44. compatiblePorts.Clear();
  45. }
  46. // Clean up ghost edge.
  47. if ((ghostEdge != null) && (graphView != null))
  48. {
  49. var pv = ghostEdge.input as PortView;
  50. graphView.schedule.Execute(() => {
  51. pv.portCapLit = false;
  52. // pv.UpdatePortView(pv.portData);
  53. }).ExecuteLater(10);
  54. graphView.RemoveElement(ghostEdge);
  55. }
  56. if (wasPanned)
  57. {
  58. if (!resetPositionOnPan || didConnect)
  59. {
  60. Vector3 p = graphView.contentViewContainer.transform.position;
  61. Vector3 s = graphView.contentViewContainer.transform.scale;
  62. graphView.UpdateViewTransform(p, s);
  63. }
  64. }
  65. if (panSchedule != null)
  66. panSchedule.Pause();
  67. if (ghostEdge != null)
  68. {
  69. ghostEdge.input = null;
  70. ghostEdge.output = null;
  71. }
  72. if (draggedPort != null && !didConnect)
  73. {
  74. draggedPort.portCapLit = false;
  75. draggedPort = null;
  76. }
  77. if (edgeCandidate != null)
  78. {
  79. edgeCandidate.SetEnabled(true);
  80. }
  81. ghostEdge = null;
  82. edgeCandidate = null;
  83. graphView = null;
  84. }
  85. public override bool HandleMouseDown(MouseDownEvent evt)
  86. {
  87. Vector2 mousePosition = evt.mousePosition;
  88. if ((draggedPort == null) || (edgeCandidate == null))
  89. {
  90. return false;
  91. }
  92. graphView = draggedPort.GetFirstAncestorOfType<GraphView>();
  93. if (graphView == null)
  94. {
  95. return false;
  96. }
  97. if (edgeCandidate.parent == null)
  98. {
  99. graphView.AddElement(edgeCandidate);
  100. }
  101. bool startFromOutput = (draggedPort.direction == Direction.Output);
  102. edgeCandidate.candidatePosition = mousePosition;
  103. edgeCandidate.SetEnabled(false);
  104. if (startFromOutput)
  105. {
  106. edgeCandidate.output = draggedPort;
  107. edgeCandidate.input = null;
  108. }
  109. else
  110. {
  111. edgeCandidate.output = null;
  112. edgeCandidate.input = draggedPort;
  113. }
  114. draggedPort.portCapLit = true;
  115. compatiblePorts.Clear();
  116. foreach (PortView port in graphView.GetCompatiblePorts(draggedPort, nodeAdapter))
  117. {
  118. compatiblePorts.TryGetValue(port.owner, out var portList);
  119. if (portList == null)
  120. portList = compatiblePorts[port.owner] = new List<PortView>();
  121. portList.Add(port);
  122. }
  123. // Sort ports by position in the node
  124. foreach (var kp in compatiblePorts)
  125. kp.Value.Sort((e1, e2) => e1.worldBound.y.CompareTo(e2.worldBound.y));
  126. // Only light compatible anchors when dragging an edge.
  127. graphView.ports.ForEach((p) => {
  128. p.OnStartEdgeDragging();
  129. });
  130. foreach (var kp in compatiblePorts)
  131. foreach (var port in kp.Value)
  132. port.highlight = true;
  133. edgeCandidate.UpdateEdgeControl();
  134. if (panSchedule == null)
  135. {
  136. panSchedule = graphView.schedule.Execute(Pan).Every(k_PanInterval).StartingIn(k_PanInterval);
  137. panSchedule.Pause();
  138. }
  139. wasPanned = false;
  140. edgeCandidate.layer = Int32.MaxValue;
  141. return true;
  142. }
  143. internal Vector2 GetEffectivePanSpeed(Vector2 mousePos)
  144. {
  145. Vector2 effectiveSpeed = Vector2.zero;
  146. if (mousePos.x <= k_PanAreaWidth)
  147. effectiveSpeed.x = -(((k_PanAreaWidth - mousePos.x) / k_PanAreaWidth) + 0.5f) * k_PanSpeed;
  148. else if (mousePos.x >= graphView.contentContainer.layout.width - k_PanAreaWidth)
  149. effectiveSpeed.x = (((mousePos.x - (graphView.contentContainer.layout.width - k_PanAreaWidth)) / k_PanAreaWidth) + 0.5f) * k_PanSpeed;
  150. if (mousePos.y <= k_PanAreaWidth)
  151. effectiveSpeed.y = -(((k_PanAreaWidth - mousePos.y) / k_PanAreaWidth) + 0.5f) * k_PanSpeed;
  152. else if (mousePos.y >= graphView.contentContainer.layout.height - k_PanAreaWidth)
  153. effectiveSpeed.y = (((mousePos.y - (graphView.contentContainer.layout.height - k_PanAreaWidth)) / k_PanAreaWidth) + 0.5f) * k_PanSpeed;
  154. effectiveSpeed = Vector2.ClampMagnitude(effectiveSpeed, k_MaxPanSpeed);
  155. return effectiveSpeed;
  156. }
  157. Vector2 lastMousePos;
  158. public override void HandleMouseMove(MouseMoveEvent evt)
  159. {
  160. var ve = (VisualElement)evt.target;
  161. Vector2 gvMousePos = ve.ChangeCoordinatesTo(graphView.contentContainer, evt.localMousePosition);
  162. panDiff = GetEffectivePanSpeed(gvMousePos);
  163. if (panDiff != Vector3.zero)
  164. panSchedule.Resume();
  165. else
  166. panSchedule.Pause();
  167. Vector2 mousePosition = evt.mousePosition;
  168. lastMousePos = evt.mousePosition;
  169. edgeCandidate.candidatePosition = mousePosition;
  170. // Draw ghost edge if possible port exists.
  171. Port endPort = GetEndPort(mousePosition);
  172. if (endPort != null)
  173. {
  174. if (ghostEdge == null)
  175. {
  176. ghostEdge = CreateEdgeView();
  177. ghostEdge.isGhostEdge = true;
  178. ghostEdge.pickingMode = PickingMode.Ignore;
  179. graphView.AddElement(ghostEdge);
  180. }
  181. if (edgeCandidate.output == null)
  182. {
  183. ghostEdge.input = edgeCandidate.input;
  184. if (ghostEdge.output != null)
  185. ghostEdge.output.portCapLit = false;
  186. ghostEdge.output = endPort;
  187. ghostEdge.output.portCapLit = true;
  188. }
  189. else
  190. {
  191. if (ghostEdge.input != null)
  192. ghostEdge.input.portCapLit = false;
  193. ghostEdge.input = endPort;
  194. ghostEdge.input.portCapLit = true;
  195. ghostEdge.output = edgeCandidate.output;
  196. }
  197. }
  198. else if (ghostEdge != null)
  199. {
  200. if (edgeCandidate.input == null)
  201. {
  202. if (ghostEdge.input != null)
  203. ghostEdge.input.portCapLit = false;
  204. }
  205. else
  206. {
  207. if (ghostEdge.output != null)
  208. ghostEdge.output.portCapLit = false;
  209. }
  210. graphView.RemoveElement(ghostEdge);
  211. ghostEdge.input = null;
  212. ghostEdge.output = null;
  213. ghostEdge = null;
  214. }
  215. }
  216. protected virtual EdgeView CreateEdgeView()
  217. {
  218. return new EdgeView();
  219. }
  220. private void Pan(TimerState ts)
  221. {
  222. graphView.viewTransform.position -= panDiff;
  223. // Workaround to force edge to update when we pan the graph
  224. edgeCandidate.output = edgeCandidate.output;
  225. edgeCandidate.input = edgeCandidate.input;
  226. edgeCandidate.UpdateEdgeControl();
  227. wasPanned = true;
  228. }
  229. public override void HandleMouseUp(MouseUpEvent evt)
  230. {
  231. bool didConnect = false;
  232. Vector2 mousePosition = evt.mousePosition;
  233. // Reset the highlights.
  234. graphView.ports.ForEach((p) => {
  235. p.OnStopEdgeDragging();
  236. });
  237. // Clean up ghost edges.
  238. if (ghostEdge != null)
  239. {
  240. if (ghostEdge.input != null)
  241. ghostEdge.input.portCapLit = false;
  242. if (ghostEdge.output != null)
  243. ghostEdge.output.portCapLit = false;
  244. graphView.RemoveElement(ghostEdge);
  245. ghostEdge.input = null;
  246. ghostEdge.output = null;
  247. ghostEdge = null;
  248. }
  249. Port endPort = GetEndPort(mousePosition);
  250. if (endPort == null && listener != null)
  251. {
  252. listener.OnDropOutsidePort(edgeCandidate, mousePosition);
  253. }
  254. edgeCandidate.SetEnabled(true);
  255. if (edgeCandidate.input != null)
  256. edgeCandidate.input.portCapLit = false;
  257. if (edgeCandidate.output != null)
  258. edgeCandidate.output.portCapLit = false;
  259. // If it is an existing valid edge then delete and notify the model (using DeleteElements()).
  260. if (edgeCandidate.input != null && edgeCandidate.output != null)
  261. {
  262. // Save the current input and output before deleting the edge as they will be reset
  263. Port oldInput = edgeCandidate.input;
  264. Port oldOutput = edgeCandidate.output;
  265. graphView.DeleteElements(new[] { edgeCandidate });
  266. // Restore the previous input and output
  267. edgeCandidate.input = oldInput;
  268. edgeCandidate.output = oldOutput;
  269. }
  270. // otherwise, if it is an temporary edge then just remove it as it is not already known my the model
  271. else
  272. {
  273. graphView.RemoveElement(edgeCandidate);
  274. }
  275. if (endPort != null)
  276. {
  277. if (endPort.direction == Direction.Output)
  278. edgeCandidate.output = endPort;
  279. else
  280. edgeCandidate.input = endPort;
  281. listener.OnDrop(graphView, edgeCandidate);
  282. didConnect = true;
  283. }
  284. else
  285. {
  286. edgeCandidate.output = null;
  287. edgeCandidate.input = null;
  288. }
  289. edgeCandidate.ResetLayer();
  290. edgeCandidate = null;
  291. compatiblePorts.Clear();
  292. Reset(didConnect);
  293. }
  294. Rect GetPortBounds(BaseNodeView nodeView, int index, List<PortView> portList)
  295. {
  296. var port = portList[index];
  297. var bounds = port.worldBound;
  298. if (port.orientation == Orientation.Horizontal)
  299. {
  300. // Increase horizontal port bounds
  301. bounds.xMin = nodeView.worldBound.xMin;
  302. bounds.xMax = nodeView.worldBound.xMax;
  303. if (index == 0)
  304. bounds.yMin = nodeView.worldBound.yMin;
  305. if (index == portList.Count - 1)
  306. bounds.yMax = nodeView.worldBound.yMax;
  307. if (index > 0)
  308. {
  309. Rect above = portList[index - 1].worldBound;
  310. bounds.yMin = (above.yMax + bounds.yMin) / 2.0f;
  311. }
  312. if (index < portList.Count - 1)
  313. {
  314. Rect below = portList[index + 1].worldBound;
  315. bounds.yMax = (below.yMin + bounds.yMax) / 2.0f;
  316. }
  317. if (port.direction == Direction.Input)
  318. bounds.xMin -= kPortDetectionWidth;
  319. else
  320. bounds.xMax += kPortDetectionWidth;
  321. }
  322. else
  323. {
  324. // Increase vertical port bounds
  325. if (port.direction == Direction.Input)
  326. bounds.yMin -= kPortDetectionWidth;
  327. else
  328. bounds.yMax += kPortDetectionWidth;
  329. }
  330. return bounds;
  331. }
  332. private Port GetEndPort(Vector2 mousePosition)
  333. {
  334. if (graphView == null)
  335. return null;
  336. Port bestPort = null;
  337. float bestDistance = 1e20f;
  338. foreach (var kp in compatiblePorts)
  339. {
  340. var nodeView = kp.Key;
  341. var portList = kp.Value;
  342. // We know that the port in the list is top to bottom in term of layout
  343. for (int i = 0; i < portList.Count; i++)
  344. {
  345. var port = portList[i];
  346. Rect bounds = GetPortBounds(nodeView, i, portList);
  347. float distance = Vector2.Distance(port.worldBound.position, mousePosition);
  348. // Check if mouse is over port.
  349. if (bounds.Contains(mousePosition) && distance < bestDistance)
  350. {
  351. bestPort = port;
  352. bestDistance = distance;
  353. }
  354. }
  355. }
  356. return bestPort;
  357. }
  358. }
  359. }