|
const inputConfig = { |
|
clickRadius: 10, |
|
clickTiming: 500, |
|
dClickTiming: 500, |
|
|
|
keyboardHoldTiming: 1000, |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function _mouse_observers(name = "generic_mouse_observer_array") { |
|
return makeReadOnly( |
|
{ |
|
|
|
onclick: new Observer(), |
|
|
|
ondclick: new Observer(), |
|
|
|
ondragstart: new Observer(), |
|
ondrag: new Observer(), |
|
ondragend: new Observer(), |
|
|
|
onpaintstart: new Observer(), |
|
onpaint: new Observer(), |
|
onpaintend: new Observer(), |
|
}, |
|
name |
|
); |
|
} |
|
|
|
|
|
const mouse = { |
|
|
|
|
|
|
|
|
|
_contexts: [], |
|
|
|
|
|
|
|
|
|
buttons: {}, |
|
|
|
|
|
|
|
|
|
coords: makeWriteOnce({}, "mouse.coords"), |
|
|
|
|
|
|
|
|
|
|
|
listen: makeWriteOnce({}, "mouse.listen"), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
registerContext: (name, onmove, options = {}) => { |
|
|
|
defaultOpt(options, { |
|
target: null, |
|
validate: () => true, |
|
buttons: {0: "left", 1: "middle", 2: "right"}, |
|
}); |
|
|
|
|
|
|
|
const context = { |
|
id: guid(), |
|
name, |
|
onmove, |
|
target: options.target, |
|
validate: options.validate, |
|
buttons: options.buttons, |
|
}; |
|
|
|
|
|
mouse.coords[name] = { |
|
dragging: {}, |
|
|
|
prev: { |
|
x: 0, |
|
y: 0, |
|
}, |
|
|
|
pos: { |
|
x: 0, |
|
y: 0, |
|
}, |
|
}; |
|
|
|
|
|
const onany = new Observer(); |
|
|
|
mouse.listen[name] = { |
|
onany, |
|
onwheel: new Observer(), |
|
onmousemove: new Observer(), |
|
btn: {}, |
|
}; |
|
|
|
|
|
mouse.listen[name].onwheel.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].onmousemove.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
|
|
|
|
Object.keys(options.buttons).forEach((index) => { |
|
const button = options.buttons[index]; |
|
mouse.coords[name].dragging[button] = null; |
|
mouse.listen[name].btn[button] = _mouse_observers( |
|
`mouse.listen[${name}].btn[${button}]` |
|
); |
|
|
|
|
|
mouse.listen[name].btn[button].onclick.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].btn[button].ondclick.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].btn[button].ondragstart.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].btn[button].ondrag.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].btn[button].ondragend.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].btn[button].onpaintstart.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].btn[button].onpaint.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
mouse.listen[name].btn[button].onpaintend.on( |
|
async (evn, state) => await onany.emit(evn, state), |
|
Infinity, |
|
true |
|
); |
|
}); |
|
|
|
|
|
context.coords = mouse.coords[name]; |
|
context.listen = mouse.listen[name]; |
|
|
|
|
|
mouse._contexts.push(context); |
|
|
|
return context; |
|
}, |
|
}; |
|
|
|
const _double_click_timeout = {}; |
|
const _drag_start_timeout = {}; |
|
|
|
window.addEventListener( |
|
"mousedown", |
|
(evn) => { |
|
const time = performance.now(); |
|
|
|
if (_double_click_timeout[evn.button]) { |
|
|
|
mouse._contexts.forEach(({target, name, buttons}) => { |
|
if ((!target || target === evn.target) && buttons[evn.button]) |
|
mouse.listen[name].btn[buttons[evn.button]].ondclick.emit({ |
|
target: evn.target, |
|
buttonId: evn.button, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: time, |
|
}); |
|
}); |
|
} else { |
|
|
|
_double_click_timeout[evn.button] = setTimeout( |
|
() => delete _double_click_timeout[evn.button], |
|
inputConfig.dClickTiming |
|
); |
|
} |
|
|
|
|
|
_drag_start_timeout[evn.button] = setTimeout(() => { |
|
mouse._contexts.forEach(({target, name, buttons}) => { |
|
const key = buttons[evn.button]; |
|
if ( |
|
(!target || target === evn.target) && |
|
mouse.coords[name].dragging[key] && |
|
!mouse.coords[name].dragging[key].drag && |
|
key |
|
) { |
|
mouse.listen[name].btn[key].ondragstart.emit({ |
|
target: evn.target, |
|
buttonId: evn.button, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: time, |
|
}); |
|
|
|
mouse.coords[name].dragging[key].drag = true; |
|
} |
|
}); |
|
delete _drag_start_timeout[evn.button]; |
|
}, inputConfig.clickTiming); |
|
|
|
mouse.buttons[evn.button] = time; |
|
|
|
mouse._contexts.forEach(({target, name, buttons, validate}) => { |
|
const key = buttons[evn.button]; |
|
if ( |
|
(!target || target === evn.target) && |
|
key && |
|
(!validate || validate(evn)) |
|
) { |
|
mouse.coords[name].dragging[key] = {}; |
|
mouse.coords[name].dragging[key].target = evn.target; |
|
Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos); |
|
|
|
|
|
mouse.listen[name].btn[key].onpaintstart.emit({ |
|
target: evn.target, |
|
buttonId: evn.button, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
} |
|
}); |
|
}, |
|
{ |
|
passive: false, |
|
} |
|
); |
|
|
|
window.addEventListener( |
|
"mouseup", |
|
(evn) => { |
|
const time = performance.now(); |
|
|
|
mouse._contexts.forEach(({target, name, buttons}) => { |
|
const key = buttons[evn.button]; |
|
if ( |
|
(!target || target === evn.target) && |
|
key && |
|
mouse.coords[name].dragging[key] |
|
) { |
|
const start = { |
|
x: mouse.coords[name].dragging[key].x, |
|
y: mouse.coords[name].dragging[key].y, |
|
}; |
|
|
|
|
|
const dx = mouse.coords[name].pos.x - start.x; |
|
const dy = mouse.coords[name].pos.y - start.y; |
|
|
|
if ( |
|
mouse.buttons[evn.button] && |
|
time - mouse.buttons[evn.button] < inputConfig.clickTiming && |
|
dx * dx + dy * dy < inputConfig.clickRadius * inputConfig.clickRadius |
|
) |
|
mouse.listen[name].btn[key].onclick.emit({ |
|
target: evn.target, |
|
buttonId: evn.button, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
|
|
|
|
mouse.listen[name].btn[key].onpaintend.emit({ |
|
target: evn.target, |
|
initialTarget: mouse.coords[name].dragging[key].target, |
|
buttonId: evn.button, |
|
ix: mouse.coords[name].dragging[key].x, |
|
iy: mouse.coords[name].dragging[key].y, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
|
|
|
|
if (mouse.coords[name].dragging[key].drag) |
|
mouse.listen[name].btn[key].ondragend.emit({ |
|
target: evn.target, |
|
initialTarget: mouse.coords[name].dragging[key].target, |
|
buttonId: evn.button, |
|
ix: mouse.coords[name].dragging[key].x, |
|
iy: mouse.coords[name].dragging[key].y, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
|
|
mouse.coords[name].dragging[key] = null; |
|
} |
|
}); |
|
|
|
if (_drag_start_timeout[evn.button] !== undefined) { |
|
clearTimeout(_drag_start_timeout[evn.button]); |
|
delete _drag_start_timeout[evn.button]; |
|
} |
|
mouse.buttons[evn.button] = null; |
|
}, |
|
{passive: false} |
|
); |
|
|
|
window.addEventListener( |
|
"mousemove", |
|
(evn) => { |
|
mouse._contexts.forEach(async (context) => { |
|
const target = context.target; |
|
const name = context.name; |
|
|
|
if ( |
|
!target || |
|
(target === evn.target && (!context.validate || context.validate(evn))) |
|
) { |
|
context.onmove(evn, context); |
|
|
|
mouse.listen[name].onmousemove.emit({ |
|
target: evn.target, |
|
px: mouse.coords[name].prev.x, |
|
py: mouse.coords[name].prev.y, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
|
|
Object.keys(context.buttons).forEach((index) => { |
|
const key = context.buttons[index]; |
|
|
|
if (mouse.coords[name].dragging[key]) { |
|
const dx = |
|
mouse.coords[name].pos.x - mouse.coords[name].dragging[key].x; |
|
const dy = |
|
mouse.coords[name].pos.y - mouse.coords[name].dragging[key].y; |
|
if ( |
|
!mouse.coords[name].dragging[key].drag && |
|
dx * dx + dy * dy >= |
|
inputConfig.clickRadius * inputConfig.clickRadius |
|
) { |
|
mouse.listen[name].btn[key].ondragstart.emit({ |
|
target: evn.target, |
|
buttonId: evn.button, |
|
ix: mouse.coords[name].dragging[key].x, |
|
iy: mouse.coords[name].dragging[key].y, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
|
|
mouse.coords[name].dragging[key].drag = true; |
|
} |
|
} |
|
|
|
|
|
if ( |
|
mouse.coords[name].dragging[key] && |
|
mouse.coords[name].dragging[key].drag |
|
) |
|
mouse.listen[name].btn[key].ondrag.emit({ |
|
target: evn.target, |
|
initialTarget: mouse.coords[name].dragging[key].target, |
|
button: index, |
|
ix: mouse.coords[name].dragging[key].x, |
|
iy: mouse.coords[name].dragging[key].y, |
|
px: mouse.coords[name].prev.x, |
|
py: mouse.coords[name].prev.y, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
|
|
|
|
if (mouse.coords[name].dragging[key]) { |
|
mouse.listen[name].btn[key].onpaint.emit({ |
|
target: evn.target, |
|
initialTarget: mouse.coords[name].dragging[key].target, |
|
button: index, |
|
ix: mouse.coords[name].dragging[key].x, |
|
iy: mouse.coords[name].dragging[key].y, |
|
px: mouse.coords[name].prev.x, |
|
py: mouse.coords[name].prev.y, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
} |
|
}); |
|
} |
|
}); |
|
}, |
|
{passive: false} |
|
); |
|
|
|
window.addEventListener( |
|
"wheel", |
|
(evn) => { |
|
|
|
|
|
|
|
let _discard = evn.deltaY; |
|
_discard = evn.deltaMode; |
|
|
|
mouse._contexts.forEach(({name, target, validate}) => { |
|
if ((!target || target === evn.target) && (!validate || validate(evn))) { |
|
mouse.listen[name].onwheel.emit({ |
|
target: evn.target, |
|
delta: evn.deltaY, |
|
deltaX: evn.deltaX, |
|
deltaY: evn.deltaY, |
|
deltaZ: evn.deltaZ, |
|
mode: evn.deltaMode, |
|
x: mouse.coords[name].pos.x, |
|
y: mouse.coords[name].pos.y, |
|
evn, |
|
timestamp: performance.now(), |
|
}); |
|
} |
|
}); |
|
}, |
|
{passive: false} |
|
); |
|
|
|
mouse.registerContext("window", (evn, ctx) => { |
|
ctx.coords.prev.x = ctx.coords.pos.x; |
|
ctx.coords.prev.y = ctx.coords.pos.y; |
|
ctx.coords.pos.x = evn.clientX; |
|
ctx.coords.pos.y = evn.clientY; |
|
}); |
|
|
|
|
|
|
|
|
|
const keyboard = { |
|
|
|
|
|
|
|
|
|
|
|
keys: {}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isPressed(code) { |
|
return !!this.keys[code] && this.keys[code].pressed; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isHeld(code) { |
|
return !!this.key[code] && this.keys[code].held; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
shortcuts: {}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onShortcut(shortcut, callback) { |
|
|
|
|
|
|
|
|
|
if (this.shortcuts[shortcut.key] === undefined) |
|
this.shortcuts[shortcut.key] = []; |
|
|
|
this.shortcuts[shortcut.key].push({ |
|
ctrl: shortcut.ctrl, |
|
alt: shortcut.alt, |
|
shift: shortcut.shift, |
|
id: guid(), |
|
callback, |
|
}); |
|
|
|
return callback; |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
deleteShortcut(shortcut, key = null) { |
|
if (key) { |
|
this.shortcuts[key] = this.shortcuts[key].filter( |
|
(v) => v.id !== shortcut && v.callback !== shortcut |
|
); |
|
return; |
|
} |
|
this.shortcuts.keys().forEach((key) => { |
|
this.shortcuts[key] = this.shortcuts[key].filter( |
|
(v) => v.id !== shortcut && v.callback !== shortcut |
|
); |
|
}); |
|
}, |
|
|
|
listen: { |
|
onkeydown: new Observer(), |
|
onkeyup: new Observer(), |
|
onkeyholdstart: new Observer(), |
|
onkeyholdend: new Observer(), |
|
onkeyclick: new Observer(), |
|
onshortcut: new Observer(), |
|
}, |
|
}; |
|
|
|
window.onkeydown = (evn) => { |
|
|
|
keyboard.listen.onkeydown.emit({ |
|
target: evn.target, |
|
code: evn.code, |
|
key: evn.key, |
|
evn, |
|
}); |
|
|
|
keyboard.keys[evn.code] = { |
|
pressed: true, |
|
held: false, |
|
_hold_to: setTimeout(() => { |
|
keyboard.keys[evn.code].held = true; |
|
delete keyboard.keys[evn.code]._hold_to; |
|
|
|
keyboard.listen.onkeyholdstart.emit({ |
|
target: evn.target, |
|
code: evn.code, |
|
key: evn.key, |
|
evn, |
|
}); |
|
}, inputConfig.keyboardHoldTiming), |
|
}; |
|
|
|
|
|
switch (evn.target.tagName.toLowerCase()) { |
|
case "input": |
|
case "textarea": |
|
case "select": |
|
case "button": |
|
return; |
|
default: |
|
|
|
break; |
|
} |
|
|
|
const callbacks = keyboard.shortcuts[evn.code]; |
|
|
|
if (callbacks) |
|
callbacks.forEach((callback) => { |
|
let activate = true; |
|
|
|
if (callback.ctrl !== null && !!callback.ctrl !== evn.ctrlKey) |
|
activate = false; |
|
if (callback.shift !== null && !!callback.shift !== evn.shiftKey) |
|
activate = false; |
|
if (callback.alt !== null && !!callback.alt !== evn.altKey) |
|
activate = false; |
|
|
|
if (activate) { |
|
evn.preventDefault(); |
|
|
|
keyboard.listen.onshortcut.emit({ |
|
target: evn.target, |
|
code: evn.code, |
|
key: evn.key, |
|
id: callback.id, |
|
evn, |
|
}); |
|
callback.callback(evn); |
|
} |
|
}); |
|
}; |
|
|
|
window.onkeyup = (evn) => { |
|
|
|
keyboard.listen.onkeyup.emit({ |
|
target: evn.target, |
|
code: evn.code, |
|
key: evn.key, |
|
evn, |
|
}); |
|
if (keyboard.keys[evn.code] && keyboard.keys[evn.code].held) { |
|
|
|
keyboard.listen.onkeyholdend.emit({ |
|
target: evn.target, |
|
code: evn.code, |
|
key: evn.key, |
|
evn, |
|
}); |
|
} else { |
|
|
|
keyboard.listen.onkeyclick.emit({ |
|
target: evn.target, |
|
code: evn.code, |
|
key: evn.key, |
|
evn, |
|
}); |
|
} |
|
|
|
keyboard.keys[evn.code] = { |
|
pressed: false, |
|
held: false, |
|
}; |
|
}; |
|
|