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 {
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 {
label: string
data?: any
......
......@@ -37,4 +37,4 @@ export function areLociEqual(lociA: Loci, lociB: Loci) {
return false
}
export type Loci = StructureElement.Loci | Link.Loci | EveryLoci | EmptyLoci | Shape.Loci
\ No newline at end of file
export type Loci = StructureElement.Loci | Link.Loci | EveryLoci | EmptyLoci | Shape.Loci
\ No newline at end of file
......@@ -6,43 +6,76 @@
import { PluginBehavior } from '../behavior';
import { PluginStateObject as SO } from '../../state/base';
class _AddRepresentationToCanvas 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);
}
});
}
}
import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci';
import { MarkerAction } from 'mol-geo/geometry/marker-data';
export const AddRepresentationToCanvas = PluginBehavior.create({
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' }
});
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';
import { Task } from 'mol-task';
import { merge } from 'rxjs';
import { PluginBehaviors } from './behavior';
import { Loci, EmptyLoci } from 'mol-model/loci';
import { Representation } from 'mol-repr';
export class PluginContext {
private disposed = false;
......@@ -36,6 +38,10 @@ export class PluginContext {
data: this.state.data.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
};
......@@ -82,6 +88,8 @@ export class PluginContext {
.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.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();
await this.state.updateBehaviour(tree);
......
......@@ -7,9 +7,10 @@
import * as React from 'react';
import { PluginContext } from '../context';
import { Loci, EmptyLoci, areLociEqual } from 'mol-model/loci';
import { MarkerAction } from 'mol-geo/geometry/marker-data';
// import { Loci, EmptyLoci, areLociEqual } from 'mol-model/loci';
// import { MarkerAction } from 'mol-geo/geometry/marker-data';
import { ButtonsType } from 'mol-util/input/input-observer';
import { Canvas3dIdentifyHelper } from 'mol-plugin/util/canvas3d-identify';
interface ViewportProps {
plugin: PluginContext
......@@ -40,29 +41,21 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
const canvas3d = this.props.plugin.canvas3d;
canvas3d.input.resize.subscribe(() => this.handleResize());
let prevLoci: Loci = EmptyLoci;
canvas3d.input.move.subscribe(async ({x, y, inside, buttons}) => {
if (!inside || buttons) return;
const p = await canvas3d.identify(x, y);
if (p) {
const { loci } = canvas3d.getLoci(p);
if (!areLociEqual(loci, prevLoci)) {
canvas3d.mark(prevLoci, MarkerAction.RemoveHighlight);
canvas3d.mark(loci, MarkerAction.Highlight);
prevLoci = loci;
}
}
})
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)
}
})
const idHelper = new Canvas3dIdentifyHelper(this.props.plugin, 15);
canvas3d.input.move.subscribe(({x, y, inside, buttons}) => {
if (!inside || buttons) { return; }
idHelper.move(x, y);
});
canvas3d.input.leave.subscribe(() => {
idHelper.leave();
});
canvas3d.input.click.subscribe(({x, y, buttons}) => {
if (buttons !== ButtonsType.Flag.Primary) return;
idHelper.select(x, y);
});
}
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 {
pinch: Subject<PinchInput>,
click: Subject<ClickInput>,
move: Subject<MoveInput>,
leave: Subject<undefined>,
enter: Subject<undefined>,
resize: Subject<ResizeInput>,
dispose: () => void
......@@ -175,6 +177,8 @@ namespace InputObserver {
const wheel = new Subject<WheelInput>()
const pinch = new Subject<PinchInput>()
const resize = new Subject<ResizeInput>()
const leave = new Subject<undefined>()
const enter = new Subject<undefined>()
attach()
......@@ -189,6 +193,8 @@ namespace InputObserver {
pinch,
click,
move,
leave,
enter,
resize,
dispose
......@@ -204,6 +210,9 @@ namespace InputObserver {
window.addEventListener('mousemove', onMouseMove 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('touchmove', onTouchMove as any, false)
element.addEventListener('touchend', onTouchEnd as any, false)
......@@ -227,6 +236,9 @@ namespace InputObserver {
window.removeEventListener('mousemove', onMouseMove 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('touchmove', onTouchMove as any, false)
element.removeEventListener('touchend', onTouchEnd as any, false)
......@@ -386,6 +398,14 @@ namespace InputObserver {
}
}
function onMouseEnter (ev: Event) {
enter.next();
}
function onMouseLeave (ev: Event) {
leave.next();
}
function onResize (ev: Event) {
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