import { MODE_MASS_GENERATION, MODE_OBJECT_SELECT, MODE_PAN_VIEW, POINTER_HISTORY_SIZE, TOOLBAR_CLASSNAME, ZOOM_IN_FACTOR, ZOOM_OUT_FACTOR, } from './config.js'; export class Pointer { sim = undefined; pointerHistory = []; touchStart = undefined; // {x: undefined, y: undefined, t: undefined}; touchLatest = undefined; // {x: undefined, y: undefined, t: undefined}; suppressClick = false; constructor(sim) { this.sim = sim; // Monitor mouse movements const el = window; el.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}); }); el.addEventListener('pointerdown', e => { let target = e.target; while (target && !target.classList?.contains(TOOLBAR_CLASSNAME)) { target = target.parentNode; } if (target) { return; } this.handlePointerDown({x: e.clientX, y: e.clientY}); }); el.addEventListener('pointerup', e => { this.handlePointerUp({x: e.clientX, y: e.clientY}); }); el.addEventListener('pointerleave', e => { this.handlePointerUp({x: e.clientX, y: e.clientY}); }); // Monitor wheel events el.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); }); el.addEventListener('focus', () => { console.log('window focus'); }); el.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 } } }