diff --git a/src/mol-model/structure/structure/element/loci.ts b/src/mol-model/structure/structure/element/loci.ts
index 3e9c13faa2997eab7352a1b6b80e266d40638386..cf3ab7569da498ed159a8c849f8b9fffb1c28066 100644
--- a/src/mol-model/structure/structure/element/loci.ts
+++ b/src/mol-model/structure/structure/element/loci.ts
@@ -188,6 +188,22 @@ export namespace Loci {
return Loci(xs.structure, elements);
}
+ /** Intersect `xs` and `ys` */
+ export function intersect(xs: Loci, ys: Loci): Loci {
+ const map = new Map<number, OrderedSet<UnitIndex>>();
+ for (const e of xs.elements) map.set(e.unit.id, e.indices);
+
+ const elements: Loci['elements'][0][] = [];
+ for (const e of ys.elements) {
+ if (!map.has(e.unit.id)) continue;
+ const indices = OrderedSet.intersect(map.get(e.unit.id)!, e.indices);
+ if (OrderedSet.size(indices) === 0) continue;
+ elements[elements.length] = { unit: e.unit, indices };
+ }
+
+ return Loci(xs.structure, elements);
+ }
+
export function areIntersecting(xs: Loci, ys: Loci): boolean {
if (xs.elements.length > ys.elements.length) return areIntersecting(ys, xs);
if (Loci.isEmpty(xs)) return Loci.isEmpty(ys);
diff --git a/src/mol-plugin-state/manager/interactivity.ts b/src/mol-plugin-state/manager/interactivity.ts
index 1253bcbadc651b38fc5718687d2a7a234580dfd3..459571b4368ee7b3a8fcd2423a7f999f064ae5ca 100644
--- a/src/mol-plugin-state/manager/interactivity.ts
+++ b/src/mol-plugin-state/manager/interactivity.ts
@@ -196,6 +196,14 @@ namespace InteractivityManager {
this.mark(normalized, MarkerAction.Select);
}
+ selectJoin(current: Loci<ModelLoci>, applyGranularity = true) {
+ const normalized = this.normalizedLoci(current, applyGranularity)
+ if (StructureElement.Loci.is(normalized.loci)) {
+ this.sel.modify('intersect', normalized.loci);
+ }
+ this.mark(normalized, MarkerAction.Select);
+ }
+
selectOnly(current: Loci<ModelLoci>, applyGranularity = true) {
this.deselectAll()
const normalized = this.normalizedLoci(current, applyGranularity)
@@ -225,8 +233,9 @@ namespace InteractivityManager {
protected mark(current: Loci<ModelLoci>, action: MarkerAction.Select | MarkerAction.Deselect) {
const { loci } = current
if (StructureElement.Loci.is(loci)) {
- // do a full deselect/select for the current structure so visuals
- // that are marked with granularity unequal to 'element' are handled properly
+ // do a full deselect/select for the current structure so visuals that are
+ // marked with granularity unequal to 'element' and join/intersect operations
+ // are handled properly
super.mark({ loci: Structure.Loci(loci.structure) }, MarkerAction.Deselect)
super.mark({ loci: this.sel.getLoci(loci.structure) }, MarkerAction.Select)
} else {
diff --git a/src/mol-plugin-state/manager/structure/selection.ts b/src/mol-plugin-state/manager/structure/selection.ts
index 4d735418f27633481f65f1e32b345e20e79422f2..de1866da39ac15e5b90d5b1f4e8002cca2acac2c 100644
--- a/src/mol-plugin-state/manager/structure/selection.ts
+++ b/src/mol-plugin-state/manager/structure/selection.ts
@@ -30,7 +30,7 @@ interface StructureSelectionManagerState {
const boundaryHelper = new BoundaryHelper('98');
const HISTORY_CAPACITY = 8;
-export type StructureSelectionModifier = 'add' | 'remove' | 'set'
+export type StructureSelectionModifier = 'add' | 'remove' | 'intersect' | 'set'
export class StructureSelectionManager extends PluginComponent<StructureSelectionManagerState> {
readonly events = {
@@ -107,6 +107,19 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
return !StructureElement.Loci.areEqual(sel, entry.selection);
}
+ private intersect(loci: Loci): boolean {
+ if (!StructureElement.Loci.is(loci)) return false;
+
+ const entry = this.getEntry(loci.structure);
+ if (!entry) return false;
+
+ const sel = entry.selection;
+ entry.selection = StructureElement.Loci.intersect(entry.selection, loci);
+ this.addHistory(loci);
+ this.referenceLoci = loci
+ return !StructureElement.Loci.areEqual(sel, entry.selection);
+ }
+
private set(loci: Loci) {
if (!StructureElement.Loci.is(loci)) return false;
@@ -115,6 +128,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
const sel = entry.selection;
entry.selection = loci;
+ this.addHistory(loci);
this.referenceLoci = undefined;
return !StructureElement.Loci.areEqual(sel, entry.selection);
}
@@ -329,6 +343,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
switch (modifier) {
case 'add': changed = this.add(loci); break;
case 'remove': changed = this.remove(loci); break;
+ case 'intersect': changed = this.intersect(loci); break;
case 'set': changed = this.set(loci); break;
}
@@ -352,6 +367,9 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
case 'remove':
this.plugin.managers.interactivity.lociSelects.deselect({ loci }, applyGranularity)
break
+ case 'intersect':
+ this.plugin.managers.interactivity.lociSelects.selectJoin({ loci }, applyGranularity)
+ break
case 'set':
this.plugin.managers.interactivity.lociSelects.selectOnly({ loci }, applyGranularity)
break
diff --git a/src/mol-plugin-ui/controls/common.tsx b/src/mol-plugin-ui/controls/common.tsx
index ce264d9e6c12b253c0eeb735555112dc5e2a5609..8dfd97e9720a6ba3dee22d6cf909bbce04a6c156 100644
--- a/src/mol-plugin-ui/controls/common.tsx
+++ b/src/mol-plugin-ui/controls/common.tsx
@@ -308,7 +308,7 @@ export type ToggleButtonProps = {
style?: React.CSSProperties,
className?: string,
disabled?: boolean,
- label: string | JSX.Element,
+ label?: string | JSX.Element,
title?: string,
icon?: IconName,
isSelected?: boolean,
@@ -327,7 +327,7 @@ export class ToggleButton extends React.PureComponent<ToggleButtonProps> {
return <button onClick={this.onClick} title={this.props.title}
disabled={props.disabled} style={props.style} className={props.className}>
<Icon name={this.props.icon} />
- {this.props.isSelected ? <b>{label}</b> : label}
+ {label && this.props.isSelected ? <b>{label}</b> : label}
</button>;
}
}
diff --git a/src/mol-plugin-ui/structure/measurements.tsx b/src/mol-plugin-ui/structure/measurements.tsx
index 703be8a3e20181edef4eccf0cc803b302500b22a..2927255effb9c15aae7dcad44dd8329eb543e7e5 100644
--- a/src/mol-plugin-ui/structure/measurements.tsx
+++ b/src/mol-plugin-ui/structure/measurements.tsx
@@ -129,7 +129,7 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
];
return ret;
}
-
+
selectAction: ActionMenu.OnSelect = item => {
this.toggleAdd();
if (!item) return;
@@ -256,7 +256,6 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
<IconButton small={true} customClass='msp-form-control' onClick={this.toggleVisibility} icon='eye' style={{ width: '52px' }} title={cell.state.isHidden ? 'Show' : 'Hide'} toggleState={!cell.state.isHidden} />
</div>
}
-
}
function toLociBundle(data: FiniteArray<{ loci: Loci }, any>): { loci: FiniteArray<Loci, any> } {
diff --git a/src/mol-plugin-ui/structure/selection.tsx b/src/mol-plugin-ui/structure/selection.tsx
index 7b0d6050090c3b86de41b2176ff5e9837b611bc6..45620ac84010fe6d79ac0ae1165b1f570bad230c 100644
--- a/src/mol-plugin-ui/structure/selection.tsx
+++ b/src/mol-plugin-ui/structure/selection.tsx
@@ -130,16 +130,19 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
toggleAdd = this.showAction('add')
toggleRemove = this.showAction('remove')
- toggleOnly = this.showAction('set')
+ toggleIntersect = this.showAction('intersect')
+ toggleSet = this.showAction('set')
toggleColor = this.showAction('color')
+ // TODO better icons
get controls() {
return <div>
<div className='msp-control-row msp-select-row'>
- <ToggleButton icon='plus' label='Add' toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
- <ToggleButton icon='minus' label='Rem' toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
- <ToggleButton icon='flash' label='Set' toggle={this.toggleOnly} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
- <ToggleButton icon='brush' label='Color' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} />
+ <ToggleButton icon='plus' title='Add' toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
+ <ToggleButton icon='minus' title='Remove' toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
+ <ToggleButton icon='star' title='Intersect' toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
+ <ToggleButton icon='flash' title='Set' toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
+ <ToggleButton icon='brush' title='Color' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} />
</div>
{(this.state.action && this.state.action !== 'color') && <ActionMenu items={this.queries} onSelect={this.selectQuery} />}
{this.state.action === 'color' && <div className='msp-control-offset'><ApplyColorControls /></div>}
@@ -172,7 +175,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];
history.push(<li key={e!.label}>
- <button className='msp-btn msp-btn-block msp-form-control' style={{ borderRight: '6px solid transparent', overflow: 'hidden' }}
+ <button className='msp-btn msp-btn-block msp-form-control' style={{ overflow: 'hidden' }}
title='Click to focus.' onClick={this.focusLoci(e.loci)}>
<span dangerouslySetInnerHTML={{ __html: e.label.split('|').reverse().join(' | ') }} />
</button>