Skip to content
Snippets Groups Projects
Commit 60728458 authored by Alexander Rose's avatar Alexander Rose
Browse files

intersect modifier for ui selections

parent f7af352f
No related branches found
No related tags found
No related merge requests found
......@@ -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);
......
......@@ -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 {
......
......@@ -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
......
......@@ -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>;
}
}
......
......@@ -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> } {
......
......@@ -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>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment