Browse Source

move frame node observables to NodePort. Add feedback on selection of frame port. add hiding behavior to move port buttons

Kyle Belfort 5 years ago
parent
commit
3679c24112

+ 7 - 1
nodeEditor/src/diagram/graphCanvas.scss

@@ -30,7 +30,13 @@
         
         .img {
             width: 100%;
-        }     
+        }
+
+        img.selected {
+            margin: 3px 1px 0 4px;
+            box-shadow: 0 0 0 4px;
+            border-radius: 50%;
+        }
         
         &:hover, &.selected {
             filter: brightness(2);

+ 0 - 10
nodeEditor/src/diagram/graphCanvas.tsx

@@ -199,16 +199,6 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
             this._candidatePort = port;
         });
 
-        props.globalState.onFramePortMoveUpObserver.add((nodePort: NodePort) => {
-            const frame = this._frames.find(frame => frame.id === nodePort.frameId);
-            frame?.moveFramePortUp(nodePort);
-        });
-
-        props.globalState.onFramePortMoveDownObserver.add((nodePort: NodePort) => {
-            const frame = this._frames.find(frame => frame.id === nodePort.frameId);
-            frame?.moveFramePortDown(nodePort);
-        })
-
         props.globalState.onGridSizeChanged.add(() => {
             this.gridSize = DataStorage.ReadNumber("GridSize", 20);
         });

+ 111 - 17
nodeEditor/src/diagram/graphFrame.ts

@@ -20,6 +20,10 @@ enum ResizingDirection {
         BottomLeft
 }
 
+export enum FramePortPosition {
+    Top, Middle, Bottom
+};
+
 export class GraphFrame {
     private readonly CollapsedWidth = 200;
     private static _FrameCounter = 0;
@@ -49,6 +53,8 @@ export class GraphFrame {
     private _onSelectionChangedObserver: Nullable<Observer<Nullable<GraphNode | NodeLink | GraphFrame | NodePort>>>;
     private _isCollapsed = false;
     private _ports: NodePort[] = []; // Ports on Outside of Frame
+    private _frameInPorts: NodePort[] = [];
+    private _frameOutPorts: NodePort[] = [];
     private _controlledPorts: NodePort[] = []; // Ports on Nodes that are shown on outside of frame
     private _id: number;
     private _comments: string;
@@ -75,9 +81,21 @@ export class GraphFrame {
     private _createInputPort(port: NodePort, node: GraphNode) {
         let localPort = NodePort.CreatePortElement(port.connectionPoint, node, this._inputPortContainer, null, this._ownerCanvas.globalState, true, this.id, GraphFrame._FramePortCounter++)
         this._ports.push(localPort);
+        this._frameInPorts.push(localPort);
 
         port.delegatedPort = localPort;
         this._controlledPorts.push(port);
+        localPort.onFramePortMoveUpObservable.add((nodePort: NodePort) => {
+            if(this.id === nodePort.frameId) {
+                this.moveFramePortUp(nodePort);
+            }
+        });
+
+        localPort.onFramePortMoveDownObservable.add((nodePort: NodePort) => {
+            if(this.id === nodePort.frameId) {
+                this.moveFramePortDown(nodePort);
+            }
+        })
     }
 
     public set isCollapsed(value: boolean) {
@@ -108,6 +126,19 @@ export class GraphFrame {
                                     portAdded = true;
                                     localPort = NodePort.CreatePortElement(port.connectionPoint, link.nodeB!, this._outputPortContainer, null, this._ownerCanvas.globalState, false, this.id, GraphFrame._FramePortCounter++);
                                     this._ports.push(localPort);
+                                    this._frameOutPorts.push(localPort);
+
+                                    localPort.onFramePortMoveUpObservable.add((nodePort: NodePort) => {
+                                        if(this.id === nodePort.frameId) {
+                                            this.moveFramePortUp(nodePort);
+                                        }
+                                    });
+                            
+                                    localPort.onFramePortMoveDownObservable.add((nodePort: NodePort) => {
+                                        if(this.id === nodePort.frameId) {
+                                            this.moveFramePortDown(nodePort);
+                                        }
+                                    })
                                 } else {
                                     localPort = this._ports.filter(p => p.connectionPoint === port.connectionPoint)[0];
                                 }
@@ -120,8 +151,21 @@ export class GraphFrame {
                     } else {
                         let localPort = NodePort.CreatePortElement(port.connectionPoint, node, this._outputPortContainer, null, this._ownerCanvas.globalState, false, this.id, GraphFrame._FramePortCounter++)
                         this._ports.push(localPort);
+                        this._frameOutPorts.push(localPort);
                         port.delegatedPort = localPort;
                         this._controlledPorts.push(port);
+
+                        localPort.onFramePortMoveUpObservable.add((nodePort: NodePort) => {
+                            if(this.id === nodePort.frameId) {
+                                this.moveFramePortUp(nodePort);
+                            }
+                        });
+                
+                        localPort.onFramePortMoveDownObservable.add((nodePort: NodePort) => {
+                            if(this.id === nodePort.frameId) {
+                                this.moveFramePortDown(nodePort);
+                            }
+                        });
                     }
                 }
 
@@ -138,6 +182,29 @@ export class GraphFrame {
                     }
                 }
             }
+
+            for(let i = 0; i < this._frameInPorts.length; i++) {
+                const port = this._frameInPorts[i];
+                if(i === 0){
+                    port.framePortPosition = FramePortPosition.Top;
+                } else if(i === this._frameInPorts.length -1){
+                    port.framePortPosition = FramePortPosition.Bottom;
+                } else {
+                    port.framePortPosition = FramePortPosition.Middle;
+                }
+            }
+
+            for(let i = 0; i < this._frameOutPorts.length; i++) {
+                const port = this._frameInPorts[i];
+                if(i === 0){
+                    port.framePortPosition = FramePortPosition.Top
+                } else if(i === this._frameInPorts.length -1){
+                    port.framePortPosition = FramePortPosition.Bottom
+                } else {
+                    port.framePortPosition = FramePortPosition.Middle
+                }
+            }
+
         } else {
             this.element.classList.remove("collapsed");
             this._outputPortContainer.innerHTML = "";
@@ -153,6 +220,8 @@ export class GraphFrame {
             })
 
             this._ports = [];
+            this._frameInPorts = [];
+            this._frameOutPorts = [];
             this._controlledPorts = [];
 
             for (var node of this._nodes) {
@@ -549,57 +618,82 @@ export class GraphFrame {
         evt.stopPropagation();
     }
 
-    public moveFramePortUp(nodePort: NodePort){
+    private moveFramePortUp(nodePort: NodePort){
         if(nodePort.isInput) {
             if(this._inputPortContainer.children.length < 2) {
                 return;
             }
             const elementsArray = Array.from(this._inputPortContainer.childNodes);
-            this._moveFramePortUp(elementsArray, nodePort);
+            this._movePortUp(elementsArray, nodePort);
         } else {
             if(this._outputPortContainer.children.length < 2) {
                 return;
             }
             const elementsArray = Array.from(this._outputPortContainer.childNodes);
-            this._moveFramePortUp(elementsArray, nodePort);
+            this._movePortUp(elementsArray, nodePort);
         }
 
     }
 
-    private _moveFramePortUp(elementsArray: ChildNode[], nodePort: NodePort) {
-        const indexInContainer = (elementsArray as HTMLElement[]).findIndex(elem => elem.dataset.framePortId === `${nodePort.framePortId}`)
-        if(indexInContainer === 0){
+    private _movePortUp(elementsArray: ChildNode[], nodePort: NodePort) {
+        // update UI
+        const indexInElementArray = (elementsArray as HTMLElement[]).findIndex(elem => elem.dataset.framePortId === `${nodePort.framePortId}`)
+        if(indexInElementArray === 0){
             return;
         }
-        const secondPort = elementsArray[indexInContainer];
-        const firstPort =  elementsArray[indexInContainer -1];
-        firstPort.parentElement?.insertBefore(secondPort, firstPort);
+        const secondPortElement = elementsArray[indexInElementArray];
+        const firstPortElement =  elementsArray[indexInElementArray -1];
+        firstPortElement.parentElement?.insertBefore(secondPortElement, firstPortElement);
+
+        // update Frame Port Container
+        const indexInContainer = this._frameInPorts.findIndex(framePort => framePort === nodePort);
+        [this._frameInPorts[indexInContainer -1], this._frameInPorts[indexInContainer]] = [this._frameInPorts[indexInContainer], this._frameInPorts[indexInContainer -1]]; // swap idicies
+        
+        // notify nodePort if it is now at Top (indexInElementArray === 1)
+        if (indexInElementArray === 1) {
+            nodePort.onFramePortPositionChangedObservable.notifyObservers(FramePortPosition.Top);
+        } else {
+            nodePort.onFramePortPositionChangedObservable.notifyObservers(FramePortPosition.Middle);
+        }
     }
     
-    public moveFramePortDown(nodePort: NodePort){
+    private moveFramePortDown(nodePort: NodePort){
         if(nodePort.isInput) {
             if(this._inputPortContainer.children.length < 2) {
                 return;
             }
             const elementsArray = Array.from(this._inputPortContainer.childNodes);
-            this._moveFramePortDown(elementsArray, nodePort);
+            this._movePortDown(elementsArray, nodePort);
         } else {
             if(this._outputPortContainer.children.length < 2) {
                 return;
             }
             const elementsArray = Array.from(this._outputPortContainer.childNodes);
-            this._moveFramePortDown(elementsArray, nodePort);
+            this._movePortDown(elementsArray, nodePort);
         }
     }
 
-    private _moveFramePortDown(elementsArray: ChildNode[], nodePort: NodePort) {
-        const indexInContainer = (elementsArray as HTMLElement[]).findIndex(elem => elem.dataset.framePortId === `${nodePort.framePortId}`)
-        if(indexInContainer === elementsArray.length -1){
+    private _movePortDown(elementsArray: ChildNode[], nodePort: NodePort) {
+        // update UI
+        const indexInElementArray = (elementsArray as HTMLElement[]).findIndex(elem => elem.dataset.framePortId === `${nodePort.framePortId}`)
+        if(indexInElementArray === elementsArray.length -1){
             return;
         }
-        const firstPort = elementsArray[indexInContainer];
-        const secondPort =  elementsArray[indexInContainer + 1];
+        const firstPort = elementsArray[indexInElementArray];
+        const secondPort =  elementsArray[indexInElementArray + 1];
         firstPort.parentElement?.insertBefore(secondPort, firstPort);
+
+        // update Frame Port Container
+        const indexInContainer = this._frameInPorts.findIndex(framePort => framePort === nodePort);
+        [this._frameInPorts[indexInContainer], this._frameInPorts[indexInContainer + 1]] = [this._frameInPorts[indexInContainer + 1], this._frameInPorts[indexInContainer]]; // swap idicies
+
+        // notify nodePort if it is now at bottom (indexInContainer === elementsArray.length-2)
+        if(indexInContainer === elementsArray.length-2) {
+            nodePort.onFramePortPositionChangedObservable.notifyObservers(FramePortPosition.Bottom);
+        } else {
+            nodePort.onFramePortPositionChangedObservable.notifyObservers(FramePortPosition.Middle);
+        }
+
     }
 
     private initResizing = (evt: PointerEvent) => {

+ 42 - 2
nodeEditor/src/diagram/nodePort.ts

@@ -3,21 +3,28 @@ import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/
 import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
 import { GlobalState } from '../globalState';
 import { Nullable } from 'babylonjs/types';
-import { Observer } from 'babylonjs/Misc/observable';
+import { Observer, Observable } from 'babylonjs/Misc/observable';
 import { Vector2 } from 'babylonjs/Maths/math.vector';
 import { IDisplayManager } from './display/displayManager';
 import { GraphNode } from './graphNode';
+import { NodeLink } from './nodeLink';
+import { GraphFrame, FramePortPosition } from './graphFrame';
 
 export class NodePort {
+    private _onFramePortMoveUpObservable = new Observable<NodePort>();
+    private _onFramePortMoveDownObservable = new Observable<NodePort>();
+    private _onFramePortPositionChangedObservable = new Observable<FramePortPosition>();
     private _element: HTMLDivElement;
     private _img: HTMLImageElement;
     private _globalState: GlobalState;
     private _onCandidateLinkMovedObserver: Nullable<Observer<Nullable<Vector2>>>;
+    private _onSelectionChangedObserver: Nullable<Observer<Nullable<GraphNode | NodeLink | GraphFrame | NodePort>>>;  
     private _portLabel: Element;
     private _frameId: Nullable<number>
     private _isInput: boolean;
+    private _framePortPosition: FramePortPosition
     private _framePortId: Nullable<number>;
-
+    
     public delegatedPort: Nullable<NodePort> = null;
 
     public get element(): HTMLDivElement {
@@ -28,6 +35,18 @@ export class NodePort {
         return this._element;
     }
 
+    public get onFramePortMoveUpObservable() {
+        return this._onFramePortMoveUpObservable;
+    }
+
+    public get onFramePortMoveDownObservable() {
+        return this._onFramePortMoveDownObservable;
+    }
+
+    public get onFramePortPositionChangedObservable() {
+        return this._onFramePortPositionChangedObservable;
+    }
+
     public get frameId() {
         return this._frameId;
     }
@@ -48,6 +67,15 @@ export class NodePort {
         this._portLabel.innerHTML = newLabel;
     }
 
+    public get framePortPosition() {
+        return this._framePortPosition;
+    }
+
+    public set framePortPosition(position: FramePortPosition) {
+        this._framePortPosition = position;
+        this.onFramePortPositionChangedObservable.notifyObservers(position);
+    }
+
     public refresh() {
         this._element.style.background = BlockTools.GetColorFromConnectionNodeType(this.connectionPoint.type);
         switch (this.connectionPoint.type) {
@@ -105,11 +133,23 @@ export class NodePort {
             this._globalState.onCandidatePortSelected.notifyObservers(this);
         });
 
+        this._onSelectionChangedObserver = this._globalState.onSelectionChangedObservable.add((selection) => {
+            if (selection === this) {
+                this._img.classList.add("selected");
+            } else {
+                this._img.classList.remove("selected");
+            }
+        });
+
         this.refresh();
     }
 
     public dispose() {
         this._globalState.onCandidateLinkMoved.remove(this._onCandidateLinkMovedObserver);
+
+        if (this._onSelectionChangedObserver) {
+            this._globalState.onSelectionChangedObservable.remove(this._onSelectionChangedObserver);
+        }
     }
 
     public static CreatePortElement(connectionPoint: NodeMaterialConnectionPoint, node: GraphNode, root: HTMLElement, 

+ 22 - 8
nodeEditor/src/diagram/properties/nodePortPropertyComponent.tsx

@@ -5,16 +5,30 @@ import { GlobalState } from '../../globalState';
 import { TextInputLineComponent } from '../../sharedComponents/textInputLineComponent';
 import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
 import { NodePort } from '../nodePort';
+import { FramePortPosition } from '../graphFrame';
+import { Nullable, Observer } from 'babylonjs';
 
 export interface INodePortPropertyTabComponentProps {
     globalState: GlobalState
     nodePort: NodePort;
 }
 
-export class NodePortPropertyTabComponent extends React.Component<INodePortPropertyTabComponentProps> {
+export class NodePortPropertyTabComponent extends React.Component<INodePortPropertyTabComponentProps, {framePortPosition: FramePortPosition}> {    
+    private _onFramePortPositionChangedObserver: Nullable<Observer<FramePortPosition>>;
 
     constructor(props: INodePortPropertyTabComponentProps) {
-        super(props)
+        super(props);
+        this.state = {
+            framePortPosition: this.props.nodePort.framePortPosition
+        };
+
+        this._onFramePortPositionChangedObserver = this.props.nodePort.onFramePortPositionChangedObservable.add((position) => {
+            this.setState({framePortPosition: position})
+        });
+    }
+
+    componentWillUnmount() {
+        this.props.nodePort.onFramePortPositionChangedObservable.remove(this._onFramePortPositionChangedObserver)
     }
 
     render() {      
@@ -29,13 +43,13 @@ export class NodePortPropertyTabComponent extends React.Component<INodePortPrope
             <div>
                 <LineContainerComponent title="GENERAL">
                     <TextInputLineComponent globalState={this.props.globalState} label="Port Label" propertyName="portLabel" target={this.props.nodePort}/>
-                    <ButtonLineComponent label="Move Node Up" onClick={() => {
-                                this.props.globalState.onFramePortMoveUpObserver.notifyObservers(this.props.nodePort);
-                            }} />
+                     {this.state.framePortPosition !== FramePortPosition.Top && <ButtonLineComponent label="Move Node Up" onClick={() => {
+                                this.props.nodePort.onFramePortMoveUpObservable.notifyObservers(this.props.nodePort);
+                            }} />}
 
-                    <ButtonLineComponent label="Move Node Down" onClick={() => {
-                                this.props.globalState.onFramePortMoveDownObserver.notifyObservers(this.props.nodePort);
-                            }} />
+                    {this.state.framePortPosition !== FramePortPosition.Bottom && <ButtonLineComponent label="Move Node Down" onClick={() => {
+                                this.props.nodePort.onFramePortMoveDownObservable.notifyObservers(this.props.nodePort);
+                            }} />}
                 </LineContainerComponent>
             </div>
         </div>

+ 0 - 2
nodeEditor/src/globalState.ts

@@ -37,8 +37,6 @@ export class GlobalState {
     onSelectionBoxMoved = new Observable<ClientRect | DOMRect>();
     onFrameCreated = new Observable<GraphFrame>();
     onCandidatePortSelected = new Observable<Nullable<NodePort>>();
-    onFramePortMoveUpObserver = new Observable<NodePort>();
-    onFramePortMoveDownObserver = new Observable<NodePort>();
     onGetNodeFromBlock: (block: NodeMaterialBlock) => GraphNode;
     onGridSizeChanged = new Observable<void>();
     previewMeshType: PreviewMeshType;