Parcourir la source

intersect modifier for ui selections

Alexander Rose il y a 5 ans
Parent
commit
607284585c

+ 16 - 0
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);

+ 11 - 2
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 {

+ 19 - 1
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

+ 2 - 2
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>;
     }
 }

+ 1 - 2
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> } {

+ 9 - 6
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>