From 566f979e86ffbbbf88b28e24c52b389708a9fb4c Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Mon, 11 Jun 2018 07:39:21 +0200 Subject: [PATCH] use events for highlight & select --- src/apps/viewer/index.tsx | 16 ++++++ src/mol-app/event/basic.ts | 6 +- .../ui/visualization/sequence-view.tsx | 7 ++- src/mol-app/ui/visualization/viewport.tsx | 24 ++++++-- src/mol-util/input/input-observer.ts | 4 +- src/mol-view/viewer.ts | 55 ++++++++----------- 6 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/apps/viewer/index.tsx b/src/apps/viewer/index.tsx index b4d3823d0..940efc7d0 100644 --- a/src/apps/viewer/index.tsx +++ b/src/apps/viewer/index.tsx @@ -23,6 +23,9 @@ import { EntityTreeController } from 'mol-app/controller/entity/tree'; import { TransformListController } from 'mol-app/controller/transform/list'; import { TransformList } from 'mol-app/ui/transform/list'; import { SequenceView } from 'mol-app/ui/visualization/sequence-view'; +import { InteractivityEvents } from 'mol-app/event/basic'; +import { MarkerAction } from 'mol-geo/util/marker-data'; +import { EveryLoci } from 'mol-model/loci'; const elm = document.getElementById('app') if (!elm) throw new Error('Can not find element with id "app".') @@ -94,4 +97,17 @@ ctx.layout.setState({ }) // ctx.viewport.setState() +ctx.dispatcher.getStream(InteractivityEvents.HighlightLoci).subscribe(event => { + ctx.stage.viewer.mark(EveryLoci, MarkerAction.RemoveHighlight) + if (event && event.data) { + ctx.stage.viewer.mark(event.data, MarkerAction.Highlight) + } +}) + +ctx.dispatcher.getStream(InteractivityEvents.SelectLoci).subscribe(event => { + if (event && event.data) { + ctx.stage.viewer.mark(event.data, MarkerAction.ToggleSelect) + } +}) + ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm); diff --git a/src/mol-app/event/basic.ts b/src/mol-app/event/basic.ts index b87ea5c14..f52d6e651 100644 --- a/src/mol-app/event/basic.ts +++ b/src/mol-app/event/basic.ts @@ -11,7 +11,7 @@ import { Dispatcher } from '../service/dispatcher' import { LayoutState } from '../controller/layout'; import { ViewportOptions } from '../controller/visualization/viewport'; import { Job } from '../service/job'; -import { Element } from 'mol-model/structure' +import { Loci } from 'mol-model/loci'; const Lane = Dispatcher.Lane; @@ -34,5 +34,7 @@ export namespace LayoutEvents { } export namespace InteractivityEvents { - export const HighlightElementLoci = Event.create<Element.Loci | undefined>('bs.Interactivity.HighlightElementLoci', Lane.Slow); + export const HighlightLoci = Event.create<Loci>('bs.Interactivity.HighlightLoci', Lane.Slow); + export const SelectLoci = Event.create<Loci>('bs.Interactivity.SelectLoci', Lane.Slow); + export const LabelLoci = Event.create<Loci>('bs.Interactivity.LabelLoci', Lane.Slow); } diff --git a/src/mol-app/ui/visualization/sequence-view.tsx b/src/mol-app/ui/visualization/sequence-view.tsx index 21bef90b8..520505ca2 100644 --- a/src/mol-app/ui/visualization/sequence-view.tsx +++ b/src/mol-app/ui/visualization/sequence-view.tsx @@ -11,6 +11,7 @@ import { Structure, StructureSequence, Queries, Selection } from 'mol-model/stru import { Context } from '../../context/context'; import { InteractivityEvents } from '../../event/basic'; import { SyncRuntimeContext } from 'mol-task/execution/synchronous'; +import { EmptyLoci } from 'mol-model/loci'; export class SequenceView extends View<SequenceViewController, {}, {}> { render() { @@ -36,14 +37,14 @@ class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSeque async raiseInteractityEvent(seqId?: number) { if (typeof seqId === 'undefined') { - InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0); + InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci); return; } const query = createQuery(this.props.seq.entityId, seqId); const loci = Selection.toLoci(await query(this.props.structure, SyncRuntimeContext)); - if (loci.elements.length === 0) InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0); - else InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, loci); + if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci); + else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci); } diff --git a/src/mol-app/ui/visualization/viewport.tsx b/src/mol-app/ui/visualization/viewport.tsx index a13c12809..3a0b9afe5 100644 --- a/src/mol-app/ui/visualization/viewport.tsx +++ b/src/mol-app/ui/visualization/viewport.tsx @@ -14,6 +14,8 @@ import { View } from '../view'; import { HelpBox, Toggle, Button } from '../controls/common' import { Slider } from '../controls/slider' import { ImageCanvas } from './image-canvas'; +import { InteractivityEvents } from '../../event/basic'; +import { labelFirst } from 'mol-view/label'; export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> { state = { showSceneOptions: false, showHelp: false }; @@ -142,10 +144,6 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl? }) }) - viewer.identified.subscribe(info => { - this.setState({ info }) - }) - viewer.didDraw.subscribe(() => { // this.setState({ imageData: viewer.getImageData() }) this.setState({ @@ -158,7 +156,23 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl? }) viewer.input.resize.subscribe(() => this.handleResize()) - this.handleResize() + + viewer.input.move.subscribe(({x, y, inside}) => { + if (!inside) return + const p = viewer.identify(x, y) + const loci = viewer.getLoci(p) + InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci); + + // TODO use LabelLoci event and make configurable + const label = labelFirst(loci) + const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}` + this.setState({ info }) + }) + + viewer.input.click.subscribe(({x, y}) => { + const loci = viewer.getLoci(viewer.identify(x, y)) + InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci); + }) } componentWillUnmount() { diff --git a/src/mol-util/input/input-observer.ts b/src/mol-util/input/input-observer.ts index 5d572d23f..b1f7073cf 100644 --- a/src/mol-util/input/input-observer.ts +++ b/src/mol-util/input/input-observer.ts @@ -98,6 +98,7 @@ export type MoveInput = { y: number, pageX: number, pageY: number, + inside: boolean, } & BaseInput export type PinchInput = { @@ -355,7 +356,8 @@ namespace InputObserver { eventOffset(pointerEnd, ev) const { pageX, pageY } = ev const [ x, y ] = pointerEnd - move.next({ x, y, pageX, pageY, buttons, modifiers }) + const inside = insideBounds(pointerEnd) + move.next({ x, y, pageX, pageY, buttons, modifiers, inside }) if (dragging === DraggingState.Stopped) return diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 6be2f3607..32684d7f4 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -22,9 +22,8 @@ import { createRenderTarget } from 'mol-gl/webgl/render-target'; import Scene from 'mol-gl/scene'; import { RenderVariant } from 'mol-gl/webgl/render-item'; import { PickingId, decodeIdRGBA } from 'mol-geo/util/picking'; -import { labelFirst } from './label'; import { MarkerAction } from 'mol-geo/util/marker-data'; -import { EveryLoci } from 'mol-model/loci'; +import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; interface Viewer { center: (p: Vec3) => void @@ -41,7 +40,9 @@ interface Viewer { requestDraw: () => void animate: () => void pick: () => void - identify: (x: number, y: number) => void + identify: (x: number, y: number) => PickingId + mark: (loci: Loci, action: MarkerAction) => void + getLoci: (pickingId: PickingId) => Loci reprCount: BehaviorSubject<number> identified: BehaviorSubject<string> @@ -76,35 +77,7 @@ namespace Viewer { const startTime = performance.now() const didDraw = new BehaviorSubject(0) - const input = InputObserver.create(canvas) - input.resize.subscribe(handleResize) - input.move.subscribe(({x, y}) => { - const p = identify(x, y) - let label = '' - reprMap.forEach((roSet, repr) => { - repr.mark(EveryLoci, MarkerAction.RemoveHighlight) - const loci = repr.getLoci(p) - if (loci) { - label = labelFirst(loci) - repr.mark(loci, MarkerAction.Highlight) - } - scene.update() - requestDraw() - }) - identified.next(`Object: ${p.objectId}, Instance: ${p.instanceId}, Element: ${p.elementId}, Label: ${label}`) - }) - input.click.subscribe(({x, y}) => { - const p = identify(x, y) - reprMap.forEach((roSet, repr) => { - const loci = repr.getLoci(p) - if (loci) { - repr.mark(loci, MarkerAction.ToggleSelect) - scene.update() - requestDraw() - } - }) - }) const camera = PerspectiveCamera.create({ near: 0.1, @@ -141,6 +114,24 @@ namespace Viewer { const prevProjectionView = Mat4.zero() const prevSceneView = Mat4.zero() + function getLoci(pickingId: PickingId) { + let loci: Loci = EmptyLoci + reprMap.forEach((_, repr) => { + const _loci = repr.getLoci(pickingId) + if (!isEmptyLoci(_loci)) { + if (!isEmptyLoci(loci)) console.warn('found another loci') + loci = _loci + } + }) + return loci + } + + function mark(loci: Loci, action: MarkerAction) { + reprMap.forEach((roSet, repr) => repr.mark(loci, action)) + scene.update() + requestDraw() + } + let nearPlaneDelta = 0 function computeNearDistance() { const focusRadius = scene.boundingSphere.radius @@ -295,6 +286,8 @@ namespace Viewer { animate, pick, identify, + mark, + getLoci, handleResize, resetCamera: () => { -- GitLab