Browse Source

Merge pull request #283 from MadCatX/improve-measurements-ux

Improve measurements user experience
David Sehnal 3 years ago
parent
commit
d232b01cf9

+ 45 - 1
src/mol-plugin-state/manager/structure/measurement.ts

@@ -17,10 +17,12 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { MeasurementRepresentationCommonTextParams, LociLabelTextParams } from '../../../mol-repr/shape/loci/common';
 import { LineParams } from '../../../mol-repr/structure/representation/line';
 import { Expression } from '../../../mol-script/language/expression';
+import { Color } from '../../../mol-util/color';
 
 export { StructureMeasurementManager };
 
 export const MeasurementGroupTag = 'measurement-group';
+export const MeasurementOrderLabelTag = 'measurement-order-label';
 
 export type StructureMeasurementCell = StateObjectCell<PluginStateObject.Shape.Representation3D, StateTransform<StateTransformer<PluginStateObject.Molecule.Structure.Selections, PluginStateObject.Shape.Representation3D, any>>>
 
@@ -281,6 +283,41 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
         await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
     }
 
+    async addOrderLabels(locis: StructureElement.Loci[]) {
+        const update = this.getGroup();
+
+        const current = this.plugin.state.data.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Selections).withTag(MeasurementOrderLabelTag));
+        for (const obj of current)
+            update.delete(obj);
+
+        let order = 1;
+        for (const loci of locis) {
+            const cell = this.plugin.helpers.substructureParent.get(loci.structure);
+            if (!cell) continue;
+
+            const dependsOn = [cell.transform.ref];
+
+            update
+                .apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
+                    selections: [
+                        { key: 'a', ref: cell.transform.ref, expression: StructureElement.Loci.toExpression(loci) },
+                    ],
+                    isTransitive: true,
+                    label: 'Order'
+                }, { dependsOn, tags: MeasurementOrderLabelTag })
+                .apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
+                    textColor: Color.fromRgb(255, 255, 255),
+                    borderColor: Color.fromRgb(0, 0, 0),
+                    borderWidth: 0.5,
+                    textSize: 0.33,
+                    customText: `${order++}`
+                }, { tags: MeasurementOrderLabelTag });
+        }
+
+        const state = this.plugin.state.data;
+        await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
+    }
+
     private _empty: any[] = [];
     private getTransforms<T extends StateTransformer<A, B, any>, A extends PluginStateObject.Molecule.Structure.Selections, B extends StateObject>(transformer: T) {
         const state = this.plugin.state.data;
@@ -291,8 +328,15 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
     }
 
     private sync() {
+        const labels = [];
+        for (const cell of this.getTransforms(StateTransforms.Representation.StructureSelectionsLabel3D) as StructureMeasurementCell[]) {
+            const tags = (cell.obj as any)['tags'] as string[];
+            if (!tags || !tags.includes(MeasurementOrderLabelTag))
+                labels.push(cell);
+        }
+
         const updated = this.updateState({
-            labels: this.getTransforms(StateTransforms.Representation.StructureSelectionsLabel3D),
+            labels,
             distances: this.getTransforms(StateTransforms.Representation.StructureSelectionsDistance3D),
             angles: this.getTransforms(StateTransforms.Representation.StructureSelectionsAngle3D),
             dihedrals: this.getTransforms(StateTransforms.Representation.StructureSelectionsDihedral3D),

+ 30 - 3
src/mol-plugin-ui/structure/measurements.tsx

@@ -62,7 +62,6 @@ export class MeasurementList extends PurePluginUIComponent {
 
     render() {
         const measurements = this.plugin.managers.structure.measurement.state;
-
         return <div style={{ marginTop: '6px' }}>
             {this.renderGroup(measurements.labels, 'Labels')}
             {this.renderGroup(measurements.distances, 'Distances')}
@@ -80,6 +79,7 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
     componentDidMount() {
         this.subscribe(this.selection.events.additionsHistoryUpdated, () => {
             this.forceUpdate();
+            this.updateOrderLabels();
         });
 
         this.subscribe(this.plugin.behaviors.state.isBusy, v => {
@@ -87,6 +87,33 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
         });
     }
 
+    componentWillUnmount() {
+        this.clearOrderLabels();
+        super.componentWillUnmount();
+    }
+
+    componentDidUpdate(prevProps: {}, prevState: { isBusy: boolean, action?: 'add' | 'options' }) {
+        if (this.state.action !== prevState.action)
+            this.updateOrderLabels();
+    }
+
+    clearOrderLabels() {
+        this.plugin.managers.structure.measurement.addOrderLabels([]);
+    }
+
+    updateOrderLabels() {
+        if (this.state.action !== 'add') {
+            this.clearOrderLabels();
+            return;
+        }
+
+        const locis = [];
+        const history = this.selection.additionsHistory;
+        for (let idx = 0; idx < history.length && idx < 4; idx++)
+            locis.push(history[idx].loci);
+        this.plugin.managers.structure.measurement.addOrderLabels(locis);
+    }
+
     get selection() {
         return this.plugin.managers.structure.selection;
     }
@@ -163,8 +190,8 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
 
     historyEntry(e: StructureSelectionHistoryEntry, idx: number) {
         const history = this.plugin.managers.structure.selection.additionsHistory;
-        return <div className='msp-flex-row' key={e.id}>
-            <Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={() => this.plugin.managers.interactivity.lociHighlights.clearHighlights()}>
+        return <div className='msp-flex-row' key={e.id} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={() => this.plugin.managers.interactivity.lociHighlights.clearHighlights()}>
+            <Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }}>
                 {idx}. <span dangerouslySetInnerHTML={{ __html: e.label }} />
             </Button>
             {history.length > 1 && <IconButton svg={ArrowUpwardSvg} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'up')} flex='20px' title={'Move up'} />}