Skip to content
Snippets Groups Projects
Commit c5fff9d3 authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: highlight and selection on canvas helper

parent d1d02a18
No related branches found
No related tags found
No related merge requests found
...@@ -21,6 +21,12 @@ export interface PickingId { ...@@ -21,6 +21,12 @@ export interface PickingId {
groupId: number groupId: number
} }
export namespace PickingId {
export function areSame(a: PickingId, b: PickingId) {
return a.objectId === b.objectId && a.instanceId === b.instanceId && a.groupId === b.groupId;
}
}
export interface PickingInfo { export interface PickingInfo {
label: string label: string
data?: any data?: any
......
...@@ -37,4 +37,4 @@ export function areLociEqual(lociA: Loci, lociB: Loci) { ...@@ -37,4 +37,4 @@ export function areLociEqual(lociA: Loci, lociB: Loci) {
return false return false
} }
export type Loci = StructureElement.Loci | Link.Loci | EveryLoci | EmptyLoci | Shape.Loci export type Loci = StructureElement.Loci | Link.Loci | EveryLoci | EmptyLoci | Shape.Loci
\ No newline at end of file \ No newline at end of file
...@@ -6,43 +6,76 @@ ...@@ -6,43 +6,76 @@
import { PluginBehavior } from '../behavior'; import { PluginBehavior } from '../behavior';
import { PluginStateObject as SO } from '../../state/base'; import { PluginStateObject as SO } from '../../state/base';
import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci';
class _AddRepresentationToCanvas extends PluginBehavior.Handler { import { MarkerAction } from 'mol-geo/geometry/marker-data';
register(): void {
this.subscribeObservable(this.ctx.events.state.data.object.created, o => {
if (!SO.isRepresentation3D(o.obj)) return;
this.ctx.canvas3d.add(o.obj.data);
this.ctx.canvas3d.requestDraw(true);
});
this.subscribeObservable(this.ctx.events.state.data.object.updated, o => {
const oo = o.obj;
if (!SO.isRepresentation3D(oo)) return;
this.ctx.canvas3d.add(oo.data);
this.ctx.canvas3d.requestDraw(true);
});
this.subscribeObservable(this.ctx.events.state.data.object.removed, o => {
const oo = o.obj;
if (!SO.isRepresentation3D(oo)) return;
this.ctx.canvas3d.remove(oo.data);
this.ctx.canvas3d.requestDraw(true);
oo.data.destroy();
});
this.subscribeObservable(this.ctx.events.state.data.object.replaced, o => {
if (o.oldObj && SO.isRepresentation3D(o.oldObj)) {
this.ctx.canvas3d.remove(o.oldObj.data);
this.ctx.canvas3d.requestDraw(true);
o.oldObj.data.destroy();
}
if (o.newObj && SO.isRepresentation3D(o.newObj)) {
this.ctx.canvas3d.add(o.newObj.data);
this.ctx.canvas3d.requestDraw(true);
}
});
}
}
export const AddRepresentationToCanvas = PluginBehavior.create({ export const AddRepresentationToCanvas = PluginBehavior.create({
name: 'add-representation-to-canvas', name: 'add-representation-to-canvas',
ctor: _AddRepresentationToCanvas, ctor: class extends PluginBehavior.Handler {
register(): void {
this.subscribeObservable(this.ctx.events.state.data.object.created, o => {
if (!SO.isRepresentation3D(o.obj)) return;
this.ctx.canvas3d.add(o.obj.data);
this.ctx.canvas3d.requestDraw(true);
});
this.subscribeObservable(this.ctx.events.state.data.object.updated, o => {
const oo = o.obj;
if (!SO.isRepresentation3D(oo)) return;
this.ctx.canvas3d.add(oo.data);
this.ctx.canvas3d.requestDraw(true);
});
this.subscribeObservable(this.ctx.events.state.data.object.removed, o => {
const oo = o.obj;
if (!SO.isRepresentation3D(oo)) return;
this.ctx.canvas3d.remove(oo.data);
this.ctx.canvas3d.requestDraw(true);
oo.data.destroy();
});
this.subscribeObservable(this.ctx.events.state.data.object.replaced, o => {
if (o.oldObj && SO.isRepresentation3D(o.oldObj)) {
this.ctx.canvas3d.remove(o.oldObj.data);
this.ctx.canvas3d.requestDraw(true);
o.oldObj.data.destroy();
}
if (o.newObj && SO.isRepresentation3D(o.newObj)) {
this.ctx.canvas3d.add(o.newObj.data);
this.ctx.canvas3d.requestDraw(true);
}
});
}
},
display: { name: 'Add Representation To Canvas' } display: { name: 'Add Representation To Canvas' }
});
export const HighlightLoci = PluginBehavior.create({
name: 'representation-highlight-loci',
ctor: class extends PluginBehavior.Handler {
register(): void {
let prevLoci: Loci = EmptyLoci, prevRepr: any = void 0;
this.subscribeObservable(this.ctx.behaviors.canvas.highlightLoci, current => {
if (!this.ctx.canvas3d) return;
if (current.repr !== prevRepr || !areLociEqual(current.loci, prevLoci)) {
this.ctx.canvas3d.mark(prevLoci, MarkerAction.RemoveHighlight);
this.ctx.canvas3d.mark(current.loci, MarkerAction.Highlight);
prevLoci = current.loci;
prevRepr = current.repr;
}
});
}
},
display: { name: 'Highlight Loci on Canvas' }
});
export const SelectLoci = PluginBehavior.create({
name: 'representation-select-loci',
ctor: class extends PluginBehavior.Handler {
register(): void {
this.subscribeObservable(this.ctx.behaviors.canvas.selectLoci, ({ loci }) => {
if (!this.ctx.canvas3d) return;
this.ctx.canvas3d.mark(loci, MarkerAction.Toggle);
});
}
},
display: { name: 'Select Loci on Canvas' }
}); });
\ No newline at end of file
...@@ -16,6 +16,8 @@ import { PluginCommand, PluginCommands } from './command'; ...@@ -16,6 +16,8 @@ import { PluginCommand, PluginCommands } from './command';
import { Task } from 'mol-task'; import { Task } from 'mol-task';
import { merge } from 'rxjs'; import { merge } from 'rxjs';
import { PluginBehaviors } from './behavior'; import { PluginBehaviors } from './behavior';
import { Loci, EmptyLoci } from 'mol-model/loci';
import { Representation } from 'mol-repr';
export class PluginContext { export class PluginContext {
private disposed = false; private disposed = false;
...@@ -36,6 +38,10 @@ export class PluginContext { ...@@ -36,6 +38,10 @@ export class PluginContext {
data: this.state.data.context.behaviors, data: this.state.data.context.behaviors,
behavior: this.state.behavior.context.behaviors behavior: this.state.behavior.context.behaviors
}, },
canvas: {
highlightLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
selectLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
},
command: this.commands.behaviour command: this.commands.behaviour
}; };
...@@ -82,6 +88,8 @@ export class PluginContext { ...@@ -82,6 +88,8 @@ export class PluginContext {
.and().toRoot().apply(PluginBehaviors.Data.Update, { ref: PluginBehaviors.Data.Update.id }) .and().toRoot().apply(PluginBehaviors.Data.Update, { ref: PluginBehaviors.Data.Update.id })
.and().toRoot().apply(PluginBehaviors.Data.RemoveObject, { ref: PluginBehaviors.Data.RemoveObject.id }) .and().toRoot().apply(PluginBehaviors.Data.RemoveObject, { ref: PluginBehaviors.Data.RemoveObject.id })
.and().toRoot().apply(PluginBehaviors.Representation.AddRepresentationToCanvas, { ref: PluginBehaviors.Representation.AddRepresentationToCanvas.id }) .and().toRoot().apply(PluginBehaviors.Representation.AddRepresentationToCanvas, { ref: PluginBehaviors.Representation.AddRepresentationToCanvas.id })
.and().toRoot().apply(PluginBehaviors.Representation.HighlightLoci, { ref: PluginBehaviors.Representation.HighlightLoci.id })
.and().toRoot().apply(PluginBehaviors.Representation.SelectLoci, { ref: PluginBehaviors.Representation.SelectLoci.id })
.getTree(); .getTree();
await this.state.updateBehaviour(tree); await this.state.updateBehaviour(tree);
......
...@@ -7,9 +7,10 @@ ...@@ -7,9 +7,10 @@
import * as React from 'react'; import * as React from 'react';
import { PluginContext } from '../context'; import { PluginContext } from '../context';
import { Loci, EmptyLoci, areLociEqual } from 'mol-model/loci'; // import { Loci, EmptyLoci, areLociEqual } from 'mol-model/loci';
import { MarkerAction } from 'mol-geo/geometry/marker-data'; // import { MarkerAction } from 'mol-geo/geometry/marker-data';
import { ButtonsType } from 'mol-util/input/input-observer'; import { ButtonsType } from 'mol-util/input/input-observer';
import { Canvas3dIdentifyHelper } from 'mol-plugin/util/canvas3d-identify';
interface ViewportProps { interface ViewportProps {
plugin: PluginContext plugin: PluginContext
...@@ -40,29 +41,21 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { ...@@ -40,29 +41,21 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
const canvas3d = this.props.plugin.canvas3d; const canvas3d = this.props.plugin.canvas3d;
canvas3d.input.resize.subscribe(() => this.handleResize()); canvas3d.input.resize.subscribe(() => this.handleResize());
let prevLoci: Loci = EmptyLoci; const idHelper = new Canvas3dIdentifyHelper(this.props.plugin, 15);
canvas3d.input.move.subscribe(async ({x, y, inside, buttons}) => {
if (!inside || buttons) return; canvas3d.input.move.subscribe(({x, y, inside, buttons}) => {
const p = await canvas3d.identify(x, y); if (!inside || buttons) { return; }
if (p) { idHelper.move(x, y);
const { loci } = canvas3d.getLoci(p); });
if (!areLociEqual(loci, prevLoci)) { canvas3d.input.leave.subscribe(() => {
canvas3d.mark(prevLoci, MarkerAction.RemoveHighlight); idHelper.leave();
canvas3d.mark(loci, MarkerAction.Highlight); });
prevLoci = loci;
} canvas3d.input.click.subscribe(({x, y, buttons}) => {
} if (buttons !== ButtonsType.Flag.Primary) return;
}) idHelper.select(x, y);
});
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)
}
})
} }
componentWillUnmount() { componentWillUnmount() {
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginContext } from '../context';
import { PickingId } from 'mol-geo/geometry/picking';
import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci';
import { Representation } from 'mol-repr';
export class Canvas3dIdentifyHelper {
private cX = -1;
private cY = -1;
private lastX = -1;
private lastY = -1;
private id: PickingId | undefined = void 0;
private currentIdentifyT = 0;
private prevLoci: { loci: Loci, repr?: Representation.Any } = { loci: EmptyLoci };
private prevT = 0;
private inside = false;
private async identify(select: boolean, t: number) {
if (this.lastX !== this.cX && this.lastY !== this.cY) {
this.id = await this.ctx.canvas3d.identify(this.cX, this.cY);
this.lastX = this.cX;
this.lastY = this.cY;
}
if (!this.id) return;
if (select) {
this.ctx.behaviors.canvas.selectLoci.next(this.ctx.canvas3d.getLoci(this.id));
return;
}
// only highlight the latest
if (!this.inside || this.currentIdentifyT !== t) {
return;
}
const loci = this.ctx.canvas3d.getLoci(this.id);
if (loci.repr !== this.prevLoci.repr || !areLociEqual(loci.loci, this.prevLoci.loci)) {
this.ctx.behaviors.canvas.highlightLoci.next(loci);
this.prevLoci = loci;
}
}
private animate: (t: number) => void = t => {
if (this.inside && t - this.prevT > 1000 / this.maxFps) {
this.prevT = t;
this.currentIdentifyT = t;
this.identify(false, t);
}
requestAnimationFrame(this.animate);
}
leave() {
this.inside = false;
if (this.prevLoci.loci !== EmptyLoci) {
this.prevLoci = { loci: EmptyLoci };
this.ctx.behaviors.canvas.highlightLoci.next(this.prevLoci);
this.ctx.canvas3d.requestDraw(true);
}
}
move(x: number, y: number) {
this.inside = true;
this.cX = x;
this.cY = y;
}
select(x: number, y: number) {
this.cX = x;
this.cY = y;
this.identify(true, 0);
}
constructor(private ctx: PluginContext, private maxFps: number = 15) {
this.animate(0);
}
}
\ No newline at end of file
...@@ -141,6 +141,8 @@ interface InputObserver { ...@@ -141,6 +141,8 @@ interface InputObserver {
pinch: Subject<PinchInput>, pinch: Subject<PinchInput>,
click: Subject<ClickInput>, click: Subject<ClickInput>,
move: Subject<MoveInput>, move: Subject<MoveInput>,
leave: Subject<undefined>,
enter: Subject<undefined>,
resize: Subject<ResizeInput>, resize: Subject<ResizeInput>,
dispose: () => void dispose: () => void
...@@ -175,6 +177,8 @@ namespace InputObserver { ...@@ -175,6 +177,8 @@ namespace InputObserver {
const wheel = new Subject<WheelInput>() const wheel = new Subject<WheelInput>()
const pinch = new Subject<PinchInput>() const pinch = new Subject<PinchInput>()
const resize = new Subject<ResizeInput>() const resize = new Subject<ResizeInput>()
const leave = new Subject<undefined>()
const enter = new Subject<undefined>()
attach() attach()
...@@ -189,6 +193,8 @@ namespace InputObserver { ...@@ -189,6 +193,8 @@ namespace InputObserver {
pinch, pinch,
click, click,
move, move,
leave,
enter,
resize, resize,
dispose dispose
...@@ -204,6 +210,9 @@ namespace InputObserver { ...@@ -204,6 +210,9 @@ namespace InputObserver {
window.addEventListener('mousemove', onMouseMove as any, false) window.addEventListener('mousemove', onMouseMove as any, false)
window.addEventListener('mouseup', onMouseUp as any, false) window.addEventListener('mouseup', onMouseUp as any, false)
element.addEventListener('mouseenter', onMouseEnter as any, false)
element.addEventListener('mouseleave', onMouseLeave as any, false)
element.addEventListener('touchstart', onTouchStart as any, false) element.addEventListener('touchstart', onTouchStart as any, false)
element.addEventListener('touchmove', onTouchMove as any, false) element.addEventListener('touchmove', onTouchMove as any, false)
element.addEventListener('touchend', onTouchEnd as any, false) element.addEventListener('touchend', onTouchEnd as any, false)
...@@ -227,6 +236,9 @@ namespace InputObserver { ...@@ -227,6 +236,9 @@ namespace InputObserver {
window.removeEventListener('mousemove', onMouseMove as any, false) window.removeEventListener('mousemove', onMouseMove as any, false)
window.removeEventListener('mouseup', onMouseUp as any, false) window.removeEventListener('mouseup', onMouseUp as any, false)
element.removeEventListener('mouseenter', onMouseEnter as any, false)
element.removeEventListener('mouseleave', onMouseLeave as any, false)
element.removeEventListener('touchstart', onTouchStart as any, false) element.removeEventListener('touchstart', onTouchStart as any, false)
element.removeEventListener('touchmove', onTouchMove as any, false) element.removeEventListener('touchmove', onTouchMove as any, false)
element.removeEventListener('touchend', onTouchEnd as any, false) element.removeEventListener('touchend', onTouchEnd as any, false)
...@@ -386,6 +398,14 @@ namespace InputObserver { ...@@ -386,6 +398,14 @@ namespace InputObserver {
} }
} }
function onMouseEnter (ev: Event) {
enter.next();
}
function onMouseLeave (ev: Event) {
leave.next();
}
function onResize (ev: Event) { function onResize (ev: Event) {
resize.next() resize.next()
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment