/**
* LittleJS Input System
* <br> - Tracks key down, pressed, and released
* <br> - Also tracks mouse buttons, position, and wheel
* <br> - Supports multiple gamepads
* @namespace Input
*/
'use strict';
/** Returns true if device key is down
* @param {Number} key
* @param {Number} [device=0]
* @return {Boolean}
* @memberof Input */
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
/** Returns true if device key was pressed this frame
* @param {Number} key
* @param {Number} [device=0]
* @return {Boolean}
* @memberof Input */
const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
/** Returns true if device key was released this frame
* @param {Number} key
* @param {Number} [device=0]
* @return {Boolean}
* @memberof Input */
const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][key] & 4 ? 1 : 0;
/** Clears all input
* @memberof Input */
const clearInput = ()=> inputData[0] = [];
/** Returns true if mouse button is down
* @param {Number} button
* @return {Boolean}
* @memberof Input */
const mouseIsDown = keyIsDown;
/** Returns true if mouse button was pressed
* @param {Number} button
* @return {Boolean}
* @memberof Input */
const mouseWasPressed = keyWasPressed;
/** Returns true if mouse button was released
* @param {Number} button
* @return {Boolean}
* @memberof Input */
const mouseWasReleased = keyWasReleased;
/** Mouse pos in world space
* @type {Vector2}
* @memberof Input */
let mousePos = vec2();
/** Mouse pos in screen space
* @type {Vector2}
* @memberof Input */
let mousePosScreen = vec2();
/** Mouse wheel delta this frame
* @memberof Input */
let mouseWheel = 0;
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
* @memberof Input */
let isUsingGamepad = 0;
/** Returns true if gamepad button is down
* @param {Number} button
* @param {Number} [gamepad=0]
* @return {Boolean}
* @memberof Input */
const gamepadIsDown = (button, gamepad=0)=> keyIsDown(button, gamepad+1);
/** Returns true if gamepad button was pressed
* @param {Number} button
* @param {Number} [gamepad=0]
* @return {Boolean}
* @memberof Input */
const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed(button, gamepad+1);
/** Returns true if gamepad button was released
* @param {Number} button
* @param {Number} [gamepad=0]
* @return {Boolean}
* @memberof Input */
const gamepadWasReleased = (button, gamepad=0)=> keyWasReleased(button, gamepad+1);
/** Returns gamepad stick value
* @param {Number} stick
* @param {Number} [gamepad=0]
* @return {Vector2}
* @memberof Input */
const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
///////////////////////////////////////////////////////////////////////////////
// Input update called by engine
const inputData = [[]];
function inputUpdate()
{
// clear input when lost focus (prevent stuck keys)
document.hasFocus() || clearInput();
// update mouse world space position
mousePos = screenToWorld(mousePosScreen);
// update gamepads if enabled
gamepadsUpdate();
}
function inputUpdatePost()
{
// clear input to prepare for next frame
for (const deviceInputData of inputData)
for (const i in deviceInputData)
deviceInputData[i] &= 1;
mouseWheel = 0;
}
///////////////////////////////////////////////////////////////////////////////
// Keyboard event handlers
onkeydown = e=>
{
if (debug && e.target != document.body) return;
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
debug || e.preventDefault();
}
onkeyup = e=>
{
if (debug && e.target != document.body) return;
inputData[0][remapKeyCode(e.keyCode)] = 4;
}
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
///////////////////////////////////////////////////////////////////////////////
// Mouse event handlers
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
onmousemove = e=>
{
// convert mouse pos to canvas space
if (!mainCanvas) return;
const rect = mainCanvas.getBoundingClientRect();
mousePosScreen.x = mainCanvasSize.x * percent(e.x, rect.right, rect.left);
mousePosScreen.y = mainCanvasSize.y * percent(e.y, rect.bottom, rect.top);
}
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
oncontextmenu = e=> !1; // prevent right click menu
///////////////////////////////////////////////////////////////////////////////
// Gamepad input
const stickData = [];
function gamepadsUpdate()
{
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
return;
// poll gamepads
const gamepads = navigator.getGamepads();
for (let i = gamepads.length; i--;)
{
// get or create gamepad data
const gamepad = gamepads[i];
const data = inputData[i+1] || (inputData[i+1] = []);
const sticks = stickData[i] || (stickData[i] = []);
if (gamepad)
{
// read clamp dead zone of analog sticks
const deadZone = .3, deadZoneMax = .8;
const applyDeadZone = (v)=>
v > deadZone ? percent( v, deadZoneMax, deadZone) :
v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
// read analog sticks
for (let j = 0; j < gamepad.axes.length-1; j+=2)
sticks[j>>1] = vec2(applyDeadZone(gamepad.axes[j]), applyDeadZone(-gamepad.axes[j+1])).clampLength();
// read buttons
for (let j = gamepad.buttons.length; j--;)
{
const button = gamepad.buttons[j];
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
isUsingGamepad |= !i && button.pressed;
}
if (gamepadDirectionEmulateStick)
{
// copy dpad to left analog stick when pressed
const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
if (dpad.lengthSquared())
sticks[0] = dpad.clampLength();
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
// Touch input
/** True if a touch device has been detected
* @const {boolean}
* @memberof Input */
const isTouchDevice = inputTouchEnable && window.ontouchstart !== undefined;
if (isTouchDevice)
{
// handle all touch events the same way
let wasTouching, hadTouchInput;
ontouchstart = ontouchmove = ontouchend = e=>
{
e.button = 0; // all touches are left click
// check if touching and pass to mouse events
const touching = e.touches.length;
if (touching)
{
hadTouchInput || zzfx(0, hadTouchInput=1) ; // fix mobile audio, force it to play a sound the first time
// set event pos and pass it along
e.x = e.touches[0].clientX;
e.y = e.touches[0].clientY;
wasTouching ? onmousemove(e) : onmousedown(e);
}
else if (wasTouching)
onmouseup(e);
// set was touching
wasTouching = touching;
// prevent normal mouse events from being called
return !e.cancelable;
}
}