text: /**
   * The renderer for a gdjs.RuntimeGame using Pixi.js.
   * @class RuntimeGamePixiRenderer
   * @memberof gdjs
   * @param {gdjs.RuntimeGame} game The game that is being rendered
   * @param {boolean} forceFullscreen If fullscreen should be always activated
   */
   gdjs.RuntimeGamePixiRenderer = function(game, forceFullscreen) {
    this._game = game;
  
    this._isFullPage = false; //Used to track if the canvas is displayed on the full page.
    this._isFullscreen = false; //Used to track if the window is displayed as fullscreen (see setFullscreen method).
    this._forceFullscreen = forceFullscreen; //If set to true, the canvas will always be displayed as fullscreen, even if _isFullscreen == false.
  
    /** @type {?PIXI.Renderer} */
    this._pixiRenderer = null;
    this._canvasWidth = 0; // Current width of the canvas (might be scaled down/up compared to renderer)
    this._canvasHeight = 0; // Current height of the canvas (might be scaled down/up compared to renderer)
    this._keepRatio = true;
    this._marginLeft = this._marginTop = this._marginRight = this._marginBottom = 0;
  };
  
  gdjs.RuntimeGameRenderer = gdjs.RuntimeGamePixiRenderer; //Register the class to let the engine use it.
  
  /**
   * Create a standard canvas inside canvasArea.
   *
   */
  gdjs.RuntimeGamePixiRenderer.prototype.createStandardCanvas = function(
    parentElement
  ) {
    //Create the renderer and setup the rendering area
    //"preserveDrawingBuffer: true" is needed to avoid flickering and background issues on some mobile phones (see #585 #572 #566 #463)
    this._pixiRenderer = PIXI.autoDetectRenderer(
      {
        width: this._game.getGameResolutionWidth(),
        height: this._game.getGameResolutionHeight(),
        preserveDrawingBuffer: true,
        antialias: false,
      }
    );
    parentElement.appendChild(this._pixiRenderer.view); // add the renderer view element to the DOM
    this._pixiRenderer.view.style['position'] = 'relative';
    this._pixiRenderer.view.tabIndex = 1; //Ensure that the canvas has the focus.
    this._resizeCanvas();
  
    // Handle scale mode
    if (this._game.getScaleMode() === 'nearest') {
      this._pixiRenderer.view.style['image-rendering'] = '-moz-crisp-edges';
      this._pixiRenderer.view.style['image-rendering'] =
        '-webkit-optimize-contrast';
      this._pixiRenderer.view.style['image-rendering'] = '-webkit-crisp-edges';
      this._pixiRenderer.view.style['image-rendering'] = 'pixelated';
    }
  
    //Handle resize
    var that = this;
    window.addEventListener('resize', function() {
      that._game.onWindowInnerSizeChanged();
      that._resizeCanvas();
      that._game._notifySceneForResize = true;
    });
  
    return this._pixiRenderer;
  };
  
  gdjs.RuntimeGamePixiRenderer.getWindowInnerWidth = function() {
    return typeof window !== 'undefined' ? window.innerWidth : 800;
  };
  
  gdjs.RuntimeGamePixiRenderer.getWindowInnerHeight = function() {
    return typeof window !== 'undefined' ? window.innerHeight : 800;
  };
  
  /**
   * Update the game renderer size according to the "game resolution".
   * Called when game resolution changes.
   *
   * Note that if the canvas is fullscreen, it won't be resized, but when going back to
   * non fullscreen mode, the requested size will be used.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.updateRendererSize = function() {
    this._resizeCanvas();
  };
  
  /**
   * Resize the renderer (the "game resolution") and the canvas (which can be larger
   * or smaller to fill the page, with optional margins).
   *
   * @private
   */
  gdjs.RuntimeGamePixiRenderer.prototype._resizeCanvas = function() {
    // Set the Pixi renderer size to the game size.
    // There is no "smart" resizing to be done here: the rendering of the game
    // should be done with the size set on the game.
    if (
      this._pixiRenderer.width !== this._game.getGameResolutionWidth() ||
      this._pixiRenderer.height !== this._game.getGameResolutionHeight()
    ) {
      this._pixiRenderer.resize(
        this._game.getGameResolutionWidth(),
        this._game.getGameResolutionHeight()
      );
    }
  
    // Set the canvas size.
    // Resizing is done according to the settings. This is a "CSS" resize
    // only, so won't create visual artifacts during the rendering.
    var isFullPage =
      this._forceFullscreen || this._isFullPage || this._isFullscreen;
    var canvasWidth = this._game.getGameResolutionWidth();
    var canvasHeight = this._game.getGameResolutionHeight();
    var maxWidth = window.innerWidth - this._marginLeft - this._marginRight;
    var maxHeight = window.innerHeight - this._marginTop - this._marginBottom;
    if (maxWidth < 0) maxWidth = 0;
    if (maxHeight < 0) maxHeight = 0;
  
    if (isFullPage && !this._keepRatio) {
      canvasWidth = maxWidth;
      canvasHeight = maxHeight;
    } else if (
      (isFullPage && this._keepRatio) ||
      canvasWidth > maxWidth ||
      canvasHeight > maxHeight
    ) {
      var factor = maxWidth / canvasWidth;
      if (canvasHeight * factor > maxHeight) factor = maxHeight / canvasHeight;
  
      canvasWidth *= factor;
      canvasHeight *= factor;
    }
  
    this._pixiRenderer.view.style['top'] = 0;//this._marginTop + (maxHeight - canvasHeight) / 2 + 'px';
    this._pixiRenderer.view.style['left'] = 0;//this._marginLeft + (maxWidth - canvasWidth) / 2 + 'px';
    this._pixiRenderer.view.style.width = canvasWidth + 'px';
    this._pixiRenderer.view.style.height = canvasHeight + 'px';
  
    // Store the canvas size for fast access to it.
    this._canvasWidth = canvasWidth;
    this._canvasHeight = canvasHeight;
  };
  
  /**
   * Set if the aspect ratio must be kept when the game canvas is resized to fill
   * the page.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.keepAspectRatio = function(enable) {
    if (this._keepRatio === enable) return;
  
    this._keepRatio = enable;
    this._resizeCanvas();
    this._game._notifySceneForResize = true;
  };
  
  /**
   * Change the margin that must be preserved around the game canvas.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.setMargins = function(
    top,
    right,
    bottom,
    left
  ) {
    if (
      this._marginTop === top &&
      this._marginRight === right &&
      this._marginBottom === bottom &&
      this._marginLeft === left
    )
      return;
  
    this._marginTop = top;
    this._marginRight = right;
    this._marginBottom = bottom;
    this._marginLeft = left;
    this._resizeCanvas();
    this._game._notifySceneForResize = true;
  };
  
  /**
   * Update the window size, if possible.
   * @param {number} width The new width, in pixels.
   * @param {number} height The new height, in pixels.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.setWindowSize = function(width, height) {
    var electron = this.getElectron();
    if (electron) {
      // Use Electron BrowserWindow API
      var browserWindow = electron.remote.getCurrentWindow();
      if (browserWindow) {
        browserWindow.setContentSize(width, height);
      }
    } else {
      console.warn("Window size can't be changed on this platform.");
    }
  };
  
  /**
   * Center the window on screen.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.centerWindow = function() {
    var electron = this.getElectron();
    if (electron) {
      // Use Electron BrowserWindow API
      var browserWindow = electron.remote.getCurrentWindow();
      if (browserWindow) {
        browserWindow.center();
      }
    } else {
      // Not supported
    }
  };
  
  /**
   * De/activate fullscreen for the game.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.setFullScreen = function(enable) {
    if (this._forceFullscreen) return;
  
    if (this._isFullscreen !== enable) {
      this._isFullscreen = !!enable;
  
      var electron = this.getElectron();
      if (electron) {
        // Use Electron BrowserWindow API
        var browserWindow = electron.remote.getCurrentWindow();
        if (browserWindow) {
          browserWindow.setFullScreen(this._isFullscreen);
        }
      } else {
        // Use HTML5 Fullscreen API
        //TODO: Do this on a user gesture, otherwise most browsers won't activate fullscreen
        if (this._isFullscreen) {
          if (document.documentElement.requestFullScreen) {
            document.documentElement.requestFullScreen();
          } else if (document.documentElement.mozRequestFullScreen) {
            document.documentElement.mozRequestFullScreen();
          } else if (document.documentElement.webkitRequestFullScreen) {
            document.documentElement.webkitRequestFullScreen();
          }
        } else {
          if (document.cancelFullScreen) {
            document.cancelFullScreen();
          } else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
          } else if (document.webkitCancelFullScreen) {
            document.webkitCancelFullScreen();
          }
        }
      }
  
      this._resizeCanvas();
      this._notifySceneForResize = true;
    }
  };
  
  /**
   * Add the standard events handler.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.bindStandardEvents = function(
    manager,
    window,
    document
  ) {
    var renderer = this._pixiRenderer;
    var canvas = renderer.view;
  
    //Translate an event (mouse or touch) made on the canvas on the page
    //to game coordinates.
    var that = this;
    function getEventPosition(e) {
      var pos = [0, 0];
      if (e.pageX) {
        pos[0] = e.pageX - canvas.offsetLeft;
        pos[1] = e.pageY - canvas.offsetTop;
      } else {
        pos[0] =
          e.clientX +
          document.body.scrollLeft +
          document.documentElement.scrollLeft -
          canvas.offsetLeft;
        pos[1] =
          e.clientY +
          document.body.scrollTop +
          document.documentElement.scrollTop -
          canvas.offsetTop;
      }
  
      //Handle the fact that the game is stretched to fill the canvas.
      pos[0] *= that._game.getGameResolutionWidth() / (that._canvasWidth || 1);
      pos[1] *= that._game.getGameResolutionHeight() / (that._canvasHeight || 1);
  
      return pos;
    }
  
    //Some browsers lacks definition of some variables used to do calculations
    //in getEventPosition. They are defined to 0 as they are useless.
    (function ensureOffsetsExistence() {
      if (isNaN(canvas.offsetLeft)) {
        canvas.offsetLeft = 0;
        canvas.offsetTop = 0;
      }
      if (isNaN(document.body.scrollLeft)) {
        document.body.scrollLeft = 0;
        document.body.scrollTop = 0;
      }
      if (
        document.documentElement === undefined ||
        document.documentElement === null
      ) {
        document.documentElement = {};
      }
      if (isNaN(document.documentElement.scrollLeft)) {
        document.documentElement.scrollLeft = 0;
        document.documentElement.scrollTop = 0;
      }
      if (isNaN(canvas.offsetLeft)) {
        canvas.offsetLeft = 0;
        canvas.offsetTop = 0;
      }
    })();
  
    //Keyboard
    document.onkeydown = function(e) {
      manager.onKeyPressed(e.keyCode, e.location);
    };
    document.onkeyup = function(e) {
      manager.onKeyReleased(e.keyCode, e.location);
    };
    //Mouse
    renderer.view.onmousemove = function(e) {
      var pos = getEventPosition(e);
      manager.onMouseMove(pos[0], pos[1]);
    };
    renderer.view.onmousedown = function(e) {
      manager.onMouseButtonPressed(
        e.button === 2
          ? gdjs.InputManager.MOUSE_RIGHT_BUTTON
          : e.button === 1
          ? gdjs.InputManager.MOUSE_MIDDLE_BUTTON
          : gdjs.InputManager.MOUSE_LEFT_BUTTON
      );
      if (window.focus !== undefined) window.focus();
      return false;
    };
    renderer.view.onmouseup = function(e) {
      manager.onMouseButtonReleased(
        e.button === 2
          ? gdjs.InputManager.MOUSE_RIGHT_BUTTON
          : e.button === 1
          ? gdjs.InputManager.MOUSE_MIDDLE_BUTTON
          : gdjs.InputManager.MOUSE_LEFT_BUTTON
      );
      return false;
    };
    window.addEventListener(
      'click',
      function(e) {
        if (window.focus !== undefined) window.focus();
        e.preventDefault();
        return false;
      },
      false
    );
    renderer.view.oncontextmenu = function(event) {
      event.preventDefault();
      event.stopPropagation();
      return false;
    };
    renderer.view.onmousewheel = function(event) {
      manager.onMouseWheel(event.wheelDelta);
    };
    //Touches
    //Also simulate mouse events when receiving touch events
    window.addEventListener('touchmove', function(e) {
      e.preventDefault();
      if (e.changedTouches) {
        for (var i = 0; i < e.changedTouches.length; ++i) {
          var pos = getEventPosition(e.changedTouches[i]);
          manager.onTouchMove(e.changedTouches[i].identifier, pos[0], pos[1]);
        }
      }
    });
    window.addEventListener('touchstart', function(e) {
      e.preventDefault();
      if (e.changedTouches) {
        for (var i = 0; i < e.changedTouches.length; ++i) {
          var pos = getEventPosition(e.changedTouches[i]);
          manager.onTouchStart(e.changedTouches[i].identifier, pos[0], pos[1]);
        }
      }
      return false;
    });
    window.addEventListener('touchend', function(e) {
      e.preventDefault();
      if (e.changedTouches) {
        for (var i = 0; i < e.changedTouches.length; ++i) {
          var pos = getEventPosition(e.changedTouches[i]);
          manager.onTouchEnd(e.changedTouches[i].identifier);
        }
      }
      return false;
    });
  };
  
  gdjs.RuntimeGamePixiRenderer.prototype.setWindowTitle = function(title) {
    if (typeof document !== 'undefined') document.title = title;
  };
  
  gdjs.RuntimeGamePixiRenderer.prototype.getWindowTitle = function() {
    return typeof document !== 'undefined' ? document.title : '';
  };
  
  gdjs.RuntimeGamePixiRenderer.prototype.startGameLoop = function(fn) {
    requestAnimationFrame(gameLoop);
  
    var oldTime = 0;
    function gameLoop(time) {
      var dt = oldTime ? time - oldTime : 0;
      oldTime = time;
  
      if (fn(dt)) requestAnimationFrame(gameLoop);
    }
  };
  
  gdjs.RuntimeGamePixiRenderer.prototype.getPIXIRenderer = function() {
    return this._pixiRenderer;
  };
  
  /**
   * Open the given URL in the system browser (or a new tab)
   */
  gdjs.RuntimeGamePixiRenderer.prototype.openURL = function(url) {
    // Try to detect the environment to use the most adapted
    // way of opening an URL.
    if (typeof Cocoon !== 'undefined' && Cocoon.App && Cocoon.App.openURL) {
      Cocoon.App.openURL(url);
    } else if (typeof window !== 'undefined') {
      var target = window.cordova ? '_system' : '_blank';
      window.open(url, target);
    }
  };
  
  /**
   * Close the game, if applicable
   */
  gdjs.RuntimeGamePixiRenderer.prototype.stopGame = function() {
    // Try to detect the environment to use the most adapted
    // way of closing the app
    var electron = this.getElectron();
    if (electron) {
      var browserWindow = electron.remote.getCurrentWindow();
      if (browserWindow) {
        browserWindow.close();
      }
    } else if (
      typeof navigator !== 'undefined' &&
      navigator.app &&
      navigator.app.exitApp
    ) {
      navigator.app.exitApp();
    }
  
    // HTML5 games on mobile/browsers don't have a way to close their window/page.
  };
  
  /**
   * Get the canvas DOM element.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.getCanvas = function() {
    return this._pixiRenderer.view;
  };
  
  /**
   * Check if the device supports WebGL.
   * @returns {boolean} true if WebGL is supported
   */
  gdjs.RuntimeGamePixiRenderer.prototype.isWebGLSupported = function() {
    return this._pixiRenderer.type === PIXI.RENDERER_TYPE.WEBGL;
  };
  
  /**
   * Get the electron module, if running as a electron renderer process.
   */
  gdjs.RuntimeGamePixiRenderer.prototype.getElectron = function() {
    if (typeof require !== 'undefined') {
      return require('electron');
    }
  
    return null;
  };
  