Enhancement: Eyedropper (#948)

* cursor helper

* shortcuts

* Helper styling

* linter fix

* dasharray param fix
master
olekhshch 2024-01-30 08:14:17 +01:00 committed by GitHub
parent 9f77e9c63a
commit 77b7bd95b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 103 additions and 42 deletions

View File

@ -780,7 +780,7 @@ class EditorStartup {
cancelTool () { cancelTool () {
const mode = this.svgCanvas.getMode() const mode = this.svgCanvas.getMode()
// list of modes that are currently save to cancel // 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)) { if (modesToCancel.includes(mode)) {
this.leftPanel.clickSelect() this.leftPanel.clickSelect()
} }

View File

@ -31,37 +31,57 @@ export default {
const { ChangeElementCommand } = svgCanvas.history const { ChangeElementCommand } = svgCanvas.history
// svgdoc = S.svgroot.parentNode.ownerDocument, // svgdoc = S.svgroot.parentNode.ownerDocument,
const addToHistory = (cmd) => { svgCanvas.undoMgr.addCommandToHistory(cmd) } const addToHistory = (cmd) => { svgCanvas.undoMgr.addCommandToHistory(cmd) }
const currentStyle = { const currentStyle = {}
fillPaint: 'red',
fillOpacity: 1.0,
strokePaint: 'black',
strokeOpacity: 1.0,
strokeWidth: 5,
strokeDashArray: null,
opacity: 1.0,
strokeLinecap: 'butt',
strokeLinejoin: 'miter'
}
const { $id, $click } = svgCanvas 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 * @param {module:svgcanvas.SvgCanvas#event:ext_selectedChanged|module:svgcanvas.SvgCanvas#event:ext_elementChanged} opts
* @returns {void} * @returns {void}
*/ */
const getStyle = (opts) => { 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 let elem = null
if (!opts.multiselected && opts.elems[0] && if (!opts.multiselected && opts.elems[0] &&
!['svg', 'g', 'use'].includes(opts.elems[0].nodeName) !['svg', 'g', 'use'].includes(opts.elems[0].nodeName)
) { ) {
elem = opts.elems[0] elem = opts.elems[0]
tool.classList.remove('disabled')
// grab the current style // grab the current style
currentStyle.fillPaint = elem.getAttribute('fill') || 'black' currentStyle.fillPaint = elem.getAttribute('fill') || 'black'
currentStyle.fillOpacity = elem.getAttribute('fill-opacity') || 1.0 currentStyle.fillOpacity = elem.getAttribute('fill-opacity') || 1.0
@ -72,9 +92,6 @@ export default {
currentStyle.strokeLinecap = elem.getAttribute('stroke-linecap') currentStyle.strokeLinecap = elem.getAttribute('stroke-linecap')
currentStyle.strokeLinejoin = elem.getAttribute('stroke-linejoin') currentStyle.strokeLinejoin = elem.getAttribute('stroke-linejoin')
currentStyle.opacity = elem.getAttribute('opacity') || 1.0 currentStyle.opacity = elem.getAttribute('opacity') || 1.0
// disable eye-dropper tool
} else {
tool.classList.add('disabled')
} }
} }
@ -83,45 +100,89 @@ export default {
callback () { callback () {
// Add the button and its handler(s) // Add the button and its handler(s)
const title = `${name}:buttons.0.title` 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 = ` const buttonTemplate = `
<se-button id="tool_eyedropper" title="${title}" src="eye_dropper.svg" shortcut=${key}></se-button> <se-button id="tool_eyedropper" title="${title}" src="eye_dropper.svg" shortcut=${key}></se-button>
` `
svgCanvas.insertChildAtIndex($id('tools_left'), buttonTemplate, 12) svgCanvas.insertChildAtIndex($id('tools_left'), buttonTemplate, 12)
$click($id('tool_eyedropper'), () => { $click($id('tool_eyedropper'), () => {
if (this.leftPanel.updateLeftPanel('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 // if we have selected an element, grab its paint and enable the eye dropper button
selectedChanged: getStyle, selectedChanged: getStyle,
elementChanged: getStyle,
mouseDown (opts) { mouseDown (opts) {
const mode = svgCanvas.getMode() const mode = svgCanvas.getMode()
if (mode === 'eyedropper') { if (mode === name) {
const e = opts.event const e = opts.event
const { target } = e const { target } = e
if (!['svg', 'g', 'use'].includes(target.nodeName)) { if (!['svg', 'g', 'use'].includes(target.nodeName)) {
const changes = {} const changes = {}
const change = function (elem, attrname, newvalue) { // If some style is picked - applies it to the target, if no style - picks it from the target
changes[attrname] = elem.getAttribute(attrname) if (Object.keys(currentStyle).length > 0) {
elem.setAttribute(attrname, newvalue) 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))
} }
} }
} }