Browse Source

mol-plugin-ui: Selection/measurement UI updates

David Sehnal 5 years ago
parent
commit
82989cae57

+ 3 - 4
src/mol-plugin-state/manager/structure/hierarchy.ts

@@ -19,8 +19,7 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
         currentModels: this.ev.behavior(this.state.currentModels)
     }
 
-    private syncCurrent() {
-        const hierarchy = this.behaviors.hierarchy.value;
+    private syncCurrent(hierarchy: StructureHierarchy) {
         const current = this.behaviors.currentModels.value;
         if (current.length === 0) {
             const models = hierarchy.trajectories[0]?.models;
@@ -50,10 +49,10 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
             return;
         }
 
-        const currentModels = this.syncCurrent();
+        const currentModels = this.syncCurrent(update.hierarchy);
         this.updateState({ hierarchy: update.hierarchy, currentModels });
 
-        this.behaviors.hierarchy.next(this.state.hierarchy)
+        this.behaviors.hierarchy.next(this.state.hierarchy);
         this.behaviors.currentModels.next(this.state.currentModels);
     }
 

+ 25 - 13
src/mol-plugin-state/manager/structure/measurement.ts

@@ -33,7 +33,7 @@ export interface StructureMeasurementManagerState {
     distances: StructureMeasurementCell[],
     angles: StructureMeasurementCell[],
     dihedrals: StructureMeasurementCell[],
-    // TODO: orientations
+    orientations: StructureMeasurementCell[],
     options: StructureMeasurementOptions
 }
 
@@ -56,12 +56,7 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
     }
 
     async setOptions(options: StructureMeasurementOptions) {
-        this.updateState({ options });
-
-        if (this.state.distances.length === 0) {
-            this.stateUpdated();
-            return;
-        }
+        if (this.updateState({ options })) this.stateUpdated();
 
         const update = this.plugin.state.dataState.build();
         for (const cell of this.state.distances) {
@@ -80,6 +75,9 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
         for (const cell of this.state.dihedrals) {
             update.to(cell).update((old: any) => { old.textColor = options.textColor; old.textSize = options.textSize; });
         }
+        
+        if (update.editInfo.count === 0) return;
+
         await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.dataState, tree: update, options: { doNotLogTiming: true } });
     }
 
@@ -102,7 +100,11 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
                 isTransitive: true,
                 label: 'Distance'
             }, { dependsOn })
-            .apply(StateTransforms.Representation.StructureSelectionsDistance3D, { unitLabel: this.state.options.distanceUnitLabel })
+            .apply(StateTransforms.Representation.StructureSelectionsDistance3D, {
+                unitLabel: this.state.options.distanceUnitLabel,
+                textSize: this.state.options.textSize,
+                textColor: this.state.options.textColor
+            })
 
         const state = this.plugin.state.dataState;
         await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
@@ -130,7 +132,10 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
                 isTransitive: true,
                 label: 'Angle'
             }, { dependsOn })
-            .apply(StateTransforms.Representation.StructureSelectionsAngle3D)
+            .apply(StateTransforms.Representation.StructureSelectionsAngle3D, {
+                textSize: this.state.options.textSize,
+                textColor: this.state.options.textColor
+            })
 
         const state = this.plugin.state.dataState;
         await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
@@ -161,7 +166,10 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
                 isTransitive: true,
                 label: 'Dihedral'
             }, { dependsOn })
-            .apply(StateTransforms.Representation.StructureSelectionsDihedral3D)
+            .apply(StateTransforms.Representation.StructureSelectionsDihedral3D, {
+                textSize: this.state.options.textSize,
+                textColor: this.state.options.textColor
+            })
 
         const state = this.plugin.state.dataState;
         await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
@@ -183,7 +191,10 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
                 isTransitive: true,
                 label: 'Label'
             }, { dependsOn })
-            .apply(StateTransforms.Representation.StructureSelectionsLabel3D)
+            .apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
+                textSize: this.state.options.textSize,
+                textColor: this.state.options.textColor
+            })
 
         const state = this.plugin.state.dataState;
         await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
@@ -225,13 +236,14 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
             labels: this.getTransforms(StateTransforms.Representation.StructureSelectionsLabel3D),
             distances: this.getTransforms(StateTransforms.Representation.StructureSelectionsDistance3D),
             angles: this.getTransforms(StateTransforms.Representation.StructureSelectionsAngle3D),
-            dihedrals: this.getTransforms(StateTransforms.Representation.StructureSelectionsDihedral3D)
+            dihedrals: this.getTransforms(StateTransforms.Representation.StructureSelectionsDihedral3D),
+            orientations: this.getTransforms(StateTransforms.Representation.StructureSelectionsOrientation3D)
         });
         if (updated) this.stateUpdated();
     }
 
     constructor(private plugin: PluginContext) {
-        super({ labels: [], distances: [], angles: [], dihedrals: [], options: DefaultStructureMeasurementOptions });
+        super({ labels: [], distances: [], angles: [], dihedrals: [], orientations: [], options: DefaultStructureMeasurementOptions });
 
         plugin.state.dataState.events.changed.subscribe(e => {
             if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;

+ 88 - 2
src/mol-plugin-ui/structure/measurements.tsx

@@ -11,10 +11,11 @@ import { Loci } from '../../mol-model/loci';
 import { FiniteArray } from '../../mol-util/type-helpers';
 import { State } from '../../mol-state';
 import { PluginStateObject } from '../../mol-plugin-state/objects';
-import { IconButton, ExpandGroup } from '../controls/common';
+import { IconButton, ExpandGroup, ToggleButton } from '../controls/common';
 import { PluginCommands } from '../../mol-plugin/commands';
 import { StructureMeasurementCell, StructureMeasurementOptions, StructureMeasurementParams } from '../../mol-plugin-state/manager/structure/measurement';
 import { ParameterControls } from '../controls/parameters';
+import { ActionMenu } from '../controls/action-menu';
 
 // TODO details, options (e.g. change text for labels)
 // TODO better updates on state changes
@@ -60,15 +61,100 @@ export class StructureMeasurementsControls extends CollapsableControls<{}, Struc
         const measurements = this.plugin.managers.structure.measurement.state;
 
         return <>
+            <AddMeasurement />
             {this.renderGroup(measurements.labels, 'Labels')}
             {this.renderGroup(measurements.distances, 'Distances')}
             {this.renderGroup(measurements.angles, 'Angles')}
             {this.renderGroup(measurements.dihedrals, 'Dihedrals')}
+            {this.renderGroup(measurements.orientations, 'Orientations')}
             <MeasurementsOptions />
         </>
     }
 }
 
+export class AddMeasurement extends PurePluginUIComponent<{}, { isDisabled: boolean, showActions: boolean }> {
+    state = { isDisabled: false, showActions: false }
+
+    componentDidMount() {
+        this.subscribe(this.selection.events.changed, () => {
+            this.forceUpdate()
+        });
+
+        this.subscribe(this.plugin.behaviors.state.isBusy, v => {
+            this.setState({ isDisabled: v })
+        });
+    }
+
+    get selection() {
+        return this.plugin.managers.structure.selection;
+    }
+
+    measureDistance = () => {
+        const loci = this.plugin.managers.structure.selection.history;
+        this.plugin.managers.structure.measurement.addDistance(loci[0].loci, loci[1].loci);
+    }
+
+    measureAngle = () => {
+        const loci = this.plugin.managers.structure.selection.history;
+        this.plugin.managers.structure.measurement.addAngle(loci[0].loci, loci[1].loci, loci[2].loci);
+    }
+
+    measureDihedral = () => {
+        const loci = this.plugin.managers.structure.selection.history;
+        this.plugin.managers.structure.measurement.addDihedral(loci[0].loci, loci[1].loci, loci[2].loci, loci[3].loci);
+    }
+
+    addLabel = () => {
+        const loci = this.plugin.managers.structure.selection.history;
+        this.plugin.managers.structure.measurement.addLabel(loci[0].loci);
+    }
+
+    addOrientation = () => {
+        const loci = this.plugin.managers.structure.selection.history;
+        this.plugin.managers.structure.measurement.addOrientation(loci[0].loci);
+    }
+
+
+    get actions(): ActionMenu.Items {
+        const history = this.selection.history;
+        const ret: ActionMenu.Item[] = [];
+
+        if (history.length >= 1) {
+            ret.push(ActionMenu.Item('Label', this.addLabel));
+            ret.push(ActionMenu.Item('Orientation', this.addOrientation));
+        }
+        if (history.length >= 2) {
+            ret.push(ActionMenu.Item('Distance', this.measureDistance));
+        }
+        if (history.length >= 3) {
+            ret.push(ActionMenu.Item('Angle', this.measureAngle));
+        }
+        if (history.length >= 3) {
+            ret.push(ActionMenu.Item('Dihedral Angle', this.measureDihedral));
+        }
+        return ret;
+    }
+    
+    selectAction: ActionMenu.OnSelect = item => {
+        this.toggleAdd();
+        if (!item) return;
+        (item?.value as any)();
+    }
+
+    toggleAdd = () => this.setState({ showActions: !this.state.showActions });
+
+    render() {
+        if (this.state.isDisabled || this.selection.history.length === 0) return null;
+
+        return <>
+            <div className='msp-control-row msp-select-row'>
+                <ToggleButton icon='plus' label='Add Measurement' toggle={this.toggleAdd} isSelected={this.state.showActions} disabled={this.state.isDisabled} />
+            </div>
+            {this.state.showActions && <ActionMenu items={this.actions} onSelect={this.selectAction} />}
+        </>
+    }
+}
+
 export class MeasurementsOptions extends PurePluginUIComponent<{}, { isDisabled: boolean }> {
     state = { isDisabled: false }
 
@@ -89,7 +175,7 @@ export class MeasurementsOptions extends PurePluginUIComponent<{}, { isDisabled:
     render() {
         const measurements = this.plugin.managers.structure.measurement.state;
 
-        return <ExpandGroup header='Options'>
+        return <ExpandGroup header='Global Options'>
             <ParameterControls params={StructureMeasurementParams} values={measurements.options} onChangeObject={this.changed} isDisabled={this.state.isDisabled} />
         </ExpandGroup>;
     }

+ 11 - 63
src/mol-plugin-ui/structure/selection.tsx

@@ -15,7 +15,7 @@ import { ParameterControls } from '../controls/parameters';
 import { stripTags } from '../../mol-util/string';
 import { StructureElement } from '../../mol-model/structure';
 import { ActionMenu } from '../controls/action-menu';
-import { ToggleButton } from '../controls/common';
+import { ToggleButton, ExpandGroup } from '../controls/common';
 import { Icon } from '../controls/icons';
 import { StructureSelectionModifier } from '../../mol-plugin-state/manager/structure/selection';
 
@@ -56,7 +56,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
     get stats() {
         const stats = this.plugin.managers.structure.selection.stats
         if (stats.structureCount === 0 || stats.elementCount === 0) {
-            return 'Selected nothing'
+            return 'Nothing Selected'
         } else {
             return `Selected ${stripTags(stats.label)}`
         }
@@ -82,31 +82,6 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
         }
     }
 
-    measureDistance = () => {
-        const loci = this.plugin.managers.structure.selection.history;
-        this.plugin.managers.structure.measurement.addDistance(loci[0].loci, loci[1].loci);
-    }
-
-    measureAngle = () => {
-        const loci = this.plugin.managers.structure.selection.history;
-        this.plugin.managers.structure.measurement.addAngle(loci[0].loci, loci[1].loci, loci[2].loci);
-    }
-
-    measureDihedral = () => {
-        const loci = this.plugin.managers.structure.selection.history;
-        this.plugin.managers.structure.measurement.addDihedral(loci[0].loci, loci[1].loci, loci[2].loci, loci[3].loci);
-    }
-
-    addLabel = () => {
-        const loci = this.plugin.managers.structure.selection.history;
-        this.plugin.managers.structure.measurement.addLabel(loci[0].loci);
-    }
-
-    addOrientation = () => {
-        const loci = this.plugin.managers.structure.selection.history;
-        this.plugin.managers.structure.measurement.addOrientation(loci[0].loci);
-    }
-
     setProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
         if (p.name === 'granularity') {
             PluginCommands.Interactivity.SetProps(this.plugin, { props: { granularity: p.value } });
@@ -146,10 +121,10 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
 
     get controls() {
         return <div>
-            <div className='msp-control-row msp-button-row'>
-                <ToggleButton label='Select' toggle={this.toggleAdd} isSelected={this.state.queryAction === 'add'} disabled={this.state.isDisabled} />
-                <ToggleButton label='Deselect' toggle={this.toggleRemove} isSelected={this.state.queryAction === 'remove'} disabled={this.state.isDisabled} />
-                <ToggleButton label='Only' toggle={this.toggleOnly} isSelected={this.state.queryAction === 'set'} disabled={this.state.isDisabled} />
+            <div className='msp-control-row msp-select-row'>
+                <ToggleButton icon='plus' label='Select' toggle={this.toggleAdd} isSelected={this.state.queryAction === 'add'} disabled={this.state.isDisabled} />
+                <ToggleButton icon='minus' label='Deselect' toggle={this.toggleRemove} isSelected={this.state.queryAction === 'remove'} disabled={this.state.isDisabled} />
+                <ToggleButton icon='flash' label='Only' toggle={this.toggleOnly} isSelected={this.state.queryAction === 'set'} disabled={this.state.isDisabled} />
             </div>
             {this.state.queryAction && <ActionMenu items={this.queries} onSelect={this.selectQuery} />}
         </div>
@@ -171,7 +146,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
     }
 
     renderControls() {
-        const latest: JSX.Element[] = [];
+        const history: JSX.Element[] = [];
 
         const mng = this.plugin.managers.structure.selection;
 
@@ -179,7 +154,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
 
         for (let i = 0, _i = Math.min(4, mng.history.length); i < _i; i++) {
             const e = mng.history[i];
-            latest.push(<li key={e!.label}>
+            history.push(<li key={e!.label}>
                 <button className='msp-btn msp-btn-block msp-form-control' style={{ borderRight: '6px solid transparent', overflow: 'hidden' }}
                     title='Click to focus.' onClick={this.focusLoci(e.loci)}>
                     <span dangerouslySetInnerHTML={{ __html: e.label.split('|').reverse().join(' | ') }} />
@@ -199,38 +174,11 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
             </div>
             <ParameterControls params={StructureSelectionParams} values={this.values} onChange={this.setProps} isDisabled={this.state.isDisabled} />
             {this.controls}
-            { latest.length > 0 &&
-            <>
-                <div className='msp-control-group-header' style={{ marginTop: '1px' }}><span>Latest Selections &amp; Measurement</span></div>
+            {history.length > 0 && <ExpandGroup header='Selection History'>
                 <ul style={{ listStyle: 'none', marginTop: '1px', marginBottom: '0' }} className='msp-state-list'>
-                    {latest}
+                    {history}
                 </ul>
-                {latest.length >= 1 && <div className='msp-control-row msp-row-text'>
-                    <button className='msp-btn msp-btn-block' onClick={this.addLabel} title='Add label for latest selection'>
-                        Add Label
-                    </button>
-                </div>}
-                {latest.length >= 1 && <div className='msp-control-row msp-row-text'>
-                    <button className='msp-btn msp-btn-block' onClick={this.addOrientation} title='Add orientation box/axes for latest selection'>
-                        Add Orientation
-                    </button>
-                </div>}
-                {latest.length >= 2 && <div className='msp-control-row msp-row-text'>
-                    <button className='msp-btn msp-btn-block' onClick={this.measureDistance} title='Measure distance between latest 2 selections'>
-                        Measure Distance
-                    </button>
-                </div>}
-                {latest.length >= 3 && <div className='msp-control-row msp-row-text'>
-                    <button className='msp-btn msp-btn-block' onClick={this.measureAngle} title='Measure angle between latest 3 selections'>
-                        Measure Angle
-                    </button>
-                </div>}
-                {latest.length >= 4 && <div className='msp-control-row msp-row-text'>
-                    <button className='msp-btn msp-btn-block' onClick={this.measureDihedral} title='Measure dihedral between latest 4 selections'>
-                        Measure Dihedral
-                    </button>
-                </div>}
-            </>}
+            </ExpandGroup>}
         </div>
     }
 }