From f3411a14bdd98ac960edd0158a06a3b86d9b47f5 Mon Sep 17 00:00:00 2001 From: Samuel Prevost Date: Sat, 25 Apr 2026 10:23:45 +0200 Subject: [PATCH] fix(measurements): rectangle stays a rectangle on corner drag Dragging a corner used to move only that corner, turning the rect into an arbitrary quad. Constrain to axis-aligned: dragged corner follows the cursor, the diagonally-opposite corner stays put, the two adjacent corners are recomputed from the cross of (dragged.x, opp.y) and (opp.x, dragged.y) so the shape stays rectangular. Corner indices stay stable (c0 is still the logical TL even if the box flips through itself), so handle keys keep tracking the same corner. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/CorrectedImageViewer.vue | 40 ++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/components/CorrectedImageViewer.vue b/src/components/CorrectedImageViewer.vue index 9b697a9..5ca4e71 100644 --- a/src/components/CorrectedImageViewer.vue +++ b/src/components/CorrectedImageViewer.vue @@ -1163,25 +1163,45 @@ function applyDrag( if (handleKey === "a") return { ...original, a: { x: original.a.x + dx, y: original.a.y + dy } } if (handleKey === "b") return { ...original, b: { x: original.b.x + dx, y: original.b.y + dy } } } else if (original.type === "rectangle") { - // Per spec: dragging an individual corner only moves that corner — - // the rect can become non-rectangular. The array index stays - // stable so TL/TR/BR/BL labels don't shift. + // Constrain to an axis-aligned rectangle: the dragged corner + // follows the cursor, the diagonally-opposite corner stays put, + // and the two adjacent corners are recomputed from the cross of + // (dragged.x, opp.y) and (opp.x, dragged.y) so the shape stays + // rectangular. Corner indices stay stable — c0 is still the + // logical TL even if the box flips through itself. const cornerIdx: 0 | 1 | 2 | 3 | null = handleKey === "c0" ? 0 : handleKey === "c1" ? 1 : handleKey === "c2" ? 2 : handleKey === "c3" ? 3 : null if (cornerIdx !== null) { - const next: [Point, Point, Point, Point] = [ - { ...original.corners[0] }, - { ...original.corners[1] }, - { ...original.corners[2] }, - { ...original.corners[3] }, - ] - next[cornerIdx] = { + const moving = { x: original.corners[cornerIdx].x + dx, y: original.corners[cornerIdx].y + dy, } + const oppIdx = ((cornerIdx + 2) % 4) as 0 | 1 | 2 | 3 + const opp = { ...original.corners[oppIdx] } + const next: [Point, Point, Point, Point] = [ + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { x: 0, y: 0 }, + { x: 0, y: 0 }, + ] + next[cornerIdx] = moving + next[oppIdx] = opp + if (cornerIdx === 0 || cornerIdx === 2) { + // TL ↔ BR diagonal: TR=(BR.x, TL.y), BL=(TL.x, BR.y). + const tl = next[0] + const br = next[2] + next[1] = { x: br.x, y: tl.y } + next[3] = { x: tl.x, y: br.y } + } else { + // TR ↔ BL diagonal: TL=(BL.x, TR.y), BR=(TR.x, BL.y). + const tr = next[1] + const bl = next[3] + next[0] = { x: bl.x, y: tr.y } + next[2] = { x: tr.x, y: bl.y } + } return { ...original, corners: next } } } else if (original.type === "ellipse") {