Files
feuerwerk/src/editor/texture/SliceDialog.ts
T
2026-09-17 14:19:53 +02:00

89 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}