aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/animationtimeline/Track.tsx
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-11-02 02:12:19 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-11-02 02:12:19 -0400
commita1d00a36ef1afa97198a825bd25ebb4c5e598848 (patch)
treee0c0454c99938562132794333a22e490e3e37cb9 /src/client/views/animationtimeline/Track.tsx
parent78d8261522c0079b0298613a856547a9ac96ef50 (diff)
parent84c15417f2247fc650a9f7b2c959479519bd3ebb (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/animationtimeline/Track.tsx')
-rw-r--r--src/client/views/animationtimeline/Track.tsx125
1 files changed, 68 insertions, 57 deletions
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 1010332f5..f36b5ade8 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,28 +34,29 @@ 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', '_freeform_panX', '_freeform_panY', '_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() {
- const regions = await DocListCastAsync(this.props.animatedDoc.regions);
- if (!regions) this.props.animatedDoc.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
- //these two lines are exactly same from timeline.tsx
- const relativeHeight = window.innerHeight / 20;
- runInAction(() => (this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT)); //for responsiveness
- this._timelineVisibleReaction = this.timelineVisibleReaction();
- this._currentBarXReaction = this.currentBarXReaction();
- if (DocListCast(this.props.animatedDoc.regions).length === 0) this.createRegion(this.time);
- this.props.animatedDoc.hidden = false;
- this.props.animatedDoc.opacity = 1;
- // this.autoCreateKeyframe();
+ componentDidMount() {
+ DocListCastAsync(this.props.animatedDoc.regions).then(regions => {
+ if (!regions) this.props.animatedDoc.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
+ //these two lines are exactly same from timeline.tsx
+ const relativeHeight = window.innerHeight / 20;
+ runInAction(() => (this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT)); //for responsiveness
+ this._timelineVisibleReaction = this.timelineVisibleReaction();
+ this._currentBarXReaction = this.currentBarXReaction();
+ if (DocListCast(this.props.animatedDoc.regions).length === 0) this.createRegion(this.time);
+ this.props.animatedDoc.hidden = false;
+ this.props.animatedDoc.opacity = 1;
+ // this.autoCreateKeyframe();
+ });
}
/**
@@ -84,37 +87,37 @@ 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!);
+ saveKeyframe = () => {
+ 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 +146,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);
}
}
},
@@ -178,7 +181,7 @@ export class Track extends React.Component<IProps> {
this.timeChange();
} else {
this.props.animatedDoc.hidden = true;
- this.props.animatedDoc.opacity = 0;
+ this.props.animatedDoc !== this.props.collection && (this.props.animatedDoc.opacity = 0);
//if (this._autoKfReaction) this._autoKfReaction();
}
}
@@ -221,23 +224,23 @@ export class Track extends React.Component<IProps> {
* when scrubber position changes. Need to edit the logic
*/
@action
- timeChange = async () => {
- if (this.saveStateKf !== undefined) {
- await this.saveKeyframe();
- } else if (this._newKeyframe) {
- await this.saveKeyframe();
+ timeChange = () => {
+ if (this.saveStateKf !== undefined || this._newKeyframe) {
+ this.saveKeyframe();
}
- const regiondata = await this.findRegion(Math.round(this.time)); //finds a region that the scrubber is on
+ const regiondata = 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 currentkf: Doc | undefined = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
+ const leftkf: Doc | undefined = RegionHelpers.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
+ const rightkf: Doc | undefined = RegionHelpers.calcMinRight(regiondata, this.time); //right keyframe, if it exists
+ const currentkf: Doc | undefined = this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
if (currentkf) {
- await this.applyKeys(currentkf);
- this.saveStateKf = currentkf;
- this.saveStateRegion = regiondata;
+ this.applyKeys(currentkf);
+ runInAction(() => {
+ this.saveStateKf = currentkf;
+ this.saveStateRegion = regiondata;
+ });
} else if (leftkf && rightkf) {
- await this.interpolate(leftkf, rightkf);
+ this.interpolate(leftkf, rightkf);
}
}
};
@@ -247,7 +250,7 @@ export class Track extends React.Component<IProps> {
* need to change the logic here
*/
@action
- private applyKeys = async (kf: Doc) => {
+ private applyKeys = (kf: Doc) => {
this.primitiveWhitelist.forEach(key => {
if (!kf[key]) {
this.props.animatedDoc[key] = undefined;
@@ -275,9 +278,12 @@ export class Track extends React.Component<IProps> {
* basic linear interpolation function
*/
@action
- interpolate = async (left: Doc, right: Doc) => {
+ interpolate = (left: Doc, right: Doc) => {
this.primitiveWhitelist.forEach(key => {
- if (left[key] && right[key] && typeof left[key] === 'number' && typeof right[key] === 'number') {
+ if (key === 'opacity' && this.props.animatedDoc === this.props.collection) {
+ return;
+ }
+ 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 +312,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 +322,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 +341,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 +365,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 +391,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>