Add D.RANGE toggle chip to LIGHT tab; letterbox camera polaroid to 3:4 card

This commit is contained in:
2026-09-08 17:26:39 +07:00
parent b5a257e033
commit 669224fa39
2 changed files with 63 additions and 60 deletions
+56 -57
View File
@@ -13,6 +13,9 @@ import { FRAMES } from '../utils/frameUtils';
// Darkroom-flavoured: amber = active/edited, red = destructive, mono labels.
type TabId = 'recipes' | '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 = 'cx' | 'cxb' | 'dr';
interface AdjustmentPanelProps {
activeTab: TabId;
@@ -72,9 +75,9 @@ export default function AdjustmentPanel({
// The single parameter whose slider row is open — a new selection replaces
// the previous row (one row at a time, above the chips).
const [openParam, setOpenParam] = useState<string | null>(null);
// The single color-chrome group whose OFF/WEAK/STRONG strip is open above the
// chips (same one-row-at-a-time pattern as openParam, but for choice chips).
const [openGroup, setOpenGroup] = useState<'cx' | 'cxb' | null>(null);
// The single choice-group whose strip (OFF/WEAK/STRONG, or the DR ladder) is
// open above the chips (same one-row-at-a-time pattern as openParam).
const [openGroup, setOpenGroup] = useState<GroupKey | null>(null);
useEffect(() => {
setOpenParam(null);
setOpenGroup(null);
@@ -85,7 +88,7 @@ export default function AdjustmentPanel({
setOpenParam((prev) => (prev === key ? null : key));
};
const toggleGroup = (g: 'cx' | 'cxb') => {
const toggleGroup = (g: GroupKey) => {
haptic();
setOpenGroup((prev) => (prev === g ? null : g));
};
@@ -210,22 +213,46 @@ export default function AdjustmentPanel({
}
};
// Color-chrome groups: one toggle chip among the WB chips (COLOR CHROME /
// CHROME BLUE); tapping it reveals the OFF/WEAK/STRONG strip above the chip
// row. Amber text while an effect is active, amber border while its strip is
// open — same affordances as the parameter chips.
const chromeGroup: Record<
'cx' | 'cxb',
{ label: string; value: ColorAdjustments['colorChrome']; onChange: (v: ColorAdjustments['colorChrome']) => void }
> = {
cx: { label: 'COLOR CHROME', value: a.colorChrome || 'none', onChange: (v) => onUpdateAdjustments({ colorChrome: v }) },
cxb: { label: 'CHROME BLUE', value: a.colorChromeBlue || 'none', onChange: (v) => onUpdateAdjustments({ colorChromeBlue: v }) },
};
// Choice-groups: one toggle chip sits in the chip row (COLOR CHROME /
// CHROME BLUE among the WB chips, D.RANGE after the LIGHT params); tapping it
// reveals a strip above the chip row with that group's options. Amber text
// while a value differs from the group's off value, amber border while its
// strip is open — same affordances as the parameter chips.
const chromeOptions = [
{ v: 'none', d: 'OFF' },
{ v: 'weak', d: 'WEAK' },
{ v: 'strong', d: 'STRONG' },
];
const drOptions = [
{ v: 'auto', d: 'AUTO' },
{ v: '100', d: 'DR100' },
{ v: '200', d: 'DR200' },
{ v: '400', d: 'DR400' },
];
const groupDefs: Record<GroupKey, { 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'] }),
},
cxb: {
label: 'CHROME BLUE', off: 'none', value: a.colorChromeBlue || 'none', options: chromeOptions,
onPick: (v) => onUpdateAdjustments({ colorChromeBlue: v as ColorAdjustments['colorChromeBlue'] }),
},
dr: {
label: 'D.RANGE', off: 'auto', value: String(a.dynamicRange ?? 'auto'), options: drOptions,
onPick: (v) =>
onUpdateAdjustments({
dynamicRange: (v === 'auto' ? 'auto' : parseInt(v, 10)) as ColorAdjustments['dynamicRange'],
}),
},
};
const groupChip = (g: GroupKey): ChipDef => ({
key: `grp-${g}`,
label: groupDefs[g].label,
active: openGroup === g,
amberValue: openGroup !== g && groupDefs[g].value !== groupDefs[g].off,
onPress: () => toggleGroup(g),
});
// ---- per-tab chip rows (bottom, above the tool rail) -----------------------
const chipsRow = (() => {
@@ -258,36 +285,18 @@ export default function AdjustmentPanel({
}
case 'iq':
// LIGHT: continuous params on one row; D.RANGE is a toggle chip that
// opens its AUTO/DR100/DR200/DR400 strip above (same as the chrome
// groups on WB).
return (
<View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 6 }}
>
{choiceChips(
'dr',
[
{ v: 'auto', d: 'AUTO' },
{ v: '100', d: 'DR100' },
{ v: '200', d: 'DR200' },
{ v: '400', d: 'DR400' },
],
String(a.dynamicRange ?? 'auto'),
(val) =>
onUpdateAdjustments({
dynamicRange: (val === 'auto' ? 'auto' : parseInt(val, 10)) as ColorAdjustments['dynamicRange'],
})
)}
</ScrollView>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 6 }}
>
{paramChips(paramDefs.iq)}
</ScrollView>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 6 }}
>
{paramChips(paramDefs.iq)}
{renderChip(groupChip('dr'))}
</ScrollView>
);
case 'wb':
@@ -313,15 +322,7 @@ export default function AdjustmentPanel({
wbApply
)}
{paramChips(paramDefs.wb)}
{(['cx', 'cxb'] as const).map((g) =>
renderChip({
key: `grp-${g}`,
label: chromeGroup[g].label,
active: openGroup === g,
amberValue: openGroup !== g && chromeGroup[g].value !== 'none',
onPress: () => toggleGroup(g),
})
)}
{(['cx', 'cxb'] as const).map((g) => renderChip(groupChip(g)))}
</ScrollView>
</View>
);
@@ -417,14 +418,12 @@ export default function AdjustmentPanel({
>
{renderChip({
key: `grp-hint-${openGroup}`,
label: chromeGroup[openGroup].label,
label: groupDefs[openGroup].label,
disabled: true,
active: false,
onPress: () => {},
})}
{choiceChips(openGroup, chromeOptions, chromeGroup[openGroup].value, (v) =>
chromeGroup[openGroup].onChange(v as ColorAdjustments['colorChrome'])
)}
{choiceChips(openGroup, groupDefs[openGroup].options, groupDefs[openGroup].value, groupDefs[openGroup].onPick)}
</ScrollView>
</View>
)}
+7 -3
View File
@@ -395,14 +395,18 @@ export default function Viewfinder({
return { dx: (vw - dw) / 2, dy: (vh - dh) / 2, dw, dh };
}, [skiaImage, vw, vh]);
// Polaroid card geometry (JS/overlay space) for the CURRENT mode. Library
// stills keep the photo aspect; camera previews use the screen aspect (the
// feed fills the screen, so the card overlays it in view coordinates).
// stills keep the photo aspect; the camera assumes the upright capture
// aspect of the photo output (4:3 sensor → 3:4 portrait), NOT the screen
// aspect: a screen-aspect window made the card nearly fullscreen and
// pole-like, whereas the real print is a shorter 3:4 card letterboxed on
// screen. ponytail: verified on Xiaomi quality-mode captures (4:3); if the
// session negotiates 16:9 photos, change to 9/16.
const polaroidGeo = useMemo(() => {
if (selectedFrame !== 'polaroid') return null;
const aspect =
mode === 'library' && skiaImage && skiaImage.width() > 0 && skiaImage.height() > 0
? skiaImage.width() / skiaImage.height()
: vw / vh;
: 3 / 4;
return polaroidLayout(vw, vh, aspect, 'contain');
}, [selectedFrame, mode, skiaImage, vw, vh]);
const onLibTouchStart = (e: any) => {