feat(frame): move the watermark controls into a FRAME sub-panel
This commit is contained in:
@@ -189,6 +189,8 @@ export default function App() {
|
||||
// local to AdjustmentPanel) because an open row arms drag-anywhere-on-the-
|
||||
// image adjustment in the Viewfinder. Cleared when the panel/tab closes.
|
||||
const [openParamKey, setOpenParamKey] = useState<string | null>(null);
|
||||
// FRAME → WATERMARK sub-panel is open (the rail's WATERMARK tab is gone).
|
||||
const [wmPanelOpen, setWmPanelOpen] = useState(false);
|
||||
|
||||
const [libraryImageUri, setLibraryImageUri] = useState<string | null>(null);
|
||||
// Session photo history (camera captures + library exports). The camera
|
||||
@@ -1273,12 +1275,12 @@ export default function App() {
|
||||
wallLandscape={wallLandscape}
|
||||
// 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'}
|
||||
wmEditing={openTab === 'frame' && wmPanelOpen}
|
||||
onWmPositionChange={(x, y) => setCustomWm((p) => ({ ...p, x, y }))}
|
||||
onWmEdit={() => {
|
||||
// Double-tap on the mark re-opens its editor (and with it the
|
||||
// placement layer), then focuses the input.
|
||||
setOpenTab('watermark');
|
||||
setOpenTab('frame');
|
||||
setOpenParamKey(null);
|
||||
setWmEditNonce((n) => n + 1);
|
||||
}}
|
||||
@@ -1356,11 +1358,12 @@ export default function App() {
|
||||
wmEditNonce={wmEditNonce}
|
||||
wmRotation={wmRotation}
|
||||
onRotateWm={() => setWmRotation((r) => (r + 90) % 360)}
|
||||
onWmPanelChange={setWmPanelOpen}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<ToolRail activeTab={openTab} onToggle={handleToggleTab} mode={mode} />
|
||||
<ToolRail activeTab={openTab} onToggle={handleToggleTab} />
|
||||
|
||||
<CameraControls
|
||||
lastPhotoUri={lastPhotoUri}
|
||||
|
||||
@@ -16,7 +16,7 @@ import RecipeCreateModal from './RecipeCreateModal';
|
||||
// Tapping a parameter chip toggles its slider row; chips stay visible.
|
||||
// Darkroom-flavoured: amber = active/edited, red = destructive, mono labels.
|
||||
|
||||
type TabId = 'recipes' | 'favorites' | 'iq' | 'wb' | 'filters' | 'frame' | 'watermark';
|
||||
type TabId = 'recipes' | 'favorites' | 'iq' | 'wb' | 'filters' | 'frame';
|
||||
// Choice-group keys that open a strip above the chip row: color chrome groups
|
||||
// (wb tab) and the D.RANGE selector (iq tab).
|
||||
type GroupKey =
|
||||
@@ -31,7 +31,9 @@ type GroupKey =
|
||||
| 'gpsColor'
|
||||
| 'gpsName'
|
||||
| 'gpsTime'
|
||||
| 'crop';
|
||||
| 'crop'
|
||||
// Not a strip: the WATERMARK row REPLACES the frame chips (sub-panel).
|
||||
| 'wm';
|
||||
|
||||
interface AdjustmentPanelProps {
|
||||
activeTab: TabId;
|
||||
@@ -111,6 +113,9 @@ interface AdjustmentPanelProps {
|
||||
// the GPS lines and the custom text, in the preview and in the export.
|
||||
wmRotation: number;
|
||||
onRotateWm: () => void;
|
||||
// FRAME → WATERMARK sub-panel is open: App needs this to arm the mark's
|
||||
// drag/pinch layer on the image (the old `activeTab === 'watermark'`).
|
||||
onWmPanelChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
interface ChipDef {
|
||||
@@ -222,6 +227,7 @@ export default function AdjustmentPanel({
|
||||
onToggleRotation,
|
||||
wmRotation,
|
||||
onRotateWm,
|
||||
onWmPanelChange,
|
||||
}: AdjustmentPanelProps) {
|
||||
// Double-tap on the mark in the viewfinder bumps wmEditNonce -> focus the
|
||||
// input again so the text can be retyped after the keyboard was dismissed.
|
||||
@@ -252,6 +258,11 @@ export default function AdjustmentPanel({
|
||||
setShowRecipes(false);
|
||||
setCreateVisible(false);
|
||||
}, [activeTab]);
|
||||
// The WATERMARK sub-panel is what arms the mark's drag layer on the image:
|
||||
// one effect on the strip state keeps App's flag true exactly while it shows.
|
||||
useEffect(() => {
|
||||
onWmPanelChange(openGroup === 'wm');
|
||||
}, [openGroup]);
|
||||
// CROP is a plain-frame tool: the moment the selected frame drops it the chip
|
||||
// unmounts, so the ratio strip must close with it (no orphan CROP row).
|
||||
useEffect(() => {
|
||||
@@ -411,7 +422,6 @@ export default function AdjustmentPanel({
|
||||
),
|
||||
filters: toRows(PARAM_DEFS.filters),
|
||||
frame: [],
|
||||
watermark: [],
|
||||
};
|
||||
|
||||
// TEMP chip's value: the remembered preset while the engine pair still
|
||||
@@ -477,7 +487,7 @@ export default function AdjustmentPanel({
|
||||
...[...names].sort().map((n) => ({ v: n, d: n })),
|
||||
];
|
||||
}, []);
|
||||
const groupDefs: Record<GroupKey, { label: string; off: string; value: string; options: { v: string; d: string }[]; onPick: (v: string) => void }> = {
|
||||
const groupDefs: Record<Exclude<GroupKey, 'wm'>, { label: string; off: string; value: string; options: { v: string; d: string }[]; onPick: (v: string) => void }> = {
|
||||
cx: {
|
||||
label: 'COLOR CHROME', off: 'none', value: a.colorChrome || 'none', options: chromeOptions,
|
||||
onPick: (v) => onUpdateAdjustments({ colorChrome: v as ColorAdjustments['colorChrome'] }),
|
||||
@@ -568,7 +578,7 @@ export default function AdjustmentPanel({
|
||||
onPick: (v) => onUpdateGpsWm({ ...gpsWm, showTime: v === 'on' }),
|
||||
},
|
||||
};
|
||||
const groupChip = (g: GroupKey): ChipDef => ({
|
||||
const groupChip = (g: Exclude<GroupKey, 'wm'>): ChipDef => ({
|
||||
key: `grp-${g}`,
|
||||
label: groupDefs[g].label,
|
||||
active: openGroup === g,
|
||||
@@ -581,6 +591,49 @@ export default function AdjustmentPanel({
|
||||
label: wbTempLabel(),
|
||||
});
|
||||
|
||||
// WATERMARK is a sub-panel of FRAME now (the rail tab is gone): its chip swaps
|
||||
// the frame chips for the watermark controls, behind a breadcrumb back.
|
||||
const wmOn = useGeotag || customWm.enabled;
|
||||
const wmChip = (): ChipDef => ({
|
||||
key: 'grp-wm',
|
||||
label: wmOn ? 'WATERMARK ON' : 'WATERMARK',
|
||||
active: openGroup === 'wm',
|
||||
amberValue: openGroup !== 'wm' && wmOn,
|
||||
onPress: () => toggleGroup('wm'),
|
||||
});
|
||||
const wmChips = (): ChipDef[] => [
|
||||
{
|
||||
key: 'gps',
|
||||
label: useGeotag ? 'GPS WATERMARK ON' : 'GPS WATERMARK OFF',
|
||||
active: useGeotag,
|
||||
onPress: () => onToggleGeotag(!useGeotag),
|
||||
},
|
||||
...(useGeotag ? [groupChip('gpsColor'), groupChip('gpsName'), groupChip('gpsTime')] : []),
|
||||
{
|
||||
key: 'custom-wm',
|
||||
label: customWm.enabled ? 'CUSTOM WATERMARK ON' : 'CUSTOM WATERMARK OFF',
|
||||
active: customWm.enabled,
|
||||
onPress: () => onUpdateCustomWm({ ...customWm, enabled: !customWm.enabled }),
|
||||
},
|
||||
...(wmOn
|
||||
? [{
|
||||
key: 'wm-rotate',
|
||||
label: `ROTATE ${wmRotation}°`,
|
||||
active: wmRotation !== 0,
|
||||
onPress: onRotateWm,
|
||||
}]
|
||||
: []),
|
||||
...(customWm.enabled
|
||||
? [
|
||||
{ key: 'custom-wm-accept', label: 'ACCEPT', active: true, onPress: onAcceptCustomWm },
|
||||
{ key: 'custom-wm-cancel', label: 'CANCEL', active: false, onPress: onCancelCustomWm },
|
||||
groupChip('wmColor'),
|
||||
groupChip('wmSize'),
|
||||
groupChip('wmFont'),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
// ---- per-tab chip rows (bottom, above the tool rail) -----------------------
|
||||
const chipsRow = (() => {
|
||||
switch (activeTab) {
|
||||
@@ -693,6 +746,24 @@ export default function AdjustmentPanel({
|
||||
);
|
||||
|
||||
case 'frame':
|
||||
// FRAME → WATERMARK: the sub-panel row (breadcrumb back to the frames).
|
||||
if (openGroup === 'wm') {
|
||||
return (
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 8 }}
|
||||
>
|
||||
{renderChip({
|
||||
key: 'wm-back',
|
||||
label: '< FRAME',
|
||||
active: false,
|
||||
onPress: () => setOpenGroup(null),
|
||||
})}
|
||||
{wmChips().map(renderChip)}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ScrollView
|
||||
horizontal
|
||||
@@ -719,55 +790,7 @@ export default function AdjustmentPanel({
|
||||
active: wallLandscape,
|
||||
onPress: onToggleWallLandscape,
|
||||
})}
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
case 'watermark':
|
||||
return (
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 8 }}
|
||||
>
|
||||
{renderChip({
|
||||
key: 'gps',
|
||||
label: useGeotag ? 'GPS WATERMARK ON' : 'GPS WATERMARK OFF',
|
||||
active: useGeotag,
|
||||
onPress: () => onToggleGeotag(!useGeotag),
|
||||
})}
|
||||
{useGeotag && renderChip(groupChip('gpsColor'))}
|
||||
{useGeotag && renderChip(groupChip('gpsName'))}
|
||||
{useGeotag && renderChip(groupChip('gpsTime'))}
|
||||
{renderChip({
|
||||
key: 'custom-wm',
|
||||
label: customWm.enabled ? 'CUSTOM WATERMARK ON' : 'CUSTOM WATERMARK OFF',
|
||||
active: customWm.enabled,
|
||||
onPress: () => onUpdateCustomWm({ ...customWm, enabled: !customWm.enabled }),
|
||||
})}
|
||||
{(useGeotag || customWm.enabled) &&
|
||||
renderChip({
|
||||
key: 'wm-rotate',
|
||||
label: `ROTATE ${wmRotation}°`,
|
||||
active: wmRotation !== 0,
|
||||
onPress: onRotateWm,
|
||||
})}
|
||||
{customWm.enabled &&
|
||||
renderChip({
|
||||
key: 'custom-wm-accept',
|
||||
label: 'ACCEPT',
|
||||
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'))}
|
||||
{renderChip(wmChip())}
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
@@ -820,7 +843,7 @@ export default function AdjustmentPanel({
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{openGroup && (
|
||||
{openGroup && openGroup !== 'wm' && (
|
||||
<View className="border-b border-zinc-800/60">
|
||||
<ScrollView
|
||||
horizontal
|
||||
@@ -872,7 +895,7 @@ export default function AdjustmentPanel({
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
{activeTab === 'watermark' && customWm.enabled && (
|
||||
{activeTab === 'frame' && openGroup === 'wm' && customWm.enabled && (
|
||||
<View className="border-b border-amber-500/30 bg-amber-500/5 px-3 py-2">
|
||||
<TextInput
|
||||
ref={wmInputRef}
|
||||
|
||||
@@ -2,14 +2,11 @@ import React from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
|
||||
export type TabId = 'recipes' | 'favorites' | 'iq' | 'wb' | 'filters' | 'frame' | 'watermark';
|
||||
export type TabId = 'recipes' | 'favorites' | 'iq' | 'wb' | 'filters' | 'frame';
|
||||
|
||||
interface ToolRailProps {
|
||||
activeTab: TabId | null;
|
||||
onToggle: (tab: TabId) => void;
|
||||
// FRAME only exists in library mode (camera shows every frame WYSIWYG through
|
||||
// the viewfinder overlays); WATERMARK is available in both modes.
|
||||
mode: 'camera' | 'library';
|
||||
}
|
||||
|
||||
const TOOLS: { id: TabId; label: string }[] = [
|
||||
@@ -19,12 +16,12 @@ const TOOLS: { id: TabId; label: string }[] = [
|
||||
{ id: 'wb', label: 'WB' },
|
||||
{ id: 'filters', label: 'FX' },
|
||||
{ id: 'frame', label: 'FRAME' },
|
||||
{ id: 'watermark', label: 'WATERMARK' },
|
||||
];
|
||||
|
||||
export default function ToolRail({ activeTab, onToggle, mode }: ToolRailProps) {
|
||||
// FRAME (frame picker) belongs to library stills only: camera mode hides it.
|
||||
const tools = TOOLS.filter((t) => t.id !== 'frame' || mode === 'library');
|
||||
export default function ToolRail({ activeTab, onToggle }: ToolRailProps) {
|
||||
// FRAME is on in BOTH modes: the camera composes through the frame's window
|
||||
// WYSIWYG (and WATERMARK now lives inside this tab).
|
||||
const tools = TOOLS;
|
||||
return (
|
||||
<View className="py-1.5">
|
||||
<ScrollView
|
||||
|
||||
Reference in New Issue
Block a user