fix(studio): a dust spot is a place on the photo, so the angle carries it too

This commit is contained in:
2026-10-13 07:08:28 +07:00
parent 66f044822e
commit b37ec97352
3 changed files with 177 additions and 64 deletions
+31 -13
View File
@@ -1499,20 +1499,30 @@ export function Workspace() {
[remember, setAdjustment]
);
// The ROTATE strip and the two mirrors are the only things that move the photo
// under the marks already on it, and a mark is a place on the photo, not a
// place on the screen: turn the frame and every spot the dust brush laid down
// has to turn with it, or the repair slides off its mote and the mote is
// uncovered again (shared/utils/orientation.ts). One undo step, like the turn
// itself — UNDO hands back the frame AND the marks of the frame before it.
// The ROTATE strip, the STRAIGHTEN ruler and the two mirrors are the only
// things that move the photo under the marks already on it, and a mark is a
// place on the photo, not a place on the screen: move the frame and every spot
// the dust brush laid down has to move with it, or the repair slides off its
// mote and the mote is uncovered again (shared/utils/orientation.ts). One undo
// step, like the move itself — UNDO hands back the frame AND the marks of the
// frame before it.
// The frame the marks are drawn in is the state above, read through a ref so a
// STRAIGHTEN drag reads the angle it handed over last rather than the one the
// last render saw — the pointer outruns the render, and a mark carried twice
// from the same angle would land short. The reading is taken where the move is
// asked for, never inside the update: by the time React runs that, the ref
// holds the frame the user has just moved to.
const orientRef = useRef<Orientation>({ rot: rotation, straighten, flipH, flipV });
orientRef.current = { rot: rotation, straighten, flipH, flipV };
const carryMarks = useCallback(
(next: Orientation) => {
const from = { ...orientRef.current };
setRecipe((prev) => ({
...prev,
adjustments: layMarks(prev.adjustments, { rot: rotation, flipH, flipV }, next, photoDims),
adjustments: layMarks(prev.adjustments, from, next, photoDims),
}));
},
[rotation, flipH, flipV, photoDims]
[photoDims]
);
// FX's HEAL brush writes the spots the same way a knob writes its value: one
@@ -1668,13 +1678,21 @@ export function Workspace() {
// dragging the ruler is one undo step instead of one per degree.
const setStraightenOnce = useCallback(
(v: number) => {
const next = Math.max(-180, Math.min(180, Math.round(v)));
const from = orientRef.current;
if (next === from.straighten) return;
if (!sliderEditRef.current) {
sliderEditRef.current = true;
remember();
}
setStraighten(Math.max(-180, Math.min(180, Math.round(v))));
// The angle is drawn on the photo like the quarter turn is, so the marks go
// with it — and the ref moves with them, so the next notch of a drag is a
// step from here and not from the last render.
carryMarks({ rot: from.rot, straighten: next, flipH: from.flipH, flipV: from.flipV });
orientRef.current = { ...from, straighten: next };
setStraighten(next);
},
[remember]
[carryMarks, remember]
);
// The mark's box on the photo writes through one door: dragging moves x/y,
@@ -2871,7 +2889,7 @@ export function Workspace() {
const next = v === 'reset' ? 0 : (Number(v) as 0 | 90 | 180 | 270);
// RESET levels the whole frame, angle and mirrors included.
const level = v === 'reset';
carryMarks({ rot: next, flipH: level ? false : flipH, flipV: level ? false : flipV });
carryMarks({ rot: next, straighten: level ? 0 : straighten, flipH: level ? false : flipH, flipV: level ? false : flipV });
setRotation(next);
if (level) {
setStraighten(0);
@@ -3779,7 +3797,7 @@ export function Workspace() {
active: flipH,
onClick: () => {
remember();
carryMarks({ rot: rotation, flipH: !flipH, flipV });
carryMarks({ rot: rotation, straighten, flipH: !flipH, flipV });
setFlipH((v) => !v);
},
},
@@ -3789,7 +3807,7 @@ export function Workspace() {
active: flipV,
onClick: () => {
remember();
carryMarks({ rot: rotation, flipH, flipV: !flipV });
carryMarks({ rot: rotation, straighten, flipH, flipV: !flipV });
setFlipV((v) => !v);
},
},