From 545508987903be8c2f361bbee8b3beae683c73b5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 26 Oct 2023 23:23:48 -0400
Subject: a variety of fixes to the animation timeline to make it make some
 sense. lots still broken.

---
 src/client/views/animationtimeline/Track.tsx | 77 +++++++++++++++-------------
 1 file changed, 42 insertions(+), 35 deletions(-)

(limited to 'src/client/views/animationtimeline/Track.tsx')

diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 1010332f5..2fd062a88 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -1,17 +1,19 @@
 import { action, computed, intercept, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Opt, DocListCastAsync } from '../../../fields/Doc';
+import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc';
 import { Copy } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast } from '../../../fields/Types';
 import { Transform } from '../../util/Transform';
-import { Keyframe, KeyframeFunc, RegionData } from './Keyframe';
+import { Region, RegionData, RegionHelpers } from './Region';
+import { Timeline } from './Timeline';
 import './Track.scss';
 
 interface IProps {
+    timeline: Timeline;
     animatedDoc: Doc;
     currentBarX: number;
     transform: Transform;
@@ -32,14 +34,14 @@ export class Track extends React.Component<IProps> {
     @observable private _newKeyframe: boolean = false;
     private readonly MAX_TITLE_HEIGHT = 75;
     @observable private _trackHeight = 0;
-    private primitiveWhitelist = ['x', 'y', '_width', '_height', 'opacity', '_layout_scrollTop'];
+    private primitiveWhitelist = ['x', 'y', '_width', '_height', '_rotation', 'opacity', '_layout_scrollTop'];
     private objectWhitelist = ['data'];
 
     @computed private get regions() {
         return DocListCast(this.props.animatedDoc.regions);
     }
     @computed private get time() {
-        return NumCast(KeyframeFunc.convertPixelTime(this.props.currentBarX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
+        return NumCast(RegionHelpers.convertPixelTime(this.props.currentBarX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
     }
 
     async componentDidMount() {
@@ -85,36 +87,36 @@ export class Track extends React.Component<IProps> {
      */
     @action
     saveKeyframe = async () => {
-        const keyframes = Cast(this.saveStateRegion?.keyframes, listSpec(Doc)) as List<Doc>;
-        const kfIndex = keyframes.indexOf(this.saveStateKf!);
+        if (this.props.timeline.IsPlaying || !this.saveStateRegion || !this.saveStateKf) {
+            this.saveStateKf = undefined;
+            this.saveStateRegion = undefined;
+            return;
+        }
+        const keyframes = Cast(this.saveStateRegion.keyframes, listSpec(Doc)) as List<Doc>;
+        const kfIndex = keyframes.indexOf(this.saveStateKf);
         const kf = keyframes[kfIndex] as Doc; //index in the keyframe
         if (this._newKeyframe) {
-            DocListCast(this.saveStateRegion?.keyframes).forEach((kf, index) => {
+            DocListCast(this.saveStateRegion.keyframes).forEach((kf, index) => {
                 this.copyDocDataToKeyFrame(kf);
                 kf.opacity = index === 0 || index === 3 ? 0.1 : 1;
             });
             this._newKeyframe = false;
         }
         if (!kf) return;
-        if (kf.type === KeyframeFunc.KeyframeType.default) {
-            // only save for non-fades
-            this.copyDocDataToKeyFrame(kf);
-            const leftkf = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, kf); // lef keyframe, if it exists
-            const rightkf = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, kf); //right keyframe, if it exists
-            if (leftkf?.type === KeyframeFunc.KeyframeType.fade) {
+        // only save for non-fades
+        if (this.copyDocDataToKeyFrame(kf)) {
+            const leftkf = RegionHelpers.calcMinLeft(this.saveStateRegion, this.time, kf); // lef keyframe, if it exists
+            const rightkf = RegionHelpers.calcMinRight(this.saveStateRegion, this.time, kf); //right keyframe, if it exists
+            if (leftkf?.type === RegionHelpers.KeyframeType.end) {
                 //replicating this keyframe to fades
-                const edge = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, leftkf);
+                const edge = RegionHelpers.calcMinLeft(this.saveStateRegion, this.time, leftkf);
                 edge && this.copyDocDataToKeyFrame(edge);
                 leftkf && this.copyDocDataToKeyFrame(leftkf);
-                edge && (edge.opacity = 0.1);
-                leftkf && (leftkf.opacity = 1);
             }
-            if (rightkf?.type === KeyframeFunc.KeyframeType.fade) {
-                const edge = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, rightkf);
+            if (rightkf?.type === RegionHelpers.KeyframeType.end) {
+                const edge = RegionHelpers.calcMinRight(this.saveStateRegion, this.time, rightkf);
                 edge && this.copyDocDataToKeyFrame(edge);
                 rightkf && this.copyDocDataToKeyFrame(rightkf);
-                edge && (edge.opacity = 0.1);
-                rightkf && (rightkf.opacity = 1);
             }
         }
         keyframes[kfIndex] = kf;
@@ -143,7 +145,7 @@ export class Track extends React.Component<IProps> {
                     const r = region as RegionData; //for some region is returning undefined... which is not the case
                     if (DocListCast(r.keyframes).find(kf => kf.time === this.time) === undefined) {
                         //basically when there is no additional keyframe at that timespot
-                        this.makeKeyData(r, this.time, KeyframeFunc.KeyframeType.default);
+                        this.makeKeyData(r, this.time, RegionHelpers.KeyframeType.default);
                     }
                 }
             },
@@ -222,20 +224,20 @@ export class Track extends React.Component<IProps> {
      */
     @action
     timeChange = async () => {
-        if (this.saveStateKf !== undefined) {
-            await this.saveKeyframe();
-        } else if (this._newKeyframe) {
+        if (this.saveStateKf !== undefined || this._newKeyframe) {
             await this.saveKeyframe();
         }
         const regiondata = await this.findRegion(Math.round(this.time)); //finds a region that the scrubber is on
         if (regiondata) {
-            const leftkf: Doc | undefined = await KeyframeFunc.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
-            const rightkf: Doc | undefined = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists
+            const leftkf: Doc | undefined = await RegionHelpers.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
+            const rightkf: Doc | undefined = await RegionHelpers.calcMinRight(regiondata, this.time); //right keyframe, if it exists
             const currentkf: Doc | undefined = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
             if (currentkf) {
                 await this.applyKeys(currentkf);
-                this.saveStateKf = currentkf;
-                this.saveStateRegion = regiondata;
+                runInAction(() => {
+                    this.saveStateKf = currentkf;
+                    this.saveStateRegion = regiondata;
+                });
             } else if (leftkf && rightkf) {
                 await this.interpolate(leftkf, rightkf);
             }
@@ -277,7 +279,7 @@ export class Track extends React.Component<IProps> {
     @action
     interpolate = async (left: Doc, right: Doc) => {
         this.primitiveWhitelist.forEach(key => {
-            if (left[key] && right[key] && typeof left[key] === 'number' && typeof right[key] === 'number') {
+            if (typeof left[key] === 'number' && typeof right[key] === 'number') {
                 //if it is number, interpolate
                 const dif = NumCast(right[key]) - NumCast(left[key]);
                 const deltaLeft = this.time - NumCast(left.time);
@@ -306,7 +308,7 @@ export class Track extends React.Component<IProps> {
     onInnerDoubleClick = (e: React.MouseEvent) => {
         const inner = this._inner.current!;
         const offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this.props.transform.Scale);
-        this.createRegion(KeyframeFunc.convertPixelTime(offsetX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
+        this.createRegion(RegionHelpers.convertPixelTime(offsetX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
     };
 
     /**
@@ -316,10 +318,10 @@ export class Track extends React.Component<IProps> {
     createRegion = (time: number) => {
         if (this.findRegion(time) === undefined) {
             //check if there is a region where double clicking (prevents phantom regions)
-            const regiondata = KeyframeFunc.defaultKeyframe(); //create keyframe data
+            const regiondata = RegionHelpers.defaultKeyframe(); //create keyframe data
 
             regiondata.position = time; //set position
-            const rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, regiondata, this.regions);
+            const rightRegion = RegionHelpers.findAdjacentRegion(RegionHelpers.Direction.right, regiondata, this.regions);
 
             if (rightRegion && rightRegion.position - regiondata.position <= 4000) {
                 //edge case when there is less than default 4000 duration space between this and right region
@@ -335,7 +337,7 @@ export class Track extends React.Component<IProps> {
     };
 
     @action
-    makeKeyData = (regiondata: RegionData, time: number, type: KeyframeFunc.KeyframeType = KeyframeFunc.KeyframeType.default) => {
+    makeKeyData = (regiondata: RegionData, time: number, type: RegionHelpers.KeyframeType = RegionHelpers.KeyframeType.default) => {
         //Kfpos is mouse offsetX, representing time
         const trackKeyFrames = DocListCast(regiondata.keyframes);
         const existingkf = trackKeyFrames.find(TK => TK.time === time);
@@ -359,16 +361,21 @@ export class Track extends React.Component<IProps> {
 
     @action
     copyDocDataToKeyFrame = (doc: Doc) => {
+        var somethingChanged = false;
         this.primitiveWhitelist.map(key => {
             const originalVal = this.props.animatedDoc[key];
-            doc[key] = originalVal instanceof ObjectField ? originalVal[Copy]() : originalVal;
+            somethingChanged = somethingChanged || originalVal !== doc[key];
+            if (doc.type === RegionHelpers.KeyframeType.end && key === 'opacity') doc.opacity = 0;
+            else doc[key] = originalVal instanceof ObjectField ? originalVal[Copy]() : originalVal;
         });
+        return somethingChanged;
     };
 
     /**
      * UI sstuff here. Not really much to change
      */
     render() {
+        const saveStateKf = this.saveStateKf;
         return (
             <div className="track-container">
                 <div className="track">
@@ -380,7 +387,7 @@ export class Track extends React.Component<IProps> {
                         onPointerOver={() => Doc.BrushDoc(this.props.animatedDoc)}
                         onPointerOut={() => Doc.UnBrushDoc(this.props.animatedDoc)}>
                         {this.regions?.map((region, i) => {
-                            return <Keyframe key={`${i}`} {...this.props} RegionData={region} makeKeyData={this.makeKeyData} />;
+                            return <Region key={`${i}`} {...this.props} saveStateKf={saveStateKf} RegionData={region} makeKeyData={this.makeKeyData} />;
                         })}
                     </div>
                 </div>
-- 
cgit v1.2.3-70-g09d2

