Alejandro Toledo пре 5 година
родитељ
комит
9e1a2ddff4

+ 61 - 19
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -17,6 +17,7 @@ import { Scene } from "babylonjs/scene";
 import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
 import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
 import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
 import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
 import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
 import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
+import { Nullable } from 'babylonjs/types';
 
 
 require("./curveEditor.scss");
 require("./curveEditor.scss");
 
 
@@ -34,7 +35,6 @@ interface ICanvasAxis {
 }
 }
 
 
 export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
 export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
-    animations: Animation[],
     animationName: string,
     animationName: string,
     animationType: string,
     animationType: string,
     animationTargetProperty: string,
     animationTargetProperty: string,
@@ -63,7 +63,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
     // Canvas Length *Review this functionality
     // Canvas Length *Review this functionality
     readonly _entityName: string;
     readonly _entityName: string;
     readonly _canvasLength: number = 20;
     readonly _canvasLength: number = 20;
-    private _newAnimations: Animation[] = [];
     private _svgKeyframes: IKeyframeSvgPoint[] = [];
     private _svgKeyframes: IKeyframeSvgPoint[] = [];
     private _frames: Vector2[] = [];
     private _frames: Vector2[] = [];
     private _isPlaying: boolean = false;
     private _isPlaying: boolean = false;
@@ -99,7 +98,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         // will update this until we have a top scroll/zoom feature
         // will update this until we have a top scroll/zoom feature
         let valueInd = [2, 1.8, 1.6, 1.4, 1.2, 1, 0.8, 0.6, 0.4, 0.2, 0];
         let valueInd = [2, 1.8, 1.6, 1.4, 1.2, 1, 0.8, 0.6, 0.4, 0.2, 0];
         this.state = {
         this.state = {
-            animations: this._newAnimations,
             selected: initialSelection,
             selected: initialSelection,
             isOpen: true,
             isOpen: true,
             currentPathData: initialPathData,
             currentPathData: initialPathData,
@@ -225,6 +223,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                 element = <li className={this.state.selected && this.state.selected.name === animation.name ? 'active' : ''} key={i} onClick={() => this.selectAnimation(animation)}>
                 element = <li className={this.state.selected && this.state.selected.name === animation.name ? 'active' : ''} key={i} onClick={() => this.selectAnimation(animation)}>
                     <p>{animation.name}&nbsp;
                     <p>{animation.name}&nbsp;
                     <span>{animation.targetProperty}</span></p>
                     <span>{animation.targetProperty}</span></p>
+                    { !(this.props.entity instanceof TargetedAnimation) ? this.state.selected && this.state.selected.name === animation.name ? <ButtonLineComponent label={"Remove"} onClick={() => this.deleteAnimation()} /> : null : null }
                 </li>
                 </li>
                 break;
                 break;
             case Animation.ANIMATIONTYPE_VECTOR2:
             case Animation.ANIMATIONTYPE_VECTOR2:
@@ -319,6 +318,20 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
 
 
     }
     }
 
 
+    deleteAnimation(){
+        let currentSelected = this.state.selected;
+        if (this.props.entity instanceof TargetedAnimation){
+            console.log("no animation remove allowed");
+        } else {
+            let animations = (this.props.entity as IAnimatable).animations;
+            if (animations){
+                let updatedAnimations = animations.filter(anim => anim !== currentSelected);
+                (this.props.entity as IAnimatable).animations = updatedAnimations as Nullable<Animation[]>;
+            } 
+        }
+        
+    }
+
     addAnimation() {
     addAnimation() {
         if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
         if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
 
 
@@ -416,24 +429,39 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                         break;
                         break;
                 }
                 }
 
 
-                let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, 30, animationDataType);
+                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);
 
 
-                // Start with two keyframes
-                var keys = [];
-                keys.push({
-                    frame: 0,
-                    value: startValue,
-                    outTangent: outTangent
-                });
+                if (alreadyAnimatedProperty){
+                    this.setState({ notification: `The property "${this.state.animationTargetProperty}" already has an animation` });
+                } else if (alreadyAnimationName){
+                    this.setState({ notification: `There is already an animation with the name: "${this.state.animationName}"` });
+                } else {
+
+                    let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, 30, animationDataType);
+
+                    // Start with two keyframes
+                    var keys = [];
+                    keys.push({
+                        frame: 0,
+                        value: startValue,
+                        outTangent: outTangent
+                    });
 
 
-                keys.push({
-                    inTangent: inTangent,
-                    frame: 100,
-                    value: endValue
-                });
+                    keys.push({
+                        inTangent: inTangent,
+                        frame: 100,
+                        value: endValue
+                    });
 
 
-                animation.setKeys(keys);
-                (this.props.entity as IAnimatable).animations?.push(animation);
+                    animation.setKeys(keys);
+                    (this.props.entity as IAnimatable).animations?.push(animation);
+                }
 
 
             } else {
             } else {
                 this.setState({ notification: `The property "${this.state.animationTargetProperty}" is not a "${this.state.animationType}" type` });
                 this.setState({ notification: `The property "${this.state.animationTargetProperty}" is not a "${this.state.animationType}" type` });
@@ -1169,6 +1197,20 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         }
         }
     }
     }
 
 
+    updateFrameInKeyFrame(frame: number, index: number){
+        
+        if (this.state && this.state.selected){
+            let animation = this.state.selected;
+            let keys = [...animation.getKeys()];
+
+            keys[index].frame = frame;
+
+            animation.setKeys(keys);
+
+            this.selectAnimation(animation);
+        }
+    }
+
     render() {
     render() {
         return (
         return (
             <div id="animation-curve-editor">
             <div id="animation-curve-editor">
@@ -1281,7 +1323,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                         </div>
                         </div>
                     </div>
                     </div>
                     <div className="row">
                     <div className="row">
-                        <Timeline currentFrame={this.state.currentFrame} onCurrentFrameChange={(frame: number) => this.changeCurrentFrame(frame)} keyframes={this.state.selected && this.state.selected.getKeys()} selected={this.state.selected && this.state.selected.getKeys()[0]}></Timeline>
+                        <Timeline currentFrame={this.state.currentFrame} dragKeyframe={(frame: number, index:number) => this.updateFrameInKeyFrame(frame, index)} onCurrentFrameChange={(frame: number) => this.changeCurrentFrame(frame)} keyframes={this.state.selected && this.state.selected.getKeys()} selected={this.state.selected && this.state.selected.getKeys()[0]}></Timeline>
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>

+ 84 - 20
inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

@@ -9,18 +9,21 @@ interface ITimelineProps {
     selected: IAnimationKey | null;
     selected: IAnimationKey | null;
     currentFrame: number;
     currentFrame: number;
     onCurrentFrameChange: (frame: number) => void;
     onCurrentFrameChange: (frame: number) => void;
+    dragKeyframe: (frame: number, index: number) => void;
 }
 }
 
 
 
 
-export class Timeline extends React.Component<ITimelineProps, { selected: IAnimationKey }>{
+export class Timeline extends React.Component<ITimelineProps, { selected: IAnimationKey, activeKeyframe: number | null }>{
     readonly _frames: object[] = Array(300).fill({});
     readonly _frames: object[] = Array(300).fill({});
     private _scrollable: React.RefObject<HTMLDivElement>;
     private _scrollable: React.RefObject<HTMLDivElement>;
+    private _direction: number;
     constructor(props: ITimelineProps) {
     constructor(props: ITimelineProps) {
         super(props);
         super(props);
-        if (this.props.selected !== null){
-        this.state = { selected: this.props.selected };
+        if (this.props.selected !== null) {
+            this.state = { selected: this.props.selected, activeKeyframe: null };
         }
         }
         this._scrollable = React.createRef();
         this._scrollable = React.createRef();
+        this._direction = 0;
     }
     }
 
 
     handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
     handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
@@ -44,42 +47,103 @@ export class Timeline extends React.Component<ITimelineProps, { selected: IAnima
 
 
     nextKeyframe(event: React.MouseEvent<HTMLDivElement>) {
     nextKeyframe(event: React.MouseEvent<HTMLDivElement>) {
         event.preventDefault();
         event.preventDefault();
-        if (this.props.keyframes !== null){
-        let first = this.props.keyframes.find(kf => kf.frame > this.props.currentFrame);
-        if (first) {
-            this.props.onCurrentFrameChange(first.frame);
-            this.setState({ selected: first });
-            (this._scrollable.current as HTMLDivElement).scrollLeft = first.frame * 5;
+        if (this.props.keyframes !== null) {
+            let first = this.props.keyframes.find(kf => kf.frame > this.props.currentFrame);
+            if (first) {
+                this.props.onCurrentFrameChange(first.frame);
+                this.setState({ selected: first });
+                (this._scrollable.current as HTMLDivElement).scrollLeft = first.frame * 5;
+            }
         }
         }
     }
     }
-    }
 
 
     previousKeyframe(event: React.MouseEvent<HTMLDivElement>) {
     previousKeyframe(event: React.MouseEvent<HTMLDivElement>) {
         event.preventDefault();
         event.preventDefault();
-        if (this.props.keyframes !== null){
-        let first = this.props.keyframes.find(kf => kf.frame < this.props.currentFrame);
-        if (first) {
-            this.props.onCurrentFrameChange(first.frame);
-            this.setState({ selected: first });
-            (this._scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * 5);
+        if (this.props.keyframes !== null) {
+            let first = this.props.keyframes.find(kf => kf.frame < this.props.currentFrame);
+            if (first) {
+                this.props.onCurrentFrameChange(first.frame);
+                this.setState({ selected: first });
+                (this._scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * 5);
+            }
         }
         }
     }
     }
+
+    dragStart(e: React.TouchEvent<SVGSVGElement>): void;
+    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    dragStart(e: any): void {
+        e.preventDefault();
+        this.setState({ activeKeyframe: parseInt(e.target.id.replace('kf_', '')) });
+        this._direction = e.clientX;
+
+    }
+
+    drag(e: React.TouchEvent<SVGSVGElement>): void;
+    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    drag(e: any): void {
+        e.preventDefault();
+        if (this.props.keyframes) {
+            if (this.state.activeKeyframe === parseInt(e.target.id.replace('kf_', ''))) {
+                let updatedKeyframe = this.props.keyframes[this.state.activeKeyframe];
+                if (this._direction > e.clientX) {
+                    console.log(`Dragging left ${this.state.activeKeyframe}`);
+                    let used = this.isFrameBeingUsed(updatedKeyframe.frame - 1, -1);
+                    if (used){
+                        updatedKeyframe.frame = used
+                    } 
+                    
+                } else {
+                    console.log(`Dragging Right ${this.state.activeKeyframe}`)
+                    let used = this.isFrameBeingUsed(updatedKeyframe.frame + 1, 1);
+                    if (used){
+                        updatedKeyframe.frame = used
+                    } 
+                }
+
+                this.props.dragKeyframe(updatedKeyframe.frame, this.state.activeKeyframe);
+
+            }
+        }
+    }
+
+    isFrameBeingUsed(frame: number, direction: number){
+        let used = this.props.keyframes?.find(kf => kf.frame === frame);
+        if (used){
+            this.isFrameBeingUsed(used.frame + direction, direction);
+            return false;
+        } else {
+            return frame;
+        }
+    }
+
+    dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
+    dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    dragEnd(e: any): void {
+        e.preventDefault();
+        this._direction = 0;
+        this.setState({ activeKeyframe: null })
     }
     }
 
 
     render() {
     render() {
         return (
         return (
             <>
             <>
                 <div className="timeline">
                 <div className="timeline">
-                    <div ref={this._scrollable} className="display-line">
-                        <svg viewBox="0 0 2010 100" style={{ width: 2000 }}>
+                    <div ref={this._scrollable} className="display-line" >
+                        <svg viewBox="0 0 2010 100" style={{ width: 2000 }} onMouseMove={(e) => this.drag(e)}
+                                        onTouchMove={(e) => this.drag(e)}
+                                        onTouchStart={(e) => this.dragStart(e)}
+                                        onTouchEnd={(e) => this.dragEnd(e)}
+                                        onMouseDown={(e) => this.dragStart(e)}
+                                        onMouseUp={(e) => this.dragEnd(e)}
+                                        onMouseLeave={(e) => this.dragEnd(e)}>
 
 
                             <line x1={this.props.currentFrame * 10} y1="10" x2={this.props.currentFrame * 10} y2="20" style={{ stroke: '#12506b', strokeWidth: 6 }} />
                             <line x1={this.props.currentFrame * 10} y1="10" x2={this.props.currentFrame * 10} y2="20" style={{ stroke: '#12506b', strokeWidth: 6 }} />
 
 
                             {
                             {
                                 this.props.keyframes && this.props.keyframes.map((kf, i) => {
                                 this.props.keyframes && this.props.keyframes.map((kf, i) => {
 
 
-                                    return <svg key={`kf_${i}`}>
-                                        <line x1={kf.frame * 10} y1="10" x2={kf.frame * 10} y2="20" style={{ stroke: 'red', strokeWidth: 6 }} />
+                                    return <svg key={`kf_${i}`} style={{ cursor: 'pointer' }} tabIndex={i + 40} >
+                                        <line id={`kf_${i.toString()}`} x1={kf.frame * 10} y1="10" x2={kf.frame * 10} y2="20" style={{ stroke: 'red', strokeWidth: 6 }} />
                                     </svg>
                                     </svg>
 
 
                                 })
                                 })