import type { CanvasGenerationDialogMode, CanvasGenerationDialogState, CanvasLayer, CanvasViewport, CharacterAnimationPanelState, CropExpandPanelState, GenerateDialogState, QuickEditPanelState, } from './ImageCanvasEditorTypes'; type CanvasSize = { width: number; height: number; }; type OverlayAnchor = Pick; 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 === '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' ); }