Browse Source

Merge pull request #8304 from toledoal/addanimation

AddAnimation Component
David Catuhe 5 năm trước cách đây
mục cha
commit
34958ee0a3

+ 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 entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
+- Added Curve editor to manage selected 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 - 1
inspector/src/components/actionTabs/lines/iconButtonLineComponent.tsx

@@ -4,6 +4,7 @@ export interface IIconButtonLineComponentProps {
     icon: string;
     onClick: () => void;
     tooltip: string;
+    active?: boolean;
 }
 
 export class IconButtonLineComponent extends React.Component<IIconButtonLineComponentProps> {
@@ -14,7 +15,7 @@ export class IconButtonLineComponent extends React.Component<IIconButtonLineComp
     render() {
 
         return (
-            <div title={this.props.tooltip} className={`icon ${this.props.icon}`} onClick={() => this.props.onClick()} />
+            <div style={{backgroundColor: this.props.active ? '#111111' : 'transparent'}} title={this.props.tooltip} className={`icon ${this.props.icon}`} onClick={() => this.props.onClick()} />
         );
     }
 }

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

@@ -0,0 +1,282 @@
+
+import * as React from "react";
+import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
+import { Animation } from 'babylonjs/Animations/animation';
+import { Vector2, Vector3, Quaternion } from 'babylonjs/Maths/math.vector';
+import { Size } from 'babylonjs/Maths/math.size';
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
+import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
+
+interface IAddAnimationProps {
+   isOpen: boolean;
+   close: () => void;
+   entity: IAnimatable;
+   onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+   setNotificationMessage: (message: string) => void;
+}
+
+export class AddAnimation extends React.Component<IAddAnimationProps, {animationName: string, animationTargetProperty: string, animationType:string, loopMode: number, animationTargetPath:string}>{ 
+    constructor(props: IAddAnimationProps) {
+        super(props);
+        this.state = { animationName: "", animationTargetPath: "", animationType: "Float", loopMode: Animation.ANIMATIONLOOPMODE_CYCLE, animationTargetProperty: ""}
+    }
+
+    getAnimationTypeofChange(selected: string) {
+        let dataType = 0;
+        switch (selected) {
+            case "Float":
+                dataType = Animation.ANIMATIONTYPE_FLOAT;
+                break;
+            case "Quaternion":
+                dataType = Animation.ANIMATIONTYPE_QUATERNION;
+                break;
+            case "Vector3":
+                dataType = Animation.ANIMATIONTYPE_VECTOR3;
+                break;
+            case "Vector2":
+                dataType = Animation.ANIMATIONTYPE_VECTOR2;
+                break;
+            case "Size":
+                dataType = Animation.ANIMATIONTYPE_SIZE;
+                break;
+            case "Color3":
+                dataType = Animation.ANIMATIONTYPE_COLOR3;
+                break;
+            case "Color4":
+                dataType = Animation.ANIMATIONTYPE_COLOR4;
+                break;
+        }
+
+        return dataType;
+
+    }
+
+    addAnimation() {
+        if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
+
+            let matchTypeTargetProperty = this.state.animationTargetProperty.split('.');
+            let animationDataType = this.getAnimationTypeofChange(this.state.animationType);
+            let matched = false;
+
+            if (matchTypeTargetProperty.length === 1) {
+                let match = (this.props.entity as any)[matchTypeTargetProperty[0]];
+
+                if (match) {
+                    switch (match.constructor.name) {
+                        case "Vector2":
+                            animationDataType === Animation.ANIMATIONTYPE_VECTOR2 ? matched = true : matched = false;
+                            break;
+                        case "Vector3":
+                            animationDataType === Animation.ANIMATIONTYPE_VECTOR3 ? matched = true : matched = false;
+                            break;
+                        case "Quaternion":
+                            animationDataType === Animation.ANIMATIONTYPE_QUATERNION ? matched = true : matched = false;
+                            break;
+                        case "Color3":
+                            animationDataType === Animation.ANIMATIONTYPE_COLOR3 ? matched = true : matched = false;
+                            break;
+                        case "Color4":
+                            animationDataType === Animation.ANIMATIONTYPE_COLOR4 ? matched = true : matched = false;
+                            break;
+                        case "Size":
+                            animationDataType === Animation.ANIMATIONTYPE_SIZE ? matched = true : matched = false;
+                            break;
+                        default: console.log("not recognized");
+                            break;
+                    }
+                } else {
+                   this.props.setNotificationMessage(`The selected entity doesn't have a ${matchTypeTargetProperty[0]} property`)
+                }
+            } else if (matchTypeTargetProperty.length > 1) {
+                let match = (this.props.entity as any)[matchTypeTargetProperty[0]][matchTypeTargetProperty[1]];
+                if (typeof match === "number") {
+                    animationDataType === Animation.ANIMATIONTYPE_FLOAT ? matched = true : matched = false;
+                }
+            }
+
+            if (matched) {
+
+                let startValue;
+                let endValue;
+                let outTangent;
+                let inTangent;
+                // Default start and end values for new animations
+                switch (animationDataType) {
+                    case Animation.ANIMATIONTYPE_FLOAT:
+                        startValue = 1;
+                        endValue = 1;
+                        outTangent = 0;
+                        inTangent = 0;
+                        break;
+                    case Animation.ANIMATIONTYPE_VECTOR2:
+                        startValue = new Vector2(1, 1);
+                        endValue = new Vector2(1, 1);
+                        outTangent = Vector2.Zero();
+                        inTangent = Vector2.Zero();
+                        break;
+                    case Animation.ANIMATIONTYPE_VECTOR3:
+                        startValue = new Vector3(1, 1, 1);
+                        endValue = new Vector3(1, 1, 1);
+                        outTangent = Vector3.Zero();
+                        inTangent = Vector3.Zero();
+                        break;
+                    case Animation.ANIMATIONTYPE_QUATERNION:
+                        startValue = new Quaternion(1, 1, 1, 1);
+                        endValue = new Quaternion(1, 1, 1, 1);
+                        outTangent = Quaternion.Zero();
+                        inTangent = Quaternion.Zero();
+                        break;
+                    case Animation.ANIMATIONTYPE_COLOR3:
+                        startValue = new Color3(1, 1, 1);
+                        endValue = new Color3(1, 1, 1);
+                        outTangent = new Color3(0, 0, 0);
+                        inTangent = new Color3(0, 0, 0);
+                        break;
+                    case Animation.ANIMATIONTYPE_COLOR4:
+                        startValue = new Color4(1, 1, 1, 1);
+                        endValue = new Color4(1, 1, 1, 1);
+                        outTangent = new Color4(0, 0, 0, 0);
+                        inTangent = new Color4(0, 0, 0, 0);
+                        break;
+                    case Animation.ANIMATIONTYPE_SIZE:
+                        startValue = new Size(1, 1);
+                        endValue = new Size(1, 1);
+                        outTangent = Size.Zero();
+                        inTangent = Size.Zero();
+                        break;
+                    default: console.log("not recognized");
+                        break;
+                }
+
+                let alreadyAnimatedProperty = (this.props.entity as IAnimatable).animations?.find(anim =>
+                    anim.targetProperty === this.state.animationTargetProperty
+                    , this);
+
+                let alreadyAnimationName = (this.props.entity as IAnimatable).animations?.find(anim =>
+                    anim.name === this.state.animationName
+                    , this);
+
+                if (alreadyAnimatedProperty) {
+                    this.props.setNotificationMessage(`The property "${this.state.animationTargetProperty}" already has an animation`);
+                } else if (alreadyAnimationName) {
+                    this.props.setNotificationMessage(`There is already an animation with the name: "${this.state.animationName}"`);
+                } else {
+
+                    let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, 30, animationDataType);
+
+                    // Start with two keyframes
+                    var keys = [];
+                    keys.push({
+                        frame: 0,
+                        value: startValue,
+                        outTangent: outTangent
+                    });
+
+                    keys.push({
+                        inTangent: inTangent,
+                        frame: 100,
+                        value: endValue
+                    });
+
+                    animation.setKeys(keys);
+
+                    if (this.props.entity.animations){
+                        const store = this.props.entity.animations;
+                        const updatedCollection = [...this.props.entity.animations, animation]
+                        this.raiseOnPropertyChanged(updatedCollection, store);
+                        this.props.entity.animations = updatedCollection;
+                        this.props.close();
+                    }   
+                }
+            } else {
+                this.props.setNotificationMessage(`The property "${this.state.animationTargetProperty}" is not a "${this.state.animationType}" type`);
+            }
+        } else {
+            this.props.setNotificationMessage(`You need to provide a name and target property.`);
+        }
+    }
+
+    raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.entity,
+            property: 'animations',
+            value: newValue,
+            initialValue: previousValue
+        });
+    }
+
+    handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
+        event.preventDefault();
+        this.setState({ animationName: event.target.value.trim() });
+    }
+    
+    handlePathChange(event: React.ChangeEvent<HTMLInputElement>) {
+        event.preventDefault();
+        this.setState({ animationTargetPath: event.target.value.trim() });
+    }
+
+    handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
+        event.preventDefault();
+        this.setState({ animationType: event.target.value });
+    }
+
+    handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>) {
+        event.preventDefault();
+        this.setState({ animationTargetProperty: event.target.value });
+    }
+
+    handleLoopModeChange(event: React.ChangeEvent<HTMLSelectElement>) {
+        event.preventDefault();
+        this.setState({ loopMode: parseInt(event.target.value) });
+    }
+     
+    render() { 
+       return (
+        <div className="new-animation" style={{ display: this.props.isOpen ? "block" : "none" }}>
+            <div className="sub-content">
+            <div className="label-input">
+                <label>Target Path</label>
+                <input type="text" value={this.state.animationTargetPath} onChange={(e) => this.handlePathChange(e)}></input>
+            </div>
+            <div className="label-input">
+                <label>Display Name</label>
+                <input type="text" value={this.state.animationName} onChange={(e) => this.handleNameChange(e)}></input>
+            </div>
+            <div className="label-input">
+                <label>Property</label>
+                <input type="text" value={this.state.animationTargetProperty} onChange={(e) => this.handlePropertyChange(e)}></input>
+            </div>
+            <div className="label-input">
+                <label>Type</label>
+                <select onChange={(e) => this.handleTypeChange(e)} value={this.state.animationType}>
+                    <option value="Float">Float</option>
+                    <option value="Vector3">Vector3</option>
+                    <option value="Vector2">Vector2</option>
+                    <option value="Quaternion">Quaternion</option>
+                    <option value="Color3">Color3</option>
+                    <option value="Color4">Color4</option>
+                    <option value="Size">Size</option>
+                </select>
+            </div>
+            <div className="label-input">
+                <label>Loop Mode</label>
+                <select onChange={(e) => this.handleLoopModeChange(e)} value={this.state.loopMode}>
+                    <option value={Animation.ANIMATIONLOOPMODE_CYCLE}>Cycle</option>
+                    <option value={Animation.ANIMATIONLOOPMODE_RELATIVE}>Relative</option>
+                    <option value={Animation.ANIMATIONLOOPMODE_CONSTANT}>Constant</option>
+                </select>
+            </div>
+           <div className="confirm-buttons">
+            <ButtonLineComponent label={"Create"} onClick={() => this.addAnimation()} />
+            </div>
+            </div>
+        </div>
+        )
+    }
+} 

+ 20 - 224
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -1,8 +1,6 @@
 import * as React from "react";
 import { Animation } from 'babylonjs/Animations/animation';
-import { Vector2, Vector3, Quaternion } from 'babylonjs/Maths/math.vector';
-import { Size } from 'babylonjs/Maths/math.size';
-import { Color3, Color4 } from 'babylonjs/Maths/math.color';
+import { Vector2 } from 'babylonjs/Maths/math.vector';
 import { EasingFunction } from 'babylonjs/Animations/easing';
 import { IAnimationKey } from 'babylonjs/Animations/animationKey';
 import { IKeyframeSvgPoint } from './keyframeSvgPoint';
@@ -15,7 +13,9 @@ 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';
 
 require("./curveEditor.scss");
 
@@ -32,9 +32,6 @@ interface ICanvasAxis {
 }
 
 export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
-    animationName: string,
-    animationType: string,
-    animationTargetProperty: string,
     isOpen: boolean,
     selected: Animation | null,
     currentPathData: string | undefined,
@@ -53,7 +50,8 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
     currentPoint: SVGPoint | undefined,
     lastFrame: number,
     playheadPos: number,
-    isPlaying: boolean
+    isPlaying: boolean,
+    isAnimationDialogOpen: boolean
 }> {
 
     // Height scale *Review this functionaliy
@@ -105,9 +103,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             isOpen: true,
             currentPathData: initialPathData,
             svgKeyframes: this._svgKeyframes,
-            animationTargetProperty: 'position.x',
-            animationName: "",
-            animationType: "Float",
             currentFrame: 0,
             currentValue: 1,
             isFlatTangentMode: false,
@@ -122,7 +117,8 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             currentPoint: undefined,
             scale: 1,
             playheadPos: 0,
-            isPlaying: this.isAnimationPlaying()
+            isPlaying: this.isAnimationPlaying(),
+            isAnimationDialogOpen: false
         }
     }
 
@@ -197,27 +193,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         }
     }
 
-
-
-    /**
-    * Add New Animation
-    * This section handles events from AnimationCreation.
-    */
-    handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
-        event.preventDefault();
-        this.setState({ animationName: event.target.value.trim() });
-    }
-
-    handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
-        event.preventDefault();
-        this.setState({ animationType: event.target.value });
-    }
-
-    handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>) {
-        event.preventDefault();
-        this.setState({ animationTargetProperty: event.target.value });
-    }
-
     setListItem(animation: Animation, i: number) {
         let element;
 
@@ -291,36 +266,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         return element;
     }
 
-    getAnimationTypeofChange(selected: string) {
-        let dataType = 0;
-        switch (selected) {
-            case "Float":
-                dataType = Animation.ANIMATIONTYPE_FLOAT;
-                break;
-            case "Quaternion":
-                dataType = Animation.ANIMATIONTYPE_QUATERNION;
-                break;
-            case "Vector3":
-                dataType = Animation.ANIMATIONTYPE_VECTOR3;
-                break;
-            case "Vector2":
-                dataType = Animation.ANIMATIONTYPE_VECTOR2;
-                break;
-            case "Size":
-                dataType = Animation.ANIMATIONTYPE_SIZE;
-                break;
-            case "Color3":
-                dataType = Animation.ANIMATIONTYPE_COLOR3;
-                break;
-            case "Color4":
-                dataType = Animation.ANIMATIONTYPE_COLOR4;
-                break;
-        }
-
-        return dataType;
-
-    }
-
     deleteAnimation() {
         let currentSelected = this.state.selected;
         if (this.props.entity instanceof TargetedAnimation) {
@@ -334,146 +279,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         }
     }
 
-    addAnimation() {
-        if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
-
-            let matchTypeTargetProperty = this.state.animationTargetProperty.split('.');
-            let animationDataType = this.getAnimationTypeofChange(this.state.animationType);
-            let matched = false;
-
-            if (matchTypeTargetProperty.length === 1) {
-                let match = (this.props.entity as any)[matchTypeTargetProperty[0]];
-
-                if (match) {
-                    switch (match.constructor.name) {
-                        case "Vector2":
-                            animationDataType === Animation.ANIMATIONTYPE_VECTOR2 ? matched = true : matched = false;
-                            break;
-                        case "Vector3":
-                            animationDataType === Animation.ANIMATIONTYPE_VECTOR3 ? matched = true : matched = false;
-                            break;
-                        case "Quaternion":
-                            animationDataType === Animation.ANIMATIONTYPE_QUATERNION ? matched = true : matched = false;
-                            break;
-                        case "Color3":
-                            animationDataType === Animation.ANIMATIONTYPE_COLOR3 ? matched = true : matched = false;
-                            break;
-                        case "Color4":
-                            animationDataType === Animation.ANIMATIONTYPE_COLOR4 ? matched = true : matched = false;
-                            break;
-                        case "Size":
-                            animationDataType === Animation.ANIMATIONTYPE_SIZE ? matched = true : matched = false;
-                            break;
-                        default: console.log("not recognized");
-                            break;
-                    }
-                } else {
-                    this.setState({ notification: `The selected entity doesn't have a ${matchTypeTargetProperty[0]} property` });
-                }
-            } else if (matchTypeTargetProperty.length > 1) {
-                let match = (this.props.entity as any)[matchTypeTargetProperty[0]][matchTypeTargetProperty[1]];
-                if (typeof match === "number") {
-                    animationDataType === Animation.ANIMATIONTYPE_FLOAT ? matched = true : matched = false;
-                }
-            }
-
-            if (matched) {
-
-                let startValue;
-                let endValue;
-                let outTangent;
-                let inTangent;
-                // Default start and end values for new animations
-                switch (animationDataType) {
-                    case Animation.ANIMATIONTYPE_FLOAT:
-                        startValue = 1;
-                        endValue = 1;
-                        outTangent = 0;
-                        inTangent = 0;
-                        break;
-                    case Animation.ANIMATIONTYPE_VECTOR2:
-                        startValue = new Vector2(1, 1);
-                        endValue = new Vector2(1, 1);
-                        outTangent = Vector2.Zero();
-                        inTangent = Vector2.Zero();
-                        break;
-                    case Animation.ANIMATIONTYPE_VECTOR3:
-                        startValue = new Vector3(1, 1, 1);
-                        endValue = new Vector3(1, 1, 1);
-                        outTangent = Vector3.Zero();
-                        inTangent = Vector3.Zero();
-                        break;
-                    case Animation.ANIMATIONTYPE_QUATERNION:
-                        startValue = new Quaternion(1, 1, 1, 1);
-                        endValue = new Quaternion(1, 1, 1, 1);
-                        outTangent = Quaternion.Zero();
-                        inTangent = Quaternion.Zero();
-                        break;
-                    case Animation.ANIMATIONTYPE_COLOR3:
-                        startValue = new Color3(1, 1, 1);
-                        endValue = new Color3(1, 1, 1);
-                        outTangent = new Color3(0, 0, 0);
-                        inTangent = new Color3(0, 0, 0);
-                        break;
-                    case Animation.ANIMATIONTYPE_COLOR4:
-                        startValue = new Color4(1, 1, 1, 1);
-                        endValue = new Color4(1, 1, 1, 1);
-                        outTangent = new Color4(0, 0, 0, 0);
-                        inTangent = new Color4(0, 0, 0, 0);
-                        break;
-                    case Animation.ANIMATIONTYPE_SIZE:
-                        startValue = new Size(1, 1);
-                        endValue = new Size(1, 1);
-                        outTangent = Size.Zero();
-                        inTangent = Size.Zero();
-                        break;
-                    default: console.log("not recognized");
-                        break;
-                }
-
-                let alreadyAnimatedProperty = (this.props.entity as IAnimatable).animations?.find(anim =>
-                    anim.targetProperty === this.state.animationTargetProperty
-                    , this);
-
-                let alreadyAnimationName = (this.props.entity as IAnimatable).animations?.find(anim =>
-                    anim.name === this.state.animationName
-                    , this);
-
-                if (alreadyAnimatedProperty) {
-                    this.setState({ notification: `The property "${this.state.animationTargetProperty}" already has an animation` });
-                } else if (alreadyAnimationName) {
-                    this.setState({ notification: `There is already an animation with the name: "${this.state.animationName}"` });
-                } else {
-
-                    let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, 30, animationDataType);
-
-                    // Start with two keyframes
-                    var keys = [];
-                    keys.push({
-                        frame: 0,
-                        value: startValue,
-                        outTangent: outTangent
-                    });
-
-                    keys.push({
-                        inTangent: inTangent,
-                        frame: 100,
-                        value: endValue
-                    });
-
-                    animation.setKeys(keys);
-                    (this.props.entity as IAnimatable).animations?.push(animation);
-                }
-
-            } else {
-                this.setState({ notification: `The property "${this.state.animationTargetProperty}" is not a "${this.state.animationType}" type` });
-            }
-
-        } else {
-            this.setState({ notification: "You need to provide a name and target property." });
-        }
-    }
-
     /**
     * Keyframe Manipulation
     * This section handles events from SvgDraggableArea.
@@ -1283,29 +1088,20 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 <div className="content">
                     <div className="row">
                         <div className="animation-list">
-                            <div style={{ display: this._isTargetedAnimation ? "none" : "block" }}>
-                                <div className="label-input">
-                                    <label>Animation Name</label>
-                                    <input type="text" value={this.state.animationName} onChange={(e) => this.handleNameChange(e)}></input>
-                                </div>
-                                <div className="label-input">
-                                    <label>Type</label>
-                                    <select onChange={(e) => this.handleTypeChange(e)} value={this.state.animationType}>
-                                        <option value="Float">Float</option>
-                                        <option value="Vector3">Vector3</option>
-                                        <option value="Vector2">Vector2</option>
-                                        <option value="Quaternion">Quaternion</option>
-                                        <option value="Color3">Color3</option>
-                                        <option value="Color4">Color4</option>
-                                        <option value="Size">Size</option>
-                                    </select>
-                                </div>
-                                <div className="label-input">
-                                    <label>Target Property</label>
-                                    <input type="text" value={this.state.animationTargetProperty} onChange={(e) => this.handlePropertyChange(e)}></input>
-                                </div>
-                                <ButtonLineComponent label={"Add Animation"} onClick={() => this.addAnimation()} />
+                            <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>

+ 1 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/assets/checkboxCheckedIcon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;}</style></defs><g id="UI"><rect class="cls-1" width="20" height="20"/><path class="cls-2" d="M15,5V15H5V5Zm-.63.63H5.61v8.78h8.78ZM8.75,13l-2.1-2.1.44-.44,1.66,1.65L12.91,7.9l.44.44Z"/></g></svg>

+ 1 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/assets/checkboxDefaultIcon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;}</style></defs><g id="UI"><rect class="cls-1" width="20" height="20"/><path class="cls-2" d="M15,5V15H5V5Zm-.63.63H5.61v8.78h8.78Z"/></g></svg>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/assets/editIcon.svg


+ 1 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/assets/loadIcon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;}</style></defs><g id="parameters"><rect class="cls-1" width="30" height="30"/><path class="cls-2" d="M21.78,19l-1.27-1.27V23h-1V17.71L18.24,19l-.71-.71L20,15.8l2.48,2.47ZM8.51,22h10v1h-11V7h8.71l4.29,4.29V15h-1V12h-4V8h-7Zm8-11H18.8L16.51,8.71Z"/></g></svg>

+ 1 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/assets/saveIcon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;}</style></defs><g id="parameters"><rect class="cls-1" width="30" height="30"/><path class="cls-2" d="M21,8a1,1,0,0,1,.39.08,1,1,0,0,1,.31.21,1.21,1.21,0,0,1,.22.32A.91.91,0,0,1,22,9V22H9.79L8,20.2V9a.85.85,0,0,1,.08-.39,1,1,0,0,1,.21-.31,1.07,1.07,0,0,1,.32-.22A.85.85,0,0,1,9,8ZM11,14h8V9H11Zm6,4H12v3h1V19h1v2h3Zm4-9H20v6H10V9H9V19.79L10.2,21H11V17h7v4h3Z"/></g></svg>

+ 136 - 9
inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss

@@ -6,6 +6,10 @@
     .icon {
         width: 40px;
         height: 40px;
+        &.medium {
+            width: 30px;
+            height: 30px;
+        }
         &.babylon-logo {
             background-image: url('./assets/babylonLogo.svg');
             background-repeat: no-repeat;
@@ -101,6 +105,14 @@
             color: white;
         }
 
+        &.animation-edit {
+            background-image: url('./assets/editIcon.svg');
+            background-repeat: no-repeat;
+            background-color: transparent;
+            background-size: contain;
+            color: white;
+        }
+
         &.animation-end {
             background-image: url('./assets/animationEndIcon.svg');
             background-repeat: no-repeat;
@@ -227,6 +239,39 @@
             color: white;
         }
 
+        &.save {
+            background-image: url('./assets/saveIcon.svg');
+            background-repeat: no-repeat;
+            background-color: transparent;
+            background-size: contain;
+            color: white;
+        }
+
+        &.load {
+            background-image: url('./assets/loadIcon.svg');
+            background-repeat: no-repeat;
+            background-color: transparent;
+            background-size: contain;
+            color: white;
+        }
+
+        &.checked {
+            background-image: url('./assets/checkboxCheckedIcon.svg');
+            background-repeat: no-repeat;
+            background-color: transparent;
+            background-size: contain;
+            color: white;
+        }
+
+        &.unchecked {
+            background-image: url('./assets/checkboxDefaultIcon.svg');
+            background-repeat: no-repeat;
+            background-color: transparent;
+            background-size: contain;
+            color: white;
+        }
+
+
         &.new-key {
             background-image: url('./assets/newKeyIcon.svg');
             background-repeat: no-repeat;
@@ -263,7 +308,7 @@
         border-radius: 5px;
         color: white;
         right: 2em;
-        z-index: 10;
+        z-index: 1000;
 
         button{
             position: absolute;
@@ -362,6 +407,7 @@
             flex-direction: row;
             width: 100vw;
             height: 84vh;
+            background-color: #333333;
 
             .timeline{
                 width: 100vw;
@@ -415,9 +461,62 @@
         }
 
         .animation-list{
-            padding: 1.5rem;
-            background: #333333;
+            margin: 10px;
+            margin-top: 0;
+            margin-bottom: 0;
+            background: #111111;
             color: white;
+            width: 210px;
+
+            .controls-header {
+                display: flex;
+                background-color: #252525;
+            }
+
+            .new-animation{
+                display: block;
+                position: absolute;
+                background-color: #111111;
+                height: 367px;
+                z-index: 10;
+
+                .sub-header {
+                    display: flex;
+                    align-items: start;
+                    width: 250px;
+                    justify-content: space-between;
+                    .title {
+                    margin: 0px;
+                    font-size: 15pt;
+                    }
+                }
+
+                .sub-content{
+                    background-color: #111111;
+                    padding: 10px;
+                    display: inline-grid;
+                    grid-template-columns: 63px 127px;
+                    grid-template-rows: repeat(6, 30px);
+                    font-size: 12px;
+
+                    .confirm-buttons {
+                        grid-column-start: 2;
+
+                        .buttonLine {
+                            button {
+                                width: 60px;
+                                height: 20px;
+                                background-color: rgb(68, 68, 68);
+                                color: white;
+                                font-size: 12px;
+                                line-height: 11px;
+                                margin: 5px;
+                            }
+                        }
+                    }
+                }
+            }
+
             ul {
                 list-style:none;
                 padding-left: 0px;
@@ -429,8 +528,6 @@
                 }
                 li {
                     p {
-                        font-weight: bolder;
-                        font-variant: all-small-caps;
                         display: inline;
                     }
                     cursor: pointer;
@@ -466,10 +563,40 @@
             }
 
             .label-input{
-                display: grid;
-                height: 54px;
-                place-items: center stretch;
-                color:white;
+                display: contents;
+
+                label {
+                    text-align: right;
+                }
+
+                input {
+                    margin-left: 5px;
+                    height: 20px;
+                    background-color: #444444;
+                    border: none;
+                    color: white;
+                    padding-left: 3px;
+                    font-size: 10px;
+                    &:focus{
+                        border-radius: 0px;
+                        outline-style: auto;
+                        outline-color: lightgrey;
+                    }
+                }
+
+                select {
+                    height: 20px;
+                    margin-left: 5px;
+                    font-size: 10px;
+                    background-color: #444444;
+                    border: none;
+                    color: white;
+                    &:focus{
+                        border-radius: 0px;
+                        outline-style: auto;
+                        outline-color: lightgrey;
+                    }
+                }
             }
         }