import type { TextureAsset } from '../../core/model/Asset'; import { DEFAULT_SLICE, type SliceOptions, sliceFrames } from '../../core/model/texture'; import { type Button, Checkbox, Label, NumberField, Spacer, Theme, type Widget } from '../../ui'; import { FormDialog } from '../FormDialog'; const FIELD_WIDTH = 76; /** * Cuts a sheet into a grid, asked for as columns and rows rather than a cell size: the counts are * what the user knows, and a cell size derived from the picture cannot disagree with them. */ export class SliceDialog extends FormDialog { private readonly options: SliceOptions = { ...DEFAULT_SLICE }; private replace = true; private readonly summary: Label; private readonly confirm: Button; constructor( private readonly asset: TextureAsset, onSlice: (options: SliceOptions, replace: boolean) => void, ) { super(340, `Slice "${asset.name}" into a grid`); this.content.add(new Label(`Picture is ${asset.width} × ${asset.height} px`, { style: 'muted' })); this.content.add(this.pair('Columns', 'columns', 1, 'Rows', 'rows', 1)); this.content.add(this.pair('Offset X', 'offsetX', 0, 'Offset Y', 'offsetY', 0)); this.content.add(this.pair('Spacing X', 'spacingX', 0, 'Spacing Y', 'spacingY', 0)); const box = new Checkbox(this.replace); box.onChange = (value) => { this.replace = Boolean(value); }; this.content.add( this.formRow(box, new Label('Replace the frames this texture already has', { style: 'muted' })), ); this.summary = new Label('', { style: 'muted' }); this.content.add(this.summary); this.confirm = this.addButtons('Slice', 'primary', () => onSlice({ ...this.options }, this.replace)); this.refreshSummary(); } private pair( labelA: string, keyA: keyof SliceOptions, minA: number, labelB: string, keyB: keyof SliceOptions, minB: number, ): Widget { return this.formRow( this.fieldLabel(labelA), this.fieldFor(keyA, minA), new Spacer(Theme.padding), this.fieldLabel(labelB), this.fieldFor(keyB, minB), ); } private fieldFor(key: keyof SliceOptions, min: number): NumberField { const field = new NumberField(this.options[key], { min, step: 1, decimals: 0, onChange: (value) => { this.options[key] = Math.round(Number(value)); this.refreshSummary(); }, }); field.minWidth = FIELD_WIDTH; return field; } private refreshSummary(): void { const rects = sliceFrames(this.asset.width, this.asset.height, this.options); const first = rects[0]; this.summary.setText( first ? `${rects.length} frames of ${first.w} × ${first.h} px` : 'These numbers do not fit the picture – no frames.', ); this.summary.setStyle(first ? 'muted' : 'body'); this.confirm.setEnabled(rects.length > 0); this.refreshLayout(); } }