make cursors less finicky
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
518e60f44a
commit
42967f0a9a
12 changed files with 152 additions and 59 deletions
|
@ -16,4 +16,7 @@ const global = {
|
|||
get connection() {
|
||||
return this._connection;
|
||||
},
|
||||
|
||||
// If there is a selected input
|
||||
hasActiveInput: false,
|
||||
};
|
||||
|
|
|
@ -188,6 +188,7 @@ function startup() {
|
|||
}
|
||||
|
||||
function setFixedHost(h, changePromptMessage) {
|
||||
console.info(`[index] Fixed host to '${h}'`);
|
||||
const hostInput = document.getElementById("host");
|
||||
host = h;
|
||||
hostInput.value = h;
|
||||
|
|
|
@ -242,9 +242,28 @@ mouse.registerContext(
|
|||
ctx.coords.pos.x = Math.round(layerCoords.x);
|
||||
ctx.coords.pos.y = Math.round(layerCoords.y);
|
||||
},
|
||||
{target: imageCollection.inputElement}
|
||||
{
|
||||
target: imageCollection.inputElement,
|
||||
validate: (evn) => {
|
||||
if (!global.hasActiveInput || evn.type === "mousemove") return true;
|
||||
return false;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Redraw on active input state change
|
||||
(() => {
|
||||
mouse.listen.window.onany.on((evn) => {
|
||||
const activeInput = DOM.hasActiveInput();
|
||||
if (global.hasActiveInput !== activeInput) {
|
||||
global.hasActiveInput = activeInput;
|
||||
toolbar.currentTool &&
|
||||
toolbar.currentTool.state.redraw &&
|
||||
toolbar.currentTool.state.redraw();
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
mouse.listen.window.onwheel.on((evn) => {
|
||||
if (evn.evn.ctrlKey) {
|
||||
evn.evn.preventDefault();
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
* An object for mouse event listeners
|
||||
*
|
||||
* @typedef MouseListenerContext
|
||||
* @property {Observer} onany A listener for any mouse events
|
||||
* @property {Observer} onmousemove A mouse move handler
|
||||
* @property {Observer} onwheel A mouse wheel handler
|
||||
* @property {Record<string, MouseListenerBtnContext>} btn Button handlers
|
||||
|
@ -67,6 +68,7 @@
|
|||
* @property {ContextMoveTransformer} onmove The coordinate transform callback
|
||||
* @property {(evn) => void} onany A function to be run on any event
|
||||
* @property {?HTMLElement} target The target
|
||||
* @property {(evn) => boolean} validate A function to be check if we will process an event
|
||||
* @property {MouseCoordContext} coords Coordinates object
|
||||
* @property {MouseListenerContext} listen Listeners object
|
||||
*/
|
||||
|
|
112
js/lib/input.js
112
js/lib/input.js
|
@ -63,16 +63,16 @@ const mouse = {
|
|||
* @param {ContextMoveTransformer} onmove The function to perform coordinate transform
|
||||
* @param {object} options Extra options
|
||||
* @param {HTMLElement} [options.target=null] Target filtering
|
||||
* @param {(evn: any) => boolean} [options.validate] Checks if we will process this event or not
|
||||
* @param {Record<number, string>} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping
|
||||
* @param {(evn) => void} [options.genericcb=null] Function that will be run for all events (useful for preventDefault)
|
||||
* @returns {MouseContext}
|
||||
*/
|
||||
registerContext: (name, onmove, options = {}) => {
|
||||
// Options
|
||||
defaultOpt(options, {
|
||||
target: null,
|
||||
validate: () => true,
|
||||
buttons: {0: "left", 1: "middle", 2: "right"},
|
||||
genericcb: null,
|
||||
});
|
||||
|
||||
// Context information
|
||||
|
@ -81,8 +81,8 @@ const mouse = {
|
|||
id: guid(),
|
||||
name,
|
||||
onmove,
|
||||
onany: options.genericcb,
|
||||
target: options.target,
|
||||
validate: options.validate,
|
||||
buttons: options.buttons,
|
||||
};
|
||||
|
||||
|
@ -102,12 +102,27 @@ const mouse = {
|
|||
};
|
||||
|
||||
// Listeners
|
||||
const onany = new Observer();
|
||||
|
||||
mouse.listen[name] = {
|
||||
onany,
|
||||
onwheel: new Observer(),
|
||||
onmousemove: new Observer(),
|
||||
btn: {},
|
||||
};
|
||||
|
||||
// Always process onany events first
|
||||
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
|
||||
);
|
||||
|
||||
// Button specific items
|
||||
Object.keys(options.buttons).forEach((index) => {
|
||||
const button = options.buttons[index];
|
||||
|
@ -115,6 +130,48 @@ const mouse = {
|
|||
mouse.listen[name].btn[button] = _mouse_observers(
|
||||
`mouse.listen[${name}].btn[${button}]`
|
||||
);
|
||||
|
||||
// Always process onany events first
|
||||
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
|
||||
);
|
||||
});
|
||||
|
||||
// Add to context
|
||||
|
@ -183,11 +240,13 @@ window.addEventListener(
|
|||
|
||||
mouse.buttons[evn.button] = time;
|
||||
|
||||
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
||||
mouse._contexts.forEach(({target, name, buttons, validate}) => {
|
||||
const key = buttons[evn.button];
|
||||
if ((!target || target === evn.target) && key) {
|
||||
onany && onany();
|
||||
|
||||
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);
|
||||
|
@ -214,14 +273,14 @@ window.addEventListener(
|
|||
(evn) => {
|
||||
const time = performance.now();
|
||||
|
||||
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
||||
mouse._contexts.forEach(({target, name, buttons, validate}) => {
|
||||
const key = buttons[evn.button];
|
||||
if (
|
||||
(!target || target === evn.target) &&
|
||||
key &&
|
||||
mouse.coords[name].dragging[key]
|
||||
mouse.coords[name].dragging[key] &&
|
||||
(!validate || validate(evn))
|
||||
) {
|
||||
onany && onany();
|
||||
const start = {
|
||||
x: mouse.coords[name].dragging[key].x,
|
||||
y: mouse.coords[name].dragging[key].y,
|
||||
|
@ -292,7 +351,10 @@ window.addEventListener(
|
|||
const target = context.target;
|
||||
const name = context.name;
|
||||
|
||||
if (!target || target === evn.target) {
|
||||
if (
|
||||
!target ||
|
||||
(target === evn.target && (!context.validate || context.validate(evn)))
|
||||
) {
|
||||
context.onmove(evn, context);
|
||||
|
||||
mouse.listen[name].onmousemove.emit({
|
||||
|
@ -378,19 +440,21 @@ window.addEventListener(
|
|||
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(),
|
||||
});
|
||||
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}
|
||||
|
|
|
@ -224,9 +224,6 @@ const layers = {
|
|||
// Input element (overlay element for input handling)
|
||||
const inputel = document.createElement("div");
|
||||
inputel.id = `collection-input-${id}`;
|
||||
inputel.addEventListener("mouseover", (evn) => {
|
||||
document.activeElement.blur();
|
||||
});
|
||||
inputel.classList.add("collection-input-overlay");
|
||||
element.appendChild(inputel);
|
||||
|
||||
|
|
|
@ -106,9 +106,9 @@ class Observer {
|
|||
* Sends a message to all observers
|
||||
*
|
||||
* @param {T} msg The message to send to the observers
|
||||
* @param {any} state The initial state
|
||||
*/
|
||||
async emit(msg) {
|
||||
const state = {};
|
||||
async emit(msg, state = {}) {
|
||||
const promises = [];
|
||||
for (const {handler, wait} of this._handlers) {
|
||||
const run = async () => {
|
||||
|
@ -128,6 +128,25 @@ class Observer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static DOM utility functions
|
||||
*/
|
||||
class DOM {
|
||||
static inputTags = new Set(["input", "textarea"]);
|
||||
|
||||
/**
|
||||
* Checks if there is an active input
|
||||
*
|
||||
* @returns Whether there is currently an active input
|
||||
*/
|
||||
static hasActiveInput() {
|
||||
return (
|
||||
document.activeElement &&
|
||||
this.inputTags.has(document.activeElement.tagName.toLowerCase())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a simple UID in the format xxxx-xxxx-...-xxxx, with x being [0-9a-f]
|
||||
*
|
||||
|
|
|
@ -313,11 +313,9 @@ const uil = {
|
|||
const layers = imageCollection._layers;
|
||||
|
||||
layers.reduceRight((_, layer) => {
|
||||
console.debug(layer.name, layer.category, layer.hidden);
|
||||
if (categories.has(layer.category) && !layer.hidden)
|
||||
ctx.drawImage(layer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
||||
});
|
||||
console.debug("END");
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
|
|
@ -1228,7 +1228,7 @@ const dreamTool = () =>
|
|||
y += snap(evn.y, 0, 64);
|
||||
}
|
||||
|
||||
state.erasePrevReticle = _tool._cursor_draw(x, y);
|
||||
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||
|
||||
if (state.selection.exists) {
|
||||
const bb = state.selection.bb;
|
||||
|
@ -1600,7 +1600,7 @@ const img2imgTool = () =>
|
|||
y += snap(evn.y, 0, 64);
|
||||
}
|
||||
|
||||
state.erasePrevReticle = _tool._cursor_draw(x, y);
|
||||
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||
|
||||
// Resolution
|
||||
let bb = null;
|
||||
|
|
|
@ -14,7 +14,7 @@ const _tool = {
|
|||
* @param {string} [style.genSizeTextStyle = "#FFF5"] Style of the text for diplaying generation size
|
||||
* @param {string} [style.toolTextStyle = "#FFF5"] Style of the text for the tool name
|
||||
* @param {number} [style.reticleWidth = 1] Width of the line of the reticle
|
||||
* @param {string} [style.reticleStyle = "#FFF"] Style of the line of the reticle
|
||||
* @param {string} [style.reticleStyle] Style of the line of the reticle
|
||||
*
|
||||
* @returns A function that erases this reticle drawing
|
||||
*/
|
||||
|
@ -24,7 +24,7 @@ const _tool = {
|
|||
genSizeTextStyle: "#FFF5",
|
||||
toolTextStyle: "#FFF5",
|
||||
reticleWidth: 1,
|
||||
reticleStyle: "#FFF",
|
||||
reticleStyle: global.hasActiveInput ? "#BBF" : "#FFF",
|
||||
});
|
||||
|
||||
const bbvp = {
|
||||
|
@ -110,14 +110,14 @@ const _tool = {
|
|||
* @param {number} y Y world coordinate of the cursor
|
||||
* @param {object} style Style of the lines of the cursor
|
||||
* @param {string} [style.width = 3] Line width of the lines of the cursor
|
||||
* @param {string} [style.style = "#FFF5"] Stroke style of the lines of the cursor
|
||||
* @param {string} [style.style] Stroke style of the lines of the cursor
|
||||
*
|
||||
* @returns A function that erases this cursor drawing
|
||||
*/
|
||||
_cursor_draw(x, y, style = {}) {
|
||||
defaultOpt(style, {
|
||||
width: 3,
|
||||
style: "#FFF5",
|
||||
style: global.hasActiveInput ? "#BBF5" : "#FFF5",
|
||||
});
|
||||
const vpc = viewport.canvasToView(x, y);
|
||||
|
||||
|
|
|
@ -206,6 +206,7 @@ const selectTransformTool = () =>
|
|||
state.movecb = (evn) => {
|
||||
ovLayer.clear();
|
||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||
state.erasePrevCursor && state.erasePrevCursor();
|
||||
imageCollection.inputElement.style.cursor = "auto";
|
||||
state.lastMouseTarget = evn.target;
|
||||
state.lastMouseMove = evn;
|
||||
|
@ -355,15 +356,7 @@ const selectTransformTool = () =>
|
|||
}
|
||||
|
||||
// Draw current cursor location
|
||||
uiCtx.lineWidth = 3;
|
||||
uiCtx.strokeStyle = "#FFF";
|
||||
|
||||
uiCtx.beginPath();
|
||||
uiCtx.moveTo(vpc.x, vpc.y + 10);
|
||||
uiCtx.lineTo(vpc.x, vpc.y - 10);
|
||||
uiCtx.moveTo(vpc.x + 10, vpc.y);
|
||||
uiCtx.lineTo(vpc.x - 10, vpc.y);
|
||||
uiCtx.stroke();
|
||||
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||
|
||||
uiCtx.restore();
|
||||
};
|
||||
|
|
|
@ -151,18 +151,23 @@ const stampTool = () =>
|
|||
);
|
||||
const resourceWrapper = document.createElement("div");
|
||||
resourceWrapper.id = `resource-${resource.id}`;
|
||||
resourceWrapper.title = resource.name;
|
||||
resourceWrapper.classList.add("resource", "list-item");
|
||||
const resourceTitle = document.createElement("input");
|
||||
resourceTitle.value = resource.name;
|
||||
resourceTitle.title = resource.name;
|
||||
resourceTitle.style.pointerEvents = "none";
|
||||
resourceTitle.addEventListener("change", () => {
|
||||
resource.name = resourceTitle.value;
|
||||
resource.dirty = true;
|
||||
resourceTitle.title = resourceTitle.value;
|
||||
resourceWrapper.title = resourceTitle.value;
|
||||
|
||||
syncResources();
|
||||
});
|
||||
resourceTitle.addEventListener("keyup", function (event) {
|
||||
if (event.key === "Enter") {
|
||||
resourceTitle.blur();
|
||||
}
|
||||
});
|
||||
|
||||
resourceTitle.addEventListener("blur", () => {
|
||||
resourceTitle.style.pointerEvents = "none";
|
||||
|
@ -301,6 +306,7 @@ const stampTool = () =>
|
|||
|
||||
const vpc = viewport.canvasToView(x, y);
|
||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||
state.erasePrevCursor && state.erasePrevCursor();
|
||||
|
||||
uiCtx.save();
|
||||
|
||||
|
@ -314,16 +320,7 @@ const stampTool = () =>
|
|||
}
|
||||
|
||||
// Draw current cursor location
|
||||
uiCtx.lineWidth = 3;
|
||||
uiCtx.strokeStyle = "#FFF";
|
||||
|
||||
uiCtx.beginPath();
|
||||
uiCtx.moveTo(vpc.x, vpc.y + 10);
|
||||
uiCtx.lineTo(vpc.x, vpc.y - 10);
|
||||
uiCtx.moveTo(vpc.x + 10, vpc.y);
|
||||
uiCtx.lineTo(vpc.x - 10, vpc.y);
|
||||
uiCtx.stroke();
|
||||
|
||||
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||
uiCtx.restore();
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue