From 9ee014bcffca2287af44b17fa7d99bf836bcc011 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Wed, 20 Feb 2019 10:59:13 +0100
Subject: [PATCH] mol-plugin: improved model index animation; added animation
 to BasicWrapper

---
 src/apps/basic-wrapper/index.html          | 53 ++++++++++++++++++----
 src/apps/basic-wrapper/index.ts            |  9 ++++
 src/mol-plugin/state/animation/built-in.ts | 13 ++++--
 src/mol-plugin/state/animation/manager.ts  | 13 +++++-
 src/mol-plugin/state/animation/model.ts    | 12 ++---
 5 files changed, 81 insertions(+), 19 deletions(-)

diff --git a/src/apps/basic-wrapper/index.html b/src/apps/basic-wrapper/index.html
index b4a202a2d..38c2bf792 100644
--- a/src/apps/basic-wrapper/index.html
+++ b/src/apps/basic-wrapper/index.html
@@ -12,23 +12,39 @@
             }
             #app {
                 position: absolute;
-                left: 100px;
+                left: 160px;
                 top: 100px;
                 width: 600px;
                 height: 600px;
                 border: 1px solid #ccc;
             }
+
+            #controls {
+                position: absolute;
+                width: 130px;
+                top: 10px;
+                left: 10px;
+            }
+
+            #controls > button {
+                display: block;
+                width: 100%;
+            }
+
+            #controls > hr {
+                margin: 5px 0;
+            }
         </style>
         <link rel="stylesheet" type="text/css" href="app.css" />
         <script type="text/javascript" src="./index.js"></script>
     </head>
     <body>
-        <button id='spin'>Toggle Spin</button>
-        <button id='asym'>Load Asym Unit</button>
-        <button id='asm'>Load Assemly 1</button>
+        <div id='controls'>
+
+        </div>
         <div id="app"></div>
         <script>            
-            var pdbId = '5ire', assemblyId= '1';
+            var pdbId = '1grm', assemblyId= '1';
             var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
             var format = 'cif';
 
@@ -40,9 +56,30 @@
             BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId });
             BasicMolStarWrapper.toggleSpin();
 
-            document.getElementById('spin').onclick = () => BasicMolStarWrapper.toggleSpin();
-            document.getElementById('asym').onclick = () => BasicMolStarWrapper.load({ url: url, format: format });
-            document.getElementById('asm').onclick = () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId });
+            addControl('Toggle Spin', () => BasicMolStarWrapper.toggleSpin());
+
+            addSeparator();
+
+            addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format }));
+            addControl('Load Assembly 1', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
+
+            addSeparator();
+
+            addControl('Play Forward', () => BasicMolStarWrapper.animate.modelIndex.forward(8 /** maxFPS */));
+            addControl('Stop', () => BasicMolStarWrapper.animate.modelIndex.stop());
+            addControl('Play Backward', () => BasicMolStarWrapper.animate.modelIndex.backward(8 /** maxFPS */));
+
+            function addControl(label, action) {
+                var btn = document.createElement('button');
+                btn.onclick = action;
+                btn.innerText = label;
+                document.getElementById('controls').appendChild(btn);
+            }
+
+            function addSeparator() {
+                var hr = document.createElement('hr');
+                document.getElementById('controls').appendChild(hr);
+            }
         </script>
     </body>
 </html>
\ No newline at end of file
diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts
index b45753f91..f5104d8e6 100644
--- a/src/apps/basic-wrapper/index.ts
+++ b/src/apps/basic-wrapper/index.ts
@@ -13,6 +13,7 @@ import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/re
 import { Color } from 'mol-util/color';
 import { StateTreeBuilder } from 'mol-state/tree/builder';
 import { PluginStateObject as PSO } from 'mol-plugin/state/objects';
+import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in';
 require('mol-plugin/skin/light.scss')
 
 type SupportedFormats = 'cif' | 'pdb'
@@ -98,6 +99,14 @@ class BasicWrapper {
         PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
         if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
     }
+
+    animate = {
+        modelIndex: {
+            forward: (maxFPS = 8) => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, maxFPS | 0), direction: 'forward' }) },
+            backward: (maxFPS = 8) => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, maxFPS | 0), direction: 'backward' }) },
+            stop: () => this.plugin.state.animation.stop()
+        }
+    }
 }
 
 (window as any).BasicMolStarWrapper = new BasicWrapper();
\ No newline at end of file
diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts
index cc6ef04c6..4da3661e9 100644
--- a/src/mol-plugin/state/animation/built-in.ts
+++ b/src/mol-plugin/state/animation/built-in.ts
@@ -14,8 +14,11 @@ import { ParamDefinition } from 'mol-util/param-definition';
 export const AnimateModelIndex = PluginStateAnimation.create({
     name: 'built-in.animate-model-index',
     display: { name: 'Animate Model Index' },
-    params: () => ({ maxFPS: ParamDefinition.Numeric(3, { min: 0.5, max: 30, step: 0.5 }) }),
-    initialState: () => ({ frame: 1 }),
+    params: () => ({
+        direction: ParamDefinition.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]),
+        maxFPS: ParamDefinition.Numeric(3, { min: 0.5, max: 30, step: 0.5 })
+    }),
+    initialState: () => ({ }),
     async apply(animState, t, ctx) {
         // limit fps
         if (t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.maxFPS) {
@@ -28,19 +31,21 @@ export const AnimateModelIndex = PluginStateAnimation.create({
 
         const update = state.build();
 
+        const dir = ctx.params.direction === 'backward' ? -1 : 1;
+
         for (const m of models) {
             const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
             if (!parent || !parent.obj) continue;
             const traj = parent.obj as PluginStateObject.Molecule.Trajectory;
             update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
                 old => {
-                    let modelIndex = animState.frame % traj.data.length;
+                    let modelIndex = (old.modelIndex + dir) % traj.data.length;
                     if (modelIndex < 0) modelIndex += traj.data.length;
                     return { modelIndex };
                 });
         }
 
         await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update });
-        return { kind: 'next', state: { frame: animState.frame + 1 } };
+        return { kind: 'next', state: {} };
     }
 })
\ No newline at end of file
diff --git a/src/mol-plugin/state/animation/manager.ts b/src/mol-plugin/state/animation/manager.ts
index 8ba64a749..7504d54bd 100644
--- a/src/mol-plugin/state/animation/manager.ts
+++ b/src/mol-plugin/state/animation/manager.ts
@@ -13,6 +13,7 @@ export { PluginAnimationManager }
 
 // TODO: pause functionality (this needs to reset if the state tree changes)
 // TODO: handle unregistered animations on state restore
+// TODO: better API
 
 class PluginAnimationManager extends PluginComponent<PluginAnimationManager.State> {
     private map = new Map<string, PluginStateAnimation>();
@@ -37,7 +38,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
     updateParams(newParams: Partial<PluginAnimationManager.State['params']>) {
         this.updateState({ params: { ...this.latestState.params, ...newParams } });
         const anim = this.map.get(this.latestState.params.current)!;
-        const params = anim.params(this.context);
+        const params = anim.params(this.context) as PD.Params;
         this._current = {
             anim,
             params,
@@ -69,6 +70,16 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat
         }
     }
 
+    play<P>(animation: PluginStateAnimation<P>, params: P) {
+        this.stop();
+        if (!this.map.has(animation.name)) {
+            this.register(animation);
+        }
+        this.updateParams({ current: animation.name });
+        this.updateParams(params);
+        this.start();
+    }
+
     start() {
         this.updateState({ animationState: 'playing' });
         this.triggerUpdate();
diff --git a/src/mol-plugin/state/animation/model.ts b/src/mol-plugin/state/animation/model.ts
index 82ba43ca9..88d99c653 100644
--- a/src/mol-plugin/state/animation/model.ts
+++ b/src/mol-plugin/state/animation/model.ts
@@ -12,11 +12,11 @@ export { PluginStateAnimation }
 // TODO: helpers for building animations (once more animations are added)
 //       for example "composite animation"
 
-interface PluginStateAnimation<P extends PD.Params = any, S = any> {
+interface PluginStateAnimation<P = any, S = any> {
     name: string,
     readonly display: { readonly name: string, readonly description?: string },
-    params: (ctx: PluginContext) => P,
-    initialState(params: PD.Values<P>, ctx: PluginContext): S,
+    params: (ctx: PluginContext) => PD.For<P>,
+    initialState(params: P, ctx: PluginContext): S,
 
     /**
      * Apply the current frame and modify the state.
@@ -38,12 +38,12 @@ namespace PluginStateAnimation {
     }
 
     export type ApplyResult<S> = { kind: 'finished' } | { kind: 'skip' } | { kind: 'next', state: S }
-    export interface Context<P extends PD.Params> {
-        params: PD.Values<P>,
+    export interface Context<P> {
+        params: P,
         plugin: PluginContext
     }
 
-    export function create<P extends PD.Params, S>(params: PluginStateAnimation<P, S>) {
+    export function create<P, S>(params: PluginStateAnimation<P, S>) {
         return params;
     }
 }
\ No newline at end of file
-- 
GitLab