feat(masks): add mask editing mode

This commit is contained in:
syntaxbullet
2026-07-03 18:07:04 +02:00
parent 4a21b28ed0
commit 15f3a934e0
13 changed files with 211 additions and 20 deletions

View File

@@ -77,7 +77,14 @@ export function App({ app }: AppProps) {
dispatch={app.store.dispatch}
/>
</div>
<LayersSheet document={state.document} selection={state.editor.selection} open={layersOpen} dispatch={app.store.dispatch} onClose={() => setLayersOpen(false)} />
<LayersSheet
document={state.document}
selection={state.editor.selection}
maskEditLayerId={state.editor.maskEditLayerId}
open={layersOpen}
dispatch={app.store.dispatch}
onClose={() => setLayersOpen(false)}
/>
<div className="absolute inset-x-0 bottom-4 z-10 flex justify-center">
<BottomControlsIsland
viewport={state.editor.viewport}

View File

@@ -12,12 +12,13 @@ import { downloadArtboardPng } from "./exportArtboardPng";
export type LayersSheetProps = {
document: ImageDocument;
selection: SelectionState;
maskEditLayerId?: string;
open: boolean;
dispatch: AppStore["dispatch"];
onClose: () => void;
};
export function LayersSheet({ document, selection, open, dispatch, onClose }: LayersSheetProps) {
export function LayersSheet({ document, selection, maskEditLayerId, open, dispatch, onClose }: LayersSheetProps) {
const selectedArtboardId = selection.artboardId ?? document.artboards[0]?.id;
const selectedLayer = findLayerInfoInDocument(document, selection.layerIds[0]);
const canGroup = Boolean(selection.artboardId && selection.layerIds.length > 0);
@@ -137,6 +138,7 @@ export function LayersSheet({ document, selection, open, dispatch, onClose }: La
editingTitle={editingTitle}
setEditingTitle={setEditingTitle}
selectedMaskLayer={selectedLayer}
maskEditLayerId={maskEditLayerId}
dispatch={dispatch}
/>
))
@@ -159,6 +161,7 @@ function LayerRow({
editingTitle,
setEditingTitle,
selectedMaskLayer,
maskEditLayerId,
dispatch,
}: {
document: ImageDocument;
@@ -170,6 +173,7 @@ function LayerRow({
editingTitle: EditingTitle | undefined;
setEditingTitle: (editingTitle: EditingTitle | undefined) => void;
selectedMaskLayer?: LayerInfo;
maskEditLayerId?: string;
dispatch: AppStore["dispatch"];
}) {
const selected = selectedLayerIds.includes(layer.id);
@@ -178,6 +182,7 @@ function LayerRow({
const maskIndent = maskLayer ? 24 : 0;
const canSetMask =
Boolean(selectedMaskLayer && layerInfo && selectedMaskLayer.layer.id !== layer.id && selectedMaskLayer.artboardId === layerInfo.artboardId && selectedMaskLayer.parentGroupId === layerInfo.parentGroupId);
const editingMask = maskEditLayerId === layer.clippingMask?.maskLayerId;
return (
<div>
<div
@@ -244,6 +249,13 @@ function LayerRow({
<div className="mt-1 flex items-center gap-2 text-xs text-sky-100/60" style={{ paddingLeft: 52 + depth * 16 + maskIndent }}>
<span className="h-px w-5 bg-sky-200/25" />
<span>masked by {maskLayer.name}</span>
<button
type="button"
className={`rounded-full px-2 py-0.5 transition ${editingMask ? "bg-sky-300 text-black" : "bg-sky-400/10 text-sky-100/75 hover:bg-sky-400/20 hover:text-sky-50"}`}
onClick={() => dispatch(commandIds.toolSetMaskEditLayer, { layerId: editingMask ? undefined : layer.clippingMask?.maskLayerId })}
>
{editingMask ? "Editing mask" : "Edit mask"}
</button>
</div>
) : null}
{layer.type === "group"
@@ -259,6 +271,7 @@ function LayerRow({
editingTitle={editingTitle}
setEditingTitle={setEditingTitle}
selectedMaskLayer={selectedMaskLayer}
maskEditLayerId={maskEditLayerId}
dispatch={dispatch}
/>
))

View File

@@ -15,7 +15,7 @@ export type BrushSession = {
export function beginBrushSession(document: ImageDocument, editor: EditorState, point: Vec2D): BrushSession | undefined {
if (isPanInteractionMode(editor.tools.interactionMode) || (editor.tools.activeTool !== "brush" && editor.tools.activeTool !== "eraser")) return undefined;
const layerId = editor.selection.layerIds[0];
const layerId = editor.maskEditLayerId ?? editor.selection.layerIds[0];
if (!layerId) return undefined;
const layer = findRasterLayer(document.artboards.flatMap((artboard) => artboard.layers), layerId);
if (!layer || layer.locked || !layer.visible) return undefined;
@@ -43,7 +43,7 @@ export async function updateBrushSession(options: {
height: asset.intrinsicSize.h,
from: documentPointToAssetPoint(options.session.previousPoint, layer, asset.intrinsicSize.w, asset.intrinsicSize.h),
to: documentPointToAssetPoint(options.point, layer, asset.intrinsicSize.w, asset.intrinsicSize.h),
color: options.color,
color: state.editor.maskEditLayerId ? "#ffffff" : options.color,
size: options.size,
hardness: options.hardness,
mode: options.session.mode,