85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import type { FederatedPointerEvent } from 'pixi.js';
|
||
import { previewHolder } from '../../core/model/animationPreview';
|
||
import type { AnimationAsset } from '../../core/model/Asset';
|
||
import { findAssetOf } from '../../core/model/Project';
|
||
import type { EditorState } from '../state';
|
||
import { Theme } from '../../ui';
|
||
import { collectHolderItems } from '../../viewport/items';
|
||
import { FreeViewport } from '../viewport/FreeViewport';
|
||
import type { Frame } from '../viewport/WorldViewport';
|
||
|
||
/**
|
||
* The picture above the timeline (#99): the animation's root with every row's value at the
|
||
* playhead written over it, drawn like the Scene tab draws a scene but never edited – a drag pans,
|
||
* the wheel zooms, nothing is picked. What it draws comes from `previewHolder`, so the document
|
||
* stays untouched and undo never hears of it.
|
||
*/
|
||
export class PreviewCanvas extends FreeViewport {
|
||
private assetId = '';
|
||
/** Frames the content on the first draw with a size – a fresh subject gets a whole view. */
|
||
private fitPending = false;
|
||
|
||
constructor(
|
||
state: EditorState,
|
||
/** Where the timeline's playhead stands, in frames, and whether it runs ping-pong's way back. */
|
||
private readonly playhead: () => { frame: number; reverse: boolean },
|
||
) {
|
||
super(state);
|
||
this.on('pointerdown', (e: FederatedPointerEvent) => {
|
||
if (e.button === 0 || e.button === 1) this.startPan(e);
|
||
});
|
||
this.on('globalpointermove', (e: FederatedPointerEvent) => this.panTo(e));
|
||
this.on('pointerup', () => this.endPan());
|
||
this.on('pointerupoutside', () => this.endPan());
|
||
state.on('load', () => this.show(''));
|
||
}
|
||
|
||
show(assetId: string): void {
|
||
if (this.assetId === assetId) return;
|
||
this.assetId = assetId;
|
||
this.fitPending = true;
|
||
this.redraw();
|
||
}
|
||
|
||
override fit(): void {
|
||
this.fitPending = false;
|
||
super.fit();
|
||
}
|
||
|
||
/** The values as drawn, for a check: an object's position and property bag at the playhead. */
|
||
itemDrawn(objectId: string): { x: number; y: number; props: Record<string, unknown> } | null {
|
||
const item = this.lastItems.find((i) => i.id === objectId);
|
||
return item ? { x: item.x, y: item.y, props: item.props } : null;
|
||
}
|
||
|
||
/** Skipped while the tab is hidden, like the Game tab: a frame costs a collect of the root. */
|
||
override redraw(): void {
|
||
if (!this.visible) return;
|
||
if (this.fitPending && this.w > 0 && this.h > 0) {
|
||
this.fit();
|
||
return;
|
||
}
|
||
super.redraw();
|
||
}
|
||
|
||
protected override collectFrame(): Frame {
|
||
const asset = this.asset();
|
||
const head = this.playhead();
|
||
const holder = asset && previewHolder(this.state.project, asset, head.frame, head.reverse);
|
||
return collectHolderItems(this.state.project, holder, Theme.colors.viewport);
|
||
}
|
||
|
||
protected override get baseColor(): number {
|
||
return Theme.colors.viewport;
|
||
}
|
||
|
||
private asset(): AnimationAsset | undefined {
|
||
return this.assetId ? findAssetOf(this.state.project, this.assetId, 'animation') : undefined;
|
||
}
|
||
|
||
/** A scene root paints its own background; anything placed on the preview's holder has none. */
|
||
protected override paintsBackground(): boolean {
|
||
return findAssetOf(this.state.project, this.asset()?.root ?? '', 'scene') !== undefined;
|
||
}
|
||
}
|