285 lines
8.4 KiB
JavaScript
285 lines
8.4 KiB
JavaScript
|
const inputConfig = {
|
||
|
clickRadius: 10, // Radius to be considered a click (pixels). If farther, turns into a drag
|
||
|
clickTiming: 500, // Timing window to be considered a click (ms). If longer, turns into a drag
|
||
|
dClickTiming: 500, // Timing window to be considered a double click (ms).
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Mouse input processing
|
||
|
*/
|
||
|
// Base object generator functions
|
||
|
function _context_coords() {
|
||
|
return {
|
||
|
dragging: {
|
||
|
left: null,
|
||
|
middle: null,
|
||
|
right: null,
|
||
|
},
|
||
|
|
||
|
prev: {
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
},
|
||
|
|
||
|
pos: {
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
function _mouse_observers() {
|
||
|
return {
|
||
|
// Simple click handlers
|
||
|
onclick: new Observer(),
|
||
|
// Double click handlers (will still trigger simple click handler as well)
|
||
|
ondclick: new Observer(),
|
||
|
// Drag handler
|
||
|
ondragstart: new Observer(),
|
||
|
ondrag: new Observer(),
|
||
|
ondragend: new Observer(),
|
||
|
// Paint handler (like drag handler, but with no delay); will trigger during clicks too
|
||
|
onpaintstart: new Observer(),
|
||
|
onpaint: new Observer(),
|
||
|
onpaintend: new Observer(),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function _context_observers() {
|
||
|
return {
|
||
|
left: _mouse_observers(),
|
||
|
middle: _mouse_observers(),
|
||
|
right: _mouse_observers(),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const mouse = {
|
||
|
buttons: {
|
||
|
right: null,
|
||
|
left: null,
|
||
|
middle: null,
|
||
|
},
|
||
|
|
||
|
// Mouse Actions in Window Coordinates
|
||
|
window: _context_coords(),
|
||
|
|
||
|
// Mouse Actions in Canvas Coordinates
|
||
|
canvas: _context_coords(),
|
||
|
|
||
|
// Mouse Actions in World Coordinates
|
||
|
world: _context_coords(),
|
||
|
|
||
|
listen: {
|
||
|
window: _context_observers(),
|
||
|
canvas: _context_observers(),
|
||
|
world: _context_observers(),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
function _mouse_state_snapshot() {
|
||
|
return {
|
||
|
buttons: window.structuredClone(mouse.buttons),
|
||
|
window: window.structuredClone(mouse.window),
|
||
|
canvas: window.structuredClone(mouse.canvas),
|
||
|
world: window.structuredClone(mouse.world),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const _double_click_timeout = {};
|
||
|
const _drag_start_timeout = {};
|
||
|
|
||
|
window.onmousedown = (evn) => {
|
||
|
const time = new Date();
|
||
|
|
||
|
// Processes for a named button
|
||
|
const onhold = (key) => () => {
|
||
|
if (_double_click_timeout[key]) {
|
||
|
// ondclick event
|
||
|
['window', 'canvas', 'world'].forEach((ctx) =>
|
||
|
mouse.listen[ctx][key].ondclick.emit({
|
||
|
buttonId: evn.button,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
})
|
||
|
);
|
||
|
} else {
|
||
|
// Start timer
|
||
|
_double_click_timeout[key] = setTimeout(
|
||
|
() => delete _double_click_timeout[key],
|
||
|
inputConfig.dClickTiming
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Set drag start timeout
|
||
|
_drag_start_timeout[key] = setTimeout(() => {
|
||
|
['window', 'canvas', 'world'].forEach((ctx) => {
|
||
|
mouse.listen[ctx][key].ondragstart.emit({
|
||
|
buttonId: evn.button,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
});
|
||
|
if (mouse[ctx].dragging[key])
|
||
|
mouse[ctx].dragging[key].drag = true;
|
||
|
|
||
|
delete _drag_start_timeout[key];
|
||
|
});
|
||
|
}, inputConfig.clickTiming);
|
||
|
|
||
|
['window', 'canvas', 'world'].forEach((ctx) => {
|
||
|
mouse.buttons[key] = time;
|
||
|
mouse[ctx].dragging[key] = {};
|
||
|
Object.assign(mouse[ctx].dragging[key], mouse[ctx].pos);
|
||
|
|
||
|
// onpaintstart event
|
||
|
mouse.listen[ctx][key].onpaintstart.emit({
|
||
|
buttonId: evn.button,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// Runs the correct handler
|
||
|
const buttons = [onhold('left'), onhold('middle'), onhold('right')];
|
||
|
|
||
|
buttons[evn.button] && buttons[evn.button]();
|
||
|
};
|
||
|
|
||
|
window.onmouseup = (evn) => {
|
||
|
const time = new Date();
|
||
|
|
||
|
// Processes for a named button
|
||
|
const onrelease = (key) => () => {
|
||
|
['window', 'canvas', 'world'].forEach((ctx) => {
|
||
|
const start = {
|
||
|
x: mouse[ctx].dragging[key].x,
|
||
|
y: mouse[ctx].dragging[key].y,
|
||
|
};
|
||
|
|
||
|
// onclick event
|
||
|
const dx = mouse[ctx].pos.x - start.x;
|
||
|
const dy = mouse[ctx].pos.y - start.y;
|
||
|
|
||
|
if (
|
||
|
time.getTime() - mouse.buttons[key].getTime() <
|
||
|
inputConfig.clickTiming &&
|
||
|
dx * dx + dy * dy <
|
||
|
inputConfig.clickRadius * inputConfig.clickRadius
|
||
|
)
|
||
|
mouse.listen[ctx][key].onclick.emit({
|
||
|
buttonId: evn.button,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
});
|
||
|
|
||
|
// onpaintend event
|
||
|
mouse.listen[ctx][key].onpaintend.emit({
|
||
|
buttonId: evn.button,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
});
|
||
|
|
||
|
// ondragend event
|
||
|
if (mouse[ctx].dragging[key].drag)
|
||
|
mouse.listen[ctx][key].ondragend.emit({
|
||
|
buttonId: evn.button,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
});
|
||
|
|
||
|
mouse[ctx].dragging[key] = null;
|
||
|
});
|
||
|
|
||
|
if (_drag_start_timeout[key] !== undefined) {
|
||
|
clearTimeout(_drag_start_timeout[key]);
|
||
|
delete _drag_start_timeout[key];
|
||
|
}
|
||
|
mouse.buttons[key] = null;
|
||
|
};
|
||
|
|
||
|
// Runs the correct handler
|
||
|
const buttons = [
|
||
|
onrelease('left'),
|
||
|
onrelease('middle'),
|
||
|
onrelease('right'),
|
||
|
];
|
||
|
|
||
|
buttons[evn.button] && buttons[evn.button]();
|
||
|
};
|
||
|
|
||
|
window.onmousemove = (evn) => {
|
||
|
// Set Window Coordinates
|
||
|
Object.assign(mouse.window.prev, mouse.window.pos);
|
||
|
mouse.window.pos = { x: evn.clientX, y: evn.clientY };
|
||
|
|
||
|
// Set Canvas Coordinates (using overlay canvas as reference)
|
||
|
if (evn.target.id === 'overlayCanvas') {
|
||
|
Object.assign(mouse.canvas.prev, mouse.canvas.pos);
|
||
|
mouse.canvas.pos = { x: evn.layerX, y: evn.layerY };
|
||
|
}
|
||
|
|
||
|
// Set World Coordinates (For now the same as canvas coords; Will be useful with infinite canvas)
|
||
|
if (evn.target.id === 'overlayCanvas') {
|
||
|
Object.assign(mouse.world.prev, mouse.world.pos);
|
||
|
mouse.world.pos = { x: evn.layerX, y: evn.layerY };
|
||
|
}
|
||
|
|
||
|
['window', 'canvas', 'world'].forEach((ctx) => {
|
||
|
['left', 'middle', 'right'].forEach((key) => {
|
||
|
// ondrag event
|
||
|
if (mouse[ctx].dragging[key] && mouse[ctx].dragging[key].drag)
|
||
|
mouse.listen[ctx][key].ondrag.emit({
|
||
|
px: mouse[ctx].prev.x,
|
||
|
py: mouse[ctx].prev.x,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
});
|
||
|
|
||
|
// onpaint event
|
||
|
if (mouse[ctx].dragging[key])
|
||
|
mouse.listen[ctx][key].onpaint.emit({
|
||
|
px: mouse[ctx].prev.x,
|
||
|
py: mouse[ctx].prev.x,
|
||
|
x: mouse[ctx].pos.x,
|
||
|
y: mouse[ctx].pos.y,
|
||
|
timestamp: new Date(),
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
/** MOUSE DEBUG */
|
||
|
/*
|
||
|
mouse.listen.window.right.onclick.on(() =>
|
||
|
console.debug('mouse.listen.window.right.onclick')
|
||
|
);
|
||
|
|
||
|
mouse.listen.window.right.ondclick.on(() =>
|
||
|
console.debug('mouse.listen.window.right.ondclick')
|
||
|
);
|
||
|
mouse.listen.window.right.ondragstart.on(() =>
|
||
|
console.debug('mouse.listen.window.right.ondragstart')
|
||
|
);
|
||
|
mouse.listen.window.right.ondrag.on(() =>
|
||
|
console.debug('mouse.listen.window.right.ondrag')
|
||
|
);
|
||
|
mouse.listen.window.right.ondragend.on(() =>
|
||
|
console.debug('mouse.listen.window.right.ondragend')
|
||
|
);
|
||
|
|
||
|
mouse.listen.window.right.onpaintstart.on(() =>
|
||
|
console.debug('mouse.listen.window.right.onpaintstart')
|
||
|
);
|
||
|
mouse.listen.window.right.onpaint.on(() =>
|
||
|
console.debug('mouse.listen.window.right.onpaint')
|
||
|
);
|
||
|
mouse.listen.window.right.onpaintend.on(() =>
|
||
|
console.debug('mouse.listen.window.right.onpaintend')
|
||
|
);
|
||
|
*/
|