diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts index 4144341112d4451cd550835c4109f5fb38b99efc..252c80dea57256aca95bf5fa13080f40eab25277 100644 --- a/src/mol-plugin/state/animation/built-in.ts +++ b/src/mol-plugin/state/animation/built-in.ts @@ -95,9 +95,40 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({ durationInMs: PD.Numeric(3000, { min: 100, max: 10000, step: 100}) }), initialState: () => ({ t: 0 }), + async setup(_, plugin) { + const state = plugin.state.dataState; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D)); + + const update = state.build(); + let changed = false; + for (const r of reprs) { + const unwinds = state.select(StateSelection.Generators.byValue(r) + .children() + .filter(c => c.transform.transformer === StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D)); + if (unwinds.length > 0) continue; + + changed = true; + update.to(r.transform.ref) + .apply(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D, { t: 0 }, { props: { tag: 'animate-assembly-unwind' } }); + } + + if (!changed) return; + + return plugin.runTask(state.updateTree(update)); + }, + async teardown(_, plugin) { + const state = plugin.state.dataState; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) + .filter(c => c.transform.props.tag === 'animate-assembly-unwind')); + if (reprs.length === 0) return; + + const update = state.build(); + for (const r of reprs) update.delete(r.transform.ref); + return plugin.runTask(state.updateTree(update)); + }, async apply(animState, t, ctx) { const state = ctx.plugin.state.dataState; - const anims = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Structure.Representation3DState) + const anims = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3DState) .filter(c => c.transform.transformer === StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D)); if (anims.length === 0) { @@ -127,6 +158,37 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({ durationInMs: PD.Numeric(3000, { min: 100, max: 10000, step: 100}) }), initialState: () => ({ t: 0 }), + async setup(_, plugin) { + const state = plugin.state.dataState; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D)); + + const update = state.build(); + let changed = false; + for (const r of reprs) { + const unwinds = state.select(StateSelection.Generators.byValue(r) + .children() + .filter(c => c.transform.transformer === StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D)); + if (unwinds.length > 0) continue; + + changed = true; + update.to(r.transform.ref) + .apply(StateTransforms.Representation.ExplodeStructureRepresentation3D, { t: 0 }, { props: { tag: 'animate-units-explode' } }); + } + + if (!changed) return; + + return plugin.runTask(state.updateTree(update)); + }, + async teardown(_, plugin) { + const state = plugin.state.dataState; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) + .filter(c => c.transform.props.tag === 'animate-units-explode')); + if (reprs.length === 0) return; + + const update = state.build(); + for (const r of reprs) update.delete(r.transform.ref); + return plugin.runTask(state.updateTree(update)); + }, async apply(animState, t, ctx) { const state = ctx.plugin.state.dataState; const anims = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Structure.Representation3DState) diff --git a/src/mol-plugin/state/animation/manager.ts b/src/mol-plugin/state/animation/manager.ts index 78d380625b7a2db54f7b62bb72b6125ac543fe5d..b9121f2328e7c3bb9afd9f95885fabeeea8369bc 100644 --- a/src/mol-plugin/state/animation/manager.ts +++ b/src/mol-plugin/state/animation/manager.ts @@ -88,30 +88,41 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat this.start(); } - start() { + async start() { this.updateState({ animationState: 'playing' }); if (!this.context.behaviors.state.isAnimating.value) { this.context.behaviors.state.isAnimating.next(true); } this.triggerUpdate(); + const anim = this._current.anim; + if (anim.setup) { + await anim.setup(this._current.paramValues, this.context); + } + this._current.lastTime = 0; this._current.startedTime = -1; - this._current.state = this._current.anim.initialState(this._current.paramValues, this.context); + this._current.state = this._current.anim.initialState(anim, this.context); requestAnimationFrame(this.animate); } - stop() { + async stop() { if (typeof this._frame !== 'undefined') cancelAnimationFrame(this._frame); - if (this.context.behaviors.state.isAnimating.value) { - this.context.behaviors.state.isAnimating.next(false); - } if (this.state.animationState !== 'stopped') { + const anim = this._current.anim; + if (anim.teardown) { + await anim.teardown(this._current.paramValues, this.context); + } + this.updateState({ animationState: 'stopped' }); this.triggerUpdate(); } + + if (this.context.behaviors.state.isAnimating.value) { + this.context.behaviors.state.isAnimating.next(false); + } } get isAnimating() { diff --git a/src/mol-plugin/state/animation/model.ts b/src/mol-plugin/state/animation/model.ts index 88d99c653879f566900b18e04a639533d8e7c378..21aa69100290a36ef7a8c745177fccf8689e4954 100644 --- a/src/mol-plugin/state/animation/model.ts +++ b/src/mol-plugin/state/animation/model.ts @@ -18,6 +18,10 @@ interface PluginStateAnimation<P = any, S = any> { params: (ctx: PluginContext) => PD.For<P>, initialState(params: P, ctx: PluginContext): S, + // TODO: support state in setup/teardown? + setup?(params: P, ctx: PluginContext): void | Promise<void>, + teardown?(params: P, ctx: PluginContext): void | Promise<void>, + /** * Apply the current frame and modify the state. * @param t Current absolute time since the animation started.