import { TOOLBAR_HALF_WIDTH, clamp, } from './ImageCanvasEditorModel'; import { ICON_COMPOSER_HORIZONTAL_CHROME_REM, ICON_COMPOSER_MIN_WIDTH_REM, ICON_DESCRIPTION_CARD_WIDTH_REM, } from './ImageCanvasGenerationModel'; import type { CanvasGenerationDialogMode, CanvasGenerationDialogState, CanvasLayer, CanvasViewport, CharacterAnimationPanelState, GenerateDialogState, QuickEditPanelState, } from './ImageCanvasEditorTypes'; type CanvasSize = { width: number; height: number; }; type OverlayAnchor = Pick; 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, iconDescriptionCount, }: { dialog: CanvasGenerationDialogState | null; composerStyle: CanvasOverlayStyle | null; iconDescriptionCount: number; }): CanvasComposerOverlayStyle | null { if (dialog?.mode !== 'icon' || !composerStyle) { return null; } return { ...composerStyle, width: `${Math.max( ICON_COMPOSER_MIN_WIDTH_REM, ICON_COMPOSER_HORIZONTAL_CHROME_REM + iconDescriptionCount * ICON_DESCRIPTION_CARD_WIDTH_REM, ).toFixed(1)}rem`, }; } export function resolveSelectedToolbarStyle({ selectedLayer, viewport, canvasSize, }: { selectedLayer: CanvasLayer | null; viewport: CanvasViewport; canvasSize: CanvasSize; }): CanvasOverlayStyle | null { if (!selectedLayer) { return null; } return { left: clamp( viewport.x + selectedLayer.x * viewport.scale + (selectedLayer.width * viewport.scale) / 2, TOOLBAR_HALF_WIDTH, Math.max(TOOLBAR_HALF_WIDTH, canvasSize.width - TOOLBAR_HALF_WIDTH), ), top: Math.max(10, viewport.y + selectedLayer.y * viewport.scale - 12), }; } export function resolveQuickEditPanelStyle({ panel, sourceLayer, viewport, canvasSize, }: { panel: QuickEditPanelState | null; sourceLayer: CanvasLayer | null; viewport: CanvasViewport; canvasSize: CanvasSize; }): CanvasOverlayStyle | null { if (!panel || !sourceLayer) { return null; } return { left: clamp( viewport.x + (sourceLayer.x + sourceLayer.width / 2) * viewport.scale, 12, Math.max(12, canvasSize.width - 12), ), top: clamp( viewport.y + (sourceLayer.y + sourceLayer.height) * viewport.scale + 12, 12, Math.max(12, canvasSize.height - 360), ), }; } export function resolveCharacterAnimationPanelStyle({ panel, sourceLayer, viewport, canvasSize, }: { panel: CharacterAnimationPanelState | null; sourceLayer: CanvasLayer | null; viewport: CanvasViewport; canvasSize: CanvasSize; }): CanvasOverlayStyle | null { if (!panel || !sourceLayer) { return null; } return { left: clamp( viewport.x + (sourceLayer.x + sourceLayer.width) * viewport.scale + 12, 12, Math.max(12, canvasSize.width - 364), ), top: clamp( viewport.y + sourceLayer.y * viewport.scale, 12, Math.max(12, canvasSize.height - 520), ), }; } export function isCanvasGenerationComposerVisible( dialog: GenerateDialogState | null, ): dialog is GenerateDialogState & { mode: CanvasGenerationDialogMode } { return ( dialog?.mode === 'generate' || dialog?.mode === 'spec' || dialog?.mode === 'character' || dialog?.mode === 'icon' ); }