import { Button, type ButtonOptions, HStack, Label, Popup, Spacer, Theme, type Widget } from '../ui'; /** Matches the inspector's label column. */ const LABEL_WIDTH = 76; /** * A small modal form – the texture's Slice dialog, the timeline's row and frames dialogs: a bold * title, rows led by a label in the inspector's label column, Cancel and a confirming button last. */ export abstract class FormDialog extends Popup { protected constructor(minWidth: number, title: string) { super({ modal: true, padding: Theme.padding * 2, gap: Theme.gap }); this.minWidth = minWidth; this.content.add(new Label(title, { style: 'bodyBold' })); } protected fieldLabel(text: string): Label { const label = new Label(text, { style: 'muted' }); label.minWidth = LABEL_WIDTH; return label; } /** A row of `widgets`, left-aligned at the height of an inspector row. */ protected formRow(...widgets: Widget[]): HStack { const row = new HStack({ gap: Theme.gap, align: 'center' }); row.minHeight = Theme.rowHeight; for (const widget of widgets) row.add(widget); row.add(new Spacer(), 1); return row; } /** Cancel and the confirming button, right-aligned; the dialog closes before `onConfirm` runs. */ protected addButtons(text: string, variant: ButtonOptions['variant'], onConfirm: () => void): Button { const confirm = new Button(text, { variant }); confirm.onClick = this.closeThen(onConfirm); this.content.add(dialogButtons(this.closeThen(), confirm)); return confirm; } } /** The row a dialog ends on: Cancel and `confirm` on the right, `leading` – if any – on the left. */ export function dialogButtons( onCancel: () => void, confirm: Button, leading?: Button | readonly Button[], ): HStack { const buttons = new HStack({ gap: Theme.gap, align: 'center' }); for (const button of leading === undefined ? [] : Array.isArray(leading) ? leading : [leading]) { buttons.add(button); } buttons.add(new Spacer(), 1); const cancel = new Button('Cancel'); cancel.onClick = onCancel; buttons.add(cancel); buttons.add(confirm); return buttons; } /** One open dialog of a kind at a time: opening the next closes the last. `current` is for the check scripts. */ export function dialogSlot(): { current(): T | null; open(dialog: T): T } { let current: T | null = null; return { current: () => current, open(dialog) { current?.close(); current = dialog; dialog.onClose = () => { if (current === dialog) current = null; }; dialog.openCentered(); return dialog; }, }; }