The scene view and the animation preview share one camera, the three form dialogs one base, and keys are found through one set of helpers (#85)
game-maker / gate (push) Successful in 1m36s
game-maker / suite (push) Successful in 5m41s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-17 14:19:53 +02:00
1 parent bf02c9e8b3
commit 2a390e52ff
22 files changed
+418 -452

No files matched your search

+15 -36
View File
@@ -1,16 +1,15 @@
import type { TextureAsset } from '../../core/model/Asset';
import { DEFAULT_SLICE, type SliceOptions, sliceFrames } from '../../core/model/texture';
import { Button, Checkbox, HStack, Label, NumberField, Popup, Spacer, Theme, type Widget } from '../../ui';
import { type Button, Checkbox, Label, NumberField, Spacer, Theme, type Widget } from '../../ui';
import { FormDialog } from '../FormDialog';
/** Matches the inspector's label column. */
const LABEL_WIDTH = 76;
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 Popup {
export class SliceDialog extends FormDialog {
private readonly options: SliceOptions = { ...DEFAULT_SLICE };
private replace = true;
private readonly summary: Label;
@@ -20,37 +19,25 @@ export class SliceDialog extends Popup {
private readonly asset: TextureAsset,
onSlice: (options: SliceOptions, replace: boolean) => void,
) {
super({ modal: true, padding: Theme.padding * 2, gap: Theme.gap });
this.minWidth = 340;
this.content.add(new Label(`Slice "${asset.name}" into a grid`, { style: 'bodyBold' }));
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 replaceRow = new HStack({ gap: Theme.gap, align: 'center' });
replaceRow.minHeight = Theme.rowHeight;
const box = new Checkbox(this.replace);
box.onChange = (value) => {
this.replace = Boolean(value);
};
replaceRow.add(box);
replaceRow.add(new Label('Replace the frames this texture already has', { style: 'muted' }));
this.content.add(replaceRow);
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);
const buttons = new HStack({ gap: Theme.gap, align: 'center' });
buttons.add(new Spacer(), 1);
const cancel = new Button('Cancel');
cancel.onClick = this.closeThen();
buttons.add(cancel);
this.confirm = new Button('Slice', { variant: 'primary' });
this.confirm.onClick = this.closeThen(() => onSlice({ ...this.options }, this.replace));
buttons.add(this.confirm);
this.content.add(buttons);
this.confirm = this.addButtons('Slice', 'primary', () => onSlice({ ...this.options }, this.replace));
this.refreshSummary();
}
@@ -63,21 +50,13 @@ export class SliceDialog extends Popup {
keyB: keyof SliceOptions,
minB: number,
): Widget {
const row = new HStack({ gap: Theme.gap, align: 'center' });
row.minHeight = Theme.rowHeight;
row.add(this.labelFor(labelA));
row.add(this.fieldFor(keyA, minA));
row.add(new Spacer(Theme.padding));
row.add(this.labelFor(labelB));
row.add(this.fieldFor(keyB, minB));
row.add(new Spacer(), 1);
return row;
}
private labelFor(text: string): Label {
const label = new Label(text, { style: 'muted' });
label.minWidth = LABEL_WIDTH;
return label;
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 {