The animation tab's Fit frames the preview, and the separate Centre button is gone (#111)
game-maker / gate (push) Successful in 1m32s
game-maker / suite (push) Successful in 5m37s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-17 07:12:21 +02:00
1 parent cc14988338
commit 4c92a9478f
5 files changed
+23 -16

No files matched your search

+13 -3
View File
@@ -15,11 +15,14 @@ import {
} from '../ui'; } from '../ui';
import type { EditorState } from './state'; import type { EditorState } from './state';
/** The canvas each tab wraps: the header's Fit button and the tab's own plumbing need this much. */ /**
* The canvas each tab wraps: the header's Fit button and the tab's own plumbing need this much.
* A canvas without `fit` belongs to a tab whose Fit frames something else (see `AssetTabEditor.fit`).
*/
export interface TabCanvas { export interface TabCanvas {
readonly currentAssetId: string; readonly currentAssetId: string;
show(assetId: string): void; show(assetId: string): void;
fit(): void; fit?(): void;
} }
export interface AssetTabOptions { export interface AssetTabOptions {
@@ -39,6 +42,7 @@ export abstract class AssetTabEditor<
> extends VStack { > extends VStack {
/** Public for the check scripts. */ /** Public for the check scripts. */
readonly picker: Dropdown; readonly picker: Dropdown;
readonly fitButton: Button;
protected readonly info: Label; protected readonly info: Label;
/** Assigned by `mount`. Public because the check hooks and the play loop reach for it. */ /** Assigned by `mount`. Public because the check hooks and the play loop reach for it. */
canvas!: C; canvas!: C;
@@ -50,6 +54,7 @@ export abstract class AssetTabEditor<
) { ) {
super(); super();
this.picker = new Dropdown('—', { width: 200, onClick: () => this.openPicker() }); this.picker = new Dropdown('—', { width: 200, onClick: () => this.openPicker() });
this.fitButton = new Button('Fit', { onClick: () => this.fit() });
this.info = new Label('', { style: 'small', align: 'right' }); this.info = new Label('', { style: 'small', align: 'right' });
this.info.minWidth = options.infoWidth; this.info.minWidth = options.infoWidth;
} }
@@ -63,7 +68,7 @@ export abstract class AssetTabEditor<
header.add(new Label(this.options.noun, { style: 'muted' })); header.add(new Label(this.options.noun, { style: 'muted' }));
header.add(this.picker); header.add(this.picker);
for (const widget of extras) header.add(widget); for (const widget of extras) header.add(widget);
header.add(new Button('Fit', { onClick: () => this.canvas.fit() })); header.add(this.fitButton);
header.add(new Spacer(), 1); header.add(new Spacer(), 1);
header.add(this.info); header.add(this.info);
@@ -103,6 +108,11 @@ export abstract class AssetTabEditor<
return canvas; return canvas;
} }
/** What the header's Fit button frames. Override when the tab has more than one view. */
protected fit(): void {
this.canvas.fit?.();
}
/** Enable/disable whatever `extras` the subclass passed to `mount`. */ /** Enable/disable whatever `extras` the subclass passed to `mount`. */
protected syncExtras(_asset: A | null): void {} protected syncExtras(_asset: A | null): void {}
+7 -4
View File
@@ -23,7 +23,6 @@ export class AnimationEditor extends AssetTabEditor<AnimationAsset, TimelineCanv
readonly rootPicker: Dropdown; readonly rootPicker: Dropdown;
readonly addRowButton: Button; readonly addRowButton: Button;
readonly playButton: Button; readonly playButton: Button;
readonly centreButton: Button;
readonly preview: PreviewCanvas; readonly preview: PreviewCanvas;
constructor(state: EditorState) { constructor(state: EditorState) {
@@ -31,14 +30,18 @@ export class AnimationEditor extends AssetTabEditor<AnimationAsset, TimelineCanv
this.rootPicker = new Dropdown('—', { width: ROOT_WIDTH, onClick: () => this.openRootPicker() }); this.rootPicker = new Dropdown('—', { width: ROOT_WIDTH, onClick: () => this.openRootPicker() });
this.addRowButton = new Button('+ Row', { onClick: () => this.openRowDialog() }); this.addRowButton = new Button('+ Row', { onClick: () => this.openRowDialog() });
this.playButton = new Button('Play', { width: 64, onClick: () => this.togglePlay() }); this.playButton = new Button('Play', { width: 64, onClick: () => this.togglePlay() });
this.centreButton = new Button('Centre', { onClick: () => this.preview.fit() });
const timeline = new TimelineCanvas(state); const timeline = new TimelineCanvas(state);
this.preview = new PreviewCanvas(state, () => ({ this.preview = new PreviewCanvas(state, () => ({
frame: timeline.playhead, frame: timeline.playhead,
reverse: timeline.playheadReverse, reverse: timeline.playheadReverse,
})); }));
timeline.onPlayhead = () => this.preview.redraw(); timeline.onPlayhead = () => this.preview.redraw();
this.mount(timeline, [this.rootPicker, this.addRowButton, this.playButton, this.centreButton]); this.mount(timeline, [this.rootPicker, this.addRowButton, this.playButton]);
}
/** Fit frames the preview; the timeline keeps its own zoom and scroll. */
protected override fit(): void {
this.preview.fit();
} }
/** The preview above, the timeline below, each taking half. */ /** The preview above, the timeline below, each taking half. */
@@ -81,7 +84,7 @@ export class AnimationEditor extends AssetTabEditor<AnimationAsset, TimelineCanv
protected override syncExtras(asset: AnimationAsset | null): void { protected override syncExtras(asset: AnimationAsset | null): void {
const root = asset ? findAsset(this.state.project, asset.root) : undefined; const root = asset ? findAsset(this.state.project, asset.root) : undefined;
this.preview.show(asset?.id ?? ''); this.preview.show(asset?.id ?? '');
this.centreButton.setEnabled(Boolean(root)); this.fitButton.setEnabled(Boolean(root));
this.rootPicker.setText(root ? root.name : asset ? 'Animates…' : '—'); this.rootPicker.setText(root ? root.name : asset ? 'Animates…' : '—');
this.rootPicker.setEnabled(asset !== null); this.rootPicker.setEnabled(asset !== null);
// A row needs something to aim at. // A row needs something to aim at.
-6
View File
@@ -131,12 +131,6 @@ export class TimelineCanvas extends Widget {
return this.assetId; return this.assetId;
} }
/** Back to frame 0 at the default zoom. */
fit(): void {
this.view = { ...DEFAULT_VIEW };
this.redraw();
}
get isPlaying(): boolean { get isPlaying(): boolean {
return this.playingNow; return this.playingNow;
} }
+1 -1
View File
@@ -492,7 +492,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
rect: (objectId: string) => editor.animationEditor.preview.objectRect(objectId), rect: (objectId: string) => editor.animationEditor.preview.objectRect(objectId),
item: (objectId: string) => editor.animationEditor.preview.itemDrawn(objectId), item: (objectId: string) => editor.animationEditor.preview.itemDrawn(objectId),
info: () => editor.animationEditor.preview.debugInfo(), info: () => editor.animationEditor.preview.debugInfo(),
centreAt: () => centreOf(editor.animationEditor.centreButton), fitAt: () => centreOf(editor.animationEditor.fitButton),
}, },
}, },
container: { container: {
+2 -2
View File
@@ -135,8 +135,8 @@ export async function animation(t) {
return json(`__gm.animation.preview.rect(${JSON.stringify(armId)})`); return json(`__gm.animation.preview.rect(${JSON.stringify(armId)})`);
}; };
await scrubTo(10); await scrubTo(10);
const centre = await json('__gm.animation.preview.centreAt()'); const fit = await json('__gm.animation.preview.fitAt()');
await click(centre.x, centre.y); await click(fit.x, fit.y);
await sleep(200); await sleep(200);
const start = await rectAt(0); const start = await rectAt(0);
const end = await rectAt(10); const end = await rectAt(10);