diff --git a/index.html b/index.html index 43e743a..28e2ff7 100644 --- a/index.html +++ b/index.html @@ -295,6 +295,7 @@ people, person, humans, human, divers, diver, glitch, error, text, watermark, ba + diff --git a/js/input.js b/js/input.js index ba1ed32..684f777 100644 --- a/js/input.js +++ b/js/input.js @@ -201,6 +201,8 @@ window.onmouseup = (evn) => { 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, @@ -213,6 +215,8 @@ window.onmouseup = (evn) => { 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, @@ -264,6 +268,8 @@ window.onmousemove = (evn) => { 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, @@ -283,6 +289,8 @@ window.onmousemove = (evn) => { 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, @@ -297,6 +305,8 @@ window.onmousemove = (evn) => { 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, @@ -384,7 +394,9 @@ const keyboard = { }, deleteShortcut(id) { this.shortcuts.keys().forEach((key) => { - this.shortcuts[key] = this.shortcuts[key].filter((v) => v.id !== id); + this.shortcuts[key] = this.shortcuts[key].filter( + (v) => v.id !== id && v.callback !== id + ); }); }, diff --git a/js/settingsbar.js b/js/settingsbar.js index 6258425..e327bda 100644 --- a/js/settingsbar.js +++ b/js/settingsbar.js @@ -107,6 +107,7 @@ function createSlider(name, wrapper, options = {}) { textEl.value = `${name}: ${value}`; }); textEl.addEventListener("focus", () => { + overEl.style.pointerEvents = "none"; textEl.value = value; }); @@ -125,7 +126,6 @@ function createSlider(name, wrapper, options = {}) { mouse.listen.window.left.onclick.on((evn) => { if (evn.target === overEl) { - overEl.style.pointerEvents = "none"; textEl.select(); } }); diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index b3bbef2..028008b 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -212,3 +212,179 @@ const dream_img2img_callback = (evn, state) => { dream(bb.x, bb.y, request, {method: "img2img", stopMarching, bb}); } }; + +/** + * Registers Tools + */ +const dreamTool = () => + toolbar.registerTool( + "res/icons/image-plus.svg", + "Dream", + (state, opt) => { + // Draw new cursor immediately + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + state.mousemovecb({ + ...mouse.coords.canvas.pos, + target: {id: "overlayCanvas"}, + }); + + // Start Listeners + mouse.listen.canvas.onmousemove.on(state.mousemovecb); + mouse.listen.canvas.left.onclick.on(state.dreamcb); + mouse.listen.canvas.right.onclick.on(state.erasecb); + }, + (state, opt) => { + // Clear Listeners + mouse.listen.canvas.onmousemove.clear(state.mousemovecb); + mouse.listen.canvas.left.onclick.clear(state.dreamcb); + mouse.listen.canvas.right.onclick.clear(state.erasecb); + }, + { + init: (state) => { + state.snapToGrid = true; + state.overMaskPx = 0; + state.mousemovecb = (evn) => _reticle_draw(evn, state.snapToGrid); + state.dreamcb = (evn) => { + dream_generate_callback(evn, state); + }; + state.erasecb = (evn) => dream_erase_callback(evn, state); + }, + populateContextMenu: (menu, state) => { + if (!state.ctxmenu) { + state.ctxmenu = {}; + state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( + state, + "snapToGrid", + "Snap To Grid" + ).label; + state.ctxmenu.overMaskPxLabel = _toolbar_input.slider( + state, + "overMaskPx", + "Overmask px", + 0, + 128, + 1 + ).slider; + } + + menu.appendChild(state.ctxmenu.snapToGridLabel); + menu.appendChild(document.createElement("br")); + menu.appendChild(state.ctxmenu.overMaskPxLabel); + }, + shortcut: "D", + } + ); + +const img2imgTool = () => + toolbar.registerTool( + "res/icons/image.svg", + "Img2Img", + (state, opt) => { + // Draw new cursor immediately + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + state.mousemovecb({ + ...mouse.coords.canvas.pos, + target: {id: "overlayCanvas"}, + }); + + // Start Listeners + mouse.listen.canvas.onmousemove.on(state.mousemovecb); + mouse.listen.canvas.left.onclick.on(state.dreamcb); + mouse.listen.canvas.right.onclick.on(state.erasecb); + }, + (state, opt) => { + // Clear Listeners + mouse.listen.canvas.onmousemove.clear(state.mousemovecb); + mouse.listen.canvas.left.onclick.clear(state.dreamcb); + mouse.listen.canvas.right.onclick.clear(state.erasecb); + }, + { + init: (state) => { + state.snapToGrid = true; + state.denoisingStrength = 0.7; + + state.borderMaskSize = 64; + + state.mousemovecb = (evn) => { + _reticle_draw(evn, state.snapToGrid); + const bb = getBoundingBox( + evn.x, + evn.y, + basePixelCount * scaleFactor, + basePixelCount * scaleFactor, + state.snapToGrid && basePixelCount + ); + + // For displaying border mask + const auxCanvas = document.createElement("canvas"); + auxCanvas.width = bb.w; + auxCanvas.height = bb.h; + const auxCtx = auxCanvas.getContext("2d"); + + if (state.borderMaskSize > 0) { + auxCtx.fillStyle = "#FF6A6A50"; + auxCtx.fillRect(0, 0, state.borderMaskSize, bb.h); + auxCtx.fillRect(0, 0, bb.w, state.borderMaskSize); + auxCtx.fillRect( + bb.w - state.borderMaskSize, + 0, + state.borderMaskSize, + bb.h + ); + auxCtx.fillRect( + 0, + bb.h - state.borderMaskSize, + bb.w, + state.borderMaskSize + ); + } + + const tmp = ovCtx.globalAlpha; + ovCtx.globalAlpha = 0.4; + ovCtx.drawImage(auxCanvas, bb.x, bb.y); + ovCtx.globalAlpha = tmp; + }; + state.dreamcb = (evn) => { + dream_img2img_callback(evn, state); + }; + state.erasecb = (evn) => dream_erase_callback(evn, state); + }, + populateContextMenu: (menu, state) => { + if (!state.ctxmenu) { + state.ctxmenu = {}; + // Snap To Grid Checkbox + state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( + state, + "snapToGrid", + "Snap To Grid" + ).label; + + // Denoising Strength Slider + state.ctxmenu.denoisingStrengthSlider = _toolbar_input.slider( + state, + "denoisingStrength", + "Denoising Strength", + 0, + 1, + 0.05 + ).slider; + + // Border Mask Size Slider + state.ctxmenu.borderMaskSlider = _toolbar_input.slider( + state, + "borderMaskSize", + "Border Mask Size", + 0, + 128, + 1 + ).slider; + } + + menu.appendChild(state.ctxmenu.snapToGridLabel); + menu.appendChild(document.createElement("br")); + menu.appendChild(state.ctxmenu.denoisingStrengthSlider); + menu.appendChild(state.ctxmenu.borderMaskSlider); + }, + shortcut: "I", + } + ); diff --git a/js/ui/tool/maskbrush.js b/js/ui/tool/maskbrush.js index bc3a3c5..384594c 100644 --- a/js/ui/tool/maskbrush.js +++ b/js/ui/tool/maskbrush.js @@ -1,4 +1,4 @@ -const mask_brush_draw_callback = (evn, state) => { +const _mask_brush_draw_callback = (evn, state) => { if (evn.initialTarget.id === "overlayCanvas") { maskPaintCtx.globalCompositeOperation = "source-over"; maskPaintCtx.strokeStyle = "#FF6A6A"; @@ -12,7 +12,7 @@ const mask_brush_draw_callback = (evn, state) => { } }; -const mask_brush_erase_callback = (evn, state) => { +const _mask_brush_erase_callback = (evn, state) => { if (evn.initialTarget.id === "overlayCanvas") { maskPaintCtx.globalCompositeOperation = "destination-out"; maskPaintCtx.strokeStyle = "#FFFFFFFF"; @@ -25,3 +25,85 @@ const mask_brush_erase_callback = (evn, state) => { maskPaintCtx.stroke(); } }; + +const maskBrushTool = () => + toolbar.registerTool( + "res/icons/paintbrush.svg", + "Mask Brush", + (state, opt) => { + // Draw new cursor immediately + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}}); + + // Start Listeners + mouse.listen.canvas.onmousemove.on(state.movecb); + mouse.listen.canvas.onwheel.on(state.wheelcb); + mouse.listen.canvas.left.onpaint.on(state.drawcb); + mouse.listen.canvas.right.onpaint.on(state.erasecb); + }, + (state, opt) => { + // Clear Listeners + mouse.listen.canvas.onmousemove.clear(state.movecb); + mouse.listen.canvas.onwheel.on(state.wheelcb); + mouse.listen.canvas.left.onpaint.clear(state.drawcb); + mouse.listen.canvas.right.onpaint.clear(state.erasecb); + }, + { + init: (state) => { + state.config = { + brushScrollSpeed: 1 / 4, + minBrushSize: 10, + maxBrushSize: 500, + }; + + state.brushSize = 64; + state.setBrushSize = (size) => { + state.brushSize = size; + state.ctxmenu.brushSizeRange.value = size; + state.ctxmenu.brushSizeText.value = size; + }; + + state.movecb = (evn) => { + if (evn.target.id === "overlayCanvas") { + // draw big translucent red blob cursor + ovCtx.beginPath(); + ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line??? + ovCtx.fillStyle = "#FF6A6A50"; + ovCtx.fill(); + } + }; + + state.wheelcb = (evn) => { + if (evn.target.id === "overlayCanvas") { + state.brushSize = state.setBrushSize( + state.brushSize - + Math.floor(state.config.brushScrollSpeed * evn.delta) + ); + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + state.movecb(evn); + } + }; + + state.drawcb = (evn) => _mask_brush_draw_callback(evn, state); + state.erasecb = (evn) => _mask_brush_erase_callback(evn, state); + }, + populateContextMenu: (menu, state) => { + if (!state.ctxmenu) { + state.ctxmenu = {}; + const brushSizeSlider = _toolbar_input.slider( + state, + "brushSize", + "Brush Size", + state.config.minBrushSize, + state.config.maxBrushSize, + 1 + ); + state.ctxmenu.brushSizeSlider = brushSizeSlider.slider; + state.setBrushSize = brushSizeSlider.setValue; + } + + menu.appendChild(state.ctxmenu.brushSizeSlider); + }, + shortcut: "M", + } + ); diff --git a/js/ui/tool/populate.js b/js/ui/tool/populate.js new file mode 100644 index 0000000..e69de29 diff --git a/js/ui/tool/select.js b/js/ui/tool/select.js new file mode 100644 index 0000000..df46a9b --- /dev/null +++ b/js/ui/tool/select.js @@ -0,0 +1,153 @@ +const selectTransformTool = () => + toolbar.registerTool( + "res/icons/box-select.svg", + "Select Image", + (state, opt) => { + // Draw new cursor immediately + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}}); + + mouse.listen.canvas.onmousemove.on(state.movecb); + mouse.listen.canvas.left.ondragstart.on(state.dragstartcb); + mouse.listen.canvas.left.ondragend.on(state.dragendcb); + + mouse.listen.canvas.right.onclick.on(state.cancelcb); + }, + (state, opt) => { + mouse.listen.canvas.onmousemove.clear(state.movecb); + mouse.listen.canvas.left.ondragstart.clear(state.dragstartcb); + mouse.listen.canvas.left.ondragend.clear(state.dragendcb); + + mouse.listen.canvas.right.onclick.clear(state.cancelcb); + }, + { + init: (state) => { + state.snapToGrid = true; + state.dragging = null; + + const selectionBB = (x1, y1, x2, y2) => { + return { + x: Math.min(x1, x2), + y: Math.min(y1, y2), + w: Math.abs(x1 - x2), + h: Math.abs(y1 - y2), + }; + }; + + state.movecb = (evn) => { + if (evn.target.id === "overlayCanvas") { + let x = evn.x; + let y = evn.y; + if (state.snapToGrid) { + x += snap(evn.x, true, 64); + y += snap(evn.y, true, 64); + } + + // Draw dragging box + if (state.dragging) { + ovCtx.setLineDash([2, 2]); + ovCtx.lineWidth = 1; + ovCtx.strokeStyle = "#FFF"; + + const ix = state.dragging.ix; + const iy = state.dragging.iy; + + const bb = selectionBB(ix, iy, x, y); + + ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); + ovCtx.setLineDash([]); + } + + // Draw selection box + if (state.selected) { + ovCtx.lineWidth = 1; + ovCtx.strokeStyle = "#FFF"; + + ovCtx.setLineDash([4, 2]); + ovCtx.strokeRect( + state.selected.x, + state.selected.y, + state.selected.w, + state.selected.h + ); + ovCtx.setLineDash([]); + } + + // Draw cuttent cursor location + ovCtx.lineWidth = 3; + ovCtx.strokeStyle = "#FFF"; + + ovCtx.beginPath(); + ovCtx.moveTo(x, y + 10); + ovCtx.lineTo(x, y - 10); + ovCtx.moveTo(x + 10, y); + ovCtx.lineTo(x - 10, y); + ovCtx.stroke(); + } + }; + + state.dragstartcb = (evn) => { + if (evn.target.id === "overlayCanvas") { + let ix = evn.ix; + let iy = evn.iy; + if (state.snapToGrid) { + ix += snap(evn.ix, true, 64); + iy += snap(evn.iy, true, 64); + } + + state.dragging = {ix, iy}; + } + }; + + state.dragendcb = (evn) => { + if (evn.target.id === "overlayCanvas" && state.dragging) { + let x = evn.x; + let y = evn.y; + if (state.snapToGrid) { + x += snap(evn.x, true, 64); + y += snap(evn.y, true, 64); + } + + state.selected = selectionBB( + state.dragging.ix, + state.dragging.iy, + x, + y + ); + state.dragging = null; + } + }; + + state.cancelcb = (evn) => { + if (evn.target.id === "overlayCanvas") { + if (state.dragging) state.dragging = null; + else state.selected = null; + + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + state.movecb(evn); + } + }; + + // Keyboard callbacks + state.keydowncb = (evn) => { + console.debug(evn); + }; + + state.keyclickcb = (evn) => { + console.debug(evn); + }; + }, + populateContextMenu: (menu, state) => { + if (!state.ctxmenu) { + state.ctxmenu = {}; + // Snap To Grid Checkbox + state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( + state, + "snapToGrid", + "Snap To Grid" + ).label; + } + menu.appendChild(state.ctxmenu.snapToGridLabel); + }, + } + ); diff --git a/js/ui/toolbar.js b/js/ui/toolbar.js index 3296afa..b290606 100644 --- a/js/ui/toolbar.js +++ b/js/ui/toolbar.js @@ -134,15 +134,7 @@ const _toolbar_input = { return {checkbox, label}; }, - slider: ( - state, - dataKey, - text, - min = 0, - max = 1, - step = 0.1, - defaultValue = 0.3 - ) => { + slider: (state, dataKey, text, min = 0, max = 1, step = 0.1) => { const slider = document.createElement("div"); const value = createSlider(text, slider, { @@ -152,7 +144,7 @@ const _toolbar_input = { valuecb: (v) => { state[dataKey] = v; }, - defaultValue, + defaultValue: state[dataKey], }); return { @@ -179,6 +171,7 @@ const _reticle_draw = (evn, snapToGrid = true) => { ); // draw targeting square reticle thingy cursor + ovCtx.lineWidth = 1; ovCtx.strokeStyle = "#FFF"; ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame } @@ -189,179 +182,8 @@ const tools = {}; /** * Dream tool */ -tools.dream = toolbar.registerTool( - "res/icons/image-plus.svg", - "Dream", - (state, opt) => { - // Draw new cursor immediately - ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); - state.mousemovecb({ - ...mouse.coords.canvas.pos, - target: {id: "overlayCanvas"}, - }); - - // Start Listeners - mouse.listen.canvas.onmousemove.on(state.mousemovecb); - mouse.listen.canvas.left.onclick.on(state.dreamcb); - mouse.listen.canvas.right.onclick.on(state.erasecb); - }, - (state, opt) => { - // Clear Listeners - mouse.listen.canvas.onmousemove.clear(state.mousemovecb); - mouse.listen.canvas.left.onclick.clear(state.dreamcb); - mouse.listen.canvas.right.onclick.clear(state.erasecb); - }, - { - init: (state) => { - state.snapToGrid = true; - state.overMaskPx = 0; - state.mousemovecb = (evn) => _reticle_draw(evn, state.snapToGrid); - state.dreamcb = (evn) => { - dream_generate_callback(evn, state); - }; - state.erasecb = (evn) => dream_erase_callback(evn, state); - }, - populateContextMenu: (menu, state) => { - if (!state.ctxmenu) { - state.ctxmenu = {}; - state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( - state, - "snapToGrid", - "Snap To Grid" - ).label; - state.ctxmenu.overMaskPxLabel = _toolbar_input.slider( - state, - "overMaskPx", - "Overmask px", - 0, - 128, - 1, - 64 - ).slider; - } - - menu.appendChild(state.ctxmenu.snapToGridLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.overMaskPxLabel); - }, - shortcut: "D", - } -); - -tools.img2img = toolbar.registerTool( - "res/icons/image.svg", - "Img2Img", - (state, opt) => { - // Draw new cursor immediately - ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); - state.mousemovecb({ - ...mouse.coords.canvas.pos, - target: {id: "overlayCanvas"}, - }); - - // Start Listeners - mouse.listen.canvas.onmousemove.on(state.mousemovecb); - mouse.listen.canvas.left.onclick.on(state.dreamcb); - mouse.listen.canvas.right.onclick.on(state.erasecb); - }, - (state, opt) => { - // Clear Listeners - mouse.listen.canvas.onmousemove.clear(state.mousemovecb); - mouse.listen.canvas.left.onclick.clear(state.dreamcb); - mouse.listen.canvas.right.onclick.clear(state.erasecb); - }, - { - init: (state) => { - state.snapToGrid = true; - state.denoisingStrength = 0.7; - - state.borderMaskSize = 64; - - state.mousemovecb = (evn) => { - _reticle_draw(evn, state.snapToGrid); - const bb = getBoundingBox( - evn.x, - evn.y, - basePixelCount * scaleFactor, - basePixelCount * scaleFactor, - state.snapToGrid && basePixelCount - ); - - // For displaying border mask - const auxCanvas = document.createElement("canvas"); - auxCanvas.width = bb.w; - auxCanvas.height = bb.h; - const auxCtx = auxCanvas.getContext("2d"); - - if (state.borderMaskSize > 0) { - auxCtx.fillStyle = "#FF6A6A50"; - auxCtx.fillRect(0, 0, state.borderMaskSize, bb.h); - auxCtx.fillRect(0, 0, bb.w, state.borderMaskSize); - auxCtx.fillRect( - bb.w - state.borderMaskSize, - 0, - state.borderMaskSize, - bb.h - ); - auxCtx.fillRect( - 0, - bb.h - state.borderMaskSize, - bb.w, - state.borderMaskSize - ); - } - - const tmp = ovCtx.globalAlpha; - ovCtx.globalAlpha = 0.4; - ovCtx.drawImage(auxCanvas, bb.x, bb.y); - ovCtx.globalAlpha = tmp; - }; - state.dreamcb = (evn) => { - dream_img2img_callback(evn, state); - }; - state.erasecb = (evn) => dream_erase_callback(evn, state); - }, - populateContextMenu: (menu, state) => { - if (!state.ctxmenu) { - state.ctxmenu = {}; - // Snap To Grid Checkbox - state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( - state, - "snapToGrid", - "Snap To Grid" - ).label; - - // Denoising Strength Slider - state.ctxmenu.denoisingStrengthSlider = _toolbar_input.slider( - state, - "denoisingStrength", - "Denoising Strength", - 0, - 1, - 0.05, - 0.7 - ).slider; - - // Border Mask Size Slider - state.ctxmenu.borderMaskSlider = _toolbar_input.slider( - state, - "borderMaskSize", - "Border Mask Size", - 0, - 128, - 1, - 64 - ).slider; - } - - menu.appendChild(state.ctxmenu.snapToGridLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.denoisingStrengthSlider); - menu.appendChild(state.ctxmenu.borderMaskSlider); - }, - shortcut: "I", - } -); +tools.dream = dreamTool(); +tools.img2img = img2imgTool(); /** * Mask Editing tools @@ -371,86 +193,13 @@ toolbar.addSeparator(); /** * Mask Brush tool */ -tools.maskbrush = toolbar.registerTool( - "res/icons/paintbrush.svg", - "Mask Brush", - (state, opt) => { - // Draw new cursor immediately - ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); - state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}}); +tools.maskbrush = maskBrushTool(); - // Start Listeners - mouse.listen.canvas.onmousemove.on(state.movecb); - mouse.listen.canvas.onwheel.on(state.wheelcb); - mouse.listen.canvas.left.onpaint.on(state.drawcb); - mouse.listen.canvas.right.onpaint.on(state.erasecb); - }, - (state, opt) => { - // Clear Listeners - mouse.listen.canvas.onmousemove.clear(state.movecb); - mouse.listen.canvas.onwheel.on(state.wheelcb); - mouse.listen.canvas.left.onpaint.clear(state.drawcb); - mouse.listen.canvas.right.onpaint.clear(state.erasecb); - }, - { - init: (state) => { - state.config = { - brushScrollSpeed: 1 / 4, - minBrushSize: 10, - maxBrushSize: 500, - }; +/** + * Image Editing tools + */ +toolbar.addSeparator(); - state.brushSize = 64; - state.setBrushSize = (size) => { - state.brushSize = size; - state.ctxmenu.brushSizeRange.value = size; - state.ctxmenu.brushSizeText.value = size; - }; - - state.movecb = (evn) => { - if (evn.target.id === "overlayCanvas") { - // draw big translucent red blob cursor - ovCtx.beginPath(); - ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line??? - ovCtx.fillStyle = "#FF6A6A50"; - ovCtx.fill(); - } - }; - - state.wheelcb = (evn) => { - if (evn.target.id === "overlayCanvas") { - state.brushSize = state.setBrushSize( - state.brushSize - - Math.floor(state.config.brushScrollSpeed * evn.delta) - ); - ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); - state.movecb(evn); - } - }; - - state.drawcb = (evn) => mask_brush_draw_callback(evn, state); - state.erasecb = (evn) => mask_brush_erase_callback(evn, state); - }, - populateContextMenu: (menu, state) => { - if (!state.ctxmenu) { - state.ctxmenu = {}; - const brushSizeSlider = _toolbar_input.slider( - state, - "brushSize", - "Brush Size", - state.config.minBrushSize, - state.config.maxBrushSize, - 1, - 64 - ); - state.ctxmenu.brushSizeSlider = brushSizeSlider.slider; - state.setBrushSize = brushSizeSlider.setValue; - } - - menu.appendChild(state.ctxmenu.brushSizeSlider); - }, - shortcut: "M", - } -); +tools.selecttransform = selectTransformTool(); toolbar.tools[0].enable(); diff --git a/js/util.js b/js/util.js index 0de261b..bb75916 100644 --- a/js/util.js +++ b/js/util.js @@ -64,7 +64,10 @@ function snap(i, scaled = true, gridSize = 64) { scaleOffset = gridSize / 2; } } - var snapOffset = (i % gridSize) - scaleOffset; + const modulus = i % gridSize; + var snapOffset = modulus - scaleOffset; + if (modulus > gridSize / 2) snapOffset = modulus - gridSize; + if (snapOffset == 0) { return snapOffset; } diff --git a/res/icons/box-select.svg b/res/icons/box-select.svg new file mode 100644 index 0000000..3d6affd --- /dev/null +++ b/res/icons/box-select.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file