123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- import * as React from "react";
- import { GlobalState } from '../globalState';
- import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
- import { GraphNode } from './graphNode';
- import * as dagre from 'dagre';
- import { Nullable } from 'babylonjs/types';
- import { NodePort } from './nodePort';
- import { DataStorage } from 'babylonjs/Misc/dataStorage';
- import { IEditorData} from '../nodeLocationInfo';
- import { Button } from 'babylonjs-gui/2D/controls/button';
- import { Engine } from 'babylonjs/Engines/engine';
- import { Scene } from 'babylonjs/scene';
- import { Container, Rectangle } from 'babylonjs-gui';
- require("./graphCanvas.scss");
- export interface IGraphCanvasComponentProps {
- globalState: GlobalState
- }
- export type FramePortData = {
- }
- export const isFramePortData = (variableToCheck: any): variableToCheck is FramePortData => {
- if (variableToCheck) {
- return (variableToCheck as FramePortData) !== undefined;
- }
- else return false;
- }
- export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentProps> {
- private readonly MinZoom = 0.1;
- private readonly MaxZoom = 4;
- private _hostCanvas: HTMLDivElement;
- private _graphCanvas: HTMLDivElement;
- private _selectionContainer: HTMLDivElement;
- private _frameContainer: HTMLDivElement;
- private _svgCanvas: HTMLElement;
- private _rootContainer: HTMLDivElement;
- private _nodes: GraphNode[] = [];
- private _guiNodes: GraphNode[] = [];
- private _mouseStartPointX: Nullable<number> = null;
- private _mouseStartPointY: Nullable<number> = null
- private _dropPointX = 0;
- private _dropPointY = 0;
- private _selectionStartX = 0;
- private _selectionStartY = 0;
- private _candidateLinkedHasMoved = false;
- private _x = 0;
- private _y = 0;
- private _zoom = 1;
- private _selectedNodes: GraphNode[] = [];
- private _selectedGuiNodes: GraphNode[] = [];
- private _selectedPort: Nullable<NodePort> = null;
- private _gridSize = 20;
- private _selectionBox: Nullable<HTMLDivElement> = null;
- private _frameCandidate: Nullable<HTMLDivElement> = null;
- private _altKeyIsPressed = false;
- private _ctrlKeyIsPressed = false;
- private _oldY = -1;
- public _frameIsMoving = false;
- public _isLoading = false;
- public get gridSize() {
- return this._gridSize;
- }
- public set gridSize(value: number) {
- this._gridSize = value;
-
- this.updateTransform();
- }
- public get globalState(){
- return this.props.globalState;
- }
- public get nodes() {
- return this._nodes;
- }
- public get zoom() {
- return this._zoom;
- }
- public set zoom(value: number) {
- if (this._zoom === value) {
- return;
- }
- this._zoom = value;
-
- this.updateTransform();
- }
- public get x() {
- return this._x;
- }
- public set x(value: number) {
- this._x = value;
-
- this.updateTransform();
- }
- public get y() {
- return this._y;
- }
- public set y(value: number) {
- this._y = value;
-
- this.updateTransform();
- }
- public get selectedNodes() {
- return this._selectedNodes;
- }
- public get selectedGuiNodes() {
- return this._selectedGuiNodes;
- }
- public get selectedPort() {
- return this._selectedPort;
- }
- public get canvasContainer() {
- return this._graphCanvas;
- }
- public get hostCanvas() {
- return this._hostCanvas;
- }
- public get svgCanvas() {
- return this._svgCanvas;
- }
- public get selectionContainer() {
- return this._selectionContainer;
- }
- public get frameContainer() {
- return this._frameContainer;
- }
- constructor(props: IGraphCanvasComponentProps) {
- super(props);
- props.globalState.onSelectionChangedObservable.add(selection => {
- if (!selection) {
- this.selectedGuiNodes.forEach(element => {
- element.isSelected = false;
- });
- this._selectedNodes = [];
- this._selectedGuiNodes = [];
- this._selectedPort = null;
- } else {
- if (selection instanceof GraphNode){
- if (this._ctrlKeyIsPressed) {
- if (this._selectedNodes.indexOf(selection) === -1) {
- this._selectedNodes.push(selection);
- this._selectedGuiNodes.push(selection);
- }
- } else {
- this._selectedNodes = [selection];
- this._selectedGuiNodes = [selection];
- }
- } else if(selection instanceof NodePort){
- this._selectedNodes = [];
- this._selectedGuiNodes = [];
- this._selectedPort = selection;
- } else {
- this._selectedNodes = [];
- this._selectedGuiNodes = [];
- //this._selectedPort = selection.port;
- }
- }
- });
- props.globalState.onGridSizeChanged.add(() => {
- this.gridSize = DataStorage.ReadNumber("GridSize", 20);
- });
- this.props.globalState.hostDocument!.addEventListener("keyup", () => this.onKeyUp(), false);
- this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
- this._altKeyIsPressed = evt.altKey;
- this._ctrlKeyIsPressed = evt.ctrlKey;
- }, false);
- this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => {
- this._altKeyIsPressed = false;
- this._ctrlKeyIsPressed = false;
- }, false);
- // Store additional data to serialization object
- this.props.globalState.storeEditorData = (editorData, graphFrame) => {
- editorData.frames = [];
- if (graphFrame) {
- editorData.frames.push(graphFrame!.serialize());
- } else {
- editorData.x = this.x;
- editorData.y = this.y;
- editorData.zoom = this.zoom;
- }
- }
- }
- public getGridPosition(position: number, useCeil = false) {
- let gridSize = this.gridSize;
- if (gridSize === 0) {
- return position;
- }
- if (useCeil) {
- return gridSize * Math.ceil(position / gridSize);
- }
- return gridSize * Math.floor(position / gridSize);
- }
-
- public getGridPositionCeil(position: number) {
- let gridSize = this.gridSize;
- if (gridSize === 0) {
- return position;
- }
- return gridSize * Math.ceil(position / gridSize);
- }
- updateTransform() {
- this._rootContainer.style.transform = `translate(${this._x}px, ${this._y}px) scale(${this._zoom})`;
- if (DataStorage.ReadBoolean("ShowGrid", true)) {
- this._hostCanvas.style.backgroundSize = `${this._gridSize * this._zoom}px ${this._gridSize * this._zoom}px`;
- this._hostCanvas.style.backgroundPosition = `${this._x}px ${this._y}px`;
- } else {
- this._hostCanvas.style.backgroundSize = `0`;
- }
- }
- onKeyUp() {
- this._altKeyIsPressed = false;
- this._ctrlKeyIsPressed = false;
- this._oldY = -1;
- }
- findNodeFromBlock(block: NodeMaterialBlock) {
- return this.nodes.filter(n => n.block === block)[0];
- }
- reset() {
- for (var node of this._nodes) {
- node.dispose();
- }
-
- this._nodes = [];
- this._graphCanvas.innerHTML = "";
- this._svgCanvas.innerHTML = "";
- }
- appendBlock(block: NodeMaterialBlock) {
- let newNode = new GraphNode(block, this.props.globalState, null);
- newNode.appendVisual(this._graphCanvas, this);
- this._nodes.push(newNode);
- return newNode;
- }
- distributeGraph() {
- this.x = 0;
- this.y = 0;
- this.zoom = 1;
- let graph = new dagre.graphlib.Graph();
- graph.setGraph({});
- graph.setDefaultEdgeLabel(() => ({}));
- graph.graph().rankdir = "LR";
- // Build dagre graph
- this._nodes.forEach(node => {
- graph.setNode(node.id.toString(), {
- id: node.id,
- type: "node",
- width: node.width,
- height: node.height
- });
- });
-
- this._nodes.forEach(node => {
- node.block.outputs.forEach(output => {
- if (!output.hasEndpoints) {
- return;
- }
- });
- });
- // Distribute
- dagre.layout(graph);
- // Update graph
- let dagreNodes = graph.nodes().map(node => graph.node(node));
- dagreNodes.forEach((dagreNode: any) => {
- if (!dagreNode) {
- return;
- }
- if (dagreNode.type === "node") {
- for (var node of this._nodes) {
- if (node.id === dagreNode.id) {
- node.x = dagreNode.x - dagreNode.width / 2;
- node.y = dagreNode.y - dagreNode.height / 2;
- node.cleanAccumulation();
- return;
- }
- }
- return;
- }
-
- });
- }
- componentDidMount() {
- this._hostCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas") as HTMLDivElement;
- this._rootContainer = this.props.globalState.hostDocument.getElementById("graph-container") as HTMLDivElement;
- this._graphCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas-container") as HTMLDivElement;
- this._svgCanvas = this.props.globalState.hostDocument.getElementById("graph-svg-container") as HTMLElement;
- this._selectionContainer = this.props.globalState.hostDocument.getElementById("selection-container") as HTMLDivElement;
- this._frameContainer = this.props.globalState.hostDocument.getElementById("frame-container") as HTMLDivElement;
-
- this.gridSize = DataStorage.ReadNumber("GridSize", 20);
- this.updateTransform();
- }
- onMove(evt: React.PointerEvent) {
- // Selection box
- /*if (this._selectionBox) {
- const rootRect = this.canvasContainer.getBoundingClientRect();
- const localX = evt.pageX - rootRect.left;
- const localY = evt.pageY - rootRect.top;
- if (localX > this._selectionStartX) {
- this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
- this._selectionBox.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
- } else {
- this._selectionBox.style.left = `${localX / this.zoom}px`;
- this._selectionBox.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
- }
- if (localY > this._selectionStartY) {
- this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
- this._selectionBox.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
- } else {
- this._selectionBox.style.top = `${localY / this.zoom}px`;
- this._selectionBox.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
- }
-
- this.props.globalState.onSelectionBoxMoved.notifyObservers(this._selectionBox.getBoundingClientRect());
- return;
- }
- // Candidate frame box
- if (this._frameCandidate) {
- const rootRect = this.canvasContainer.getBoundingClientRect();
- const localX = evt.pageX - rootRect.left;
- const localY = evt.pageY - rootRect.top;
- if (localX > this._selectionStartX) {
- this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
- this._frameCandidate.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
- } else {
- this._frameCandidate.style.left = `${localX / this.zoom}px`;
- this._frameCandidate.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
- }
- if (localY > this._selectionStartY) {
- this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
- this._frameCandidate.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
- } else {
- this._frameCandidate.style.top = `${localY / this.zoom}px`;
- this._frameCandidate.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
- }
- return;
- }
- // Candidate link
- if (this._candidateLink) {
- const rootRect = this.canvasContainer.getBoundingClientRect();
- this._candidatePort = null;
- this.props.globalState.onCandidateLinkMoved.notifyObservers(new Vector2(evt.pageX, evt.pageY));
- this._dropPointX = (evt.pageX - rootRect.left) / this.zoom;
- this._dropPointY = (evt.pageY - rootRect.top) / this.zoom;
- this._candidateLink.update(this._dropPointX, this._dropPointY, true);
- this._candidateLinkedHasMoved = true;
-
- return;
- }
- // Zoom with mouse + alt
- if (this._altKeyIsPressed && evt.buttons === 1) {
- if (this._oldY < 0) {
- this._oldY = evt.pageY;
- }
- let zoomDelta = (evt.pageY - this._oldY) / 10;
- if (Math.abs(zoomDelta) > 5) {
- const oldZoom = this.zoom;
- this.zoom = Math.max(Math.min(this.MaxZoom, this.zoom + zoomDelta / 100), this.MinZoom);
- const boundingRect = evt.currentTarget.getBoundingClientRect();
- const clientWidth = boundingRect.width;
- const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
- const clientX = evt.clientX - boundingRect.left;
-
- const xFactor = (clientX - this.x) / oldZoom / clientWidth;
-
- this.x = this.x - widthDiff * xFactor;
- this._oldY = evt.pageY;
- }
- return;
- }
- // Move canvas
- this._rootContainer.style.cursor = "move";
- */
- if (this._mouseStartPointX != null && this._mouseStartPointY != null) {
- //this.x += evt.clientX - this._mouseStartPointX;
- //this.y += evt.clientY - this._mouseStartPointY;
- var x = this._mouseStartPointX;
- var y = this._mouseStartPointY;
- this._guiNodes.forEach(element => {
- element._onMove(new BABYLON.Vector2(evt.clientX, evt.clientY),
- new BABYLON.Vector2( x, y));
- });
- this._mouseStartPointX = evt.clientX;
- this._mouseStartPointY = evt.clientY;
- //doing the dragging for the gui node.
- }
- }
- onDown(evt: React.PointerEvent<HTMLElement>) {
- this._rootContainer.setPointerCapture(evt.pointerId);
- // Selection?
- if (evt.currentTarget === this._hostCanvas && evt.ctrlKey) {
- this._selectionBox = this.props.globalState.hostDocument.createElement("div");
- this._selectionBox.classList.add("selection-box");
- this._selectionContainer.appendChild(this._selectionBox);
- const rootRect = this.canvasContainer.getBoundingClientRect();
- this._selectionStartX = (evt.pageX - rootRect.left);
- this._selectionStartY = (evt.pageY - rootRect.top);
- this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
- this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
- this._selectionBox.style.width = "0px";
- this._selectionBox.style.height = "0px";
- return;
- }
- // Frame?
- if (evt.currentTarget === this._hostCanvas && evt.shiftKey) {
- this._frameCandidate = this.props.globalState.hostDocument.createElement("div");
- this._frameCandidate.classList.add("frame-box");
- this._frameContainer.appendChild(this._frameCandidate);
- const rootRect = this.canvasContainer.getBoundingClientRect();
- this._selectionStartX = (evt.pageX - rootRect.left);
- this._selectionStartY = (evt.pageY - rootRect.top);
- this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
- this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
- this._frameCandidate.style.width = "0px";
- this._frameCandidate.style.height = "0px";
- return;
- }
- // Port dragging
- if (evt.nativeEvent.srcElement && (evt.nativeEvent.srcElement as HTMLElement).nodeName === "IMG") {
- return;
- }
- this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
- this._mouseStartPointX = evt.clientX;
- this._mouseStartPointY = evt.clientY;
- }
- onUp(evt: React.PointerEvent) {
- this._mouseStartPointX = null;
- this._mouseStartPointY = null;
- this._rootContainer.releasePointerCapture(evt.pointerId);
- this._oldY = -1;
-
- if (this._selectionBox) {
- this._selectionBox.parentElement!.removeChild(this._selectionBox);
- this._selectionBox = null;
- }
- if (this._frameCandidate) {
- this._frameCandidate.parentElement!.removeChild(this._frameCandidate);
- this._frameCandidate = null;
- }
- }
- onWheel(evt: React.WheelEvent) {
- let delta = evt.deltaY < 0 ? 0.1 : -0.1;
- let oldZoom = this.zoom;
- this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
- const boundingRect = evt.currentTarget.getBoundingClientRect();
- const clientWidth = boundingRect.width;
- const clientHeight = boundingRect.height;
- const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
- const heightDiff = clientHeight * this.zoom - clientHeight * oldZoom;
- const clientX = evt.clientX - boundingRect.left;
- const clientY = evt.clientY - boundingRect.top;
- const xFactor = (clientX - this.x) / oldZoom / clientWidth;
- const yFactor = (clientY - this.y) / oldZoom / clientHeight;
- this.x = this.x - widthDiff * xFactor;
- this.y = this.y - heightDiff * yFactor;
- evt.stopPropagation();
- }
- zoomToFit() {
- // Get negative offset
- let minX = 0;
- let minY = 0;
- this._nodes.forEach(node => {
- if (node.x < minX) {
- minX = node.x;
- }
- if (node.y < minY) {
- minY = node.y;
- }
- });
- // Restore to 0
- this._nodes.forEach(node => {
- node.x += -minX;
- node.y += -minY;
- node.cleanAccumulation();
- });
- // Get correct zoom
- const xFactor = this._rootContainer.clientWidth / this._rootContainer.scrollWidth;
- const yFactor = this._rootContainer.clientHeight / this._rootContainer.scrollHeight;
- const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
-
- this.zoom = zoomFactor;
- this.x = 0;
- this.y = 0;
- }
- processEditorData(editorData: IEditorData) {
- this.x = editorData.x || 0;
- this.y = editorData.y || 0;
- this.zoom = editorData.zoom || 1;
- // Frames
- }
- createGUICanvas()
- {
- // Get the canvas element from the DOM.
- const canvas = document.getElementById("graph-canvas") as HTMLCanvasElement;
- // Associate a Babylon Engine to it.
- const engine = new BABYLON.Engine(canvas);
-
- // Create our first scene.
- var scene = new BABYLON.Scene(engine);
- // This creates and positions a free camera (non-mesh)
- var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
-
- // This targets the camera to scene origin
- camera.setTarget(BABYLON.Vector3.Zero());
-
- // This attaches the camera to the canvas
- camera.attachControl(true);
-
- // GUI
- this.globalState.guiTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
-
- engine.runRenderLoop(() => {this.updateGUIs(); scene.render()});
- }
-
- public addNewButton()
- {
- if(!this.globalState.guiTexture)
- {
- this.createGUICanvas();
- }
- var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Click Me");
- button1.width = "150px"
- button1.height = "40px";
- button1.color = "#FFFFFFFF";
- button1.cornerRadius = 20;
- button1.background = "#138016FF";
- button1.onPointerUpObservable.add(function() {
- });
- var fakeNodeMaterialBlock = new NodeMaterialBlock("Button");
- var newGuiNode = new GraphNode(fakeNodeMaterialBlock, this.globalState, button1);
- newGuiNode.appendVisual(this._graphCanvas, this);
- this._guiNodes.push(newGuiNode);
- this.globalState.guiTexture.addControl(button1);
- }
- public addNewSlider()
- {
- if(!this.globalState.guiTexture)
- {
- this.createGUICanvas();
- }
- var slider1 = new BABYLON.GUI.Slider("Slider");
- slider1.width = "150px"
- slider1.height = "40px";
- slider1.color = "#FFFFFFFF";
- slider1.background = "#138016FF";
- slider1.onPointerUpObservable.add(function() {
- });
- var fakeNodeMaterialBlock = new NodeMaterialBlock("Slider");
- var newGuiNode = new GraphNode(fakeNodeMaterialBlock, this.globalState, slider1);
- newGuiNode.appendVisual(this._graphCanvas, this);
- this._guiNodes.push(newGuiNode);
- this.globalState.guiTexture.addControl(slider1);
- }
- //private _advancedTexture: BABYLON.GUI.AdvancedDynamicTexture;
- updateGUIs()
- {
- this._guiNodes.forEach(element => {
- element.updateVisual();
-
- });
- }
-
- render() {
- //var canv = new HTMLCanvasElement;
- var canv = <canvas id="graph-canvas"
- onWheel={evt => this.onWheel(evt)}
- onPointerMove={evt => this.onMove(evt)}
- onPointerDown={evt => this.onDown(evt)}
- onPointerUp={evt => this.onUp(evt)}
- >
- <div id="graph-container">
- <div id="graph-canvas-container">
-
- </div>
- <div id="frame-container">
- </div>
- <svg id="graph-svg-container">
- </svg>
- <div id="selection-container">
- </div>
- </div>
- </canvas>
- return (
- canv
- );
- }
- }
|