Graph.hx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. package hide.view;
  2. import haxe.Timer;
  3. import haxe.rtti.Meta;
  4. import js.jquery.JQuery;
  5. import h2d.col.Point;
  6. import h2d.col.IPoint;
  7. import hide.comp.SVG;
  8. import hide.view.shadereditor.Box;
  9. import hrt.shgraph.ShaderNode;
  10. import hrt.shgraph.ShaderType;
  11. import hrt.shgraph.ShaderType.SType;
  12. enum EdgeState { None; FromInput; FromOutput; }
  13. typedef Edge = { from : Box, nodeFrom : JQuery, to : Box, nodeTo : JQuery, elt : JQuery };
  14. class Graph extends FileView {
  15. var parent : JQuery;
  16. var editor : SVG;
  17. var editorMatrix : JQuery;
  18. var statusBar : JQuery;
  19. var listOfBoxes : Array<Box> = [];
  20. var listOfEdges : Array<Edge> = [];
  21. var transformMatrix : Array<Float> = [1, 0, 0, 1, 0, 0];
  22. var isPanning : Bool = false;
  23. static var MAX_ZOOM = 1.3;
  24. static var CENTER_OFFSET_Y = 0.1; // percent of height
  25. // used for moving when mouse is close to borders
  26. static var BORDER_SIZE = 50;
  27. static var SPEED_BORDER_MOVE = 0.05;
  28. var timerUpdateView : Timer;
  29. // used for selection
  30. var listOfBoxesSelected : Array<Box> = [];
  31. var recSelection : JQuery;
  32. var startRecSelection : h2d.col.Point;
  33. var lastClickDrag : h2d.col.Point;
  34. var lastClickPan : h2d.col.Point;
  35. // used to build edge
  36. static var NODE_TRIGGER_NEAR = 2000.0;
  37. var isCreatingLink : EdgeState = None;
  38. var edgeStyle = {stroke : ""};
  39. var startLinkBox : Box;
  40. var endLinkBox : Box;
  41. var startLinkGrNode : JQuery;
  42. var endLinkNode : JQuery;
  43. var currentLink : JQuery; // draft of edge
  44. // used for deleting
  45. var currentEdge : Edge;
  46. override function onDisplay() {
  47. element.html('
  48. <div class="flex vertical" >
  49. <div class="flex-elt graph-view" tabindex="0" >
  50. <div class="heaps-scene" tabindex="1" >
  51. </div>
  52. <div id="rightPanel" class="tabs" >
  53. </div>
  54. </div>
  55. </div>');
  56. parent = element.find(".heaps-scene");
  57. editor = new SVG(parent);
  58. statusBar = new Element('<div id="status-bar" ><pre> </pre></div>').appendTo(parent).find("pre");
  59. statusBar.on("wheel", (e) -> { e.stopPropagation(); });
  60. editorMatrix = editor.group(editor.element);
  61. // rectangle Selection
  62. parent.on("mousedown", function(e) {
  63. if (e.button == 0) {
  64. startRecSelection = new Point(lX(e.clientX), lY(e.clientY));
  65. if (currentEdge != null) {
  66. currentEdge.elt.removeClass("selected");
  67. currentEdge = null;
  68. }
  69. clearSelectionBoxes();
  70. return;
  71. }
  72. if (e.button == 1) {
  73. lastClickPan = new Point(e.clientX, e.clientY);
  74. isPanning = true;
  75. return;
  76. }
  77. });
  78. parent.on("mousemove", function(e : js.jquery.Event) {
  79. e.preventDefault();
  80. e.cancelBubble=true;
  81. e.returnValue=false;
  82. mouseMoveFunction(e.clientX, e.clientY);
  83. });
  84. var document = new Element(js.Browser.document);
  85. document.on("mouseup", function(e) {
  86. if(timerUpdateView != null)
  87. stopUpdateViewPosition();
  88. if (e.button == 0) {
  89. // Stop rectangle selection
  90. lastClickDrag = null;
  91. startRecSelection = null;
  92. if (recSelection != null) {
  93. recSelection.remove();
  94. recSelection = null;
  95. for (b in listOfBoxes)
  96. if (b.selected)
  97. listOfBoxesSelected.push(b);
  98. return;
  99. }
  100. return;
  101. }
  102. // Stop panning
  103. if (e.button == 1) {
  104. lastClickDrag = null;
  105. isPanning = false;
  106. return;
  107. }
  108. });
  109. // Zoom control
  110. parent.on("wheel", function(e) {
  111. if (e.originalEvent.deltaY < 0) {
  112. zoom(1.1, e.clientX, e.clientY);
  113. } else {
  114. zoom(0.9, e.clientX, e.clientY);
  115. }
  116. });
  117. listOfBoxes = [];
  118. listOfEdges = [];
  119. updateMatrix();
  120. }
  121. function mouseMoveFunction(clientX : Int, clientY : Int) {
  122. if (isCreatingLink != None) {
  123. startUpdateViewPosition();
  124. createLink(clientX, clientY);
  125. return;
  126. }
  127. // Moving edge
  128. if (currentEdge != null) {
  129. var distOutput = distanceToElement(currentEdge.nodeFrom, clientX, clientY);
  130. var distInput = distanceToElement(currentEdge.nodeTo, clientX, clientY);
  131. if (distOutput > distInput) {
  132. replaceEdge(FromOutput, currentEdge.nodeTo, clientX, clientY);
  133. } else {
  134. replaceEdge(FromInput, currentEdge, clientX, clientY);
  135. }
  136. currentEdge = null;
  137. return;
  138. }
  139. if (isPanning) {
  140. pan(new Point(clientX - lastClickPan.x, clientY - lastClickPan.y));
  141. lastClickPan.x = clientX;
  142. lastClickPan.y = clientY;
  143. return;
  144. }
  145. // Edit rectangle selection
  146. if (startRecSelection != null) {
  147. startUpdateViewPosition();
  148. var endRecSelection = new h2d.col.Point(lX(clientX), lY(clientY));
  149. var xMin = startRecSelection.x;
  150. var xMax = endRecSelection.x;
  151. var yMin = startRecSelection.y;
  152. var yMax = endRecSelection.y;
  153. if (startRecSelection.x > endRecSelection.x) {
  154. xMin = endRecSelection.x;
  155. xMax = startRecSelection.x;
  156. }
  157. if (startRecSelection.y > endRecSelection.y) {
  158. yMin = endRecSelection.y;
  159. yMax = startRecSelection.y;
  160. }
  161. if (recSelection != null) recSelection.remove();
  162. recSelection = editor.rect(editorMatrix, xMin, yMin, xMax - xMin, yMax - yMin).addClass("rect-selection");
  163. for (box in listOfBoxes) {
  164. if (isInside(box, new Point(xMin, yMin), new Point(xMax, yMax))) {
  165. box.setSelected(true);
  166. } else {
  167. box.setSelected(false);
  168. }
  169. }
  170. return;
  171. }
  172. // Move selected boxes
  173. if (listOfBoxesSelected.length > 0 && lastClickDrag != null) {
  174. startUpdateViewPosition();
  175. var dx = (lX(clientX) - lastClickDrag.x);
  176. var dy = (lY(clientY) - lastClickDrag.y);
  177. for (b in listOfBoxesSelected) {
  178. b.setPosition(b.getX() + dx, b.getY() + dy);
  179. updatePosition(b);
  180. // move edges from and to this box
  181. for (edge in listOfEdges) {
  182. if (edge.from == b || edge.to == b) {
  183. edge.elt.remove();
  184. edgeStyle.stroke = edge.nodeFrom.css("fill");
  185. edge.elt = createCurve(edge.nodeFrom, edge.nodeTo);
  186. edge.elt.on("mousedown", function(e) {
  187. e.stopPropagation();
  188. clearSelectionBoxes();
  189. this.currentEdge = edge;
  190. currentEdge.elt.addClass("selected");
  191. });
  192. }
  193. }
  194. }
  195. lastClickDrag.x = lX(clientX);
  196. lastClickDrag.y = lY(clientY);
  197. return;
  198. }
  199. }
  200. dynamic function updatePosition(box : Box) { }
  201. function addBox(p : Point, nodeClass : Class<ShaderNode>, node : ShaderNode) : Box {
  202. var className = std.Type.getClassName(nodeClass);
  203. className = className.substr(className.lastIndexOf(".") + 1);
  204. var box = new Box(editor, editorMatrix, p.x, p.y, node);
  205. var elt = box.getElement();
  206. elt.mousedown(function(e) {
  207. if (e.button != 0)
  208. return;
  209. e.stopPropagation();
  210. lastClickDrag = new Point(lX(e.clientX), lY(e.clientY));
  211. if (!box.selected) {
  212. if (!e.ctrlKey) {
  213. // when not group selection and click on box not selected
  214. clearSelectionBoxes();
  215. listOfBoxesSelected = [box];
  216. } else
  217. listOfBoxesSelected.push(box);
  218. box.setSelected(true);
  219. }
  220. });
  221. elt.mouseup(function(e) {
  222. if (e.button != 0)
  223. return;
  224. lastClickDrag = null;
  225. });
  226. listOfBoxes.push(box);
  227. for (inputKey in box.getInstance().getInputInfoKeys()) {
  228. var inputInfo = box.getInstance().getInputInfo(inputKey);
  229. if (inputInfo == null) {
  230. trace(inputKey);
  231. }
  232. var defaultValue = null;
  233. if (inputInfo.hasProperty) {
  234. defaultValue = Reflect.field(box.getInstance(), 'prop_${inputKey}');
  235. if (defaultValue == null) {
  236. defaultValue = "0";
  237. }
  238. }
  239. var grNode = box.addInput(editor, inputInfo.name, defaultValue, inputInfo.type);
  240. if (defaultValue != null) {
  241. var fieldEditInput = grNode.find("input");
  242. fieldEditInput.on("change", function(ev) {
  243. var tmpValue = Std.parseFloat(fieldEditInput.val());
  244. if (Math.isNaN(tmpValue) ) {
  245. fieldEditInput.addClass("error");
  246. } else {
  247. Reflect.setField(box.getInstance(), 'prop_${inputKey}', tmpValue);
  248. fieldEditInput.val(tmpValue);
  249. fieldEditInput.removeClass("error");
  250. }
  251. });
  252. }
  253. grNode.find(".node").attr("field", inputKey);
  254. grNode.on("mousedown", function(e : js.jquery.Event) {
  255. e.stopPropagation();
  256. var node = grNode.find(".node");
  257. if (node.attr("hasLink") != null) {
  258. replaceEdge(FromOutput, node, e.clientX, e.clientY);
  259. return;
  260. }
  261. isCreatingLink = FromInput;
  262. startLinkGrNode = grNode;
  263. startLinkBox = box;
  264. edgeStyle.stroke = node.css("fill");
  265. setAvailableOutputNodes(box, grNode.find(".node").attr("field"));
  266. });
  267. }
  268. for (outputKey in box.getInstance().getOutputInfoKeys()) {
  269. var outputInfo = box.getInstance().getOutputInfo(outputKey);
  270. var grNode = box.addOutput(editor, outputInfo.name, box.getInstance().getOutputType(outputKey));
  271. grNode.find(".node").attr("field", outputKey);
  272. grNode.on("mousedown", function(e) {
  273. e.stopPropagation();
  274. var node = grNode.find(".node");
  275. isCreatingLink = FromOutput;
  276. startLinkGrNode = grNode;
  277. startLinkBox = box;
  278. edgeStyle.stroke = node.css("fill");
  279. setAvailableInputNodes(box, startLinkGrNode.find(".node").attr("field"));
  280. });
  281. }
  282. box.generateProperties(editor);
  283. return box;
  284. }
  285. function removeBox(box : Box, trackChanges = true) {
  286. removeEdges(box);
  287. box.dispose();
  288. listOfBoxes.remove(box);
  289. }
  290. function removeEdges(box : Box) {
  291. var length = listOfEdges.length;
  292. for (i in 0...length) {
  293. var edge = listOfEdges[length-i-1];
  294. if (edge.from == box || edge.to == box) {
  295. removeEdge(edge); // remove edge from listOfEdges
  296. }
  297. }
  298. }
  299. function removeEdge(edge : Edge) {
  300. edge.elt.remove();
  301. edge.nodeTo.removeAttr("hasLink");
  302. edge.nodeTo.parent().removeClass("hasLink");
  303. listOfEdges.remove(edge);
  304. }
  305. function replaceEdge(state : EdgeState, ?edge : Edge, ?node : JQuery, x : Int, y : Int) {
  306. switch (state) {
  307. case FromOutput:
  308. for (e in listOfEdges) {
  309. if (e.nodeTo.is(node)) {
  310. isCreatingLink = FromOutput;
  311. startLinkGrNode = e.nodeFrom.parent();
  312. startLinkBox = e.from;
  313. edgeStyle.stroke = e.nodeFrom.css("fill");
  314. setAvailableInputNodes(e.from, e.nodeFrom.attr("field"));
  315. removeEdge(e);
  316. createLink(x, y);
  317. return;
  318. }
  319. }
  320. case FromInput:
  321. for (e in listOfEdges) {
  322. if (e.nodeTo.is(edge.nodeTo) && e.nodeFrom.is(edge.nodeFrom)) {
  323. isCreatingLink = FromInput;
  324. startLinkGrNode = e.nodeTo.parent();
  325. startLinkBox = e.to;
  326. edgeStyle.stroke = e.nodeFrom.css("fill");
  327. setAvailableOutputNodes(e.to, e.nodeTo.attr("field"));
  328. removeEdge(e);
  329. createLink(x, y);
  330. return;
  331. }
  332. }
  333. default:
  334. return;
  335. }
  336. }
  337. function setAvailableInputNodes(boxOutput : Box, field : String) {
  338. var type = boxOutput.getInstance().getOutputType(field);
  339. var sType : SType;
  340. if (type == null) {
  341. sType = boxOutput.getInstance().getOutputInfo(field).type;
  342. } else {
  343. sType = ShaderType.getSType(type);
  344. }
  345. for (box in listOfBoxes) {
  346. for (input in box.inputs) {
  347. if (box.getInstance().checkTypeAndCompatibilyInput(input.attr("field"), sType)) {
  348. input.addClass("nodeMatch");
  349. }
  350. }
  351. }
  352. }
  353. function setAvailableOutputNodes(boxInput : Box, field : String) {
  354. for (box in listOfBoxes) {
  355. for (output in box.outputs) {
  356. var outputField = output.attr("field");
  357. var type = box.getInstance().getOutputType(outputField);
  358. var sType : SType;
  359. if (type == null) {
  360. sType = box.getInstance().getOutputInfo(outputField).type;
  361. } else {
  362. sType = ShaderType.getSType(type);
  363. }
  364. if (boxInput.getInstance().checkTypeAndCompatibilyInput(field, sType)) {
  365. output.addClass("nodeMatch");
  366. }
  367. }
  368. }
  369. }
  370. function clearAvailableNodes() {
  371. editor.element.find(".nodeMatch").removeClass("nodeMatch");
  372. }
  373. function error(str : String, ?idBox : Int) {
  374. statusBar.html(str);
  375. statusBar.addClass("error");
  376. new Element(".box").removeClass("error");
  377. if (idBox != null) {
  378. var elt = new Element('#${idBox}');
  379. elt.addClass("error");
  380. }
  381. }
  382. function info(str : String) {
  383. statusBar.html(str);
  384. statusBar.removeClass("error");
  385. new Element(".box").removeClass("error");
  386. }
  387. function createEdgeInEditorGraph(edge) {
  388. listOfEdges.push(edge);
  389. edge.nodeTo.attr("hasLink", "true");
  390. edge.nodeTo.parent().addClass("hasLink");
  391. edge.elt.on("mousedown", function(e) {
  392. e.stopPropagation();
  393. clearSelectionBoxes();
  394. this.currentEdge = edge;
  395. currentEdge.elt.addClass("selected");
  396. });
  397. }
  398. function createLink(clientX : Int, clientY : Int) {
  399. var nearestNode = null;
  400. var minDistNode = NODE_TRIGGER_NEAR;
  401. // checking nearest box
  402. var nearestBox = listOfBoxes[0];
  403. var minDist = distanceToBox(nearestBox, clientX, clientY);
  404. for (i in 1...listOfBoxes.length) {
  405. var tmpDist = distanceToBox(listOfBoxes[i], clientX, clientY);
  406. if (tmpDist < minDist) {
  407. minDist = tmpDist;
  408. nearestBox = listOfBoxes[i];
  409. }
  410. }
  411. // checking nearest node in the nearest box
  412. if (isCreatingLink == FromInput) {
  413. var startIndex = 0;
  414. while (startIndex < nearestBox.outputs.length && !nearestBox.outputs[startIndex].hasClass("nodeMatch")) {
  415. startIndex++;
  416. }
  417. if (startIndex < nearestBox.outputs.length) {
  418. nearestNode = nearestBox.outputs[startIndex];
  419. minDistNode = distanceToElement(nearestNode, clientX, clientY);
  420. for (i in startIndex+1...nearestBox.outputs.length) {
  421. if (!nearestBox.outputs[i].hasClass("nodeMatch"))
  422. continue;
  423. var tmpDist = distanceToElement(nearestBox.outputs[i], clientX, clientY);
  424. if (tmpDist < minDistNode) {
  425. minDistNode = tmpDist;
  426. nearestNode = nearestBox.outputs[i];
  427. }
  428. }
  429. }
  430. } else {
  431. // input has one edge at most
  432. var startIndex = 0;
  433. while (startIndex < nearestBox.inputs.length && !nearestBox.inputs[startIndex].hasClass("nodeMatch")) {
  434. startIndex++;
  435. }
  436. if (startIndex < nearestBox.inputs.length) {
  437. nearestNode = nearestBox.inputs[startIndex];
  438. minDistNode = distanceToElement(nearestNode, clientX, clientY);
  439. for (i in startIndex+1...nearestBox.inputs.length) {
  440. if (!nearestBox.inputs[i].hasClass("nodeMatch"))
  441. continue;
  442. var tmpDist = distanceToElement(nearestBox.inputs[i], clientX, clientY);
  443. if (tmpDist < minDistNode) {
  444. minDistNode = tmpDist;
  445. nearestNode = nearestBox.inputs[i];
  446. }
  447. }
  448. }
  449. }
  450. if (minDistNode < NODE_TRIGGER_NEAR) {
  451. endLinkNode = nearestNode;
  452. endLinkBox = nearestBox;
  453. } else {
  454. endLinkNode = null;
  455. endLinkBox = null;
  456. }
  457. // create edge
  458. if (currentLink != null) currentLink.remove();
  459. currentLink = createCurve(startLinkGrNode.find(".node"), nearestNode, minDistNode, clientX, clientY, true);
  460. }
  461. function createCurve(start : JQuery, end : JQuery, ?distance : Float, ?x : Float, ?y : Float, ?isDraft : Bool) {
  462. var offsetEnd;
  463. var offsetStart = start.offset();
  464. if (distance == null || distance < NODE_TRIGGER_NEAR) {
  465. offsetEnd = end.offset();
  466. } else {
  467. offsetEnd = { top : y, left : x };
  468. }
  469. if (isCreatingLink == FromInput) {
  470. var tmp = offsetStart;
  471. offsetStart = offsetEnd;
  472. offsetEnd = tmp;
  473. }
  474. var startX = lX(offsetStart.left) + Box.NODE_RADIUS;
  475. var startY = lY(offsetStart.top) + Box.NODE_RADIUS;
  476. var diffDistanceY = offsetEnd.top - offsetStart.top;
  477. var signCurveY = ((diffDistanceY > 0) ? -1 : 1);
  478. diffDistanceY = Math.abs(diffDistanceY);
  479. var valueCurveX = 100;
  480. var valueCurveY = 1;
  481. var maxDistanceY = 900;
  482. var curve = editor.curve(null,
  483. startX,
  484. startY,
  485. lX(offsetEnd.left) + Box.NODE_RADIUS,
  486. lY(offsetEnd.top) + Box.NODE_RADIUS,
  487. startX + valueCurveX * (Math.min(maxDistanceY, diffDistanceY)/maxDistanceY),
  488. startY + signCurveY * valueCurveY * (Math.min(maxDistanceY, diffDistanceY)/maxDistanceY),
  489. edgeStyle)
  490. .addClass("edge");
  491. editorMatrix.prepend(curve);
  492. if (isDraft)
  493. curve.addClass("draft");
  494. return curve;
  495. }
  496. function clearSelectionBoxes() {
  497. for(b in listOfBoxesSelected) b.setSelected(false);
  498. listOfBoxesSelected = [];
  499. if (this.currentEdge != null) {
  500. currentEdge.elt.removeClass("selected");
  501. }
  502. }
  503. function startUpdateViewPosition() {
  504. if (timerUpdateView != null)
  505. return;
  506. timerUpdateView = new Timer(0);
  507. timerUpdateView.run = function() {
  508. var posCursor = new Point(ide.mouseX - parent.offset().left, ide.mouseY - parent.offset().top);
  509. var wasUpdated = false;
  510. if (posCursor.x < BORDER_SIZE) {
  511. pan(new Point((BORDER_SIZE - posCursor.x)*SPEED_BORDER_MOVE, 0));
  512. wasUpdated = true;
  513. }
  514. if (posCursor.y < BORDER_SIZE) {
  515. pan(new Point(0, (BORDER_SIZE - posCursor.y)*SPEED_BORDER_MOVE));
  516. wasUpdated = true;
  517. }
  518. var rightBorder = parent.width() - BORDER_SIZE;
  519. if (posCursor.x > rightBorder) {
  520. pan(new Point((rightBorder - posCursor.x)*SPEED_BORDER_MOVE, 0));
  521. wasUpdated = true;
  522. }
  523. var botBorder = parent.height() - BORDER_SIZE;
  524. if (posCursor.y > botBorder) {
  525. pan(new Point(0, (botBorder - posCursor.y)*SPEED_BORDER_MOVE));
  526. wasUpdated = true;
  527. }
  528. mouseMoveFunction(ide.mouseX, ide.mouseY);
  529. };
  530. }
  531. function stopUpdateViewPosition() {
  532. if (timerUpdateView != null) {
  533. timerUpdateView.stop();
  534. timerUpdateView = null;
  535. }
  536. }
  537. function getGraphDims(?boxes) {
  538. if( boxes == null )
  539. boxes = listOfBoxes;
  540. if( boxes.length == 0 ) return null;
  541. var xMin = boxes[0].getX();
  542. var yMin = boxes[0].getY();
  543. var xMax = xMin + boxes[0].getWidth();
  544. var yMax = yMin + boxes[0].getHeight();
  545. for (i in 1...boxes.length) {
  546. var b = boxes[i];
  547. xMin = Math.min(xMin, b.getX());
  548. yMin = Math.min(yMin, b.getY());
  549. xMax = Math.max(xMax, b.getX() + b.getWidth());
  550. yMax = Math.max(yMax, b.getY() + b.getHeight());
  551. }
  552. var center = new IPoint(Std.int(xMin + (xMax - xMin)/2), Std.int(yMin + (yMax - yMin)/2));
  553. center.y += Std.int(editor.element.height()*CENTER_OFFSET_Y);
  554. return {
  555. xMin : xMin,
  556. yMin : yMin,
  557. xMax : xMax,
  558. yMax : yMax,
  559. center : center,
  560. };
  561. }
  562. function centerView() {
  563. if (listOfBoxes.length == 0) return;
  564. var dims = getGraphDims();
  565. var scale = Math.min(1, Math.min((editor.element.width() - 50) / (dims.xMax - dims.xMin), (editor.element.height() - 50) / (dims.yMax - dims.yMin)));
  566. transformMatrix[4] = editor.element.width()/2 - dims.center.x;
  567. transformMatrix[5] = editor.element.height()/2 - dims.center.y;
  568. transformMatrix[0] = scale;
  569. transformMatrix[3] = scale;
  570. var x = editor.element.width()/2;
  571. var y = editor.element.height()/2;
  572. transformMatrix[4] = x - (x - transformMatrix[4]) * scale;
  573. transformMatrix[5] = y - (y - transformMatrix[5]) * scale;
  574. updateMatrix();
  575. }
  576. function clampView() {
  577. if (listOfBoxes.length == 0) return;
  578. var dims = getGraphDims();
  579. var width = editor.element.width();
  580. var height = editor.element.height();
  581. var scale = transformMatrix[0];
  582. if( transformMatrix[4] + dims.xMin * scale > width )
  583. transformMatrix[4] = width - dims.xMin * scale;
  584. if( transformMatrix[4] + dims.xMax * scale < 0 )
  585. transformMatrix[4] = -1 * dims.xMax * scale;
  586. if( transformMatrix[5] + dims.yMin * scale > height )
  587. transformMatrix[5] = height - dims.yMin * scale;
  588. if( transformMatrix[5] + dims.yMax * scale < 0 )
  589. transformMatrix[5] = -1 * dims.yMax * scale;
  590. }
  591. function updateMatrix() {
  592. editorMatrix.attr({transform: 'matrix(${transformMatrix.join(' ')})'});
  593. }
  594. function zoom(scale : Float, x : Int, y : Int) {
  595. if (scale > 1 && transformMatrix[0] > MAX_ZOOM) {
  596. return;
  597. }
  598. transformMatrix[0] *= scale;
  599. transformMatrix[3] *= scale;
  600. x -= Std.int(editor.element.offset().left);
  601. y -= Std.int(editor.element.offset().top);
  602. transformMatrix[4] = x - (x - transformMatrix[4]) * scale;
  603. transformMatrix[5] = y - (y - transformMatrix[5]) * scale;
  604. clampView();
  605. updateMatrix();
  606. }
  607. function pan(p : Point) {
  608. transformMatrix[4] += p.x;
  609. transformMatrix[5] += p.y;
  610. clampView();
  611. updateMatrix();
  612. }
  613. function isVisible() : Bool {
  614. return editor.element.is(":visible");
  615. }
  616. // Useful method
  617. function isInside(b : Box, min : Point, max : Point) {
  618. if (max.x < b.getX() || min.x > b.getX() + b.getWidth())
  619. return false;
  620. if (max.y < b.getY() || min.y > b.getY() + b.getHeight())
  621. return false;
  622. return true;
  623. }
  624. function distanceToBox(b : Box, x : Int, y : Int) {
  625. var dx = Math.max(Math.abs(lX(x) - (b.getX() + (b.getWidth() / 2))) - b.getWidth() / 2, 0);
  626. var dy = Math.max(Math.abs(lY(y) - (b.getY() + (b.getHeight() / 2))) - b.getHeight() / 2, 0);
  627. return dx * dx + dy * dy;
  628. }
  629. function distanceToElement(element : JQuery, x : Int, y : Int) {
  630. if (element == null)
  631. return NODE_TRIGGER_NEAR+1;
  632. var dx = Math.max(Math.abs(x - (element.offset().left + element.width() / 2)) - element.width() / 2, 0);
  633. var dy = Math.max(Math.abs(y - (element.offset().top + element.height() / 2)) - element.height() / 2, 0);
  634. return dx * dx + dy * dy;
  635. }
  636. function gX(x : Float) : Float {
  637. return x*transformMatrix[0] + transformMatrix[4];
  638. }
  639. function gY(y : Float) : Float {
  640. return y*transformMatrix[3] + transformMatrix[5];
  641. }
  642. function gPos(x : Float, y : Float) : Point {
  643. return new Point(gX(x), gY(y));
  644. }
  645. function lX(x : Float) : Float {
  646. var screenOffset = editor.element.offset();
  647. x -= screenOffset.left;
  648. return (x - transformMatrix[4])/transformMatrix[0];
  649. }
  650. function lY(y : Float) : Float {
  651. var screenOffset = editor.element.offset();
  652. y -= screenOffset.top;
  653. return (y - transformMatrix[5])/transformMatrix[3];
  654. }
  655. function lPos(x : Float, y : Float) : Point {
  656. return new Point(lX(x), lY(y));
  657. }
  658. }