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