import { MODE_MASS_GENERATION, MODE_OBJECT_SELECT, MODE_PAN_VIEW, POINTER_HISTORY_SIZE, ZOOM_IN_FACTOR, ZOOM_OUT_FACTOR } from './config.js'; export class Pointer { sim = undefined; pointerHistory = []; suppressClick = false; constructor(sim) { this.sim = sim; // Monitor mouse movements const {canvas} = this.sim.display; window.addEventListener('pointermove', e => { if (this.sim.getOption('debug.cursorInfo')) { this.sim.info['pointermove'] = [`${e.clientX.toPrecision(6)}, `, `${e.clientY.toPrecision(6)}`]; } this.handlePointerMove({x: e.clientX, y: e.clientY}); }); canvas.addEventListener('pointerdown', e => { this.handlePointerDown({x: e.clientX, y: e.clientY}); }); window.addEventListener('pointerup', e => { this.handlePointerUp({x: e.clientX, y: e.clientY}); }); // window.addEventListener('pointerleave', e => { // this.handlePointerUp({x: e.clientX, y: e.clientY}); // }); // Monitor wheel events canvas.addEventListener('wheel', e => { const factor = e.deltaY < 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR; const {x, y} = this.sim.screenToSim(e.clientX, e.clientY); this.sim.scheduleZoom({x, y}, factor); }); window.addEventListener('focus', () => { console.log('window focus'); }); window.addEventListener('blur', () => { console.log('window blur'); }); } handlePointerDown({x: clientX, y: clientY}) { this.updatePointer({x: clientX, y: clientY}); switch (this.sim.getCurrentMode()) { case MODE_MASS_GENERATION: { const {x, y} = this.sim.screenToSim(clientX, clientY) this.sim.system.handlePointerDown({x, y}); break; } case MODE_PAN_VIEW: { this.sim.panning.handlePointerDown({x: clientX, y: clientY}); break; } case MODE_OBJECT_SELECT: { this.sim.select.handlePointerDown({x: clientX, y: clientY}); break; } } } // Handle cursor (mouse or touch) movement handlePointerMove({x: clientX, y: clientY}) { // TODO: If e.touches.length > 1, user may be engaging pinch to zoom this.updatePointer({x: clientX, y: clientY}); switch (this.sim.getCurrentMode()) { case MODE_MASS_GENERATION: { const {x, y} = this.sim.screenToSim(clientX, clientY); this.sim.system.handlePointerMove({x, y}); break; } case MODE_PAN_VIEW: { this.sim.panning.handlePointerMove({x: clientX, y: clientY}); break; } case MODE_OBJECT_SELECT: { this.sim.select.handlePointerMove({x: clientX, y: clientY}); break; } } } handlePointerUp({x: clientX, y: clientY}) { switch (this.sim.getCurrentMode()) { case MODE_MASS_GENERATION: { const {x, y} = this.sim.screenToSim(clientX, clientY); this.sim.system.handlePointerUp({x, y}); break; } case MODE_PAN_VIEW: { this.sim.panning.handlePointerUp({x: clientX, y: clientY}); break; } case MODE_OBJECT_SELECT: { this.sim.select.handlePointerUp({x: clientX, y: clientY}); break; } } } frame() { // Add another entry for the current pointer position const {pointerHistory} = this; if (pointerHistory.length) { const currentPointer = pointerHistory[pointerHistory.length - 1]; this.updatePointer(currentPointer); } } getPointerVelocity(points = POINTER_HISTORY_SIZE) { // Average over pointer history if (this.pointerHistory.length < 2) { return this.latestVelocity; } points = Math.min(points, POINTER_HISTORY_SIZE, this.pointerHistory.length); const start = this.pointerHistory[this.pointerHistory.length - points]; const end = this.pointerHistory[this.pointerHistory.length - 1]; const dt = (end.t - start.t); return { x: (end.x - start.x) / dt, y: (end.y - start.y) / dt, dt }; } updatePointer({x, y}) { const t = this.sim.rawTime; while (this.pointerHistory.length >= POINTER_HISTORY_SIZE) { this.pointerHistory.shift(); } const v = this.getPointerVelocity(); this.pointerHistory.push({t, x, y, v}); } get latestVelocity() { const latestPointer = this.pointerHistory[this.pointerHistory.length - 1]; return { x: 0, y: 0, ...latestPointer?.v } } }