Source: core/Renderer.js

import {Utils} from '../utils/Utils';
import {Matrix} from '../math/Matrix';
import InteractionManager from '../eventer/InteractionManager';

/**
 * @param {object} options 舞台的配置项
 * @param {string} options.dom 舞台要附着的`canvas`元素
 * @param {number} [options.resolution] 设置舞台的分辨率,`默认为` 1
 * @param {boolean} [options.interactive] 设置舞台是否可交互,`默认为` true
 * @param {number} [options.width] 设置舞台的宽, `默认为` 附着的canvas.width
 * @param {number} [options.height] 设置舞台的高, `默认为` 附着的canvas.height
 * @param {string} [options.backgroundColor] 设置舞台的背景颜色,`默认为` ‘transparent’
 */
function Renderer(options) {
  /**
   * 场景的canvas的dom
   *
   * @member {CANVAS}
   */
  this.canvas = Utils.isString(options.dom) ?
    document.getElementById(options.dom) ||
    document.querySelector(options.dom) :
    options.dom;

  this.realWidth = options.width || this.canvas.width;
  this.realHeight = options.height || this.canvas.height;

  /**
   * 场景的canvas的绘图环境
   *
   * @member {context2d}
   */
  this.ctx = this.canvas.getContext('2d');
  this.canvas.style.backgroundColor = options.backgroundColor || 'transparent';

  /**
   * 场景是否自动清除上一帧的像素内容
   *
   * @member {Boolean}
   */
  this.autoClear = true;

  /**
   * 场景是否应用style控制宽高
   *
   * @member {Boolean}
   */
  this.autoStyle = false;

  /**
   * 整个场景的初始矩阵
   */
  this.rootMatrix = new Matrix();

  /**
   * 场景分辨率
   *
   * @member {Number}
   * @private
   */
  this._resolution = 0;


  /**
   * 场景分辨率
   *
   * @member {Number}
   */
  this.resolution = options.resolution || 1;

  /**
   * canvas的宽度
   *
   * @member {Number}
   */
  this.width = this.canvas.width = this.realWidth * this.resolution;

  /**
   * canvas的高度
   *
   * @member {Number}
   */
  this.height = this.canvas.height = this.realHeight * this.resolution;

  this.interactionManager = new InteractionManager(this);

  /**
   * 舞台是否可交互
   *
   * @member {Boolean}
   * @private
   */
  this._enableinteractive = null;

  // update interaction in every tick
  const interactionUpdate = (snippet) => {
    this.interactionManager.update(snippet);
  };

  this.interactiveOnChange = function() {
    if (this.enableinteractive) {
      this.on('prerender', interactionUpdate);
      this.interactionManager.addEvents();
    } else {
      this.off('prerender', interactionUpdate);
      this.interactionManager.removeEvents();
    }
  };

  this.enableinteractive = Utils.isBoolean(options.interactive) ?
    options.interactive :
    true;

  this.currentScene = null;
}

Renderer.prototype.clear = function() {
  this.ctx.setTransform(1, 0, 0, 1, 0, 0);
  this.ctx.clearRect(0, 0, this.width, this.height);
};

Renderer.prototype.render = function(scene, snippet) {
  this.currentScene = scene;

  this.emit('preupdate', snippet);
  this.currentScene.updateTimeline(snippet);
  this.currentScene.updatePosture(this.rootMatrix);
  this.emit('postupdate', snippet);


  this.emit('prerender', snippet);
  if (this.autoClear) this.clear();
  this.currentScene.render(this.ctx);
  this.emit('postrender', snippet);
};

/**
 * 舞台尺寸设置
 *
 * @param {number} w canvas的width值
 * @param {number} h canvas的height值
 */
Renderer.prototype.resize = function(w, h) {
  if (Utils.isNumber(w) && Utils.isNumber(h)) {
    this.realWidth = w;
    this.realHeight = h;
  } else {
    w = this.realWidth;
    h = this.realHeight;
  }
  this.width = this.canvas.width = w * this.resolution;
  this.height = this.canvas.height = h * this.resolution;
};

/**
 * proxy this.interactionManager event-emit
 * Emit an event to all registered event listeners.
 *
 * @param {String} event The name of the event.
 */
Renderer.prototype.emit = function(...args) {
  this.interactionManager.emit(...args);
};

/**
 * proxy this.interactionManager event-on
 * Register a new EventListener for the given event.
 *
 * @param {String} event Name of the event.
 * @param {Function} fn Callback function.
 * @param {Mixed} [context=this] The context of the function.
 */
Renderer.prototype.on = function(...args) {
  this.interactionManager.on(...args);
};

/**
 * proxy this.interactionManager event-once
 * Add an EventListener that's only called once.
 *
 * @param {String} event Name of the event.
 * @param {Function} fn Callback function.
 * @param {Mixed} [context=this] The context of the function.
 */
Renderer.prototype.once = function(...args) {
  this.interactionManager.once(...args);
};

/**
 * proxy this.interactionManager event-off
 * @param {String} event The event we want to remove.
 * @param {Function} fn The listener that we need to find.
 * @param {Mixed} context Only remove listeners matching this context.
 * @param {Boolean} once Only remove once listeners.
 */
Renderer.prototype.off = function(...args) {
  this.interactionManager.off(...args);
};

/**
 * 场景设置分辨率
 *
 * @member {Number}
 * @name resolution
 * @memberof JC.Renderer#
 */
Object.defineProperty(Renderer.prototype, 'resolution', {
  get: function() {
    return this._resolution;
  },
  set: function(value) {
    if (this._resolution !== value) {
      this._resolution = value;
      this.rootMatrix.identity().scale(value, value);
      this.resize();
    }
  },
});

/**
 * 标记场景是否可交互,涉及到是否进行事件检测
 *
 * @member {Boolean}
 * @name enableinteractive
 * @memberof JC.Renderer#
 */
Object.defineProperty(Renderer.prototype, 'enableinteractive', {
  get: function() {
    return this._enableinteractive;
  },
  set: function(value) {
    if (this._enableinteractive !== value) {
      this._enableinteractive = value;
      this.interactiveOnChange();
    }
  },
});

export {Renderer};