Files
image-studio/input/layers-panel.ts
syntaxbullet 5eaf37ba28 feat: enhance layer mask handling and opacity management
- 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.
2026-07-05 16:06:23 +02:00

109 lines
4.3 KiB
TypeScript

import { commandIds } from "@commands/ids";
import type { Dispatch } from "@commands/dispatcher";
import type { ImageDocument } from "@core/document";
import type { ArtboardId, LayerId } from "@core/id";
import type { Layer } from "@core/layer";
import type { KeybindEvent } from "./keyboard";
export type LayerInfo = {
artboardId: ArtboardId;
parentGroupId?: LayerId;
layer: Layer;
};
export type LayerDropTarget = {
artboardId: ArtboardId;
layer: Layer;
};
export type DeleteSelectionState = {
artboardId?: ArtboardId;
layerIds: LayerId[];
};
export function handleDeleteSelectionKey(options: { event: KeybindEvent; selection: DeleteSelectionState; dispatch: Dispatch }): boolean {
if (options.event.altKey || options.event.ctrlKey || options.event.metaKey) return false;
if (options.event.key !== "Backspace" && options.event.key !== "Delete") return false;
for (const layerId of options.selection.layerIds) options.dispatch(commandIds.documentRemoveLayer, { layerId });
if (options.selection.layerIds.length === 0 && options.selection.artboardId) {
options.dispatch(commandIds.documentRemoveArtboard, { id: options.selection.artboardId });
}
return true;
}
export function resolveLayerDrop(options: {
document: ImageDocument;
sourceLayerId: LayerId;
target: LayerDropTarget;
verticalRatio: number;
}): { layerId: LayerId; toArtboardId: ArtboardId; toParentGroupId?: LayerId; toIndex: number } | undefined {
const targetInfo = findLayerInfoInDocument(options.document, options.target.layer.id);
const sourceInfo = findLayerInfoInDocument(options.document, options.sourceLayerId);
if (!targetInfo || !sourceInfo || options.sourceLayerId === options.target.layer.id) return undefined;
if (isDescendantLayer(sourceInfo.layer, options.target.layer.id)) return undefined;
const verticalRatio = Math.max(0, Math.min(1, options.verticalRatio));
const dropIntoGroup = options.target.layer.type === "group" && verticalRatio >= 0.33 && verticalRatio <= 0.66;
if (dropIntoGroup) {
return {
layerId: options.sourceLayerId,
toArtboardId: options.target.artboardId,
toParentGroupId: options.target.layer.id,
toIndex: options.target.layer.children.length,
};
}
const siblings = targetInfo.parentGroupId
? findGroup(options.document, targetInfo.parentGroupId)?.children
: options.document.artboards.find((artboard) => artboard.id === targetInfo.artboardId)?.layers;
if (!siblings) return undefined;
const targetIndex = siblings.findIndex((layer) => layer.id === options.target.layer.id);
const sourceIndex = sourceInfo.parentGroupId === targetInfo.parentGroupId && sourceInfo.artboardId === targetInfo.artboardId
? siblings.findIndex((layer) => layer.id === options.sourceLayerId)
: -1;
const rawIndex = verticalRatio < 0.5 ? targetIndex : targetIndex + 1;
const adjustedIndex = sourceIndex >= 0 && sourceIndex < rawIndex ? rawIndex - 1 : rawIndex;
return {
layerId: options.sourceLayerId,
toArtboardId: targetInfo.artboardId,
toParentGroupId: targetInfo.parentGroupId,
toIndex: adjustedIndex,
};
}
export function findLayerInfoInDocument(document: ImageDocument, layerId?: LayerId): LayerInfo | undefined {
if (!layerId) return undefined;
for (const artboard of document.artboards) {
const found = findLayerInfo(artboard.layers, layerId, artboard.id);
if (found) return found;
}
return undefined;
}
export function findGroup(document: ImageDocument, groupId: LayerId): Extract<Layer, { type: "group" }> | undefined {
const info = findLayerInfoInDocument(document, groupId);
return info?.layer.type === "group" ? info.layer : undefined;
}
function findLayerInfo(layers: Layer[], layerId: LayerId, artboardId: ArtboardId, parentGroupId?: LayerId): LayerInfo | undefined {
for (const layer of layers) {
if (layer.id === layerId) return { artboardId, parentGroupId, layer };
if (layer.type === "group") {
const found = findLayerInfo(layer.children, layerId, artboardId, layer.id);
if (found) return found;
}
}
return undefined;
}
function isDescendantLayer(layer: Layer, descendantLayerId: LayerId): boolean {
if (layer.type !== "group") return false;
for (const child of layer.children) {
if (child.id === descendantLayerId || isDescendantLayer(child, descendantLayerId)) return true;
}
return false;
}