graphCanvas.tsx 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. import * as React from "react";
  2. import { GlobalState } from '../globalState';
  3. import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
  4. import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes';
  5. import { GraphNode } from './graphNode';
  6. import * as dagre from 'dagre';
  7. import { Nullable } from 'babylonjs/types';
  8. import { NodeLink } from './nodeLink';
  9. import { NodePort } from './nodePort';
  10. import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection, NodeMaterialConnectionPointCompatibilityStates } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
  11. import { Vector2 } from 'babylonjs/Maths/math.vector';
  12. import { FragmentOutputBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fragmentOutputBlock';
  13. import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
  14. import { DataStorage } from 'babylonjs/Misc/dataStorage';
  15. import { GraphFrame } from './graphFrame';
  16. import { IEditorData } from '../nodeLocationInfo';
  17. import { FrameNodePort } from './frameNodePort';
  18. require("./graphCanvas.scss");
  19. export interface IGraphCanvasComponentProps {
  20. globalState: GlobalState
  21. }
  22. export type FramePortData = {
  23. frame: GraphFrame,
  24. port: FrameNodePort
  25. }
  26. export const isFramePortData = (variableToCheck: any): variableToCheck is FramePortData => {
  27. if (variableToCheck) {
  28. return (variableToCheck as FramePortData).port !== undefined;
  29. }
  30. else return false;
  31. }
  32. export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentProps> {
  33. private readonly MinZoom = 0.1;
  34. private readonly MaxZoom = 4;
  35. private _hostCanvas: HTMLDivElement;
  36. private _graphCanvas: HTMLDivElement;
  37. private _selectionContainer: HTMLDivElement;
  38. private _frameContainer: HTMLDivElement;
  39. private _svgCanvas: HTMLElement;
  40. private _rootContainer: HTMLDivElement;
  41. private _nodes: GraphNode[] = [];
  42. private _links: NodeLink[] = [];
  43. private _mouseStartPointX: Nullable<number> = null;
  44. private _mouseStartPointY: Nullable<number> = null
  45. private _dropPointX = 0;
  46. private _dropPointY = 0;
  47. private _selectionStartX = 0;
  48. private _selectionStartY = 0;
  49. private _candidateLinkedHasMoved = false;
  50. private _x = 0;
  51. private _y = 0;
  52. private _zoom = 1;
  53. private _selectedNodes: GraphNode[] = [];
  54. private _selectedLink: Nullable<NodeLink> = null;
  55. private _selectedPort: Nullable<NodePort> = null;
  56. private _candidateLink: Nullable<NodeLink> = null;
  57. private _candidatePort: Nullable<NodePort | FrameNodePort> = null;
  58. private _gridSize = 20;
  59. private _selectionBox: Nullable<HTMLDivElement> = null;
  60. private _selectedFrame: Nullable<GraphFrame> = null;
  61. private _frameCandidate: Nullable<HTMLDivElement> = null;
  62. private _frames: GraphFrame[] = [];
  63. private _altKeyIsPressed = false;
  64. private _ctrlKeyIsPressed = false;
  65. private _oldY = -1;
  66. public _frameIsMoving = false;
  67. public _isLoading = false;
  68. public get gridSize() {
  69. return this._gridSize;
  70. }
  71. public set gridSize(value: number) {
  72. this._gridSize = value;
  73. this.updateTransform();
  74. }
  75. public get globalState(){
  76. return this.props.globalState;
  77. }
  78. public get nodes() {
  79. return this._nodes;
  80. }
  81. public get links() {
  82. return this._links;
  83. }
  84. public get frames() {
  85. return this._frames;
  86. }
  87. public get zoom() {
  88. return this._zoom;
  89. }
  90. public set zoom(value: number) {
  91. if (this._zoom === value) {
  92. return;
  93. }
  94. this._zoom = value;
  95. this.updateTransform();
  96. }
  97. public get x() {
  98. return this._x;
  99. }
  100. public set x(value: number) {
  101. this._x = value;
  102. this.updateTransform();
  103. }
  104. public get y() {
  105. return this._y;
  106. }
  107. public set y(value: number) {
  108. this._y = value;
  109. this.updateTransform();
  110. }
  111. public get selectedNodes() {
  112. return this._selectedNodes;
  113. }
  114. public get selectedLink() {
  115. return this._selectedLink;
  116. }
  117. public get selectedFrame() {
  118. return this._selectedFrame;
  119. }
  120. public get selectedPort() {
  121. return this._selectedPort;
  122. }
  123. public get canvasContainer() {
  124. return this._graphCanvas;
  125. }
  126. public get hostCanvas() {
  127. return this._hostCanvas;
  128. }
  129. public get svgCanvas() {
  130. return this._svgCanvas;
  131. }
  132. public get selectionContainer() {
  133. return this._selectionContainer;
  134. }
  135. public get frameContainer() {
  136. return this._frameContainer;
  137. }
  138. constructor(props: IGraphCanvasComponentProps) {
  139. super(props);
  140. props.globalState.onSelectionChangedObservable.add(selection => {
  141. if (!selection) {
  142. this._selectedNodes = [];
  143. this._selectedLink = null;
  144. this._selectedFrame = null;
  145. this._selectedPort = null;
  146. } else {
  147. if (selection instanceof NodeLink) {
  148. this._selectedNodes = [];
  149. this._selectedFrame = null;
  150. this._selectedLink = selection;
  151. this._selectedPort = null;
  152. } else if (selection instanceof GraphFrame) {
  153. this._selectedNodes = [];
  154. this._selectedFrame = selection;
  155. this._selectedLink = null;
  156. this._selectedPort = null;
  157. } else if (selection instanceof GraphNode){
  158. if (this._ctrlKeyIsPressed) {
  159. if (this._selectedNodes.indexOf(selection) === -1) {
  160. this._selectedNodes.push(selection);
  161. }
  162. } else {
  163. this._selectedNodes = [selection];
  164. }
  165. } else if(selection instanceof NodePort && !selection.hasLabel()){ // if node port is uneditable, select graphNode instead
  166. props.globalState.onSelectionChangedObservable.notifyObservers(selection.node)
  167. } else if(selection instanceof NodePort){
  168. this._selectedNodes = [];
  169. this._selectedFrame = null;
  170. this._selectedLink = null;
  171. this._selectedPort = selection;
  172. } else {
  173. this._selectedNodes = [];
  174. this._selectedFrame = null;
  175. this._selectedLink = null;
  176. this._selectedPort = selection.port;
  177. }
  178. }
  179. });
  180. props.globalState.onCandidatePortSelectedObservable.add(port => {
  181. this._candidatePort = port;
  182. });
  183. props.globalState.onGridSizeChanged.add(() => {
  184. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  185. });
  186. this.props.globalState.hostDocument!.addEventListener("keyup", () => this.onKeyUp(), false);
  187. this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
  188. this._altKeyIsPressed = evt.altKey;
  189. this._ctrlKeyIsPressed = evt.ctrlKey;
  190. }, false);
  191. this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => {
  192. this._altKeyIsPressed = false;
  193. this._ctrlKeyIsPressed = false;
  194. }, false);
  195. // Store additional data to serialization object
  196. this.props.globalState.storeEditorData = (editorData, graphFrame) => {
  197. editorData.frames = [];
  198. if (graphFrame) {
  199. editorData.frames.push(graphFrame!.serialize());
  200. } else {
  201. editorData.x = this.x;
  202. editorData.y = this.y;
  203. editorData.zoom = this.zoom;
  204. for (var frame of this._frames) {
  205. editorData.frames.push(frame.serialize());
  206. }
  207. }
  208. }
  209. }
  210. public getGridPosition(position: number, useCeil = false) {
  211. let gridSize = this.gridSize;
  212. if (gridSize === 0) {
  213. return position;
  214. }
  215. if (useCeil) {
  216. return gridSize * Math.ceil(position / gridSize);
  217. }
  218. return gridSize * Math.floor(position / gridSize);
  219. }
  220. public getGridPositionCeil(position: number) {
  221. let gridSize = this.gridSize;
  222. if (gridSize === 0) {
  223. return position;
  224. }
  225. return gridSize * Math.ceil(position / gridSize);
  226. }
  227. updateTransform() {
  228. this._rootContainer.style.transform = `translate(${this._x}px, ${this._y}px) scale(${this._zoom})`;
  229. if (DataStorage.ReadBoolean("ShowGrid", true)) {
  230. this._hostCanvas.style.backgroundSize = `${this._gridSize * this._zoom}px ${this._gridSize * this._zoom}px`;
  231. this._hostCanvas.style.backgroundPosition = `${this._x}px ${this._y}px`;
  232. } else {
  233. this._hostCanvas.style.backgroundSize = `0`;
  234. }
  235. }
  236. onKeyUp() {
  237. this._altKeyIsPressed = false;
  238. this._ctrlKeyIsPressed = false;
  239. this._oldY = -1;
  240. }
  241. findNodeFromBlock(block: NodeMaterialBlock) {
  242. return this.nodes.filter(n => n.block === block)[0];
  243. }
  244. reset() {
  245. for (var node of this._nodes) {
  246. node.dispose();
  247. }
  248. const frames = this._frames.splice(0);
  249. for (var frame of frames) {
  250. frame.dispose();
  251. }
  252. this._nodes = [];
  253. this._frames = [];
  254. this._links = [];
  255. this._graphCanvas.innerHTML = "";
  256. this._svgCanvas.innerHTML = "";
  257. }
  258. connectPorts(pointA: NodeMaterialConnectionPoint, pointB: NodeMaterialConnectionPoint) {
  259. var blockA = pointA.ownerBlock;
  260. var blockB = pointB.ownerBlock;
  261. var nodeA = this.findNodeFromBlock(blockA);
  262. var nodeB = this.findNodeFromBlock(blockB);
  263. if (!nodeA || !nodeB) {
  264. return;
  265. }
  266. var portA = nodeA.getPortForConnectionPoint(pointA);
  267. var portB = nodeB.getPortForConnectionPoint(pointB);
  268. if (!portA || !portB) {
  269. return;
  270. }
  271. for (var currentLink of this._links) {
  272. if (currentLink.portA === portA && currentLink.portB === portB) {
  273. return;
  274. }
  275. if (currentLink.portA === portB && currentLink.portB === portA) {
  276. return;
  277. }
  278. }
  279. const link = new NodeLink(this, portA, nodeA, portB, nodeB);
  280. this._links.push(link);
  281. nodeA.links.push(link);
  282. nodeB.links.push(link);
  283. }
  284. removeLink(link: NodeLink) {
  285. let index = this._links.indexOf(link);
  286. if (index > -1) {
  287. this._links.splice(index, 1);
  288. }
  289. link.dispose();
  290. }
  291. appendBlock(block: NodeMaterialBlock) {
  292. let newNode = new GraphNode(block, this.props.globalState);
  293. newNode.appendVisual(this._graphCanvas, this);
  294. this._nodes.push(newNode);
  295. return newNode;
  296. }
  297. distributeGraph() {
  298. this.x = 0;
  299. this.y = 0;
  300. this.zoom = 1;
  301. let graph = new dagre.graphlib.Graph();
  302. graph.setGraph({});
  303. graph.setDefaultEdgeLabel(() => ({}));
  304. graph.graph().rankdir = "LR";
  305. // Build dagre graph
  306. this._nodes.forEach(node => {
  307. if (this._frames.some(f => f.nodes.indexOf(node) !== -1)) {
  308. return;
  309. }
  310. graph.setNode(node.id.toString(), {
  311. id: node.id,
  312. type: "node",
  313. width: node.width,
  314. height: node.height
  315. });
  316. });
  317. this._frames.forEach(frame => {
  318. graph.setNode(frame.id.toString(), {
  319. id: frame.id,
  320. type: "frame",
  321. width: frame.element.clientWidth,
  322. height: frame.element.clientHeight
  323. });
  324. })
  325. this._nodes.forEach(node => {
  326. node.block.outputs.forEach(output => {
  327. if (!output.hasEndpoints) {
  328. return;
  329. }
  330. output.endpoints.forEach(endpoint => {
  331. let sourceFrames = this._frames.filter(f => f.nodes.indexOf(node) !== -1);
  332. let targetFrames = this._frames.filter(f => f.nodes.some(n => n.block === endpoint.ownerBlock));
  333. let sourceId = sourceFrames.length > 0 ? sourceFrames[0].id : node.id;
  334. let targetId = targetFrames.length > 0 ? targetFrames[0].id : endpoint.ownerBlock.uniqueId;
  335. graph.setEdge(sourceId.toString(), targetId.toString());
  336. });
  337. });
  338. });
  339. // Distribute
  340. dagre.layout(graph);
  341. // Update graph
  342. let dagreNodes = graph.nodes().map(node => graph.node(node));
  343. dagreNodes.forEach((dagreNode: any) => {
  344. if (dagreNode.type === "node") {
  345. for (var node of this._nodes) {
  346. if (node.id === dagreNode.id) {
  347. node.x = dagreNode.x - dagreNode.width / 2;
  348. node.y = dagreNode.y - dagreNode.height / 2;
  349. node.cleanAccumulation();
  350. return;
  351. }
  352. }
  353. return;
  354. }
  355. for (var frame of this._frames) {
  356. if (frame.id === dagreNode.id) {
  357. this._frameIsMoving = true;
  358. frame.move(dagreNode.x - dagreNode.width / 2, dagreNode.y - dagreNode.height / 2, false);
  359. frame.cleanAccumulation();
  360. this._frameIsMoving = false;
  361. return;
  362. }
  363. }
  364. });
  365. }
  366. componentDidMount() {
  367. this._hostCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas") as HTMLDivElement;
  368. this._rootContainer = this.props.globalState.hostDocument.getElementById("graph-container") as HTMLDivElement;
  369. this._graphCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas-container") as HTMLDivElement;
  370. this._svgCanvas = this.props.globalState.hostDocument.getElementById("graph-svg-container") as HTMLElement;
  371. this._selectionContainer = this.props.globalState.hostDocument.getElementById("selection-container") as HTMLDivElement;
  372. this._frameContainer = this.props.globalState.hostDocument.getElementById("frame-container") as HTMLDivElement;
  373. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  374. this.updateTransform();
  375. }
  376. onMove(evt: React.PointerEvent) {
  377. // Selection box
  378. if (this._selectionBox) {
  379. const rootRect = this.canvasContainer.getBoundingClientRect();
  380. const localX = evt.pageX - rootRect.left;
  381. const localY = evt.pageY - rootRect.top;
  382. if (localX > this._selectionStartX) {
  383. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  384. this._selectionBox.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  385. } else {
  386. this._selectionBox.style.left = `${localX / this.zoom}px`;
  387. this._selectionBox.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  388. }
  389. if (localY > this._selectionStartY) {
  390. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  391. this._selectionBox.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  392. } else {
  393. this._selectionBox.style.top = `${localY / this.zoom}px`;
  394. this._selectionBox.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  395. }
  396. this.props.globalState.onSelectionBoxMoved.notifyObservers(this._selectionBox.getBoundingClientRect());
  397. return;
  398. }
  399. // Candidate frame box
  400. if (this._frameCandidate) {
  401. const rootRect = this.canvasContainer.getBoundingClientRect();
  402. const localX = evt.pageX - rootRect.left;
  403. const localY = evt.pageY - rootRect.top;
  404. if (localX > this._selectionStartX) {
  405. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  406. this._frameCandidate.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  407. } else {
  408. this._frameCandidate.style.left = `${localX / this.zoom}px`;
  409. this._frameCandidate.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  410. }
  411. if (localY > this._selectionStartY) {
  412. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  413. this._frameCandidate.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  414. } else {
  415. this._frameCandidate.style.top = `${localY / this.zoom}px`;
  416. this._frameCandidate.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  417. }
  418. return;
  419. }
  420. // Candidate link
  421. if (this._candidateLink) {
  422. const rootRect = this.canvasContainer.getBoundingClientRect();
  423. this._candidatePort = null;
  424. this.props.globalState.onCandidateLinkMoved.notifyObservers(new Vector2(evt.pageX, evt.pageY));
  425. this._dropPointX = (evt.pageX - rootRect.left) / this.zoom;
  426. this._dropPointY = (evt.pageY - rootRect.top) / this.zoom;
  427. this._candidateLink.update(this._dropPointX, this._dropPointY, true);
  428. this._candidateLinkedHasMoved = true;
  429. return;
  430. }
  431. // Zoom with mouse + alt
  432. if (this._altKeyIsPressed && evt.buttons === 1) {
  433. if (this._oldY < 0) {
  434. this._oldY = evt.pageY;
  435. }
  436. let zoomDelta = (evt.pageY - this._oldY) / 10;
  437. if (Math.abs(zoomDelta) > 5) {
  438. const oldZoom = this.zoom;
  439. this.zoom = Math.max(Math.min(this.MaxZoom, this.zoom + zoomDelta / 100), this.MinZoom);
  440. const boundingRect = evt.currentTarget.getBoundingClientRect();
  441. const clientWidth = boundingRect.width;
  442. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  443. const clientX = evt.clientX - boundingRect.left;
  444. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  445. this.x = this.x - widthDiff * xFactor;
  446. this._oldY = evt.pageY;
  447. }
  448. return;
  449. }
  450. // Move canvas
  451. this._rootContainer.style.cursor = "move";
  452. if (this._mouseStartPointX === null || this._mouseStartPointY === null) {
  453. return;
  454. }
  455. this.x += evt.clientX - this._mouseStartPointX;
  456. this.y += evt.clientY - this._mouseStartPointY;
  457. this._mouseStartPointX = evt.clientX;
  458. this._mouseStartPointY = evt.clientY;
  459. }
  460. onDown(evt: React.PointerEvent<HTMLElement>) {
  461. this._rootContainer.setPointerCapture(evt.pointerId);
  462. // Selection?
  463. if (evt.currentTarget === this._hostCanvas && evt.ctrlKey) {
  464. this._selectionBox = this.props.globalState.hostDocument.createElement("div");
  465. this._selectionBox.classList.add("selection-box");
  466. this._selectionContainer.appendChild(this._selectionBox);
  467. const rootRect = this.canvasContainer.getBoundingClientRect();
  468. this._selectionStartX = (evt.pageX - rootRect.left);
  469. this._selectionStartY = (evt.pageY - rootRect.top);
  470. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  471. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  472. this._selectionBox.style.width = "0px";
  473. this._selectionBox.style.height = "0px";
  474. return;
  475. }
  476. // Frame?
  477. if (evt.currentTarget === this._hostCanvas && evt.shiftKey) {
  478. this._frameCandidate = this.props.globalState.hostDocument.createElement("div");
  479. this._frameCandidate.classList.add("frame-box");
  480. this._frameContainer.appendChild(this._frameCandidate);
  481. const rootRect = this.canvasContainer.getBoundingClientRect();
  482. this._selectionStartX = (evt.pageX - rootRect.left);
  483. this._selectionStartY = (evt.pageY - rootRect.top);
  484. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  485. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  486. this._frameCandidate.style.width = "0px";
  487. this._frameCandidate.style.height = "0px";
  488. return;
  489. }
  490. // Port dragging
  491. if (evt.nativeEvent.srcElement && (evt.nativeEvent.srcElement as HTMLElement).nodeName === "IMG") {
  492. if (!this._candidateLink) {
  493. let portElement = ((evt.nativeEvent.srcElement as HTMLElement).parentElement as any).port as NodePort;
  494. this._candidateLink = new NodeLink(this, portElement, portElement.node);
  495. this._candidateLinkedHasMoved = false;
  496. }
  497. return;
  498. }
  499. this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
  500. this._mouseStartPointX = evt.clientX;
  501. this._mouseStartPointY = evt.clientY;
  502. }
  503. onUp(evt: React.PointerEvent) {
  504. this._mouseStartPointX = null;
  505. this._mouseStartPointY = null;
  506. this._rootContainer.releasePointerCapture(evt.pointerId);
  507. this._oldY = -1;
  508. if (this._candidateLink) {
  509. if (this._candidateLinkedHasMoved) {
  510. this.processCandidatePort();
  511. this.props.globalState.onCandidateLinkMoved.notifyObservers(null);
  512. } else { // is a click event on NodePort
  513. if(this._candidateLink.portA instanceof FrameNodePort) { //only on Frame Node Ports
  514. const port = this._candidateLink.portA;
  515. const frame = this.frames.find((frame: GraphFrame) => frame.id === port.parentFrameId);
  516. if (frame) {
  517. const data: FramePortData = {
  518. frame,
  519. port
  520. }
  521. this.props.globalState.onSelectionChangedObservable.notifyObservers(data);
  522. }
  523. } else if(this._candidateLink.portA instanceof NodePort){
  524. this.props.globalState.onSelectionChangedObservable.notifyObservers(this._candidateLink.portA );
  525. }
  526. }
  527. this._candidateLink.dispose();
  528. this._candidateLink = null;
  529. this._candidatePort = null;
  530. }
  531. if (this._selectionBox) {
  532. this._selectionBox.parentElement!.removeChild(this._selectionBox);
  533. this._selectionBox = null;
  534. }
  535. if (this._frameCandidate) {
  536. let newFrame = new GraphFrame(this._frameCandidate, this);
  537. this._frames.push(newFrame);
  538. this._frameCandidate.parentElement!.removeChild(this._frameCandidate);
  539. this._frameCandidate = null;
  540. this.props.globalState.onSelectionChangedObservable.notifyObservers(newFrame);
  541. }
  542. }
  543. onWheel(evt: React.WheelEvent) {
  544. let delta = evt.deltaY < 0 ? 0.1 : -0.1;
  545. let oldZoom = this.zoom;
  546. this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
  547. const boundingRect = evt.currentTarget.getBoundingClientRect();
  548. const clientWidth = boundingRect.width;
  549. const clientHeight = boundingRect.height;
  550. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  551. const heightDiff = clientHeight * this.zoom - clientHeight * oldZoom;
  552. const clientX = evt.clientX - boundingRect.left;
  553. const clientY = evt.clientY - boundingRect.top;
  554. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  555. const yFactor = (clientY - this.y) / oldZoom / clientHeight;
  556. this.x = this.x - widthDiff * xFactor;
  557. this.y = this.y - heightDiff * yFactor;
  558. evt.stopPropagation();
  559. }
  560. zoomToFit() {
  561. // Get negative offset
  562. let minX = 0;
  563. let minY = 0;
  564. this._nodes.forEach(node => {
  565. if (this._frames.some(f => f.nodes.indexOf(node) !== -1)) {
  566. return;
  567. }
  568. if (node.x < minX) {
  569. minX = node.x;
  570. }
  571. if (node.y < minY) {
  572. minY = node.y;
  573. }
  574. });
  575. this._frames.forEach(frame => {
  576. if (frame.x < minX) {
  577. minX = frame.x;
  578. }
  579. if (frame.y < minY) {
  580. minY = frame.y;
  581. }
  582. });
  583. // Restore to 0
  584. this._frames.forEach(frame => {
  585. frame.x += -minX;
  586. frame.y += -minY;
  587. frame.cleanAccumulation();
  588. });
  589. this._nodes.forEach(node => {
  590. node.x += -minX;
  591. node.y += -minY;
  592. node.cleanAccumulation();
  593. });
  594. // Get correct zoom
  595. const xFactor = this._rootContainer.clientWidth / this._rootContainer.scrollWidth;
  596. const yFactor = this._rootContainer.clientHeight / this._rootContainer.scrollHeight;
  597. const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
  598. this.zoom = zoomFactor;
  599. this.x = 0;
  600. this.y = 0;
  601. }
  602. processCandidatePort() {
  603. let pointB = this._candidateLink!.portA.connectionPoint;
  604. let nodeB = this._candidateLink!.portA.node;
  605. let pointA: NodeMaterialConnectionPoint;
  606. let nodeA: GraphNode;
  607. if (this._candidatePort) {
  608. pointA = this._candidatePort.connectionPoint;
  609. nodeA = this._candidatePort.node;
  610. } else {
  611. if (pointB.direction === NodeMaterialConnectionPointDirection.Output) {
  612. return;
  613. }
  614. // No destination so let's spin a new input block
  615. let pointName = "output", inputBlock;
  616. let customInputBlock = this._candidateLink!.portA.connectionPoint.createCustomInputBlock();
  617. if (!customInputBlock) {
  618. inputBlock = new InputBlock(NodeMaterialBlockConnectionPointTypes[this._candidateLink!.portA.connectionPoint.type], undefined, this._candidateLink!.portA.connectionPoint.type);
  619. } else {
  620. [inputBlock, pointName] = customInputBlock;
  621. }
  622. this.props.globalState.nodeMaterial.attachedBlocks.push(inputBlock);
  623. pointA = (inputBlock as any)[pointName];
  624. nodeA = this.appendBlock(inputBlock);
  625. nodeA.x = this._dropPointX - 200;
  626. nodeA.y = this._dropPointY - 50;
  627. }
  628. if (pointA.direction === NodeMaterialConnectionPointDirection.Input) {
  629. let temp = pointB;
  630. pointB = pointA;
  631. pointA = temp;
  632. let tempNode = nodeA;
  633. nodeA = nodeB;
  634. nodeB = tempNode;
  635. }
  636. if (pointB.connectedPoint === pointA) {
  637. return;
  638. }
  639. if (pointB === pointA) {
  640. return;
  641. }
  642. if (pointB.direction === pointA.direction) {
  643. return;
  644. }
  645. if (pointB.ownerBlock === pointA.ownerBlock) {
  646. return;
  647. }
  648. // Check compatibility
  649. let isFragmentOutput = pointB.ownerBlock.getClassName() === "FragmentOutputBlock";
  650. let compatibilityState = pointA.checkCompatibilityState(pointB);
  651. if ((pointA.needDualDirectionValidation || pointB.needDualDirectionValidation) && compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible && !(pointA instanceof InputBlock)) {
  652. compatibilityState = pointB.checkCompatibilityState(pointA);
  653. }
  654. if (compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible) {
  655. if (isFragmentOutput) {
  656. let fragmentBlock = pointB.ownerBlock as FragmentOutputBlock;
  657. if (pointB.name === "rgb" && fragmentBlock.rgba.isConnected) {
  658. nodeB.getLinksForConnectionPoint(fragmentBlock.rgba)[0].dispose();
  659. } else if (pointB.name === "rgba" && fragmentBlock.rgb.isConnected) {
  660. nodeB.getLinksForConnectionPoint(fragmentBlock.rgb)[0].dispose();
  661. }
  662. }
  663. } else {
  664. let message = "";
  665. switch (compatibilityState) {
  666. case NodeMaterialConnectionPointCompatibilityStates.TypeIncompatible:
  667. message = "Cannot connect two different connection types";
  668. break;
  669. case NodeMaterialConnectionPointCompatibilityStates.TargetIncompatible:
  670. message = "Source block can only work in fragment shader whereas destination block is currently aimed for the vertex shader";
  671. break;
  672. }
  673. this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers(message);
  674. return;
  675. }
  676. if (pointB.isConnected) {
  677. let links = nodeB.getLinksForConnectionPoint(pointB);
  678. links.forEach(link => {
  679. link.dispose();
  680. });
  681. }
  682. if (pointB.ownerBlock.inputsAreExclusive) { // Disconnect all inputs if block has exclusive inputs
  683. pointB.ownerBlock.inputs.forEach(i => {
  684. let links = nodeB.getLinksForConnectionPoint(i);
  685. links.forEach(link => {
  686. link.dispose();
  687. });
  688. })
  689. }
  690. pointA.connectTo(pointB);
  691. this.connectPorts(pointA, pointB);
  692. nodeB.refresh();
  693. this.props.globalState.onRebuildRequiredObservable.notifyObservers();
  694. }
  695. processEditorData(editorData: IEditorData, isImportingAFrame = false) {
  696. if (!isImportingAFrame) {
  697. const frames = this._frames.splice(0);
  698. for (var frame of frames) {
  699. frame.dispose();
  700. }
  701. this._frames = [];
  702. this.x = editorData.x || 0;
  703. this.y = editorData.y || 0;
  704. this.zoom = editorData.zoom || 1;
  705. }
  706. // Frames
  707. if (editorData.frames) {
  708. if (isImportingAFrame) {
  709. var frame = GraphFrame.Parse(editorData.frames[editorData.frames.length - 1], this, editorData.map);
  710. this._frames.push(frame);
  711. this.globalState.onSelectionChangedObservable.notifyObservers(frame);
  712. } else {
  713. for (var frameData of editorData.frames) {
  714. var frame = GraphFrame.Parse(frameData, this, editorData.map);
  715. this._frames.push(frame);
  716. }
  717. }
  718. }
  719. }
  720. render() {
  721. return (
  722. <div id="graph-canvas"
  723. onWheel={evt => this.onWheel(evt)}
  724. onPointerMove={evt => this.onMove(evt)}
  725. onPointerDown={evt => this.onDown(evt)}
  726. onPointerUp={evt => this.onUp(evt)}
  727. >
  728. <div id="graph-container">
  729. <div id="graph-canvas-container">
  730. </div>
  731. <div id="frame-container">
  732. </div>
  733. <svg id="graph-svg-container">
  734. </svg>
  735. <div id="selection-container">
  736. </div>
  737. </div>
  738. </div>
  739. );
  740. }
  741. }