From 5e6b18503b4dd74a8150985724caa0c0d4c3dd64 Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Wed, 14 Dec 2022 15:12:44 -0300 Subject: [PATCH] Should mostly solve #94 Signed-off-by: Victor Seiji Hariki --- js/lib/util.js | 73 ++++++++++++++++++++++++------------- js/ui/tool/dream.js | 87 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 33 deletions(-) diff --git a/js/lib/util.js b/js/lib/util.js index ef90c6b..6a6e32c 100644 --- a/js/lib/util.js +++ b/js/lib/util.js @@ -2,15 +2,27 @@ * Some type definitions before the actual code */ /** - * Represents a simple bounding box - * - * @typedef BoundingBox - * @type {Object} - * @property {number} x - Leftmost coordinate of the box - * @property {number} y - Topmost coordinate of the box - * @property {number} w - The bounding box Width - * @property {number} h - The bounding box Height + * Represents a simple bouding box */ +class BoundingBox { + x = 0; + y = 0; + w = 0; + h = 0; + + constructor({x, y, w, h} = {x: 0, y: 0, w: 0, h: 0}) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + contains(x, y) { + return ( + this.x < x && this.y < y && x < this.x + this.w && y < this.y + this.h + ); + } +} /** * A simple implementation of the Observer programming pattern @@ -19,28 +31,34 @@ class Observer { /** * List of handlers - * @type {Set<(msg: T) => void | Promise>} + * @type {Array<{handler: (msg: T) => void | Promise, priority: number}>} */ - _handlers = new Set(); + _handlers = []; /** * Adds a observer to the events * - * @param {(msg: T) => void | Promise} callback The function to run when receiving a message - * @returns {(msg:T) => void | Promise} The callback we received + * @param {(msg: T, state?: any) => void | Promise} callback The function to run when receiving a message + * @param {number} priority The priority level of the observer + * @param {boolean} wait If the handler must be waited for before continuing + * @returns {(msg:T, state?: any) => void | Promise} The callback we received */ - on(callback) { - this._handlers.add(callback); + on(callback, priority = 0, wait = false) { + this._handlers.push({handler: callback, priority, wait}); + this._handlers.sort((a, b) => b.priority - a.priority); return callback; } /** * Removes a observer * - * @param {(msg: T) => void | Promise} callback The function used to register the callback + * @param {(msg: T, state?: any) => void | Promise} callback The function used to register the callback * @returns {boolean} Whether the handler existed */ clear(callback) { - return this._handlers.delete(callback); + const index = this._handlers.findIndex((v) => v.handler === callback); + if (index === -1) return false; + this._handlers.splice(index, 1); + return true; } /** * Sends a message to all observers @@ -48,16 +66,23 @@ class Observer { * @param {T} msg The message to send to the observers */ async emit(msg) { - return Promise.all( - Array.from(this._handlers).map(async (handler) => { + const state = {}; + const promises = []; + for (const {handler, wait} of this._handlers) { + const run = async () => { try { - await handler(msg); + await handler(msg, state); } catch (e) { console.warn("Observer failed to run handler"); console.warn(e); } - }) - ); + }; + + if (wait) await run(); + else promises.push(run()); + } + + return Promise.all(promises); } } @@ -211,12 +236,12 @@ function getBoundingBox(cx, cy, w, h, gridSnap = null, offset = 0) { box.x = Math.round(offs.x + cx); box.y = Math.round(offs.y + cy); - return { + return new BoundingBox({ x: Math.floor(box.x - w / 2), y: Math.floor(box.y - h / 2), w: Math.round(w), h: Math.round(h), - }; + }); } class NoContentError extends Error {} @@ -236,7 +261,7 @@ function cropCanvas(sourceCanvas, options = {}) { const h = sourceCanvas.height; var imageData = sourceCanvas.getContext("2d").getImageData(0, 0, w, h); /** @type {BoundingBox} */ - const bb = {x: 0, y: 0, w: 0, h: 0}; + const bb = new BoundingBox(); let minx = w; let maxx = -1; diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index 0b4f020..5e8cd2b 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -291,7 +291,8 @@ const _generate = async (endpoint, request, bb, options = {}) => { fetch(`${host}${url}interrupt`, {method: "POST"}); interruptButton.disabled = true; }); - const stopMarchingAnts = march(bb); + const marchingOptions = {}; + const stopMarchingAnts = march(bb, marchingOptions); // First Dream Run console.info(`[dream] Generating images for prompt '${request.prompt}'`); @@ -471,6 +472,62 @@ const _generate = async (endpoint, request, bb, options = {}) => { keyboard.listen.onkeyclick.on(onarrow); + // For handling mouse events for navigation + const onmovehandler = mouse.listen.world.onmousemove.on( + (evn, state) => { + const contains = bb.contains(evn.x, evn.y); + + if (!contains && !state.dream_processed) + imageCollection.inputElement.style.cursor = "auto"; + if (!contains || state.dream_processed) marchingOptions.style = "#FFF"; + + if (!state.dream_processed && contains) { + marchingOptions.style = "#F55"; + + imageCollection.inputElement.style.cursor = "pointer"; + + state.dream_processed = true; + } + }, + 0, + true + ); + + const onclickhandler = mouse.listen.world.btn.left.onclick.on( + (evn, state) => { + if (!state.dream_processed && bb.contains(evn.x, evn.y)) { + applyImg(); + imageCollection.inputElement.style.cursor = "auto"; + state.dream_processed = true; + } + }, + 1, + true + ); + const oncancelhandler = mouse.listen.world.btn.right.onclick.on( + (evn, state) => { + if (!state.dream_processed && bb.contains(evn.x, evn.y)) { + discardImg(); + imageCollection.inputElement.style.cursor = "auto"; + state.dream_processed = true; + } + }, + 1, + true + ); + const onwheelhandler = mouse.listen.world.onwheel.on( + (evn, state) => { + console.debug(evn, state); + if (!state.dream_processed && bb.contains(evn.x, evn.y)) { + if (evn.delta < 0) nextImg(); + else prevImg(); + state.dream_processed = true; + } + }, + 1, + true + ); + // Cleans up const clean = (removeBrushMask = false) => { if (removeBrushMask) { @@ -482,6 +539,12 @@ const _generate = async (endpoint, request, bb, options = {}) => { keyboard.listen.onkeyclick.clear(onarrow); // Remove area from no-generate list generationAreas.delete(areaid); + + // Stop handling inputs + mouse.listen.world.onmousemove.clear(onmovehandler); + mouse.listen.world.onwheel.clear(onwheelhandler); + mouse.listen.world.btn.left.onclick.clear(onclickhandler); + mouse.listen.world.btn.right.onclick.clear(oncancelhandler); }; redraw(); @@ -1097,7 +1160,7 @@ const dreamTool = () => }; if (state.selected) { - const bb = {x: 0, y: 0, w: 0, h: 0}; + const bb = new BoundingBox(); const minx = Math.min(state.selected.now.x, state.selected.start.x); const miny = Math.min(state.selected.now.y, state.selected.start.y); @@ -1167,10 +1230,12 @@ const dreamTool = () => state.mousemovecb(state.lastMouseMove); }; - state.wheelcb = (evn) => { + state.wheelcb = (evn, estate) => { + if (estate.dream_processed) return; _dream_onwheel(evn, state); }; - state.dreamcb = (evn) => { + state.dreamcb = (evn, estate) => { + if (estate.dream_processed) return; const bb = (state.selected && state.selected.bb) || getBoundingBox( @@ -1187,12 +1252,13 @@ const dreamTool = () => dream_generate_callback(bb, resolution, state); state.selected = null; }; - state.erasecb = (evn) => { + state.erasecb = (evn, estate) => { if (state.selected) { state.selected = null; state.redraw(); return; } + if (estate.dream_processed) return; const bb = getBoundingBox( evn.x, evn.y, @@ -1419,7 +1485,7 @@ const img2imgTool = () => let request = null; if (state.selected) { - bb = {x: 0, y: 0, w: 0, h: 0}; + bb = new BoundingBox(); const minx = Math.min(state.selected.now.x, state.selected.start.x); const miny = Math.min(state.selected.now.y, state.selected.start.y); @@ -1580,10 +1646,12 @@ const img2imgTool = () => state.mousemovecb(state.lastMouseMove); }; - state.wheelcb = (evn) => { + state.wheelcb = (evn, estate) => { + if (estate.dream_processed) return; _dream_onwheel(evn, state); }; - state.dreamcb = (evn) => { + state.dreamcb = (evn, estate) => { + if (estate.dream_processed) return; const bb = (state.selected && state.selected.bb) || getBoundingBox( @@ -1601,7 +1669,8 @@ const img2imgTool = () => state.selected = null; state.redraw(); }; - state.erasecb = (evn) => { + state.erasecb = (evn, estate) => { + if (estate.dream_processed) return; if (state.selected) { state.selected = null; state.redraw();