Files
Genarrative/src/components/image-editor/ImageCanvasOverlayModel.ts
T
suzmii 72f268e088
Project CI / Repository checks (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Feat/游戏场景需求v1.0 (#139)
实现[游戏场景需求v1.0](https://kcnz41bksl1c.feishu.cn/wiki/JSF3wdhduinpqhkrVGKcZFp4nng?psg_id=8477599259997387860&refer_index=1&refer_type=citation)。

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Co-authored-by: suzmii <suzmii@foxmail.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/139
Co-authored-by: 董羽秦 <suzmii@qq.com>
Co-committed-by: 董羽秦 <suzmii@qq.com>
2026-08-08 10:25:42 +08:00

220 lines
5.2 KiB
TypeScript

import type {
CanvasGenerationDialogMode,
CanvasGenerationDialogState,
CanvasLayer,
CanvasViewport,
CharacterAnimationPanelState,
CropExpandPanelState,
GenerateDialogState,
QuickEditPanelState,
} from './ImageCanvasEditorTypes';
type CanvasSize = {
width: number;
height: number;
};
type OverlayAnchor = Pick<CanvasLayer, 'x' | 'y' | 'width' | 'height'>;
const QUICK_EDIT_FOCUS_HORIZONTAL_MARGIN = 56;
const QUICK_EDIT_FOCUS_SOURCE_TOP = 56;
const QUICK_EDIT_PANEL_GAP = 12;
const QUICK_EDIT_PANEL_ESTIMATED_HEIGHT = 260;
const QUICK_EDIT_PANEL_BOTTOM_MARGIN = 4;
export type CanvasOverlayStyle = {
left: number;
top: number;
};
export type CanvasComposerOverlayStyle = CanvasOverlayStyle & {
width?: string;
};
export function resolveGenerationAnchor({
dialog,
generatedLayer,
}: {
dialog: CanvasGenerationDialogState | null;
generatedLayer: CanvasLayer | null;
}) {
if (!dialog) {
return null;
}
return generatedLayer ?? dialog.placeholder ?? null;
}
export function resolveGenerationComposerStyle({
dialog,
anchor,
viewport,
}: {
dialog: CanvasGenerationDialogState | null;
anchor: OverlayAnchor | null;
viewport: CanvasViewport;
}): CanvasOverlayStyle | null {
if (
!dialog ||
dialog.status === 'generating' ||
dialog.composerOpen === false ||
!anchor
) {
return null;
}
return {
left: viewport.x + (anchor.x + anchor.width / 2) * viewport.scale,
top: viewport.y + (anchor.y + anchor.height) * viewport.scale + 10,
};
}
export function resolveIconComposerStyle({
dialog,
composerStyle,
}: {
dialog: CanvasGenerationDialogState | null;
composerStyle: CanvasOverlayStyle | null;
}): CanvasComposerOverlayStyle | null {
if (dialog?.mode !== 'icon' || !composerStyle) {
return null;
}
return composerStyle;
}
export function resolveSelectedToolbarStyle({
selectedLayer,
viewport,
}: {
selectedLayer: CanvasLayer | null;
viewport: CanvasViewport;
canvasSize: CanvasSize;
}): CanvasOverlayStyle | null {
if (!selectedLayer) {
return null;
}
return {
left:
viewport.x +
selectedLayer.x * viewport.scale +
(selectedLayer.width * viewport.scale) / 2,
top: Math.max(10, viewport.y + selectedLayer.y * viewport.scale - 12),
};
}
export function resolveQuickEditFocusViewport({
sourceLayer,
canvasSize,
}: {
sourceLayer: CanvasLayer;
canvasSize: CanvasSize;
}): CanvasViewport {
const safeSourceWidth = Math.max(1, sourceLayer.width);
const safeSourceHeight = Math.max(1, sourceLayer.height);
const availableWidth = Math.max(
1,
canvasSize.width - QUICK_EDIT_FOCUS_HORIZONTAL_MARGIN * 2,
);
const availableHeight = Math.max(
1,
canvasSize.height -
QUICK_EDIT_FOCUS_SOURCE_TOP -
QUICK_EDIT_PANEL_GAP -
QUICK_EDIT_PANEL_ESTIMATED_HEIGHT -
QUICK_EDIT_PANEL_BOTTOM_MARGIN,
);
const scale = Math.min(
1,
availableWidth / safeSourceWidth,
availableHeight / safeSourceHeight,
);
return {
scale,
x:
canvasSize.width / 2 -
(sourceLayer.x + sourceLayer.width / 2) * scale,
y: QUICK_EDIT_FOCUS_SOURCE_TOP - sourceLayer.y * scale,
};
}
export function resolveQuickEditPanelStyle({
panel,
sourceLayer,
viewport,
}: {
panel: QuickEditPanelState | null;
sourceLayer: CanvasLayer | null;
viewport: CanvasViewport;
canvasSize: CanvasSize;
}): CanvasOverlayStyle | null {
if (!panel || !sourceLayer) {
return null;
}
return {
left:
viewport.x + (sourceLayer.x + sourceLayer.width / 2) * viewport.scale,
top:
viewport.y +
(sourceLayer.y + sourceLayer.height) * viewport.scale +
QUICK_EDIT_PANEL_GAP,
};
}
export function resolveCropExpandPanelStyle({
panel,
sourceLayer,
viewport,
}: {
panel: CropExpandPanelState | null;
sourceLayer: CanvasLayer | null;
viewport: CanvasViewport;
canvasSize: CanvasSize;
}): CanvasOverlayStyle | null {
if (!panel || !sourceLayer) {
return null;
}
return {
left:
viewport.x + (sourceLayer.x + sourceLayer.width) * viewport.scale + 12,
top: viewport.y + sourceLayer.y * viewport.scale,
};
}
export function resolveCharacterAnimationPanelStyle({
panel,
sourceLayer,
viewport,
}: {
panel: CharacterAnimationPanelState | null;
sourceLayer: CanvasLayer | null;
viewport: CanvasViewport;
canvasSize: CanvasSize;
}): CanvasOverlayStyle | null {
if (!panel || !sourceLayer) {
return null;
}
return {
left:
viewport.x + (sourceLayer.x + sourceLayer.width) * viewport.scale + 12,
top: viewport.y + sourceLayer.y * viewport.scale,
};
}
export function isCanvasGenerationComposerVisible(
dialog: GenerateDialogState | null,
): dialog is GenerateDialogState & { mode: CanvasGenerationDialogMode } {
return (
dialog?.mode === 'generate' ||
dialog?.mode === 'scene' ||
dialog?.mode === 'spec' ||
dialog?.mode === 'character' ||
dialog?.mode === 'icon' ||
dialog?.mode === 'ui-design' ||
dialog?.mode === 'quick-edit' ||
dialog?.mode === 'publication' ||
dialog?.mode === 'character-animation' ||
dialog?.mode === 'video' ||
dialog?.mode === 'audio-sound-effect' ||
dialog?.mode === 'audio-background-music'
);
}