From d9a1a2d204b96daf7f99516b905c2a4b1f33b5a9 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Sun, 3 Mar 2019 16:11:29 +0100
Subject: [PATCH] mol-plugin: interactivity tweaks

---
 src/mol-plugin/behavior/dynamic/camera.ts     |  2 +-
 .../behavior/dynamic/representation.ts        |  4 +-
 .../structure-representation-interaction.ts   | 21 ++++++-
 .../dynamic/volume-streaming/behavior.ts      | 59 +++++++++++++------
 .../dynamic/volume-streaming/transformers.ts  |  8 ++-
 src/mol-plugin/behavior/static/state.ts       |  6 +-
 src/mol-plugin/context.ts                     | 15 +++--
 src/mol-plugin/ui/viewport.tsx                |  4 +-
 src/mol-plugin/util/loci-label-manager.ts     |  2 +-
 9 files changed, 82 insertions(+), 39 deletions(-)

diff --git a/src/mol-plugin/behavior/dynamic/camera.ts b/src/mol-plugin/behavior/dynamic/camera.ts
index 34da03e87..39c5f2355 100644
--- a/src/mol-plugin/behavior/dynamic/camera.ts
+++ b/src/mol-plugin/behavior/dynamic/camera.ts
@@ -14,7 +14,7 @@ export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extr
     category: 'interaction',
     ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number }> {
         register(): void {
-            this.subscribeObservable(this.ctx.events.canvas3d.click, ({ current, buttons, modifiers }) => {
+            this.subscribeObservable(this.ctx.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
                 if (!this.ctx.canvas3d || buttons !== ButtonsType.Flag.Primary || !ModifiersKeys.areEqual(modifiers, ModifiersKeys.None)) return;
 
                 const sphere = Loci.getBoundingSphere(current.loci);
diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts
index 42b448182..47dab0e53 100644
--- a/src/mol-plugin/behavior/dynamic/representation.ts
+++ b/src/mol-plugin/behavior/dynamic/representation.ts
@@ -26,7 +26,7 @@ export const HighlightLoci = PluginBehavior.create({
             let prev: Representation.Loci = { loci: EmptyLoci, repr: void 0 };
             const sel = this.ctx.helpers.structureSelection;
 
-            this.subscribeObservable(this.ctx.events.canvas3d.highlight, ({ current, modifiers }) => {
+            this.subscribeObservable(this.ctx.behaviors.canvas3d.highlight, ({ current, modifiers }) => {
                 if (!this.ctx.canvas3d) return;
 
                 if (StructureElement.isLoci(current.loci)) {
@@ -70,7 +70,7 @@ export const SelectLoci = PluginBehavior.create({
                 }
             }
 
-            this.subscribeObservable(this.ctx.events.canvas3d.click, ({ current, buttons, modifiers }) => {
+            this.subscribeObservable(this.ctx.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
                 if (!this.ctx.canvas3d) return;
 
                 if (current.loci.kind === 'empty-loci') {
diff --git a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts
index a73b2f2d3..a64d05695 100644
--- a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts
+++ b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts
@@ -84,11 +84,30 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
         return { state, builder, refs };
     }
 
+    private clear() {
+        const state = this.plugin.state.dataState;
+        const groups = state.select(StateSelection.Generators.root.subtree().filter(o => o.transform.props.tag === Tags.Group));
+        if (groups.length === 0) return;
+
+        const update = state.build();
+        for (const g of groups) update.delete(g.transform.ref);
+
+        PluginCommands.State.Update.dispatch(this.plugin, { state, tree: update, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
+    }
+
     register(ref: string): void {
         // this.ref = ref;
 
-        this.subscribeObservable(this.plugin.events.canvas3d.click, ({ current, buttons }) => {
+        this.subscribeObservable(this.plugin.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
             if (buttons !== ButtonsType.Flag.Secondary) return;
+
+            if (current.loci.kind === 'empty-loci') {
+                if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
+                    this.clear();
+                    return;
+                }
+            }
+
             // TODO: support link loci as well?
             if (!StructureElement.isLoci(current.loci)) return;
 
diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts
index 983ceac74..2f5f15508 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts
@@ -21,6 +21,8 @@ import { urlCombine } from 'mol-util/url';
 import { VolumeServerHeader, VolumeServerInfo } from './model';
 import { CreateVolumeStreamingBehavior } from './transformers';
 import { ButtonsType } from 'mol-util/input/input-observer';
+import { PluginCommands } from 'mol-plugin/command';
+import { StateSelection } from 'mol-state';
 
 export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
 
@@ -146,34 +148,55 @@ export namespace VolumeStreaming {
             return ret;
         }
 
+        private updateDynamicBox(ref: string, box: Box3D) {
+            if (this.params.view.name !== 'selection-box') return;
+
+            const eR = this.params.view.params.radius;
+            const state = this.plugin.state.dataState;
+            const update = state.build().to(ref).update(CreateVolumeStreamingBehavior, old => ({
+                ...old,
+                view: {
+                    name: 'selection-box' as 'selection-box',
+                    params: {
+                        radius: eR,
+                        bottomLeft: box.min,
+                        topRight: box.max
+                    }
+                }
+            }));
+
+            PluginCommands.State.Update.dispatch(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
+        }
+
+        private getStructureRoot(ref: string) {
+            return this.plugin.state.dataState.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0];
+        }
+
         register(ref: string): void {
             // this.ref = ref;
 
-            this.subscribeObservable(this.plugin.events.canvas3d.click, ({ current, buttons }) => {
+            this.subscribeObservable(this.plugin.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
                 if (buttons !== ButtonsType.Flag.Secondary || this.params.view.name !== 'selection-box') return;
+
+                if (current.loci.kind === 'empty-loci') {
+                    if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
+                        this.updateDynamicBox(ref, Box3D.empty());
+                        return;
+                    }
+                }
+
                 // TODO: support link loci as well?
                 // Perhaps structure loci too?
                 if (!StructureElement.isLoci(current.loci)) return;
 
-                // TODO: check if it's the related structure
-                const loci = StructureElement.Loci.extendToWholeResidues(current.loci);
+                const parent = this.plugin.helpers.substructureParent.get(current.loci.structure);
+                if (!parent) return;
+                const root = this.getStructureRoot(ref);
+                if (!root || !root.obj || root.obj !== parent.obj) return;
 
-                const eR = this.params.view.params.radius;
+                const loci = StructureElement.Loci.extendToWholeResidues(current.loci);
                 const box = StructureElement.Loci.getBoundary(loci).box;
-                const update = this.plugin.state.dataState.build().to(ref).update(CreateVolumeStreamingBehavior, old => ({
-                    ...old,
-                    view: {
-                        name: 'selection-box' as 'selection-box',
-                        params: {
-                            radius: eR,
-                            bottomLeft: box.min,
-                            topRight: box.max
-                        }
-                    }
-                }));
-
-                // TODO: create/use command queue here and cancel any ongoing updates.
-                this.plugin.runTask(this.plugin.state.dataState.updateTree(update));
+                this.updateDynamicBox(ref, box);
             });
         }
 
diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
index 0e4a8173a..bca90d153 100644
--- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
+++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
@@ -52,7 +52,9 @@ export const InitVolumeStreaming = StateAction.build({
 
     const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
 
-    const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior, PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data)));
+    const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior,
+        PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data)));
+
     if (params.method === 'em') {
         behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { props: { isGhost: true } });
     } else {
@@ -96,7 +98,7 @@ const CreateVolumeStreamingInfo = PluginStateTransform.BuiltIn({
             emDefaultContourLevel,
             structure: a.data
         };
-        return new VolumeServerInfo(data, { label: `Volume Streaming: ${dataId}` });
+        return new VolumeServerInfo(data, { label: `Volume Server: ${dataId}` });
     })
 });
 
@@ -118,7 +120,7 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
     apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {
         const behavior = new VolumeStreaming.Behavior(plugin, a.data);
         await behavior.update(params);
-        return new VolumeStreaming(behavior, { label: 'Streaming Controls' });
+        return new VolumeStreaming(behavior, { label: 'Volume Streaming' });
     }),
     update({ b, newParams }) {
         return Task.create('Update Volume Streaming', async _ => {
diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts
index 83c2d1072..747f37008 100644
--- a/src/mol-plugin/behavior/static/state.ts
+++ b/src/mol-plugin/behavior/static/state.ts
@@ -105,16 +105,16 @@ export function Highlight(ctx: PluginContext) {
         // const cell = state.select(ref)[0]
         // const repr = cell && SO.isRepresentation3D(cell.obj) ? cell.obj.data : undefined
         // if (cell && cell.obj && cell.obj.type === PluginStateObject.Molecule.Structure.type) {
-        //     ctx.events.canvas3d.highlight.next({ current: { loci: Structure.Loci(cell.obj.data) } });
+        //     ctx.behaviors.canvas3d.highlight.next({ current: { loci: Structure.Loci(cell.obj.data) } });
         // } else if (repr) {
-        //     ctx.events.canvas3d.highlight.next({ current: { loci: EveryLoci, repr } });
+        //     ctx.behaviors.canvas3d.highlight.next({ current: { loci: EveryLoci, repr } });
         // }
     });
 }
 
 export function ClearHighlight(ctx: PluginContext) {
     PluginCommands.State.ClearHighlight.subscribe(ctx, ({ state, ref }) => {
-        // ctx.events.canvas3d.highlight.next({ current: { loci: EmptyLoci } });
+        // ctx.behaviors.canvas3d.highlight.next({ current: { loci: EmptyLoci } });
     });
 }
 
diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index 459b569e7..7fd0a9a0c 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -32,6 +32,8 @@ import { TaskManager } from './util/task-manager';
 import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
 import { StructureElementSelectionManager } from './util/structure-element-selection';
 import { SubstructureParentHelper } from './util/substructure-parent-helper';
+import { Representation } from 'mol-repr/representation';
+import { ModifiersKeys } from 'mol-util/input/input-observer';
 
 export class PluginContext {
     private disposed = false;
@@ -59,18 +61,15 @@ export class PluginContext {
         log: this.ev<LogEntry>(),
         task: this.tasks.events,
         canvas3d: {
-            settingsUpdated: this.ev(),
-
-            highlight: this.ev<Canvas3D.HighlightEvent>(),
-            click: this.ev<Canvas3D.ClickEvent>()
+            settingsUpdated: this.ev()
         }
     };
 
     readonly behaviors = {
-        // canvas: {
-        //     highlightLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any, modifiers?: ModifiersKeys }>({ loci: EmptyLoci }),
-        //     selectLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any, modifiers?: ModifiersKeys }>({ loci: EmptyLoci }),
-        // },
+        canvas3d: {
+            highlight: this.ev.behavior<Canvas3D.HighlightEvent>({ current: Representation.Loci.Empty, prev: Representation.Loci.Empty }),
+            click: this.ev.behavior<Canvas3D.ClickEvent>({ current: Representation.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0 })
+        },
         labels: {
             highlight: this.ev.behavior<{ entries: ReadonlyArray<LociLabelEntry> }>({ entries: [] })
         },
diff --git a/src/mol-plugin/ui/viewport.tsx b/src/mol-plugin/ui/viewport.tsx
index 408c174a3..fdb374fbb 100644
--- a/src/mol-plugin/ui/viewport.tsx
+++ b/src/mol-plugin/ui/viewport.tsx
@@ -110,8 +110,8 @@ export class Viewport extends PluginUIComponent<{ }, ViewportState> {
         const canvas3d = this.plugin.canvas3d;
         this.subscribe(canvas3d.input.resize, this.handleResize);
 
-        this.subscribe(canvas3d.interaction.click, e => this.plugin.events.canvas3d.click.next(e));
-        this.subscribe(canvas3d.interaction.highlight, e => this.plugin.events.canvas3d.highlight.next(e));
+        this.subscribe(canvas3d.interaction.click, e => this.plugin.behaviors.canvas3d.click.next(e));
+        this.subscribe(canvas3d.interaction.highlight, e => this.plugin.behaviors.canvas3d.highlight.next(e));
         this.subscribe(this.plugin.layout.events.updated, () => {
             setTimeout(this.handleResize, 50);
         });
diff --git a/src/mol-plugin/util/loci-label-manager.ts b/src/mol-plugin/util/loci-label-manager.ts
index b49814798..9475f0b6f 100644
--- a/src/mol-plugin/util/loci-label-manager.ts
+++ b/src/mol-plugin/util/loci-label-manager.ts
@@ -35,6 +35,6 @@ export class LociLabelManager {
     }
 
     constructor(public ctx: PluginContext) {
-        ctx.events.canvas3d.highlight.subscribe(ev => ctx.behaviors.labels.highlight.next({ entries: this.getInfo(ev.current) }));
+        ctx.behaviors.canvas3d.highlight.subscribe(ev => ctx.behaviors.labels.highlight.next({ entries: this.getInfo(ev.current) }));
     }
 }
\ No newline at end of file
-- 
GitLab