diff --git a/celstomp/celstomp-app.js b/celstomp/celstomp-app.js index 3d924b6..a30930d 100644 --- a/celstomp/celstomp-app.js +++ b/celstomp/celstomp-app.js @@ -358,6 +358,7 @@ bctx.fillRect(0, 0, contentW, contentH); bctx.strokeRect(0, 0, contentW, contentH); drawRectSelectionOverlay(fxctx); + drawLineToolPreview(fxctx); } function onionCompositeOperation() { @@ -404,6 +405,9 @@ function clearFx() { fxctx.setTransform(1, 0, 0, 1, 0, 0); fxctx.clearRect(0, 0, fxCanvas.width, fxCanvas.height); + setTransform(fxctx); + drawRectSelectionOverlay(fxctx); + drawLineToolPreview(fxctx); } function wireBrushButtonRightClick() { diff --git a/celstomp/css/components/tools.css b/celstomp/css/components/tools.css index f8c57f3..11dd82b 100644 --- a/celstomp/css/components/tools.css +++ b/celstomp/css/components/tools.css @@ -126,6 +126,22 @@ cursor: pointer; } +#islandToolsSlot #toolSeg > label svg { + width: 20px; + height: 20px; + color: rgba(255,255,255,0.85); + position: relative; + z-index: 1; +} + +#islandToolsSlot #toolSeg > label:has(svg) { + background-image: none; +} + +#islandToolsSlot #toolSeg > label:has(svg)::before { + display: none; +} + #islandToolsSlot #toolSeg > label::before{ content: ""; width: 20px; diff --git a/celstomp/js/input/pointer-events.js b/celstomp/js/input/pointer-events.js index b0e5534..cd58e81 100644 --- a/celstomp/js/input/pointer-events.js +++ b/celstomp/js/input/pointer-events.js @@ -12,6 +12,8 @@ let brushSize = 3; let autofill = false; let trailPoints = []; +let lineToolStart = null; +let lineToolPreview = null; function pressure(e) { const pid = Number.isFinite(e?.pointerId) ? e.pointerId : -1; @@ -338,6 +340,17 @@ function startStroke(e) { startPan(e); return; } + if (tool === "line") { + isDrawing = true; + const hex = colorToHex(currentColor); + strokeHex = activeLayer === LAYER.FILL ? fillWhite : hex; + activeSubColor[activeLayer] = strokeHex; + ensureSublayer(activeLayer, strokeHex); + renderLayerSwatches(activeLayer); + lineToolStart = { x, y }; + lineToolPreview = { x, y }; + return; + } if (activeLayer === PAPER_LAYER) { return; } @@ -404,6 +417,11 @@ function continueStroke(e) { }; return; } + if (tool === "line") { + lineToolPreview = { x, y }; + queueRenderAll(); + return; + } if (tool === "fill-eraser" || tool === "fill-brush") { fxTransform(); fxStamp1px(lastPt.x, lastPt.y, x, y); @@ -474,6 +492,25 @@ function endStroke() { stabilizedPt = null; return; } + if (tool === "line" && lineToolStart && lineToolPreview) { + const hex = strokeHex || activeSubColor?.[activeLayer] || colorToHex(currentColor); + const off = getFrameCanvas(activeLayer, currentFrame, hex); + const ctx = off.getContext("2d"); + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + ctx.lineWidth = brushSize; + ctx.strokeStyle = hex; + ctx.beginPath(); + ctx.moveTo(lineToolStart.x, lineToolStart.y); + ctx.lineTo(lineToolPreview.x, lineToolPreview.y); + ctx.stroke(); + markFrameHasContent(activeLayer, currentFrame, hex); + lineToolStart = null; + lineToolPreview = null; + queueRenderAll(); + updateTimelineHasContent(currentFrame); + return; + } if (tool === "lasso-erase" && lassoActive) { lassoActive = false; applyLassoErase(); @@ -843,6 +880,20 @@ function drawRectSelectionOverlay(ctx) { ctx.strokeRect(rectSelection.x, rectSelection.y, rectSelection.w, rectSelection.h); ctx.restore(); } +function drawLineToolPreview(ctx) { + if (!lineToolStart || !lineToolPreview) return; + ctx.save(); + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + ctx.lineWidth = brushSize; + ctx.strokeStyle = currentColor; + ctx.globalAlpha = 0.5; + ctx.beginPath(); + ctx.moveTo(lineToolStart.x, lineToolStart.y); + ctx.lineTo(lineToolPreview.x, lineToolPreview.y); + ctx.stroke(); + ctx.restore(); +} function beginRectSelect(e) { if (activeLayer === PAPER_LAYER) return; const pos = getCanvasPointer(e); diff --git a/celstomp/js/tools/brush-helper.js b/celstomp/js/tools/brush-helper.js index c18d299..7682367 100644 --- a/celstomp/js/tools/brush-helper.js +++ b/celstomp/js/tools/brush-helper.js @@ -315,9 +315,9 @@ function getBrushSizeForPreview(toolKind) { function updateBrushPreview() { if (!_brushPrevEl || !_brushPrevCanvas) return; const toolKind = getActiveToolKindForPreview(); - const isBrush = toolKind === "brush"; + const showForTools = toolKind === "brush" || toolKind === "eraser" || toolKind === "line"; const isEraser = toolKind === "eraser"; - if (!isBrush && !isEraser) { + if (!showForTools) { _brushPrevEl.style.display = "none"; return; } diff --git a/celstomp/js/ui/interaction-shortcuts.js b/celstomp/js/ui/interaction-shortcuts.js index 5359ed6..704d7a7 100644 --- a/celstomp/js/ui/interaction-shortcuts.js +++ b/celstomp/js/ui/interaction-shortcuts.js @@ -600,12 +600,13 @@ function wireKeyboardShortcuts() { const toolByKey = { 1: "brush", 2: "eraser", - 3: "fill-brush", - 4: "fill-eraser", - 5: "lasso-fill", - 6: "lasso-erase", - 7: "rect-select", - 8: "eyedropper" + 3: "line", + 4: "fill-brush", + 5: "fill-eraser", + 6: "lasso-fill", + 7: "lasso-erase", + 8: "rect-select", + 9: "eyedropper" }; document.addEventListener("keydown", e => { if (e.defaultPrevented) return; @@ -690,27 +691,34 @@ function onWindowKeyDown(e) { }); } if (isDigit(3)) { + e.preventDefault(); + pickTool({ + id: "tool-line", + value: "line" + }); + } + if (isDigit(4)) { e.preventDefault(); pickTool({ id: "tool-fillbrush", value: "fill-brush" }); } - if (isDigit(4)) { + if (isDigit(5)) { e.preventDefault(); pickTool({ id: "tool-filleraser", value: "fill-eraser" }); } - if (isDigit(5)) { + if (isDigit(6)) { e.preventDefault(); pickTool({ id: "tool-lassoFill", value: "lasso-fill" }); } - if (isDigit(6)) { + if (isDigit(7)) { e.preventDefault(); pickTool({ id: "tool-lassoErase", @@ -718,14 +726,14 @@ function onWindowKeyDown(e) { value: "lasso-erase" }); } - if (isDigit(7)) { + if (isDigit(8)) { e.preventDefault(); pickTool({ id: "tool-rectSelect", value: "rect-select" }); } - if (isDigit(8)) { + if (isDigit(9)) { e.preventDefault(); pickTool({ id: "tool-eyedropper", diff --git a/celstomp/js/ui/ui-components.js b/celstomp/js/ui/ui-components.js index 3614e29..fc949ee 100644 --- a/celstomp/js/ui/ui-components.js +++ b/celstomp/js/ui/ui-components.js @@ -4,6 +4,7 @@ const tools = [ { id: 'tool-brush', val: 'brush', label: 'Brush', checked: true }, { id: 'tool-eraser', val: 'eraser', label: 'Eraser' }, + { id: 'tool-line', val: 'line', label: 'Line', icon: '' }, { id: 'tool-fillbrush', val: 'fill-brush', label: 'Fill Brush' }, { id: 'tool-filleraser', val: 'fill-eraser', label: 'Eraser Fill' }, { id: 'tool-lassoFill', val: 'lasso-fill', label: 'Lasso Fill' }, @@ -27,7 +28,12 @@ const lbl = document.createElement('label'); lbl.htmlFor = t.id; lbl.dataset.tool = t.val; - lbl.textContent = t.label; + lbl.setAttribute('aria-label', t.label); + if (t.icon) { + lbl.innerHTML = t.icon; + } else { + lbl.textContent = t.label; + } if (t.val === 'brush') lbl.id = 'toolBrushLabel'; if (t.val === 'eraser') lbl.id = 'toolEraserLabel'; diff --git a/celstomp/parts/modals.js b/celstomp/parts/modals.js index 5351362..823e69f 100644 --- a/celstomp/parts/modals.js +++ b/celstomp/parts/modals.js @@ -52,12 +52,13 @@ document.getElementById('part-modals').innerHTML = `

Tools

1Brush
2Eraser
-
3Fill Brush
-
4Fill Eraser
-
5Lasso Fill
-
6Lasso Erase
-
7Rect Select
-
8Eyedropper
+
3Line
+
4Fill Brush
+
5Fill Eraser
+
6Lasso Fill
+
7Lasso Erase
+
8Rect Select
+
9Eyedropper

Navigation