Alejandro Toledo 5 年之前
父節點
當前提交
373b36133c

+ 16 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -69,6 +69,7 @@ export class AnimationCurveEditorComponent extends React.Component<
     selectedPathData: ICurveData[] | undefined;
     selectedCoordinate: number;
     animationLimit: number;
+    fps: number;
   }
 > {
   private _snippetUrl = 'https://snippet.babylonjs.com';
@@ -164,6 +165,7 @@ export class AnimationCurveEditorComponent extends React.Component<
       selectedPathData: initialPathData,
       selectedCoordinate: 0,
       animationLimit: 100,
+      fps: 0,
     };
   }
 
@@ -1435,6 +1437,9 @@ export class AnimationCurveEditorComponent extends React.Component<
               }}
               globalState={this.props.globalState}
               snippetServer={this._snippetUrl}
+              setFps={(fps: number) => {
+                this.setState({ fps: fps });
+              }}
             />
 
             <div
@@ -1477,7 +1482,7 @@ export class AnimationCurveEditorComponent extends React.Component<
                     <rect
                       x='-4%'
                       y='0%'
-                      width='5%'
+                      width='4%'
                       height='101%'
                       fill='#222'
                     ></rect>
@@ -1521,6 +1526,15 @@ export class AnimationCurveEditorComponent extends React.Component<
                         {f.value}
                       </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}
+                          y1='-100'
+                          x2={f.value}
+                          y2='5%'
+                        ></line>
+                      ) : null}
                     </svg>
                   ))}
                 </SvgDraggableArea>
@@ -1555,6 +1569,7 @@ export class AnimationCurveEditorComponent extends React.Component<
               animationLimit={this.state.animationLimit}
               keyframes={this.state.selected && this.state.selected.getKeys()}
               selected={this.state.selected && this.state.selected.getKeys()[0]}
+              fps={this.state.fps}
             ></Timeline>
           </div>
         </div>

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

@@ -540,6 +540,7 @@ body {
           margin-right: 0px;
           padding-left: 10px;
           padding-right: 10px;
+          width: 782px;
 
           &::-webkit-scrollbar {
             height: 0.4em;
@@ -559,6 +560,9 @@ body {
           width: 60px;
           margin-left: 10px;
           margin-right: 10px;
+          position: absolute;
+          right: 9px;
+          bottom: 11px;
 
           input {
             text-align: center;
@@ -609,13 +613,17 @@ body {
               .right-grabber {
                 display: flex;
                 align-items: center;
+                cursor: pointer;
+                .text {
+                  pointer-events: none;
+                }
               }
 
               .left-draggable,
               .right-draggable {
-                cursor: pointer;
                 display: flex;
                 align-items: center;
+                pointer-events: none;
                 &:active {
                   .grabber {
                     background-color: #555555;

+ 33 - 10
inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx

@@ -22,6 +22,7 @@ interface IEditorControlsProps {
   onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
   setNotificationMessage: (message: string) => void;
   selectAnimation: (selected: Animation, axis?: SelectedCoordinate) => void;
+  setFps: (fps: number) => void;
   globalState: GlobalState;
   snippetServer: string;
 }
@@ -37,6 +38,7 @@ export class EditorControls extends React.Component<
     animationsCount: number;
     framesPerSecond: number;
     snippetId: string;
+    loopMode: number;
   }
 > {
   constructor(props: IEditorControlsProps) {
@@ -50,8 +52,9 @@ export class EditorControls extends React.Component<
       isSaveTabOpen: false,
       isLoadTabOpen: false,
       isLoopActive: false,
+      loopMode: Animation.ANIMATIONLOOPMODE_CYCLE,
       animationsCount: count,
-      framesPerSecond: 60,
+      framesPerSecond: 0,
       snippetId: '',
     };
   }
@@ -69,6 +72,25 @@ export class EditorControls extends React.Component<
     return (this.props.entity as IAnimatable).animations?.length ?? 0;
   }
 
+  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({
+      isLoopActive: !this.state.isLoopActive,
+      loopMode: loopMode,
+    });
+  }
+
   handleTabs(tab: number) {
     let state = {
       isAnimationTabOpen: true,
@@ -116,6 +138,7 @@ export class EditorControls extends React.Component<
   }
 
   handleChangeFps(fps: number) {
+    this.props.setFps(fps);
     this.setState({ framesPerSecond: fps });
   }
 
@@ -155,12 +178,14 @@ export class EditorControls extends React.Component<
             icon='medium load'
             onClick={() => this.handleTabs(1)}
           ></IconButtonLineComponent>
-          <IconButtonLineComponent
-            active={this.state.isSaveTabOpen}
-            tooltip='Save Animation'
-            icon='medium save'
-            onClick={() => this.handleTabs(2)}
-          ></IconButtonLineComponent>
+          {this.state.animationsCount === 0 ? null : (
+            <IconButtonLineComponent
+              active={this.state.isSaveTabOpen}
+              tooltip='Save Animation'
+              icon='medium save'
+              onClick={() => this.handleTabs(2)}
+            ></IconButtonLineComponent>
+          )}
           {this.state.animationsCount === 0 ? null : (
             <IconButtonLineComponent
               active={this.state.isEditTabOpen}
@@ -190,9 +215,7 @@ export class EditorControls extends React.Component<
                   ? 'loop-active last'
                   : 'loop-inactive last'
               }`}
-              onClick={() => {
-                this.setState({ isLoopActive: !this.state.isLoopActive });
-              }}
+              onClick={() => this.changeLoopBehavior()}
             ></IconButtonLineComponent>
           ) : null}
         </div>

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

@@ -56,7 +56,7 @@ export class GraphActionsBar extends React.Component<IGraphActionsBarProps> {
             onClick={this.props.addKeyframe}
           />
           <IconButtonLineComponent
-            tooltip={'Remove Keyframe'}
+            tooltip={'Focus'}
             icon='frame'
             onClick={this.props.removeKeyframe}
           />

+ 74 - 47
inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

@@ -12,6 +12,7 @@ interface ITimelineProps {
   playPause: (direction: number) => void;
   isPlaying: boolean;
   animationLimit: number;
+  fps: number;
 }
 
 export class Timeline extends React.Component<
@@ -68,6 +69,10 @@ export class Timeline extends React.Component<
       const scrollFrameLength = end - start;
       const widthPercentage = (scrollFrameLength * 100) / scrollFrameLimit;
       const scrollPixelWidth = (widthPercentage * containerWidth) / 100;
+      // check 0 values here... to avoid Infinity
+      if (scrollPixelWidth === Infinity) {
+        console.log('error infinity');
+      }
       return scrollPixelWidth;
     } else {
       return undefined;
@@ -95,7 +100,10 @@ export class Timeline extends React.Component<
 
   handleLimitChange(event: React.ChangeEvent<HTMLInputElement>) {
     event.preventDefault();
-    const newLimit = parseInt(event.target.value);
+    let newLimit = parseInt(event.target.value);
+    if (isNaN(newLimit)) {
+      newLimit = 0;
+    }
     this.setState({
       end: newLimit,
       scrollWidth: this.calculateScrollWidth(this.state.start, newLimit),
@@ -253,11 +261,36 @@ export class Timeline extends React.Component<
   scrollDrag(e: any) {
     e.preventDefault();
     if (e.target.className === 'scrollbar') {
-      if (this._scrolling && this._scrollbarHandle.current) {
+      if (
+        this._scrolling &&
+        this._scrollbarHandle.current &&
+        this._scrollContainer.current
+      ) {
         let moved = e.pageX - this._shiftX;
-        if (moved > 233 && moved < 630) {
+
+        const scrollContainerWith = this._scrollContainer.current.clientWidth;
+        const startPixel = moved - 233;
+        const limitRight =
+          scrollContainerWith - (this.state.scrollWidth || 0) - 5;
+
+        if (moved > 233 && startPixel < limitRight) {
           this._scrollbarHandle.current.style.left = moved + 'px';
           (this._scrollable.current as HTMLDivElement).scrollLeft = moved + 10;
+
+          const startPixelPercent = (startPixel * 100) / scrollContainerWith;
+
+          const selectionStartFrame = Math.round(
+            (startPixelPercent * this.props.animationLimit) / 100
+          );
+
+          const selectionEndFrame =
+            this.state.selectionLength.length + selectionStartFrame;
+
+          this.setState({
+            start: selectionStartFrame,
+            end: selectionEndFrame,
+            selectionLength: this.range(selectionStartFrame, selectionEndFrame),
+          });
         }
       }
     }
@@ -299,26 +332,13 @@ export class Timeline extends React.Component<
         let moving =
           e.clientX -
           this._scrollbarHandle.current.getBoundingClientRect().left;
-        // const scrollContainerWith = this._scrollContainer.current.clientWidth;
-        //const pixelFrameRatio = scrollContainerWith / this.props.animationLimit;
 
         let containerWidth = this.state.scrollWidth ?? 0;
         let resizePercentage = (100 * Math.abs(moving)) / containerWidth;
 
         let frameChange = (resizePercentage * this.state.end) / 100;
 
-        // let framesTo;
-
-        // if (Math.sign(moving) === 1) {
-        //   framesTo = this.state.end + Math.round(frameChange);
-        // } else {
-        //   framesTo = this.state.end - Math.round(frameChange);
-        // }
-        // let Toleft = framesTo * pixelFrameRatio + 233;
         let framesTo = Math.round(frameChange);
-        console.log('frames ' + framesTo + ' >> resize' + resizePercentage);
-
-        // this._scrollbarHandle.current.style.left = Toleft + 'px';
 
         this.setState({
           end: framesTo,
@@ -333,10 +353,7 @@ export class Timeline extends React.Component<
   scrollDragEnd(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
   scrollDragEnd(e: any) {
     e.preventDefault();
-    if (e.target.className === 'scrollbar') {
-      this._scrolling = false;
-    }
-
+    this._scrolling = false;
     this.active = '';
     this._shiftX = 0;
   }
@@ -345,6 +362,14 @@ export class Timeline extends React.Component<
     return Array.from({ length: end - start }, (_, i) => start + i * 1);
   }
 
+  getKeyframe(frame: number) {
+    if (this.props.keyframes) {
+      return this.props.keyframes.find((x) => x.frame === frame);
+    } else {
+      return false;
+    }
+  }
+
   render() {
     return (
       <>
@@ -411,31 +436,32 @@ export class Timeline extends React.Component<
                             y2='40'
                             style={{ stroke: '#555555', strokeWidth: 0.5 }}
                           />
+
+                          {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 }}
+                              />
+                            </svg>
+                          ) : null}
                         </>
                       }
                     </svg>
                   );
                 })}
-
-                {this.props.keyframes &&
-                  this.props.keyframes.map((kf, i) => {
-                    return (
-                      <svg
-                        key={`kf_${i}`}
-                        style={{ cursor: 'pointer' }}
-                        tabIndex={i + 40}
-                      >
-                        <line
-                          id={`kf_${i.toString()}`}
-                          x1={kf.frame * 10}
-                          y1='0'
-                          x2={kf.frame * 10}
-                          y2='40'
-                          style={{ stroke: '#ffc017', strokeWidth: 1 }}
-                        />
-                      </svg>
-                    );
-                  })}
               </svg>
             </div>
 
@@ -476,13 +502,14 @@ export class Timeline extends React.Component<
                   </div>
                 </div>
               </div>
-              <div className='input-frame'>
-                <input
-                  type='number'
-                  value={this.props.animationLimit}
-                  onChange={(e) => this.handleLimitChange(e)}
-                ></input>
-              </div>
+            </div>
+
+            <div className='input-frame'>
+              <input
+                type='number'
+                value={this.props.animationLimit ?? 0}
+                onChange={(e) => this.handleLimitChange(e)}
+              ></input>
             </div>
           </div>
         </div>