The animation tab's Fit frames the preview, and the separate Centre button is gone (#111)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cc14988338
commit
4c92a9478f
5 files changed
+23
-16
No files matched your search
@@ -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 {}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in new issue
Block a user