Files
Genarrative/packages/image-canvas-core/src/layerCommands.ts
T
menghao 03a4410272
Project CI / Native shell tests (push) Failing after 11m56s
Project CI / Repository checks (push) Successful in 1m5s
Project CI / Frontend tests (push) Successful in 3m20s
Project CI / Backend tests (push) Successful in 4m1s
game agent无限画布开发 (#136)
主要工作是共享同一套“画布内核与通用 UI 源码”,网站和 Tauri 只分别实现宿主适配层。

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Co-authored-by: kdletters <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/136
Co-authored-by: menghao <mh18530625731@163.com>
Co-committed-by: menghao <mh18530625731@163.com>
2026-08-12 10:54:16 +08:00

260 lines
6.3 KiB
TypeScript

import { getCanvasGenerationSelectionId } from './selection';
import type {
CanvasClipboard,
CanvasContextMenuState,
CanvasLayer,
} from './types';
export type CanvasLayerMoveMode = 'up' | 'down' | 'top' | 'bottom';
export type CanvasLayerFlipAxis = 'x' | 'y';
export type CanvasLayerCopyBlockReason =
| 'pending-resource'
| 'self-contained-local-unsupported'
| 'unresolved-resource';
type CanvasPoint = {
x: number;
y: number;
};
function cloneLayer(layer: CanvasLayer): CanvasLayer {
return {
...layer,
generationInputs: layer.generationInputs
? {
...layer.generationInputs,
fields: layer.generationInputs.fields.map((field) => ({ ...field })),
references: layer.generationInputs.references.map((reference) => ({
...reference,
})),
...(layer.generationInputs.soundEffect
? { soundEffect: { ...layer.generationInputs.soundEffect } }
: {}),
}
: layer.generationInputs,
};
}
export function resolveContextTargetLayerIds(
menu: CanvasContextMenuState | null,
selectedLayerIds: string[],
) {
if (!menu || menu.kind === 'blank') {
return [];
}
const targetId =
menu.kind === 'generation-dialog'
? getCanvasGenerationSelectionId(menu.dialogId)
: menu.layerId;
return selectedLayerIds.includes(targetId)
? [...selectedLayerIds]
: [targetId];
}
export function getCanvasLayersByIds(
layers: CanvasLayer[],
targetIds: string[],
) {
return layers.filter((layer) => targetIds.includes(layer.id));
}
export function getCanvasLayerCopyBlockReason(
layers: CanvasLayer[],
): CanvasLayerCopyBlockReason | null {
for (const layer of layers) {
if (layer.resourcePersistenceState === 'registered') {
continue;
}
if (layer.resourcePersistenceState === 'pending') {
return 'pending-resource';
}
if (layer.resourcePersistenceState === 'self-contained-local') {
return 'self-contained-local-unsupported';
}
if (
layer.resourcePersistenceState === 'unresolved-local' ||
layer.resourceId.startsWith('local-')
) {
return 'unresolved-resource';
}
}
return null;
}
export function canCopyCanvasLayers(layers: CanvasLayer[]) {
return layers.length > 0 && getCanvasLayerCopyBlockReason(layers) === null;
}
export function duplicateCanvasLayers({
sourceLayers,
allLayers,
canvasPoint,
renameCopies = true,
stamp = Date.now(),
}: {
sourceLayers: CanvasLayer[];
allLayers: CanvasLayer[];
canvasPoint?: CanvasPoint;
renameCopies?: boolean;
stamp?: number | string;
}) {
if (!canCopyCanvasLayers(sourceLayers)) {
return [];
}
const minX = Math.min(...sourceLayers.map((layer) => layer.x));
const minY = Math.min(...sourceLayers.map((layer) => layer.y));
const maxZIndex = allLayers.reduce(
(maxZ, layer) => Math.max(maxZ, layer.zIndex),
0,
);
return sourceLayers.map((layer, index) => ({
...cloneLayer(layer),
id: `layer-copy-${stamp}-${index}`,
title: renameCopies ? `${layer.title} 副本` : layer.title,
x: canvasPoint ? canvasPoint.x + (layer.x - minX) : layer.x + 32,
y: canvasPoint ? canvasPoint.y + (layer.y - minY) : layer.y + 32,
zIndex: maxZIndex + index + 1,
groupId: null,
}));
}
export function createCanvasLayerClipboard(
layers: CanvasLayer[],
targetIds: string[],
mode: CanvasClipboard['mode'],
): CanvasClipboard | null {
const targetLayers = getCanvasLayersByIds(layers, targetIds);
if (!canCopyCanvasLayers(targetLayers)) {
return null;
}
return {
layers: targetLayers.map(cloneLayer),
mode,
};
}
export function removeCanvasLayers(layers: CanvasLayer[], targetIds: string[]) {
if (!targetIds.length) {
return layers;
}
return layers.filter((layer) => !targetIds.includes(layer.id));
}
export function updateCanvasLayersByIds(
layers: CanvasLayer[],
targetIds: string[],
updater: (layer: CanvasLayer, targetIds: string[]) => CanvasLayer,
) {
if (!targetIds.length) {
return layers;
}
return layers.map((layer) =>
targetIds.includes(layer.id) ? updater(layer, targetIds) : layer,
);
}
export function moveCanvasLayers(
layers: CanvasLayer[],
targetIds: string[],
mode: CanvasLayerMoveMode,
) {
if (!targetIds.length) {
return layers;
}
const maxZIndex = layers.reduce(
(maxZ, layer) => Math.max(maxZ, layer.zIndex),
0,
);
const minZIndex = layers.reduce(
(minZ, layer) => Math.min(minZ, layer.zIndex),
0,
);
let offsetIndex = 0;
return updateCanvasLayersByIds(layers, targetIds, (layer) => {
if (mode === 'up') {
return { ...layer, zIndex: layer.zIndex + 1 };
}
if (mode === 'down') {
return { ...layer, zIndex: layer.zIndex - 1 };
}
offsetIndex += 1;
if (mode === 'top') {
return { ...layer, zIndex: maxZIndex + offsetIndex };
}
return {
...layer,
zIndex: minZIndex - (targetIds.length - offsetIndex + 1),
};
});
}
export function groupCanvasLayers(
layers: CanvasLayer[],
targetIds: string[],
groupId: string,
) {
return updateCanvasLayersByIds(layers, targetIds, (layer) => ({
...layer,
groupId,
}));
}
export function ungroupCanvasLayers(
layers: CanvasLayer[],
targetIds: string[],
) {
return updateCanvasLayersByIds(layers, targetIds, (layer) => ({
...layer,
groupId: null,
}));
}
export function toggleCanvasLayersVisibility(
layers: CanvasLayer[],
targetIds: string[],
) {
const shouldHide = getCanvasLayersByIds(layers, targetIds).some(
(layer) => !layer.hidden,
);
return updateCanvasLayersByIds(layers, targetIds, (layer) => ({
...layer,
hidden: shouldHide,
}));
}
export function toggleCanvasLayersLock(
layers: CanvasLayer[],
targetIds: string[],
) {
const shouldLock = getCanvasLayersByIds(layers, targetIds).some(
(layer) => !layer.locked,
);
return updateCanvasLayersByIds(layers, targetIds, (layer) => ({
...layer,
locked: shouldLock,
}));
}
export function flipCanvasLayers(
layers: CanvasLayer[],
targetIds: string[],
axis: CanvasLayerFlipAxis,
) {
return updateCanvasLayersByIds(layers, targetIds, (layer) =>
axis === 'x'
? {
...layer,
flipX: !layer.flipX,
}
: {
...layer,
flipY: !layer.flipY,
},
);
}