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

@@ -23,6 +23,7 @@ export const commandIds = {
selectionAddLayer: "selection.addLayer",
toolSetActive: "tool.setActive",
toolSetBrushSettings: "tool.setBrushSettings",
toolSetMaskEditLayer: "tool.setMaskEditLayer",
toolEnterTemporaryPan: "tool.enterTemporaryPan",
toolExitTemporaryPan: "tool.exitTemporaryPan",
transformBegin: "transform.begin",

View File

@@ -50,10 +50,10 @@ export type { CommandRegistry } from "./registry";
export { createCommandRegistry } from "./registry";
export { selectionAddLayerCommand, selectionClearCommand, selectionCommands, selectionSetCommand } from "./selection";
export type { SelectionAddLayerPayload, SelectionSetPayload } from "./selection";
export { toolCommands, toolEnterTemporaryPanCommand, toolExitTemporaryPanCommand, toolSetActiveCommand, toolSetBrushSettingsCommand } from "./tool";
export { toolCommands, toolEnterTemporaryPanCommand, toolExitTemporaryPanCommand, toolSetActiveCommand, toolSetBrushSettingsCommand, toolSetMaskEditLayerCommand } from "./tool";
export { transformBeginCommand, transformCommands, transformEndCommand, transformSetBoundsCommand, transformUpdateCommand } from "./transform";
export type { TransformBeginPayload, TransformSetBoundsPayload, TransformUpdatePayload } from "./transform";
export type { ToolSetActivePayload, ToolSetBrushSettingsPayload } from "./tool";
export type { ToolSetActivePayload, ToolSetBrushSettingsPayload, ToolSetMaskEditLayerPayload } from "./tool";
export {
viewportCommands,
viewportPanCommand,

View File

@@ -21,7 +21,7 @@ import type {
DocumentUngroupLayerPayload,
} from "./document";
import type { SelectionAddLayerPayload, SelectionSetPayload } from "./selection";
import type { ToolSetActivePayload, ToolSetBrushSettingsPayload } from "./tool";
import type { ToolSetActivePayload, ToolSetBrushSettingsPayload, ToolSetMaskEditLayerPayload } from "./tool";
import type { TransformBeginPayload, TransformSetBoundsPayload, TransformUpdatePayload } from "./transform";
import type {
ViewportFitArtboardPayload,
@@ -56,6 +56,7 @@ export type CommandPayloads = {
[commandIds.selectionAddLayer]: SelectionAddLayerPayload;
[commandIds.toolSetActive]: ToolSetActivePayload;
[commandIds.toolSetBrushSettings]: ToolSetBrushSettingsPayload;
[commandIds.toolSetMaskEditLayer]: ToolSetMaskEditLayerPayload;
[commandIds.toolEnterTemporaryPan]: void;
[commandIds.toolExitTemporaryPan]: void;
[commandIds.transformBegin]: TransformBeginPayload;

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import { createInitialAppState } from "@editor/initial-state";
import { toolEnterTemporaryPanCommand, toolExitTemporaryPanCommand, toolSetActiveCommand, toolSetBrushSettingsCommand } from "./tool";
import { toolEnterTemporaryPanCommand, toolExitTemporaryPanCommand, toolSetActiveCommand, toolSetBrushSettingsCommand, toolSetMaskEditLayerCommand } from "./tool";
describe("tool commands", () => {
test("sets active tool", () => {
@@ -14,6 +14,12 @@ describe("tool commands", () => {
expect(next.editor.tools.brush).toEqual({ color: "#ff0000", size: 24, hardness: 50 });
});
test("sets mask edit layer", () => {
const next = toolSetMaskEditLayerCommand.execute({ state: createInitialAppState("Test") }, { layerId: "mask-1" });
expect(next.editor.maskEditLayerId).toBe("mask-1");
});
test("enters and exits temporary pan", () => {
const initial = createInitialAppState("Test");
const panning = toolEnterTemporaryPanCommand.execute({ state: initial }, undefined);

View File

@@ -1,3 +1,4 @@
import type { LayerId } from "@core/id";
import type { BrushSettings, ToolId } from "@editor/tools";
import type { Command } from "./command";
import { commandIds } from "./ids";
@@ -8,6 +9,10 @@ export type ToolSetActivePayload = {
export type ToolSetBrushSettingsPayload = Partial<BrushSettings>;
export type ToolSetMaskEditLayerPayload = {
layerId?: LayerId;
};
export const toolSetActiveCommand: Command<ToolSetActivePayload> = {
id: commandIds.toolSetActive,
name: "Set active tool",
@@ -47,6 +52,20 @@ export const toolSetBrushSettingsCommand: Command<ToolSetBrushSettingsPayload> =
},
};
export const toolSetMaskEditLayerCommand: Command<ToolSetMaskEditLayerPayload> = {
id: commandIds.toolSetMaskEditLayer,
name: "Set mask edit layer",
execute({ state }, payload) {
return {
...state,
editor: {
...state.editor,
maskEditLayerId: payload.layerId,
},
};
},
};
export const toolEnterTemporaryPanCommand: Command = {
id: commandIds.toolEnterTemporaryPan,
name: "Enter temporary pan",
@@ -87,7 +106,7 @@ export const toolExitTemporaryPanCommand: Command = {
},
};
export const toolCommands = [toolSetActiveCommand, toolSetBrushSettingsCommand, toolEnterTemporaryPanCommand, toolExitTemporaryPanCommand] satisfies Command<unknown>[];
export const toolCommands = [toolSetActiveCommand, toolSetBrushSettingsCommand, toolSetMaskEditLayerCommand, toolEnterTemporaryPanCommand, toolExitTemporaryPanCommand] satisfies Command<unknown>[];
function clampNumber(value: number, min: number, max: number) {
if (!Number.isFinite(value)) return min;