151 lines
4.0 KiB
JavaScript
151 lines
4.0 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
}
|