From d19c203d8dc8522e5b9f12f225fa1398abded4ca Mon Sep 17 00:00:00 2001 From: Dario Mory Date: Thu, 3 Sep 2026 14:57:28 +0700 Subject: [PATCH 1/2] Make text layers easier to drag hitTestLayer rejected any pixel with alpha below 10, and a text raster is mostly transparent between glyphs, so the layer slipped out from under the cursor unless you grabbed a stroke exactly. Text layers are now picked by their bounding box. The trade-off is that a text layer captures clicks across its whole box, including the gaps; picking a layer underneath means clicking outside the text bounds. Fixes #2 --- src/canvas/draw-document.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/canvas/draw-document.ts b/src/canvas/draw-document.ts index 3a76702..6fa2de5 100644 --- a/src/canvas/draw-document.ts +++ b/src/canvas/draw-document.ts @@ -99,6 +99,11 @@ export function hitTestLayer(document: Document, docX: number, docY: number): La const width = source.width * scaleX; const height = source.height * scaleY; if (docX < x || docY < y || docX > x + width || docY > y + height) continue; + // Text rasters are mostly transparent between glyphs, so a per-pixel alpha + // test makes them slip out from under the cursor. Their bounding box is the + // grabbable area instead. + if (layer.kind === "text" || layer.role === "text") return layer; + const localX = (docX - x) / scaleX; const localY = (docY - y) / scaleY; if (source instanceof HTMLCanvasElement && sampleCanvasAlpha(source, localX, localY) < 10) { From d53574c59ab1473000bde0a948c3438c7783399c Mon Sep 17 00:00:00 2001 From: Dario Mory Date: Thu, 3 Sep 2026 16:22:24 +0700 Subject: [PATCH 2/2] Give text layers a forgiving hit box Adds a little slop around the text bounds so a near miss at the edge still grabs the layer, and documents that the same box drives both dragging and click-to-edit - which is the behaviour being asked for: click anywhere in the text rectangle rather than hunting for a glyph. Verified by measuring the rendered text box and probing a grid inside it: all fifteen points grab the layer, gaps between letters included, and clicking a gap with the Text tool reopens the text for editing. Fixes #2 --- src/canvas/draw-document.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/canvas/draw-document.ts b/src/canvas/draw-document.ts index 6fa2de5..e7e563f 100644 --- a/src/canvas/draw-document.ts +++ b/src/canvas/draw-document.ts @@ -86,6 +86,9 @@ function drawCheckerboard( } } +/** Document-space slop around a text layer, so near misses still grab it. */ +const TEXT_HIT_PADDING = 6; + export function hitTestLayer(document: Document, docX: number, docY: number): Layer | undefined { for (let i = document.layers.length - 1; i >= 0; i -= 1) { const layer = document.layers[i]; @@ -98,11 +101,14 @@ export function hitTestLayer(document: Document, docX: number, docY: number): La const scaleY = layer.transform.scaleY; const width = source.width * scaleX; const height = source.height * scaleY; - if (docX < x || docY < y || docX > x + width || docY > y + height) continue; - // Text rasters are mostly transparent between glyphs, so a per-pixel alpha - // test makes them slip out from under the cursor. Their bounding box is the - // grabbable area instead. - if (layer.kind === "text" || layer.role === "text") return layer; + // Text rasters sit tight around the glyphs and are mostly transparent + // between them, so a per-pixel alpha test makes text slip out from under + // the cursor. Text is picked by its whole box, plus a little slop at the + // edges, and that box is what both dragging and click-to-edit use. + const isText = layer.kind === "text" || layer.role === "text"; + const pad = isText ? TEXT_HIT_PADDING : 0; + if (docX < x - pad || docY < y - pad || docX > x + width + pad || docY > y + height + pad) continue; + if (isText) return layer; const localX = (docX - x) / scaleX; const localY = (docY - y) / scaleY;