Files
image-studio/view/bottom-controls/ChromaKeyControls.tsx
syntaxbullet 5915c62a9a refactor: Update bottom controls for improved styling and functionality
- Adjusted button styles across various components for consistency and better UX.
- Enhanced layout of action controls to utilize whitespace more effectively.
- Updated slider styles for a more modern appearance and improved usability.
- Refined input fields and labels for better accessibility and readability.
- Introduced new app surface styles for a cohesive design across the application.
- Added tests for canvas cursor behavior to ensure correct cursor display during operations.
2026-07-11 14:55:32 +02:00

153 lines
6.6 KiB
TypeScript

import { useEffect } from "react";
import { DropHalf } from "@phosphor-icons/react";
import { commandIds } from "@commands/ids";
import type { ImageDocument } from "@core/document";
import type { AppStore } from "@editor/store";
import type { ChromaKeySettings } from "@editor/tools";
import type { SelectionState } from "@editor/state";
import { applyChromaKeyMask, previewChromaKey, resolveChromaKeyTarget } from "@operations/masks/chromaKey";
import { BottomControlColorPicker } from "./ColorPicker";
import { BottomControlDivider } from "./Divider";
import { BottomControlSlider } from "./Slider";
import { bottomControlFieldClass, bottomControlIconSlotClass, bottomControlLabelClass, bottomControlMenuClass } from "./styles";
export type ChromaKeyControlsProps = {
document: ImageDocument;
selection: SelectionState;
settings: ChromaKeySettings;
dispatch: AppStore["dispatch"];
};
export function ChromaKeyControls({ document, selection, settings, dispatch }: ChromaKeyControlsProps) {
const target = resolveChromaKeyTarget(document, selection);
useEffect(() => {
return () => {
dispatch(commandIds.toolSetBrushStrokePreview, undefined);
};
}, [dispatch]);
useEffect(() => {
let cancelled = false;
if (!target) {
dispatch(commandIds.toolSetBrushStrokePreview, undefined);
return;
}
void previewChromaKey(target.asset.source, target.asset.intrinsicSize.w, target.asset.intrinsicSize.h, settings).then((source) => {
if (cancelled) return;
dispatch(commandIds.toolSetBrushStrokePreview, { layerId: target.layer.id, assetId: target.asset.id, source });
});
return () => {
cancelled = true;
};
}, [dispatch, settings.choke, settings.color, settings.despeckle, settings.feather, settings.softness, settings.spill, settings.tolerance, target?.asset.id, target?.asset.source, target?.asset.intrinsicSize.w, target?.asset.intrinsicSize.h, target?.layer.id]);
return (
<div className={bottomControlMenuClass()}>
<span className={bottomControlIconSlotClass()} title="Chroma key">
<DropHalf size={24} weight="regular" />
</span>
<BottomControlDivider />
<label className={bottomControlFieldClass()}>
<span className={bottomControlLabelClass()}>Key</span>
<BottomControlColorPicker
value={settings.color}
aria-label="Chroma key color"
onValueChange={(color) => dispatch(commandIds.toolSetChromaKeySettings, { color })}
/>
</label>
<BottomControlDivider />
<label className={bottomControlFieldClass()}>
<span className={bottomControlLabelClass()}>Tol</span>
<BottomControlSlider
min={0}
max={255}
value={settings.tolerance}
className="min-w-7 w-[clamp(1.75rem,5vw,4.5rem)] shrink"
aria-label="Chroma key tolerance"
onValueChange={(tolerance) => dispatch(commandIds.toolSetChromaKeySettings, { tolerance })}
/>
<span className="w-8 text-right text-xs font-medium text-white/85">{Math.round(settings.tolerance)}</span>
</label>
<BottomControlDivider />
<label className={bottomControlFieldClass()}>
<span className={bottomControlLabelClass()}>Soft</span>
<BottomControlSlider
min={0}
max={255}
value={settings.softness}
className="min-w-7 w-[clamp(1.75rem,5vw,4.5rem)] shrink"
aria-label="Chroma key edge softness"
onValueChange={(softness) => dispatch(commandIds.toolSetChromaKeySettings, { softness })}
/>
<span className="w-8 text-right text-xs font-medium text-white/85">{Math.round(settings.softness)}</span>
</label>
<BottomControlDivider />
<label className={bottomControlFieldClass()}>
<span className={bottomControlLabelClass()}>Feather</span>
<BottomControlSlider
min={0}
max={20}
value={settings.feather}
className="min-w-7 w-[clamp(1.75rem,5vw,4rem)] shrink"
aria-label="Chroma key mask feather"
onValueChange={(feather) => dispatch(commandIds.toolSetChromaKeySettings, { feather })}
/>
<span className="w-7 text-right text-xs font-medium text-white/85">{Math.round(settings.feather)}</span>
</label>
<BottomControlDivider />
<label className={bottomControlFieldClass()}>
<span className={bottomControlLabelClass()}>Choke</span>
<BottomControlSlider
min={-20}
max={20}
value={settings.choke}
className="min-w-7 w-[clamp(1.75rem,5vw,4rem)] shrink"
aria-label="Chroma key mask choke or expand"
onValueChange={(choke) => dispatch(commandIds.toolSetChromaKeySettings, { choke })}
/>
<span className="w-7 text-right text-xs font-medium text-white/85">{Math.round(settings.choke)}</span>
</label>
<BottomControlDivider />
<label className={bottomControlFieldClass()}>
<span className={bottomControlLabelClass()}>Clean</span>
<BottomControlSlider
min={0}
max={20}
value={settings.despeckle}
className="min-w-7 w-[clamp(1.75rem,5vw,4rem)] shrink"
aria-label="Chroma key mask despeckle"
onValueChange={(despeckle) => dispatch(commandIds.toolSetChromaKeySettings, { despeckle })}
/>
<span className="w-7 text-right text-xs font-medium text-white/85">{Math.round(settings.despeckle)}</span>
</label>
<BottomControlDivider />
<label className={bottomControlFieldClass()}>
<span className={bottomControlLabelClass()}>Spill</span>
<BottomControlSlider
min={0}
max={100}
value={settings.spill}
className="min-w-7 w-[clamp(1.75rem,5vw,4.5rem)] shrink"
aria-label="Chroma key spill suppression"
onValueChange={(spill) => dispatch(commandIds.toolSetChromaKeySettings, { spill })}
/>
<span className="w-8 text-right text-xs font-medium text-white/85">{Math.round(settings.spill)}</span>
</label>
<BottomControlDivider />
<button
type="button"
className="rounded-md bg-sky-300 px-4 py-1.5 text-xs font-semibold text-slate-950 transition hover:bg-sky-200 disabled:pointer-events-none disabled:opacity-35"
disabled={!target}
title={target ? "Create or update a layer mask from the chroma key" : "Select one image or raster layer to create a chroma key mask"}
onClick={() => target && void applyChromaKeyMask(target, settings, dispatch)}
>
{target?.maskAsset ? "Update Mask" : "Create Mask"}
</button>
</div>
);
}