Alejandro Toledo 4 years ago
parent
commit
927be7a727

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

@@ -10,7 +10,7 @@
 - Added SubSurfaceScattering on PBR materials ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added edition of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
 - Added edition of procedural texture in the node material editor ([Deltakosh](https://github.com/deltakosh))
-- 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))
 

+ 39 - 10
inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx

@@ -53,7 +53,9 @@ export class AddAnimation extends React.Component<
             animationName: editingAnimation ? editingAnimation.name : "",
             animationTargetPath: "",
             animationType: editingAnimation ? editingAnimation.dataType : Animation.ANIMATIONTYPE_FLOAT,
-            loopMode: editingAnimation ? editingAnimation.loopMode ?? Animation.ANIMATIONLOOPMODE_CYCLE : Animation.ANIMATIONLOOPMODE_CYCLE,
+            loopMode: editingAnimation
+                ? editingAnimation.loopMode ?? Animation.ANIMATIONLOOPMODE_CYCLE
+                : Animation.ANIMATIONLOOPMODE_CYCLE,
             animationTargetProperty: editingAnimation ? editingAnimation.targetProperty : "",
             isUpdating: editingAnimation ? true : false,
         };
@@ -164,7 +166,9 @@ export class AddAnimation extends React.Component<
                             break;
                     }
                 } else {
-                    this.props.setNotificationMessage(`The selected entity doesn't have a ${matchTypeTargetProperty[0]} property`);
+                    this.props.setNotificationMessage(
+                        `The selected entity doesn't have a ${matchTypeTargetProperty[0]} property`
+                    );
                 }
             } else if (matchTypeTargetProperty.length > 1) {
                 let matchProp = (this.props.entity as any)[matchTypeTargetProperty[0]];
@@ -177,16 +181,31 @@ export class AddAnimation extends React.Component<
             }
 
             if (matched) {
-                let alreadyAnimatedProperty = (this.props.entity as IAnimatable).animations?.find((anim) => anim.targetProperty === this.state.animationTargetProperty, this);
+                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);
+                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`);
+                    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}"`);
+                    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, this.props.fps, animationDataType);
+                    let animation = new Animation(
+                        this.state.animationName,
+                        this.state.animationTargetProperty,
+                        this.props.fps,
+                        animationDataType
+                    );
 
                     // Start with empty keyframes
                     var keys: IAnimationKey[] = [];
@@ -210,7 +229,11 @@ export class AddAnimation extends React.Component<
                     }
                 }
             } else {
-                this.props.setNotificationMessage(`The property "${this.state.animationTargetProperty}" is not a "${this.getTypeAsString(this.state.animationType)}" type`);
+                this.props.setNotificationMessage(
+                    `The property "${this.state.animationTargetProperty}" is not a "${this.getTypeAsString(
+                        this.state.animationType
+                    )}" type`
+                );
             }
         } else {
             this.props.setNotificationMessage(`You need to provide a name and target property.`);
@@ -281,7 +304,11 @@ export class AddAnimation extends React.Component<
                     {this.state.isUpdating ? null : (
                         <div className="label-input">
                             <label>Property</label>
-                            <input type="text" value={this.state.animationTargetProperty} onChange={this.handlePropertyChange}></input>
+                            <input
+                                type="text"
+                                value={this.state.animationTargetProperty}
+                                onChange={this.handlePropertyChange}
+                            ></input>
                         </div>
                     )}
                     {this.state.isUpdating ? null : (
@@ -310,7 +337,9 @@ export class AddAnimation extends React.Component<
                     </div>
                     <div className="confirm-buttons">
                         <ButtonLineComponent label={confirmLabel} onClick={confirmHandleOnClick} />
-                        {this.props.entity.animations?.length !== 0 ? <ButtonLineComponent label={"Cancel"} onClick={this.props.close} /> : null}
+                        {this.props.entity.animations?.length !== 0 ? (
+                            <ButtonLineComponent label={"Cancel"} onClick={this.props.close} />
+                        ) : null}
                     </div>
                 </div>
             </div>

+ 36 - 4
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx

@@ -62,12 +62,44 @@ export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps, { visi
         const visibleCircle = this.props.selected ? "#ffc017" : "black";
         return (
             <>
-                <line className={`control-point ${this.props.active ? "active" : ""}`} x1={this.props.anchor.x} y1={this.props.anchor.y} x2={this.state.visiblePoint.x} y2={this.state.visiblePoint.y} strokeWidth="0.8%" />
-                <svg x={this.state.visiblePoint.x} y={this.state.visiblePoint.y} style={{ overflow: "visible" }} onClick={this.select}>
-                    <circle type={this.props.type} data-id={this.props.index} className={visibleCircleClass} cx="0" cy="0" r="0.75%" stroke="aqua" strokeWidth={strokeVisibleCircle} fill={visibleCircle} />
+                <line
+                    className={`control-point ${this.props.active ? "active" : ""}`}
+                    x1={this.props.anchor.x}
+                    y1={this.props.anchor.y}
+                    x2={this.state.visiblePoint.x}
+                    y2={this.state.visiblePoint.y}
+                    strokeWidth="0.8%"
+                />
+                <svg
+                    x={this.state.visiblePoint.x}
+                    y={this.state.visiblePoint.y}
+                    style={{ overflow: "visible" }}
+                    onClick={this.select}
+                >
+                    <circle
+                        type={this.props.type}
+                        data-id={this.props.index}
+                        className={visibleCircleClass}
+                        cx="0"
+                        cy="0"
+                        r="0.75%"
+                        stroke="aqua"
+                        strokeWidth={strokeVisibleCircle}
+                        fill={visibleCircle}
+                    />
                 </svg>
                 <svg x={this.props.control.x} y={this.props.control.y} style={{ overflow: "visible", display: "none" }}>
-                    <circle type={this.props.type} data-id={this.props.index} className={nonVisibleCircleClass} cx="0" cy="0" r="0.7%" stroke="white" strokeWidth={0} fill={"white"} />
+                    <circle
+                        type={this.props.type}
+                        data-id={this.props.index}
+                        className={nonVisibleCircleClass}
+                        cx="0"
+                        cy="0"
+                        r="0.7%"
+                        stroke="white"
+                        strokeWidth={0}
+                        fill={"white"}
+                    />
                 </svg>
             </>
         );

+ 228 - 48
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -166,7 +166,10 @@ interface IAnimationCurveEditorComponentState {
  * @property {GlobalState} globalState the playground globalstate
  * Check the IAnimationCurveEditorComponentState for state functionality
  */
-export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, IAnimationCurveEditorComponentState> {
+export class AnimationCurveEditorComponent extends React.Component<
+    IAnimationCurveEditorComponentProps,
+    IAnimationCurveEditorComponentState
+> {
     // Global properties
     readonly _entityName: string;
     private _snippetUrl = "https://snippet.babylonjs.com";
@@ -215,10 +218,16 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         } else {
             this._isTargetedAnimation = false;
 
-            let hasAnimations = this.props.entity.animations !== undefined || this.props.entity.animations !== null ? this.props.entity.animations : false;
+            let hasAnimations =
+                this.props.entity.animations !== undefined || this.props.entity.animations !== null
+                    ? this.props.entity.animations
+                    : false;
             initialSelection = hasAnimations !== false ? hasAnimations && hasAnimations[0] : null;
 
-            initialLerpMode = initialSelection !== undefined ? this.analyzeAnimationForLerp(this.props.entity.animations && initialSelection) : false;
+            initialLerpMode =
+                initialSelection !== undefined
+                    ? this.analyzeAnimationForLerp(this.props.entity.animations && initialSelection)
+                    : false;
             initialPathData = initialSelection && this.getPathData(initialSelection);
             initialPathData = initialPathData === null || initialPathData === undefined ? undefined : initialPathData;
         }
@@ -237,7 +246,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             isTangentMode: false,
             isBrokenMode: false,
             lerpMode: initialLerpMode,
-            playheadOffset: this._graphCanvas.current ? this._graphCanvas.current.children[0].clientWidth / (_canvasLength * 10) : 0,
+            playheadOffset: this._graphCanvas.current
+                ? this._graphCanvas.current.children[0].clientWidth / (_canvasLength * 10)
+                : 0,
             // Set default frame for visible canvas
             frameAxisLength: this.setFrameAxis(_canvasLength),
             // Set default values for the visible canvas
@@ -275,7 +286,11 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
     componentDidMount() {
         this.state.selected && this.selectAnimation(this.state.selected);
         // Control the window resize event
-        if (this._editor.current && this._editor.current.ownerDocument && this._editor.current.ownerDocument.defaultView) {
+        if (
+            this._editor.current &&
+            this._editor.current.ownerDocument &&
+            this._editor.current.ownerDocument.defaultView
+        ) {
             this._editorWindow = this._editor.current.ownerDocument.defaultView;
             this._editorWindow.addEventListener("resize", this.onWindowResizeWidth.bind(this));
         }
@@ -594,7 +609,12 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * @param newValue New value of animated property
      * @param coordinate The selected property coordinate to animate (i.e. x, y, z)
      */
-    updateValuePerCoordinate(dataType: number, value: number | Vector2 | Vector3 | Color3 | Color4 | Size | Quaternion, newValue: number, coordinate?: number) {
+    updateValuePerCoordinate(
+        dataType: number,
+        value: number | Vector2 | Vector3 | Color3 | Color4 | Size | Quaternion,
+        newValue: number,
+        coordinate?: number
+    ) {
         if (dataType === Animation.ANIMATIONTYPE_FLOAT) {
             value = newValue;
         }
@@ -757,9 +777,15 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             }
         }
 
-        let updatedValue = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * this._scaleFactor;
+        let updatedValue =
+            ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * this._scaleFactor;
 
-        const updatedValueInCoordinate = this.updateValuePerCoordinate(animation.dataType, keys[index].value, updatedValue, coordinate);
+        const updatedValueInCoordinate = this.updateValuePerCoordinate(
+            animation.dataType,
+            keys[index].value,
+            updatedValue,
+            coordinate
+        );
 
         keys[index].value = updatedValueInCoordinate;
 
@@ -786,7 +812,12 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * @param dataType type of animation
      * @param coordinate coordinate to change its value (x, y, or z)
      */
-    updateLeftControlPoint(updatedSvgKeyFrame: IKeyframeSvgPoint, key: IAnimationKey, dataType: number, coordinate: number) {
+    updateLeftControlPoint(
+        updatedSvgKeyFrame: IKeyframeSvgPoint,
+        key: IAnimationKey,
+        dataType: number,
+        coordinate: number
+    ) {
         if (updatedSvgKeyFrame.isLeftActive) {
             if (updatedSvgKeyFrame.leftControlPoint !== null) {
                 // Rotate Control Points
@@ -795,11 +826,16 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
 
                 let distanceAmplitudeOfX = updatedSvgKeyFrame.leftControlPoint.x - distanceWithPreviousKeyframe;
 
-                let slope = (updatedSvgKeyFrame.leftControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) / (updatedSvgKeyFrame.leftControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
+                let slope =
+                    (updatedSvgKeyFrame.leftControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) /
+                    (updatedSvgKeyFrame.leftControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
 
-                let newValueOfY = (distanceAmplitudeOfX - updatedSvgKeyFrame.leftControlPoint.x) * slope + updatedSvgKeyFrame.keyframePoint.y;
+                let newValueOfY =
+                    (distanceAmplitudeOfX - updatedSvgKeyFrame.leftControlPoint.x) * slope +
+                    updatedSvgKeyFrame.keyframePoint.y;
 
-                let updatedValue = ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
+                let updatedValue =
+                    ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
 
                 if (updatedValue > -100 && updatedValue < 100) {
                     key.inTangent = slope;
@@ -822,7 +858,12 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * @param dataType type of animation
      * @param coordinate coordinate to change its value (x, y, or z)
      */
-    updateRightControlPoint(updatedSvgKeyFrame: IKeyframeSvgPoint, key: IAnimationKey, dataType: number, coordinate: number) {
+    updateRightControlPoint(
+        updatedSvgKeyFrame: IKeyframeSvgPoint,
+        key: IAnimationKey,
+        dataType: number,
+        coordinate: number
+    ) {
         if (updatedSvgKeyFrame.isRightActive) {
             if (updatedSvgKeyFrame.rightControlPoint !== null) {
                 // Get the next svgKeyframe and measure distance between these two points
@@ -830,11 +871,16 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
 
                 let distanceAmplitudeOfX = updatedSvgKeyFrame.rightControlPoint.x + distanceWithNextKeyframe;
 
-                let slope = (updatedSvgKeyFrame.rightControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) / (updatedSvgKeyFrame.rightControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
+                let slope =
+                    (updatedSvgKeyFrame.rightControlPoint.y - updatedSvgKeyFrame.keyframePoint.y) /
+                    (updatedSvgKeyFrame.rightControlPoint.x - updatedSvgKeyFrame.keyframePoint.x);
 
-                let newValueOfY = (distanceAmplitudeOfX - updatedSvgKeyFrame.rightControlPoint.x) * slope + updatedSvgKeyFrame.keyframePoint.y;
+                let newValueOfY =
+                    (distanceAmplitudeOfX - updatedSvgKeyFrame.rightControlPoint.x) * slope +
+                    updatedSvgKeyFrame.keyframePoint.y;
 
-                let updatedValue = ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
+                let updatedValue =
+                    ((newValueOfY - updatedSvgKeyFrame.keyframePoint.y) * this._scaleFactor) / this._heightScale;
 
                 if (updatedValue > -100 && updatedValue < 100) {
                     key.outTangent = slope * -1;
@@ -860,7 +906,8 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             let indexOfKeyframe = this.state.svgKeyframes.indexOf(updatedSvgKeyFrame);
             let previousKeyframe = this.state.svgKeyframes[indexOfKeyframe - 1];
             if (previousKeyframe?.keyframePoint) {
-                distanceWithPreviousKeyframe = Vector2.Distance(updatedSvgKeyFrame.keyframePoint, previousKeyframe.keyframePoint) / 2;
+                distanceWithPreviousKeyframe =
+                    Vector2.Distance(updatedSvgKeyFrame.keyframePoint, previousKeyframe.keyframePoint) / 2;
             }
         }
 
@@ -869,7 +916,8 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             let indexOfKeyframe = this.state.svgKeyframes.indexOf(updatedSvgKeyFrame);
             let nextKeyframe = this.state.svgKeyframes[indexOfKeyframe + 1];
             if (nextKeyframe?.keyframePoint) {
-                distanceWithNextKeyframe = Vector2.Distance(nextKeyframe.keyframePoint, updatedSvgKeyFrame.keyframePoint) / 2;
+                distanceWithNextKeyframe =
+                    Vector2.Distance(nextKeyframe.keyframePoint, updatedSvgKeyFrame.keyframePoint) / 2;
             }
         }
 
@@ -954,7 +1002,12 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * Finds the selected svg keyframe and passes the correct value to the correct animated keyframe.
      */
     setKeyframeValue = () => {
-        if (this.state.actionableKeyframe.frame !== "" && this.state.actionableKeyframe.frame !== undefined && this.state.actionableKeyframe.value !== "" && this.state.actionableKeyframe.value !== undefined) {
+        if (
+            this.state.actionableKeyframe.frame !== "" &&
+            this.state.actionableKeyframe.frame !== undefined &&
+            this.state.actionableKeyframe.value !== "" &&
+            this.state.actionableKeyframe.value !== undefined
+        ) {
             if (this.state.selected !== null) {
                 let currentSelected = this.state.svgKeyframes?.find((kf) => kf.selected);
                 if (currentSelected) {
@@ -981,7 +1034,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                             frame: this.state.actionableKeyframe.frame as number,
                             value: this.state.actionableKeyframe.value,
                         });
-                        const { prev, next } = this.getPreviousAndNextKeyframe(this.state.actionableKeyframe.frame as number);
+                        const { prev, next } = this.getPreviousAndNextKeyframe(
+                            this.state.actionableKeyframe.frame as number
+                        );
                         this.setState({ maxFrame: next, minFrame: prev });
                     }
                 }
@@ -1032,7 +1087,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
     setBrokenMode = () => {
         if (this.state.selected !== null) {
             let animation = this.state.selected;
-            this.setState({ isBrokenMode: !this.state.isBrokenMode }, () => this.selectAnimation(animation, this.state.selectedCoordinate));
+            this.setState({ isBrokenMode: !this.state.isBrokenMode }, () =>
+                this.selectAnimation(animation, this.state.selectedCoordinate)
+            );
         }
     };
 
@@ -1044,7 +1101,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         const animation = this.state.selected;
         if (this.state.svgKeyframes && animation) {
             const keys = animation.getKeys();
-            const selectedKeyframe = this.state.svgKeyframes.find((keyframe: IKeyframeSvgPoint) => keyframe.selected && (keyframe.isLeftActive || keyframe.isRightActive));
+            const selectedKeyframe = this.state.svgKeyframes.find(
+                (keyframe: IKeyframeSvgPoint) => keyframe.selected && (keyframe.isLeftActive || keyframe.isRightActive)
+            );
 
             if (selectedKeyframe !== null && selectedKeyframe) {
                 const { order, coordinate } = this.decodeCurveId(selectedKeyframe.id);
@@ -1372,7 +1431,15 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             return undefined;
         } else {
             // Get the animation properties
-            const { easingMode, easingType, usesTangents, valueType, highestFrame, name, targetProperty } = this.getAnimationData(animation);
+            const {
+                easingMode,
+                easingType,
+                usesTangents,
+                valueType,
+                highestFrame,
+                name,
+                targetProperty,
+            } = this.getAnimationData(animation);
 
             // Set the initial point of the curve
             const startKey = keyframes[0];
@@ -1391,7 +1458,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
 
                 const curveColor = valueType === Animation.ANIMATIONTYPE_FLOAT ? colors[4] : colors[d];
                 // START OF LINE/CURVE
-                let data: string | undefined = `M${startKey.frame * this._pixelFrameUnit}, ${this._heightScale - startValue[d] * middle}`; //
+                let data: string | undefined = `M${startKey.frame * this._pixelFrameUnit}, ${
+                    this._heightScale - startValue[d] * middle
+                }`; //
 
                 if (this.state) {
                     if (usesTangents) {
@@ -1438,7 +1507,11 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         let targetPropertyPath = animation.targetPropertyPath;
         let framesPerSecond = animation.framePerSecond;
         let highestFrame = animation.getHighestFrame();
-        let usesTangents = animation.getKeys().find((kf) => kf.hasOwnProperty("inTangent") || kf.hasOwnProperty("outTangent")) !== undefined ? true : false;
+        let usesTangents =
+            animation.getKeys().find((kf) => kf.hasOwnProperty("inTangent") || kf.hasOwnProperty("outTangent")) !==
+            undefined
+                ? true
+                : false;
         let valueType = animation.dataType;
         let easingType, easingMode;
         let easingFunction: EasingFunction = animation.getEasingFunction() as EasingFunction;
@@ -1505,7 +1578,14 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * @param coordinate (x, y, z) value
      * @param animationName The animation name to generate the curve id
      */
-    curvePathWithoutTangents(keyframes: IAnimationKey[], data: string, middle: number, type: number, coordinate: number, animationName: string) {
+    curvePathWithoutTangents(
+        keyframes: IAnimationKey[],
+        data: string,
+        middle: number,
+        type: number,
+        coordinate: number,
+        animationName: string
+    ) {
         const updatedKeyframes = this.calculateLinearTangents(keyframes);
         return this.curvePathWithTangents(updatedKeyframes, data, middle, type, coordinate, animationName);
     }
@@ -1519,7 +1599,14 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * @param coordinate (x, y, z) value
      * @param animationName The animation name to generate the curve id
      */
-    curvePathWithTangents(keyframes: IAnimationKey[], data: string, middle: number, type: number, coordinate: number, animationName: string) {
+    curvePathWithTangents(
+        keyframes: IAnimationKey[],
+        data: string,
+        middle: number,
+        type: number,
+        coordinate: number,
+        animationName: string
+    ) {
         keyframes.forEach((key, i) => {
             // Create a unique id for curve
             const curveId = this.encodeCurveId(animationName, i);
@@ -1574,8 +1661,14 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             }
 
             // Calculate the control points from the specified tangent values
-            var inT = key.inTangent === null || key.inTangent === undefined ? defaultTangent : this.getValueAsArray(type, key.inTangent)[coordinate];
-            var outT = key.outTangent === null || key.outTangent === undefined ? defaultTangent : this.getValueAsArray(type, key.outTangent)[coordinate];
+            var inT =
+                key.inTangent === null || key.inTangent === undefined
+                    ? defaultTangent
+                    : this.getValueAsArray(type, key.inTangent)[coordinate];
+            var outT =
+                key.outTangent === null || key.outTangent === undefined
+                    ? defaultTangent
+                    : this.getValueAsArray(type, key.outTangent)[coordinate];
 
             defaultWeight = 1 * this._pixelFrameUnit; // update based on control points
 
@@ -1598,7 +1691,10 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             // Define each point of the cuvre and the value on the SVG Path.
             if (i === 0) {
                 svgKeyframe = {
-                    keyframePoint: new Vector2(key.frame * this._pixelFrameUnit, this._heightScale - keyframe_valueAsArray * middle),
+                    keyframePoint: new Vector2(
+                        key.frame * this._pixelFrameUnit,
+                        this._heightScale - keyframe_valueAsArray * middle
+                    ),
                     rightControlPoint: outTangent,
                     leftControlPoint: null,
                     id: curveId,
@@ -1613,7 +1709,10 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 }
             } else {
                 svgKeyframe = {
-                    keyframePoint: new Vector2(key.frame * this._pixelFrameUnit, this._heightScale - keyframe_valueAsArray * middle),
+                    keyframePoint: new Vector2(
+                        key.frame * this._pixelFrameUnit,
+                        this._heightScale - keyframe_valueAsArray * middle
+                    ),
                     rightControlPoint: outTangent,
                     leftControlPoint: inTangent,
                     id: curveId,
@@ -1714,7 +1813,14 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 let intermediatePoint75 = new Vector2((pointB.x - pointA.x) * v + pointA.x, yInt75);
 
                 // Gets the four control points of bezier curve
-                let controlPoints = this.interpolateControlPoints(pointA, intermediatePoint25, u, intermediatePoint75, v, pointB);
+                let controlPoints = this.interpolateControlPoints(
+                    pointA,
+                    intermediatePoint25,
+                    u,
+                    intermediatePoint75,
+                    v,
+                    pointB
+                );
 
                 if (controlPoints !== undefined) {
                     this.setKeyframePoint(controlPoints, i, keyframes.length);
@@ -1769,7 +1875,14 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * @param v distance between p0 and p3 as percentage to affect p2
      * @param p3 finish point of curve
      */
-    interpolateControlPoints(p0: Vector2, p1: Vector2, u: number, p2: Vector2, v: number, p3: Vector2): Vector2[] | undefined {
+    interpolateControlPoints(
+        p0: Vector2,
+        p1: Vector2,
+        u: number,
+        p2: Vector2,
+        v: number,
+        p3: Vector2
+    ): Vector2[] | undefined {
         let a = 0.0;
         let b = 0.0;
         let c = 0.0;
@@ -1921,7 +2034,12 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 if (keys.length !== 0) {
                     const firstFrame = keys[0].frame;
                     const LastFrame = this.state.selected.getHighestFrame();
-                    this._mainAnimatable = this.props.scene.beginAnimation(target, firstFrame, LastFrame, this.state.isLooping);
+                    this._mainAnimatable = this.props.scene.beginAnimation(
+                        target,
+                        firstFrame,
+                        LastFrame,
+                        this.state.isLooping
+                    );
                     this._mainAnimatable.stop();
                 }
             }
@@ -1995,7 +2113,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         if (animation) {
             const hasKeyframe = animation.getKeys().find((x) => x.frame === frame);
             const currentValue = this.calculateCurrentPointInCurve(frame);
-            const value = hasKeyframe ? this.getValueAsArray(animation.dataType, hasKeyframe.value)[this.state.selectedCoordinate] : currentValue ?? 0;
+            const value = hasKeyframe
+                ? this.getValueAsArray(animation.dataType, hasKeyframe.value)[this.state.selectedCoordinate]
+                : currentValue ?? 0;
             const keyframe: IAnimationKey = { frame, value };
             this.setState(
                 {
@@ -2018,7 +2138,10 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
      * @param frame the frame to calculate its current value on the curve
      */
     calculateCurrentPointInCurve = (frame: number): number | undefined => {
-        if (this.state.selectedPathData !== undefined && this.state.selectedPathData[this.state.selectedCoordinate] !== undefined) {
+        if (
+            this.state.selectedPathData !== undefined &&
+            this.state.selectedPathData[this.state.selectedCoordinate] !== undefined
+        ) {
             const selectedCurve = this.state.selectedPathData[this.state.selectedCoordinate].domCurve.current;
             if (selectedCurve !== null) {
                 const curveLength = selectedCurve.getTotalLength();
@@ -2027,7 +2150,10 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 const currentPointInCurve = selectedCurve.getPointAtLength(frameValue);
                 const middle = this._heightScale / 2;
 
-                const offset = (currentPointInCurve?.y * this._heightScale - this._heightScale ** 2 / 2) / middle / this._heightScale;
+                const offset =
+                    (currentPointInCurve?.y * this._heightScale - this._heightScale ** 2 / 2) /
+                    middle /
+                    this._heightScale;
 
                 const unit = Math.sign(offset);
                 const currentValue = unit === -1 ? Math.abs(offset + unit) : unit - offset;
@@ -2060,7 +2186,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             if (keyframe.value === null) {
                 value = this.state.panningY;
             } else {
-                value = this.getValueAsArray(this.state.selected.dataType, keyframe.value)[this.state.selectedCoordinate];
+                value = this.getValueAsArray(this.state.selected.dataType, keyframe.value)[
+                    this.state.selectedCoordinate
+                ];
             }
 
             const valueScale = this._heightScale / this._scaleFactor;
@@ -2141,10 +2269,20 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                     let firstFrame = keys[0].frame;
                     let LastFrame = this.state.selected.getHighestFrame();
                     if (direction === 1) {
-                        this._mainAnimatable = this.props.scene.beginAnimation(target, firstFrame, LastFrame, this.state.isLooping);
+                        this._mainAnimatable = this.props.scene.beginAnimation(
+                            target,
+                            firstFrame,
+                            LastFrame,
+                            this.state.isLooping
+                        );
                     }
                     if (direction === -1) {
-                        this._mainAnimatable = this.props.scene.beginAnimation(target, LastFrame, firstFrame, this.state.isLooping);
+                        this._mainAnimatable = this.props.scene.beginAnimation(
+                            target,
+                            LastFrame,
+                            firstFrame,
+                            this.state.isLooping
+                        );
                     }
                     if (!this.state.isLooping && this._mainAnimatable) {
                         this._mainAnimatable.onAnimationEnd = () => this.playPause(0);
@@ -2268,7 +2406,11 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 firstFrame = keysCopy[0].frame;
                 lastFrame = keysCopy[keysCopy.length - 1].frame;
                 // If not selected get all keyframes
-                keysCopy.sort((a, b) => this.getValueAsArray(animation.dataType, a.value)[coordinate] - this.getValueAsArray(animation.dataType, b.value)[coordinate]);
+                keysCopy.sort(
+                    (a, b) =>
+                        this.getValueAsArray(animation.dataType, a.value)[coordinate] -
+                        this.getValueAsArray(animation.dataType, b.value)[coordinate]
+                );
                 lowest = keysCopy[0];
                 highest = keysCopy[keysCopy.length - 1];
                 keysCopy.sort((a, b) => a.frame - b.frame);
@@ -2302,7 +2444,11 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 }
 
                 // Sort to get lowest and highest values for scale
-                keysInRange.sort((a, b) => this.getValueAsArray(animation.dataType, a.value)[coordinate] - this.getValueAsArray(animation.dataType, b.value)[coordinate]);
+                keysInRange.sort(
+                    (a, b) =>
+                        this.getValueAsArray(animation.dataType, a.value)[coordinate] -
+                        this.getValueAsArray(animation.dataType, b.value)[coordinate]
+                );
 
                 lowest = keysInRange[0];
                 highest = keysInRange[keysInRange.length - 1];
@@ -2311,7 +2457,9 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
             }
 
             // calculate scale...
-            const scale = this.getValueAsArray(animation.dataType, highest?.value)[coordinate] - this.getValueAsArray(animation.dataType, lowest?.value)[coordinate];
+            const scale =
+                this.getValueAsArray(animation.dataType, highest?.value)[coordinate] -
+                this.getValueAsArray(animation.dataType, lowest?.value)[coordinate];
 
             // Scale Frames to fit width of canvas
             // reposition canvas to middle value of scale
@@ -2404,7 +2552,11 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
     render() {
         return (
             <div ref={this._editor} id="animation-curve-editor">
-                <Notification message={this.state.notification} open={this.state.notification !== "" ? true : false} close={this.clearNotification} />
+                <Notification
+                    message={this.state.notification}
+                    open={this.state.notification !== "" ? true : false}
+                    close={this.clearNotification}
+                />
                 <GraphActionsBar
                     setKeyframeValue={this.setKeyframeValueFromInput}
                     enabled={this.state.selected === null || this.state.selected === undefined ? false : true}
@@ -2481,7 +2633,15 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                                     })}
 
                                     {this.setValueLines().map((line, i) => {
-                                        return <line key={i} x1={-((this.state.frameAxisLength.length * 10) / 2)} y1={line.value} x2={this.state.frameAxisLength.length * 10} y2={line.value}></line>;
+                                        return (
+                                            <line
+                                                key={i}
+                                                x1={-((this.state.frameAxisLength.length * 10) / 2)}
+                                                y1={line.value}
+                                                x2={this.state.frameAxisLength.length * 10}
+                                                y2={line.value}
+                                            ></line>
+                                        );
                                     })}
 
                                     {/* Multiple Curves  */}
@@ -2516,16 +2676,36 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                                         />
                                     ))}
 
-                                    <rect onClick={(e) => this.moveFrameTo(e)} x={-((this.state.frameAxisLength.length * 10) / 2)} y={91 + this.state.panningY + "%"} width={this.state.frameAxisLength.length * 10} height="9%" fill="#222" style={{ cursor: "pointer" }}></rect>
+                                    <rect
+                                        onClick={(e) => this.moveFrameTo(e)}
+                                        x={-((this.state.frameAxisLength.length * 10) / 2)}
+                                        y={91 + this.state.panningY + "%"}
+                                        width={this.state.frameAxisLength.length * 10}
+                                        height="9%"
+                                        fill="#222"
+                                        style={{ cursor: "pointer" }}
+                                    ></rect>
 
                                     {this.state.frameAxisLength.map((f, i) => (
                                         <svg key={i} x="0" y={96 + this.state.panningY + "%"} className="frame-contain">
-                                            <text x={f.value} y="1px" dx="2px" style={{ fontSize: `${0.2 * this.state.scale}em` }}>
+                                            <text
+                                                x={f.value}
+                                                y="1px"
+                                                dx="2px"
+                                                style={{ fontSize: `${0.2 * this.state.scale}em` }}
+                                            >
                                                 {Math.round((f.label * 10) / this._pixelFrameUnit)}
                                             </text>
                                             <line x1={f.value} y1="0" x2={f.value} y2="5%"></line>
 
-                                            {f.value % this.state.fps === 0 && f.value !== 0 ? <line x1={f.value * this._pixelFrameUnit} y1="-100%" x2={f.value * this._pixelFrameUnit} y2="5%"></line> : null}
+                                            {f.value % this.state.fps === 0 && f.value !== 0 ? (
+                                                <line
+                                                    x1={f.value * this._pixelFrameUnit}
+                                                    y1="-100%"
+                                                    x2={f.value * this._pixelFrameUnit}
+                                                    y2="5%"
+                                                ></line>
+                                            ) : null}
                                         </svg>
                                     ))}
 

+ 75 - 12
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx

@@ -69,7 +69,10 @@ export class AnimationListTree extends React.Component<
     constructor(props: IAnimationListTreeProps) {
         super(props);
 
-        const animations = this.props.entity instanceof TargetedAnimation ? (this.props.entity as TargetedAnimation).animation : (this.props.entity as IAnimatable).animations;
+        const animations =
+            this.props.entity instanceof TargetedAnimation
+                ? (this.props.entity as TargetedAnimation).animation
+                : (this.props.entity as IAnimatable).animations;
 
         this.state = {
             selectedCoordinate: 0,
@@ -81,7 +84,9 @@ export class AnimationListTree extends React.Component<
 
     componentDidUpdate(prevProps: IAnimationListTreeProps) {
         if (this.props.entity instanceof TargetedAnimation) {
-            if ((this.props.entity as TargetedAnimation).animation !== (prevProps.entity as TargetedAnimation).animation) {
+            if (
+                (this.props.entity as TargetedAnimation).animation !== (prevProps.entity as TargetedAnimation).animation
+            ) {
                 this.setState({
                     animationList: this.generateList(),
                     animations: (this.props.entity as TargetedAnimation).animation,
@@ -177,11 +182,25 @@ export class AnimationListTree extends React.Component<
         this.props.selectAnimation(animation, coordinate);
     }
 
-    coordinateItem(i: number, animation: Animation, coordinate: string, color: string, selectedCoordinate: SelectedCoordinate) {
+    coordinateItem(
+        i: number,
+        animation: Animation,
+        coordinate: string,
+        color: string,
+        selectedCoordinate: SelectedCoordinate
+    ) {
         const setSelectedCoordinate = () => this.setSelectedCoordinate(animation, selectedCoordinate, i);
-        const handleClass = `handle-indicator ${this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"}`;
+        const handleClass = `handle-indicator ${
+            this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"
+        }`;
         return (
-            <li key={`${i}_${coordinate}`} id={`${i}_${coordinate}`} className="property" style={{ color: color }} onClick={setSelectedCoordinate}>
+            <li
+                key={`${i}_${coordinate}`}
+                id={`${i}_${coordinate}`}
+                className="property"
+                style={{ color: color }}
+                onClick={setSelectedCoordinate}
+            >
                 <div className={handleClass}></div>
                 {animation.targetProperty} {coordinate.toUpperCase()}
             </li>
@@ -193,18 +212,40 @@ export class AnimationListTree extends React.Component<
         const selectAnimation = () => this.props.selectAnimation(animation);
         const toggle = () => this.toggleProperty(i);
         return (
-            <li className={this.props.selected && this.props.selected.name === animation.name ? "property sub active" : "property sub"} key={i}>
-                <div className={`animation-arrow ${this.state.animationList && this.state.animationList[i].open ? "" : "flip"}`} onClick={toggle}></div>
+            <li
+                className={
+                    this.props.selected && this.props.selected.name === animation.name
+                        ? "property sub active"
+                        : "property sub"
+                }
+                key={i}
+            >
+                <div
+                    className={`animation-arrow ${
+                        this.state.animationList && this.state.animationList[i].open ? "" : "flip"
+                    }`}
+                    onClick={toggle}
+                ></div>
                 <p onClick={selectAnimation}>{animation.targetProperty}</p>
                 <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={editAnimation} />
                 {!((this.props.entity as TargetedAnimation).getClassName() === "TargetedAnimation") ? (
                     this.props.selected && this.props.selected.name === animation.name ? (
-                        <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={this.deleteAnimation} />
+                        <IconButtonLineComponent
+                            tooltip="Remove"
+                            icon="small animation-delete"
+                            onClick={this.deleteAnimation}
+                        />
                     ) : (
                         <div className="spacer"></div>
                     )
                 ) : null}
-                <ul className={`sub-list ${this.state.animationList && this.state.animationList[i].open ? "" : "hidden"}`}>{childrenElements.map((c) => this.coordinateItem(i, animation, c.id, c.color, c.coordinate))}</ul>
+                <ul
+                    className={`sub-list ${
+                        this.state.animationList && this.state.animationList[i].open ? "" : "hidden"
+                    }`}
+                >
+                    {childrenElements.map((c) => this.coordinateItem(i, animation, c.id, c.color, c.coordinate))}
+                </ul>
             </li>
         );
     }
@@ -215,11 +256,33 @@ export class AnimationListTree extends React.Component<
                 const editAnimation = () => this.props.editAnimation(animation);
                 const selectAnimation = () => this.props.selectAnimation(animation, 0);
                 return (
-                    <li className={this.props.selected && this.props.selected.name === animation.name ? "property active" : "property"} key={i} onClick={selectAnimation}>
+                    <li
+                        className={
+                            this.props.selected && this.props.selected.name === animation.name
+                                ? "property active"
+                                : "property"
+                        }
+                        key={i}
+                        onClick={selectAnimation}
+                    >
                         <div className={`animation-bullet`}></div>
                         <p>{animation.targetProperty}</p>
-                        <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={editAnimation} />
-                        {!(this.props.entity instanceof TargetedAnimation) ? this.props.selected && this.props.selected.name === animation.name ? <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={this.deleteAnimation} /> : <div className="spacer"></div> : null}
+                        <IconButtonLineComponent
+                            tooltip="Options"
+                            icon="small animation-options"
+                            onClick={editAnimation}
+                        />
+                        {!(this.props.entity instanceof TargetedAnimation) ? (
+                            this.props.selected && this.props.selected.name === animation.name ? (
+                                <IconButtonLineComponent
+                                    tooltip="Remove"
+                                    icon="small animation-delete"
+                                    onClick={this.deleteAnimation}
+                                />
+                            ) : (
+                                <div className="spacer"></div>
+                            )
+                        ) : null}
                     </li>
                 );
             case Animation.ANIMATIONTYPE_VECTOR2:

+ 71 - 11
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx

@@ -94,7 +94,12 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
             this.props.scene.stopAnimation(this.props.animatable);
             this._mainAnimatable = null;
         } else {
-            this._mainAnimatable = this.props.scene.beginAnimation(this.props.animatable, this._animationControl.from, this._animationControl.to, this._animationControl.loop);
+            this._mainAnimatable = this.props.scene.beginAnimation(
+                this.props.animatable,
+                this._animationControl.from,
+                this._animationControl.to,
+                this._animationControl.loop
+            );
         }
         this.forceUpdate();
     }
@@ -184,20 +189,59 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                             <TextLineComponent label="Count" value={animations.length.toString()} />
                             <ButtonLineComponent label="Edit" onClick={() => this.onOpenAnimationCurveEditor()} />
                             {animations.map((anim, i) => {
-                                return <TextLineComponent key={anim.targetProperty + i} label={"#" + i + " >"} value={anim.targetProperty} />;
+                                return (
+                                    <TextLineComponent
+                                        key={anim.targetProperty + i}
+                                        label={"#" + i + " >"}
+                                        value={anim.targetProperty}
+                                    />
+                                );
                             })}
 
                             {this._isCurveEditorOpen && (
-                                <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}>
-                                    <AnimationCurveEditorComponent scene={this.props.scene} entity={animatableAsAny} lockObject={this.props.lockObject} playOrPause={() => this.playOrPause()} globalState={this.props.globalState} />
+                                <PopupComponent
+                                    id="curve-editor"
+                                    title="Curve Animation Editor"
+                                    size={{ width: 1024, height: 512 }}
+                                    onOpen={(window: Window) => {}}
+                                    onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}
+                                >
+                                    <AnimationCurveEditorComponent
+                                        scene={this.props.scene}
+                                        entity={animatableAsAny}
+                                        lockObject={this.props.lockObject}
+                                        playOrPause={() => this.playOrPause()}
+                                        globalState={this.props.globalState}
+                                    />
                                 </PopupComponent>
                             )}
                         </LineContainerComponent>
                         {animations.length > 0 && (
-                            <LineContainerComponent globalState={this.props.globalState} title="ANIMATION GENERAL CONTROL">
-                                <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="From" target={this._animationControl} propertyName="from" onChange={() => this.onChangeFromOrTo()} />
-                                <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="To" target={this._animationControl} propertyName="to" onChange={() => this.onChangeFromOrTo()} />
-                                <CheckBoxLineComponent label="Loop" onSelect={(value) => (this._animationControl.loop = value)} isSelected={() => this._animationControl.loop} />
+                            <LineContainerComponent
+                                globalState={this.props.globalState}
+                                title="ANIMATION GENERAL CONTROL"
+                            >
+                                <FloatLineComponent
+                                    lockObject={this.props.lockObject}
+                                    isInteger={true}
+                                    label="From"
+                                    target={this._animationControl}
+                                    propertyName="from"
+                                    onChange={() => this.onChangeFromOrTo()}
+                                />
+                                <FloatLineComponent
+                                    lockObject={this.props.lockObject}
+                                    isInteger={true}
+                                    label="To"
+                                    target={this._animationControl}
+                                    propertyName="to"
+                                    onChange={() => this.onChangeFromOrTo()}
+                                />
+                                <CheckBoxLineComponent
+                                    label="Loop"
+                                    onSelect={(value) => (this._animationControl.loop = value)}
+                                    isSelected={() => this._animationControl.loop}
+                                />
                                 {this._isPlaying && (
                                     <SliderLineComponent
                                         ref={this.timelineRef}
@@ -209,7 +253,10 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                         onInput={(value) => this.onCurrentFrameChange(value)}
                                     />
                                 )}
-                                <ButtonLineComponent label={this._isPlaying ? "Stop" : "Play"} onClick={() => this.playOrPause()} />
+                                <ButtonLineComponent
+                                    label={this._isPlaying ? "Stop" : "Play"}
+                                    onClick={() => this.playOrPause()}
+                                />
                                 {(this._ranges.length > 0 || (this._animations && this._animations.length > 0)) && (
                                     <>
                                         <CheckBoxLineComponent
@@ -228,8 +275,21 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                         />
                                         {animatableAsAny.animationPropertiesOverride != null && (
                                             <div>
-                                                <CheckBoxLineComponent label="Enable blending" target={animatableAsAny.animationPropertiesOverride} propertyName="enableBlending" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                                                <SliderLineComponent label="Blending speed" target={animatableAsAny.animationPropertiesOverride} propertyName="blendingSpeed" minimum={0} maximum={0.1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                                                <CheckBoxLineComponent
+                                                    label="Enable blending"
+                                                    target={animatableAsAny.animationPropertiesOverride}
+                                                    propertyName="enableBlending"
+                                                    onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                                                />
+                                                <SliderLineComponent
+                                                    label="Blending speed"
+                                                    target={animatableAsAny.animationPropertiesOverride}
+                                                    propertyName="blendingSpeed"
+                                                    minimum={0}
+                                                    maximum={0.1}
+                                                    step={0.01}
+                                                    onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                                                />
                                             </div>
                                         )}
                                     </>

+ 50 - 10
inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx

@@ -94,30 +94,70 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
     render() {
         return (
             <div className="controls">
-                <IconButtonLineComponent tooltip="Animation Start" icon="animation-start" onClick={this.moveToAnimationStart}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Previous Keyframe" icon="animation-lastkey" onClick={this.previousKeyframe}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Animation Start"
+                    icon="animation-start"
+                    onClick={this.moveToAnimationStart}
+                ></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Previous Keyframe"
+                    icon="animation-lastkey"
+                    onClick={this.previousKeyframe}
+                ></IconButtonLineComponent>
                 {this.props.isPlaying ? (
                     <div className="stop-container">
                         {this.state.playingType === "reverse" ? (
                             <>
-                                <IconButtonLineComponent tooltip="Pause" icon="animation-stop" onClick={this.pause}></IconButtonLineComponent>
-                                <IconButtonLineComponent tooltip="Play Forward" icon="animation-playfwd" onClick={this.play}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Pause"
+                                    icon="animation-stop"
+                                    onClick={this.pause}
+                                ></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Play Forward"
+                                    icon="animation-playfwd"
+                                    onClick={this.play}
+                                ></IconButtonLineComponent>
                             </>
                         ) : (
                             <>
-                                <IconButtonLineComponent tooltip="Play Reverse" icon="animation-playrev" onClick={this.playBackwards}></IconButtonLineComponent>
-                                <IconButtonLineComponent tooltip="Pause" icon="animation-stop" onClick={this.pause}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Play Reverse"
+                                    icon="animation-playrev"
+                                    onClick={this.playBackwards}
+                                ></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Pause"
+                                    icon="animation-stop"
+                                    onClick={this.pause}
+                                ></IconButtonLineComponent>
                             </>
                         )}
                     </div>
                 ) : (
                     <div className="stop-container">
-                        <IconButtonLineComponent tooltip="Play Reverse" icon="animation-playrev" onClick={this.playBackwards}></IconButtonLineComponent>
-                        <IconButtonLineComponent tooltip="Play Forward" icon="animation-playfwd" onClick={this.play}></IconButtonLineComponent>
+                        <IconButtonLineComponent
+                            tooltip="Play Reverse"
+                            icon="animation-playrev"
+                            onClick={this.playBackwards}
+                        ></IconButtonLineComponent>
+                        <IconButtonLineComponent
+                            tooltip="Play Forward"
+                            icon="animation-playfwd"
+                            onClick={this.play}
+                        ></IconButtonLineComponent>
                     </div>
                 )}
-                <IconButtonLineComponent tooltip="Next Keyframe" icon="animation-nextkey" onClick={this.nextKeyframe}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Animation End" icon="animation-end" onClick={this.moveToAnimationEnd}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Next Keyframe"
+                    icon="animation-nextkey"
+                    onClick={this.nextKeyframe}
+                ></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Animation End"
+                    icon="animation-end"
+                    onClick={this.moveToAnimationEnd}
+                ></IconButtonLineComponent>
             </div>
         );
     }

+ 52 - 7
inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx

@@ -265,17 +265,54 @@ export class EditorControls extends React.Component<IEditorControlsProps, IEdito
         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.handleFirstTab}></IconButtonLineComponent>}
-                    <IconButtonLineComponent active={this.state.isLoadTabOpen} tooltip="Load Animation" icon="medium load" onClick={this.handleSecondTab}></IconButtonLineComponent>
-                    {this.state.animationsCount === 0 ? null : <IconButtonLineComponent active={this.state.isSaveTabOpen} tooltip="Save Animation" icon="medium save" onClick={this.handleThirdTab}></IconButtonLineComponent>}
-                    {this.state.animationsCount === 0 ? null : <IconButtonLineComponent active={this.state.isEditTabOpen} tooltip="Edit Animations" icon="medium animation-edit" onClick={this.handleFourthTab}></IconButtonLineComponent>}
+                    {this.props.isTargetedAnimation ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isAnimationTabOpen}
+                            tooltip="Add Animation"
+                            icon="medium add-animation"
+                            onClick={this.handleFirstTab}
+                        ></IconButtonLineComponent>
+                    )}
+                    <IconButtonLineComponent
+                        active={this.state.isLoadTabOpen}
+                        tooltip="Load Animation"
+                        icon="medium load"
+                        onClick={this.handleSecondTab}
+                    ></IconButtonLineComponent>
+                    {this.state.animationsCount === 0 ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isSaveTabOpen}
+                            tooltip="Save Animation"
+                            icon="medium save"
+                            onClick={this.handleThirdTab}
+                        ></IconButtonLineComponent>
+                    )}
+                    {this.state.animationsCount === 0 ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isEditTabOpen}
+                            tooltip="Edit Animations"
+                            icon="medium animation-edit"
+                            onClick={this.handleFourthTab}
+                        ></IconButtonLineComponent>
+                    )}
                     {this.state.isEditTabOpen ? (
                         <div className="input-fps">
-                            <NumericInputComponent label={""} precision={0} value={this.state.framesPerSecond} onChange={this.handleChangeFps} />
+                            <NumericInputComponent
+                                label={""}
+                                precision={0}
+                                value={this.state.framesPerSecond}
+                                onChange={this.handleChangeFps}
+                            />
                             <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.changeLoopBehavior}></IconButtonLineComponent> : null}
+                    {this.state.isEditTabOpen ? (
+                        <IconButtonLineComponent
+                            tooltip="Loop/Unloop"
+                            icon={`medium ${this.state.isLoopActive ? "loop-active last" : "loop-inactive last"}`}
+                            onClick={this.changeLoopBehavior}
+                        ></IconButtonLineComponent>
+                    ) : null}
                 </div>
                 {this.props.isTargetedAnimation ? null : (
                     <AddAnimation
@@ -304,7 +341,15 @@ export class EditorControls extends React.Component<IEditorControlsProps, IEdito
                     />
                 ) : null}
 
-                {this.state.isSaveTabOpen ? <SaveSnippet lockObject={this.props.lockObject} animations={(this.props.entity as IAnimatable).animations} snippetServer={this.props.snippetServer} globalState={this.props.globalState} snippetId={this.state.snippetId} /> : null}
+                {this.state.isSaveTabOpen ? (
+                    <SaveSnippet
+                        lockObject={this.props.lockObject}
+                        animations={(this.props.entity as IAnimatable).animations}
+                        snippetServer={this.props.snippetServer}
+                        globalState={this.props.globalState}
+                        snippetId={this.state.snippetId}
+                    />
+                ) : null}
 
                 {this.state.isEditTabOpen ? (
                     <AnimationListTree

+ 53 - 9
inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx

@@ -39,7 +39,10 @@ interface IGraphActionsBarProps {
  * Has the buttons and actions for the Canvas Graph.
  * Handles input change and actions (flat, broken mode, set linear control points)
  */
-export class GraphActionsBar extends React.Component<IGraphActionsBarProps, { frame: string; value: string; min: number | undefined; max: number | undefined }> {
+export class GraphActionsBar extends React.Component<
+    IGraphActionsBarProps,
+    { frame: string; value: string; min: number | undefined; max: number | undefined }
+> {
     private _frameInput: React.RefObject<HTMLInputElement>;
     private _valueInput: React.RefObject<HTMLInputElement>;
     constructor(props: IGraphActionsBarProps) {
@@ -63,7 +66,10 @@ export class GraphActionsBar extends React.Component<IGraphActionsBarProps, { fr
             this.setState({ frame, value });
         }
 
-        if (prevProps.frameRange.min !== this.props.frameRange.min || prevProps.frameRange.max !== this.props.frameRange.max) {
+        if (
+            prevProps.frameRange.min !== this.props.frameRange.min ||
+            prevProps.frameRange.max !== this.props.frameRange.max
+        ) {
             this.setState({ min: this.props.frameRange.min, max: this.props.frameRange.max });
         }
     }
@@ -151,18 +157,56 @@ export class GraphActionsBar extends React.Component<IGraphActionsBarProps, { fr
                     <div className="icon babylon-logo"></div>
                     <div className="title">{this.props.title}</div>
                 </div>
-                <div className={`buttons-container ${this.props.enabled ? "pointer-events-enabled" : "pointer-events-disabled"}`}>
+                <div
+                    className={`buttons-container ${
+                        this.props.enabled ? "pointer-events-enabled" : "pointer-events-disabled"
+                    }`}
+                >
                     <div className="action-input frame-input">
-                        <input ref={this._frameInput} type="number" onChange={this.handleFrameChange} value={this.state.frame} max={this.state.max} min={this.state.min} step="1" disabled={this.props.actionableKeyframe.frame === undefined} onBlur={this.onBlur} />
+                        <input
+                            ref={this._frameInput}
+                            type="number"
+                            onChange={this.handleFrameChange}
+                            value={this.state.frame}
+                            max={this.state.max}
+                            min={this.state.min}
+                            step="1"
+                            disabled={this.props.actionableKeyframe.frame === undefined}
+                            onBlur={this.onBlur}
+                        />
                     </div>
                     <div className="action-input">
-                        <input ref={this._valueInput} type="number" value={this.state.value} onChange={this.handleValueChange} step="0.01" disabled={this.props.actionableKeyframe.value === undefined} onBlur={this.onBlur} />
+                        <input
+                            ref={this._valueInput}
+                            type="number"
+                            value={this.state.value}
+                            onChange={this.handleValueChange}
+                            step="0.01"
+                            disabled={this.props.actionableKeyframe.value === undefined}
+                            onBlur={this.onBlur}
+                        />
                     </div>
                     <IconButtonLineComponent tooltip={"Add Keyframe"} icon="new-key" onClick={this.props.addKeyframe} />
-                    <IconButtonLineComponent tooltip={"Frame selected keyframes"} icon="frame" onClick={this.props.frameSelectedKeyframes} />
-                    <IconButtonLineComponent tooltip={this.props.brokenMode ? "Flat selected control point" : "Flat control points"} icon="flat-tangent" onClick={this.props.flatTangent} />
-                    <IconButtonLineComponent tooltip={this.props.brokenMode ? "Broken Mode On" : "Broken Mode Off"} icon={this.props.brokenMode ? "break-tangent" : "unify-tangent"} onClick={this.props.brokeTangents} />
-                    <IconButtonLineComponent tooltip={"Linear"} icon="linear-tangent" onClick={this.props.setLerpToActiveControlPoint} />
+                    <IconButtonLineComponent
+                        tooltip={"Frame selected keyframes"}
+                        icon="frame"
+                        onClick={this.props.frameSelectedKeyframes}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={this.props.brokenMode ? "Flat selected control point" : "Flat control points"}
+                        icon="flat-tangent"
+                        onClick={this.props.flatTangent}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={this.props.brokenMode ? "Broken Mode On" : "Broken Mode Off"}
+                        icon={this.props.brokenMode ? "break-tangent" : "unify-tangent"}
+                        onClick={this.props.brokeTangents}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={"Linear"}
+                        icon="linear-tangent"
+                        onClick={this.props.setLerpToActiveControlPoint}
+                    />
                 </div>
             </div>
         );

+ 16 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx

@@ -79,8 +79,22 @@ export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
         const svgImageIcon = this.props.selected ? keySelected : keyInactive;
         return (
             <>
-                <svg className="draggable" x={this.props.keyframePoint.x} y={this.props.keyframePoint.y} style={{ overflow: "visible", cursor: "pointer" }}>
-                    <image data-id={this.props.id} className="draggable" x="-1" y="-1.5" width="3" height="3" href={svgImageIcon} onClick={this.select} />
+                <svg
+                    className="draggable"
+                    x={this.props.keyframePoint.x}
+                    y={this.props.keyframePoint.y}
+                    style={{ overflow: "visible", cursor: "pointer" }}
+                >
+                    <image
+                        data-id={this.props.id}
+                        className="draggable"
+                        x="-1"
+                        y="-1.5"
+                        width="3"
+                        height="3"
+                        href={svgImageIcon}
+                        onClick={this.select}
+                    />
                 </svg>
                 {this.props.leftControlPoint && (
                     <AnchorSvgPoint

+ 6 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx

@@ -104,7 +104,12 @@ export class LoadSnippet extends React.Component<ILoadSnippetProps, { snippetId:
     render() {
         return (
             <div className="load-container">
-                <TextInputLineComponent label="Snippet Id" lockObject={this.props.lockObject} value={this.state.snippetId} onChange={this.change} />
+                <TextInputLineComponent
+                    label="Snippet Id"
+                    lockObject={this.props.lockObject}
+                    value={this.state.snippetId}
+                    onChange={this.change}
+                />
                 <ButtonLineComponent label="Load from snippet server" onClick={this.loadFromSnippet} />
                 <div className="load-browse">
                     <p>Local File</p>

+ 15 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx

@@ -114,7 +114,11 @@ export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAn
                             });
                         }
 
-                        alert("Animations saved with ID: " + serverId + " (please note that the id was also saved to your clipboard)");
+                        alert(
+                            "Animations saved with ID: " +
+                                serverId +
+                                " (please note that the id was also saved to your clipboard)"
+                        );
                     } else {
                         alert("Unable to save your animations");
                     }
@@ -147,7 +151,13 @@ export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAn
                                 <li key={i}>
                                     <div>
                                         <label>
-                                            <input id={`save_${i}`} name={`save_${animation?.name}`} type="checkbox" checked={this.state.selectedAnimations[i].selected} onChange={this.handleCheckboxChange} />
+                                            <input
+                                                id={`save_${i}`}
+                                                name={`save_${animation?.name}`}
+                                                type="checkbox"
+                                                checked={this.state.selectedAnimations[i].selected}
+                                                onChange={this.handleCheckboxChange}
+                                            />
                                             {animation?.name}
                                         </label>
                                     </div>
@@ -157,7 +167,9 @@ export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAn
                     </ul>
                 </div>
                 <div className="save-buttons">
-                    {this.props.snippetId && <ButtonLineComponent label="Save to snippet server" onClick={this.saveToSnippet} />}
+                    {this.props.snippetId && (
+                        <ButtonLineComponent label="Save to snippet server" onClick={this.saveToSnippet} />
+                    )}
                     <ButtonLineComponent label="Save" onClick={this.saveToFile} />
                 </div>
                 <div className="save-server">

+ 32 - 6
inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx

@@ -84,7 +84,11 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
 
     // Makes sure the canvas has resposition correctly
     componentDidUpdate(prevProps: ISvgDraggableAreaProps) {
-        if (this.props.positionCanvas !== prevProps.positionCanvas && this.props.positionCanvas !== undefined && this.props.repositionCanvas) {
+        if (
+            this.props.positionCanvas !== prevProps.positionCanvas &&
+            this.props.positionCanvas !== undefined &&
+            this.props.repositionCanvas
+        ) {
             this.setState(
                 {
                     panX: this.props.positionCanvas.x,
@@ -128,7 +132,10 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         if ((e.target as SVGSVGElement).classList.contains("pannable")) {
             if (this._isControlKeyPress) {
                 this._active = true;
-                this._panStart.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
+                this._panStart.set(
+                    e.clientX - e.currentTarget.getBoundingClientRect().left,
+                    e.clientY - e.currentTarget.getBoundingClientRect().top
+                );
             }
         }
     };
@@ -144,13 +151,20 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
                 if ((e.target as SVGSVGElement).classList.contains("pannable")) {
                     if (this._isControlKeyPress) {
                         if (this._panStart.x !== 0 && this._panStart.y !== 0) {
-                            this._panStop.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
+                            this._panStop.set(
+                                e.clientX - e.currentTarget.getBoundingClientRect().left,
+                                e.clientY - e.currentTarget.getBoundingClientRect().top
+                            );
                             this.panDirection();
                         }
                     }
                 }
                 // Handles the playhead dragging
-                if (e.currentTarget.classList.contains("linear") && this._playheadDrag !== 0 && this._playheadSelected) {
+                if (
+                    e.currentTarget.classList.contains("linear") &&
+                    this._playheadDrag !== 0 &&
+                    this._playheadSelected
+                ) {
                     const moving = e.clientX - e.currentTarget.getBoundingClientRect().left;
 
                     const draggableAreaWidth = e.currentTarget.clientWidth;
@@ -336,10 +350,22 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
     }
 
     render() {
-        const viewBoxScaling = `${this.props.positionCanvas?.x} ${this.props.positionCanvas?.y} ${Math.round(this.props.scale * 200)} ${Math.round(this.props.scale * 100)}`;
+        const viewBoxScaling = `${this.props.positionCanvas?.x} ${this.props.positionCanvas?.y} ${Math.round(
+            this.props.scale * 200
+        )} ${Math.round(this.props.scale * 100)}`;
         return (
             <>
-                <svg className="linear pannable" ref={this._draggableArea} tabIndex={0} onMouseMove={this.drag} onMouseDown={this.dragStart} onMouseUp={this.dragEnd} onMouseLeave={this.dragEnd} onClick={this.focus} viewBox={viewBoxScaling}>
+                <svg
+                    className="linear pannable"
+                    ref={this._draggableArea}
+                    tabIndex={0}
+                    onMouseMove={this.drag}
+                    onMouseDown={this.dragStart}
+                    onMouseUp={this.dragEnd}
+                    onMouseLeave={this.dragEnd}
+                    onClick={this.focus}
+                    viewBox={viewBoxScaling}
+                >
                     {this.props.children}
                 </svg>
             </>

+ 30 - 4
inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx

@@ -78,12 +78,38 @@ export class TargetedAnimationGridComponent extends React.Component<ITargetedAni
             <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
                     <TextLineComponent label="Class" value={targetedAnimation.getClassName()} />
-                    <TextInputLineComponent lockObject={this.props.lockObject} label="Name" target={targetedAnimation.animation} propertyName="name" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    {targetedAnimation.target.name && <TextLineComponent label="Target" value={targetedAnimation.target.name} onLink={() => this.props.globalState.onSelectionChangedObservable.notifyObservers(targetedAnimation)} />}
+                    <TextInputLineComponent
+                        lockObject={this.props.lockObject}
+                        label="Name"
+                        target={targetedAnimation.animation}
+                        propertyName="name"
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                    />
+                    {targetedAnimation.target.name && (
+                        <TextLineComponent
+                            label="Target"
+                            value={targetedAnimation.target.name}
+                            onLink={() =>
+                                this.props.globalState.onSelectionChangedObservable.notifyObservers(targetedAnimation)
+                            }
+                        />
+                    )}
                     <ButtonLineComponent label="Edit animation" onClick={this.onOpenAnimationCurveEditor} />
                     {this._isCurveEditorOpen && (
-                        <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={this.onCloseAnimationCurveEditor}>
-                            <AnimationCurveEditorComponent scene={this.props.scene} entity={targetedAnimation as any} playOrPause={this.playOrPause} lockObject={this.props.lockObject} globalState={this.props.globalState} />
+                        <PopupComponent
+                            id="curve-editor"
+                            title="Curve Animation Editor"
+                            size={{ width: 1024, height: 512 }}
+                            onOpen={(window: Window) => {}}
+                            onClose={this.onCloseAnimationCurveEditor}
+                        >
+                            <AnimationCurveEditorComponent
+                                scene={this.props.scene}
+                                entity={targetedAnimation as any}
+                                playOrPause={this.playOrPause}
+                                lockObject={this.props.lockObject}
+                                globalState={this.props.globalState}
+                            />
                         </PopupComponent>
                     )}
                     <ButtonLineComponent label="Dispose" onClick={this.deleteAnimation} />

+ 68 - 12
inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

@@ -146,7 +146,9 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
                     scrollWidth: this.calculateScrollWidth(0, newEnd),
                 });
                 if (this._scrollbarHandle.current && this._scrollContainer.current) {
-                    this._scrollbarHandle.current.style.left = `${this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar}px`;
+                    this._scrollbarHandle.current.style.left = `${
+                        this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar
+                    }px`;
                 }
             }
         );
@@ -429,7 +431,10 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
             }
 
             if (!(framesTo >= this.state.end - 20)) {
-                let toleft = framesTo * unit + this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar * 2;
+                let toleft =
+                    framesTo * unit +
+                    this._scrollContainer.current.getBoundingClientRect().left +
+                    this._marginScrollbar * 2;
                 if (this._scrollbarHandle.current) {
                     this._scrollbarHandle.current.style.left = toleft + "px";
                 }
@@ -492,7 +497,12 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
                         scrollable={this._scrollable}
                     />
                     <div className="timeline-wrapper">
-                        <div ref={this._scrollable} className="display-line" onClick={this.setCurrentFrame} tabIndex={50}>
+                        <div
+                            ref={this._scrollable}
+                            className="display-line"
+                            onClick={this.setCurrentFrame}
+                            tabIndex={50}
+                        >
                             <svg
                                 style={{
                                     width: "100%",
@@ -509,16 +519,35 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
                                         <svg key={`tl_${frame}`}>
                                             {
                                                 <>
-                                                    {frame % Math.round(this.state.selectionLength.length / 20) === 0 ? (
+                                                    {frame % Math.round(this.state.selectionLength.length / 20) ===
+                                                    0 ? (
                                                         <>
-                                                            <text x={(i * 100) / this.state.selectionLength.length + "%"} y="18" style={{ fontSize: 10, fill: "#555555" }}>
+                                                            <text
+                                                                x={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y="18"
+                                                                style={{ fontSize: 10, fill: "#555555" }}
+                                                            >
                                                                 {frame}
                                                             </text>
-                                                            <line x1={(i * 100) / this.state.selectionLength.length + "%"} y1="22" x2={(i * 100) / this.state.selectionLength.length + "%"} y2="40" style={{ stroke: "#555555", strokeWidth: 0.5 }} />
+                                                            <line
+                                                                x1={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y1="22"
+                                                                x2={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y2="40"
+                                                                style={{ stroke: "#555555", strokeWidth: 0.5 }}
+                                                            />
                                                         </>
                                                     ) : null}
                                                     {this.getCurrentFrame(frame) ? (
-                                                        <svg x={this._scrollable.current ? this._scrollable.current.clientWidth / this.state.selectionLength.length / 2 : 1}>
+                                                        <svg
+                                                            x={
+                                                                this._scrollable.current
+                                                                    ? this._scrollable.current.clientWidth /
+                                                                      this.state.selectionLength.length /
+                                                                      2
+                                                                    : 1
+                                                            }
+                                                        >
                                                             <line
                                                                 x1={(i * 100) / this.state.selectionLength.length + "%"}
                                                                 y1="0"
@@ -526,7 +555,10 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
                                                                 y2="40"
                                                                 style={{
                                                                     stroke: "rgba(18, 80, 107, 0.26)",
-                                                                    strokeWidth: this._scrollable.current ? this._scrollable.current.clientWidth / this.state.selectionLength.length : 1,
+                                                                    strokeWidth: this._scrollable.current
+                                                                        ? this._scrollable.current.clientWidth /
+                                                                          this.state.selectionLength.length
+                                                                        : 1,
                                                                 }}
                                                             />
                                                         </svg>
@@ -534,7 +566,14 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
 
                                                     {this.getKeyframe(frame) ? (
                                                         <svg key={`kf_${i}`} tabIndex={i + 40}>
-                                                            <line id={`kf_${i.toString()}`} x1={(i * 100) / this.state.selectionLength.length + "%"} y1="0" x2={(i * 100) / this.state.selectionLength.length + "%"} y2="40" style={{ stroke: "#ffc017", strokeWidth: 1 }} />
+                                                            <line
+                                                                id={`kf_${i.toString()}`}
+                                                                x1={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y1="0"
+                                                                x2={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y2="40"
+                                                                style={{ stroke: "#ffc017", strokeWidth: 1 }}
+                                                            />
                                                         </svg>
                                                     ) : null}
                                                 </>
@@ -545,9 +584,20 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
                             </svg>
                         </div>
 
-                        <div className="timeline-scroll-handle" onMouseMove={this.scrollDrag} onMouseDown={this.scrollDragStart} onMouseUp={this.scrollDragEnd} onMouseLeave={this.scrollDragEnd} onDragStart={this.dragDomFalse}>
+                        <div
+                            className="timeline-scroll-handle"
+                            onMouseMove={this.scrollDrag}
+                            onMouseDown={this.scrollDragStart}
+                            onMouseUp={this.scrollDragEnd}
+                            onMouseLeave={this.scrollDragEnd}
+                            onDragStart={this.dragDomFalse}
+                        >
                             <div className="scroll-handle" ref={this._scrollContainer} tabIndex={60}>
-                                <div className="handle" ref={this._scrollbarHandle} style={{ width: this.state.scrollWidth }}>
+                                <div
+                                    className="handle"
+                                    ref={this._scrollbarHandle}
+                                    style={{ width: this.state.scrollWidth }}
+                                >
                                     <div className="left-grabber">
                                         <div className="left-draggable">
                                             <div className="grabber"></div>
@@ -571,7 +621,13 @@ export class Timeline extends React.Component<ITimelineProps, ITimelineState> {
                         </div>
 
                         <div className="input-frame">
-                            <input ref={this._inputAnimationLimit} type="number" value={this.state.limitValue} onChange={(e) => this.handleLimitChange(e)} onBlur={(e) => this.onInputBlur(e)}></input>
+                            <input
+                                ref={this._inputAnimationLimit}
+                                type="number"
+                                value={this.state.limitValue}
+                                onChange={(e) => this.handleLimitChange(e)}
+                                onBlur={(e) => this.onInputBlur(e)}
+                            ></input>
                         </div>
                     </div>
                 </div>