123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- * @author David Sehnal <david.sehnal@gmail.com>
- */
- import { EveryLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
- import { Structure, StructureElement } from '../../mol-model/structure';
- import { PluginContext } from '../../mol-plugin/context';
- import { Representation } from '../../mol-repr/representation';
- import { ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
- import { MarkerAction } from '../../mol-util/marker-action';
- import { shallowEqual } from '../../mol-util/object';
- import { ParamDefinition as PD } from '../../mol-util/param-definition';
- import { StatefulPluginComponent } from '../component';
- import { StructureSelectionManager } from './structure/selection';
- import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
- export { InteractivityManager };
- interface InteractivityManagerState {
- props: PD.ValuesFor<InteractivityManager.Params>
- }
- // TODO: make this customizable somewhere?
- const DefaultInteractivityFocusOptions = {
- minRadius: 6,
- extraRadius: 6,
- durationMs: 250,
- };
- export type InteractivityFocusLociOptions = typeof DefaultInteractivityFocusOptions
- class InteractivityManager extends StatefulPluginComponent<InteractivityManagerState> {
- readonly lociSelects: InteractivityManager.LociSelectManager;
- readonly lociHighlights: InteractivityManager.LociHighlightManager;
- private _props = PD.getDefaultValues(InteractivityManager.Params)
- readonly events = {
- propsUpdated: this.ev()
- };
- get props(): Readonly<InteractivityManagerState['props']> { return { ...this.state.props }; }
- setProps(props: Partial<InteractivityManager.Props>) {
- const old = this.props;
- const _new = { ...this.state.props, ...props };
- if (shallowEqual(old, _new)) return;
- this.updateState({ props: _new });
- this.lociSelects.setProps(_new);
- this.lociHighlights.setProps(_new);
- this.events.propsUpdated.next(void 0);
- }
- constructor(readonly plugin: PluginContext, props: Partial<InteractivityManager.Props> = {}) {
- super({ props: { ...PD.getDefaultValues(InteractivityManager.Params), ...props } });
- this.lociSelects = new InteractivityManager.LociSelectManager(plugin, this._props);
- this.lociHighlights = new InteractivityManager.LociHighlightManager(plugin, this._props);
- }
- }
- namespace InteractivityManager {
- export const Params = {
- granularity: PD.Select('residue', Loci.GranularityOptions, { label: 'Picking Level', description: 'Controls if selections are expanded upon picking to whole residues, chains, structures, instances, or left as atoms and coarse elements' }),
- };
- export type Params = typeof Params
- export type Props = PD.Values<Params>
- export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
- export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
- export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
- /**
- * The `noRender` argument indicates that the action should only update the internal
- * data structure but not render anything user visible. For example 1) no drawing of
- * the canvas3d scene or 2) no ui update of loci labels. This is useful because some
- * actions require clearing any markings before they can be applied.
- */
- export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction, /* test */ noRender?: boolean) => void
- export abstract class LociMarkManager {
- protected providers: LociMarkProvider[] = [];
- protected sel: StructureSelectionManager
- readonly props: Readonly<Props> = PD.getDefaultValues(Params)
- setProps(props: Partial<Props>) {
- Object.assign(this.props, props);
- }
- addProvider(provider: LociMarkProvider) {
- this.providers.push(provider);
- }
- removeProvider(provider: LociMarkProvider) {
- this.providers = this.providers.filter(p => p !== provider);
- // TODO clear, then re-apply remaining providers
- }
- protected normalizedLoci(reprLoci: Representation.Loci, applyGranularity = true) {
- const { loci, repr } = reprLoci;
- const granularity = applyGranularity ? this.props.granularity : undefined;
- return { loci: Loci.normalize(loci, granularity), repr };
- }
- protected mark(current: Representation.Loci, action: MarkerAction, noRender = false) {
- if (!Loci.isEmpty(current.loci)) {
- for (const p of this.providers) p(current, action, noRender);
- }
- }
- constructor(public readonly ctx: PluginContext, props: Partial<Props> = {}) {
- this.sel = ctx.managers.structure.selection;
- this.setProps(props);
- }
- }
- //
- export class LociHighlightManager extends LociMarkManager {
- private prev: Representation.Loci[] = [];
- private isHighlighted(loci: Representation.Loci) {
- for (const p of this.prev) {
- if (Representation.Loci.areEqual(p, loci)) return true;
- }
- return false;
- }
- private addHighlight(loci: Representation.Loci) {
- this.mark(loci, MarkerAction.Highlight);
- this.prev.push(loci);
- }
- clearHighlights = (noRender = false) => {
- for (const p of this.prev) {
- this.mark(p, MarkerAction.RemoveHighlight, noRender);
- }
- this.prev.length = 0;
- }
- highlight(current: Representation.Loci, applyGranularity = true) {
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (!this.isHighlighted(normalized)) {
- this.addHighlight(normalized);
- }
- }
- highlightOnly(current: Representation.Loci, applyGranularity = true) {
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (!this.isHighlighted(normalized)) {
- if (Loci.isEmpty(normalized.loci)) {
- this.clearHighlights();
- } else {
- this.clearHighlights(true);
- this.addHighlight(normalized);
- }
- }
- }
- highlightOnlyExtend(current: Representation.Loci, applyGranularity = true) {
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (StructureElement.Loci.is(normalized.loci)) {
- const extended = {
- loci: this.sel.tryGetRange(normalized.loci) || normalized.loci,
- repr: normalized.repr
- };
- if (!this.isHighlighted(extended)) {
- if (Loci.isEmpty(extended.loci)) {
- this.clearHighlights();
- } else {
- this.clearHighlights(true);
- this.addHighlight(extended);
- }
- }
- }
- }
- }
- //
- export class LociSelectManager extends LociMarkManager {
- toggle(current: Representation.Loci, applyGranularity = true) {
- if (Loci.isEmpty(current.loci)) return;
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (StructureElement.Loci.is(normalized.loci)) {
- this.toggleSel(normalized);
- } else {
- super.mark(normalized, MarkerAction.Toggle);
- }
- }
- toggleExtend(current: Representation.Loci, applyGranularity = true) {
- if (Loci.isEmpty(current.loci)) return;
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (StructureElement.Loci.is(normalized.loci)) {
- const loci = this.sel.tryGetRange(normalized.loci) || normalized.loci;
- this.toggleSel({ loci, repr: normalized.repr });
- }
- }
- select(current: Representation.Loci, applyGranularity = true) {
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (StructureElement.Loci.is(normalized.loci)) {
- this.sel.modify('add', normalized.loci);
- }
- this.mark(normalized, MarkerAction.Select);
- }
- selectJoin(current: Representation.Loci, 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: Representation.Loci, applyGranularity = true) {
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (StructureElement.Loci.is(normalized.loci)) {
- // only deselect for the structure of the given loci
- this.deselect({ loci: Structure.toStructureElementLoci(normalized.loci.structure), repr: normalized.repr }, false);
- this.sel.modify('set', normalized.loci);
- }
- this.mark(normalized, MarkerAction.Select);
- }
- deselect(current: Representation.Loci, applyGranularity = true) {
- const normalized = this.normalizedLoci(current, applyGranularity);
- if (StructureElement.Loci.is(normalized.loci)) {
- this.sel.modify('remove', normalized.loci);
- }
- this.mark(normalized, MarkerAction.Deselect);
- }
- deselectAll() {
- this.sel.clear();
- this.mark({ loci: EveryLoci }, MarkerAction.Deselect);
- }
- deselectAllOnEmpty(current: Representation.Loci) {
- if (isEmptyLoci(current.loci)) this.deselectAll();
- }
- protected mark(current: Representation.Loci, action: MarkerAction.Select | MarkerAction.Deselect) {
- const { loci } = current;
- if (!Loci.isEmpty(loci)) {
- if (StructureElement.Loci.is(loci)) {
- // 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, true);
- super.mark({ loci: this.sel.getLoci(loci.structure) }, MarkerAction.Select);
- } else {
- super.mark(current, action);
- }
- }
- }
- private toggleSel(current: Representation.Loci) {
- if (this.sel.has(current.loci)) {
- this.sel.modify('remove', current.loci);
- this.mark(current, MarkerAction.Deselect);
- } else {
- this.sel.modify('add', current.loci);
- this.mark(current, MarkerAction.Select);
- }
- }
- }
- }
|