1/** 2 * Per-tool stroke-modifier sliders (Opacity / Flow / Softness) for 3 * Eraser, Brush, and Clone. The three sections share identical UX: 4 * 5 * - Opacity slider: writes to state, updates label, fades the 6 * preview swatch opacity. 7 * - Flow slider: writes to state, updates label, fades the swatch 8 * opacity AND swaps its border style (dashed at low flow â dotted 9 * at high flow) so the user sees the "denseness" change. 10 * - Softness slider: writes to state, updates label, tweens the 11 * radial-gradient inner stop on the swatch so it visually fades 12 * from hard disk to soft falloff. 13 * 14 * The whole block was three near-identical 30-LOC copies before; now 15 * it's one helper that takes the tool's prefix + a state-field bag. 16 * 17 * Usage: just call wireStrokeToolSliders() â the DOM IDs are wired 18 * statically from #ge-{eraser,brush,clone}-{opacity,flow,softness} 19 * + their labels + preview swatches. 20 */ 21import { state } from './state.js'; 22 23/** Wire the three sliders for one stroke tool. */ 24function wireToolSliders(prefix, fields) { 25 const opPrev = document.getElementById(`ge-${prefix}-preview-opacity`); 26 const flPrev = document.getElementById(`ge-${prefix}-preview-flow`); 27 const softPrev = document.getElementById(`ge-${prefix}-preview-softness`); 28 29 document.getElementById(`ge-${prefix}-opacity`)?.addEventListener('input', (e) => { 30 state[fields.opacity] = parseInt(e.target.value); 31 document.getElementById(`ge-${prefix}-opacity-label`).textContent = state[fields.opacity] + '%'; 32 if (opPrev) opPrev.style.opacity = (state[fields.opacity] / 100).toFixed(2); 33 }); 34 35 document.getElementById(`ge-${prefix}-flow`)?.addEventListener('input', (e) => { 36 state[fields.flow] = parseInt(e.target.value); 37 document.getElementById(`ge-${prefix}-flow-label`).textContent = state[fields.flow] + '%'; 38 // Lower flow â fewer / sparser dots. Cycle dot densities by 39 // swapping the dashed/dotted border style and fading opacity. 40 if (flPrev) { 41 const denseness = Math.max(1, Math.round(state[fields.flow] / 20)); 42 flPrev.style.borderStyle = denseness <= 2 ? 'dashed' : 'dotted'; 43 flPrev.style.opacity = (0.3 + (state[fields.flow] / 100) * 0.6).toFixed(2); 44 } 45 }); 46 47 document.getElementById(`ge-${prefix}-softness`)?.addEventListener('input', (e) => { 48 state[fields.softness] = parseInt(e.target.value); 49 document.getElementById(`ge-${prefix}-softness-label`).textContent = state[fields.softness] + '%'; 50 // Preview tweens from a hard disk into a soft radial gradient as 51 // softness rises (the CSS already sets the radial gradient â we 52 // just tween the inner solid radius to communicate the falloff). 53 if (softPrev) { 54 const innerStop = Math.max(0, 60 - state[fields.softness] * 0.55); 55 softPrev.style.background = `radial-gradient(circle, var(--fg) 0%, var(--fg) ${innerStop}%, transparent 90%)`; 56 } 57 }); 58} 59 60export function wireStrokeToolSliders() { 61 wireToolSliders('eraser', { opacity: 'eraserOpacity', flow: 'eraserFlow', softness: 'eraserSoftness' }); 62 wireToolSliders('brush', { opacity: 'brushOpacity', flow: 'brushFlow', softness: 'brushSoftness' }); 63 wireToolSliders('clone', { opacity: 'cloneOpacity', flow: 'cloneFlow', softness: 'cloneSoftness' }); 64}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.