import type { TextureAsset, TextureFrame } from '../model/Asset'; import { findAsset, type Project } from '../model/Project'; import { findFrame } from '../model/texture'; export const NO_TEXTURE_LABEL = 'None'; const FRAME_SEPARATOR = ' / '; export interface TextureChoice { /** Asset id for the whole picture, frame id for one cut-out of it. */ id: string; label: string; asset: TextureAsset; /** Null for the whole-picture entry. */ frame: TextureFrame | null; } /** * The texture's name, "Sheet / Frame" for a frame, "None" for the empty id, `?` when it no * longer resolves – a dangling reference shows its id rather than passing for an empty field. */ export function textureRefLabel(project: Project | null, id: string): string { if (id === '') return NO_TEXTURE_LABEL; if (!project) return `${id}?`; const asset = findAsset(project, id); if (asset) return asset.type === 'texture' ? asset.name : `${id}?`; const found = findFrame(project, id); return found ? `${found.asset.name}${FRAME_SEPARATOR}${found.frame.name}` : `${id}?`; } /** * Everything a sprite may wear, in menu order: each texture followed by its own frames. Textures * are sorted by name so the order does not depend on upload time; frames keep creation order, * which for a sliced sheet is reading order. */ export function textureRefChoices(project: Project): TextureChoice[] { const textures = project.assets .filter((a): a is TextureAsset => a.type === 'texture') .sort((a, b) => a.name.localeCompare(b.name)); const choices: TextureChoice[] = []; for (const asset of textures) { choices.push({ id: asset.id, label: asset.name, asset, frame: null }); for (const frame of asset.frames) choices.push({ id: frame.id, label: `${asset.name}${FRAME_SEPARATOR}${frame.name}`, asset, frame, }); } return choices; }