diff --git a/src/apps/viewer/index.tsx b/src/apps/viewer/index.tsx index b4d3823d0bc0d8f2fe473fc73cf2e33b5b4ba2f6..940efc7d074991890d466e3ec52e10da2fa25c69 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 b87ea5c14eec10f8c2a7ff7161cd569221611bde..f52d6e651296d7839c83388cfa827587949aa553 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 21bef90b8afe67226ea4cf62b799778664043b73..520505ca2df4992a7466a06491d39893321e8342 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 a13c12809066f2d3b9c790f5d9c082986e54e112..3a0b9afe506fb5acf14b311bb6f4c135aa0b8885 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 5d572d23f3a6b75b4630ed477973769b4749fcd3..b1f7073cf5858f442cca6e071160faeea6ec8717 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 6be2f36075718c04bbf51bad3217e8ea2af861ec..32684d7f413c0fd6ad575311d7ba2f9aff50e733 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: () => {