Bladeren bron

mol-plugin: PluginContext refactoring

David Sehnal 5 jaren geleden
bovenliggende
commit
dc72dbbc97

+ 3 - 3
src/apps/basic-wrapper/index.ts

@@ -48,7 +48,7 @@ class BasicWrapper {
         });
 
         this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(StripedResidues.propertyProvider.descriptor.name, StripedResidues.colorThemeProvider!);
-        this.plugin.lociLabels.addProvider(StripedResidues.labelProvider!);
+        this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
         this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
     }
 
@@ -173,10 +173,10 @@ class BasicWrapper {
                 'group-by': Q.struct.atomProperty.macromolecular.residueKey()
             }), data);
             const loci = StructureSelection.toLociWithSourceUnits(sel);
-            this.plugin.interactivity.lociHighlights.highlightOnly({ loci });
+            this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
         },
         clearHighlight: () => {
-            this.plugin.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
+            this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
         }
     }
 

+ 1 - 1
src/examples/proteopedia-wrapper/index.ts

@@ -72,7 +72,7 @@ class MolStarProteopediaWrapper {
 
         this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add('proteopedia-custom', customColoring);
         this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.propertyProvider.descriptor.name, EvolutionaryConservation.colorThemeProvider!);
-        this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
+        this.plugin.managers.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
         this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider, true);
     }
 

+ 1 - 1
src/mol-model-props/common/custom-element-property.ts

@@ -15,7 +15,7 @@ import { Loci } from '../../mol-model/loci';
 import { OrderedSet } from '../../mol-data/int';
 import { CustomModelProperty } from './custom-model-property';
 import { CustomProperty } from './custom-property';
-import { LociLabelProvider } from '../../mol-plugin/util/loci-label-manager';
+import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
 
 export { CustomElementProperty };
 

+ 13 - 13
src/mol-plugin/util/interactivity.ts → src/mol-plugin-state/manager/interactivity.ts

@@ -10,38 +10,38 @@ import { ModifiersKeys, ButtonsType } from '../../mol-util/input/input-observer'
 import { Representation } from '../../mol-repr/representation';
 import { StructureElement } from '../../mol-model/structure';
 import { MarkerAction } from '../../mol-util/marker-action';
-import { PluginContext } from '../context';
+import { PluginContext } from '../../mol-plugin/context';
 import { Structure } from '../../mol-model/structure';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { PluginCommands } from '../commands';
-import { StructureSelectionManager } from '../../mol-plugin-state/manager/structure/selection';
+import { PluginCommands } from '../../mol-plugin/commands';
+import { StructureSelectionManager } from './structure/selection';
 
-export { Interactivity }
+export { InteractivityManager }
 
-class Interactivity {
-    readonly lociSelects: Interactivity.LociSelectManager;
-    readonly lociHighlights: Interactivity.LociHighlightManager;
+class InteractivityManager {
+    readonly lociSelects: InteractivityManager.LociSelectManager;
+    readonly lociHighlights: InteractivityManager.LociHighlightManager;
 
-    private _props = PD.getDefaultValues(Interactivity.Params)
+    private _props = PD.getDefaultValues(InteractivityManager.Params)
 
     get props() { return { ...this._props } }
-    setProps(props: Partial<Interactivity.Props>) {
+    setProps(props: Partial<InteractivityManager.Props>) {
         Object.assign(this._props, props)
         this.lociSelects.setProps(this._props)
         this.lociHighlights.setProps(this._props)
     }
 
-    constructor(readonly ctx: PluginContext, props: Partial<Interactivity.Props> = {}) {
+    constructor(readonly ctx: PluginContext, props: Partial<InteractivityManager.Props> = {}) {
         Object.assign(this._props, props)
 
-        this.lociSelects = new Interactivity.LociSelectManager(ctx, this._props);
-        this.lociHighlights = new Interactivity.LociHighlightManager(ctx, this._props);
+        this.lociSelects = new InteractivityManager.LociSelectManager(ctx, this._props);
+        this.lociHighlights = new InteractivityManager.LociHighlightManager(ctx, this._props);
 
         PluginCommands.Interactivity.SetProps.subscribe(ctx, e => this.setProps(e.props));
     }
 }
 
-namespace Interactivity {
+namespace InteractivityManager {
     export interface Loci<T extends ModelLoci = ModelLoci> { loci: T, repr?: Representation.Any }
 
     export namespace Loci {

+ 1 - 1
src/mol-plugin/util/loci-label-manager.ts → src/mol-plugin-state/manager/loci-label.ts

@@ -37,6 +37,6 @@ export class LociLabelManager {
     }
 
     constructor(public ctx: PluginContext) {
-        ctx.interactivity.lociHighlights.addProvider((loci, action) => ctx.behaviors.labels.highlight.next({ entries: this.getInfo(loci, action) }))
+        ctx.managers.interactivity.lociHighlights.addProvider((loci, action) => ctx.behaviors.labels.highlight.next({ entries: this.getInfo(loci, action) }))
     }
 }

+ 3 - 3
src/mol-plugin-state/manager/structure/selection.ts

@@ -348,13 +348,13 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
     private triggerInteraction(modifier: StructureSelectionModifier, loci: Loci, applyGranularity = true) {
         switch (modifier) {
             case 'add':
-                this.plugin.interactivity.lociSelects.select({ loci }, applyGranularity)
+                this.plugin.managers.interactivity.lociSelects.select({ loci }, applyGranularity)
                 break
             case 'remove':
-                this.plugin.interactivity.lociSelects.deselect({ loci }, applyGranularity)
+                this.plugin.managers.interactivity.lociSelects.deselect({ loci }, applyGranularity)
                 break
             case 'set':
-                this.plugin.interactivity.lociSelects.selectOnly({ loci }, applyGranularity)
+                this.plugin.managers.interactivity.lociSelects.selectOnly({ loci }, applyGranularity)
                 break
         }
     }

+ 1 - 1
src/mol-plugin-ui/controls.tsx

@@ -9,7 +9,7 @@ import * as React from 'react';
 import { PluginCommands } from '../mol-plugin/commands';
 import { UpdateTrajectory } from '../mol-plugin-state/actions/structure';
 import { PluginUIComponent } from './base';
-import { LociLabelEntry } from '../mol-plugin/util/loci-label-manager';
+import { LociLabelEntry } from '../mol-plugin-state/manager/loci-label';
 import { IconButton } from './controls/common';
 import { PluginStateObject } from '../mol-plugin-state/objects';
 import { StateTransforms } from '../mol-plugin-state/transforms';

+ 9 - 9
src/mol-plugin-ui/sequence/sequence.tsx

@@ -7,7 +7,7 @@
 
 import * as React from 'react'
 import { PluginUIComponent } from '../base';
-import { Interactivity } from '../../mol-plugin/util/interactivity';
+import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
 import { MarkerAction } from '../../mol-util/marker-action';
 import { ButtonsType, ModifiersKeys, getButtons, getModifiers, getButton } from '../../mol-util/input/input-observer';
 import { SequenceWrapper } from './wrapper';
@@ -32,12 +32,12 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
     private lastMouseOverSeqIdx = -1;
     private highlightQueue = new Subject<{ seqIdx: number, buttons: number, button: number, modifiers: ModifiersKeys }>();
 
-    private lociHighlightProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
+    private lociHighlightProvider = (loci: InteractivityManager.Loci, action: MarkerAction) => {
         const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
         if (changed) this.updateMarker();
     }
 
-    private lociSelectionProvider = (loci: Interactivity.Loci, action: MarkerAction) => {
+    private lociSelectionProvider = (loci: InteractivityManager.Loci, action: MarkerAction) => {
         const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
         if (changed) this.updateMarker();
     }
@@ -53,8 +53,8 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
     }
 
     componentDidMount() {
-        this.plugin.interactivity.lociHighlights.addProvider(this.lociHighlightProvider)
-        this.plugin.interactivity.lociSelects.addProvider(this.lociSelectionProvider)
+        this.plugin.managers.interactivity.lociHighlights.addProvider(this.lociHighlightProvider)
+        this.plugin.managers.interactivity.lociSelects.addProvider(this.lociSelectionProvider)
 
         this.subscribe(debounceTime<{ seqIdx: number, buttons: number, button: number, modifiers: ModifiersKeys }>(15)(this.highlightQueue), (e) => {
             const loci = this.getLoci(e.seqIdx < 0 ? void 0 : e.seqIdx)
@@ -65,8 +65,8 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
     }
 
     componentWillUnmount() {
-        this.plugin.interactivity.lociHighlights.removeProvider(this.lociHighlightProvider)
-        this.plugin.interactivity.lociSelects.removeProvider(this.lociSelectionProvider)
+        this.plugin.managers.interactivity.lociHighlights.removeProvider(this.lociHighlightProvider)
+        this.plugin.managers.interactivity.lociSelects.removeProvider(this.lociSelectionProvider)
     }
 
     getLoci(seqIdx: number | undefined) {
@@ -86,7 +86,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
     }
 
     hover(loci: StructureElement.Loci | undefined, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
-        const ev = { current: Interactivity.Loci.Empty, buttons, button, modifiers }
+        const ev = { current: InteractivityManager.Loci.Empty, buttons, button, modifiers }
         if (loci !== undefined && !StructureElement.Loci.isEmpty(loci)) {
             ev.current = { loci };
         }
@@ -94,7 +94,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
     }
 
     click(loci: StructureElement.Loci | undefined, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
-        const ev = { current: Interactivity.Loci.Empty, buttons, button, modifiers }
+        const ev = { current: InteractivityManager.Loci.Empty, buttons, button, modifiers }
         if (loci !== undefined && !StructureElement.Loci.isEmpty(loci)) {
             ev.current = { loci };
         }

+ 4 - 4
src/mol-plugin-ui/structure/measurements.tsx

@@ -208,15 +208,15 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
         const selections = this.selections;
         if (!selections) return;
 
-        this.plugin.interactivity.lociHighlights.clearHighlights();
+        this.plugin.managers.interactivity.lociHighlights.clearHighlights();
         for (const d of selections.data) {
-            this.plugin.interactivity.lociHighlights.highlight({ loci: d.loci }, false);
+            this.plugin.managers.interactivity.lociHighlights.highlight({ loci: d.loci }, false);
         }
-        this.plugin.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
+        this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
     }
 
     clearHighlight = () => {
-        this.plugin.interactivity.lociHighlights.clearHighlights();
+        this.plugin.managers.interactivity.lociHighlights.clearHighlights();
     }
 
     focus = () => {

+ 3 - 3
src/mol-plugin-ui/structure/selection.tsx

@@ -10,7 +10,7 @@ import { CollapsableControls, CollapsableState } from '../base';
 import { StructureSelectionQuery, StructureSelectionQueryList } from '../../mol-plugin-state/helpers/structure-selection-query';
 import { PluginCommands } from '../../mol-plugin/commands';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { Interactivity } from '../../mol-plugin/util/interactivity';
+import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
 import { ParameterControls } from '../controls/parameters';
 import { stripTags } from '../../mol-util/string';
 import { StructureElement } from '../../mol-model/structure';
@@ -25,7 +25,7 @@ export const DefaultQueries = ActionMenu.createItems(StructureSelectionQueryList
 });
 
 const StructureSelectionParams = {
-    granularity: Interactivity.Params.granularity,
+    granularity: InteractivityManager.Params.granularity,
 }
 
 interface StructureSelectionControlsState extends CollapsableState {
@@ -90,7 +90,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
 
     get values () {
         return {
-            granularity: this.plugin.interactivity.props.granularity,
+            granularity: this.plugin.managers.interactivity.props.granularity,
         }
     }
 

+ 2 - 2
src/mol-plugin-ui/toast.tsx

@@ -40,11 +40,11 @@ class ToastEntry extends PluginUIComponent<{ entry: PluginToastManager.Entry }>
 
 export class Toasts extends PluginUIComponent {
     componentDidMount() {
-        this.subscribe(this.plugin.toasts.events.changed, () => this.forceUpdate());
+        this.subscribe(this.plugin.managers.toast.events.changed, () => this.forceUpdate());
     }
 
     render() {
-        const state = this.plugin.toasts.state;
+        const state = this.plugin.managers.toast.state;
 
         if (!state.entries.count()) return null;
 

+ 1 - 1
src/mol-plugin/behavior/dynamic/camera.ts

@@ -40,7 +40,7 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
                 if (!this.ctx.canvas3d) return;
                 const p = this.params;
                 if (Binding.match(this.params.bindings.clickCenterFocus, button, modifiers)) {
-                    const loci = Loci.normalize(current.loci, this.ctx.interactivity.props.granularity)
+                    const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity)
                     if (Loci.isEmpty(loci)) {
                         PluginCommands.Camera.Reset(this.ctx, { })
                     } else {

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/computed/accessible-surface-area.ts

@@ -41,7 +41,7 @@ export const AccessibleSurfaceArea = PluginBehavior.create<{ autoAttach: boolean
 
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('accessible-surface-area', AccessibleSurfaceAreaColorThemeProvider)
-            this.ctx.lociLabels.addProvider(this.label);
+            this.ctx.managers.lociLabels.addProvider(this.label);
         }
 
         unregister() {
@@ -50,7 +50,7 @@ export const AccessibleSurfaceArea = PluginBehavior.create<{ autoAttach: boolean
 
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('accessible-surface-area')
-            this.ctx.lociLabels.removeProvider(this.label);
+            this.ctx.managers.lociLabels.removeProvider(this.label);
         }
     },
     params: () => ({

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts

@@ -100,14 +100,14 @@ export const Interactions = PluginBehavior.create<{ autoAttach: boolean, showToo
         register(): void {
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('interaction-type', InteractionTypeColorThemeProvider)
-            this.ctx.lociLabels.addProvider(this.label);
+            this.ctx.managers.lociLabels.addProvider(this.label);
             this.ctx.structureRepresentation.registry.add('interactions', InteractionsRepresentationProvider)
         }
 
         unregister() {
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('interaction-type')
-            this.ctx.lociLabels.removeProvider(this.label);
+            this.ctx.managers.lociLabels.removeProvider(this.label);
             this.ctx.structureRepresentation.registry.remove('interactions')
         }
     },

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/computed/valence-model.ts

@@ -86,12 +86,12 @@ export const ValenceModel = PluginBehavior.create<{ autoAttach: boolean, showToo
 
         register(): void {
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
-            this.ctx.lociLabels.addProvider(this.label);
+            this.ctx.managers.lociLabels.addProvider(this.label);
         }
 
         unregister() {
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
-            this.ctx.lociLabels.removeProvider(this.label);
+            this.ctx.managers.lociLabels.removeProvider(this.label);
         }
     },
     params: () => ({

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts

@@ -44,7 +44,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
 
         register(): void {
             this.ctx.customModelProperties.register(this.provider, false);
-            this.ctx.lociLabels.addProvider(this.labelPDBeValidation);
+            this.ctx.managers.lociLabels.addProvider(this.labelPDBeValidation);
 
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', StructureQualityReportColorThemeProvider)
         }
@@ -59,7 +59,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
 
         unregister() {
             this.ctx.customModelProperties.unregister(StructureQualityReportProvider.descriptor.name);
-            this.ctx.lociLabels.removeProvider(this.labelPDBeValidation);
+            this.ctx.managers.lociLabels.removeProvider(this.labelPDBeValidation);
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('pdbe-structure-quality-report')
         }
     },

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts

@@ -42,7 +42,7 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
 
             this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
 
-            this.ctx.lociLabels.addProvider(this.label);
+            this.ctx.managers.lociLabels.addProvider(this.label);
 
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(Tag.DensityFit, DensityFitColorThemeProvider)
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(Tag.GeometryQuality, GeometryQualityColorThemeProvider)
@@ -65,7 +65,7 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
 
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
 
-            this.ctx.lociLabels.removeProvider(this.label);
+            this.ctx.managers.lociLabels.removeProvider(this.label);
 
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(Tag.DensityFit)
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(Tag.GeometryQuality)

+ 22 - 22
src/mol-plugin/behavior/dynamic/representation.ts

@@ -10,7 +10,7 @@ import { PluginContext } from '../../../mol-plugin/context';
 import { PluginStateObject as SO } from '../../../mol-plugin-state/objects';
 import { lociLabel } from '../../../mol-theme/label';
 import { PluginBehavior } from '../behavior';
-import { Interactivity } from '../../util/interactivity';
+import { InteractivityManager } from '../../../mol-plugin-state/manager/interactivity';
 import { StateTreeSpine } from '../../../mol-state/tree/spine';
 import { StateSelection } from '../../../mol-state';
 import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
@@ -39,7 +39,7 @@ export const HighlightLoci = PluginBehavior.create({
     name: 'representation-highlight-loci',
     category: 'interaction',
     ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
-        private lociMarkProvider = (interactionLoci: Interactivity.Loci, action: MarkerAction) => {
+        private lociMarkProvider = (interactionLoci: InteractivityManager.Loci, action: MarkerAction) => {
             if (!this.ctx.canvas3d) return;
             this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action)
         }
@@ -49,23 +49,23 @@ export const HighlightLoci = PluginBehavior.create({
                 let matched = false
 
                 if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
-                    this.ctx.interactivity.lociHighlights.highlightOnly(current)
+                    this.ctx.managers.interactivity.lociHighlights.highlightOnly(current)
                     matched = true
                 }
 
                 if (Binding.match(this.params.bindings.hoverHighlightOnlyExtend, buttons, modifiers)) {
-                    this.ctx.interactivity.lociHighlights.highlightOnlyExtend(current)
+                    this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend(current)
                     matched = true
                 }
 
                 if (!matched) {
-                    this.ctx.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EmptyLoci })
+                    this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EmptyLoci })
                 }
             });
-            this.ctx.interactivity.lociHighlights.addProvider(this.lociMarkProvider)
+            this.ctx.managers.interactivity.lociHighlights.addProvider(this.lociMarkProvider)
         }
         unregister() {
-            this.ctx.interactivity.lociHighlights.removeProvider(this.lociMarkProvider)
+            this.ctx.managers.interactivity.lociHighlights.removeProvider(this.lociMarkProvider)
         }
     },
     params: () => HighlightLociParams,
@@ -92,7 +92,7 @@ export const SelectLoci = PluginBehavior.create({
     category: 'interaction',
     ctor: class extends PluginBehavior.Handler<SelectLociProps> {
         private spine: StateTreeSpine.Impl
-        private lociMarkProvider = (interactionLoci: Interactivity.Loci, action: MarkerAction) => {
+        private lociMarkProvider = (interactionLoci: InteractivityManager.Loci, action: MarkerAction) => {
             if (!this.ctx.canvas3d) return;
             this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action)
         }
@@ -111,16 +111,16 @@ export const SelectLoci = PluginBehavior.create({
             }
         }
         register() {
-            const lociIsEmpty = (current: Interactivity.Loci) => Loci.isEmpty(current.loci)
-            const lociIsNotEmpty = (current: Interactivity.Loci) => !Loci.isEmpty(current.loci)
-
-            const actions: [keyof typeof DefaultSelectLociBindings, (current: Interactivity.Loci) => void, ((current: Interactivity.Loci) => boolean) | undefined][] = [
-                ['clickSelect', current => this.ctx.interactivity.lociSelects.select(current), lociIsNotEmpty],
-                ['clickToggle', current => this.ctx.interactivity.lociSelects.toggle(current), lociIsNotEmpty],
-                ['clickToggleExtend', current => this.ctx.interactivity.lociSelects.toggleExtend(current), lociIsNotEmpty],
-                ['clickSelectOnly', current => this.ctx.interactivity.lociSelects.selectOnly(current), lociIsNotEmpty],
-                ['clickDeselect', current => this.ctx.interactivity.lociSelects.deselect(current), lociIsNotEmpty],
-                ['clickDeselectAllOnEmpty', () => this.ctx.interactivity.lociSelects.deselectAll(), lociIsEmpty],
+            const lociIsEmpty = (current: InteractivityManager.Loci) => Loci.isEmpty(current.loci)
+            const lociIsNotEmpty = (current: InteractivityManager.Loci) => !Loci.isEmpty(current.loci)
+
+            const actions: [keyof typeof DefaultSelectLociBindings, (current: InteractivityManager.Loci) => void, ((current: InteractivityManager.Loci) => boolean) | undefined][] = [
+                ['clickSelect', current => this.ctx.managers.interactivity.lociSelects.select(current), lociIsNotEmpty],
+                ['clickToggle', current => this.ctx.managers.interactivity.lociSelects.toggle(current), lociIsNotEmpty],
+                ['clickToggleExtend', current => this.ctx.managers.interactivity.lociSelects.toggleExtend(current), lociIsNotEmpty],
+                ['clickSelectOnly', current => this.ctx.managers.interactivity.lociSelects.selectOnly(current), lociIsNotEmpty],
+                ['clickDeselect', current => this.ctx.managers.interactivity.lociSelects.deselect(current), lociIsNotEmpty],
+                ['clickDeselectAllOnEmpty', () => this.ctx.managers.interactivity.lociSelects.deselectAll(), lociIsEmpty],
             ];
 
             // sort the action so that the ones with more modifiers trigger sooner.
@@ -142,7 +142,7 @@ export const SelectLoci = PluginBehavior.create({
                     }
                 }
             });
-            this.ctx.interactivity.lociSelects.addProvider(this.lociMarkProvider)
+            this.ctx.managers.interactivity.lociSelects.addProvider(this.lociMarkProvider)
 
             this.subscribeObservable(this.ctx.events.state.object.created, ({ ref }) => this.applySelectMark(ref));
 
@@ -156,7 +156,7 @@ export const SelectLoci = PluginBehavior.create({
             });
         }
         unregister() {
-            this.ctx.interactivity.lociSelects.removeProvider(this.lociMarkProvider)
+            this.ctx.managers.interactivity.lociSelects.removeProvider(this.lociMarkProvider)
         }
         constructor(ctx: PluginContext, params: SelectLociProps) {
             super(ctx, params)
@@ -172,8 +172,8 @@ export const DefaultLociLabelProvider = PluginBehavior.create({
     category: 'interaction',
     ctor: class implements PluginBehavior<undefined> {
         private f = (loci: Loci) => lociLabel(loci);
-        register() { this.ctx.lociLabels.addProvider(this.f); }
-        unregister() { this.ctx.lociLabels.removeProvider(this.f); }
+        register() { this.ctx.managers.lociLabels.addProvider(this.f); }
+        unregister() { this.ctx.managers.lociLabels.removeProvider(this.f); }
         constructor(protected ctx: PluginContext) { }
     },
     display: { name: 'Provide Default Loci Label' }

+ 1 - 1
src/mol-plugin/behavior/static/misc.ts

@@ -22,7 +22,7 @@ export function Canvas3DSetSettings(ctx: PluginContext) {
 
 export function InteractivitySetProps(ctx: PluginContext) {
     PluginCommands.Interactivity.SetProps.subscribe(ctx, e => {
-        ctx.interactivity.setProps(e.props);
+        ctx.managers.interactivity.setProps(e.props);
         ctx.events.interactivity.propsUpdated.next();
     })
 }

+ 5 - 5
src/mol-plugin/behavior/static/state.ts

@@ -108,14 +108,14 @@ export function Highlight(ctx: PluginContext) {
         const cell = state.select(ref)[0];
         if (!cell) return;
         if (SO.Molecule.Structure.is(cell.obj)) {
-            ctx.interactivity.lociHighlights.highlightOnly({ loci: Structure.Loci(cell.obj.data) }, false);
+            ctx.managers.interactivity.lociHighlights.highlightOnly({ loci: Structure.Loci(cell.obj.data) }, false);
         } else if (cell && SO.isRepresentation3D(cell.obj)) {
             const { repr } = cell.obj.data
-            ctx.interactivity.lociHighlights.highlightOnly({ loci: repr.getLoci(), repr }, false);
+            ctx.managers.interactivity.lociHighlights.highlightOnly({ loci: repr.getLoci(), repr }, false);
         } else if (SO.Molecule.Structure.Selections.is(cell.obj)) {
-            ctx.interactivity.lociHighlights.clearHighlights();
+            ctx.managers.interactivity.lociHighlights.clearHighlights();
             for (const entry of cell.obj.data) {
-                ctx.interactivity.lociHighlights.highlight({ loci: entry.loci }, false);
+                ctx.managers.interactivity.lociHighlights.highlight({ loci: entry.loci }, false);
             }
         }
 
@@ -126,7 +126,7 @@ export function Highlight(ctx: PluginContext) {
 
 export function ClearHighlight(ctx: PluginContext) {
     PluginCommands.State.ClearHighlight.subscribe(ctx, ({ state, ref }) => {
-        ctx.interactivity.lociHighlights.clearHighlights();
+        ctx.managers.interactivity.lociHighlights.clearHighlights();
     });
 }
 

+ 2 - 2
src/mol-plugin/commands.ts

@@ -11,7 +11,7 @@ import { Canvas3DProps } from '../mol-canvas3d/canvas3d';
 import { PluginLayoutStateProps } from './layout';
 import { StructureElement } from '../mol-model/structure';
 import { PluginState } from './state';
-import { Interactivity } from './util/interactivity';
+import { InteractivityManager } from '../mol-plugin-state/manager/interactivity';
 import { PluginToast } from './util/toast';
 import { Vec3 } from '../mol-math/linear-algebra';
 
@@ -44,7 +44,7 @@ export const PluginCommands = {
         }
     },
     Interactivity: {
-        SetProps: PluginCommand<{ props: Partial<Interactivity.Props> }>(),
+        SetProps: PluginCommand<{ props: Partial<InteractivityManager.Props> }>(),
         Structure: {
             Highlight: PluginCommand<{ loci: StructureElement.Loci, isOff?: boolean }>(),
             Select: PluginCommand<{ loci: StructureElement.Loci, isOff?: boolean }>()

+ 11 - 13
src/mol-plugin/context.ts

@@ -28,14 +28,14 @@ import { PluginSpec } from './spec';
 import { PluginState } from './state';
 import { DataFormatRegistry } from '../mol-plugin-state/actions/data-format';
 import { StateTransformParameters } from '../mol-plugin-ui/state/common';
-import { LociLabelEntry, LociLabelManager } from './util/loci-label-manager';
+import { LociLabelEntry, LociLabelManager } from '../mol-plugin-state/manager/loci-label';
 import { TaskManager } from './util/task-manager';
 import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
 import { SubstructureParentHelper } from './util/substructure-parent-helper';
 import { ModifiersKeys } from '../mol-util/input/input-observer';
 import { isProductionMode, isDebugMode } from '../mol-util/debug';
 import { Model, Structure } from '../mol-model/structure';
-import { Interactivity } from './util/interactivity';
+import { InteractivityManager } from '../mol-plugin-state/manager/interactivity';
 import { StructureRepresentationHelper } from './util/structure-representation-helper';
 import { StructureOverpaintHelper } from './util/structure-overpaint-helper';
 import { PluginToastManager } from './util/toast';
@@ -92,8 +92,8 @@ export class PluginContext {
             isBusy: this.ev.behavior<boolean>(false)
         },
         interaction: {
-            hover: this.ev.behavior<Interactivity.HoverEvent>({ current: Interactivity.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 }),
-            click: this.ev.behavior<Interactivity.ClickEvent>({ current: Interactivity.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 })
+            hover: this.ev.behavior<InteractivityManager.HoverEvent>({ current: InteractivityManager.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 }),
+            click: this.ev.behavior<InteractivityManager.ClickEvent>({ current: InteractivityManager.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 })
         },
         labels: {
             highlight: this.ev.behavior<{ entries: ReadonlyArray<LociLabelEntry> }>({ entries: [] })
@@ -105,11 +105,6 @@ export class PluginContext {
 
     readonly canvas3d: Canvas3D | undefined;
     readonly layout = new PluginLayout(this);
-    readonly toasts = new PluginToastManager(this);
-    readonly interactivity: Interactivity;
-
-    readonly lociLabels: LociLabelManager;
-
 
     readonly structureRepresentation = {
         registry: new StructureRepresentationRegistry(),
@@ -136,8 +131,11 @@ export class PluginContext {
             hierarchy: new StructureHierarchyManager(this),
             measurement: new StructureMeasurementManager(this),
             selection: new StructureSelectionManager(this)
-        }
-    };
+        },
+        interactivity: void 0 as any as InteractivityManager,
+        lociLabels: void 0 as any as LociLabelManager,
+        toast: new PluginToastManager(this)
+    } as const
 
     readonly customModelProperties = new CustomProperty.Registry<Model>();
     readonly customStructureProperties = new CustomProperty.Registry<Structure>();
@@ -295,8 +293,8 @@ export class PluginContext {
         this.initAnimations();
         this.initCustomParamEditors();
 
-        this.interactivity = new Interactivity(this);
-        this.lociLabels = new LociLabelManager(this);
+        (this.managers.interactivity as InteractivityManager) = new InteractivityManager(this);
+        (this.managers.lociLabels as LociLabelManager) = new LociLabelManager(this);
 
         (this.builders.representation as RepresentationBuilder)= new RepresentationBuilder(this);
 

+ 3 - 3
src/mol-plugin/state.ts

@@ -16,7 +16,7 @@ import { PluginCommands } from './commands';
 import { PluginAnimationManager } from '../mol-plugin-state/animation/manager';
 import { ParamDefinition as PD } from '../mol-util/param-definition';
 import { UUID } from '../mol-util';
-import { Interactivity } from './util/interactivity';
+import { InteractivityManager } from '../mol-plugin-state/manager/interactivity';
 export { PluginState }
 
 class PluginState {
@@ -58,7 +58,7 @@ class PluginState {
             } : void 0,
             cameraSnapshots: p.cameraSnapshots ? this.cameraSnapshots.getStateSnapshot() : void 0,
             canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0,
-            interactivity: p.interactivity ? { props: this.plugin.interactivity.props } : void 0,
+            interactivity: p.interactivity ? { props: this.plugin.managers.interactivity.props } : void 0,
             durationInMs: params && params.durationInMs
         };
     }
@@ -158,7 +158,7 @@ namespace PluginState {
             props?: Canvas3DProps
         },
         interactivity?: {
-            props?: Interactivity.Props
+            props?: InteractivityManager.Props
         },
         durationInMs?: number
     }