From 78c76a921459c98e692829dcb200e18438d0feea Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Thu, 1 Nov 2018 10:41:17 -0700 Subject: [PATCH] added selection support to canvas app --- src/apps/canvas/component/viewport.tsx | 37 ++++++++++---- src/mol-canvas3d/canvas3d.ts | 8 ++- src/mol-canvas3d/controls/trackball.ts | 14 ++--- src/mol-geo/geometry/marker-data.ts | 8 +++ .../structure/visual/util/polymer.ts | 3 ++ src/mol-util/input/input-observer.ts | 51 +++++++++++-------- 6 files changed, 80 insertions(+), 41 deletions(-) diff --git a/src/apps/canvas/component/viewport.tsx b/src/apps/canvas/component/viewport.tsx index 427b32a20..68a243fa3 100644 --- a/src/apps/canvas/component/viewport.tsx +++ b/src/apps/canvas/component/viewport.tsx @@ -9,6 +9,8 @@ import { App } from '../app'; import { MarkerAction } from 'mol-geo/geometry/marker-data'; import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci'; import { labelFirst } from 'mol-theme/label'; +import { ButtonsType } from 'mol-util/input/input-observer'; +import { throttleTime } from 'rxjs/operators' interface ViewportProps { app: App @@ -40,21 +42,25 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { } this.handleResize() - const viewer = this.props.app.canvas3d + const canvas3d = this.props.app.canvas3d - viewer.input.resize.subscribe(() => this.handleResize()) + canvas3d.input.resize.subscribe(() => this.handleResize()) - let prevLoci: Loci = EmptyLoci - viewer.input.move.subscribe(async ({x, y, inside, buttons}) => { - if (!inside || buttons) return - const p = await viewer.identify(x, y) + let prevHighlightLoci: Loci = EmptyLoci + // TODO can the 'only ever have one extra element in the queue' functionality be done with rxjs? + let highlightQueueLength = 0 + canvas3d.input.move.pipe(throttleTime(50)).subscribe(async ({x, y, inside, buttons}) => { + if (!inside || buttons || highlightQueueLength > 2) return + ++highlightQueueLength + const p = await canvas3d.identify(x, y) + --highlightQueueLength if (p) { - const loci = viewer.getLoci(p) + const loci = canvas3d.getLoci(p) - if (!areLociEqual(loci, prevLoci)) { - viewer.mark(prevLoci, MarkerAction.RemoveHighlight) - viewer.mark(loci, MarkerAction.Highlight) - prevLoci = loci + if (!areLociEqual(loci, prevHighlightLoci)) { + canvas3d.mark(prevHighlightLoci, MarkerAction.RemoveHighlight) + canvas3d.mark(loci, MarkerAction.Highlight) + prevHighlightLoci = loci const label = labelFirst(loci) const pickingInfo = `${label}` @@ -63,6 +69,15 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { } }) + canvas3d.input.click.subscribe(async ({x, y, buttons}) => { + if (buttons !== ButtonsType.Flag.Primary) return + const p = await canvas3d.identify(x, y) + if (p) { + const loci = canvas3d.getLoci(p) + canvas3d.mark(loci, MarkerAction.Toggle) + } + }) + this.props.app.taskCountChanged.subscribe(({ count, info }) => { this.setState({ taskInfo: count > 0 ? info : '' }) }) diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 14159075f..f4f40be83 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -213,6 +213,7 @@ namespace Canvas3D { render('pickObject', pickDirty) render('pickInstance', pickDirty) render('pickGroup', pickDirty) + ctx.gl.finish() pickDirty = false } @@ -244,7 +245,12 @@ namespace Canvas3D { isPicking = false - return { objectId, instanceId, groupId } + // TODO + if (objectId === -1 || instanceId === -1 || groupId === -1) { + return { objectId: -1, instanceId: -1, groupId: -1 } + } else { + return { objectId, instanceId, groupId } + } } handleResize() diff --git a/src/mol-canvas3d/controls/trackball.ts b/src/mol-canvas3d/controls/trackball.ts index 554d81f0a..c88dda597 100644 --- a/src/mol-canvas3d/controls/trackball.ts +++ b/src/mol-canvas3d/controls/trackball.ts @@ -11,7 +11,7 @@ import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra'; import { cameraLookAt, Viewport } from '../camera/util'; -import InputObserver, { DragInput, WheelInput, ButtonsFlag, PinchInput } from 'mol-util/input/input-observer'; +import InputObserver, { DragInput, WheelInput, ButtonsType, PinchInput } from 'mol-util/input/input-observer'; import { Object3D } from 'mol-gl/object3d'; export const DefaultTrackballControlsProps = { @@ -243,24 +243,24 @@ namespace TrackballControls { function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) { if (isStart) { - if (buttons === ButtonsFlag.Primary) { + if (buttons === ButtonsType.Flag.Primary) { Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY)) Vec2.copy(_movePrev, _moveCurr) - } else if (buttons === ButtonsFlag.Auxilary) { + } else if (buttons === ButtonsType.Flag.Auxilary) { Vec2.copy(_zoomStart, getMouseOnScreen(pageX, pageY)) Vec2.copy(_zoomEnd, _zoomStart) - } else if (buttons === ButtonsFlag.Secondary) { + } else if (buttons === ButtonsType.Flag.Secondary) { Vec2.copy(_panStart, getMouseOnScreen(pageX, pageY)) Vec2.copy(_panEnd, _panStart) } } - if (buttons === ButtonsFlag.Primary) { + if (buttons === ButtonsType.Flag.Primary) { Vec2.copy(_movePrev, _moveCurr) Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY)) - } else if (buttons === ButtonsFlag.Auxilary) { + } else if (buttons === ButtonsType.Flag.Auxilary) { Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY)) - } else if (buttons === ButtonsFlag.Secondary) { + } else if (buttons === ButtonsType.Flag.Secondary) { Vec2.copy(_panEnd, getMouseOnScreen(pageX, pageY)) } } diff --git a/src/mol-geo/geometry/marker-data.ts b/src/mol-geo/geometry/marker-data.ts index 56e920c33..3fd6dc64d 100644 --- a/src/mol-geo/geometry/marker-data.ts +++ b/src/mol-geo/geometry/marker-data.ts @@ -18,6 +18,7 @@ export enum MarkerAction { RemoveHighlight, Select, Deselect, + Toggle, Clear } @@ -44,6 +45,13 @@ export function applyMarkerAction(array: Uint8Array, start: number, end: number, v -= 2 } break + case MarkerAction.Toggle: + if (v >= 2) { + v -= 2 + } else { + v += 2 + } + break case MarkerAction.Clear: v = 0 break diff --git a/src/mol-geo/representation/structure/visual/util/polymer.ts b/src/mol-geo/representation/structure/visual/util/polymer.ts index 80a632088..a2739dfa3 100644 --- a/src/mol-geo/representation/structure/visual/util/polymer.ts +++ b/src/mol-geo/representation/structure/visual/util/polymer.ts @@ -68,6 +68,9 @@ export function getPolymerElementLoci(pickingId: PickingId, group: Unit.Symmetry const { objectId, instanceId, groupId } = pickingId if (id === objectId) { const unit = group.units[instanceId] + if (unit === undefined) { + console.log(id, { objectId, instanceId, groupId }, group.units) + } const unitIndex = OrderedSet.indexOf(unit.elements, unit.polymerElements[groupId]) as StructureElement.UnitIndex if (unitIndex !== -1) { const indices = OrderedSet.ofSingleton(unitIndex) diff --git a/src/mol-util/input/input-observer.ts b/src/mol-util/input/input-observer.ts index cd6417f52..08e475e6d 100644 --- a/src/mol-util/input/input-observer.ts +++ b/src/mol-util/input/input-observer.ts @@ -9,6 +9,7 @@ import { Subject } from 'rxjs'; import { Vec2 } from 'mol-math/linear-algebra'; import toPixels from '../to-pixels' +import { BitFlags } from 'mol-util'; function getButtons(event: MouseEvent | Touch) { if (typeof event === 'object') { @@ -21,7 +22,7 @@ function getButtons(event: MouseEvent | Touch) { } else if (b === 3) { return 2 } else if (b > 0) { - return 1<<(b-1) + return 1 << (b - 1) } } else if ('button' in event) { const b = (event as any).button // 'any' to support older browsers @@ -30,7 +31,7 @@ function getButtons(event: MouseEvent | Touch) { } else if (b === 2) { return 2 } else if (b >= 0) { - return 1<<b + return 1 << b } } } @@ -50,19 +51,26 @@ export type ModifiersKeys = { meta: boolean } -export const enum ButtonsFlag { - /** No button or un-initialized */ - None = 0x0, - /** Primary button (usually left) */ - Primary = 0x1, - /** Secondary button (usually right) */ - Secondary = 0x2, - /** Auxilary button (usually middle or mouse wheel button) */ - Auxilary = 0x4, - /** 4th button (typically the "Browser Back" button) */ - Forth = 0x8, - /** 5th button (typically the "Browser Forward" button) */ - Five = 0x10, +export interface ButtonsType extends BitFlags<ButtonsType.Flag> { } + +export namespace ButtonsType { + export const has: (ss: ButtonsType, f: Flag) => boolean = BitFlags.has + export const create: (fs: Flag) => ButtonsType = BitFlags.create + + export const enum Flag { + /** No button or un-initialized */ + None = 0x0, + /** Primary button (usually left) */ + Primary = 0x1, + /** Secondary button (usually right) */ + Secondary = 0x2, + /** Auxilary button (usually middle or mouse wheel button) */ + Auxilary = 0x4, + /** 4th button (typically the "Browser Back" button) */ + Forth = 0x8, + /** 5th button (typically the "Browser Forward" button) */ + Five = 0x10, + } } type BaseInput = { @@ -190,11 +198,11 @@ namespace InputObserver { element.addEventListener( 'contextmenu', onContextMenu, false ) element.addEventListener('wheel', onMouseWheel as any, false) - element.addEventListener('mousedown', onPointerDown as any, false) + element.addEventListener('mousedown', onMouseDown as any, false) // for dragging to work outside canvas bounds, // mouse move/up events have to be added to a parent, i.e. window window.addEventListener('mousemove', onMouseMove as any, false) - window.addEventListener('mouseup', onPointerUp as any, false) + window.addEventListener('mouseup', onMouseUp as any, false) element.addEventListener('touchstart', onTouchStart as any, false) element.addEventListener('touchmove', onTouchMove as any, false) @@ -270,10 +278,10 @@ namespace InputObserver { function onTouchStart (ev: TouchEvent) { if (ev.touches.length === 1) { - buttons = ButtonsFlag.Primary + buttons = ButtonsType.Flag.Primary onPointerDown(ev.touches[0]) } else if (ev.touches.length >= 2) { - buttons = ButtonsFlag.Secondary + buttons = ButtonsType.Flag.Secondary onPointerDown(getCenterTouch(ev)) pinch.next({ distance: lastTouchDistance, delta: 0, isStart: true }) @@ -284,12 +292,12 @@ namespace InputObserver { function onTouchMove (ev: TouchEvent) { if (ev.touches.length === 1) { - buttons = ButtonsFlag.Primary + buttons = ButtonsType.Flag.Primary onPointerMove(ev.touches[0]) } else if (ev.touches.length >= 2) { const touchDistance = getTouchDistance(ev) if (lastTouchDistance - touchDistance < 4) { - buttons = ButtonsFlag.Secondary + buttons = ButtonsType.Flag.Secondary onPointerMove(getCenterTouch(ev)) } else { pinch.next({ @@ -313,7 +321,6 @@ namespace InputObserver { } function onMouseUp (ev: MouseEvent) { - buttons = getButtons(ev) onPointerUp(ev) } -- GitLab