- Introduced `getLayerMask` utility to streamline layer mask retrieval. - Updated layer rendering logic to incorporate layer masks and opacity adjustments. - Added functionality to prevent dropping a group into its descendants. - Enhanced image texture rendering to support opacity parameters across various rendering functions. - Implemented group layer bounds application for better scaling and positioning. - Added tests to ensure correct behavior when handling layer masks and group layers. - Created new types for asset generation provenance to track generated assets more effectively.
75 lines
3.0 KiB
TypeScript
75 lines
3.0 KiB
TypeScript
import { commandIds } from "@commands/ids";
|
|
import type { Dispatch } from "@commands/dispatcher";
|
|
import type { ImageDocument } from "@core/document";
|
|
import type { Layer } from "@core/layer";
|
|
import { getLayerMask } from "@core/layer-mask-utils";
|
|
import { resolveTransformTargetBounds, viewportPointToDocumentPoint, type InputViewportState } from "./document-geometry";
|
|
import type { PointerInputEvent } from "./pointer";
|
|
|
|
export function handleArtboardSelection(options: {
|
|
event: PointerInputEvent;
|
|
document: ImageDocument;
|
|
viewport: InputViewportState;
|
|
dispatch: Dispatch;
|
|
}): boolean {
|
|
if (options.event.pointerType !== "mouse" || (options.event.buttons & 1) !== 1) return false;
|
|
|
|
const point = viewportPointToDocumentPoint(options.event.position, options.viewport);
|
|
const layerHit = findTopmostLayerAtPoint(options.document, point);
|
|
if (layerHit) {
|
|
options.dispatch(commandIds.selectionSet, { artboardId: layerHit.artboardId, layerIds: [layerHit.layerId] });
|
|
return true;
|
|
}
|
|
|
|
const artboard = [...options.document.artboards].reverse().find((candidate) => {
|
|
if (!candidate.visible || candidate.locked) return false;
|
|
const bounds = candidate.bounds;
|
|
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
});
|
|
|
|
if (!artboard) {
|
|
options.dispatch(commandIds.selectionClear, undefined);
|
|
return true;
|
|
}
|
|
|
|
options.dispatch(commandIds.selectionSet, { artboardId: artboard.id, layerIds: [] });
|
|
return true;
|
|
}
|
|
|
|
function findTopmostLayerAtPoint(document: ImageDocument, point: { x: number; y: number }) {
|
|
const maskLayerIds = collectMaskLayerIds(document.artboards.flatMap((artboard) => artboard.layers));
|
|
for (const artboard of [...document.artboards].reverse()) {
|
|
if (!artboard.visible || artboard.locked) continue;
|
|
const layerId = findTopmostLayerInTreeAtPoint(document, [...artboard.layers].reverse(), point, maskLayerIds);
|
|
if (layerId) return { artboardId: artboard.id, layerId };
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function findTopmostLayerInTreeAtPoint(document: ImageDocument, layers: Layer[], point: { x: number; y: number }, maskLayerIds: ReadonlySet<string>): string | undefined {
|
|
for (const layer of layers) {
|
|
if (!layer.visible || layer.locked || maskLayerIds.has(layer.id)) continue;
|
|
if (layer.type === "group") {
|
|
const childId = findTopmostLayerInTreeAtPoint(document, [...layer.children].reverse(), point, maskLayerIds);
|
|
if (childId) return childId;
|
|
}
|
|
|
|
const bounds = resolveTransformTargetBounds(document, { type: "layer", id: layer.id });
|
|
if (bounds && point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h) {
|
|
return layer.id;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function collectMaskLayerIds(layers: readonly Layer[], ids = new Set<string>()): Set<string> {
|
|
for (const layer of layers) {
|
|
const layerMask = getLayerMask(layer);
|
|
if (layerMask) ids.add(layerMask.maskLayerId);
|
|
if (layer.type === "group") collectMaskLayerIds(layer.children, ids);
|
|
}
|
|
return ids;
|
|
}
|