瀏覽代碼

Merge pull request #8307 from toledoal/edit-animation

Edit animation
David Catuhe 5 年之前
父節點
當前提交
06738730b6

+ 1 - 1
dist/preview release/what's new.md

@@ -8,7 +8,7 @@
 - Added HDR texture filtering tools to the sandbox ([Sebavan](https://github.com/sebavan/))
 - Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
-- Added Curve editor to manage selected entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
+- Added Curve editor to manage entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
 - Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 - Added support for **thin instances** for faster mesh instances. [Doc](https://doc.babylonjs.com/how_to/how_to_use_thininstances) ([Popov72](https://github.com/Popov72))
 

+ 2 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx

@@ -15,6 +15,7 @@ interface IAddAnimationProps {
    entity: IAnimatable;
    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
    setNotificationMessage: (message: string) => void;
+   changed: () => void;
 }
 
 export class AddAnimation extends React.Component<IAddAnimationProps, {animationName: string, animationTargetProperty: string, animationType:string, loopMode: number, animationTargetPath:string}>{ 
@@ -187,6 +188,7 @@ export class AddAnimation extends React.Component<IAddAnimationProps, {animation
                         const updatedCollection = [...this.props.entity.animations, animation]
                         this.raiseOnPropertyChanged(updatedCollection, store);
                         this.props.entity.animations = updatedCollection;
+                        this.props.changed();
                         this.props.close();
                     }   
                 }

+ 8 - 123
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -10,12 +10,9 @@ import { Playhead } from './playhead';
 import { Notification } from './notification';
 import { GraphActionsBar } from './graphActionsBar';
 import { Scene } from "babylonjs/scene";
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
 import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
 import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
-import { AddAnimation } from './addAnimation';
-import { Nullable } from 'babylonjs/types';
-import { IconButtonLineComponent } from '../../../lines/iconButtonLineComponent';
+import { EditorControls } from './editorControls';
 
 require("./curveEditor.scss");
 
@@ -51,7 +48,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
     lastFrame: number,
     playheadPos: number,
     isPlaying: boolean,
-    isAnimationDialogOpen: boolean
 }> {
 
     // Height scale *Review this functionaliy
@@ -118,7 +114,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             scale: 1,
             playheadPos: 0,
             isPlaying: this.isAnimationPlaying(),
-            isAnimationDialogOpen: false
         }
     }
 
@@ -193,92 +188,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         }
     }
 
-    setListItem(animation: Animation, i: number) {
-        let element;
-
-        switch (animation.dataType) {
-            case Animation.ANIMATIONTYPE_FLOAT:
-                element = <li className={this.state.selected && this.state.selected.name === animation.name ? 'active' : ''} key={i} onClick={() => this.selectAnimation(animation)}>
-                    <p>{animation.name}&nbsp;
-                    <span>{animation.targetProperty}</span></p>
-                    {!(this.props.entity instanceof TargetedAnimation) ? this.state.selected && this.state.selected.name === animation.name ? <ButtonLineComponent label={"Remove"} onClick={() => this.deleteAnimation()} /> : null : null}
-                </li>
-                break;
-            case Animation.ANIMATIONTYPE_VECTOR2:
-                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
-                    <ul>
-                        <li key={`${i}_x`}>Property <strong>X</strong></li>
-                        <li key={`${i}_y`}>Property <strong>Y</strong></li>
-                    </ul>
-                </li>
-                break;
-            case Animation.ANIMATIONTYPE_VECTOR3:
-                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
-                    <ul>
-                        <li key={`${i}_x`}>Property <strong>X</strong></li>
-                        <li key={`${i}_y`}>Property <strong>Y</strong></li>
-                        <li key={`${i}_z`}>Property <strong>Z</strong></li>
-                    </ul>
-                </li>
-                break;
-            case Animation.ANIMATIONTYPE_QUATERNION:
-                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
-                    <ul>
-                        <li key={`${i}_x`}>Property <strong>X</strong></li>
-                        <li key={`${i}_y`}>Property <strong>Y</strong></li>
-                        <li key={`${i}_z`}>Property <strong>Z</strong></li>
-                        <li key={`${i}_w`}>Property <strong>W</strong></li>
-                    </ul>
-                </li>
-                break;
-            case Animation.ANIMATIONTYPE_COLOR3:
-                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
-                    <ul>
-                        <li key={`${i}_r`}>Property <strong>R</strong></li>
-                        <li key={`${i}_g`}>Property <strong>G</strong></li>
-                        <li key={`${i}_b`}>Property <strong>B</strong></li>
-                    </ul>
-                </li>
-                break;
-            case Animation.ANIMATIONTYPE_COLOR4:
-                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
-                    <ul>
-                        <li key={`${i}_r`}>Property <strong>R</strong></li>
-                        <li key={`${i}_g`}>Property <strong>G</strong></li>
-                        <li key={`${i}_b`}>Property <strong>B</strong></li>
-                        <li key={`${i}_a`}>Property <strong>A</strong></li>
-                    </ul>
-                </li>
-                break;
-            case Animation.ANIMATIONTYPE_SIZE:
-                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
-                    <ul>
-                        <li key={`${i}_width`}>Property <strong>Width</strong></li>
-                        <li key={`${i}_height`}>Property <strong>Height</strong></li>
-                    </ul>
-                </li>
-                break;
-            default: console.log("not recognized");
-                element = null;
-                break;
-        }
-
-        return element;
-    }
-
-    deleteAnimation() {
-        let currentSelected = this.state.selected;
-        if (this.props.entity instanceof TargetedAnimation) {
-            console.log("no animation remove allowed");
-        } else {
-            let animations = (this.props.entity as IAnimatable).animations;
-            if (animations) {
-                let updatedAnimations = animations.filter(anim => anim !== currentSelected);
-                (this.props.entity as IAnimatable).animations = updatedAnimations as Nullable<Animation[]>;
-            }
-        }
-    }
-
     /**
     * Keyframe Manipulation
     * This section handles events from SvgDraggableArea.
@@ -1087,37 +996,13 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                     
                 <div className="content">
                     <div className="row">
-                        <div className="animation-list">
-                            <div className="controls-header">
-                            {this._isTargetedAnimation ? null : <IconButtonLineComponent active={this.state.isAnimationDialogOpen} tooltip="Add Animation" icon="medium add-animation" onClick={() => { this.setState({ isAnimationDialogOpen: true})}}></IconButtonLineComponent>}
-                            <IconButtonLineComponent tooltip="Load Animation" icon="medium load" onClick={() => { this.setState({ isAnimationDialogOpen: true})}}></IconButtonLineComponent>
-                            <IconButtonLineComponent tooltip="Save Animation" icon="medium save" onClick={() => { this.setState({ isAnimationDialogOpen: true})}}></IconButtonLineComponent>
-                            <IconButtonLineComponent tooltip="Edit Animations" icon="medium animation-edit" onClick={() => { this.setState({ isAnimationDialogOpen: true})}}></IconButtonLineComponent>
-                            <IconButtonLineComponent tooltip="Loop/Unloop" icon="medium loop-active" onClick={() => { this.setState({ isAnimationDialogOpen: true})}}></IconButtonLineComponent>
-                            </div>
-                            { (this.props.entity instanceof TargetedAnimation) ? null : 
-                                <AddAnimation 
-                                    isOpen={this.state.isAnimationDialogOpen} 
-                                    close={() => { this.setState({isAnimationDialogOpen: false})}} 
-                                    entity={this.props.entity} 
-                                    setNotificationMessage={(message: string) => { this.setState({notification: message})}} />
-                            }
-
-                            <div className="object-tree">
-                                <ul>
-                                    {
-
-                                        this.props.entity instanceof TargetedAnimation ? this.setListItem(this.props.entity.animation, 0) :
-                                            this.props.entity.animations && this.props.entity.animations.map((animation, i) => {
-
-                                                return this.setListItem(animation, i);
-
-                                            })}
-
-                                </ul>
-                            </div>
-                        </div>
-
+                        <EditorControls selectAnimation={(animation: Animation) => this.selectAnimation(animation)} 
+                        isTargetedAnimation={this._isTargetedAnimation} 
+                        entity={this.props.entity} 
+                        selected={this.state.selected} 
+                        setNotificationMessage={(message: string) => { this.setState({notification: message})}}
+                        />
+                        
                         <div ref={this._graphCanvas} className="graph-chart" onWheel={(e) => this.zoom(e)} >
 
                             <Playhead frame={this.state.currentFrame} offset={this.state.playheadOffset} />

+ 47 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss

@@ -3,6 +3,10 @@
 
     font-family: acumin-pro-condensed;
 
+    .last {
+        margin-left: 3px;
+    }
+
     .icon {
         width: 40px;
         height: 40px;
@@ -221,6 +225,7 @@
             background-color: transparent;
             background-size: contain;
             color: white;
+            cursor: pointer;
         }
 
         &.loop-inactive {
@@ -229,6 +234,7 @@
             background-color: transparent;
             background-size: contain;
             color: white;
+            cursor: pointer;
         }
 
         &.move {
@@ -245,6 +251,7 @@
             background-color: transparent;
             background-size: contain;
             color: white;
+            cursor: pointer;
         }
 
         &.load {
@@ -253,6 +260,7 @@
             background-color: transparent;
             background-size: contain;
             color: white;
+            cursor: pointer;
         }
 
         &.checked {
@@ -471,6 +479,40 @@
             .controls-header {
                 display: flex;
                 background-color: #252525;
+
+               .input-fps{
+                display: flex;
+                align-items: center;
+                width: 52px;
+                padding-left: 4px;
+                    .numeric{
+                        input{
+                            width: 52px;
+                            font-size: 12px;
+                            height: 22px;
+                            background-color: black;
+                            border: none;
+                            color: white;
+                            text-align: center;
+                            &::-webkit-inner-spin-button, &::-webkit-outer-spin-button {
+                                appearance: none;
+                                -webkit-appearance: none; 
+                                margin: 0;
+                            }
+                            font-family: acumin-pro-condensed;
+                        }
+                    }
+                    p {
+                        color: white;
+                        font-size: 12px;
+                        margin: 0px;
+                        position: relative;
+                        right: 17px;
+                        line-height: 20px;
+                        height: 20px;
+                        margin-top: -2px;
+                    }
+               } 
             }
 
             .new-animation{
@@ -511,6 +553,8 @@
                                 font-size: 12px;
                                 line-height: 11px;
                                 margin: 5px;
+                                font-size: 10px;
+                                font-family: acumin-pro-condensed;
                             }
                         }
                     }
@@ -557,9 +601,8 @@
                 background-color:#111111;
                 padding: 10px;
                 margin-top: 19px;
-                height: 11em;
                 overflow: scroll;
-                overflow-x: hidden;
+                overflow-x: auto;
             }
 
             .label-input{
@@ -582,6 +625,7 @@
                         outline-style: auto;
                         outline-color: lightgrey;
                     }
+                    font-family: acumin-pro-condensed;
                 }
 
                 select {
@@ -596,6 +640,7 @@
                         outline-style: auto;
                         outline-color: lightgrey;
                     }
+                    font-family: acumin-pro-condensed;
                 }
             }
         }

+ 197 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx

@@ -0,0 +1,197 @@
+
+import * as React from "react";
+
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
+import { Animation } from 'babylonjs/Animations/animation';
+import { IconButtonLineComponent } from '../../../lines/iconButtonLineComponent';
+import { NumericInputComponent } from '../../../lines/numericInputComponent';
+import { AddAnimation } from './addAnimation';
+import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
+import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
+import { Nullable } from 'babylonjs/types';
+
+interface IEditorControlsProps {
+   isTargetedAnimation: boolean;
+   entity: IAnimatable | TargetedAnimation;
+   selected: Animation | null
+   onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+   setNotificationMessage: (message: string) => void;
+   selectAnimation: (selected: Animation) => void;
+}
+
+export class EditorControls extends React.Component<IEditorControlsProps, {isAnimationTabOpen: boolean, isEditTabOpen: boolean, isLoadTabOpen: boolean, isSaveTabOpen: boolean, isLoopActive: boolean, animationsCount: number; framesPerSecond: number}>{ 
+    
+    constructor(props: IEditorControlsProps) {
+        super(props);
+        let count = this.props.isTargetedAnimation ? 1 : (this.props.entity as IAnimatable).animations?.length ?? 0;
+        this.state = { isAnimationTabOpen: false, isEditTabOpen: false, isSaveTabOpen: false, isLoadTabOpen: false, isLoopActive: false, animationsCount: count, framesPerSecond: 60 }
+    }
+
+    animationsChanged(){
+        let recount = (this.props.entity as IAnimatable).animations?.length ?? 0;
+        this.setState( { animationsCount: recount } );
+    }
+
+    deleteAnimation() {
+        let currentSelected = this.props.selected;
+        if (this.props.entity instanceof TargetedAnimation) {
+            console.log("no animation remove allowed");
+        } else {
+            let animations = (this.props.entity as IAnimatable).animations;
+            if (animations) {
+                let updatedAnimations = animations.filter(anim => anim !== currentSelected);
+                (this.props.entity as IAnimatable).animations = updatedAnimations as Nullable<Animation[]>;
+            }
+        }
+    }
+
+    setListItem(animation: Animation, i: number) {
+        let element;
+
+        switch (animation.dataType) {
+            case Animation.ANIMATIONTYPE_FLOAT:
+                element = <li className={this.props.selected && this.props.selected.name === animation.name ? 'active' : ''} key={i} onClick={() => this.props.selectAnimation(animation)}>
+                    <p>{animation.name}&nbsp;
+                    <span>{animation.targetProperty}</span></p>
+                    {!(this.props.entity instanceof TargetedAnimation) ? this.props.selected && this.props.selected.name === animation.name ? <IconButtonLineComponent tooltip="Remove" icon="delete" onClick={() => this.deleteAnimation()} /> : null : null}
+                </li>
+                break;
+            case Animation.ANIMATIONTYPE_VECTOR2:
+                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
+                    <ul>
+                        <li key={`${i}_x`}>Property <strong>X</strong></li>
+                        <li key={`${i}_y`}>Property <strong>Y</strong></li>
+                    </ul>
+                </li>
+                break;
+            case Animation.ANIMATIONTYPE_VECTOR3:
+                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
+                    <ul>
+                        <li key={`${i}_x`}>Property <strong>X</strong></li>
+                        <li key={`${i}_y`}>Property <strong>Y</strong></li>
+                        <li key={`${i}_z`}>Property <strong>Z</strong></li>
+                    </ul>
+                </li>
+                break;
+            case Animation.ANIMATIONTYPE_QUATERNION:
+                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
+                    <ul>
+                        <li key={`${i}_x`}>Property <strong>X</strong></li>
+                        <li key={`${i}_y`}>Property <strong>Y</strong></li>
+                        <li key={`${i}_z`}>Property <strong>Z</strong></li>
+                        <li key={`${i}_w`}>Property <strong>W</strong></li>
+                    </ul>
+                </li>
+                break;
+            case Animation.ANIMATIONTYPE_COLOR3:
+                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
+                    <ul>
+                        <li key={`${i}_r`}>Property <strong>R</strong></li>
+                        <li key={`${i}_g`}>Property <strong>G</strong></li>
+                        <li key={`${i}_b`}>Property <strong>B</strong></li>
+                    </ul>
+                </li>
+                break;
+            case Animation.ANIMATIONTYPE_COLOR4:
+                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
+                    <ul>
+                        <li key={`${i}_r`}>Property <strong>R</strong></li>
+                        <li key={`${i}_g`}>Property <strong>G</strong></li>
+                        <li key={`${i}_b`}>Property <strong>B</strong></li>
+                        <li key={`${i}_a`}>Property <strong>A</strong></li>
+                    </ul>
+                </li>
+                break;
+            case Animation.ANIMATIONTYPE_SIZE:
+                element = <li className="property" key={i}><p>{animation.targetProperty}</p>
+                    <ul>
+                        <li key={`${i}_width`}>Property <strong>Width</strong></li>
+                        <li key={`${i}_height`}>Property <strong>Height</strong></li>
+                    </ul>
+                </li>
+                break;
+            default: console.log("not recognized");
+                element = null;
+                break;
+        }
+
+        return element;
+    }
+
+    handleTabs(tab: number){
+
+        let state = {isAnimationTabOpen: true, isLoadTabOpen: false, isSaveTabOpen: false, isEditTabOpen: false };
+
+        switch(tab){
+            case 0:
+                state = {isAnimationTabOpen: true, isLoadTabOpen: false, isSaveTabOpen: false, isEditTabOpen: false };
+                break;
+            case 1:
+                state = {isAnimationTabOpen: false, isLoadTabOpen: true, isSaveTabOpen: false, isEditTabOpen: false };
+                break;
+            case 2:
+                state = {isAnimationTabOpen: false, isLoadTabOpen: false, isSaveTabOpen: true, isEditTabOpen: false };
+                break;
+            case 3:
+                state = {isAnimationTabOpen: false, isLoadTabOpen: false, isSaveTabOpen: false, isEditTabOpen: true };
+                break;
+        }
+
+        this.setState(state);
+    }
+
+    handleChangeFps(fps: number){
+        this.setState({framesPerSecond: fps});
+    }
+
+    render() { 
+        return (
+            <div className="animation-list">
+            <div className="controls-header">
+            {this.props.isTargetedAnimation ? null : <IconButtonLineComponent active={this.state.isAnimationTabOpen} tooltip="Add Animation" icon="medium add-animation" onClick={() => this.handleTabs(0)}></IconButtonLineComponent>}
+            <IconButtonLineComponent active={this.state.isLoadTabOpen} tooltip="Load Animation" icon="medium load" onClick={() => this.handleTabs(1)}></IconButtonLineComponent>
+            <IconButtonLineComponent active={this.state.isSaveTabOpen} tooltip="Save Animation" icon="medium save" onClick={() => this.handleTabs(2)}></IconButtonLineComponent>
+            {this.state.animationsCount === 0 ? null : 
+            <IconButtonLineComponent active={this.state.isEditTabOpen} tooltip="Edit Animations" icon="medium animation-edit" onClick={() => this.handleTabs(3)}></IconButtonLineComponent> 
+            }
+            { this.state.isEditTabOpen ?
+            <div className="input-fps">
+                <NumericInputComponent label={""} precision={0} value={this.state.framesPerSecond} onChange={(framesPerSecond: number) => this.handleChangeFps(framesPerSecond)}/>
+                <p>fps</p>
+            </div> : null
+            }
+            { this.state.isEditTabOpen ?
+            <IconButtonLineComponent tooltip="Loop/Unloop" icon={`medium ${this.state.isLoopActive ? 'loop-active last' : 'loop-inactive last'}`} onClick={() => { this.setState({ isLoopActive: !this.state.isLoopActive })}}></IconButtonLineComponent> : null
+            }
+            </div>
+            { this.props.isTargetedAnimation ? null : 
+                <AddAnimation 
+                    isOpen={this.state.isAnimationTabOpen} 
+                    close={() => { this.setState({isAnimationTabOpen: false})}} 
+                    entity={(this.props.entity as IAnimatable)} 
+                    setNotificationMessage={(message: string) => { this.props.setNotificationMessage(message) }}
+                    changed={() => this.animationsChanged() }
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
+            }
+
+            { this.state.isLoadTabOpen ? <div>Load</div> : null }
+
+            { this.state.isSaveTabOpen ? <div>Save</div> : null }
+
+            { this.state.isEditTabOpen ?
+                <div className="object-tree">
+                    <ul>
+                        {
+                            this.props.isTargetedAnimation ? this.setListItem((this.props.entity as TargetedAnimation).animation, 0) :
+                                (this.props.entity as IAnimatable).animations && (this.props.entity as IAnimatable).animations?.map((animation, i) => {
+                                    return this.setListItem(animation, i);
+                                })}
+
+                    </ul>
+                </div>
+            : null }
+        </div>
+        );
+    }
+}