Show the picture with the last edit undone while the finger holds it
Pressing and holding on the photo with a LIGHT/WB/FX panel open asks App for a preview drawn without the edit that gesture just made; lifting the finger asks for the edited look back. Pegged to one gesture, not one pointer-move: App only re-snapshots the 'before' state after a 700ms pause, so the whole drag compares against its own starting point. Preview-only - the sliders, RECIPE and the export keep reading the real adjustments. Moving past TAP_SLOP, a second finger, or a release all end the peek, and a peek is not a tap, so tap-to-focus and double-tap zoom are untouched.
This commit is contained in:
@@ -106,6 +106,11 @@ interface ViewfinderProps {
|
||||
value: number;
|
||||
onChange: (v: number) => void;
|
||||
} | null;
|
||||
// Hold-to-compare: while armed (a LIGHT/WB/FX panel is open) pressing and
|
||||
// holding anywhere on the picture asks App to draw it with the last edit
|
||||
// undone; the finger coming off asks for the edited look back, so the user
|
||||
// can flick between the before and the after. Absent prop = feature off.
|
||||
onComparePeek?: (on: boolean) => void;
|
||||
// Custom text watermark (WATERMARK tab -> CUSTOM chip). Drawn live on the
|
||||
// image like the GPS mark; dragging/tapping the image moves it. Fractions are
|
||||
// 0..1 of the photo/window area. Absent prop = feature off.
|
||||
@@ -231,6 +236,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
onExposureChange,
|
||||
meteringAdjustments,
|
||||
imageAdjustTarget,
|
||||
onComparePeek,
|
||||
customWm = { enabled: false, text: '', x: 0.5, y: 0.5 },
|
||||
onWmPositionChange,
|
||||
gpsWm,
|
||||
@@ -625,6 +631,55 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
// Priority: an open panel param row (imageAdjustTarget) > EV (AE locked).
|
||||
// A plain tap (no movement) always falls through to tap-to-focus.
|
||||
const TAP_SLOP = 14; // px of movement before a touch counts as a drag
|
||||
|
||||
// ---- Hold-to-compare (see onComparePeek) ---------------------------------
|
||||
// The timer only arms when App has something to compare against; moving past
|
||||
// TAP_SLOP (or a second finger) hands the touch back to its normal job —
|
||||
// adjust / pinch / pan — and drops the peek.
|
||||
const HOLD_MS = 350;
|
||||
const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const holdAnchorRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const peekingRef = useRef(false);
|
||||
const onComparePeekRef = useRef(onComparePeek);
|
||||
onComparePeekRef.current = onComparePeek;
|
||||
const endPeek = () => {
|
||||
if (!peekingRef.current) return;
|
||||
peekingRef.current = false;
|
||||
onComparePeekRef.current?.(false);
|
||||
};
|
||||
const cancelHold = () => {
|
||||
if (holdTimerRef.current) {
|
||||
clearTimeout(holdTimerRef.current);
|
||||
holdTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
const startHold = (x: number, y: number) => {
|
||||
cancelHold();
|
||||
holdAnchorRef.current = { x, y };
|
||||
if (!onComparePeekRef.current) return;
|
||||
holdTimerRef.current = setTimeout(() => {
|
||||
holdTimerRef.current = null;
|
||||
peekingRef.current = true;
|
||||
onComparePeekRef.current?.(true);
|
||||
}, HOLD_MS);
|
||||
};
|
||||
// Ends any hold and reports whether this gesture WAS peeking — a peek is not
|
||||
// a tap, so its release must skip the tap actions below.
|
||||
const endHold = () => {
|
||||
cancelHold();
|
||||
holdAnchorRef.current = null;
|
||||
const was = peekingRef.current;
|
||||
endPeek();
|
||||
return was;
|
||||
};
|
||||
const holdNoteMove = (x: number, y: number) => {
|
||||
const a = holdAnchorRef.current;
|
||||
if (!a) return;
|
||||
if (Math.abs(x - a.x) <= TAP_SLOP && Math.abs(y - a.y) <= TAP_SLOP) return;
|
||||
holdAnchorRef.current = null;
|
||||
cancelHold();
|
||||
endPeek();
|
||||
};
|
||||
const imageAdjustTargetRef = useRef(imageAdjustTarget);
|
||||
imageAdjustTargetRef.current = imageAdjustTarget;
|
||||
type ImgDragSession = {
|
||||
@@ -676,9 +731,14 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
panUsedRef.current = false;
|
||||
if (gts.length < 2) pinchRef.current = null;
|
||||
imgDragRef.current = { y0: pageYOf(e), kind, v0, param, moved: false, lastSent: null };
|
||||
const ht = e?.nativeEvent?.touches?.[0] ?? e?.nativeEvent;
|
||||
startHold(ht?.pageX ?? 0, pageYOf(e));
|
||||
};
|
||||
const onImageDragMove = (e: any) => {
|
||||
const g = imgDragRef.current;
|
||||
const holdTs = e?.nativeEvent?.touches ?? [];
|
||||
if (holdTs.length >= 2) endHold();
|
||||
else holdNoteMove(holdTs[0]?.pageX ?? 0, pageYOf(e));
|
||||
// Framed window (polaroid card / wall opening) on the live camera: two
|
||||
// fingers pinch the picture and drag it inside the frame — exactly the crop
|
||||
// the export takes (frameWindowZoom). One finger drags it too, but only a
|
||||
@@ -808,6 +868,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
const onImageDragEnd = (e: any) => {
|
||||
const g = imgDragRef.current;
|
||||
imgDragRef.current = null;
|
||||
const peeked = endHold();
|
||||
wmPinchRef.current = null;
|
||||
wmDragTgtRef.current = null;
|
||||
wmDragRef.current = null;
|
||||
@@ -826,8 +887,9 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
commitWm();
|
||||
return;
|
||||
}
|
||||
if (!g.moved) {
|
||||
// Plain tap: lock focus at the tapped point (drag never re-locks).
|
||||
if (!g.moved && !peeked) {
|
||||
// Plain tap: lock focus at the tapped point (drag never re-locks). A
|
||||
// hold-to-compare is not a tap either.
|
||||
handleTapFocus(e.nativeEvent.locationX, e.nativeEvent.locationY);
|
||||
}
|
||||
};
|
||||
@@ -1710,6 +1772,8 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
return;
|
||||
}
|
||||
const ts = e.nativeEvent.touches;
|
||||
if (ts.length === 1) startHold(ts[0].pageX, ts[0].pageY);
|
||||
else endHold();
|
||||
panRef.current =
|
||||
ts.length === 1
|
||||
? {
|
||||
@@ -1764,6 +1828,8 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
return;
|
||||
}
|
||||
const ts = e.nativeEvent.touches;
|
||||
if (ts.length === 1) holdNoteMove(ts[0].pageX, ts[0].pageY);
|
||||
else endHold();
|
||||
// WATERMARK tab open: two fingers scale the mark under them (or do
|
||||
// nothing) — the photo itself only zooms once the editor is closed.
|
||||
if (ts.length >= 2 && wmEditing) {
|
||||
@@ -1881,6 +1947,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
cropDragRef.current = null;
|
||||
return;
|
||||
}
|
||||
const peeked = endHold();
|
||||
const wasPinch = pinchUsedRef.current;
|
||||
const wasPan = panUsedRef.current;
|
||||
// Decided at touch-down; the reset below would lose it.
|
||||
@@ -1895,8 +1962,8 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
panRef.current = null;
|
||||
panUsedRef.current = false;
|
||||
// A drag is neither a tap nor the first half of a double-tap.
|
||||
if (wasPan || wmMoved) lastTapRef.current = null;
|
||||
if (wasPinch || wasPan) return; // a double-tap is always a single finger
|
||||
if (wasPan || wmMoved || peeked) lastTapRef.current = null;
|
||||
if (wasPinch || wasPan || peeked) 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
|
||||
@@ -2548,6 +2615,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
onResponderGrant={onLibTouchStart}
|
||||
onResponderMove={onLibTouchMove}
|
||||
onResponderRelease={onLibTouchEnd}
|
||||
onResponderTerminate={onLibTouchEnd}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -2627,6 +2695,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
onResponderGrant={onLibTouchStart}
|
||||
onResponderMove={onLibTouchMove}
|
||||
onResponderRelease={onLibTouchEnd}
|
||||
onResponderTerminate={onLibTouchEnd}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -2777,6 +2846,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
onResponderGrant={onLibTouchStart}
|
||||
onResponderMove={onLibTouchMove}
|
||||
onResponderRelease={onLibTouchEnd}
|
||||
onResponderTerminate={onLibTouchEnd}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user