import WebGL2D from './WebGL2D' import { Render2DConstructor } from './Render2D' import { KeydownCallbackMap } from './Animate2D' export interface AnimateWebGL2DConstructor extends Render2DConstructor { frameRenderCallback: (tick: number, renderer: WebGL2D) => void keydownCallbackMap?: KeydownCallbackMap } export default class AnimateWebGL2D extends WebGL2D { private frameRenderCallback: (tick: number, renderer: WebGL2D) => void private runState: 'play' | 'pause' = 'play' public tick: number = 0 private defaultKeydownCallbackMap: KeydownCallbackMap = { 'Space': { callback: () => { if (this.runState === 'play') { const event = new CustomEvent('pause') window.dispatchEvent(event) } else { const event = new CustomEvent('play') window.dispatchEvent(event) } }, options: { preventDefault: true } }, 'Enter': { callback: () => { const event = new CustomEvent('step') window.dispatchEvent(event) }, options: { preventDefault: true } } } constructor(opts: AnimateWebGL2DConstructor) { const { frameRenderCallback, autoPlay = true, frameRate = 60, title = 'Canvas', id = 'canvas_0', } = opts super({ ...opts, frameRate, autoPlay, title, id }) this.frameRenderCallback = frameRenderCallback this.frameRate = frameRate window.addEventListener('render', () => { this.frameRenderCallback(this.tick, this) }) window.addEventListener('stop', () => { this.pause() }) window.addEventListener('pause', () => { this.pause() }) window.addEventListener('play', () => { this.play() this.step() }) window.addEventListener('step', () => { if (this.runState === 'play') { return } this.step() }) window.addEventListener('fps', (e: Event) => { const customEvent = e as CustomEvent this.frameRate = customEvent.detail }) window.addEventListener('keydown', (e: KeyboardEvent) => { const mergedMaps = { ...this.defaultKeydownCallbackMap, ...opts.keydownCallbackMap } const codeAction = mergedMaps?.[e.code] if (codeAction) { const { callback, options } = codeAction callback(e) if (options.preventDefault) { e.preventDefault() } } }) if (autoPlay) { this.runState = 'play' this.play() } else { this.runState = 'pause' this.pause() } this.step() } public step() { this.frameRenderCallback(this.tick, this) this.tick++ if (this.runState === 'play') { setTimeout( () => requestAnimationFrame(() => this.step()), 1000 / this.frameRate ) } } public play() { this.runState = 'play' this.step() } public pause() { this.runState = 'pause' } public toggle() { if (this.runState === 'play') { this.pause() } else { this.play() } } public stepForward() { this.pause() this.step() } }