Browse Source

Ready to publish!

David Catuhe 5 years ago
parent
commit
a9381cc986

+ 8 - 1
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -104,7 +104,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                                 DataStorage.StoreBoolean("EmbedTextures", value);
                             }}
                         />
-                        <SliderLineComponent label="Grid size" minimum={0} maximum={50} step={5} 
+                        <SliderLineComponent label="Grid size" minimum={0} maximum={100} step={5} 
                             decimalCount={0} 
                             directValue={gridSize}
                             onChange={value => {
@@ -113,6 +113,13 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                                 this.forceUpdate();
                             }}
                         />
+                        <CheckBoxLineComponent label="Show grid" 
+                            isSelected={() => DataStorage.ReadBoolean("ShowGrid", true)}
+                            onSelect={(value: boolean) => {
+                                DataStorage.StoreBoolean("ShowGrid", value);                
+                                this.props.globalState.onGridSizeChanged.notifyObservers();
+                            }}
+                        />
                     </LineContainerComponent>
                     <LineContainerComponent title="FILE">                        
                         <FileButtonLineComponent label="Load" onClick={(file) => this.load(file)} accept=".json" />

+ 34 - 6
nodeEditor/src/diagram/graphCanvas.scss

@@ -6,10 +6,21 @@
     font: 14px "acumin-pro";  
     user-select: none;
     overflow: hidden;
-
     background-image:
-        linear-gradient(to right, grey 1px, transparent 1px),
-        linear-gradient(to bottom, grey 1px, transparent 1px);  
+        linear-gradient(to right, #4F4E4F 1px, transparent 1px),
+        linear-gradient(to bottom, #4F4E4F 1px, transparent 1px);  
+
+    #selection-container {
+        pointer-events: none;
+        
+        .selection-box {
+            z-index: 10;
+            position: absolute;
+            background: rgba(72, 72, 196, 0.5);
+            border: blue solid 4px;
+            border-radius: 10px;
+        }
+    }
 
     #graph-container {
         width: 100%;
@@ -18,7 +29,8 @@
         top: 0;
         transform-origin: left top;
         display: grid;
-        grid-template: 100% 100%;        
+        grid-template: 100% 100%;     
+        cursor: move;   
 
         #graph-svg-container {
             grid-row: 1;
@@ -33,16 +45,19 @@
                 &.selected {                    
                     stroke: white !important;
                     stroke-dasharray: 10, 2;
-                }                   
+                }       
             }
 
             .selection-link {
                 stroke-width: 16px;
+                opacity: 0;
                 &:hover, &.selected {
                     stroke: white !important;
                     opacity: 0.4;
                 }
+                transition: opacity 75ms;
                 stroke: transparent;                        
+                cursor: pointer;
             }
         }
 
@@ -67,8 +82,21 @@
                 grid-template-columns: 100%;
                 color: white;
 
+                .selection-border {                    
+                    grid-row: 1 / span 3;
+                    grid-column: 1;
+                    margin: -4px;
+
+                    transition: border-color 100ms;
+
+                    border: 4px solid transparent;
+                    border-radius: 12px;
+                }
+
                 &.selected {
-                    border-color: white;
+                    .selection-border {  
+                        border-color: white;
+                    }
                 }
 
                 .header {

+ 87 - 13
nodeEditor/src/diagram/graphCanvas.tsx

@@ -19,8 +19,12 @@ export interface IGraphCanvasComponentProps {
 }
 
 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 _svgCanvas: HTMLElement;
     private _rootContainer: HTMLDivElement;
     private _nodes: GraphNode[] = [];
@@ -29,6 +33,8 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
     private _mouseStartPointY: Nullable<number> = null
     private _dropPointX = 0;
     private _dropPointY = 0;
+    private _selectionStartX = 0;
+    private _selectionStartY = 0;
     private _x = 0;
     private _y = 0;
     private _zoom = 1;
@@ -37,6 +43,7 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
     private _candidateLink: Nullable<NodeLink> = null;
     private _candidatePort: Nullable<NodePort> = null;
     private _gridSize = 20;
+    private _selectionBox: Nullable<HTMLDivElement> = null;
 
     private _altKeyIsPressed = false;
     private _ctrlKeyIsPressed = false;
@@ -47,12 +54,9 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
     }
 
     public set gridSize(value: number) {
-        if (this._gridSize === value) {
-            return;
-        }
         this._gridSize = value;
-
-        this._hostCanvas.style.backgroundSize = `${value}px ${value}px`;
+        
+        this.updateTransform();
     }
 
     public get globalState(){
@@ -164,11 +168,18 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
 		if (gridSize === 0) {
 			return position;
 		}
-		return gridSize * Math.floor((position + gridSize / 2) / gridSize);
+		return gridSize * Math.floor(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() {        
@@ -283,8 +294,9 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
         dagreNodes.forEach(dagreNode => {
             for (var node of this._nodes) {
                 if (node.id === dagreNode.id) {
-                    node.x = this.getGridPosition(dagreNode.x - dagreNode.width / 2);
-                    node.y = this.getGridPosition(dagreNode.y - dagreNode.height / 2);
+                    node.x = dagreNode.x - dagreNode.width / 2;
+                    node.y = dagreNode.y - dagreNode.height / 2;
+                    node.cleanAccumulation();
                     return;
                 }
             }
@@ -296,12 +308,41 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
         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.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 link
         if (this._candidateLink) {        
             const rootRect = this.canvasContainer.getBoundingClientRect();       
@@ -323,7 +364,18 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
 
             let zoomDelta = (evt.pageY - this._oldY) / 10;
             if (Math.abs(zoomDelta) > 5) {
-                this.zoom += zoomDelta / 100;
+                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;
@@ -345,6 +397,22 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
     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";
+        }
+
+        // Port dragging
         if (evt.nativeEvent.srcElement && (evt.nativeEvent.srcElement as HTMLElement).nodeName === "IMG") {
             if (!this._candidateLink) {
                 let portElement = ((evt.nativeEvent.srcElement as HTMLElement).parentElement as any).port as NodePort;
@@ -355,8 +423,7 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
 
         this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
         this._mouseStartPointX = evt.clientX;
-        this._mouseStartPointY = evt.clientY;
-        
+        this._mouseStartPointY = evt.clientY;        
     }
 
     onUp(evt: React.PointerEvent) {
@@ -372,13 +439,18 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
             this._candidateLink = null;
             this._candidatePort = null;
         }
+
+        if (this._selectionBox) {
+           this._selectionBox.parentElement!.removeChild(this._selectionBox);
+           this._selectionBox = null;
+        }
     }
 
     onWheel(evt: React.WheelEvent) {
         let delta = evt.deltaY < 0 ? 0.1 : -0.1;
 
         let oldZoom = this.zoom;
-        this.zoom = Math.min(Math.max(0.1, this.zoom + delta), 4);
+        this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
 
         const boundingRect = evt.currentTarget.getBoundingClientRect();
         const clientWidth = boundingRect.width;
@@ -503,13 +575,15 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
                 onWheel={evt => this.onWheel(evt)}
                 onPointerMove={evt => this.onMove(evt)}
                 onPointerDown={evt =>  this.onDown(evt)}   
-                onPointerUp={evt =>  this.onUp(evt)}   
+                onPointerUp={evt =>  this.onUp(evt)} 
             >    
                 <div id="graph-container">
                     <div id="graph-canvas-container">
                     </div>     
                     <svg id="graph-svg-container">
                     </svg>
+                    <div id="selection-container">                        
+                    </div>
                 </div>
             </div>
         );

+ 63 - 16
nodeEditor/src/diagram/graphNode.ts

@@ -24,10 +24,13 @@ export class GraphNode {
     private _links: NodeLink[] = [];    
     private _x = 0;
     private _y = 0;
+    private _gridAlignedX = 0;
+    private _gridAlignedY = 0;    
     private _mouseStartPointX: Nullable<number> = null;
     private _mouseStartPointY: Nullable<number> = null    
     private _globalState: GlobalState;
     private _onSelectionChangedObserver: Nullable<Observer<Nullable<GraphNode | NodeLink>>>;   
+    private _onSelectionBoxMovedObserver: Nullable<Observer<ClientRect | DOMRect>>;  
     private _onUpdateRequiredObserver: Nullable<Observer<void>>;  
     private _ownerCanvas: GraphCanvasComponent; 
     private _isSelected: boolean;
@@ -37,6 +40,14 @@ export class GraphNode {
         return this._links;
     }
 
+    public get gridAlignedX() {
+        return this._gridAlignedX;
+    }
+
+    public get gridAlignedY() {
+        return this._gridAlignedY;
+    }
+
     public get x() {
         return this._x;
     }
@@ -45,9 +56,10 @@ export class GraphNode {
         if (this._x === value) {
             return;
         }
-        
         this._x = value;
-        this._visual.style.left = `${value}px`;
+        
+        this._gridAlignedX = this._ownerCanvas.getGridPosition(value);
+        this._visual.style.left = `${this._gridAlignedX}px`;
 
         this._refreshLinks();
     }
@@ -62,7 +74,9 @@ export class GraphNode {
         }
 
         this._y = value;
-        this._visual.style.top = `${value}px`;
+
+        this._gridAlignedY = this._ownerCanvas.getGridPosition(value);
+        this._visual.style.top = `${this._gridAlignedY}px`;
 
         this._refreshLinks();
     }
@@ -95,7 +109,12 @@ export class GraphNode {
         this._isSelected = value;
 
         if (!value) {
-            this._visual.classList.remove("selected");
+            this._visual.classList.remove("selected");    
+            let indexInSelection = this._ownerCanvas.selectedNodes.indexOf(this);
+
+            if (indexInSelection > -1) {
+                this._ownerCanvas.selectedNodes.splice(indexInSelection, 1);
+            }
         } else {
             this._globalState.onSelectionChangedObservable.notifyObservers(this);  
         }
@@ -119,6 +138,16 @@ export class GraphNode {
         this._onUpdateRequiredObserver = this._globalState.onUpdateRequiredObservable.add(() => {
             this.refresh();
         });
+
+        this._onSelectionBoxMovedObserver = this._globalState.onSelectionBoxMoved.add(rect1 => {
+            const rect2 = this._visual.getBoundingClientRect();
+            var overlap = !(rect1.right < rect2.left || 
+                rect1.left > rect2.right || 
+                rect1.bottom < rect2.top || 
+                rect1.top > rect2.bottom);
+
+            this.isSelected = overlap;
+        });
     }
 
     public getPortForConnectionPoint(point: NodeMaterialConnectionPoint) {
@@ -190,7 +219,13 @@ export class GraphNode {
             return;
         }
 
-        this._globalState.onSelectionChangedObservable.notifyObservers(this);
+        const indexInSelection = this._ownerCanvas.selectedNodes.indexOf(this) ;
+        if (indexInSelection=== -1) {
+            this._globalState.onSelectionChangedObservable.notifyObservers(this);
+        } else if (evt.ctrlKey) {
+            this.isSelected = false;
+        }
+
         evt.stopPropagation();
 
         this._mouseStartPointX = evt.clientX;
@@ -199,8 +234,17 @@ export class GraphNode {
         this._visual.setPointerCapture(evt.pointerId);
     }
 
+    public cleanAccumulation() {
+        this.x = this.gridAlignedX;
+        this.y = this.gridAlignedY;
+    }
+
     private _onUp(evt: PointerEvent) {
         evt.stopPropagation();
+
+        for (var selectedNode of this._ownerCanvas.selectedNodes) {
+            selectedNode.cleanAccumulation();
+        }
         
         this._mouseStartPointX = null;
         this._mouseStartPointY = null;
@@ -208,25 +252,20 @@ export class GraphNode {
     }
 
     private _onMove(evt: PointerEvent) {
-        if (this._mouseStartPointX === null || this._mouseStartPointY === null) {
+        if (this._mouseStartPointX === null || this._mouseStartPointY === null || evt.ctrlKey) {
             return;
         }
 
-        let newX = this._ownerCanvas.getGridPosition(evt.clientX - this._mouseStartPointX);
-        let newY = this._ownerCanvas.getGridPosition(evt.clientY - this._mouseStartPointY);
+        let newX = evt.clientX - this._mouseStartPointX;
+        let newY = evt.clientY - this._mouseStartPointY;
 
         for (var selectedNode of this._ownerCanvas.selectedNodes) {
             selectedNode.x += newX / this._ownerCanvas.zoom;
             selectedNode.y += newY / this._ownerCanvas.zoom;
         }
 
-        if (Math.abs(newX) > 0) { 
-            this._mouseStartPointX = evt.clientX;
-        }
-
-        if (Math.abs(newY) > 0) {
-            this._mouseStartPointY = evt.clientY;   
-        }
+        this._mouseStartPointX = evt.clientX;
+        this._mouseStartPointY = evt.clientY;   
 
         evt.stopPropagation();
     }
@@ -288,9 +327,13 @@ export class GraphNode {
         this._connections.appendChild(this._outputsContainer);      
 
         this._content = root.ownerDocument!.createElement("div");
-        this._content.classList.add("content");
+        this._content.classList.add("content");        
         this._visual.appendChild(this._content);     
 
+        var selectionBorder = root.ownerDocument!.createElement("div");
+        selectionBorder.classList.add("selection-border");
+        this._visual.appendChild(selectionBorder);     
+
 
         root.appendChild(this._visual);
 
@@ -315,6 +358,10 @@ export class GraphNode {
             this._globalState.onUpdateRequiredObservable.remove(this._onUpdateRequiredObserver);
         }
 
+        if (this._onSelectionBoxMovedObserver) {
+            this._globalState.onSelectionBoxMoved.remove(this._onSelectionBoxMovedObserver);
+        }
+
         if (this._visual.parentElement) {
             this._visual.parentElement.removeChild(this._visual);
         }

+ 3 - 1
nodeEditor/src/globalState.ts

@@ -30,7 +30,8 @@ export class GlobalState {
     onBackFaceCullingChanged = new Observable<void>();
     onDepthPrePassChanged = new Observable<void>();
     onAnimationCommandActivated = new Observable<void>();
-    onCandidateLinkMoved = new Observable<Nullable<Vector2>>();    
+    onCandidateLinkMoved = new Observable<Nullable<Vector2>>();   
+    onSelectionBoxMoved = new Observable<ClientRect | DOMRect>();   
     onCandidatePortSelected = new Observable<Nullable<NodePort>>();
     onGetNodeFromBlock: (block: NodeMaterialBlock) => GraphNode;
     onGridSizeChanged = new Observable<void>();
@@ -45,6 +46,7 @@ export class GlobalState {
     directionalLight0: boolean;
     directionalLight1: boolean;
     controlCamera: boolean;    
+    storeEditorData:(serializationObject: any) => void;
     
     customSave?: {label: string, action: (data: string) => Promise<void>};
 

+ 36 - 19
nodeEditor/src/graphEditor.tsx

@@ -13,7 +13,7 @@ import { Nullable } from 'babylonjs/types';
 import { MessageDialogComponent } from './sharedComponents/messageDialog';
 import { BlockTools } from './blockTools';
 import { PreviewManager } from './components/preview/previewManager';
-import { INodeLocationInfo } from './nodeLocationInfo';
+import { IEditorData } from './nodeLocationInfo';
 import { PreviewMeshControlComponent } from './components/preview/previewMeshControlComponent';
 import { PreviewAreaComponent } from './components/preview/previewAreaComponent';
 import { SerializationTools } from './serializationTools';
@@ -166,6 +166,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                 }
 
                 this.props.globalState.onSelectionChangedObservable.notifyObservers(null);  
+                this.props.globalState.onRebuildRequiredObservable.notifyObservers();  
                 return;
             }
 
@@ -223,12 +224,19 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                         y = currentY;
                     }
 
-                    newNode.x = this._graphCanvas.getGridPosition(x);
-                    newNode.y = this._graphCanvas.getGridPosition(y);
+                    newNode.x = x;
+                    newNode.y = y;
+                    newNode.cleanAccumulation();
                 }
             }
 
         }, false);
+
+        this.props.globalState.storeEditorData = (editorData) => {
+            editorData.zoom = this._graphCanvas.zoom;
+            editorData.x = this._graphCanvas.x;
+            editorData.y = this._graphCanvas.y;
+        }
     }
 
     zoomToFit() {
@@ -252,7 +260,14 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
     }
 
     build() {        
-        let locations: Nullable<INodeLocationInfo[]> = this.props.globalState.nodeMaterial.editorData;
+        let editorData = this.props.globalState.nodeMaterial.editorData;
+
+        if (editorData instanceof Array) {
+            editorData = {
+                locations: editorData
+            }
+        }
+
         // setup the diagram model
         this._blocks = [];
         this._graphCanvas.reset();
@@ -280,25 +295,25 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                         }
                     }
                 }
-            });
-            
+            });            
         }
 
-        this.reOrganize(locations);
+        this.reOrganize(editorData);
     }
 
-    reOrganize(locations: Nullable<INodeLocationInfo[]> = null) {
-        if (!locations) {
+    reOrganize(editorData: Nullable<IEditorData> = null) {
+        if (!editorData || !editorData.locations) {
             this._graphCanvas.distributeGraph();
         } else {
-            this._graphCanvas.x = 0;
-            this._graphCanvas.y = 0;
-            this._graphCanvas.zoom = 1;
-            for (var location of locations) {
+            this._graphCanvas.x = editorData.x || 0;
+            this._graphCanvas.y = editorData.y || 0;
+            this._graphCanvas.zoom = editorData.zoom || 1;
+            for (var location of editorData.locations) {
                 for (var node of this._graphCanvas.nodes) {
                     if (node.block && node.block.uniqueId === location.blockId) {
-                        node.x = this._graphCanvas.getGridPosition(location.x);
-                        node.y = this._graphCanvas.getGridPosition(location.y);
+                        node.x = location.x;
+                        node.y = location.y;
+                        node.cleanAccumulation();
                         break;
                     }
                 }
@@ -368,11 +383,12 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             newNode = this.createNodeFromObject(block);
         };
 
-        let x = this._graphCanvas.getGridPosition(event.clientX - event.currentTarget.offsetLeft - this._graphCanvas.x - this.NodeWidth);
-        let y = this._graphCanvas.getGridPosition(event.clientY - event.currentTarget.offsetTop - this._graphCanvas.y - 20);
+        let x = event.clientX - event.currentTarget.offsetLeft - this._graphCanvas.x - this.NodeWidth;
+        let y = event.clientY - event.currentTarget.offsetTop - this._graphCanvas.y - 20;
         
         newNode.x = x / this._graphCanvas.zoom;
         newNode.y = y / this._graphCanvas.zoom;
+        newNode.cleanAccumulation();
 
         this.props.globalState.onSelectionChangedObservable.notifyObservers(newNode);
 
@@ -386,8 +402,9 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                 let connectedNode = existingNodes[0];
 
                 if (connectedNode.x === 0 && connectedNode.y === 0) {
-                    connectedNode.x = this._graphCanvas.getGridPosition(x) / this._graphCanvas.zoom; 
-                    connectedNode.y = this._graphCanvas.getGridPosition(y) / this._graphCanvas.zoom;
+                    connectedNode.x = x / this._graphCanvas.zoom; 
+                    connectedNode.y = y / this._graphCanvas.zoom;
+                    connectedNode.cleanAccumulation();
                     y += 80;
                 }
             }

+ 1 - 0
nodeEditor/src/main.scss

@@ -127,6 +127,7 @@
         width: 100%;
         display: grid;
         outline: 0 !important;
+        user-select: none;
 
         #preview-canvas {
             width: 100%;

+ 7 - 0
nodeEditor/src/nodeLocationInfo.ts

@@ -2,4 +2,11 @@ export interface INodeLocationInfo {
     blockId: number;
     x: number;
     y: number;
+}
+
+export interface IEditorData {
+    locations: INodeLocationInfo[];
+    x: number;
+    y: number;
+    zoom: number;
 }

+ 13 - 5
nodeEditor/src/serializationTools.ts

@@ -6,18 +6,22 @@ import { DataStorage } from './dataStorage';
 export class SerializationTools {
 
     public static UpdateLocations(material: NodeMaterial, globalState: GlobalState) {
-        material.editorData = [];
+        material.editorData = {
+            locations: []
+        };
 
         // Store node locations
         for (var block of material.attachedBlocks) {
             let node = globalState.onGetNodeFromBlock(block);
 
-            material.editorData.push({
+            material.editorData.locations.push({
                 blockId: block.uniqueId,
                 x: node ? node.x : 0,
                 y: node ? node.y : 0
             });
         }
+
+        globalState.storeEditorData(material.editorData);
     }
 
     public static Serialize(material: NodeMaterial, globalState: GlobalState) {
@@ -30,17 +34,21 @@ export class SerializationTools {
         for (var block of material.attachedBlocks) {
             let node = globalState.onGetNodeFromBlock(block);
 
-            if (!serializationObject.locations) {
-                serializationObject.locations = [];
+            if (!serializationObject.editorData) {
+                serializationObject.editorData = {
+                    locations: []
+                };
             }
 
-            serializationObject.locations.push({
+            serializationObject.editorData.locations.push({
                 blockId: block.uniqueId,
                 x: node ? node.x : 0,
                 y: node ? node.y : 0
             });
         }
 
+        globalState.storeEditorData(serializationObject.editorData);
+
         Texture.SerializeBuffers = bufferSerializationState;
 
         return JSON.stringify(serializationObject, undefined, 2);

+ 11 - 3
src/Materials/Node/nodeMaterial.ts

@@ -1231,18 +1231,26 @@ export class NodeMaterial extends PushMaterial {
         }
 
         // UI related info
-        if (source.locations) {
+        if (source.locations || source.editorData && source.editorData.locations) {
             let locations: {
                 blockId: number;
                 x: number;
                 y: number;
-            }[] = source.locations;
+            }[] = source.locations || source.editorData.locations;
 
             for (var location of locations) {
                 location.blockId = map[location.blockId].uniqueId;
             }
 
-            this.editorData = locations;
+            if (source.locations) {
+                this.editorData = {
+                    locations: locations
+                }
+            } else {
+                this.editorData = source.editorData;
+                this.editorData.locations = locations;
+            }
+
         }
     }