diff --git a/src/mol-plugin/skin/base/components/misc.scss b/src/mol-plugin/skin/base/components/misc.scss index d11284bcf2feee85acc1e334685872c053da564d..f75ff2101ae6bc1261b1fb69f418f0875365c3b2 100644 --- a/src/mol-plugin/skin/base/components/misc.scss +++ b/src/mol-plugin/skin/base/components/misc.scss @@ -66,4 +66,8 @@ background: white; cursor: inherit; display: block; +} + +.msp-animation-section { + margin-bottom: $control-spacing; } \ No newline at end of file diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index 7e6e6f172d5cace17575f9169e6ad0ef8a1ea612..5e3a349c88082113ea666c92133b15ed44591c7d 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -45,6 +45,7 @@ class PluginState { return { data: this.dataState.getSnapshot(), behaviour: this.behaviorState.getSnapshot(), + animation: this.animation.getSnapshot(), cameraSnapshots: this.cameraSnapshots.getStateSnapshot(), canvas3d: { camera: this.plugin.canvas3d.camera.getSnapshot(), @@ -62,6 +63,9 @@ class PluginState { if (snapshot.canvas3d.camera) this.plugin.canvas3d.camera.setState(snapshot.canvas3d.camera); } this.plugin.canvas3d.requestDraw(true); + if (snapshot.animation) { + this.animation.setSnapshot(snapshot.animation); + } } dispose() { @@ -94,6 +98,7 @@ namespace PluginState { export interface Snapshot { data?: State.Snapshot, behaviour?: State.Snapshot, + animation?: PluginAnimationManager.Snapshot, cameraSnapshots?: CameraSnapshotManager.StateSnapshot, canvas3d?: { camera?: Camera.Snapshot, diff --git a/src/mol-plugin/state/animation/manager.ts b/src/mol-plugin/state/animation/manager.ts index 68fd66029cdfdc97c953b6a5d27b8addce9979dc..18bd9e36c972ed3516122e11b8ca3aab9b036a40 100644 --- a/src/mol-plugin/state/animation/manager.ts +++ b/src/mol-plugin/state/animation/manager.ts @@ -82,7 +82,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat this.triggerUpdate(); } - animate = async (t: number) => { + private animate = async (t: number) => { if (this._current.startedTime < 0) this._current.startedTime = t; const newState = await this._current.anim.apply( this._current.state, @@ -100,6 +100,38 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat } } + getSnapshot(): PluginAnimationManager.Snapshot { + if (!this.current) return { state: this.latestState }; + + return { + state: this.latestState, + current: { + paramValues: this._current.paramValues, + state: this._current.anim.stateSerialization ? this._current.anim.stateSerialization.toJSON(this._current.state) : this._current.state + } + }; + } + + setSnapshot(snapshot: PluginAnimationManager.Snapshot) { + this.updateState({ animationState: snapshot.state.animationState }); + this.updateParams(snapshot.state.params); + + if (snapshot.current) { + this.current.paramValues = snapshot.current.paramValues; + this.current.state = this._current.anim.stateSerialization + ? this._current.anim.stateSerialization.fromJSON(snapshot.current.state) + : snapshot.current.state; + this.triggerUpdate(); + if (this.latestState.animationState === 'playing') this.resume(); + } + } + + private resume() { + this._current.lastTime = 0; + this._current.startedTime = -1; + requestAnimationFrame(this.animate); + } + constructor(ctx: PluginContext) { super(ctx, { params: { current: '' }, animationState: 'stopped' }); } @@ -119,4 +151,12 @@ namespace PluginAnimationManager { params: { current: string }, animationState: 'stopped' | 'playing' } + + export interface Snapshot { + state: State, + current?: { + paramValues: any, + state: any + } + } } \ No newline at end of file diff --git a/src/mol-plugin/state/animation/model.ts b/src/mol-plugin/state/animation/model.ts index 9cc06e7705625f7c7b21ba82aa293c305eb0d454..050c30600df28f1e41dab29c8b7823bc150a4632 100644 --- a/src/mol-plugin/state/animation/model.ts +++ b/src/mol-plugin/state/animation/model.ts @@ -23,9 +23,9 @@ interface PluginStateAnimation<P extends PD.Params = any, S = any> { /** * The state must be serializable to JSON. If JSON.stringify is not enough, - * custom serializer can be provided. + * custom converted to an object that works with JSON.stringify can be provided. */ - stateSerialization?: { toJSON?(state: S): any, fromJSON?(data: any): S } + stateSerialization?: { toJSON(state: S): any, fromJSON(data: any): S } } namespace PluginStateAnimation { diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx index ca7140edbb5f20566c558925ec73aa8b733e2831..e465a67a623ddfb0cf563a02590ad1bcbbeb53c5 100644 --- a/src/mol-plugin/ui/plugin.tsx +++ b/src/mol-plugin/ui/plugin.tsx @@ -62,8 +62,8 @@ class Layout extends PluginComponent { {layout.showControls && this.region('right', <div className='msp-scrollable-container msp-right-controls'> <CurrentObject /> <Controls /> - <CameraSnapshots /> <AnimationControls /> + <CameraSnapshots /> <StateSnapshots /> </div>)} {layout.showControls && this.region('bottom', <Log />)} diff --git a/src/mol-plugin/ui/state/animation.tsx b/src/mol-plugin/ui/state/animation.tsx index b43f57c998d3a7d4cb8c0554c971943b10c3e82a..c25fc4edaa18045c164b7eb0395a23912a000b8d 100644 --- a/src/mol-plugin/ui/state/animation.tsx +++ b/src/mol-plugin/ui/state/animation.tsx @@ -33,8 +33,7 @@ export class AnimationControls extends PluginComponent<{ }> { const isDisabled = anim.latestState.animationState === 'playing'; - // TODO: give it its own style - return <div style={{ marginBottom: '10px' }}> + return <div className='msp-animation-section'> <div className='msp-section-header'>Animations</div> <ParameterControls params={anim.getParams()} values={anim.latestState.params} onChange={this.updateParams} isDisabled={isDisabled} />