Source: Gamepad.js

import * as BAGEL from "./index.js";

/**
 * Handle gamepad input.
 *  <br/>
 * Assumes Standard Gamepad Layout, as described at
 * {@link https://w3c.github.io/gamepad/#dfn-standard-gamepad}
 * @constructor
 */
class Gamepad
{
	/**
	 * Initialize required variables and set up listeners.
	 */
	constructor()
	{
		this.active = false;
		this.index = null;

		let self = this;

		let connected = function(gamepadEventData)
		{
    		self.index = gamepadEventData.gamepad.index;
    		self.active = true;
    		// console.log("gamepad connected");
		}
		window.addEventListener( "gamepadconnected", connected );
		
		let disconnected = function(gamepadEventData)
		{
			self.active = false;
    		// console.log("gamepad disconnected");
		}

		window.addEventListener( "gamepaddisconnected", disconnected );

		// store raw data from browser gamepad object
		this.gamepadButtons = [];
		this.gamepadAxes    = [];

		// Standard Gamepad Mappings

		this.buttonMapping = 
		{
		  "A"      :  0, 
		  "B"      :  1, 
		  "X"      :  2, 
		  "Y"      :  3, 
		  "LB"     :  4, // Left Shoulder Button 
		  "RB"     :  5, // Right Shoulder Button
		  "LT"     :  6, // Left Trigger
		  "RT"     :  7, // Right Trigger
		  "Select" :  8, // also "Back" on some controllers
		  "Start"  :  9, 
		  "Up"     : 12, // Directional Pad
		  "Down"   : 13, // Directional Pad
		  "Left"   : 14, // Directional Pad
		  "Right"  : 15  // Directional Pad
		};

		this.axisMapping =
		{
			"Axis1X" : 0, 
			"Axis1Y" : 1,
		    "Axis2X" : 2, 
		    "Axis2Y" : 3
		};

		this.buttonFullNames = [
			"A button", "B button", "X button", "Y button",
			"Left Shoulder button", "Right Shoulder button",
			"Left Trigger", "Right Trigger",
			"Back/Select button", "Start button",
			"Directional Pad - Up", "Directional Pad - Down", 
			"Directional Pad - Left", "Directional Pad - Right"
		];

		this.axisShortNames = [
		  
		];

		this.buttonPressedSet  = new Set();
		this.buttonPressingSet = new Set();
		this.buttonReleasedSet = new Set();

		this.deadZone = 0.10;
	}

	/**
	 *  Update button state data. 
	 *  <br/>
	 *  This method should be called once per frame by the {@link Game} class. 
	 */
	update()
	{
		if ( !this.active )
		    return;

		// Firefox links axes/button array references, 
		//  but Chrome does not, 
		//  so the gamepad object must be re-polled
		this.gamepadAxes = navigator.getGamepads()[this.index].axes;
		this.gamepadButtons = navigator.getGamepads()[this.index].buttons;

		// clear previous discrete event status
		this.buttonPressedSet.clear();
		this.buttonReleasedSet.clear();

		for (let i = 0; i < this.gamepadButtons.length; i++)
		{
			// which buttons were just pressed on gamepad since last update?
			if ( this.gamepadButtons[i].pressed && !this.buttonPressingSet.has(i) )
			{
				this.buttonPressedSet.add(i);
				this.buttonPressingSet.add(i);
			}

			// which buttons are no longer being pressed on gamepad since last update?
			if ( !this.gamepadButtons[i].pressed && this.buttonPressingSet.has(i) )
			{
				this.buttonPressingSet.delete(i);
				this.buttonReleasedSet.add(i);
			}
		}
	}

	/**
	 * Check if a button was just pressed; only true for a single frame after button is pressed.
	 * @param {string} buttonName - the name of the button to check
	 * @return {boolean} true if the button was just pressed
	 */
	buttonPressed(buttonName)
	{
		let buttonIndex = this.buttonMapping[buttonName];
        return this.buttonPressedSet.has(buttonIndex);
	}

	/**
	 * Check if a button is currently down; true for the duration between button pressed and button released.
	 * @param {string} buttonName - the name of the button to check
	 * @return {boolean} true if the button is currently down
	 */
	buttonPressing(buttonName)
	{
		let buttonIndex = this.buttonMapping[buttonName];
		return this.buttonPressingSet.has(buttonIndex);
	}

	/**
	 * Check if a button was just pressed; only true for a single frame after button is released.
	 * @param {string} buttonName - the name of the button to check
	 * @return {boolean} true if the button was just released
	 */
	buttonReleased(buttonName)
	{
		let buttonIndex = this.buttonMapping[buttonName];
		return this.buttonReleasedSet.has(buttonIndex);
	}

	/**
	 * Get value representing position of axis, from lowest value (-1) to highest value (+1).
	 * @param {string} axisName - the name of the axis to check
	 * @return {number} value of axis
	 */
    getAxisValue(axisName)
    {
  	  let axisIndex = this.axisMapping[axisName];
  	  let axisValue = this.gamepadAxes[axisIndex];

  	  // values less than this.deadZone are mapped to 0
  	  if ( Math.abs(axisValue) < this.deadZone )
  	  	return 0;

  	  return axisValue;
    }

	/**
	 * Get a Vector containing both axis values of a joystick.
	 * <br/>
	 * (May be useful to simplify calculating length and angle of joystick position.)
	 * @param {number} joystickNumber - left joystick = 1, right joystick = 2
	 * @return {Vector} vector containing both axis values of specified joystick
	 */
    getJoystickVector(joystickNumber)
    {
  	  if (joystickNumber == 1)
  	  	return new BAGEL.Vector(
  	  		this.getAxisValue("Axis1X"), 
  	  		this.getAxisValue("Axis1Y") );
  	  else if (joystickNumber == 2)
  	  	return new BAGEL.Vector(
  	  		this.getAxisValue("Axis2X"), 
  	  		this.getAxisValue("Axis2Y") );
  	  else
  	  	return new BAGEL.Vector(0, 0);
    }

}

export { Gamepad };