/** @typedef {import("./tweenable").Tweenable} Tweenable */
export class Scene {
#tweenables = []
/**
* The {@link Scene} class provides a way to control groups of {@link
* Tweenable}s. It is lightweight, minimalistic, and meant to provide
* performant {@link Tweenable} batch control that users of Shifty
* might otherwise have to implement themselves. It is **not** a robust
* timeline solution, and it does **not** provide utilities for sophisticated
* animation sequencing or orchestration. If that is what you need for your
* project, consider using a more robust tool such as
* [Rekapi](http://jeremyckahn.github.io/rekapi/doc/) (a timeline layer built
* on top of Shifty).
*
* Please be aware that {@link Scene} does **not** perform any
* automatic cleanup. If you want to remove a {@link Tweenable} from a
* {@link Scene}, you must do so explicitly with either {@link
* Scene#remove} or {@link Scene#empty}.
*
*
* See the Pen
* Shifty Scene Demo by Jeremy Kahn (@jeremyckahn)
* on CodePen.
*
*
* @param {...Tweenable} tweenables
* @see https://codepen.io/jeremyckahn/pen/qvZKbe
* @constructs Scene
* @memberof shifty
*/
constructor(...tweenables) {
tweenables.forEach(this.add.bind(this))
}
/**
* A copy of the internal {@link Tweenable}s array.
* @member Scene#tweenables
* @type {Array.}
*/
get tweenables() {
return [...this.#tweenables]
}
/**
* A list of {@link Tweenable}s in the scene that have not yet ended (playing
* or not).
* @member Scene#playingTweenables
* @type {Array.}
*/
get playingTweenables() {
return this.#tweenables.filter(tweenable => !tweenable.hasEnded())
}
/**
* The {@link external:Promise}s for all {@link Tweenable}s in this
* {@link Scene} that have been configured with {@link
* Tweenable#setConfig}. Note that each call of {@link
* Scene#play} or {@link Scene#pause} creates new {@link
* external:Promise}s:
*
* const scene = new Scene(new Tweenable());
* scene.play();
*
* Promise.all(scene.promises).then(() =>
* // Plays the scene again upon completion, but a new promise is
* // created so this line only runs once.
* scene.play()
* );
*
* @member Scene#promises
* @type {Array.>}
*/
get promises() {
return this.#tweenables.map(tweenable => tweenable.then())
}
/**
* Add a {@link Tweenable} to be controlled by this {@link
* Scene}.
* @method Scene#add
* @param {Tweenable} tweenable
* @return {Tweenable} The {@link Tweenable} that was added.
*/
add(tweenable) {
this.#tweenables.push(tweenable)
return tweenable
}
/**
* Remove a {@link Tweenable} that is controlled by this {@link
* Scene}.
* @method Scene#remove
* @param {Tweenable} tweenable
* @return {Tweenable} The {@link Tweenable} that was removed.
*/
remove(tweenable) {
const index = this.#tweenables.indexOf(tweenable)
if (~index) {
this.#tweenables.splice(index, 1)
}
return tweenable
}
/**
* [Remove]{@link Scene#remove} all {@link Tweenable}s in this {@link
* Scene}.
* @method Scene#empty
* @return {Array.} The {@link Tweenable}s that were
* removed.
*/
empty() {
// Deliberate of the tweenables getter here to create a temporary array
return this.tweenables.map(this.remove.bind(this))
}
/**
* Is `true` if any {@link Tweenable} in this {@link Scene} is
* playing.
* @method Scene#isPlaying
* @return {boolean}
*/
isPlaying() {
return this.#tweenables.some(tweenable => tweenable.isPlaying())
}
/**
* Play all {@link Tweenable}s from their beginning.
* @method Scene#play
* @return {Scene}
*/
play() {
this.#tweenables.forEach(tweenable => tweenable.tween())
return this
}
/**
* {@link Tweenable#pause} all {@link Tweenable}s in this
* {@link Scene}.
* @method Scene#pause
* @return {Scene}
*/
pause() {
this.#tweenables.forEach(tweenable => tweenable.pause())
return this
}
/**
* {@link Tweenable#resume} all paused {@link Tweenable}s.
* @method Scene#resume
* @return {Scene}
*/
resume() {
this.playingTweenables.forEach(tweenable => tweenable.resume())
return this
}
/**
* {@link Tweenable#stop} all {@link Tweenable}s in this {@link
* Scene}.
* @method Scene#stop
* @param {boolean} [gotoEnd]
* @return {Scene}
*/
stop(gotoEnd) {
this.#tweenables.forEach(tweenable => tweenable.stop(gotoEnd))
return this
}
}