UNPKG

@threlte/theatre

Version:

Threlte Components for Theatre, an animation library with a professional motion design toolset

160 lines (159 loc) 5.07 kB
import { get } from 'svelte/store'; import { onChange, val } from '../theatre'; /** * ### `SequenceController` * * This class is a wrapper around the native `ISequence` object. It provides * reactive stores for the sequence's position, playing state, and length. */ export class SequenceController { key; position; playing; length; options; sequence; constructor(sequence, options = {}) { // api this.key = options?.key ?? 'default'; // not in use - for future proofing this.sequence = sequence; // theatre native object this.options = options; // to be set via config method // stores this.position = new SequencePosition(sequence, () => this.play()); this.playing = new SequencePlaying(sequence, () => this.play()); this.length = new SequenceLength(sequence); // methods this.config = this.config.bind(this); this.play = this.play.bind(this); this.pause = this.pause.bind(this); this.reset = this.reset.bind(this); } config(options) { // update options const updatedOptions = { ...this.options, ...options }; // ensure no unnecessary updates happen const noChange = Object.keys(updatedOptions).every((key) => this.options[key] == updatedOptions[key]); if (noChange) { return; } else { this.options = updatedOptions; } // update audio options if (options.audio) this.sequence.attachAudio(options.audio); // get replay conditions const replay = options.rate || options.range || options.iterationCount || options.direction || options.rafDriver; // and also check if playing const playing = get(this.playing); // trigger replay if needed if (playing && replay) { this.pause(); this.play(); } } play(opts = {}) { return this.sequence.play({ ...this.options, ...opts }); } pause() { return this.sequence.pause(); } reset() { this.position.set(0); } } // stores export class SequencePosition { sequence; play; subscribers; unsubscribe; constructor(sequence, play) { this.sequence = sequence; this.play = play; this.subscribers = new Set(); // TODO: this is a potential memory leak, when is unsubscribe called? this.unsubscribe = onChange(this.sequence.pointer.position, (position) => { this.subscribers.forEach((subscription) => subscription(position)); }); } subscribe(subscription) { this.subscribers.add(subscription); subscription(this.sequence.position); return () => { this.subscribers.delete(subscription); // TODO: run this.unsubscribe after the last delete? }; } update(callback) { this.set(callback(this.sequence.position)); } set(value) { const isPlaying = val(this.sequence.pointer.playing); this.sequence.position = value; if (isPlaying) this.play(); } } export class SequencePlaying { sequence; play; subscribers; constructor(sequence, play) { this.sequence = sequence; this.play = play; // subs this.subscribers = new Set(); // TODO: this is missing an unsubscribe!! onChange(this.sequence.pointer.playing, (playing) => { this.subscribers.forEach((subscription) => subscription(playing)); }); } subscribe(subscription) { this.subscribers.add(subscription); subscription(val(this.sequence.pointer.playing)); return () => { this.subscribers.delete(subscription); }; } update(callback) { const isPlaying = val(this.sequence.pointer.playing); const shouldBePlaying = callback(isPlaying); if (isPlaying && !shouldBePlaying) this.sequence.pause(); if (!isPlaying && shouldBePlaying) this.play(); } set(value) { const isPlaying = val(this.sequence.pointer.playing); const shouldBePlaying = value; if (isPlaying && !shouldBePlaying) this.sequence.pause(); if (!isPlaying && shouldBePlaying) this.play(); } } export class SequenceLength { sequence; subscribers; unsubscribe; constructor(sequence) { this.sequence = sequence; this.subscribers = new Set(); // TODO: this is a potential memory leak, when is unsubscribe called? this.unsubscribe = onChange(this.sequence.pointer.length, (length) => { this.subscribers.forEach((run) => run(length)); }); } subscribe(subscription) { this.subscribers.add(subscription); subscription(val(this.sequence.pointer.length)); return () => { this.subscribers.delete(subscription); }; } }