diff --git a/js/global.js b/js/global.js index 2773fd7..415c377 100644 --- a/js/global.js +++ b/js/global.js @@ -16,4 +16,7 @@ const global = { get connection() { return this._connection; }, + + // If there is a selected input + hasActiveInput: false, }; diff --git a/js/index.js b/js/index.js index 466445e..4b30cbb 100644 --- a/js/index.js +++ b/js/index.js @@ -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; diff --git a/js/initalize/layers.populate.js b/js/initalize/layers.populate.js index 64b427b..c98ffba 100644 --- a/js/initalize/layers.populate.js +++ b/js/initalize/layers.populate.js @@ -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(); diff --git a/js/lib/input.d.js b/js/lib/input.d.js index 07ae2b3..e9caa06 100644 --- a/js/lib/input.d.js +++ b/js/lib/input.d.js @@ -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} 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 */ diff --git a/js/lib/input.js b/js/lib/input.js index b431047..4a469e3 100644 --- a/js/lib/input.js +++ b/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} [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} diff --git a/js/lib/layers.js b/js/lib/layers.js index 50fa47f..3c513cd 100644 --- a/js/lib/layers.js +++ b/js/lib/layers.js @@ -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); diff --git a/js/lib/util.js b/js/lib/util.js index f6d5c60..22b3c1c 100644 --- a/js/lib/util.js +++ b/js/lib/util.js @@ -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] * diff --git a/js/ui/floating/layers.js b/js/ui/floating/layers.js index 50a8165..6682453 100644 --- a/js/ui/floating/layers.js +++ b/js/ui/floating/layers.js @@ -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; }, diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index 41150af..094838e 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -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; diff --git a/js/ui/tool/generic.js b/js/ui/tool/generic.js index 6de0388..56e3a6b 100644 --- a/js/ui/tool/generic.js +++ b/js/ui/tool/generic.js @@ -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); diff --git a/js/ui/tool/select.js b/js/ui/tool/select.js index c598750..5fd2cd5 100644 --- a/js/ui/tool/select.js +++ b/js/ui/tool/select.js @@ -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(); }; diff --git a/js/ui/tool/stamp.js b/js/ui/tool/stamp.js index 5aa1811..2699841 100644 --- a/js/ui/tool/stamp.js +++ b/js/ui/tool/stamp.js @@ -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(); };