diff --git a/src/mol-plugin-state/manager/structure/measurement.ts b/src/mol-plugin-state/manager/structure/measurement.ts index bbd3f14226a471b5c3bfb4c1c8cc8bd8ddffce0a..c18075a8f1319b7dbf93121d12f5347757066621 100644 --- a/src/mol-plugin-state/manager/structure/measurement.ts +++ b/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), diff --git a/src/mol-plugin-ui/structure/measurements.tsx b/src/mol-plugin-ui/structure/measurements.tsx index 29763a3cfe262d53a12b4a2ecbfbf8b833caf3f1..b9a8f08f93c8d8d16ac58500a52326663fcf948c 100644 --- a/src/mol-plugin-ui/structure/measurements.tsx +++ b/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'} />}