diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-11-02 02:12:19 -0400 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-11-02 02:12:19 -0400 |
| commit | a1d00a36ef1afa97198a825bd25ebb4c5e598848 (patch) | |
| tree | e0c0454c99938562132794333a22e490e3e37cb9 /src/client/views/animationtimeline/Track.tsx | |
| parent | 78d8261522c0079b0298613a856547a9ac96ef50 (diff) | |
| parent | 84c15417f2247fc650a9f7b2c959479519bd3ebb (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.tsx | 125 |
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> |
