openOutpaint/js/input.js
Victor Seiji Hariki ca3533c9e4
some more fixes and things
fixed scrolling on basically everything but the canvas, prompt fields
now automatically expand when focused, prompt, host and negprompt now
are stored on localstorage. on hover prompt and negprompt fields now
show full text in tooltips

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
2022-11-25 20:27:44 -03:00

507 lines
12 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).
keyboardHoldTiming: 1000, // Timing window after which to consider holding a key (ms)
};
/**
* Mouse input processing
*/
// Base object generator functions
function _mouse_observers() {
return {
// Simple click handler
onclick: new Observer(),
// Double click handler (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(),
};
}
const mouse = {
contexts: [],
buttons: {},
coords: {},
listen: {},
// Register Context
registerContext: (name, onmove, options = {}) => {
// Options
defaultOpt(options, {
target: null,
buttons: {0: "left", 1: "middle", 2: "right"},
});
// Context information
const context = {
id: guid(),
name,
onmove,
target: options.target,
buttons: options.buttons,
};
// Coordinate information
mouse.coords[name] = {
dragging: {},
prev: {
x: 0,
y: 0,
},
pos: {
x: 0,
y: 0,
},
};
// Listeners
mouse.listen[name] = {
onwheel: new Observer(),
onmousemove: new Observer(),
};
// Button specific items
Object.keys(options.buttons).forEach((index) => {
const button = options.buttons[index];
mouse.coords[name].dragging[button] = null;
mouse.listen[name][button] = _mouse_observers();
});
// Add to context
context.coords = mouse.coords[name];
context.listen = mouse.listen[name];
// Add to list
mouse.contexts.push(context);
return context;
},
};
const _double_click_timeout = {};
const _drag_start_timeout = {};
window.onmousedown = (evn) => {
const time = performance.now();
if (_double_click_timeout[evn.button]) {
// ondclick event
mouse.contexts.forEach(({target, name, buttons}) => {
if ((!target || target === evn.target) && buttons[evn.button])
mouse.listen[name][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 {
// Start timer
_double_click_timeout[evn.button] = setTimeout(
() => delete _double_click_timeout[evn.button],
inputConfig.dClickTiming
);
}
// Set drag start timeout
_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].drag &&
key
) {
mouse.listen[name][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}) => {
const key = buttons[evn.button];
if ((!target || target === evn.target) && key) {
mouse.coords[name].dragging[key] = {};
mouse.coords[name].dragging[key].target = evn.target;
Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos);
// onpaintstart event
mouse.listen[name][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(),
});
}
});
};
window.onmouseup = (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,
};
// onclick event
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][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(),
});
// onpaintend event
mouse.listen[name][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(),
});
// ondragend event
if (mouse.coords[name].dragging[key].drag)
mouse.listen[name][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;
};
window.onmousemove = (evn) => {
mouse.contexts.forEach((context) => {
const target = context.target;
const name = context.name;
if (!target || target === evn.target) {
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];
// ondragstart event (2)
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][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;
}
}
// ondrag event
if (
mouse.coords[name].dragging[key] &&
mouse.coords[name].dragging[key].drag
)
mouse.listen[name][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(),
});
// onpaint event
if (mouse.coords[name].dragging[key]) {
mouse.listen[name][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(),
});
}
});
}
});
};
window.addEventListener(
"wheel",
(evn) => {
mouse.contexts.forEach(({name}) => {
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;
});
mouse.registerContext(
"canvas",
(evn, ctx) => {
ctx.coords.prev.x = ctx.coords.pos.x;
ctx.coords.prev.y = ctx.coords.pos.y;
ctx.coords.pos.x = evn.layerX;
ctx.coords.pos.y = evn.layerY;
},
document.getElementById("overlayCanvas")
);
/**
* Keyboard input processing
*/
// Base object generator functions
const keyboard = {
keys: {},
isPressed(code) {
return this.keys[key].pressed;
},
isHeld(code) {
return !!this;
},
shortcuts: {},
onShortcut(shortcut, callback) {
/**
* Adds a shortcut handler (shorcut must be in format: {ctrl?: bool, alt?: bool, shift?: bool, key: string (code)})
* key must be the "code" parameter from keydown event; A key is "KeyA" for example
*/
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,
});
},
deleteShortcut(id, key = null) {
if (key) {
this.shortcuts[key] = this.shortcuts[key].filter(
(v) => v.id !== id && v.callback !== id
);
return;
}
this.shortcuts.keys().forEach((key) => {
this.shortcuts[key] = this.shortcuts[key].filter(
(v) => v.id !== id && v.callback !== id
);
});
},
listen: {
onkeydown: new Observer(),
onkeyup: new Observer(),
onkeyholdstart: new Observer(),
onkeyholdend: new Observer(),
onkeyclick: new Observer(),
onshortcut: new Observer(),
},
};
window.onkeydown = (evn) => {
// onkeydown event
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;
// onkeyholdstart event
keyboard.listen.onkeyholdstart.emit({
target: evn.target,
code: evn.code,
key: evn.key,
evn,
});
}, inputConfig.keyboardHoldTiming),
};
// Process shortcuts if input target is not a text field
switch (evn.target.tagName.toLowerCase()) {
case "input":
case "textarea":
case "select":
case "button":
return; // If in an input field, do not process shortcuts
default:
// Do nothing
break;
}
const callbacks = keyboard.shortcuts[evn.code];
if (callbacks)
callbacks.forEach((callback) => {
if (
!!callback.ctrl === evn.ctrlKey &&
!!callback.alt === evn.altKey &&
!!callback.shift === evn.shiftKey
) {
// onshortcut event
keyboard.listen.onshortcut.emit({
target: evn.target,
code: evn.code,
key: evn.key,
id: callback.id,
evn,
});
callback.callback(evn);
}
});
};
window.onkeyup = (evn) => {
// onkeyup event
keyboard.listen.onkeyup.emit({
target: evn.target,
code: evn.code,
key: evn.key,
evn,
});
if (keyboard.keys[evn.code] && keyboard.keys[evn.code].held) {
// onkeyholdend event
keyboard.listen.onkeyholdend.emit({
target: evn.target,
code: evn.code,
key: evn.key,
evn,
});
} else {
// onkeyclick event
keyboard.listen.onkeyclick.emit({
target: evn.target,
code: evn.code,
key: evn.key,
evn,
});
}
keyboard.keys[evn.code] = {
pressed: false,
held: false,
};
};