From 4549e21293e8fb2a4979899df86fd6a0ffbf829b Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Sat, 3 Dec 2022 20:00:10 -0300 Subject: [PATCH] fix chrome giant icon/add pipette tool (shift) Signed-off-by: Victor Seiji Hariki --- css/index.css | 2 + css/ui/tool/colorbrush.css | 42 +++++++++ index.html | 1 + js/initalize/layers.populate.js | 20 +++++ js/lib/input.js | 4 +- js/ui/tool/colorbrush.js | 145 ++++++++++++++++++++++++++++++-- res/icons/pipette.svg | 6 ++ 7 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 css/ui/tool/colorbrush.css create mode 100644 res/icons/pipette.svg diff --git a/css/index.css b/css/index.css index 9f6329a..acfb655 100644 --- a/css/index.css +++ b/css/index.css @@ -254,6 +254,8 @@ div.prompt-wrapper > textarea:focus { /* Style Field */ select > .style-select-option { + position: relative; + cursor: pointer; } diff --git a/css/ui/tool/colorbrush.css b/css/ui/tool/colorbrush.css new file mode 100644 index 0000000..ce7640e --- /dev/null +++ b/css/ui/tool/colorbrush.css @@ -0,0 +1,42 @@ +.brush-color-picker.wrapper { + position: relative; + + height: 32px; + + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: space-between; +} + +.brush-color-picker.picker { + cursor: pointer; + + flex: 1; + + height: 100%; + + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + + padding: 0; + border: 0; +} + +.brush-color-picker.eyedropper { + cursor: pointer; + + border-radius: 3px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + + height: 100%; + aspect-ratio: 1; + + border: 0; + + background-image: url("/res/icons/pipette.svg"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} diff --git a/index.html b/index.html index 317521b..c8efb5a 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,7 @@ + diff --git a/js/initalize/layers.populate.js b/js/initalize/layers.populate.js index 8c50a03..1f1a39b 100644 --- a/js/initalize/layers.populate.js +++ b/js/initalize/layers.populate.js @@ -37,6 +37,26 @@ const ovCtx = ovLayer.ctx; const debugCanvas = debugLayer.canvas; // where mouse cursor renders const debugCtx = debugLayer.ctx; +/** + * Function that returns a canvas with full visible information of a certain bounding box. + * + * For now, only the img is used. + * + * @param {BoundingBox} bb The bouding box to get visible data from + * @returns {HTMLCanvasElement} The canvas element containing visible image data + */ +const getVisible = (bb) => { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + canvas.width = bb.w; + canvas.height = bb.h; + ctx.drawImage(bgLayer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h); + ctx.drawImage(imgCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h); + + return canvas; +}; + debugLayer.hide(); // Hidden by default layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true}); diff --git a/js/lib/input.js b/js/lib/input.js index 4e625ee..b431047 100644 --- a/js/lib/input.js +++ b/js/lib/input.js @@ -421,7 +421,7 @@ const keyboard = { * @returns {boolean} */ isPressed(code) { - return this.keys[code].pressed; + return !!this.keys[code] && this.keys[code].pressed; }, /** @@ -431,7 +431,7 @@ const keyboard = { * @returns {boolean} */ isHeld(code) { - return this.keys[code].held; + return !!this.key[code] && this.keys[code].held; }, /** diff --git a/js/ui/tool/colorbrush.js b/js/ui/tool/colorbrush.js index 0d1fba0..27a7366 100644 --- a/js/ui/tool/colorbrush.js +++ b/js/ui/tool/colorbrush.js @@ -37,6 +37,17 @@ const colorBrushTool = () => // Draw new cursor immediately ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); state.movecb({...mouse.coords.world.pos}); + + // Layer for eyedropper magnifiying glass + state.glassLayer = imageCollection.registerLayer(null, { + bb: {x: 0, y: 0, w: 100, h: 100}, + resolution: {w: 7, h: 7}, + after: maskPaintLayer, + }); + state.glassLayer.canvas.style.display = "none"; + state.glassLayer.canvas.style.imageRendering = "pixelated"; + state.glassLayer.canvas.style.borderRadius = "50%"; + state.drawLayer = imageCollection.registerLayer(null, { after: imgLayer, }); @@ -53,6 +64,10 @@ const colorBrushTool = () => mouse.listen.world.onmousemove.on(state.movecb); mouse.listen.world.onwheel.on(state.wheelcb); + keyboard.listen.onkeydown.on(state.keydowncb); + keyboard.listen.onkeyup.on(state.keyupcb); + mouse.listen.world.btn.left.onclick.on(state.leftclickcb); + mouse.listen.world.btn.left.onpaintstart.on(state.drawstartcb); mouse.listen.world.btn.left.onpaint.on(state.drawcb); mouse.listen.world.btn.left.onpaintend.on(state.drawendcb); @@ -69,6 +84,10 @@ const colorBrushTool = () => mouse.listen.world.onmousemove.clear(state.movecb); mouse.listen.world.onwheel.clear(state.wheelcb); + keyboard.listen.onkeydown.clear(state.keydowncb); + keyboard.listen.onkeyup.clear(state.keyupcb); + mouse.listen.world.btn.left.onclick.clear(state.leftclickcb); + mouse.listen.world.btn.left.onpaintstart.clear(state.drawstartcb); mouse.listen.world.btn.left.onpaint.clear(state.drawcb); mouse.listen.world.btn.left.onpaintend.clear(state.drawendcb); @@ -81,6 +100,11 @@ const colorBrushTool = () => imageCollection.deleteLayer(state.drawLayer); imageCollection.deleteLayer(state.eraseBackup); imageCollection.deleteLayer(state.eraseLayer); + imageCollection.deleteLayer(state.glassLayer); + + // Cancel any eyedropping + state.drawing = false; + state.disableDropper(); }, { init: (state) => { @@ -102,13 +126,46 @@ const colorBrushTool = () => state.ctxmenu.brushSizeText.value = size; }; + state.eyedropper = false; + + state.enableDropper = () => { + state.eyedropper = true; + state.movecb(lastMouseMoveEvn); + state.glassLayer.canvas.style.display = "block"; + }; + + state.disableDropper = () => { + state.eyedropper = false; + state.movecb(lastMouseMoveEvn); + state.glassLayer.canvas.style.display = "none"; + }; + + let lastMouseMoveEvn = {x: 0, y: 0}; + state.movecb = (evn) => { - // draw big translucent white blob cursor + lastMouseMoveEvn = evn; + + // draw drawing cursor ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); - 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 = state.color + "50"; - ovCtx.fill(); + + if (state.eyedropper) { + const bb = getBoundingBox(evn.x, evn.y, 7, 7, false); + + const canvas = getVisible(bb); + state.glassLayer.ctx.clearRect(0, 0, 7, 7); + state.glassLayer.ctx.drawImage(canvas, 0, 0); + state.glassLayer.moveTo(evn.x - 50, evn.y - 50); + + ovCtx.beginPath(); + ovCtx.arc(evn.x, evn.y, 50, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 7x on a line??? + ovCtx.strokeStyle = "black"; + ovCtx.stroke(); + } else { + 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 7x on a line??? + ovCtx.fillStyle = state.color + "50"; + ovCtx.fill(); + } }; state.wheelcb = (evn) => { @@ -122,17 +179,68 @@ const colorBrushTool = () => } }; + /** + * These are basically for eyedropper purposes + */ + + state.keydowncb = (evn) => { + if (lastMouseMoveEvn.target === imageCollection.inputElement) + switch (evn.code) { + case "ShiftLeft": + case "ShiftRight": + state.enableDropper(); + break; + } + }; + + state.keyupcb = (evn) => { + switch (evn.code) { + case "ShiftLeft": + if (!keyboard.isPressed("ShiftRight")) { + state.disableDropper(); + } + break; + case "ShiftRight": + if (!keyboard.isPressed("ShiftLeft")) { + state.disableDropper(); + } + break; + } + }; + + state.leftclickcb = (evn) => { + if (evn.target === imageCollection.inputElement && state.eyedropper) { + const bb = getBoundingBox(evn.x, evn.y, 1, 1, false); + const visibleCanvas = getVisible(bb); + const dat = visibleCanvas + .getContext("2d") + .getImageData(0, 0, 1, 1).data; + state.setColor( + "#" + ((dat[0] << 16) | (dat[1] << 8) | dat[2]).toString(16) + ); + } + }; + + /** + * Here we actually paint things + */ state.drawstartcb = (evn) => { + if (state.eyedropper) return; + state.drawing = true; if (state.affectMask) _mask_brush_draw_callback(evn, state); _color_brush_draw_callback(evn, state); }; state.drawcb = (evn) => { + if (state.eyedropper || !state.drawing) return; if (state.affectMask) _mask_brush_draw_callback(evn, state); _color_brush_draw_callback(evn, state); }; state.drawendcb = (evn) => { + if (!state.drawing) return; + state.drawing = false; + const canvas = state.drawLayer.canvas; const ctx = state.drawLayer.ctx; @@ -231,15 +339,38 @@ const colorBrushTool = () => state.ctxmenu.brushBlurSlider = brushBlurSlider.slider; // Brush color + const brushColorPickerWrapper = document.createElement("div"); + brushColorPickerWrapper.classList.add( + "brush-color-picker", + "wrapper" + ); + const brushColorPicker = document.createElement("input"); + brushColorPicker.classList.add("brush-color-picker", "picker"); brushColorPicker.type = "color"; - brushColorPicker.style.width = "100%"; brushColorPicker.value = state.color; brushColorPicker.addEventListener("input", (evn) => { state.color = evn.target.value; }); - state.ctxmenu.brushColorPicker = brushColorPicker; + state.setColor = (color) => { + brushColorPicker.value = color; + state.color = brushColorPicker.value; + }; + + const brushColorEyeDropper = document.createElement("button"); + brushColorEyeDropper.classList.add( + "brush-color-picker", + "eyedropper" + ); + brushColorEyeDropper.addEventListener("click", () => { + state.enableDropper(); + }); + + brushColorPickerWrapper.appendChild(brushColorPicker); + brushColorPickerWrapper.appendChild(brushColorEyeDropper); + + state.ctxmenu.brushColorPicker = brushColorPickerWrapper; } menu.appendChild(state.ctxmenu.affectMaskCheckbox); diff --git a/res/icons/pipette.svg b/res/icons/pipette.svg new file mode 100644 index 0000000..89d058a --- /dev/null +++ b/res/icons/pipette.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file