diff --git a/celstomp/celstomp-app.js b/celstomp/celstomp-app.js index 3d924b6..8285eed 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); + drawRectToolPreview(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); + drawRectToolPreview(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..9199774 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 rectToolStart = null; +let rectToolPreview = null; function pressure(e) { const pid = Number.isFinite(e?.pointerId) ? e.pointerId : -1; @@ -266,6 +268,17 @@ function startStroke(e) { pickCanvasColorAtEvent(e); return; } + if (tool === "rect") { + isDrawing = true; + const hex = colorToHex(currentColor); + strokeHex = activeLayer === LAYER.FILL ? fillWhite : hex; + activeSubColor[activeLayer] = strokeHex; + ensureSublayer(activeLayer, strokeHex); + renderLayerSwatches(activeLayer); + rectToolStart = { x, y }; + rectToolPreview = { x, y }; + return; + } if (tool === "rect-select") { isDrawing = true; beginRectSelect(e); @@ -396,6 +409,11 @@ function continueStroke(e) { x: x, y: y }; + if (tool === "rect") { + rectToolPreview = { x, y }; + queueRenderAll(); + return; + } if (tool === "rect-select") { updateRectSelect(e); lastPt = { @@ -468,6 +486,24 @@ function endStroke() { strokeHex = null; queueRenderAll(); updateTimelineHasContent(currentFrame); + if (tool === "rect" && rectToolStart && rectToolPreview) { + const hex = strokeHex || activeSubColor?.[activeLayer] || colorToHex(currentColor); + const off = getFrameCanvas(activeLayer, currentFrame, hex); + const ctx = off.getContext("2d"); + ctx.strokeStyle = hex; + ctx.lineWidth = Math.max(1, brushSize); + ctx.lineCap = "round"; + ctx.beginPath(); + ctx.rect(rectToolStart.x, rectToolStart.y, rectToolPreview.x - rectToolStart.x, rectToolPreview.y - rectToolStart.y); + ctx.stroke(); + off._hasContent = true; + rectToolStart = null; + rectToolPreview = null; + queueRenderAll(); + lastPt = null; + stabilizedPt = null; + return; + } if (tool === "rect-select") { endRectSelect(); lastPt = null; @@ -1487,3 +1523,15 @@ function fillFromLineart(F) { updateTimelineHasContent(F); return true; } + +function drawRectToolPreview(ctx) { + if (!rectToolStart || !rectToolPreview) return; + ctx.save(); + ctx.strokeStyle = colorToHex(currentColor); + ctx.lineWidth = Math.max(1, brushSize) / Math.max(getZoom(), 1); + ctx.setLineDash([4 / Math.max(getZoom(), 1), 2 / Math.max(getZoom(), 1)]); + ctx.beginPath(); + ctx.rect(rectToolStart.x, rectToolStart.y, rectToolPreview.x - rectToolStart.x, rectToolPreview.y - rectToolStart.y); + ctx.stroke(); + ctx.restore(); +} diff --git a/celstomp/js/ui/interaction-shortcuts.js b/celstomp/js/ui/interaction-shortcuts.js index 5359ed6..6b72e45 100644 --- a/celstomp/js/ui/interaction-shortcuts.js +++ b/celstomp/js/ui/interaction-shortcuts.js @@ -601,11 +601,12 @@ function wireKeyboardShortcuts() { 1: "brush", 2: "eraser", 3: "fill-brush", - 4: "fill-eraser", - 5: "lasso-fill", - 6: "lasso-erase", - 7: "rect-select", - 8: "eyedropper" + 4: "rect", + 5: "fill-eraser", + 6: "lasso-fill", + 7: "lasso-erase", + 8: "rect-select", + 9: "eyedropper" }; document.addEventListener("keydown", e => { if (e.defaultPrevented) return; diff --git a/celstomp/js/ui/ui-components.js b/celstomp/js/ui/ui-components.js index 3614e29..bc0e8b0 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-rect', val: 'rect', label: 'Rect', 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..32999f8 100644 --- a/celstomp/parts/modals.js +++ b/celstomp/parts/modals.js @@ -53,11 +53,12 @@ document.getElementById('part-modals').innerHTML = `
1Brush
2Eraser
3Fill Brush
-
4Fill Eraser
-
5Lasso Fill
-
6Lasso Erase
-
7Rect Select
-
8Eyedropper
+
4Rect
+
5Fill Eraser
+
6Lasso Fill
+
7Lasso Erase
+
8Rect Select
+
9Eyedropper

Navigation