Browse Source

mol-plugin: better trajectory controls

David Sehnal 6 years ago
parent
commit
c2a76efadb

+ 14 - 0
src/mol-plugin/skin/base/components/temp.scss

@@ -149,4 +149,18 @@
         margin-bottom: 1px;
         padding: 3px 6px;
     }
+}
+
+.msp-traj-controls {
+    position: absolute;
+    left: $control-spacing;
+    top: $control-spacing;
+    line-height: $row-height;
+
+    > span {
+        color: $font-color;
+        padding-top: 1px;
+        font-size: 85%;
+        display: inline-block;
+    }
 }

+ 3 - 3
src/mol-plugin/skin/base/components/viewport.scss

@@ -75,9 +75,9 @@
     background: $default-background; //$highlight-info-background;
 
     position: absolute;
-    top: $control-spacing;
-    left: $control-spacing;
-    text-align: left;
+    right: $control-spacing;
+    bottom: $control-spacing;
+    text-align: right;
     min-height: $row-height;
     max-width: 95%;
 

+ 12 - 0
src/mol-plugin/skin/base/icons.scss

@@ -132,4 +132,16 @@
 
 .msp-icon-help-circle:before { 
 	content: "\e81d";
+}
+
+.msp-icon-model-prev:before {
+    content: "\e884";
+}
+
+.msp-icon-model-next:before {
+    content: "\e885";
+}
+
+.msp-icon-model-first:before {
+    content: "\e89c";
 }

+ 62 - 19
src/mol-plugin/ui/controls.tsx

@@ -9,31 +9,72 @@ import { PluginCommands } from 'mol-plugin/command';
 import { UpdateTrajectory } from 'mol-plugin/state/actions/structure';
 import { PluginUIComponent } from './base';
 import { LociLabelEntry } from 'mol-plugin/util/loci-label-manager';
+import { IconButton } from './controls/common';
+import { PluginStateObject } from 'mol-plugin/state/objects';
+import { StateTransforms } from 'mol-plugin/state/transforms';
+import { StateTransformer } from 'mol-state';
+import { ModelFromTrajectory } from 'mol-plugin/state/transforms/model';
 
-export class Controls extends PluginUIComponent<{ }, { }> {
-    render() {
-        return <>
+export class TrajectoryControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
+    state = { show: false, label: '' }
+
+    private update = () => {
+        const state = this.plugin.state.dataState;
+
+        const models = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model)
+            .filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory));
+
+        if (models.length === 0) {
+            this.setState({ show: false })
+        }
+
+        let label = '', count = 0, parents = new Set<string>();
+        for (const m of models) {
+            if (!m.sourceRef) continue;
+            const parent = state.cells.get(m.sourceRef)!.obj as PluginStateObject.Molecule.Trajectory;
 
-        </>;
+            if (!parent) continue;
+            if (parent.data.length > 1) {
+                if (parents.has(m.sourceRef)) {
+                    // do not show the controls if there are 2 models of the same trajectory present
+                    this.setState({ show: false });
+                }
+
+                parents.add(m.sourceRef);
+                count++;
+                if (!label) {
+                    const idx = (m.transform.params! as StateTransformer.Params<ModelFromTrajectory>).modelIndex;
+                    label = `Model ${idx + 1} / ${parent.data.length}`;
+                }
+            }
+        }
+
+        if (count > 1) label = '';
+        this.setState({ show: count > 0, label });
+    }
+
+    componentDidMount() {
+        this.subscribe(this.plugin.state.dataState.events.changed, this.update);
     }
-}
 
-export class TrajectoryControls extends PluginUIComponent {
     render() {
-        return <div>
-            <button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
-                state: this.plugin.state.dataState,
-                action: UpdateTrajectory.create({ action: 'advance', by: -1 })
-            })} title='Previou Model'>◀</button>
-            <button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
+        if (!this.state.show) return null;
+
+        return <div className='msp-traj-controls'>
+            <IconButton icon='model-first' title='First Model' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
                 state: this.plugin.state.dataState,
                 action: UpdateTrajectory.create({ action: 'reset' })
-            })} title='First Model'>↻</button>
-            <button className='msp-btn msp-btn-link' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
+            })} />
+            <IconButton icon='model-prev' title='Previous Model' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
+                state: this.plugin.state.dataState,
+                action: UpdateTrajectory.create({ action: 'advance', by: -1 })
+            })} />
+            <IconButton icon='model-next' title='Next Model' onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
                 state: this.plugin.state.dataState,
-                action: UpdateTrajectory.create({ action: 'advance', by: +1 })
-            })} title='Next Model'>►</button><br />
-        </div>
+                action: UpdateTrajectory.create({ action: 'advance', by: 1 })
+            })} />
+            { !!this.state.label && <span>{this.state.label}</span> }
+        </div>;
     }
 }
 
@@ -45,8 +86,10 @@ export class LociLabelControl extends PluginUIComponent<{}, { entries: ReadonlyA
     }
 
     render() {
-        return <div style={{ textAlign: 'right' }}>
+        if (this.state.entries.length === 0) return null;
+
+        return <div className='msp-highlight-info'>
             {this.state.entries.map((e, i) => <div key={'' + i}>{e}</div>)}
-        </div>
+        </div>;
     }
 }

+ 8 - 0
src/mol-plugin/ui/controls/common.tsx

@@ -81,6 +81,14 @@ export class NumericInput extends React.PureComponent<{
     }
 }
 
+export function IconButton(props: { icon: string, onClick: (e: React.MouseEvent<HTMLButtonElement>) => void, title?: string, toggleState?: boolean }) {
+    let className = `msp-btn msp-btn-link msp-btn-icon`;
+    if (typeof props.toggleState !== 'undefined') className += ` msp-btn-link-toggle-${props.toggleState ? 'on' : 'off'}`
+    return <button className={className} onClick={props.onClick} title={props.title}>
+        <span className={`msp-icon msp-icon-${props.icon}`}/>
+    </button>;
+}
+
 
 // export const ToggleButton = (props: {
 //     onChange: (v: boolean) => void,

+ 4 - 9
src/mol-plugin/ui/plugin.tsx

@@ -12,7 +12,7 @@ import * as React from 'react';
 import { PluginContext } from '../context';
 import { PluginReactContext, PluginUIComponent } from './base';
 import { CameraSnapshots } from './camera';
-import { Controls, LociLabelControl, TrajectoryControls } from './controls';
+import { LociLabelControl, TrajectoryControls } from './controls';
 import { StateSnapshots } from './state';
 import { StateObjectActions } from './state/actions';
 import { AnimationControls } from './state/animation';
@@ -60,7 +60,6 @@ class Layout extends PluginUIComponent {
                     {layout.showControls && this.region('left', <State />)}
                     {layout.showControls && this.region('right', <div className='msp-scrollable-container msp-right-controls'>
                         <CurrentObject />
-                        <Controls />
                         <AnimationControls />
                         <CameraSnapshots />
                         <StateSnapshots />
@@ -72,20 +71,16 @@ class Layout extends PluginUIComponent {
     }
 }
 
-export class ViewportWrapper extends PluginUIComponent {
+export class ViewportWrapper extends PluginUIComponent {    
     render() {
         return <>
             <Viewport />
-            <div style={{ position: 'absolute', left: '10px', top: '10px', color: 'white' }}>
-                <TrajectoryControls />
-            </div>
+            <TrajectoryControls />
             <ViewportControls />
             <div style={{ position: 'absolute', left: '10px', bottom: '10px' }}>
                 <BackgroundTaskProgress />
             </div>
-            <div style={{ position: 'absolute', right: '10px', bottom: '10px' }}>
-                <LociLabelControl />
-            </div>
+            <LociLabelControl />
         </>;
     }
 }

+ 2 - 2
src/mol-state/state/selection.ts

@@ -212,7 +212,7 @@ namespace StateSelection {
     registerModifier('parent', parent);
     export function parent(b: Selector) { return unique(mapEntity(b, (n, s) => s.cells.get(s.tree.transforms.get(n.transform.ref)!.parent))); }
 
-    export function findAncestorOfType(tree: StateTree, cells: State.Cells, root: StateTransform.Ref, types: StateObject.Ctor[]): StateObjectCell | undefined {
+    export function findAncestorOfType<T extends StateObject.Ctor>(tree: StateTree, cells: State.Cells, root: StateTransform.Ref, types: T[]): StateObjectCell<StateObject.From<T>> | undefined {
         let current = tree.transforms.get(root)!, len = types.length;
         while (true) {
             current = tree.transforms.get(current.parent)!;
@@ -220,7 +220,7 @@ namespace StateSelection {
             if (!cell.obj) return void 0;
             const obj = cell.obj;
             for (let i = 0; i < len; i++) {
-                if (obj.type === types[i].type) return cells.get(current.ref);
+                if (obj.type === types[i].type) return cell as StateObjectCell<StateObject.From<T>>;
             }
             if (current.ref === StateTransform.RootRef) {
                 return void 0;