Let the wall-frame photo be dragged inside its opening

This commit is contained in:
2026-09-10 15:36:51 +07:00
parent 2835f46c37
commit 2911dd57f0
+34 -4
View File
@@ -922,6 +922,10 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
const [libZoom, setLibZoom] = useState({ s: 1, tx: 0, ty: 0 });
const pinchRef = useRef<{ d: number; s: number; tx: number; ty: number; fx: number; fy: number } | null>(null);
const pinchUsedRef = useRef(false);
// Framed-window drag: anchor at touch-down (finger + transform origin), so a
// move never accumulates rounding across a slow drag.
const panRef = useRef<{ x: number; y: number; tx: number; ty: number } | null>(null);
const panUsedRef = useRef(false);
const lastTapRef = useRef<{ x: number; y: number; t: number } | null>(null);
const clampZoom = (s: number) => Math.max(1, Math.min(6, s));
// Contain-fit rect of the loaded photo in canvas coordinates (image is
@@ -1170,11 +1174,16 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
y <= box.y + box.h + 8;
// Library framed (polaroid/wall) returns reuse the SAME responder handlers as
// plain library (see the framed JSX below): single-finger drags place/move
// the text watermark while armed, pinch + double-tap repositions the photo
// inside the frame window (watermark stays armed when zoomed so detail can
// be inspected without accidentally moving text — matching plain library).
// the text watermark while armed, and otherwise DRAG the photo inside the
// framed window (same cover clamp as pinch, so the opening never shows the
// card/mat through it); pinch + double-tap zoom that same repositioning.
// The pan only bites once the photo is zoomed past 1x: at the cover scale it
// already fills the opening exactly, so the clamp has nowhere to move.
const onLibTouchStart = (e: any) => {
const ts = e.nativeEvent.touches;
panRef.current =
ts.length === 1 ? { x: ts[0].locationX, y: ts[0].locationY, tx: libZoom.tx, ty: libZoom.ty } : null;
panUsedRef.current = false;
if (ts.length >= 2) {
const [a, b] = ts;
pinchUsedRef.current = true;
@@ -1198,6 +1207,22 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
placeWm(t.pageX ?? t.locationX, t.pageY ?? t.locationY);
return;
}
// One finger, no pinch running: drag the photo inside the framed window
// (polaroid card / wall-frame opening). Goes through the same clamp as
// pinch, so the opening always stays covered by the picture.
if (ts.length === 1 && !pinchRef.current && zoomWindowRect()) {
const t = ts[0];
const pan0 = panRef.current;
if (pan0) {
const dx = t.locationX - pan0.x;
const dy = t.locationY - pan0.y;
if (Math.abs(dx) > 6 || Math.abs(dy) > 6) panUsedRef.current = true;
if (panUsedRef.current) {
setLibZoom(clampZoomToWindow(libZoom.s, pan0.tx + dx, pan0.ty + dy));
}
}
return;
}
let p = pinchRef.current;
if (ts.length >= 2 && !p) {
// Responder grant fires once on the first finger; a second finger that
@@ -1229,9 +1254,14 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
};
const onLibTouchEnd = (e: any) => {
const wasPinch = pinchUsedRef.current;
const wasPan = panUsedRef.current;
pinchRef.current = null;
pinchUsedRef.current = false;
if (wasPinch) return; // a double-tap is always a single finger
panRef.current = null;
panUsedRef.current = false;
// A drag is neither a tap nor the first half of a double-tap.
if (wasPan) lastTapRef.current = null;
if (wasPinch || wasPan) return; // a double-tap is always a single finger
const x = e.nativeEvent.locationX;
const y = e.nativeEvent.locationY;
// Mark drawn but its editor CLOSED: a double-tap ON the mark re-opens the