bemusic/bemuse

View on GitHub
bemuse/src/scintillator/context.js

Summary

Maintainability
A
2 hrs
Test Coverage
import * as PIXI from 'pixi.js'

function createRenderer(w, h) {
  hackPIXIToForceNewBlendModes()

  // For now, we are using CanvasRenderer instead of WebGLRenderer or
  // autoDetectRenderer befcause of two reasons.
  // 1. Current implementation has some problem with rendering
  //    sprite batches: https://github.com/pixijs/pixi.js/issues/1910
  // 2. It seems that Canvas performs better on some browsers, i.e. Chrome.
  //    WebGLRenderer only performs better on Firefox from the experiment.
  return new PIXI.CanvasRenderer(w, h, { transparent: true })
}

// HACK: Sometimes, when using the canvas renderer,
// the blend mode is not properly set.
function hackPIXIToForceNewBlendModes() {
  PIXI.utils.canUseNewCanvasBlendModes = () => true
}

export class Context {
  constructor(skin, { touchEventTarget } = {}) {
    this.refs = {}
    this._skin = skin
    this._touchEventTarget = touchEventTarget
    this._instance = skin.instantiate(this)
    this._renderer = createRenderer(skin.width, skin.height)
    this.stage = this._instance.object
    this.view = this._renderer.view
    this.skinData = skin.data
    this._setupInteractivity()
  }

  render(data) {
    this._instance.push(data)
    this._renderer.render(this.stage)
  }

  destroy() {
    this._instance.destroy()
    this._instance = null
    this._teardownInteractivity()
  }

  get input() {
    return this._input.get()
  }

  ref(key, object) {
    const set = this.refs[key] || (this.refs[key] = new Set())
    set.add(object)
  }

  unref(key, object) {
    const set = this.refs[key]
    if (!set) return
    set.delete(object)
  }

  _setupInteractivity() {
    let mouse = null
    let touches = []
    const onMouse = (e) => {
      mouse = e
    }
    const onUpdateMouse = (e) => {
      mouse = mouse && e
    }
    const onNoMouse = () => {
      mouse = null
    }
    const onTouch = (e) => {
      touches = [].slice.call(e.touches)
    }
    const touchTarget = this._touchEventTarget || this.view
    const width = this._skin.width
    const height = this._skin.height
    touchTarget.addEventListener('mousedown', onMouse, false)
    touchTarget.addEventListener('mousemove', onUpdateMouse, false)
    touchTarget.addEventListener('mouseup', onNoMouse, false)
    touchTarget.addEventListener('touchstart', onTouch, false)
    touchTarget.addEventListener('touchmove', onTouch, false)
    touchTarget.addEventListener('touchend', onTouch, false)
    this._teardownInteractivity = () => {
      touchTarget.removeEventListener('mousedown', onMouse, false)
      touchTarget.removeEventListener('mousemove', onUpdateMouse, false)
      touchTarget.removeEventListener('mouseup', onNoMouse, false)
      touchTarget.removeEventListener('touchstart', onTouch, false)
      touchTarget.removeEventListener('touchmove', onTouch, false)
      touchTarget.removeEventListener('touchend', onTouch, false)
    }
    this._input = {
      get: () => {
        const output = []
        const rect = this.view.getBoundingClientRect()
        if (mouse) {
          output.push(point('mouse', mouse, rect))
        }
        for (let i = 0; i < touches.length; i++) {
          const touch = touches[i]
          output.push(point('touch' + touch.identifier, touch, rect))
        }
        return output
      },
    }
    function point(id, p, rect) {
      return {
        x: ((p.clientX - rect.left) / rect.width) * width,
        y: ((p.clientY - rect.top) / rect.height) * height,
        id: id,
      }
    }
  }
}

export default Context