From 77b7bd95b7d649c0034af30fec6cc48ea347e8f6 Mon Sep 17 00:00:00 2001 From: olekhshch <96601180+olekhshch@users.noreply.github.com> Date: Tue, 30 Jan 2024 08:14:17 +0100 Subject: [PATCH] Enhancement: Eyedropper (#948) * cursor helper * shortcuts * Helper styling * linter fix * dasharray param fix --- src/editor/EditorStartup.js | 2 +- .../ext-eyedropper/ext-eyedropper.js | 143 +++++++++++++----- 2 files changed, 103 insertions(+), 42 deletions(-) diff --git a/src/editor/EditorStartup.js b/src/editor/EditorStartup.js index 8f3ccbae..30596911 100644 --- a/src/editor/EditorStartup.js +++ b/src/editor/EditorStartup.js @@ -780,7 +780,7 @@ class EditorStartup { cancelTool () { const mode = this.svgCanvas.getMode() // list of modes that are currently save to cancel - const modesToCancel = ['zoom', 'rect', 'square', 'circle', 'ellipse', 'line', 'text', 'star', 'polygon', 'eyedropper', 'shapelib', 'image'] + const modesToCancel = ['zoom', 'rect', 'square', 'circle', 'ellipse', 'line', 'text', 'star', 'polygon', 'shapelib', 'image'] if (modesToCancel.includes(mode)) { this.leftPanel.clickSelect() } diff --git a/src/editor/extensions/ext-eyedropper/ext-eyedropper.js b/src/editor/extensions/ext-eyedropper/ext-eyedropper.js index fd67e1cf..723eab82 100644 --- a/src/editor/extensions/ext-eyedropper/ext-eyedropper.js +++ b/src/editor/extensions/ext-eyedropper/ext-eyedropper.js @@ -31,37 +31,57 @@ export default { const { ChangeElementCommand } = svgCanvas.history // svgdoc = S.svgroot.parentNode.ownerDocument, const addToHistory = (cmd) => { svgCanvas.undoMgr.addCommandToHistory(cmd) } - const currentStyle = { - fillPaint: 'red', - fillOpacity: 1.0, - strokePaint: 'black', - strokeOpacity: 1.0, - strokeWidth: 5, - strokeDashArray: null, - opacity: 1.0, - strokeLinecap: 'butt', - strokeLinejoin: 'miter' - } + const currentStyle = {} const { $id, $click } = svgCanvas + // Helper to show what style is currectly picked + const helperCursor = document.createElement('div') + helperCursor.style.width = '14px' + helperCursor.style.height = '14px' + helperCursor.style.position = 'absolute' + svgEditor.workarea.appendChild(helperCursor) + + const styleHelper = () => { + const mode = svgCanvas.getMode() + + if (mode === name) { + helperCursor.style.display = 'block' + + const strokeWidthNum = Number(currentStyle.strokeWidth) + const borderStyle = currentStyle.strokeDashArray === 'none' || !currentStyle.strokeDashArray ? 'solid' : 'dotted' + + helperCursor.style.background = currentStyle.fillPaint ?? 'transparent' + helperCursor.style.opacity = currentStyle.opacity ?? 1 + helperCursor.style.border = (strokeWidthNum > 0 && currentStyle.strokePaint) ? `2px ${borderStyle} ${currentStyle.strokePaint}` : 'none' + } + } + + const resetCurrentStyle = () => { + const keys = Object.keys(currentStyle) + + keys.forEach(key => delete currentStyle[key]) + } + + const cancelHandler = () => { + if (Object.keys(currentStyle).length > 0) { + resetCurrentStyle() + styleHelper() + } else { + svgEditor.leftPanel.clickSelect() + } + } + /** * * @param {module:svgcanvas.SvgCanvas#event:ext_selectedChanged|module:svgcanvas.SvgCanvas#event:ext_elementChanged} opts * @returns {void} */ const getStyle = (opts) => { - // if we are in eyedropper mode, we don't want to disable the eye-dropper tool - const mode = svgCanvas.getMode() - if (mode === 'eyedropper') { return } - - const tool = $id('tool_eyedropper') - // enable-eye-dropper if one element is selected let elem = null if (!opts.multiselected && opts.elems[0] && !['svg', 'g', 'use'].includes(opts.elems[0].nodeName) ) { elem = opts.elems[0] - tool.classList.remove('disabled') // grab the current style currentStyle.fillPaint = elem.getAttribute('fill') || 'black' currentStyle.fillOpacity = elem.getAttribute('fill-opacity') || 1.0 @@ -72,9 +92,6 @@ export default { currentStyle.strokeLinecap = elem.getAttribute('stroke-linecap') currentStyle.strokeLinejoin = elem.getAttribute('stroke-linejoin') currentStyle.opacity = elem.getAttribute('opacity') || 1.0 - // disable eye-dropper tool - } else { - tool.classList.add('disabled') } } @@ -83,45 +100,89 @@ export default { callback () { // Add the button and its handler(s) const title = `${name}:buttons.0.title` - // #TODO: Come up with another shortcut (?) because 'I' is reserved for italic - const key = `${name}:buttons.0.key` + // const key = `${name}:buttons.0.key` + const key = 'ctrl+I' const buttonTemplate = ` ` svgCanvas.insertChildAtIndex($id('tools_left'), buttonTemplate, 12) $click($id('tool_eyedropper'), () => { if (this.leftPanel.updateLeftPanel('tool_eyedropper')) { - svgCanvas.setMode('eyedropper') + svgCanvas.setMode(name) + } + }) + + // enables helper, resets currently picked style if no element selected + document.addEventListener('modeChange', e => { + if (svgCanvas.getMode() === name) { + styleHelper() + } else { + helperCursor.style.display = 'none' + } + if (svgCanvas.getSelectedElements().length === 0) { + resetCurrentStyle() + } + }) + + // Positions helper + svgEditor.workarea.addEventListener('mousemove', (e) => { + const x = e.clientX + const y = e.clientY + + if (svgCanvas.getMode() === name) { + helperCursor.style.top = y + 'px' + helperCursor.style.left = x + 12 + 'px' + styleHelper() + } + }) + + svgEditor.workarea.addEventListener('mouseleave', e => { + helperCursor.style.display = 'none' + }) + + // Listens to Esc to reset currently picked style / set Select mode + document.addEventListener('keydown', e => { + if (e.key === 'Escape' && svgCanvas.getMode() === name) { + cancelHandler() } }) }, // if we have selected an element, grab its paint and enable the eye dropper button selectedChanged: getStyle, - elementChanged: getStyle, mouseDown (opts) { const mode = svgCanvas.getMode() - if (mode === 'eyedropper') { + if (mode === name) { const e = opts.event const { target } = e if (!['svg', 'g', 'use'].includes(target.nodeName)) { const changes = {} - const change = function (elem, attrname, newvalue) { - changes[attrname] = elem.getAttribute(attrname) - elem.setAttribute(attrname, newvalue) + // If some style is picked - applies it to the target, if no style - picks it from the target + if (Object.keys(currentStyle).length > 0) { + const change = function (elem, attrname, newvalue) { + changes[attrname] = elem.getAttribute(attrname) + elem.setAttribute(attrname, newvalue) + } + + if (currentStyle.fillPaint) { change(target, 'fill', currentStyle.fillPaint) } + if (currentStyle.fillOpacity) { change(target, 'fill-opacity', currentStyle.fillOpacity) } + if (currentStyle.strokePaint) { change(target, 'stroke', currentStyle.strokePaint) } + if (currentStyle.strokeOpacity) { change(target, 'stroke-opacity', currentStyle.strokeOpacity) } + if (currentStyle.strokeWidth) { change(target, 'stroke-width', currentStyle.strokeWidth) } + if (currentStyle.opacity) { change(target, 'opacity', currentStyle.opacity) } + if (currentStyle.strokeLinecap) { change(target, 'stroke-linecap', currentStyle.strokeLinecap) } + if (currentStyle.strokeLinejoin) { change(target, 'stroke-linejoin', currentStyle.strokeLinejoin) } + + if (currentStyle.strokeDashArray) { + change(target, 'stroke-dasharray', currentStyle.strokeDashArray) + } else { + target.removeAttribute('stroke-dasharray') + } + + addToHistory(new ChangeElementCommand(target, changes)) + } else { + getStyle({ elems: [target] }) } - - if (currentStyle.fillPaint) { change(target, 'fill', currentStyle.fillPaint) } - if (currentStyle.fillOpacity) { change(target, 'fill-opacity', currentStyle.fillOpacity) } - if (currentStyle.strokePaint) { change(target, 'stroke', currentStyle.strokePaint) } - if (currentStyle.strokeOpacity) { change(target, 'stroke-opacity', currentStyle.strokeOpacity) } - if (currentStyle.strokeWidth) { change(target, 'stroke-width', currentStyle.strokeWidth) } - if (currentStyle.strokeDashArray) { change(target, 'stroke-dasharray', currentStyle.strokeDashArray) } - if (currentStyle.opacity) { change(target, 'opacity', currentStyle.opacity) } - if (currentStyle.strokeLinecap) { change(target, 'stroke-linecap', currentStyle.strokeLinecap) } - if (currentStyle.strokeLinejoin) { change(target, 'stroke-linejoin', currentStyle.strokeLinejoin) } - - addToHistory(new ChangeElementCommand(target, changes)) } } }