diff --git a/src/mol-plugin/skin/base/components/temp.scss b/src/mol-plugin/skin/base/components/temp.scss index 587a52509ea651a42f7106b9bcfd0f0583b3bce1..c0ea1796dddf3fb5876e03425e3eeee64d71406d 100644 --- a/src/mol-plugin/skin/base/components/temp.scss +++ b/src/mol-plugin/skin/base/components/temp.scss @@ -48,6 +48,31 @@ } } +.msp-select-row { + display:flex; + flex-direction:row; + height: $row-height; + width: inherit; + + > select { + margin: 0; + flex: 1 1 auto; + margin-right: 1px; + height: $row-height; + + text-align-last: center; + background: none !important; + + > option[value = _] { + display: none; + } + } + + > select:last-child { + margin-right: 0; + } +} + .msp-state-list { list-style: none; margin-top: $control-spacing; @@ -62,7 +87,7 @@ > div { position: absolute; right: 0; - top: 0; + top: 0; } } @@ -88,7 +113,7 @@ &-current { // background: $control-background; - + a { color: $font-color; } @@ -162,7 +187,7 @@ top: $control-spacing; z-index: 10000; - .msp-traj-controls { + .msp-traj-controls { line-height: $row-height; float: left; margin-right: $control-spacing; diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index 31c64b94e133d76048cf17aed55e7694e8b4510e..416dd79dcd1a2ca285d3b485151ea7cd30099f1e 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -16,7 +16,6 @@ import { StateTransforms } from '../../mol-plugin/state/transforms'; import { StateTransformer } from '../../mol-state'; import { ModelFromTrajectory } from '../../mol-plugin/state/transforms/model'; import { AnimationControls } from './state/animation'; -import { StructureOverpaintControls } from './structure/overpaint'; import { StructureRepresentationControls } from './structure/representation'; import { StructureSelectionControls } from './structure/selection'; @@ -261,8 +260,7 @@ export class StructureToolsWrapper extends PluginUIComponent { <div className='msp-section-header'><Icon name='code' /> Structure Tools</div> <StructureSelectionControls /> - <StructureOverpaintControls /> <StructureRepresentationControls /> </div>; } -} \ No newline at end of file +} diff --git a/src/mol-plugin/ui/controls/common.tsx b/src/mol-plugin/ui/controls/common.tsx index 7d3528763407449e732934a8c5b7dcd0e122663c..9fd99992c3da223d28810e7b86f27540996690ed 100644 --- a/src/mol-plugin/ui/controls/common.tsx +++ b/src/mol-plugin/ui/controls/common.tsx @@ -137,6 +137,25 @@ export class ExpandableGroup extends React.Component<{ } } +export class ButtonSelect extends React.PureComponent<{ label: string, onChange: (value: string) => void }> { + + onChange = (e: React.ChangeEvent<HTMLSelectElement>) => { + e.preventDefault() + this.props.onChange(e.target.value) + e.target.value = '_' + } + + render() { + return <select value='_' onChange={this.onChange}> + <option key='_' value='_'>{this.props.label}</option> + {this.props.children} + </select> + } +} + +export function Options(options: [string, string][]) { + return options.map(([value, label]) => <option key={value} value={value}>{label}</option>) +} // export const ToggleButton = (props: { // onChange: (v: boolean) => void, diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index 84f9b0742dbd920a11b3f12a6115d9548ffe23d5..63ae15d197a47e5dcca16163e86f8918245968e2 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -248,7 +248,7 @@ export class BoundedIntervalControl extends SimpleParam<PD.Interval> { } let _colors: React.ReactFragment | undefined = void 0; -function ColorOptions() { +export function ColorOptions() { if (_colors) return _colors; _colors = <>{Object.keys(ColorNames).map(name => <option key={name} value={(ColorNames as { [k: string]: Color })[name]} style={{ background: `${Color.toStyle((ColorNames as { [k: string]: Color })[name])}` }} > diff --git a/src/mol-plugin/ui/structure/overpaint.tsx b/src/mol-plugin/ui/structure/overpaint.tsx deleted file mode 100644 index 5e1070213b24781c55f487cc1481e4689ca6c1ba..0000000000000000000000000000000000000000 --- a/src/mol-plugin/ui/structure/overpaint.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react'; -import { PluginUIComponent } from '../base'; -import { ParamDefinition as PD} from '../../../mol-util/param-definition'; -import { ColorNames } from '../../../mol-util/color/tables'; -import { ParameterControls } from '../controls/parameters'; -import { PluginContext } from '../../context'; -import { Color } from '../../../mol-util/color'; - -export class StructureOverpaintControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof StructureOverpaintControls.getParams>> }> { - state = { params: PD.getDefaultValues(StructureOverpaintControls.getParams(this.plugin)) } - - static getParams = (plugin: PluginContext) => { - const { types } = plugin.structureRepresentation.registry - return { - color: PD.Color(ColorNames.cyan), - type: PD.MultiSelect(types.map(t => t[0]), types) - } - } - - componentDidMount() { - - } - - set = (color: Color | -1) => { - this.plugin.helpers.structureOverpaint.set(color, this.state.params.type) - } - - add = () => { - this.set(this.state.params.color) - } - - clear = () => { - this.set(-1) - } - - clearAll = () => { - this.plugin.helpers.structureOverpaint.clearAll() - } - - render() { - return <div className='msp-transform-wrapper'> - <div className='msp-transform-header'> - <button className='msp-btn msp-btn-block'>Current Selection Overpaint</button> - </div> - <div> - <ParameterControls params={StructureOverpaintControls.getParams(this.plugin)} values={this.state.params} onChange={p => { - const params = { ...this.state.params, [p.name]: p.value }; - this.setState({ params }); - }}/> - - <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clearAll}>Clear All</button> - </div> - </div> - </div> - } -} \ No newline at end of file diff --git a/src/mol-plugin/ui/structure/representation.tsx b/src/mol-plugin/ui/structure/representation.tsx index 5e05374e28ff08c536bd11b97cb2ecb32a8c8043..d5dc2337eacef22f1979b092e385a937089754ff 100644 --- a/src/mol-plugin/ui/structure/representation.tsx +++ b/src/mol-plugin/ui/structure/representation.tsx @@ -6,56 +6,111 @@ import * as React from 'react'; import { PluginUIComponent } from '../base'; -import { ParamDefinition as PD} from '../../../mol-util/param-definition'; -import { ParameterControls } from '../controls/parameters'; -import { PluginContext } from '../../context'; +import { Structure, StructureElement } from '../../../mol-model/structure'; +import { isEmptyLoci } from '../../../mol-model/loci'; +import { ColorOptions } from '../controls/parameters'; +import { Color } from '../../../mol-util/color'; +import { ButtonSelect, Options } from '../controls/common'; +import { StructureSelectionQueries as Q } from '../../util/structure-selection-helper'; -export class StructureRepresentationControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof StructureRepresentationControls.getParams>> }> { - state = { params: PD.getDefaultValues(StructureRepresentationControls.getParams(this.plugin)) } +abstract class BaseStructureRepresentationControls extends PluginUIComponent { + onChange = (value: string) => { + console.log('onChange', value) + } - static getParams = (plugin: PluginContext) => { - const { types } = plugin.structureRepresentation.registry - return { - type: PD.Select(types[0][0], types) - } + abstract label: string + abstract lociGetter(structure: Structure): StructureElement.Loci + + show = (value: string) => { + this.plugin.helpers.structureRepresentation.set('add', value, this.lociGetter) } - componentDidMount() { + hide = (value: string) => { + this.plugin.helpers.structureRepresentation.set('remove', value, this.lociGetter) + } + color = (value: string) => { + const color = Color(parseInt(value)) + this.plugin.helpers.structureOverpaint.set(color, this.lociGetter) } - set = (mode: 'add' | 'remove' | 'only' | 'all') => { - this.plugin.helpers.structureRepresentation.setSelected(mode, this.state.params.type) + render() { + const { types } = this.plugin.structureRepresentation.registry + + return <div className='msp-control-row'> + <span title={this.label}>{this.label}</span> + <div className='msp-select-row'> + <ButtonSelect label='Show' onChange={this.show}> + <optgroup label='Show'> + {Options(types)} + </optgroup> + </ButtonSelect> + <ButtonSelect label='Hide' onChange={this.hide}> + <optgroup label='Clear'> + <option key={-1} value={-1}>TODO: All</option> + </optgroup> + <optgroup label='Hide'> + {Options(types)} + </optgroup> + </ButtonSelect> + <ButtonSelect label='Color' onChange={this.color}> + <optgroup label='Clear'> + <option key={-1} value={-1}>Theme</option> + </optgroup> + <optgroup label='Color'> + {ColorOptions()} + </optgroup> + </ButtonSelect> + </div> + </div> } +} + +class EverythingStructureRepresentationControls extends BaseStructureRepresentationControls { + label = 'Everything' + lociGetter = (structure: Structure) => { + return StructureElement.Loci.all(structure) + } +} + +class SelectionStructureRepresentationControls extends BaseStructureRepresentationControls { + label = 'Selection' + lociGetter = (structure: Structure) => { + const loci = this.plugin.helpers.structureSelectionManager.get(structure) + return isEmptyLoci(loci) ? StructureElement.Loci.none(structure) : loci + } +} + +export class StructureRepresentationControls extends PluginUIComponent { + preset = async () => { + const { structureSelection: sel, structureRepresentation: rep } = this.plugin.helpers + const lociGetter = (structure: Structure) => { + const loci = this.plugin.helpers.structureSelectionManager.get(structure) + return isEmptyLoci(loci) ? StructureElement.Loci.none(structure) : loci + } + + sel.set('add', Q.all()) + await rep.set('add', 'cartoon', lociGetter) + await rep.set('add', 'carbohydrate', lociGetter) - show = () => { this.set('add') } - hide = () => { this.set('remove') } - only = () => { this.set('only') } - showAll = () => { this.set('all') } + sel.set('only', Q.ligandsPlusConnected()) + sel.set('add', Q.branchedConnectedOnly()) + sel.set('add', Q.water()) + await rep.set('add', 'ball-and-stick', lociGetter) - hideAll = () => { - this.plugin.helpers.structureRepresentation.hideAll(this.state.params.type) + sel.set('remove', Q.all()) } render() { return <div className='msp-transform-wrapper'> <div className='msp-transform-header'> - <button className='msp-btn msp-btn-block'>Current Selection Representation</button> + <button className='msp-btn msp-btn-block'>Representation</button> </div> - <div> - <ParameterControls params={StructureRepresentationControls.getParams(this.plugin)} values={this.state.params} onChange={p => { - const params = { ...this.state.params, [p.name]: p.value }; - this.setState({ params }); - }}/> - - <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.show}>Show</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.hide}>Hide</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.only}>Only</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.showAll}>Show All</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.hideAll}>Hide All</button> - </div> + <div className='msp-btn-row-group'> + <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.preset()}>Preset</button> </div> + <EverythingStructureRepresentationControls /> + <SelectionStructureRepresentationControls /> </div> } } \ No newline at end of file diff --git a/src/mol-plugin/ui/structure/selection.tsx b/src/mol-plugin/ui/structure/selection.tsx index 41c23ec88a5944d6743d1420b7e8d2e8a04c4fd6..ea41a211c7060a1f2c4d1ba89cd9707ffe2257c6 100644 --- a/src/mol-plugin/ui/structure/selection.tsx +++ b/src/mol-plugin/ui/structure/selection.tsx @@ -6,9 +6,20 @@ import * as React from 'react'; import { PluginUIComponent } from '../base'; -import { StructureSelection, QueryFn, Queries as _Queries } from '../../../mol-model/structure'; import { formatStructureSelectionStats } from '../../util/structure-element-selection'; import { StructureSelectionQueries } from '../../util/structure-selection-helper'; +import { ButtonSelect, Options } from '../controls/common'; +import { PluginCommands } from '../../command'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { Interactivity } from '../../util/interactivity'; +import { ParameterControls } from '../controls/parameters'; +import { camelCaseToWords } from '../../../mol-util/string'; + +type SelectionModifier = 'add' | 'remove' | 'only' + +const StructureSelectionParams = { + granularity: Interactivity.Params.granularity, +} export class StructureSelectionControls extends PluginUIComponent<{}, {}> { state = {} @@ -17,38 +28,69 @@ export class StructureSelectionControls extends PluginUIComponent<{}, {}> { this.subscribe(this.plugin.events.interactivity.selectionUpdated, () => { this.forceUpdate() }); + + this.subscribe(this.plugin.events.interactivity.propsUpdated, () => { + this.forceUpdate() + }); } get stats() { return formatStructureSelectionStats(this.plugin.helpers.structureSelectionManager.stats) } - select = (query: QueryFn<StructureSelection>) => { - this.plugin.helpers.structureSelection.select(query) + setProps = (p: { param: PD.Base<any>, name: string, value: any }) => { + if (p.name === 'granularity') { + PluginCommands.Interactivity.SetProps.dispatch(this.plugin, { props: { granularity: p.value } }); + } } - clear = () => { - this.plugin.helpers.structureSelection.clearSelection() + get values () { + return { + granularity: this.plugin.interactivity.props.granularity + } } + set = (modifier: SelectionModifier, value: string) => { + const query = StructureSelectionQueries[value as keyof typeof StructureSelectionQueries]() + this.plugin.helpers.structureSelection.set(modifier, query) + } + + add = (value: string) => this.set('add', value) + remove = (value: string) => this.set('remove', value) + only = (value: string) => this.set('only', value) + render() { + const queries = Object.keys(StructureSelectionQueries).map(name => { + return [name, camelCaseToWords(name)] as [string, string] + }) + return <div className='msp-transform-wrapper'> <div className='msp-transform-header'> - <button className='msp-btn msp-btn-block'>Current Selection</button> + <button className='msp-btn msp-btn-block'>Selection</button> </div> <div> <div className='msp-control-row msp-row-text'> <div>{this.stats}</div> </div> - <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.all())}>All</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.clear()}>None</button> - </div> - <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.polymers())}>Polymers</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.ligands())}>Ligands</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.water())}>Water</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.coarse())}>Coarse</button> + <ParameterControls params={StructureSelectionParams} values={this.values} onChange={this.setProps} /> + <div className='msp-control-row'> + <div className='msp-select-row' style={{ background: '#f3f2ee' }}> + <ButtonSelect label='Add' onChange={this.add}> + <optgroup label='Add'> + {Options(queries)} + </optgroup> + </ButtonSelect> + <ButtonSelect label='Remove' onChange={this.remove}> + <optgroup label='Remove'> + {Options(queries)} + </optgroup> + </ButtonSelect> + <ButtonSelect label='Only' onChange={this.only}> + <optgroup label='Only'> + {Options(queries)} + </optgroup> + </ButtonSelect> + </div> </div> </div> </div> diff --git a/src/mol-plugin/util/interactivity.ts b/src/mol-plugin/util/interactivity.ts index 80a5cc7f0eb8b565449250577ece3fc3e98f1311..41289157b72ef799122c586740b5b9dfd02ace1d 100644 --- a/src/mol-plugin/util/interactivity.ts +++ b/src/mol-plugin/util/interactivity.ts @@ -90,13 +90,15 @@ namespace Interactivity { // TODO clear, then re-apply remaining providers } - normalizedLoci(interactivityLoci: Loci) { + normalizedLoci(interactivityLoci: Loci, applyGranularity = true) { let { loci, repr } = interactivityLoci if (this.props.granularity !== 'element' && Link.isLoci(loci)) { // convert Link.Loci to a StructureElement.Loci so granularity can be applied loci = Link.toStructureElementLoci(loci) } - loci = Granularity[this.props.granularity](loci) + if (applyGranularity) { + loci = Granularity[this.props.granularity](loci) + } if (Structure.isLoci(loci)) { // convert to StructureElement.Loci of root structure loci = Structure.toStructureElementLoci(Structure.Loci(loci.structure.parent || loci.structure)) @@ -197,6 +199,24 @@ namespace Interactivity { } } + add(current: Loci<ModelLoci>) { + const normalized: Loci<ModelLoci> = this.normalizedLoci(current, false) + this.sel.add(normalized.loci); + this.mark(normalized, MarkerAction.Select); + } + + remove(current: Loci<ModelLoci>) { + const normalized: Loci<ModelLoci> = this.normalizedLoci(current, false) + this.sel.remove(normalized.loci); + this.mark(normalized, MarkerAction.Deselect); + } + + only(current: Loci<ModelLoci>) { + const sels = this.sel.clear(); + for (const s of sels) this.mark({ loci: s }, MarkerAction.Deselect); + this.add(current); + } + constructor(ctx: PluginContext, props: Partial<Props> = {}) { super(ctx, props) ctx.behaviors.interaction.click.subscribe(e => this.apply(e)); diff --git a/src/mol-plugin/util/structure-overpaint-helper.ts b/src/mol-plugin/util/structure-overpaint-helper.ts index 9bdbce04a4eea9fb28eaa5abb31db51e085ef278..120ea8ef0405d8d5011ae08774b8ee855bc6c80e 100644 --- a/src/mol-plugin/util/structure-overpaint-helper.ts +++ b/src/mol-plugin/util/structure-overpaint-helper.ts @@ -42,12 +42,15 @@ export class StructureOverpaintHelper { await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true })); } - async set(color: Color | -1, types?: string[]) { + async set(color: Color | -1, lociGetter: (structure: Structure) => StructureElement.Loci, types?: string[]) { await this.eachRepr((update, repr, rootStructure, overpaint) => { if (types && !types.includes(repr.params!.values.type.name)) return - const loci = this.plugin.helpers.structureSelectionManager.get(rootStructure) - if (isEmptyLoci(loci) || loci.elements.length === 0) return + // TODO cleanup when loci is full structure or empty + // TODO add & use QueryOverpaintStructureRepresentation3D + + const loci = lociGetter(rootStructure) + if (loci.elements.length === 0) return const expression = getExpression(loci) const layer = { @@ -65,20 +68,6 @@ export class StructureOverpaintHelper { }) } - add(color: Color, types?: string[]) { - this.set(color, types) - } - - clear(types?: string[]) { - this.set(-1, types) - } - - clearAll() { - this.eachRepr((update, repr, rootStructure, overpaint) => { - if (overpaint) update.delete(overpaint.transform.ref) - }) - } - constructor(private plugin: PluginContext) { } diff --git a/src/mol-plugin/util/structure-representation-helper.ts b/src/mol-plugin/util/structure-representation-helper.ts index 038f4ecd9f653a2dfb991f53f49875cf6d02f83c..7db921f500afaa9e61ef52712c3cda2b96feac20 100644 --- a/src/mol-plugin/util/structure-representation-helper.ts +++ b/src/mol-plugin/util/structure-representation-helper.ts @@ -7,8 +7,7 @@ import { PluginStateObject } from '../../mol-plugin/state/objects'; import { StateTransforms } from '../../mol-plugin/state/transforms'; import { StateTransformer, StateSelection, StateObjectCell, StateTransform } from '../../mol-state'; -import { StructureElement } from '../../mol-model/structure'; -import { isEmptyLoci } from '../../mol-model/loci'; +import { StructureElement, Structure } from '../../mol-model/structure'; import { PluginContext } from '../context'; import { StructureRepresentation3DHelpers } from '../state/transforms/representation'; @@ -24,14 +23,13 @@ function getCombinedLoci(mode: SelectionModifier, loci: StructureElement.Loci, c case 'add': return StructureElement.Loci.union(loci, currentLoci) case 'remove': return StructureElement.Loci.subtract(currentLoci, loci) case 'only': return loci - case 'all': return StructureElement.Loci.all(loci.structure) } } -type SelectionModifier = 'add' | 'remove' | 'only' | 'all' +type SelectionModifier = 'add' | 'remove' | 'only' export class StructureRepresentationHelper { - async set(modifier: SelectionModifier, type: string, loci: StructureElement.Loci, structure: StructureTransform) { + private async _set(modifier: SelectionModifier, type: string, loci: StructureElement.Loci, structure: StructureTransform) { const state = this.plugin.state.dataState const update = state.build(); const s = structure.obj!.data @@ -67,28 +65,17 @@ export class StructureRepresentationHelper { await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true })); } - async setSelected(modifier: SelectionModifier, type: string) { + async set(modifier: SelectionModifier, type: string, lociGetter: (structure: Structure) => StructureElement.Loci) { const state = this.plugin.state.dataState; const structures = state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)); for (const structure of structures) { const s = structure.obj!.data - const _loci = this.plugin.helpers.structureSelectionManager.get(s) - const loci = isEmptyLoci(_loci) ? StructureElement.Loci(s, []) : _loci - - await this.set(modifier, type, loci, structure) + const loci = lociGetter(s) + await this._set(modifier, type, loci, structure) } } - async hideAll(type: string) { - const state = this.plugin.state.dataState; - const update = state.build(); - - state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure).withTag(getRepresentationManagerTag(type))).forEach(structure => update.delete(structure.transform.ref)); - - await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true })); - } - constructor(private plugin: PluginContext) { } diff --git a/src/mol-plugin/util/structure-selection-helper.ts b/src/mol-plugin/util/structure-selection-helper.ts index f159f000ae2e3f9200cabcd9a611dacf3524a283..89e229e5f9d653f8dca50b58c5403fedd6ffe2fb 100644 --- a/src/mol-plugin/util/structure-selection-helper.ts +++ b/src/mol-plugin/util/structure-selection-helper.ts @@ -7,48 +7,133 @@ import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; import { StateSelection } from '../../mol-state'; import { PluginStateObject } from '../state/objects'; -import { QueryContext, StructureSelection, QueryFn, Queries as _Queries } from '../../mol-model/structure'; +import { QueryContext, StructureSelection, QueryFn } from '../../mol-model/structure'; import { compile } from '../../mol-script/runtime/query/compiler'; -import { ButtonsType } from '../../mol-util/input/input-observer'; -import { EmptyLoci } from '../../mol-model/loci'; +import { Loci } from '../../mol-model/loci'; import { PluginContext } from '../context'; +const polymers = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']) + }) +]) + +const backboneTrace = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'atom-test': MS.core.logic.or([ + MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']), + MS.core.rel.eq([MS.ammp('label_atom_id'), 'P']) + ]) + }) +]) + +const water = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'water']) + }) +]) + +const branched = MS.struct.modifier.union([ + MS.struct.combinator.merge([ + MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'branched']) + }) + ]), + MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']), + 'residue-test': MS.core.str.match([MS.re('saccharide', 'i'), MS.ammp('chemCompType')]) + }) + ]) + ]) +]) + +const branchedPlusConnected = MS.struct.modifier.union([ + MS.struct.modifier.includeConnected({ + 0: branched, 'layer-count': 1, 'as-whole-residues': true + }) +]) + +const branchedConnectedOnly = MS.struct.modifier.union([ + MS.struct.modifier.exceptBy({ + 0: branchedPlusConnected, + by: branched + }) +]) + +const ligands = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.logic.and([ + MS.core.rel.neq([MS.ammp('entityType'), 'branched']), + MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']) + ]), + 'residue-test': MS.core.logic.not([ + MS.core.str.match([MS.re('saccharide', 'i'), MS.ammp('chemCompType')]) + ]) + }) +]) + +const ligandsPlusConnected = MS.struct.modifier.union([ + MS.struct.modifier.includeConnected({ + 0: ligands, 'layer-count': 1, 'as-whole-residues': true + }) +]) + +const coarse = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'chain-test': MS.core.set.has([ + MS.set('sphere', 'gaussian'), MS.ammp('objectPrimitive') + ]) + }) +]) + export const StructureSelectionQueries = { all: () => compile<StructureSelection>(MS.struct.generator.all()), - polymers: () => _Queries.internal.atomicSequence(), - water: () => _Queries.internal.water(), - ligands: () => _Queries.internal.atomicHet(), - coarse: () => _Queries.internal.spheres(), + polymers: () => compile<StructureSelection>(polymers), + backboneTrace: () => compile<StructureSelection>(backboneTrace), + water: () => compile<StructureSelection>(water), + branched: () => compile<StructureSelection>(branched), + branchedPlusConnected: () => compile<StructureSelection>(branchedPlusConnected), + branchedConnectedOnly: () => compile<StructureSelection>(branchedConnectedOnly), + ligands: () => compile<StructureSelection>(ligands), + ligandsPlusConnected: () => compile<StructureSelection>(ligandsPlusConnected), + coarse: () => compile<StructureSelection>(coarse), } +// + +type SelectionModifier = 'add' | 'remove' | 'only' + export class StructureSelectionHelper { - select(query: QueryFn<StructureSelection>) { + private get structures() { const state = this.plugin.state.dataState - const structures = state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)) + return state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)) + } + + private _set(modifier: SelectionModifier, loci: Loci) { + switch (modifier) { + case 'add': + this.plugin.interactivity.lociSelections.add({ loci }) + break + case 'remove': + this.plugin.interactivity.lociSelections.remove({ loci }) + break + case 'only': + this.plugin.interactivity.lociSelections.only({ loci }) + break + } + } - for (const so of structures) { + set(modifier: SelectionModifier, query: QueryFn<StructureSelection>) { + for (const so of this.structures) { const s = so.obj!.data const result = query(new QueryContext(s)) const loci = StructureSelection.toLoci2(result) - - // TODO use better API when available - this.plugin.interactivity.lociSelections.apply({ - current: { loci }, - buttons: ButtonsType.Flag.Secondary, - modifiers: { shift: false, alt: false, control: true, meta: false } - }) + this._set(modifier, loci) } } - clearSelection() { - // TODO use better API when available - this.plugin.interactivity.lociSelections.apply({ - current: { loci: EmptyLoci }, - buttons: ButtonsType.Flag.Secondary, - modifiers: { shift: false, alt: false, control: true, meta: false } - }) - } - constructor(private plugin: PluginContext) { }