/** * Build the editor's left-side tool palette. * * Pure DOM construction — no module state. The big tool-switch logic * (cursor swap, control-section toggle, transform entry, inpaint * mask plumbing, etc.) stays in the caller and arrives here as the * `onSelectTool` callback. * * @param {{ * currentTool: string, * onSelectTool: (toolId: string, btn: HTMLButtonElement, toolbar: HTMLDivElement) => void, * onClearSelection: (which: 'lasso'|'wand') => void, * }} ctx * @returns {{ toolbar: HTMLDivElement, toolKeyMap: Record }} */ export function buildToolbar({ currentTool, onSelectTool, onClearSelection }) { const toolbar = document.createElement('div'); toolbar.className = 'ge-toolbar'; const tools = [ { id: 'move', label: 'Move', icon: '✥', key: 'V' }, { id: 'crop', label: 'Crop', icon: '✂', key: 'C' }, { id: 'transform', label: 'Transform', icon: '⤢', key: 'T' }, { sep: true }, { id: 'brush', label: 'Brush', icon: '', key: 'B' }, { id: 'eraser', label: 'Eraser', icon: '', key: 'E' }, { sep: true }, { id: 'clone', label: 'Clone', icon: '', key: 'K' }, { id: 'lasso', label: 'Lasso', icon: '⟡', key: 'L' }, { id: 'wand', label: 'Wand', icon: '', key: 'W' }, { sep: true }, { id: 'inpaint', label: 'Inpaint', ai: true, icon: '', key: 'M' }, { id: 'rembg', ai: true, label: 'Bg Remove', icon: '✄' }, { id: 'sharpen', ai: true, label: 'Sharpen', icon: '◈', key: 'S' }, ]; const toolKeyMap = {}; for (const t of tools) { if (t.sep) { const sep = document.createElement('div'); sep.className = 'ge-tool-sep'; sep.textContent = t.label; toolbar.appendChild(sep); continue; } if (t.key) toolKeyMap[t.key.toLowerCase()] = t.id; const btn = document.createElement('button'); btn.className = 'ge-tool-btn' + (t.id === currentTool ? ' active' : ''); btn.dataset.tool = t.id; btn.title = t.label + (t.key ? ` (${t.key})` : ''); // Heavy 4-point AI star marker for AI-backed tools — sits just to // the left of the icon so the user can spot AI vs local tools at a // glance now that the "AI Tools" separator is gone. const aiStar = t.ai ? '' : ''; btn.classList.toggle('is-ai', !!t.ai); // Selection-clear badge — rendered only for tools that can hold a // selection (lasso, wand). Inpaint masks are first-class sub-layers // now so they get their own delete-X in the layer panel. const clearBadge = (t.id === 'lasso' || t.id === 'wand') ? '' + '' + '' : ''; btn.innerHTML = `${aiStar}${t.icon}${t.label}${clearBadge}`; // Clear-badge click stops propagation so the tool itself doesn't // toggle; the actual clear is handled by the caller. btn.querySelector('.ge-tool-clear')?.addEventListener('click', (ev) => { ev.stopPropagation(); onClearSelection(ev.currentTarget.dataset.clearTool); }); btn.addEventListener('click', () => onSelectTool(t.id, btn, toolbar)); toolbar.appendChild(btn); } return { toolbar, toolKeyMap }; }