import { POINTER_HISTORY_SIZE, ZOOM_IN_FACTOR, ZOOM_OUT_FACTOR, DISPLAY_CURSOR_INFO, DRAGGABLE_ELEMENT_CLASSNAME, } from './config.js'; function dispatchEvent(target, eventType, data) { const ev = new CustomEvent(eventType, {detail: data}); target.dispatchEvent(ev); } export class Pointer { sim = undefined; pointerHistory = []; draggingElement = undefined; constructor(sim) { this.sim = sim; // Monitor mouse movements const el = window; el.addEventListener('pointermove', e => { if (DISPLAY_CURSOR_INFO) { this.sim.info['pointermove'] = [`${e.clientX}, `, `${e.clientY}`]; } if (this.draggingElement) { // e.preventDefault(); this.draggingElement.dragging.pointerEnd = { x: e.clientX, y: e.clientY, }; } else { const {x, y} = this.sim.screenToSim(e.clientX, e.clientY); this.handlePointerMove({x, y}); } }); el.addEventListener('pointerdown', e => { // e.preventDefault(); let target = e.target; while (target && !target.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) { target = target.parentElement; } if (target?.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) { // e.preventDefault(); this.draggingElement = target; this.draggingElement.dragging = { elementStart: { x: parseInt(this.draggingElement.style.left), y: parseInt(this.draggingElement.style.top), }, pointerStart: { x: e.clientX, y: e.clientY, }, pointerEnd: { x: e.clientX, y: e.clientY, }, }; } else { this.handlePointerDown(this.sim.screenToSim(e.clientX, e.clientY)); } }); el.addEventListener('pointerup', e => { // e.preventDefault(); if (this.draggingElement) { this.draggingElement.dragging = undefined; this.draggingElement = undefined; this.lastPosition = {x: undefined, y: undefined}; } else { this.handlePointerUp(this.sim.screenToSim(e.clientX, e.clientY)); } }); el.addEventListener('click', e => { // e.preventDefault(); }); // Monitor wheel events el.addEventListener('wheel', e => { // e.preventDefault(); // Wheel scroll down => positive deltaY => ZOOM IN const factor = e.deltaY > 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR; this.sim.scheduleZoom(this.sim.screenToSim(e.clientX, e.clientY), factor); }); } getPointerVelocity() { // Average over pointer history if (this.pointerHistory.length < 2) { return {x: 0, y: 0, dt: 1}; } const start = this.pointerHistory[0]; const end = this.pointerHistory[this.pointerHistory.length - 1]; const dt = (end.t - start.t) / 1000; return { x: (end.x - start.x) / dt, y: (end.y - start.y) / dt, dt }; } clearPointerHistory() { this.pointerHistory = []; } updatePointer({x, y}) { const t = document.timeline.currentTime; this.pointerHistory.push({x, y, t}); if (this.pointerHistory.length > POINTER_HISTORY_SIZE) { this.pointerHistory.shift(); } } handlePointerDown({x, y}) { this.clearPointerHistory(); this.updatePointer({x, y}); this.sim.objects.handlePointerDown({x, y}); } handlePointerUp({x, y}) { // TODO: Conditional? this.sim.objects.handlePointerUp({x, y}); } // Handle cursor (mouse or touch) movement // TODO: If e.touches.length > 1, user may be engaging pinch to zoom handlePointerMove({x, y}) { this.updatePointer({x, y}); const {x: vx, y: vy} = this.getPointerVelocity(); // If the cursor moves while creating an object, or while an object is selected, // update the position and velocity of the object const obj = this.sim.objects.getSelectedOrCreating(); if (obj !== undefined) { obj.position.x = x; obj.position.y = y; obj.velocity.x = vx; obj.velocity.y = vy; } } }