Browse Source

Merge pull request #8464 from toledoal/panning

Panning Fixes
sebavan 5 years ago
parent
commit
4678f429d5

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

@@ -9,7 +9,7 @@
 - Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added SubSurfaceScattering on PBR materials ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added SubSurfaceScattering on PBR materials ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
 - Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
-- Added Curve editor to manage entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
+- Added Curve editor to manage selected entity's animations and edit animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
 - Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 - Added support 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))
 - 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))
 
 

+ 16 - 19
inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx

@@ -351,17 +351,6 @@ export class AddAnimation extends React.Component<
         style={{ display: this.props.isOpen ? 'block' : 'none' }}
         style={{ display: this.props.isOpen ? 'block' : 'none' }}
       >
       >
         <div className='sub-content'>
         <div className='sub-content'>
-          {this.state.isUpdating ? null : (
-            <div className='label-input'>
-              <label>Target Path</label>
-              <input
-                type='text'
-                value={this.state.animationTargetPath}
-                onChange={(e) => this.handlePathChange(e)}
-                disabled
-              ></input>
-            </div>
-          )}
           <div className='label-input'>
           <div className='label-input'>
             <label>Display Name</label>
             <label>Display Name</label>
             <input
             <input
@@ -370,14 +359,16 @@ export class AddAnimation extends React.Component<
               onChange={(e) => this.handleNameChange(e)}
               onChange={(e) => this.handleNameChange(e)}
             ></input>
             ></input>
           </div>
           </div>
-          <div className='label-input'>
-            <label>Property</label>
-            <input
-              type='text'
-              value={this.state.animationTargetProperty}
-              onChange={(e) => this.handlePropertyChange(e)}
-            ></input>
-          </div>
+          {this.state.isUpdating ? null : (
+            <div className='label-input'>
+              <label>Property</label>
+              <input
+                type='text'
+                value={this.state.animationTargetProperty}
+                onChange={(e) => this.handlePropertyChange(e)}
+              ></input>
+            </div>
+          )}
           {this.state.isUpdating ? null : (
           {this.state.isUpdating ? null : (
             <div className='label-input'>
             <div className='label-input'>
               <label>Type</label>
               <label>Type</label>
@@ -421,6 +412,12 @@ export class AddAnimation extends React.Component<
                   : () => this.addAnimation()
                   : () => this.addAnimation()
               }
               }
             />
             />
+            {this.props.entity.animations?.length !== 0 ? (
+              <ButtonLineComponent
+                label={'Cancel'}
+                onClick={this.props.close}
+              />
+            ) : null}
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>

+ 149 - 43
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -69,6 +69,9 @@ export class AnimationCurveEditorComponent extends React.Component<
     selectedCoordinate: number;
     selectedCoordinate: number;
     animationLimit: number;
     animationLimit: number;
     fps: number;
     fps: number;
+    isLooping: boolean;
+    panningY: number;
+    panningX: number;
   }
   }
 > {
 > {
   private _snippetUrl = 'https://snippet.babylonjs.com';
   private _snippetUrl = 'https://snippet.babylonjs.com';
@@ -136,6 +139,8 @@ export class AnimationCurveEditorComponent extends React.Component<
           : initialPathData;
           : initialPathData;
     }
     }
 
 
+    this._canvasLength = 240;
+
     // 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 = {
@@ -152,9 +157,7 @@ export class AnimationCurveEditorComponent extends React.Component<
         ? this._graphCanvas.current.children[0].clientWidth /
         ? this._graphCanvas.current.children[0].clientWidth /
           (this._canvasLength * 10)
           (this._canvasLength * 10)
         : 0,
         : 0,
-      frameAxisLength: new Array(this._canvasLength).fill(0).map((s, i) => {
-        return { value: i * 10, label: i };
-      }),
+      frameAxisLength: this.setFrameAxis(this._canvasLength),
       valueAxisLength: new Array(10).fill(0).map((s, i) => {
       valueAxisLength: new Array(10).fill(0).map((s, i) => {
         return { value: i * 10, label: valueInd[i] };
         return { value: i * 10, label: valueInd[i] };
       }),
       }),
@@ -165,8 +168,11 @@ export class AnimationCurveEditorComponent extends React.Component<
       isPlaying: this.isAnimationPlaying(),
       isPlaying: this.isAnimationPlaying(),
       selectedPathData: initialPathData,
       selectedPathData: initialPathData,
       selectedCoordinate: 0,
       selectedCoordinate: 0,
-      animationLimit: 120,
+      animationLimit: this._canvasLength / 2,
       fps: 60,
       fps: 60,
+      isLooping: true,
+      panningY: 0,
+      panningX: 0,
     };
     };
   }
   }
 
 
@@ -195,12 +201,41 @@ export class AnimationCurveEditorComponent extends React.Component<
     } else {
     } else {
       scaleX = this.state.scale + 0.01;
       scaleX = this.state.scale + 0.01;
     }
     }
+    console.log(scaleX);
+    //this.setState({ scale: scaleX }, this.setAxesLength);
+  }
+
+  setFrameAxis(currentLength: number) {
+    let halfNegative = new Array(currentLength / 2).fill(0).map((s, i) => {
+      return { value: -i * 10, label: -i };
+    });
+
+    let halfPositive = new Array(currentLength / 2).fill(0).map((s, i) => {
+      return { value: i * 10, label: i };
+    });
+
+    return [...halfNegative, ...halfPositive];
+  }
+
+  setValueLines() {
+    const initialValues = new Array(10).fill(0).map((s, i) => {
+      return { value: i * 10, label: -i };
+    });
+
+    const valueHeight = Math.abs(Math.round(this.state.panningY / 10));
+    const sign = Math.sign(this.state.panningY);
 
 
-    this.setState({ scale: scaleX }, this.setAxesLength);
+    const pannedValues = new Array(valueHeight).fill(0).map((s, i) => {
+      return sign === -1
+        ? { value: -i * 10, label: i }
+        : { value: (i + 10) * 10, label: (i + 10) * -1 };
+    });
+
+    return [...initialValues, ...pannedValues];
   }
   }
 
 
   setAxesLength() {
   setAxesLength() {
-    let length = 20;
+    let length = this.state.animationLimit * 2;
     let newlength = Math.round(this._canvasLength * this.state.scale);
     let newlength = Math.round(this._canvasLength * this.state.scale);
     if (!isNaN(newlength) || newlength !== undefined) {
     if (!isNaN(newlength) || newlength !== undefined) {
       length = newlength;
       length = newlength;
@@ -216,6 +251,18 @@ export class AnimationCurveEditorComponent extends React.Component<
 
 
     let valueLines = Math.round((this.state.scale * this._heightScale) / 10);
     let valueLines = Math.round((this.state.scale * this._heightScale) / 10);
 
 
+    let halfNegative = new Array(length / 2).fill(0).map((s, i) => {
+      return { value: -i * 10, label: -i };
+    });
+
+    let halfPositive = new Array(length / 2).fill(0).map((s, i) => {
+      return { value: i * 10, label: i };
+    });
+
+    let mixed = [...halfNegative, ...halfPositive];
+
+    console.log(mixed);
+
     let newFrameLength = new Array(length).fill(0).map((s, i) => {
     let newFrameLength = new Array(length).fill(0).map((s, i) => {
       return { value: i * 10, label: i };
       return { value: i * 10, label: i };
     });
     });
@@ -511,29 +558,53 @@ export class AnimationCurveEditorComponent extends React.Component<
    */
    */
   handleFrameChange(event: React.ChangeEvent<HTMLInputElement>) {
   handleFrameChange(event: React.ChangeEvent<HTMLInputElement>) {
     event.preventDefault();
     event.preventDefault();
-    this.changeCurrentFrame(parseInt(event.target.value));
+    let frame = 0;
+
+    if (
+      isNaN(event.target.valueAsNumber) ||
+      event.target.value.indexOf('.') !== -1
+    ) {
+      this.setState({
+        notification: 'Frame input only accepts integer values',
+      });
+    } else {
+      frame = parseInt(event.target.value);
+      this.changeCurrentFrame(frame);
+    }
   }
   }
 
 
   handleValueChange(event: React.ChangeEvent<HTMLInputElement>) {
   handleValueChange(event: React.ChangeEvent<HTMLInputElement>) {
     event.preventDefault();
     event.preventDefault();
-    this.setState({ currentValue: parseFloat(event.target.value) }, () => {
-      if (this.state.selected !== null) {
-        let animation = this.state.selected;
-        let keys = animation.getKeys();
-
-        let isKeyframe = keys.find((k) => k.frame === this.state.currentFrame);
-        if (isKeyframe) {
-          let updatedKeys = keys.map((k) => {
-            if (k.frame === this.state.currentFrame) {
-              k.value = this.state.currentValue;
+
+    if (isNaN(event.target.valueAsNumber)) {
+      this.setState({
+        notification: 'Value input only numeric values',
+      });
+    } else {
+      this.setState(
+        { currentValue: parseFloat(parseFloat(event.target.value).toFixed(3)) },
+        () => {
+          if (this.state.selected !== null) {
+            let animation = this.state.selected;
+            let keys = animation.getKeys();
+
+            let isKeyframe = keys.find(
+              (k) => k.frame === this.state.currentFrame
+            );
+            if (isKeyframe) {
+              let updatedKeys = keys.map((k) => {
+                if (k.frame === this.state.currentFrame) {
+                  k.value = this.state.currentValue;
+                }
+                return k;
+              });
+              this.state.selected.setKeys(updatedKeys);
+              this.selectAnimation(animation);
             }
             }
-            return k;
-          });
-          this.state.selected.setKeys(updatedKeys);
-          this.selectAnimation(animation);
+          }
         }
         }
-      }
-    });
+      );
+    }
   }
   }
 
 
   setFlatTangent() {
   setFlatTangent() {
@@ -1387,12 +1458,15 @@ export class AnimationCurveEditorComponent extends React.Component<
       if (this.props.entity instanceof TargetedAnimation) {
       if (this.props.entity instanceof TargetedAnimation) {
         target = this.props.entity.target;
         target = this.props.entity.target;
       }
       }
-      if (this.state.isPlaying) {
+      if (this.state.isPlaying && direction === 0) {
         this.props.scene.stopAnimation(target);
         this.props.scene.stopAnimation(target);
         this.setState({ isPlaying: false });
         this.setState({ isPlaying: false });
         this._isPlaying = false;
         this._isPlaying = false;
         this.forceUpdate();
         this.forceUpdate();
       } else {
       } else {
+        if (this.state.isPlaying) {
+          this.props.scene.stopAnimation(target);
+        }
         let keys = this.state.selected.getKeys();
         let keys = this.state.selected.getKeys();
         let firstFrame = keys[0].frame;
         let firstFrame = keys[0].frame;
         let LastFrame = keys[keys.length - 1].frame;
         let LastFrame = keys[keys.length - 1].frame;
@@ -1401,7 +1475,8 @@ export class AnimationCurveEditorComponent extends React.Component<
             target,
             target,
             firstFrame,
             firstFrame,
             LastFrame,
             LastFrame,
-            true
+            this.state.isLooping,
+            this.state.fps
           );
           );
         }
         }
         if (direction === -1) {
         if (direction === -1) {
@@ -1409,7 +1484,8 @@ export class AnimationCurveEditorComponent extends React.Component<
             target,
             target,
             LastFrame,
             LastFrame,
             firstFrame,
             firstFrame,
-            true
+            this.state.isLooping,
+            this.state.fps
           );
           );
         }
         }
         this._isPlaying = true;
         this._isPlaying = true;
@@ -1508,6 +1584,9 @@ export class AnimationCurveEditorComponent extends React.Component<
               setFps={(fps: number) => {
               setFps={(fps: number) => {
                 this.setState({ fps: fps });
                 this.setState({ fps: fps });
               }}
               }}
+              setIsLooping={() => {
+                this.setState({ isLooping: !this.state.isLooping });
+              }}
             />
             />
 
 
             <div
             <div
@@ -1535,6 +1614,12 @@ export class AnimationCurveEditorComponent extends React.Component<
                     updatedSvgKeyFrame: IKeyframeSvgPoint,
                     updatedSvgKeyFrame: IKeyframeSvgPoint,
                     id: string
                     id: string
                   ) => this.renderPoints(updatedSvgKeyFrame, id)}
                   ) => this.renderPoints(updatedSvgKeyFrame, id)}
+                  panningY={(panningY: number) => {
+                    this.setState({ panningY: panningY });
+                  }}
+                  panningX={(panningX: number) => {
+                    this.setState({ panningX: panningX });
+                  }}
                 >
                 >
                   {/* Multiple Curves  */}
                   {/* Multiple Curves  */}
                   {this.state.selectedPathData?.map((curve, i) => (
                   {this.state.selectedPathData?.map((curve, i) => (
@@ -1552,17 +1637,7 @@ export class AnimationCurveEditorComponent extends React.Component<
                     ></path>
                     ></path>
                   ))}
                   ))}
 
 
-                  <svg>
-                    <rect
-                      x='-4%'
-                      y='0%'
-                      width='4%'
-                      height='101%'
-                      fill='#222'
-                    ></rect>
-                  </svg>
-
-                  {this.state.valueAxisLength.map((f, i) => {
+                  {/* {this.state.valueAxisLength.map((f, i) => {
                     return (
                     return (
                       <svg key={i}>
                       <svg key={i}>
                         <text
                         <text
@@ -1574,23 +1649,54 @@ export class AnimationCurveEditorComponent extends React.Component<
                         >
                         >
                           {f.label.toFixed(1)}
                           {f.label.toFixed(1)}
                         </text>
                         </text>
-                        <line x1='0' y1={f.value} x2='105%' y2={f.value}></line>
+                        <line
+                          x1={-((this.state.frameAxisLength.length * 10) / 2)}
+                          y1={f.value}
+                          x2={this.state.frameAxisLength.length * 10}
+                          y2={f.value}
+                        ></line>
                       </svg>
                       </svg>
                     );
                     );
+                  })} */}
+
+                  {this.setValueLines().map((line, i) => {
+                    return (
+                      <text
+                        x={this.state.panningX - 5}
+                        y={line.value}
+                        dx='0'
+                        dy='1'
+                        style={{ fontSize: `${0.2 * this.state.scale}em` }}
+                      >
+                        {line.label}
+                      </text>
+                    );
+                  })}
+
+                  {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>
+                    );
                   })}
                   })}
 
 
                   <rect
                   <rect
                     onClick={(e) => this.moveFrameTo(e)}
                     onClick={(e) => this.moveFrameTo(e)}
-                    x='0%'
-                    y='91%'
-                    width='105%'
+                    x={-((this.state.frameAxisLength.length * 10) / 2)}
+                    y={90 + this.state.panningY + '%'}
+                    width={this.state.frameAxisLength.length * 10}
                     height='10%'
                     height='10%'
                     fill='#222'
                     fill='#222'
                     style={{ cursor: 'pointer' }}
                     style={{ cursor: 'pointer' }}
                   ></rect>
                   ></rect>
 
 
                   {this.state.frameAxisLength.map((f, i) => (
                   {this.state.frameAxisLength.map((f, i) => (
-                    <svg key={i} x='0' y='96%'>
+                    <svg key={i} x='0' y={96 + this.state.panningY + '%'}>
                       <text
                       <text
                         x={f.value}
                         x={f.value}
                         y='0'
                         y='0'
@@ -1604,7 +1710,7 @@ export class AnimationCurveEditorComponent extends React.Component<
                       {f.value % this.state.fps === 0 && f.value !== 0 ? (
                       {f.value % this.state.fps === 0 && f.value !== 0 ? (
                         <line
                         <line
                           x1={f.value}
                           x1={f.value}
-                          y1='-100'
+                          y1='-100%'
                           x2={f.value}
                           x2={f.value}
                           y2='5%'
                           y2='5%'
                         ></line>
                         ></line>

+ 182 - 264
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx

@@ -42,22 +42,27 @@ export enum SelectedCoordinate {
   height = 1,
   height = 1,
 }
 }
 
 
+interface ItemCoordinate {
+  id: string;
+  color: string;
+  coordinate: SelectedCoordinate;
+}
+
 export class AnimationListTree extends React.Component<
 export class AnimationListTree extends React.Component<
   IAnimationListTreeProps,
   IAnimationListTreeProps,
   {
   {
     selectedCoordinate: SelectedCoordinate;
     selectedCoordinate: SelectedCoordinate;
     selectedAnimation: number;
     selectedAnimation: number;
+    animationList: Item[] | null;
   }
   }
 > {
 > {
-  private _list: Item[] | null;
   constructor(props: IAnimationListTreeProps) {
   constructor(props: IAnimationListTreeProps) {
     super(props);
     super(props);
 
 
-    this._list = this.generateList();
-
     this.state = {
     this.state = {
       selectedCoordinate: 0,
       selectedCoordinate: 0,
       selectedAnimation: 0,
       selectedAnimation: 0,
+      animationList: this.generateList(),
     };
     };
   }
   }
 
 
@@ -76,7 +81,7 @@ export class AnimationListTree extends React.Component<
           Animation[]
           Animation[]
         >;
         >;
         this.props.deselectAnimation();
         this.props.deselectAnimation();
-        this._list = this.generateList();
+        this.setState({ animationList: this.generateList() });
       }
       }
     }
     }
   }
   }
@@ -100,9 +105,14 @@ export class AnimationListTree extends React.Component<
   }
   }
 
 
   toggleProperty(index: number) {
   toggleProperty(index: number) {
-    if (this._list !== null) {
-      let item = this._list[index];
-      item.open = !item.open;
+    if (this.state.animationList) {
+      const updated = this.state.animationList.map((a) => {
+        if (a.index === index) {
+          a.open = !a.open;
+        }
+        return a;
+      });
+      this.setState({ animationList: updated });
     }
     }
   }
   }
 
 
@@ -115,265 +125,173 @@ export class AnimationListTree extends React.Component<
     this.props.selectAnimation(animation, SelectedCoordinate.x);
     this.props.selectAnimation(animation, SelectedCoordinate.x);
   }
   }
 
 
-  setListItem(animation: Animation, i: number) {
-    let element;
-    this._list = this.generateList();
+  coordinateItem(
+    i: number,
+    animation: Animation,
+    coordinate: string,
+    color: string,
+    selectedCoordinate: SelectedCoordinate
+  ) {
+    return (
+      <li
+        key={`${i}_${coordinate}`}
+        id={`${i}_${coordinate}`}
+        className='property'
+        style={{ color: color }}
+        onClick={() =>
+          this.setSelectedCoordinate(animation, selectedCoordinate, i)
+        }
+      >
+        <div
+          className={`handle-indicator ${
+            this.state.selectedCoordinate === selectedCoordinate &&
+            this.state.selectedAnimation === i
+              ? 'show'
+              : 'hide'
+          }`}
+        ></div>
+        {animation.targetProperty} {coordinate.toUpperCase()}
+      </li>
+    );
+  }
 
 
-    if (this._list !== null) {
-      switch (animation.dataType) {
-        case Animation.ANIMATIONTYPE_FLOAT:
-          element = (
-            <li
-              className={
-                this.props.selected &&
-                this.props.selected.name === animation.name
-                  ? 'property active'
-                  : 'property'
-              }
-              key={i}
-              onClick={() => this.props.selectAnimation(animation)}
-            >
-              <div className={`animation-bullet`}></div>
-              <p>{animation.targetProperty}</p>
-              <IconButtonLineComponent
-                tooltip='Options'
-                icon='small animation-options'
-                onClick={() => this.props.editAnimation(animation)}
-              />
-              {!(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>
-          );
-          break;
-        case Animation.ANIMATIONTYPE_VECTOR2:
-          element = (
-            <li
-              className={
-                this.props.selected &&
-                this.props.selected.name === animation.name
-                  ? 'property active'
-                  : 'property'
-              }
-              key={i}
-              onClick={() => this.props.selectAnimation(animation)}
-            >
-              <p>{animation.targetProperty}</p>
-              <ul>
-                <li key={`${i}_x`}>
-                  Property <strong>X</strong>
-                </li>
-                <li key={`${i}_y`}>
-                  Property <strong>Y</strong>
-                </li>
-              </ul>
-            </li>
-          );
-          break;
-        case Animation.ANIMATIONTYPE_VECTOR3:
-          element = (
-            <li
-              className={
-                this.props.selected &&
-                this.props.selected.name === animation.name
-                  ? 'property sub active'
-                  : 'property sub'
-              }
-              key={i}
-            >
-              <div
-                className={`animation-arrow ${
-                  this._list[i].open ? '' : 'flip'
-                }`}
-                onClick={() => this.toggleProperty(i)}
-              ></div>
-              <p onClick={() => this.props.selectAnimation(animation)}>
-                {animation.targetProperty}
-              </p>
-              <IconButtonLineComponent
-                tooltip='Options'
-                icon='small animation-options'
-                onClick={() => this.props.editAnimation(animation)}
-              />
-              {!(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}
-              <ul className={`sub-list ${this._list[i].open ? '' : 'hidden'}`}>
-                <li
-                  key={`${i}_x`}
-                  id={`${i}_x`}
-                  className='property'
-                  style={{ color: '#db3e3e' }}
-                  onClick={() =>
-                    this.setSelectedCoordinate(
-                      animation,
-                      SelectedCoordinate.x,
-                      i
-                    )
-                  }
-                >
-                  <div
-                    className={`handle-indicator ${
-                      this.state.selectedCoordinate === SelectedCoordinate.x &&
-                      this.state.selectedAnimation === i
-                        ? 'show'
-                        : 'hide'
-                    }`}
-                  ></div>
-                  {animation.targetProperty} X
-                </li>
-                <li
-                  key={`${i}_y`}
-                  id={`${i}_y`}
-                  className='property'
-                  style={{ color: '#51e22d' }}
-                  onClick={() =>
-                    this.setSelectedCoordinate(
-                      animation,
-                      SelectedCoordinate.y,
-                      i
-                    )
-                  }
-                >
-                  <div
-                    className={`handle-indicator ${
-                      this.state.selectedCoordinate === SelectedCoordinate.y &&
-                      this.state.selectedAnimation === i
-                        ? 'show'
-                        : 'hide'
-                    }`}
-                  ></div>
-                  {animation.targetProperty} Y
-                </li>
-                <li
-                  key={`${i}_z`}
-                  id={`${i}_z`}
-                  className='property'
-                  style={{ color: '#00a3ff' }}
-                  onClick={() =>
-                    this.setSelectedCoordinate(
-                      animation,
-                      SelectedCoordinate.z,
-                      i
-                    )
-                  }
-                >
-                  <div
-                    className={`handle-indicator ${
-                      this.state.selectedCoordinate === SelectedCoordinate.z &&
-                      this.state.selectedAnimation === i
-                        ? 'show'
-                        : 'hide'
-                    }`}
-                  ></div>
-                  {animation.targetProperty} Z
-                </li>
-              </ul>
-            </li>
-          );
-          break;
-        case Animation.ANIMATIONTYPE_QUATERNION:
-          element = (
-            <li className='property' key={i}>
-              <p>{animation.targetProperty}</p>
-              <ul>
-                <li key={`${i}_x`}>
-                  Property <strong>X</strong>
-                </li>
-                <li key={`${i}_y`}>
-                  Property <strong>Y</strong>
-                </li>
-                <li key={`${i}_z`}>
-                  Property <strong>Z</strong>
-                </li>
-                <li key={`${i}_w`}>
-                  Property <strong>W</strong>
-                </li>
-              </ul>
-            </li>
-          );
-          break;
-        case Animation.ANIMATIONTYPE_COLOR3:
-          element = (
-            <li className='property' key={i}>
-              <p>{animation.targetProperty}</p>
-              <ul>
-                <li key={`${i}_r`}>
-                  Property <strong>R</strong>
-                </li>
-                <li key={`${i}_g`}>
-                  Property <strong>G</strong>
-                </li>
-                <li key={`${i}_b`}>
-                  Property <strong>B</strong>
-                </li>
-              </ul>
-            </li>
-          );
-          break;
-        case Animation.ANIMATIONTYPE_COLOR4:
-          element = (
-            <li className='property' key={i}>
-              <p>{animation.targetProperty}</p>
-              <ul>
-                <li key={`${i}_r`}>
-                  Property <strong>R</strong>
-                </li>
-                <li key={`${i}_g`}>
-                  Property <strong>G</strong>
-                </li>
-                <li key={`${i}_b`}>
-                  Property <strong>B</strong>
-                </li>
-                <li key={`${i}_a`}>
-                  Property <strong>A</strong>
-                </li>
-              </ul>
-            </li>
-          );
-          break;
-        case Animation.ANIMATIONTYPE_SIZE:
-          element = (
-            <li className='property' key={i}>
-              <p>{animation.targetProperty}</p>
-              <ul>
-                <li key={`${i}_width`}>
-                  Property <strong>Width</strong>
-                </li>
-                <li key={`${i}_height`}>
-                  Property <strong>Height</strong>
-                </li>
-              </ul>
-            </li>
-          );
-          break;
-        default:
-          console.log('not recognized');
-          element = null;
-          break;
-      }
+  typeAnimationItem(
+    animation: Animation,
+    i: number,
+    childrenElements: ItemCoordinate[]
+  ) {
+    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={() => this.toggleProperty(i)}
+        ></div>
+        <p onClick={() => this.props.selectAnimation(animation)}>
+          {animation.targetProperty}
+        </p>
+        <IconButtonLineComponent
+          tooltip='Options'
+          icon='small animation-options'
+          onClick={() => this.props.editAnimation(animation)}
+        />
+        {!(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}
+        <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>
+    );
+  }
 
 
-      return element;
-    } else {
-      return null;
+  setListItem(animation: Animation, i: number) {
+    switch (animation.dataType) {
+      case Animation.ANIMATIONTYPE_FLOAT:
+        return (
+          <li
+            className={
+              this.props.selected && this.props.selected.name === animation.name
+                ? 'property active'
+                : 'property'
+            }
+            key={i}
+            onClick={() => this.props.selectAnimation(animation)}
+          >
+            <div className={`animation-bullet`}></div>
+            <p>{animation.targetProperty}</p>
+            <IconButtonLineComponent
+              tooltip='Options'
+              icon='small animation-options'
+              onClick={() => this.props.editAnimation(animation)}
+            />
+            {!(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:
+        return this.typeAnimationItem(animation, i, [
+          { id: 'x', color: '#db3e3e', coordinate: SelectedCoordinate.x },
+          { id: 'y', color: '#51e22d', coordinate: SelectedCoordinate.y },
+        ]);
+      case Animation.ANIMATIONTYPE_VECTOR3:
+        return this.typeAnimationItem(animation, i, [
+          { id: 'x', color: '#db3e3e', coordinate: SelectedCoordinate.x },
+          { id: 'y', color: '#51e22d', coordinate: SelectedCoordinate.y },
+          { id: 'z', color: '#00a3ff', coordinate: SelectedCoordinate.z },
+        ]);
+      case Animation.ANIMATIONTYPE_QUATERNION:
+        return this.typeAnimationItem(animation, i, [
+          { id: 'x', color: '#db3e3e', coordinate: SelectedCoordinate.x },
+          { id: 'y', color: '#51e22d', coordinate: SelectedCoordinate.y },
+          { id: 'z', color: '#00a3ff', coordinate: SelectedCoordinate.z },
+          { id: 'w', color: '#7a4ece', coordinate: SelectedCoordinate.w },
+        ]);
+      case Animation.ANIMATIONTYPE_COLOR3:
+        return this.typeAnimationItem(animation, i, [
+          { id: 'r', color: '#db3e3e', coordinate: SelectedCoordinate.r },
+          { id: 'g', color: '#51e22d', coordinate: SelectedCoordinate.g },
+          { id: 'b', color: '#00a3ff', coordinate: SelectedCoordinate.b },
+        ]);
+      case Animation.ANIMATIONTYPE_COLOR4:
+        return this.typeAnimationItem(animation, i, [
+          { id: 'r', color: '#db3e3e', coordinate: SelectedCoordinate.r },
+          { id: 'g', color: '#51e22d', coordinate: SelectedCoordinate.g },
+          { id: 'b', color: '#00a3ff', coordinate: SelectedCoordinate.b },
+          { id: 'a', color: '#7a4ece', coordinate: SelectedCoordinate.a },
+        ]);
+      case Animation.ANIMATIONTYPE_SIZE:
+        return this.typeAnimationItem(animation, i, [
+          {
+            id: 'width',
+            color: '#db3e3e',
+            coordinate: SelectedCoordinate.width,
+          },
+          {
+            id: 'height',
+            color: '#51e22d',
+            coordinate: SelectedCoordinate.height,
+          },
+        ]);
+      default:
+        console.log('not recognized');
+        return null;
     }
     }
   }
   }
 
 

+ 139 - 75
inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx

@@ -1,94 +1,158 @@
-
-import * as React from "react";
+import * as React from 'react';
 import { IAnimationKey } from 'babylonjs/Animations/animationKey';
 import { IAnimationKey } from 'babylonjs/Animations/animationKey';
 import { IconButtonLineComponent } from '../../../lines/iconButtonLineComponent';
 import { IconButtonLineComponent } from '../../../lines/iconButtonLineComponent';
 
 
 interface IControlsProps {
 interface IControlsProps {
-    keyframes: IAnimationKey[] | null;
-    selected: IAnimationKey | null;
-    currentFrame: number;
-    onCurrentFrameChange: (frame: number) => void;
-    playPause: (direction: number) => void;
-    isPlaying: boolean;
-    scrollable: React.RefObject<HTMLDivElement>
+  keyframes: IAnimationKey[] | null;
+  selected: IAnimationKey | null;
+  currentFrame: number;
+  onCurrentFrameChange: (frame: number) => void;
+  playPause: (direction: number) => void;
+  isPlaying: boolean;
+  scrollable: React.RefObject<HTMLDivElement>;
 }
 }
 
 
-export class Controls extends React.Component<IControlsProps, { selected: IAnimationKey }>{
-    constructor(props: IControlsProps) {
-        super(props);
-        if (this.props.selected !== null) {
-            this.state = { selected: this.props.selected };
-        }
+export class Controls extends React.Component<
+  IControlsProps,
+  { selected: IAnimationKey; playingType: string }
+> {
+  constructor(props: IControlsProps) {
+    super(props);
+    if (this.props.selected !== null) {
+      this.state = { selected: this.props.selected, playingType: '' };
     }
     }
+  }
 
 
-    playBackwards() {
-        this.props.playPause(-1);
-    }
+  playBackwards() {
+    this.setState({ playingType: 'reverse' });
+    this.props.playPause(-1);
+  }
 
 
-    play() {
-        this.props.playPause(1);
-    }
+  play() {
+    this.setState({ playingType: 'forward' });
+    this.props.playPause(1);
+  }
 
 
-    pause() {
-        if (this.props.isPlaying) {
-            this.props.playPause(1);
-        }
+  pause() {
+    if (this.props.isPlaying) {
+      this.setState({ playingType: '' });
+      this.props.playPause(0);
     }
     }
+  }
 
 
-    nextFrame() {
-        this.props.onCurrentFrameChange(this.props.currentFrame + 1);
-        (this.props.scrollable.current as HTMLDivElement).scrollLeft = this.props.currentFrame * 5;
-    }
+  nextFrame() {
+    this.props.onCurrentFrameChange(this.props.currentFrame + 1);
+    (this.props.scrollable.current as HTMLDivElement).scrollLeft =
+      this.props.currentFrame * 5;
+  }
 
 
-    previousFrame() {
-        if (this.props.currentFrame !== 0) {
-            this.props.onCurrentFrameChange(this.props.currentFrame - 1);
-            (this.props.scrollable.current as HTMLDivElement).scrollLeft = -(this.props.currentFrame * 5);
-        }
+  previousFrame() {
+    if (this.props.currentFrame !== 0) {
+      this.props.onCurrentFrameChange(this.props.currentFrame - 1);
+      (this.props.scrollable.current as HTMLDivElement).scrollLeft = -(
+        this.props.currentFrame * 5
+      );
     }
     }
+  }
 
 
-    nextKeyframe() {
-        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.props.scrollable.current as HTMLDivElement).scrollLeft = first.frame * 5;
-            }
-        }
+  nextKeyframe() {
+    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.props.scrollable.current as HTMLDivElement).scrollLeft =
+          first.frame * 5;
+      }
     }
     }
+  }
 
 
-    previousKeyframe() {
-        if (this.props.keyframes !== null) {
-            let keyframes = [...this.props.keyframes]
-            let first = keyframes.reverse().find(kf => kf.frame < this.props.currentFrame);
-            if (first) {
-                this.props.onCurrentFrameChange(first.frame);
-                this.setState({ selected: first });
-                (this.props.scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * 5);
-            }
-        }
+  previousKeyframe() {
+    if (this.props.keyframes !== null) {
+      let keyframes = [...this.props.keyframes];
+      let first = keyframes
+        .reverse()
+        .find((kf) => kf.frame < this.props.currentFrame);
+      if (first) {
+        this.props.onCurrentFrameChange(first.frame);
+        this.setState({ selected: first });
+        (this.props.scrollable.current as HTMLDivElement).scrollLeft = -(
+          first.frame * 5
+        );
+      }
     }
     }
+  }
 
 
-    render() {
-        return (
-            <div className="controls">
-                <IconButtonLineComponent tooltip="Animation Start" icon="animation-start" onClick={() => this.previousFrame()}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Previous Keyframe" icon="animation-lastkey" onClick={() => this.previousKeyframe()}></IconButtonLineComponent>
-                {this.props.isPlaying ?
-                    <div className="stop-container">
-                        <IconButtonLineComponent tooltip="Play Reverse" 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>
-                    </div>
-                }
-                <IconButtonLineComponent tooltip="Next Keyframe" icon="animation-nextkey" onClick={() => this.nextKeyframe()}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Animation End" icon="animation-end" onClick={() => this.nextFrame()}></IconButtonLineComponent>
-
-            </div>
-        )
-    }
-} 
+  render() {
+    return (
+      <div className='controls'>
+        <IconButtonLineComponent
+          tooltip='Animation Start'
+          icon='animation-start'
+          onClick={() => this.previousFrame()}
+        ></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='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>
+          </div>
+        )}
+        <IconButtonLineComponent
+          tooltip='Next Keyframe'
+          icon='animation-nextkey'
+          onClick={() => this.nextKeyframe()}
+        ></IconButtonLineComponent>
+        <IconButtonLineComponent
+          tooltip='Animation End'
+          icon='animation-end'
+          onClick={() => this.nextFrame()}
+        ></IconButtonLineComponent>
+      </div>
+    );
+  }
+}

+ 7 - 6
inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss

@@ -267,7 +267,6 @@
       cursor: pointer;
       cursor: pointer;
       background-position: center;
       background-position: center;
       width: 20px;
       width: 20px;
-      margin-left: 10px;
     }
     }
 
 
     &.animation-triangle {
     &.animation-triangle {
@@ -606,6 +605,8 @@
               .scrollbar {
               .scrollbar {
                 cursor: pointer;
                 cursor: pointer;
                 width: 100%;
                 width: 100%;
+                height: 66px;
+                margin-top: -10px;
               }
               }
 
 
               .left-grabber,
               .left-grabber,
@@ -949,16 +950,17 @@
 
 
           .confirm-buttons {
           .confirm-buttons {
             grid-column-start: 2;
             grid-column-start: 2;
+            column-count: 2;
 
 
             .buttonLine {
             .buttonLine {
               button {
               button {
-                width: 60px;
+                width: 52px;
                 height: 20px;
                 height: 20px;
                 background-color: rgb(68, 68, 68);
                 background-color: rgb(68, 68, 68);
                 color: white;
                 color: white;
                 font-size: 12px;
                 font-size: 12px;
                 line-height: 11px;
                 line-height: 11px;
-                margin: 5px;
+                margin: 4px;
                 font-size: 10px;
                 font-size: 10px;
                 font-family: acumin-pro-condensed;
                 font-family: acumin-pro-condensed;
                 border: none;
                 border: none;
@@ -1023,8 +1025,8 @@
               background-position-x: 18px;
               background-position-x: 18px;
               cursor: pointer;
               cursor: pointer;
               &.flip {
               &.flip {
-                transform: rotate(180deg);
-                background-position: 2px 50%;
+                transform: rotate(-90deg);
+                background-position: 10px 13px;
               }
               }
             }
             }
 
 
@@ -1208,7 +1210,6 @@
 
 
   .buttonLine {
   .buttonLine {
     height: 30px;
     height: 30px;
-    display: grid;
     align-items: center;
     align-items: center;
     justify-items: stretch;
     justify-items: stretch;
 
 

+ 4 - 17
inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx

@@ -23,6 +23,7 @@ interface IEditorControlsProps {
   setNotificationMessage: (message: string) => void;
   setNotificationMessage: (message: string) => void;
   selectAnimation: (selected: Animation, axis?: SelectedCoordinate) => void;
   selectAnimation: (selected: Animation, axis?: SelectedCoordinate) => void;
   setFps: (fps: number) => void;
   setFps: (fps: number) => void;
+  setIsLooping: () => void;
   globalState: GlobalState;
   globalState: GlobalState;
   snippetServer: string;
   snippetServer: string;
   deselectAnimation: () => void;
   deselectAnimation: () => void;
@@ -39,7 +40,6 @@ export class EditorControls extends React.Component<
     animationsCount: number;
     animationsCount: number;
     framesPerSecond: number;
     framesPerSecond: number;
     snippetId: string;
     snippetId: string;
-    loopMode: number;
     selected: Animation | undefined;
     selected: Animation | undefined;
   }
   }
 > {
 > {
@@ -53,8 +53,7 @@ export class EditorControls extends React.Component<
       isEditTabOpen: count === 0 ? false : true,
       isEditTabOpen: count === 0 ? false : true,
       isSaveTabOpen: false,
       isSaveTabOpen: false,
       isLoadTabOpen: false,
       isLoadTabOpen: false,
-      isLoopActive: false,
-      loopMode: Animation.ANIMATIONLOOPMODE_CYCLE,
+      isLoopActive: true,
       animationsCount: count,
       animationsCount: count,
       framesPerSecond: 60,
       framesPerSecond: 60,
       snippetId: '',
       snippetId: '',
@@ -83,22 +82,10 @@ export class EditorControls extends React.Component<
   }
   }
 
 
   changeLoopBehavior() {
   changeLoopBehavior() {
-    let loopMode = this.state.loopMode;
-    const animation = this.props.selected;
-    if (loopMode === 2) {
-      loopMode = Animation.ANIMATIONLOOPMODE_CYCLE;
-    } else {
-      loopMode = Animation.ANIMATIONLOOPMODE_CONSTANT;
-    }
-    if (animation) {
-      // Notify observers
-      animation.loopMode = loopMode;
-    }
-
     this.setState({
     this.setState({
       isLoopActive: !this.state.isLoopActive,
       isLoopActive: !this.state.isLoopActive,
-      loopMode: loopMode,
     });
     });
+    this.props.setIsLooping();
   }
   }
 
 
   handleTabs(tab: number) {
   handleTabs(tab: number) {
@@ -243,7 +230,7 @@ export class EditorControls extends React.Component<
           <AddAnimation
           <AddAnimation
             isOpen={this.state.isAnimationTabOpen}
             isOpen={this.state.isAnimationTabOpen}
             close={() => {
             close={() => {
-              this.setState({ isAnimationTabOpen: false });
+              this.setState({ isAnimationTabOpen: false, isEditTabOpen: true });
             }}
             }}
             entity={this.props.entity as IAnimatable}
             entity={this.props.entity as IAnimatable}
             setNotificationMessage={(message: string) => {
             setNotificationMessage={(message: string) => {

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

@@ -45,9 +45,9 @@ export class GraphActionsBar extends React.Component<IGraphActionsBarProps> {
           <div className='action-input'>
           <div className='action-input'>
             <input
             <input
               type='number'
               type='number'
-              value={this.props.currentValue.toFixed(3)}
+              value={this.props.currentValue}
               onChange={this.props.handleValueChange}
               onChange={this.props.handleValueChange}
-              step='0.001'
+              step='0.1'
             />
             />
           </div>
           </div>
           <IconButtonLineComponent
           <IconButtonLineComponent

+ 52 - 49
inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx

@@ -11,9 +11,14 @@ interface ISvgDraggableAreaProps {
   selectedControlPoint: (type: string, id: string) => void;
   selectedControlPoint: (type: string, id: string) => void;
   deselectKeyframes: () => void;
   deselectKeyframes: () => void;
   removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
   removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
+  panningY: (panningY: number) => void;
+  panningX: (panningX: number) => void;
 }
 }
 
 
-export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
+export class SvgDraggableArea extends React.Component<
+  ISvgDraggableAreaProps,
+  { panX: number; panY: number }
+> {
   private _active: boolean;
   private _active: boolean;
   private _isCurrentPointControl: string;
   private _isCurrentPointControl: string;
   private _currentPointId: string;
   private _currentPointId: string;
@@ -28,6 +33,8 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
     this._draggableArea = React.createRef();
     this._draggableArea = React.createRef();
     this._panStart = new Vector2(0, 0);
     this._panStart = new Vector2(0, 0);
     this._panStop = new Vector2(0, 0);
     this._panStop = new Vector2(0, 0);
+
+    this.state = { panX: 0, panY: 0 };
   }
   }
 
 
   componentDidMount() {
   componentDidMount() {
@@ -60,9 +67,8 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
     }
     }
 
 
     if (e.target.classList.contains('pannable')) {
     if (e.target.classList.contains('pannable')) {
-      if (e.buttons === 1 && e.shiftKey) {
-        this._panStart.set(e.clientX, e.clientY);
-      }
+      this._active = true;
+      this._panStart.set(e.clientX, e.clientY);
     }
     }
   }
   }
 
 
@@ -75,21 +81,28 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
       var coord = this.getMousePosition(e);
       var coord = this.getMousePosition(e);
 
 
       if (coord !== undefined) {
       if (coord !== undefined) {
-        var newPoints = [...this.props.keyframeSvgPoints];
+        if (e.target.classList.contains('pannable')) {
+          if (this._panStart.x !== 0 && this._panStart.y !== 0) {
+            this._panStop.set(e.clientX, e.clientY);
+            this.panDirection();
+          }
+        } else {
+          var newPoints = [...this.props.keyframeSvgPoints];
 
 
-        let point = newPoints.find((kf) => kf.id === this._currentPointId);
-        if (point) {
-          // Check for NaN values here.
-          if (this._isCurrentPointControl === 'left') {
-            point.leftControlPoint = coord;
-            point.isLeftActive = true;
-          } else if (this._isCurrentPointControl === 'right') {
-            point.rightControlPoint = coord;
-            point.isRightActive = true;
-          } else {
-            point.keyframePoint = coord;
+          let point = newPoints.find((kf) => kf.id === this._currentPointId);
+          if (point) {
+            // Check for NaN values here.
+            if (this._isCurrentPointControl === 'left') {
+              point.leftControlPoint = coord;
+              point.isLeftActive = true;
+            } else if (this._isCurrentPointControl === 'right') {
+              point.rightControlPoint = coord;
+              point.isRightActive = true;
+            } else {
+              point.keyframePoint = coord;
+            }
+            this.props.updatePosition(point, this._currentPointId);
           }
           }
-          this.props.updatePosition(point, this._currentPointId);
         }
         }
       }
       }
     }
     }
@@ -102,13 +115,8 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
     this._active = false;
     this._active = false;
     this._currentPointId = '';
     this._currentPointId = '';
     this._isCurrentPointControl = '';
     this._isCurrentPointControl = '';
-
-    if (e.target.classList.contains('pannable')) {
-      if (this._panStart.x !== 0 && this._panStart.y !== 0) {
-        this._panStop.set(e.clientX, e.clientY);
-        this.panDirection();
-      }
-    }
+    this._panStart.set(0, 0);
+    this._panStop.set(0, 0);
   }
   }
 
 
   getMousePosition(e: React.TouchEvent<SVGSVGElement>): Vector2 | undefined;
   getMousePosition(e: React.TouchEvent<SVGSVGElement>): Vector2 | undefined;
@@ -137,30 +145,20 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
   }
   }
 
 
   panDirection() {
   panDirection() {
-    // Movement Right to Left
-    if (this._panStart.x > this._panStop.x) {
-      console.log('right to left');
-      this.panTo('right', Math.abs(this._panStart.x - this._panStop.x));
-    }
+    let movementX = this._panStart.x - this._panStop.x;
+    let movementY = this._panStart.y - this._panStop.y;
 
 
-    // Movement Right to Left
-    if (this._panStart.x < this._panStop.x) {
-      this.panTo('left', Math.abs(this._panStart.x - this._panStop.x));
-      console.log('left to right');
-    }
+    let newX = this.state.panX + movementX / 20;
+    let newY = this.state.panY + movementY / 20;
 
 
-    // Movement Bottom to Up
-    if (this._panStart.y > this._panStop.y) {
-      console.log('down up');
-    }
+    this.setState({
+      panX: Math.round(newX),
+      panY: Math.round(newY),
+    });
+    this.props.panningY(Math.round(newY));
+    this.props.panningX(Math.round(newX));
 
 
-    // Movement Up to Bottom
-    if (this._panStart.y < this._panStop.y) {
-      console.log('up down');
-    }
-
-    this._panStart.set(0, 0);
-    this._panStop.set(0, 0);
+    console.log(Math.round(newX));
   }
   }
 
 
   panTo(direction: string, value: number) {
   panTo(direction: string, value: number) {
@@ -227,6 +225,11 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
     return (
     return (
       <>
       <>
         <svg
         <svg
+          style={{ width: 30, height: 364, position: 'absolute', zIndex: 1 }}
+        >
+          <rect x='0' y='0' width='30px' height='100%' fill='#ffffff1c'></rect>
+        </svg>
+        <svg
           className='linear pannable'
           className='linear pannable'
           ref={this._draggableArea}
           ref={this._draggableArea}
           tabIndex={0}
           tabIndex={0}
@@ -237,13 +240,13 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
           onMouseDown={(e) => this.dragStart(e)}
           onMouseDown={(e) => this.dragStart(e)}
           onMouseUp={(e) => this.dragEnd(e)}
           onMouseUp={(e) => this.dragEnd(e)}
           onMouseLeave={(e) => this.dragEnd(e)}
           onMouseLeave={(e) => this.dragEnd(e)}
-          // Add way to add new keyframe
           onClick={(e) => this.focus(e)}
           onClick={(e) => this.focus(e)}
-          viewBox={`0 0 ${Math.round(this.props.scale * 200)} ${Math.round(
-            this.props.scale * 100
-          )}`}
+          viewBox={`${this.state.panX} ${this.state.panY} ${Math.round(
+            this.props.scale * 200
+          )} ${Math.round(this.props.scale * 100)}`}
         >
         >
           {this.props.children}
           {this.props.children}
+
           {this.props.keyframeSvgPoints.map((keyframe, i) => (
           {this.props.keyframeSvgPoints.map((keyframe, i) => (
             <KeyframeSvgPoint
             <KeyframeSvgPoint
               key={`${keyframe.id}_${i}`}
               key={`${keyframe.id}_${i}`}

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

@@ -288,6 +288,8 @@ export class Timeline extends React.Component<
     e.preventDefault();
     e.preventDefault();
     if (e.target.className === 'scrollbar') {
     if (e.target.className === 'scrollbar') {
       this.moveScrollbar(e.pageX);
       this.moveScrollbar(e.pageX);
+    } else {
+      console.log('out');
     }
     }
 
 
     if (this._active === 'leftDraggable') {
     if (this._active === 'leftDraggable') {