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