Add CANCEL to the watermark editor, scope the mark's touch layer to that editor, and stop a library frame following the user onto the camera
The custom watermark captured EVERY image touch whenever the chip was ON with text, in both modes: after switching from a library photo to the camera there was no way to give the image back to tap-to-focus, and a frame picked for the still rode along onto the live preview (and into the capture). - AdjustPanel: CANCEL chip next to ACCEPT. It drops the un-accepted draft and restores the last ACCEPTED mark (none if nothing was accepted). - Viewfinder: draws the mark whenever the chip is ON with text, but only lets it CAPTURE touches while its editor is open (new wmEditing prop). A double-tap ON the mark still re-opens that editor from the library viewer. - App: ACCEPT and CANCEL now close the editor (that closes the placement layer with it). Switching mode closes the deck panels, and leaves the frame behind: camera mode has no FRAME picker to undo one. Verified on device (9a6a7277): camera tap locks AE/AF with the watermark left ON; library polaroid no longer appears on the live preview after the switch; CANCEL reverts the chip to CUSTOM WATERMARK OFF and closes the panel; ACCEPT closes it and the next capture embeds the mark (amber text found at 0.5/0.375 of the 3000x4000 export), while a capture before ACCEPT carries none.
This commit is contained in:
@@ -97,6 +97,26 @@ export default function App() {
|
||||
setCustomWm(wm);
|
||||
if (!wm.enabled) setCustomWmApplied({ ...DEFAULT_CUSTOM_WM });
|
||||
};
|
||||
// Leaving the watermark editor = closing its panel. The mark keeps capturing
|
||||
// image touches ONLY while that panel is open (Viewfinder's wmArmed), so this
|
||||
// is what hands the photo back to tap-to-focus / pinch / double-tap zoom.
|
||||
const closeWatermarkEditor = () => {
|
||||
setOpenTab(null);
|
||||
setOpenParamKey(null);
|
||||
};
|
||||
// ACCEPT: the draft becomes the mark the export burns in, then the editor
|
||||
// closes so the image stops being a placement surface.
|
||||
const handleAcceptCustomWm = () => {
|
||||
setCustomWmApplied({ ...customWm });
|
||||
closeWatermarkEditor();
|
||||
};
|
||||
// CANCEL: drop every un-accepted edit and fall back to the last ACCEPTED mark
|
||||
// (none at all when nothing was ever accepted), then close exactly like
|
||||
// ACCEPT — the escape hatch a running session previously lacked.
|
||||
const handleCancelCustomWm = () => {
|
||||
setCustomWm({ ...customWmApplied });
|
||||
closeWatermarkEditor();
|
||||
};
|
||||
// Composition aspect-ratio (FULL / 4:3 / 3:2 upright) + RAW DNG sidecar.
|
||||
const [aspectRatio, setAspectRatio] = useState<AspectRatio>('full');
|
||||
const [rawEnabled, setRawEnabled] = useState(false);
|
||||
@@ -816,15 +836,20 @@ export default function App() {
|
||||
// Switching back to camera: drop GPS that belongs to a library photo, so the
|
||||
// live view (and any capture) never stamps a stale location.
|
||||
const handleSetMode = (next: 'camera' | 'library') => {
|
||||
if (next === mode) return;
|
||||
if (next === 'camera' && gpsInfo && gpsInfo.source !== 'device') {
|
||||
setGpsInfo(null);
|
||||
}
|
||||
// FRAME only exists in library mode — close the panel when switching back
|
||||
// to camera so a dead frame chip row never lingers over the live preview.
|
||||
if (next === 'camera' && openTab === 'frame') {
|
||||
setOpenTab(null);
|
||||
setOpenParamKey(null);
|
||||
}
|
||||
// The deck panels belong to one mode only (FRAME exists in library alone,
|
||||
// and the WATERMARK panel is the watermark's edit session), so every switch
|
||||
// closes them: no frame chip row over the live preview, and no watermark
|
||||
// placement layer left eating the camera's tap-to-focus.
|
||||
setOpenTab(null);
|
||||
setOpenParamKey(null);
|
||||
// A frame was picked for the still it sits on; camera mode has no FRAME
|
||||
// picker to undo it, so it must never follow the user onto the live camera
|
||||
// (and into the capture, which burns the frame in).
|
||||
if (next === 'camera') setSelectedFrame('none');
|
||||
setMode(next);
|
||||
};
|
||||
|
||||
@@ -975,8 +1000,17 @@ export default function App() {
|
||||
meteringAdjustments={meteringAdj}
|
||||
imageAdjustTarget={imageAdjustTarget}
|
||||
customWm={customWm}
|
||||
// The mark only captures image touches while its panel is open, so a
|
||||
// watermark left ON cannot block tap-to-focus or pinch/zoom.
|
||||
wmEditing={openTab === 'watermark'}
|
||||
onWmPositionChange={(x, y) => setCustomWm((p) => ({ ...p, x, y }))}
|
||||
onWmEdit={() => setWmEditNonce((n) => n + 1)}
|
||||
onWmEdit={() => {
|
||||
// Double-tap on the mark re-opens its editor (and with it the
|
||||
// placement layer), then focuses the input.
|
||||
setOpenTab('watermark');
|
||||
setOpenParamKey(null);
|
||||
setWmEditNonce((n) => n + 1);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1019,7 +1053,8 @@ export default function App() {
|
||||
onDeleteRecipe={handleDeleteRecipe}
|
||||
customWm={customWm}
|
||||
onUpdateCustomWm={handleUpdateCustomWm}
|
||||
onAcceptCustomWm={() => setCustomWmApplied({ ...customWm })}
|
||||
onAcceptCustomWm={handleAcceptCustomWm}
|
||||
onCancelCustomWm={handleCancelCustomWm}
|
||||
wmEditNonce={wmEditNonce}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -58,6 +58,10 @@ interface AdjustmentPanelProps {
|
||||
// ACCEPT: copy the live draft into the mark the export burns in. Edits stay a
|
||||
// draft (viewfinder preview only) until this is pressed.
|
||||
onAcceptCustomWm: () => void;
|
||||
// CANCEL: throw the un-accepted draft away — back to the mark that was last
|
||||
// ACCEPTED (or none at all) — and close the editor. The way out of a running
|
||||
// watermark edit that previously did not exist.
|
||||
onCancelCustomWm: () => void;
|
||||
// Bumped by a double-tap on the mark: put the caret back in the text input.
|
||||
wmEditNonce: number;
|
||||
}
|
||||
@@ -139,6 +143,7 @@ export default function AdjustmentPanel({
|
||||
customWm,
|
||||
onUpdateCustomWm,
|
||||
onAcceptCustomWm,
|
||||
onCancelCustomWm,
|
||||
wmEditNonce,
|
||||
onToggleRotation,
|
||||
}: AdjustmentPanelProps) {
|
||||
@@ -526,6 +531,13 @@ export default function AdjustmentPanel({
|
||||
active: true,
|
||||
onPress: onAcceptCustomWm,
|
||||
})}
|
||||
{customWm.enabled &&
|
||||
renderChip({
|
||||
key: 'custom-wm-cancel',
|
||||
label: 'CANCEL',
|
||||
active: false,
|
||||
onPress: onCancelCustomWm,
|
||||
})}
|
||||
{customWm.enabled && renderChip(groupChip('wmColor'))}
|
||||
{customWm.enabled && renderChip(groupChip('wmSize'))}
|
||||
{customWm.enabled && renderChip(groupChip('wmFont'))}
|
||||
|
||||
@@ -103,6 +103,12 @@ interface ViewfinderProps {
|
||||
y: number;
|
||||
};
|
||||
onWmPositionChange?: (x: number, y: number) => void;
|
||||
// True while the WATERMARK panel is open — that panel IS the mark's edit
|
||||
// session. The mark draws whenever the CUSTOM chip is ON with text, but it
|
||||
// only CAPTURES image touches (placement drags in camera mode, click-through
|
||||
// to focus / zoom) while this is set, so a watermark left ON can never
|
||||
// swallow the camera's tap-to-focus or the library's pinch/double-tap.
|
||||
wmEditing?: boolean;
|
||||
}
|
||||
|
||||
export interface ViewfinderHandle {
|
||||
@@ -186,6 +192,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
customWm = { enabled: false, text: '', x: 0.5, y: 0.5 },
|
||||
onWmPositionChange,
|
||||
onWmEdit,
|
||||
wmEditing = false,
|
||||
photoRotation = 0,
|
||||
}: ViewfinderProps,
|
||||
ref
|
||||
@@ -1018,11 +1025,10 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
// Armed = CUSTOM chip ON + non-empty text + live position callback. While
|
||||
// armed, touches on the image place/move the text (see the wm branches in the
|
||||
// camera + library gesture handlers); this is the only way to position it.
|
||||
const wmArmed = !!(
|
||||
customWm.enabled &&
|
||||
customWm.text.trim().length > 0 &&
|
||||
onWmPositionChange
|
||||
);
|
||||
// Drawn in both modes whenever the chip is ON with text; TOUCHED only while
|
||||
// the WATERMARK panel is open (wmEditing) — see the prop comment.
|
||||
const wmVisible = !!(customWm.enabled && customWm.text.trim().length > 0);
|
||||
const wmArmed = !!(wmVisible && wmEditing && onWmPositionChange);
|
||||
const [wmDragPos, setWmDragPos] = useState<{ x: number; y: number } | null>(null);
|
||||
// Photo/window area the 0..1 fractions map onto — identical rule to
|
||||
// renderGPSWatermark so both marks share the user-visible coordinate space.
|
||||
@@ -1077,7 +1083,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
// Bounding box of the drawn mark (same anchor maths as renderCustomWatermark)
|
||||
// so a double-tap ON the text re-opens the editor instead of moving it.
|
||||
const wmBox = () => {
|
||||
if (!wmArmed || !wmFont) return null;
|
||||
if (!wmVisible || !wmFont) return null;
|
||||
const area = wmArea();
|
||||
const fracX = wmDragPos?.x ?? customWm.x;
|
||||
const fracY = wmDragPos?.y ?? customWm.y;
|
||||
@@ -1162,6 +1168,23 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
if (wasPinch) 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
|
||||
// editor (and the placement layer with it). A single tap is NOT consumed —
|
||||
// this block records nothing, so the viewer rules below still run and the
|
||||
// photo keeps pinch / double-tap zoom / pan.
|
||||
if (!wmArmed && wmVisible && libZoom.s <= 1.01) {
|
||||
const prev = lastTapRef.current;
|
||||
if (
|
||||
prev &&
|
||||
Date.now() - prev.t < 350 &&
|
||||
Math.hypot(x - prev.x, y - prev.y) < 50 &&
|
||||
wmBoxHit(wmBox(), x, y)
|
||||
) {
|
||||
lastTapRef.current = null;
|
||||
onWmEdit?.();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Watermark armed: a tap places + commits the text and skips the double-tap
|
||||
// zoom bookkeeping (double-tap zoom is disabled while placing).
|
||||
if (wmArmed && libZoom.s <= 1.01) {
|
||||
@@ -1344,7 +1367,7 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
};
|
||||
|
||||
const renderCustomWatermark = () => {
|
||||
if (!wmArmed || !wmFont) return null;
|
||||
if (!wmVisible || !wmFont) return null;
|
||||
const area = wmArea();
|
||||
const fracX = wmDragPos?.x ?? customWm.x;
|
||||
const fracY = wmDragPos?.y ?? customWm.y;
|
||||
|
||||
Reference in New Issue
Block a user