# scene.js

    export class Scene {
      #tweenables = []

      /**
       * The {@link shifty.Scene} class provides a way to control groups of {@link
       * shifty.Tweenable}s. It is lightweight, minimalistic, and meant to provide
       * performant {@link shifty.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 shifty.Scene} does **not** perform any
       * automatic cleanup. If you want to remove a {@link shifty.Tweenable} from a
       * {@link shifty.Scene}, you must do so explicitly with either {@link
       * shifty.Scene#remove} or {@link shifty.Scene#empty}.
       *
       * <p class="codepen" data-height="677" data-theme-id="0" data-default-tab="js,result" data-user="jeremyckahn" data-slug-hash="qvZKbe" style="height: 677px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;" data-pen-title="Shifty Scene Demo">
       * <span>See the Pen <a href="https://codepen.io/jeremyckahn/pen/qvZKbe/">
       * Shifty Scene Demo</a> by Jeremy Kahn (<a href="https://codepen.io/jeremyckahn">@jeremyckahn</a>)
       * on <a href="https://codepen.io">CodePen</a>.</span>
       * </p>
       * <script async src="https://static.codepen.io/assets/embed/ei.js"></script>
       * @param {...shifty.Tweenable} tweenables
       * @see https://codepen.io/jeremyckahn/pen/qvZKbe
       * @constructs shifty.Scene
       */
      constructor(...tweenables) {
        tweenables.forEach(this.add.bind(this))
      }

      /**
       * A copy of the internal {@link shifty.Tweenable}s array.
       * @member shifty.Scene#tweenables
       * @type {Array.<shifty.Tweenable>}
       * @readonly
       */
      get tweenables() {
        return [...this.#tweenables]
      }

      /**
       * The {@link external:Promise}s for all {@link shifty.Tweenable}s in this
       * {@link shifty.Scene} that have been configured with {@link
       * shifty.Tweenable#setConfig}. Note that each call of {@link
       * shifty.Scene#play} or {@link shifty.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 shifty.Scene#promises
       * @type {Array.<external:Promise>}
       * @readonly
       */
      get promises() {
        return this.#tweenables.map(({ _promise }) => _promise)
      }

      /**
       * Add a {@link shifty.Tweenable} to be controlled by this {@link
       * shifty.Scene}.
       * @method shifty.Scene#add
       * @param {shifty.Tweenable} tweenable
       * @return {shifty.Tweenable} The {@link shifty.Tweenable} that was added.
       */
      add(tweenable) {
        this.#tweenables.push(tweenable)

        return tweenable
      }

      /**
       * Remove a {@link shifty.Tweenable} that is controlled by this {@link
       * shifty.Scene}.
       * @method shifty.Scene#remove
       * @param {shifty.Tweenable} tweenable
       * @return {shifty.Tweenable} The {@link shifty.Tweenable} that was removed.
       */
      remove(tweenable) {
        const index = this.#tweenables.indexOf(tweenable)

        if (~index) {
          this.#tweenables.splice(index, 1)
        }

        return tweenable
      }

      /**
       * [Remove]{@link shifty.Scene#remove} all {@link shifty.Tweenable}s in this {@link
       * shifty.Scene}.
       * @method shifty.Scene#empty
       * @return {Array.<shifty.Tweenable>} The {@link shifty.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 shifty.Tweenable} in this {@link shifty.Scene} is
       * playing.
       * @method shifty.Scene#isPlaying
       * @return {boolean}
       */
      isPlaying() {
        return this.#tweenables.some(tweenable => tweenable.isPlaying())
      }

      /**
       * Play all {@link shifty.Tweenable}s from their beginning.
       * @method shifty.Scene#play
       * @return {shifty.Scene}
       */
      play() {
        this.#tweenables.forEach(tweenable => tweenable.tween())

        return this
      }

      /**
       * {@link shifty.Tweenable#pause} all {@link shifty.Tweenable}s in this
       * {@link shifty.Scene}.
       * @method shifty.Scene#pause
       * @return {shifty.Scene}
       */
      pause() {
        this.#tweenables.forEach(tweenable => tweenable.pause())
        return this
      }

      /**
       * {@link shifty.Tweenable#resume} all paused {@link shifty.Tweenable}s.
       * @method shifty.Scene#resume
       * @return {shifty.Scene}
       */
      resume() {
        this.#tweenables.forEach(tweenable => tweenable.resume())

        return this
      }

      /**
       * {@link shifty.Tweenable#stop} all {@link shifty.Tweenable}s in this {@link
       * shifty.Scene}.
       * @method shifty.Scene#stop
       * @param {boolean} [gotoEnd]
       * @return {shifty.Scene}
       */
      stop(gotoEnd) {
        this.#tweenables.forEach(tweenable => tweenable.stop(gotoEnd))
        return this
      }
    }