From aeb9247f965d6ac73f6502323d1ee7c32f5700ab Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sun, 1 Mar 2026 10:38:06 -0800 Subject: [PATCH] fix: shape resize/rotate by converting screen coords to canvas space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resize and rotation handlers in folk-shape.ts passed raw event.clientX/Y (screen coordinates) to toLocalSpace/angleFromOrigin, but those methods expect canvas-parent coordinates. With any zoom/pan, the two coordinate systems diverge, making resize non-functional and rotation erratic. Added #screenToParent() to convert viewport coords to the parent's coordinate space using getBoundingClientRect + parent scale. Applied to: - Resize handle drag (pointermove → toLocalSpace) - Rotation start (pointerdown → angleFromOrigin) - Rotation drag (pointermove → angleFromOrigin) Also syncs ghost placeholder size with zoom changes so the dotted preview stays accurate if user zooms while in placement mode. Co-Authored-By: Claude Opus 4.6 --- lib/folk-shape.ts | 18 +++++++++++++++--- website/canvas.html | 6 ++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/folk-shape.ts b/lib/folk-shape.ts index 7ee228f..b9ad245 100644 --- a/lib/folk-shape.ts +++ b/lib/folk-shape.ts @@ -375,6 +375,18 @@ export class FolkShape extends FolkElement { return rect.width / w; } + /** Convert screen (viewport) coordinates to the parent's coordinate space. */ + #screenToParent(screenX: number, screenY: number): Point { + const parent = this.parentElement; + if (!parent) return { x: screenX, y: screenY }; + const parentRect = parent.getBoundingClientRect(); + const zoom = this.#getParentScale(); + return { + x: (screenX - parentRect.left) / zoom, + y: (screenY - parentRect.top) / zoom, + }; + } + handleEvent(event: PointerEvent | KeyboardEvent | TouchEvent) { // Handle touch events for mobile drag support if (event instanceof TouchEvent) { @@ -454,7 +466,7 @@ export class FolkShape extends FolkElement { x: this.#rect.width * this.#rect.rotateOrigin.x, y: this.#rect.height * this.#rect.rotateOrigin.y, }); - const mousePos = { x: event.clientX, y: event.clientY }; + const mousePos = this.#screenToParent(event.clientX, event.clientY); this.#startAngle = Vector.angleFromOrigin(mousePos, parentRotateOrigin) - this.#rect.rotation; } @@ -526,7 +538,7 @@ export class FolkShape extends FolkElement { const mousePos = event instanceof KeyboardEvent ? { x: currentPos.x + moveDelta.x, y: currentPos.y + moveDelta.y } - : { x: event.clientX, y: event.clientY }; + : this.#screenToParent(event.clientX, event.clientY); this.#handleResize( handle as ResizeHandle, @@ -544,7 +556,7 @@ export class FolkShape extends FolkElement { y: this.#rect.height * this.#rect.rotateOrigin.y, }); const currentAngle = Vector.angleFromOrigin( - { x: event.clientX, y: event.clientY }, + this.#screenToParent(event.clientX, event.clientY), parentRotateOrigin, ); this.rotation = currentAngle - this.#startAngle; diff --git a/website/canvas.html b/website/canvas.html index b380aff..f77a798 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -2664,6 +2664,12 @@ canvas.style.backgroundPosition = `${panX - 1}px ${panY - 1}px`; // Keep MI bridge in sync __miCanvasBridge.setViewport(panX, panY, scale); + // Keep ghost placeholder size in sync with zoom + if (ghostEl && pendingTool) { + const defaults = SHAPE_DEFAULTS[pendingTool.tagName] || { width: 300, height: 200 }; + ghostEl.style.width = (defaults.width * scale) + "px"; + ghostEl.style.height = (defaults.height * scale) + "px"; + } } document.getElementById("zoom-in").addEventListener("click", () => {