fix: shape resize/rotate by converting screen coords to canvas space
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 <noreply@anthropic.com>
This commit is contained in:
parent
7616fe0757
commit
aeb9247f96
|
|
@ -375,6 +375,18 @@ export class FolkShape extends FolkElement {
|
||||||
return rect.width / w;
|
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) {
|
handleEvent(event: PointerEvent | KeyboardEvent | TouchEvent) {
|
||||||
// Handle touch events for mobile drag support
|
// Handle touch events for mobile drag support
|
||||||
if (event instanceof TouchEvent) {
|
if (event instanceof TouchEvent) {
|
||||||
|
|
@ -454,7 +466,7 @@ export class FolkShape extends FolkElement {
|
||||||
x: this.#rect.width * this.#rect.rotateOrigin.x,
|
x: this.#rect.width * this.#rect.rotateOrigin.x,
|
||||||
y: this.#rect.height * this.#rect.rotateOrigin.y,
|
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;
|
this.#startAngle = Vector.angleFromOrigin(mousePos, parentRotateOrigin) - this.#rect.rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,7 +538,7 @@ export class FolkShape extends FolkElement {
|
||||||
const mousePos =
|
const mousePos =
|
||||||
event instanceof KeyboardEvent
|
event instanceof KeyboardEvent
|
||||||
? { x: currentPos.x + moveDelta.x, y: currentPos.y + moveDelta.y }
|
? { x: currentPos.x + moveDelta.x, y: currentPos.y + moveDelta.y }
|
||||||
: { x: event.clientX, y: event.clientY };
|
: this.#screenToParent(event.clientX, event.clientY);
|
||||||
|
|
||||||
this.#handleResize(
|
this.#handleResize(
|
||||||
handle as ResizeHandle,
|
handle as ResizeHandle,
|
||||||
|
|
@ -544,7 +556,7 @@ export class FolkShape extends FolkElement {
|
||||||
y: this.#rect.height * this.#rect.rotateOrigin.y,
|
y: this.#rect.height * this.#rect.rotateOrigin.y,
|
||||||
});
|
});
|
||||||
const currentAngle = Vector.angleFromOrigin(
|
const currentAngle = Vector.angleFromOrigin(
|
||||||
{ x: event.clientX, y: event.clientY },
|
this.#screenToParent(event.clientX, event.clientY),
|
||||||
parentRotateOrigin,
|
parentRotateOrigin,
|
||||||
);
|
);
|
||||||
this.rotation = currentAngle - this.#startAngle;
|
this.rotation = currentAngle - this.#startAngle;
|
||||||
|
|
|
||||||
|
|
@ -2664,6 +2664,12 @@
|
||||||
canvas.style.backgroundPosition = `${panX - 1}px ${panY - 1}px`;
|
canvas.style.backgroundPosition = `${panX - 1}px ${panY - 1}px`;
|
||||||
// Keep MI bridge in sync
|
// Keep MI bridge in sync
|
||||||
__miCanvasBridge.setViewport(panX, panY, scale);
|
__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", () => {
|
document.getElementById("zoom-in").addEventListener("click", () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue