feat: enhance layer masking functionality and brush controls
- Refactor LayersSheet component to support mask editing state and improve layer visibility handling. - Introduce functions to collect mask layer IDs and count display layers excluding masks. - Update BrushControls to include mask view mode options and a Done button for exiting mask editing. - Modify brush session handling to support brush previews when editing masks. - Implement a new BrushPreviewRenderer for rendering brush strokes with visual feedback. - Add document geometry utilities for transforming points and resolving layer bounds. - Ensure proper cleanup of brush preview on pointer leave and other interactions.
This commit is contained in:
@@ -2,13 +2,36 @@ import { commandIds } from "@commands/ids";
|
||||
import type { Dispatch } from "@commands/dispatcher";
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import type { Rect, Vec2D } from "@core/geometry";
|
||||
import type { EditorState } from "@editor/state";
|
||||
import { isPanInteractionMode } from "@editor/tools";
|
||||
import { resolveTransformTargetBounds, selectedTransformTarget } from "@editor/transform-targets";
|
||||
import type { TransformHandle } from "@editor/transform";
|
||||
import {
|
||||
documentRectToViewportRect,
|
||||
resolveTransformTargetBounds,
|
||||
selectedTransformTarget,
|
||||
viewportPointToDocumentPoint,
|
||||
type InputSelectionState,
|
||||
type InputTransformTarget,
|
||||
type InputViewportState,
|
||||
} from "./document-geometry";
|
||||
import { findLayerInfoInDocument } from "./layers-panel";
|
||||
import type { PointerInputEvent } from "./pointer";
|
||||
|
||||
type TransformHandle = "body" | "nw" | "n" | "ne" | "e" | "se" | "s" | "sw" | "w";
|
||||
|
||||
type InputToolId = "select" | "crop" | "brush" | "eraser" | "pan";
|
||||
|
||||
type InputInteractionMode =
|
||||
| { type: "tool"; tool: InputToolId }
|
||||
| { type: "temporary-pan"; previousTool: InputToolId };
|
||||
|
||||
export type TransformControlsEditorState = {
|
||||
viewport: InputViewportState;
|
||||
selection: InputSelectionState;
|
||||
tools: {
|
||||
activeTool: InputToolId;
|
||||
interactionMode: InputInteractionMode;
|
||||
};
|
||||
transformSession?: unknown;
|
||||
};
|
||||
|
||||
export type TransformControlsInputController = {
|
||||
pointerDown(event: PointerInputEvent): boolean;
|
||||
pointerMove(event: PointerInputEvent): boolean;
|
||||
@@ -17,7 +40,7 @@ export type TransformControlsInputController = {
|
||||
|
||||
export function createTransformControlsInputController(options: {
|
||||
getDocument: () => ImageDocument;
|
||||
getEditor: () => EditorState;
|
||||
getEditor: () => TransformControlsEditorState;
|
||||
dispatch: Dispatch;
|
||||
}): TransformControlsInputController {
|
||||
return {
|
||||
@@ -62,7 +85,7 @@ export function createTransformControlsInputController(options: {
|
||||
};
|
||||
}
|
||||
|
||||
export function hitTestArtboardTransformHandle(position: Vec2D, bounds: Rect, viewport: EditorState["viewport"]): TransformHandle | undefined {
|
||||
export function hitTestArtboardTransformHandle(position: Vec2D, bounds: Rect, viewport: InputViewportState): TransformHandle | undefined {
|
||||
const rect = documentRectToViewportRect(bounds, viewport);
|
||||
const handles = transformHandleRects(rect);
|
||||
const handle = handles.find((candidate) => pointInRect(position, candidate.rect));
|
||||
@@ -71,7 +94,7 @@ export function hitTestArtboardTransformHandle(position: Vec2D, bounds: Rect, vi
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isTransformTargetLocked(document: ImageDocument, target: { type: "artboard" | "layer"; id: string }) {
|
||||
function isTransformTargetLocked(document: ImageDocument, target: InputTransformTarget) {
|
||||
if (target.type === "artboard") {
|
||||
const artboard = document.artboards.find((candidate) => candidate.id === target.id);
|
||||
return !artboard || !artboard.visible || artboard.locked;
|
||||
@@ -97,20 +120,8 @@ function transformHandleRects(rect: Rect): { handle: TransformHandle; rect: Rect
|
||||
return points.map(({ handle, point }) => ({ handle, rect: { x: point.x - half, y: point.y - half, w: size, h: size } }));
|
||||
}
|
||||
|
||||
function viewportPointToDocumentPoint(point: Vec2D, viewport: EditorState["viewport"]): Vec2D {
|
||||
return {
|
||||
x: viewport.center.x + (point.x - viewport.size.w / 2) / viewport.zoom,
|
||||
y: viewport.center.y + (point.y - viewport.size.h / 2) / viewport.zoom,
|
||||
};
|
||||
}
|
||||
|
||||
function documentRectToViewportRect(rect: Rect, viewport: EditorState["viewport"]): Rect {
|
||||
return {
|
||||
x: viewport.size.w / 2 + (rect.x - viewport.center.x) * viewport.zoom,
|
||||
y: viewport.size.h / 2 + (rect.y - viewport.center.y) * viewport.zoom,
|
||||
w: rect.w * viewport.zoom,
|
||||
h: rect.h * viewport.zoom,
|
||||
};
|
||||
function isPanInteractionMode(interactionMode: InputInteractionMode): boolean {
|
||||
return interactionMode.type === "temporary-pan" || (interactionMode.type === "tool" && interactionMode.tool === "pan");
|
||||
}
|
||||
|
||||
function pointInRect(point: Vec2D, rect: Rect) {
|
||||
|
||||
Reference in New Issue
Block a user