@threlte/theatre
Version:
Threlte Components for Theatre, an animation library with a professional motion design toolset
160 lines (159 loc) • 5.07 kB
JavaScript
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);
};
}
}