03a4410272
主要工作是共享同一套“画布内核与通用 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>
217 lines
5.1 KiB
TypeScript
217 lines
5.1 KiB
TypeScript
import type {
|
|
CanvasGenerationDialogMode,
|
|
CanvasGenerationDialogState,
|
|
CanvasLayer,
|
|
CanvasViewport,
|
|
CharacterAnimationPanelState,
|
|
CropExpandPanelState,
|
|
GenerateDialogState,
|
|
QuickEditPanelState,
|
|
} from './types';
|
|
|
|
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'
|
|
);
|
|
}
|