소스 검색

Merge pull request #8785 from BabylonJS/msDestiny14/nme

NME Improvments: Custom Frames
David Catuhe 5 년 전
부모
커밋
ed67be7c83

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 10 - 0
nodeEditor/imgs/add.svg


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 10 - 0
nodeEditor/imgs/delete.svg


+ 69 - 18
nodeEditor/src/components/nodeList/nodeList.scss

@@ -75,33 +75,84 @@
                         color: white;
                     }
                 }
-            
-                .buttonLine {
+
+                .nonDraggableLine {
                     height: 30px;
                     display: grid;
                     align-items: center;
                     justify-items: stretch;
+                    background: #222222;
+                    text-align: center;
+                    margin: 0;
+                    box-sizing: border-box;
+                }
 
-                    button {
-                        background: #222222;
-                        margin: 5px 10px 5px 10px;
-                        color:white;
-                        padding: 4px 5px;
-                        opacity: 0.9;
-                    }
+                .withButton {
+                    height: 40px;
+                    position: relative;
+                    .icon {
+                        position: absolute;
+                        right: 10px;
+                        top: 10px;
 
-                    button:hover {
-                        opacity: 1.0;
+                        &:hover {
+                            cursor: pointer;
+                        }
                     }
 
-                    button:active {
-                        background: #282828;
-                    }   
+                    .buttonLine {
+                       
+                        position: absolute;
+                        right: 10px;
+                        top: 10px;
+                        input[type="file"] {
+                            display: none;
+                        }
+                
+                        .file-upload {            
+                            background: transparent;
+                            border: transparent;
+                            margin: 0px 0px;
+                            color:white;
+                            padding: 0px 0px 5px 20px;
+                            opacity: 0.9;
+                            cursor: pointer;
+                            text-align: center;
+                        }
+                
+                        .file-upload:hover {
+                            opacity: 1.0;
+                        }
+                
+                        .file-upload:active {
+                            transform: scale(0.98);
+                            transform-origin: 0.5 0.5;
+                        }
+                
+                        button {
+                            background: transparent;
+                            border: transparent;
+                            margin: 5px 10px 5px 10px;
+                            color:white;
+                            padding: 4px 5px;
+                            opacity: 0.9;
+                        }
+                
+                        button:hover {
+                            opacity: 0.0;
+                        }
+                
+                        button:active {
+                            background: transparent;
+                        }   
+                        
+                        button:focus {
+                            border: transparent;
+                            outline: 0px;
+                        }  
+                    }
                     
-                    button:focus {
-                        outline: 0px;
-                    }  
-                }
+                }                
 
                 .paneContainer {
                     margin-top: 3px;

+ 73 - 6
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -6,6 +6,11 @@ import { DraggableLineComponent } from '../../sharedComponents/draggableLineComp
 import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 import { Observer } from 'babylonjs/Misc/observable';
 import { Nullable } from 'babylonjs/types';
+import { DraggableLineWithButtonComponent } from '../../sharedComponents/draggableLineWithButtonComponent';
+import { LineWithFileButtonComponent } from '../../sharedComponents/lineWithFileButtonComponent';
+import { Tools } from 'babylonjs/Misc/tools';
+const addButton = require("../../../imgs/add.svg");
+const deleteButton = require('../../../imgs/delete.svg');
 
 require("./nodeList.scss");
 
@@ -144,12 +149,19 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "FragCoordBlock": "The gl_FragCoord predefined variable that contains the window relative coordinate (x, y, z, 1/w)",
         "ScreenSizeBlock": "The size (in pixels) of the screen window",
     };
+    
+    private _customFrameList: {[key: string]: string};
 
     constructor(props: INodeListComponentProps) {
         super(props);
 
         this.state = { filter: "" };
 
+        let frameJson = localStorage.getItem("Custom-Frame-List");
+        if(frameJson) {
+            this._customFrameList = JSON.parse(frameJson);
+        }
+
         this._onResetRequiredObserver = this.props.globalState.onResetRequiredObservable.add(() => {
             this.forceUpdate();
         });
@@ -163,10 +175,56 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         this.setState({ filter: filter });
     }
 
+    loadCustomFrame(file: File) {
+        Tools.ReadFile(file, async (data) => {
+            // get Frame Data from file
+            let decoder = new TextDecoder("utf-8");
+            const frameData = JSON.parse(decoder.decode(data));
+            let frameName = frameData.editorData.frames[0].name + "Custom";
+            let frameToolTip = frameData.editorData.frames[0].comments || "";
+
+            try {
+                localStorage.setItem(frameName, JSON.stringify(frameData));
+            } catch (error) {
+                this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers("Error Saving Frame");
+                return;
+            }
+
+            let frameJson = localStorage.getItem("Custom-Frame-List");
+            let frameList:  {[key: string]: string} = {};
+            if(frameJson) {
+                frameList = JSON.parse(frameJson); 
+            }
+            frameList[frameName] = frameToolTip;
+            localStorage.setItem("Custom-Frame-List", JSON.stringify(frameList));
+                this._customFrameList = frameList;
+                this.forceUpdate();
+
+        }, undefined, true);
+    }
+
+    removeItem(value : string) : void {
+        let frameJson = localStorage.getItem("Custom-Frame-List");
+            if(frameJson) {
+                let frameList = JSON.parse(frameJson);
+                delete frameList[value];
+                localStorage.removeItem(value);
+                localStorage.setItem("Custom-Frame-List", JSON.stringify(frameList));
+                this._customFrameList = frameList;
+                this.forceUpdate();
+            }        
+    }
+
     render() {
+
+        let customFrameNames: string[] = [];
+        for(let frame in this._customFrameList){
+            customFrameNames.push(frame);
+        }
+        
         // Block types used to create the menu from
         const allBlocks = {
-
+            Custom_Frames: customFrameNames,
             Animation: ["BonesBlock", "MorphTargetsBlock"],
             Color_Management: ["ReplaceColorBlock", "PosterizeBlock", "GradientBlock", "DesaturateBlock"],
             Conversion_Blocks: ["ColorMergerBlock", "ColorSplitterBlock", "VectorMergerBlock", "VectorSplitterBlock"],
@@ -211,18 +269,28 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             var blockList = (allBlocks as any)[key].filter((b: string) => !this.state.filter || b.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1)
             .sort((a: string, b: string) => a.localeCompare(b))
             .map((block: any, i: number) => {
-                let tooltip = NodeListComponent._Tooltips[block] || "";
+                if(key === "Custom_Frames") {
+                    return <DraggableLineWithButtonComponent key={block} data={block} tooltip={this._customFrameList[block] || ""} iconImage={deleteButton} iconTitle="Delete"
+                    onIconClick={ value => this.removeItem(value)}/>;
+                }
+                return <DraggableLineComponent key={block} data={block} tooltip={ NodeListComponent._Tooltips[block] || ""}/>;
 
-                return <DraggableLineComponent key={block} data={block} tooltip={tooltip}/>;
             });
 
-            if (blockList.length) {
+            if(key === "Custom_Frames") {
+                let line =  <LineWithFileButtonComponent title={"Add Custom Frame"} closed={false}
+                label="Add..." uploadName={'custom-frame-upload'} iconImage={addButton} accept=".json" onIconClick={(file) => {
+                    this.loadCustomFrame(file);
+                }}/>;
+                blockList.push(line);
+            }         
+            if(blockList.length) {
                 blockMenu.push(
                     <LineContainerComponent key={key + " blocks"} title={key.replace("__", ": ").replace("_", " ")} closed={false}>
                         {blockList}
                     </LineContainerComponent>
                 );
-            }
+           }
         }
 
         return (
@@ -244,6 +312,5 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
                 </div>
             </div>
         );
-
     }
 }

+ 26 - 1
nodeEditor/src/graphEditor.tsx

@@ -544,10 +544,35 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
     emitNewBlock(event: React.DragEvent<HTMLDivElement>) {
         var data = event.dataTransfer.getData("babylonjs-material-node") as string;
         let newNode: GraphNode;
+        
+        if(data.indexOf("Custom") > -1) {
+            let storageData = localStorage.getItem(data);
+            if(storageData) {   
+                let frameData = JSON.parse(storageData);
+
+                //edit position before loading.
+                let newX = (event.clientX - event.currentTarget.offsetLeft - this._graphCanvas.x - this.NodeWidth) / this._graphCanvas.zoom;
+                let newY = (event.clientY - event.currentTarget.offsetTop - this._graphCanvas.y - 20) / this._graphCanvas.zoom;;
+                let oldX = frameData.editorData.frames[0].x;
+                let oldY = frameData.editorData.frames[0].y;
+                frameData.editorData.frames[0].x = newX;
+                frameData.editorData.frames[0].y = newY;
+                for (var location of frameData.editorData.locations) {
+                    location.x +=  newX - oldX;
+                    location.y +=  newY - oldY;       
+                }
+
+                SerializationTools.AddFrameToMaterial(frameData, this.props.globalState, this.props.globalState.nodeMaterial); 
+                this._graphCanvas.frames[this._graphCanvas.frames.length -1].cleanAccumulation();
+                this.forceUpdate();
+                return;
+            }
+        }
 
         if (data.indexOf("Block") === -1) {
             newNode = this.addValueNode(data);
-        } else {
+        } 
+        else {
             let block = BlockTools.GetBlockFromString(data, this.props.globalState.nodeMaterial.getScene(), this.props.globalState.nodeMaterial)!;   
             
             if (block.isUnique) {

+ 31 - 0
nodeEditor/src/sharedComponents/draggableLineWithButtonComponent.tsx

@@ -0,0 +1,31 @@
+import * as React from "react";
+
+export interface IDraggableLineWithButtonComponent {
+    data: string;
+    tooltip: string;
+    iconImage: any;
+    onIconClick: (value: string) => void;
+    iconTitle: string;
+}
+
+export class DraggableLineWithButtonComponent extends React.Component<IDraggableLineWithButtonComponent> {
+    constructor(props: IDraggableLineWithButtonComponent) {
+        super(props);
+    }
+
+    render() { 
+        return (
+            <div className="draggableLine withButton" 
+                title={this.props.tooltip}
+                draggable={true}
+                onDragStart={event => {
+                    event.dataTransfer.setData("babylonjs-material-node", this.props.data);
+                }}>
+                {this.props.data.substr(0, this.props.data.length - 6)}
+                <div className="icon" onClick={() => { this.props.onIconClick(this.props.data); }} title={this.props.iconTitle}>
+                    <img title={this.props.iconTitle} src={this.props.iconImage}/>
+                </div>
+            </div>
+        );
+    }
+}

+ 52 - 0
nodeEditor/src/sharedComponents/lineWithFileButtonComponent.tsx

@@ -0,0 +1,52 @@
+import * as React from "react";
+import { DataStorage } from 'babylonjs/Misc/dataStorage';
+
+interface ILineWithFileButtonComponentProps {
+    title: string;
+    closed?: boolean;
+    label: string;
+    iconImage: any;
+    onIconClick: (file: File) => void;
+    accept: string;
+    uploadName?: string;
+}
+
+export class LineWithFileButtonComponent extends React.Component<ILineWithFileButtonComponentProps, { isExpanded: boolean }> {
+    private uploadRef: React.RefObject<HTMLInputElement>
+    constructor(props: ILineWithFileButtonComponentProps) {
+        super(props);
+
+        let initialState = DataStorage.ReadBoolean(this.props.title, !this.props.closed);
+        this.state = { isExpanded: initialState };
+        this.uploadRef = React.createRef();
+    }
+
+    onChange(evt: any) {
+        var files: File[] = evt.target.files;
+        if (files && files.length) {
+            this.props.onIconClick(files[0]);
+        }
+        evt.target.value = "";
+    }
+
+    switchExpandedState(): void {
+        const newState = !this.state.isExpanded;
+        DataStorage.WriteBoolean(this.props.title, newState);
+        this.setState({ isExpanded: newState });
+    }
+
+    render() {
+        return (
+            <div className="nonDraggableLine withButton">
+                {this.props.label}
+                <div className="icon" title={this.props.title}>
+                <img src={this.props.iconImage}/>
+                </div>
+                <div className="buttonLine" title={this.props.title}>
+                    <label htmlFor={this.props.uploadName ? this.props.uploadName : "file-upload"} className="file-upload"/>   
+                    <input ref={this.uploadRef} id={this.props.uploadName ? this.props.uploadName : "file-upload"} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
+                </div>
+            </div>
+        ); 
+    }
+}