Ver Fonte

mol-plugin: improving UI (mostly animation controls in viewport)

David Sehnal há 6 anos atrás
pai
commit
08dbec878f

+ 10 - 0
src/mol-plugin/skin/base/components/controls-base.scss

@@ -11,20 +11,30 @@
 }
 
 .msp-btn-icon {
+    border: none;
     height: $row-height;
     width: $row-height;
     line-height: $row-height;
     padding: 0;
     text-align: center;
+
+    &[disabled], &[disabled]:hover, &[disabled]:active {
+        color: $msp-btn-link-toggle-off-font-color;
+    }
 }
 
 .msp-btn-icon-small {
+    border: none;
     height: $row-height;
     width: 20px;
     font-size: 85%;
     line-height: $row-height;
     padding: 0;
     text-align: center;
+
+    &[disabled], &[disabled]:hover, &[disabled]:active {
+        color: $msp-btn-link-toggle-off-font-color;
+    }
 }
 
 .msp-btn-link {

+ 1 - 1
src/mol-plugin/skin/base/components/misc.scss

@@ -68,6 +68,6 @@
     display: block;
 }
 
-.msp-animation-section {
+.msp-contols-section {
     margin-bottom: $control-spacing;
 }

+ 23 - 1
src/mol-plugin/skin/base/components/temp.scss

@@ -159,10 +159,11 @@
         line-height: $row-height;
         float: left;
         margin-right: $control-spacing;
+        background-color: $msp-form-control-background;
 
         > span {
             color: $font-color;
-            padding-top: 1px;
+            margin-right: $control-spacing;
             font-size: 85%;
             display: inline-block;
         }
@@ -172,12 +173,33 @@
         line-height: $row-height;
         float: left;
         margin-right: $control-spacing;
+        background-color: $msp-form-control-background;
 
         > select {
             display: inline-block;
             width: 200px;
         }
     }
+
+    .msp-animation-viewport-controls {
+        line-height: $row-height;
+        float: left;
+        margin-right: $control-spacing;
+        position: relative;
+        background-color: $msp-form-control-background;
+
+        .msp-animation-viewport-controls-select {
+            width: 290px;
+            position: absolute;
+            left: 0;
+            top: $row-height + $control-spacing;
+            background: $control-background;
+
+            .msp-control-row:first-child {
+                margin-top: 0;
+            }
+        }
+    }
 }
 
 .msp-param-object-list-item {

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

@@ -65,6 +65,10 @@
 .msp-viewport-controls-scene-options {
     width: 290px;
     background: $control-background;
+
+    .msp-control-group-wrapper:first-child {
+        padding-top: 0;
+    }
 }
 
 /* highlight */

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

@@ -184,4 +184,12 @@
 
 .msp-icon-right-open:before {
 	content: "\e87d";
+}
+
+.msp-icon-cw:before {
+	content: "\e890";
+}
+
+.msp-icon-database:before {
+	content: "\e8d3";
 }

+ 46 - 20
src/mol-plugin/ui/controls.tsx

@@ -14,10 +14,9 @@ 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';
-import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in';
-import { ParamDefinition } from 'mol-util/param-definition';
+import { AnimationControls } from './state/animation';
 
-export class TrajectoryControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
+export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
     state = { show: false, label: '' }
 
     private update = () => {
@@ -76,18 +75,18 @@ export class TrajectoryControls extends PluginUIComponent<{}, { show: boolean, l
         action: UpdateTrajectory.create({ action: 'advance', by: 1 })
     });
 
-    stopAnimation = () => {
-        this.plugin.state.animation.stop();
-    }
+    // stopAnimation = () => {
+    //     this.plugin.state.animation.stop();
+    // }
 
-    playAnimation = () => {
-        const anim = this.plugin.state.animation;
-        if (anim.state.params.current === AnimateModelIndex.name) {
-            anim.start();
-        } else {
-            anim.play(AnimateModelIndex, ParamDefinition.getDefaultValues(AnimateModelIndex.params(this.plugin) as any as ParamDefinition.Params))
-        }
-    }
+    // playAnimation = () => {
+    //     const anim = this.plugin.state.animation;
+    //     if (anim.state.params.current === AnimateModelIndex.name) {
+    //         anim.start();
+    //     } else {
+    //         anim.play(AnimateModelIndex, ParamDefinition.getDefaultValues(AnimateModelIndex.params(this.plugin) as any as ParamDefinition.Params))
+    //     }
+    // }
 
     render() {
         if (!this.state.show) return null;
@@ -95,7 +94,7 @@ export class TrajectoryControls extends PluginUIComponent<{}, { show: boolean, l
         const isAnimating = this.plugin.behaviors.state.isAnimating.value;
 
         return <div className='msp-traj-controls'>
-            <IconButton icon={isAnimating ? 'stop' : 'play'} title={isAnimating ? 'Stop' : 'Play'} onClick={isAnimating ? this.stopAnimation : this.playAnimation} />
+            {/* <IconButton icon={isAnimating ? 'stop' : 'play'} title={isAnimating ? 'Stop' : 'Play'} onClick={isAnimating ? this.stopAnimation : this.playAnimation} /> */}
             <IconButton icon='model-first' title='First Model' onClick={this.reset} disabled={isAnimating} />
             <IconButton icon='model-prev' title='Previous Model' onClick={this.prev} disabled={isAnimating} />
             <IconButton icon='model-next' title='Next Model' onClick={this.next} disabled={isAnimating} />
@@ -152,16 +151,43 @@ export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBus
         const current = snapshots.state.current;
         const isPlaying = snapshots.state.isPlaying;
 
-        // TODO: better handle disabled state
-
         return <div className='msp-state-snapshot-viewport-controls'>
             <select className='msp-form-control' value={current || 'none'} onChange={this.change} disabled={this.state.isBusy || isPlaying}>
                 {!current && <option key='none' value='none'></option>}
                 {snapshots.state.entries.valueSeq().map((e, i) => <option key={e!.snapshot.id} value={e!.snapshot.id}>{`[${i! + 1}/${count}]`} {e!.name || new Date(e!.timestamp).toLocaleString()}</option>)}
             </select>
-            <IconButton icon='left-open' title='Previous State' onClick={this.prev} disabled={this.state.isBusy || isPlaying} />
-            <IconButton icon='right-open' title='Next State' onClick={this.next} disabled={this.state.isBusy || isPlaying} />
-            <IconButton icon={isPlaying ? 'pause' : 'play'} title={isPlaying ? 'Pause' : 'Play'} onClick={this.togglePlay} />
+            <IconButton icon={isPlaying ? 'pause' : 'play'} title={isPlaying ? 'Pause' : 'Cycle States'} onClick={this.togglePlay}
+                disabled={isPlaying ? false : this.state.isBusy} />
+            {!isPlaying && <>
+                <IconButton icon='left-open' title='Previous State' onClick={this.prev} disabled={this.state.isBusy || isPlaying} />
+                <IconButton icon='right-open' title='Next State' onClick={this.next} disabled={this.state.isBusy || isPlaying} />
+            </>}
+        </div>;
+    }
+}
+
+export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty: boolean, isExpanded: boolean, isUpdating: boolean, isAnimating: boolean, isPlaying: boolean }> {
+    state = { isEmpty: true, isExpanded: false, isUpdating: false, isAnimating: false, isPlaying: false };
+
+    componentDidMount() {
+        this.subscribe(this.plugin.state.snapshots.events.changed, () => this.setState({ isPlaying: this.plugin.state.snapshots.state.isPlaying }));
+        this.subscribe(this.plugin.behaviors.state.isUpdating, isUpdating => this.setState({ isUpdating, isEmpty: this.plugin.state.dataState.tree.transforms.size < 2 }));
+        this.subscribe(this.plugin.behaviors.state.isAnimating, isAnimating => this.setState({ isAnimating }));
+    }
+    toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
+    stop = () => this.plugin.state.animation.stop();
+
+    render() {
+        // if (!this.state.show) return null;
+        const isAnimating = this.state.isAnimating;
+
+        return <div className='msp-animation-viewport-controls'>
+            <IconButton icon={isAnimating ? 'stop' : 'play'} title={isAnimating ? 'Stop' : 'Select Animation'}
+                onClick={isAnimating ? this.stop : this.toggleExpanded}
+                disabled={isAnimating ? false : this.state.isUpdating || this.state.isPlaying || this.state.isEmpty} />
+            {(this.state.isExpanded && !this.state.isUpdating) && <div className='msp-animation-viewport-controls-select'>
+                <AnimationControls onStart={this.toggleExpanded} />
+            </div>}
         </div>;
     }
 }

+ 7 - 1
src/mol-plugin/ui/controls/common.tsx

@@ -81,6 +81,12 @@ export class NumericInput extends React.PureComponent<{
     }
 }
 
+export function Icon(props: {
+    name: string
+}) {
+    return <span className={`msp-icon msp-icon-${props.name}`} />;
+}
+
 export function IconButton(props: {
     icon: string,
     isSmall?: boolean,
@@ -90,7 +96,7 @@ export function IconButton(props: {
     disabled?: boolean,
     'data-id'?: string
 }) {
-    let className = `msp-btn msp-btn-link msp-btn-icon${props.isSmall ? '-small' : ''}`;
+    let className = `msp-btn-link msp-btn-icon${props.isSmall ? '-small' : ''}`;
     if (typeof props.toggleState !== 'undefined') className += ` msp-btn-link-toggle-${props.toggleState ? 'on' : 'off'}`
     return <button className={className} onClick={props.onClick} title={props.title} disabled={props.disabled} data-id={props['data-id']}>
         <span className={`msp-icon msp-icon-${props.icon}`}/>

+ 10 - 6
src/mol-plugin/ui/plugin.tsx

@@ -11,10 +11,9 @@ import { LogEntry } from 'mol-util/log-entry';
 import * as React from 'react';
 import { PluginContext } from '../context';
 import { PluginReactContext, PluginUIComponent } from './base';
-import { LociLabelControl, TrajectoryControls, StateSnapshotViewportControls } from './controls';
+import { LociLabelControl, TrajectoryViewportControls, StateSnapshotViewportControls, AnimationViewportControls } from './controls';
 import { StateSnapshots } from './state';
 import { StateObjectActions } from './state/actions';
-import { AnimationControls } from './state/animation';
 import { StateTree } from './state/tree';
 import { BackgroundTaskProgress } from './task';
 import { Viewport, ViewportControls } from './viewport';
@@ -73,7 +72,7 @@ export class ControlsWrapper extends PluginUIComponent {
     render() {
         return <div className='msp-scrollable-container msp-right-controls'>
             <CurrentObject />
-            <AnimationControls />
+            {/* <AnimationControlsWrapper /> */}
             {/* <CameraSnapshots /> */}
             <StateSnapshots />
         </div>;
@@ -85,7 +84,8 @@ export class ViewportWrapper extends PluginUIComponent {
         return <>
             <Viewport />
             <div className='msp-viewport-top-left-controls'>
-                <TrajectoryControls />
+                <AnimationViewportControls />
+                <TrajectoryViewportControls />
                 <StateSnapshotViewportControls />
             </div>
             <ViewportControls />
@@ -111,8 +111,12 @@ export class State extends PluginUIComponent {
         const kind = this.plugin.state.behavior.kind.value;
         return <div className='msp-scrollable-container'>
             <div className='msp-btn-row-group msp-data-beh'>
-                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal' }}>Data</button>
-                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal' }}>Behavior</button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal' }}>
+                    <span className='msp-icon msp-icon-database' /> Data
+                </button>
+                <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal' }}>
+                    <span className='msp-icon msp-icon-tools' /> Behavior
+                </button>
             </div>
             <StateTree state={kind === 'data' ? this.plugin.state.dataState : this.plugin.state.behaviorState} />
         </div>

+ 20 - 6
src/mol-plugin/ui/state/animation.tsx

@@ -7,8 +7,20 @@
 import * as React from 'react';
 import { PluginUIComponent } from '../base';
 import { ParameterControls, ParamOnChange } from '../controls/parameters';
+import { Icon } from '../controls/common';
 
-export class AnimationControls extends PluginUIComponent<{ }> {
+export class AnimationControlsWrapper extends PluginUIComponent<{ }> {
+    render() {
+        const anim = this.plugin.state.animation;
+        if (anim.isEmpty) return null;
+        return <div className='msp-contols-section'>
+            <div className='msp-section-header'>Animations</div>
+            <AnimationControls />
+        </div>
+    }
+}
+
+export class AnimationControls extends PluginUIComponent<{ onStart?: () => void }> {
     componentDidMount() {
         this.subscribe(this.plugin.state.animation.events.updated, () => this.forceUpdate());
     }
@@ -24,7 +36,10 @@ export class AnimationControls extends PluginUIComponent<{ }> {
     startOrStop = () => {
         const anim = this.plugin.state.animation;
         if (anim.state.animationState === 'playing') anim.stop();
-        else anim.start();
+        else {
+            if (this.props.onStart) this.props.onStart();
+            anim.start();
+        }
     }
 
     render() {
@@ -33,17 +48,16 @@ export class AnimationControls extends PluginUIComponent<{ }> {
 
         const isDisabled = anim.state.animationState === 'playing';
 
-        return <div className='msp-animation-section'>
-            <div className='msp-section-header'>Animations</div>
-
+        return <>
             <ParameterControls params={anim.getParams()} values={anim.state.params} onChange={this.updateParams} isDisabled={isDisabled} />
             <ParameterControls params={anim.current.params} values={anim.current.paramValues} onChange={this.updateCurrentParams} isDisabled={isDisabled} />
 
             <div className='msp-btn-row-group'>
                 <button className='msp-btn msp-btn-block msp-form-control' onClick={this.startOrStop}>
+                    {anim.state.animationState !== 'playing' && <Icon name='play' />}
                     {anim.state.animationState === 'playing' ? 'Stop' : 'Start'}
                 </button>
             </div>
-        </div>
+        </>;
     }
 }

+ 2 - 3
src/mol-plugin/ui/viewport.tsx

@@ -18,7 +18,7 @@ interface ViewportState {
     noWebGl: boolean
 }
 
-export class ViewportControls extends PluginUIComponent {
+export class ViewportControls extends PluginUIComponent<{}, { isSettingsExpanded: boolean }> {
     state = {
         isSettingsExpanded: false
     }
@@ -70,8 +70,7 @@ export class ViewportControls extends PluginUIComponent {
                 {this.icon('expand-layout', this.toggleExpanded, 'Toggle Expanded', this.plugin.layout.state.isExpanded)}<br />
                 {this.icon('settings', this.toggleSettingsExpanded, 'Settings', this.state.isSettingsExpanded)}
             </div>
-            {this.state.isSettingsExpanded &&
-            <div className='msp-viewport-controls-scene-options'>
+            {this.state.isSettingsExpanded && <div className='msp-viewport-controls-scene-options'>
                 <ControlGroup header='Layout' initialExpanded={true}>
                     <ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.state} onChange={this.setLayout} />
                 </ControlGroup>