From 4c92a9478ff2f51273c3bebddad61ded3bc05e6d Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Thu, 17 Sep 2026 07:12:21 +0200 Subject: [PATCH] The animation tab's Fit frames the preview, and the separate Centre button is gone (#111) Co-Authored-By: Claude Opus 5 --- src/editor/AssetTabEditor.ts | 16 +++++++++++++--- src/editor/animation/AnimationEditor.ts | 11 +++++++---- src/editor/animation/TimelineCanvas.ts | 6 ------ src/editor/debugHook.ts | 2 +- tools/e2e/25-animation.mjs | 4 ++-- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/editor/AssetTabEditor.ts b/src/editor/AssetTabEditor.ts index 89789a4c..37608631 100644 --- a/src/editor/AssetTabEditor.ts +++ b/src/editor/AssetTabEditor.ts @@ -15,11 +15,14 @@ import { } from '../ui'; 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 { readonly currentAssetId: string; show(assetId: string): void; - fit(): void; + fit?(): void; } export interface AssetTabOptions { @@ -39,6 +42,7 @@ export abstract class AssetTabEditor< > extends VStack { /** Public for the check scripts. */ readonly picker: Dropdown; + readonly fitButton: Button; protected readonly info: Label; /** Assigned by `mount`. Public because the check hooks and the play loop reach for it. */ canvas!: C; @@ -50,6 +54,7 @@ export abstract class AssetTabEditor< ) { super(); 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.minWidth = options.infoWidth; } @@ -63,7 +68,7 @@ export abstract class AssetTabEditor< header.add(new Label(this.options.noun, { style: 'muted' })); header.add(this.picker); 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(this.info); @@ -103,6 +108,11 @@ export abstract class AssetTabEditor< 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`. */ protected syncExtras(_asset: A | null): void {} diff --git a/src/editor/animation/AnimationEditor.ts b/src/editor/animation/AnimationEditor.ts index addcc218..714eefae 100644 --- a/src/editor/animation/AnimationEditor.ts +++ b/src/editor/animation/AnimationEditor.ts @@ -23,7 +23,6 @@ export class AnimationEditor extends AssetTabEditor this.openRootPicker() }); this.addRowButton = new Button('+ Row', { onClick: () => this.openRowDialog() }); this.playButton = new Button('Play', { width: 64, onClick: () => this.togglePlay() }); - this.centreButton = new Button('Centre', { onClick: () => this.preview.fit() }); const timeline = new TimelineCanvas(state); this.preview = new PreviewCanvas(state, () => ({ frame: timeline.playhead, reverse: timeline.playheadReverse, })); 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. */ @@ -81,7 +84,7 @@ export class AnimationEditor extends AssetTabEditor editor.animationEditor.preview.objectRect(objectId), item: (objectId: string) => editor.animationEditor.preview.itemDrawn(objectId), info: () => editor.animationEditor.preview.debugInfo(), - centreAt: () => centreOf(editor.animationEditor.centreButton), + fitAt: () => centreOf(editor.animationEditor.fitButton), }, }, container: { diff --git a/tools/e2e/25-animation.mjs b/tools/e2e/25-animation.mjs index 30bbe6f1..487750b4 100644 --- a/tools/e2e/25-animation.mjs +++ b/tools/e2e/25-animation.mjs @@ -135,8 +135,8 @@ export async function animation(t) { return json(`__gm.animation.preview.rect(${JSON.stringify(armId)})`); }; await scrubTo(10); - const centre = await json('__gm.animation.preview.centreAt()'); - await click(centre.x, centre.y); + const fit = await json('__gm.animation.preview.fitAt()'); + await click(fit.x, fit.y); await sleep(200); const start = await rectAt(0); const end = await rectAt(10);