Socket
Socket
Sign inDemoInstall

contro

Package Overview
Dependencies
0
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.3.0 to 2.0.0

371

dist/contro.esm.js

@@ -13,8 +13,3 @@ /*!

var MouseButton;
(function (MouseButton) {
MouseButton[MouseButton["Left"] = 0] = "Left";
MouseButton[MouseButton["Middle"] = 1] = "Middle";
MouseButton[MouseButton["Right"] = 2] = "Right";
})(MouseButton || (MouseButton = {}));
const mouseButtons = ['left', 'middle', 'right'];
class Mouse {

@@ -29,4 +24,5 @@ constructor({ canvas, doc = document }) {

this.document = doc;
let on = this.canvas.addEventListener.bind(this.canvas);
const on = this.canvas.addEventListener.bind(this.canvas);
on('mousedown', (event) => {
store.preferGamepad = false;
this.pressedButtons.add(event.button);

@@ -36,2 +32,3 @@ this.queuedButtons.add(event.button);

on('mouseup', (event) => {
store.preferGamepad = false;
this.pressedButtons.delete(event.button);

@@ -41,2 +38,3 @@ this.queuedButtons.delete(event.button);

on('mousemove', (event) => {
store.preferGamepad = false;
this.pointerMovement.x += event.movementX;

@@ -46,27 +44,69 @@ this.pointerMovement.y += event.movementY;

on('wheel', (event) => {
store.preferGamepad = false;
const distance = event.deltaY;
this.scrollDistance += distance;
});
on = this.document.addEventListener.bind(this.document);
}
isPressed(button = MouseButton.Left) {
return this.pressedButtons.has(button);
}
wasPressed(button = MouseButton.Left) {
if (this.queuedButtons.has(button)) {
this.queuedButtons.delete(button);
return true;
parseButton(button) {
if (typeof button === 'string') {
if (mouseButtons.includes(button)) {
return mouseButtons.indexOf(button);
}
else {
throw new Error(`There is no mouse button called "${button}"!`);
}
}
return false;
else {
if (button < mouseButtons.length) {
return button;
}
else {
throw new Error(`There is no mouse button with the index ${button}!`);
}
}
}
getPointerMovement() {
const movement = this.pointerMovement;
this.pointerMovement = new Vector2(0, 0);
return movement;
button(button) {
const that = this;
button = this.parseButton(button);
return {
label: ['Left', 'Middle', 'Right'][button] + ' Mouse Button',
query() {
button = that.parseButton(button);
if (!this.trigger) {
if (that.queuedButtons.has(button)) {
that.queuedButtons.delete(button);
return true;
}
return false;
}
else {
return that.pressedButtons.has(button);
}
},
get trigger() {
delete this.trigger;
return this;
},
};
}
getScrollDistance() {
const distance = this.scrollDistance;
this.scrollDistance = 0;
return distance;
pointer() {
return {
label: 'Cursor',
query: () => {
const movement = this.pointerMovement;
this.pointerMovement = new Vector2(0, 0);
return movement;
},
};
}
wheel() {
return {
label: 'Mouse wheel',
query: () => {
const distance = this.scrollDistance;
this.scrollDistance = 0;
return distance;
},
};
}
lockPointer() {

@@ -83,5 +123,43 @@ this.canvas.requestPointerLock();

/**
* A map of all the supported key values (property names) and their respective
* aliases (property values) that can be used with the `Keyboard` class. The
* first alias for each key value will be used as a label.
*/
const keyMap = {
' ': ['Space', 'Spacebar', 'Space Bar'],
'AltGraph': ['Alt Gr'],
'ArrowDown': ['Down'],
'ArrowLeft': ['Left'],
'ArrowRight': ['Right'],
'ArrowUp': ['Up'],
'Backspace': ['Backspace'],
'Control': ['Ctrl', 'Ctl'],
'Delete': ['Delete', 'Del'],
'Enter': ['Enter', 'Return'],
'Escape': ['Escape', 'Esc'],
'Insert': ['Insert', 'Ins'],
'PageDown': ['Page Down', 'PgDown'],
'PageUp': ['Page Up', 'PgUp'],
'Tab': ['Tab'],
};
function findKeyValue(keyString) {
if (keyString.length === 1)
return keyString.toLowerCase();
Object.keys(keyMap).forEach(keyValue => {
keyMap[keyValue].forEach(key => {
if (keyString.toLowerCase() === key.toLowerCase()) {
keyString = keyValue;
}
});
});
return keyString;
}
function getKeyLabel(key) {
return key in keyMap ? keyMap[key][0] : (key.length === 1 ? key.toUpperCase() : key);
}
const arrowKeyTemplates = {
arrows: ['ArrowUp', 'ArrowLeft', 'ArrowDown', 'ArrowRight'],
wasd: ['W', 'A', 'S', 'D'],
arrows: ['Arrow keys', ['ArrowUp', 'ArrowLeft', 'ArrowDown', 'ArrowRight']],
wasd: ['WASD', ['W', 'A', 'S', 'D']],
};

@@ -95,47 +173,119 @@ class Keyboard {

this.document.addEventListener('keydown', (event) => {
const key = event.key.toLowerCase();
store.preferGamepad = false;
let key = event.key;
if (key === key.toUpperCase())
key = key.toLowerCase();
this.pressedKeys.add(key);
this.queuedKeys.add(key);
return false;
});
this.document.addEventListener('keyup', (event) => {
const key = event.key.toLowerCase();
store.preferGamepad = false;
let key = event.key;
if (key === key.toUpperCase())
key = key.toLowerCase();
this.pressedKeys.delete(key);
this.queuedKeys.delete(key);
return false;
});
}
isPressed(key) {
key = key.toLowerCase();
return this.pressedKeys.has(key);
key(key) {
const that = this;
key = findKeyValue(key);
return {
label: getKeyLabel(key),
query() {
return this.trigger ? that.pressedKeys.has(key) : that.queuedKeys.delete(key);
},
get trigger() {
delete this.trigger;
return this;
},
};
}
wasPressed(key) {
key = key.toLowerCase();
if (this.queuedKeys.has(key)) {
this.queuedKeys.delete(key);
return true;
directionalKeys(keys, label) {
let defaultLabel;
if (typeof keys === 'string') {
keys = keys.toLowerCase();
if (keys in arrowKeyTemplates) {
const template = arrowKeyTemplates[keys.toLowerCase()];
defaultLabel = template[0];
keys = template[1];
}
else {
throw new Error(`Directional key template "${keys}" not found!`);
}
}
return false;
}
getMovementVector(arrowKeys) {
if (typeof arrowKeys === 'string') {
arrowKeys = arrowKeys.toLowerCase();
if (arrowKeys in arrowKeyTemplates) {
arrowKeys = arrowKeyTemplates[arrowKeys];
else {
if (keys.length === 4) {
keys = keys.map(key => findKeyValue(key));
defaultLabel = keys.map(key => getKeyLabel(key)).join('');
}
else {
throw new Error(`Arrow key template "${arrowKeys}" not found!`);
throw new Error('Directional key templates have to consist of four keys!');
}
}
const vector = new Vector2();
if (this.isPressed(arrowKeys[0]))
vector.y -= 1;
if (this.isPressed(arrowKeys[1]))
vector.x -= 1;
if (this.isPressed(arrowKeys[2]))
vector.y += 1;
if (this.isPressed(arrowKeys[3]))
vector.x += 1;
return vector;
return {
label: label || defaultLabel,
query: () => {
const vector = new Vector2();
if (this.key(keys[0]).query())
vector.y -= 1;
if (this.key(keys[1]).query())
vector.x -= 1;
if (this.key(keys[2]).query())
vector.y += 1;
if (this.key(keys[3]).query())
vector.x += 1;
return vector;
},
};
}
}
/**
* A map of all the supported button numbers (array indices) and their
* respective aliases (the strings in the arrays) that can be used with the
* `Gamepad` class. The first alias for each button will be used as a label.
*/
const buttonMap = [
['A'],
['B'],
['X'],
['Y'],
['Left Bumper', 'LB'],
['Right Bumper', 'RB'],
['Left Trigger', 'LT'],
['Right Trigger', 'RT'],
['Back', 'View'],
['Start'],
['Left Stick'],
['Right Stick'],
['Up', 'DpadUp'],
['Down', 'DpadDown'],
['Left', 'DpadLeft'],
['Right', 'DpadRight'],
['Home', 'Guide', 'Xbox'],
];
function findButtonNumber(button) {
if (typeof button === 'number')
return button;
let buttonNumber = 0;
for (const buttonAliases of buttonMap) {
for (const buttonAlias of buttonAliases) {
if (button.toLowerCase() === buttonAlias.toLowerCase()) {
return buttonNumber;
}
}
buttonNumber++;
}
}
function getButtonLabel(button) {
return buttonMap[button][0];
}
const gamepadSticks = {
left: { xAxis: 0, yAxis: 1 },
right: { xAxis: 2, yAxis: 3 },
};
class Gamepad {

@@ -145,17 +295,15 @@ constructor(

this.pressedButtons = new Set();
this.gamepadTimestamp = 0;
this.window = win;
this.navigator = nav;
this.window.addEventListener('gamepadconnected', ({ gamepad }) => {
if (!this.isConnected())
if (!this.isConnected()) {
this.gamepadIndex = gamepad.index;
store.preferGamepad = true;
}
});
this.window.addEventListener('gamepaddisconnected', ({ gamepad }) => {
if (this.gamepadIndex === gamepad.index) {
const gamepads = this.navigator.getGamepads();
if (gamepads.length) {
this.gamepadIndex = this.navigator.getGamepads()[0].index;
}
else {
this.gamepadIndex = undefined;
}
this.gamepadIndex = undefined;
store.preferGamepad = false;
}

@@ -168,23 +316,96 @@ });

get gamepad() {
return this.navigator.getGamepads()[this.gamepadIndex];
const gamepad = this.navigator.getGamepads()[this.gamepadIndex];
if (gamepad.timestamp > this.gamepadTimestamp)
store.preferGamepad = true;
this.gamepadTimestamp = gamepad.timestamp;
return gamepad;
}
isPressed(button) {
return this.isConnected() && this.gamepad.buttons[button].pressed;
button(button) {
const that = this;
const buttonNumber = findButtonNumber(button);
return {
label: getButtonLabel(buttonNumber),
fromGamepad: true,
query() {
if (!that.isConnected())
return false;
if (!this.trigger) {
if (that.gamepad.buttons[buttonNumber].pressed) {
if (!that.pressedButtons.has(buttonNumber)) {
that.pressedButtons.add(buttonNumber);
return true;
}
}
else {
that.pressedButtons.delete(buttonNumber);
}
return false;
}
else {
return that.gamepad.buttons[buttonNumber].pressed;
}
},
get trigger() {
delete this.trigger;
return this;
},
};
}
wasPressed(button) {
if (this.isConnected()) {
if (this.gamepad.buttons[button].pressed) {
if (!this.pressedButtons.has(button)) {
this.pressedButtons.add(button);
return true;
}
stick(stick) {
if (typeof stick === 'string') {
if (stick in gamepadSticks) {
stick = gamepadSticks[stick];
}
else {
this.pressedButtons.delete(button);
throw new Error(`Gamepad stick "${stick}" not found!`);
}
}
return false;
return new Vector2(this.gamepad.axes[stick.xAxis], this.gamepad.axes[stick.yAxis]);
}
}
export { Mouse, MouseButton, Keyboard, Gamepad };
function and(...controls) {
if (controls.length < 2)
throw new Error('Less than two controls specified!');
return {
label: controls.map(control => control.label).join(' + '),
query: () => {
for (const control of controls) {
if (!control.query())
return false;
}
return true;
},
};
}
function or(...controls) {
if (controls.length < 2)
throw new Error('Less than two controls specified!');
return {
get label() {
return (controls.filter(control => control.fromGamepad).length === 0 ?
controls[0]
: store.preferGamepad ?
controls.filter(control => control.fromGamepad === true)[0]
: controls.filter(control => control.fromGamepad !== true)[0]).label;
},
query: () => {
let sampleQueryValue;
for (const control of controls) {
const queryValue = control.query();
sampleQueryValue = queryValue;
if (queryValue)
return queryValue;
}
if (typeof sampleQueryValue === 'boolean')
return false;
},
};
}
let store = {
preferGamepad: false,
};
export { store, Mouse, Keyboard, Gamepad, and, or };

@@ -19,7 +19,3 @@ /*!

(function (MouseButton) {
MouseButton[MouseButton["Left"] = 0] = "Left";
MouseButton[MouseButton["Middle"] = 1] = "Middle";
MouseButton[MouseButton["Right"] = 2] = "Right";
})(exports.MouseButton || (exports.MouseButton = {}));
const mouseButtons = ['left', 'middle', 'right'];
class Mouse {

@@ -34,4 +30,5 @@ constructor({ canvas, doc = document }) {

this.document = doc;
let on = this.canvas.addEventListener.bind(this.canvas);
const on = this.canvas.addEventListener.bind(this.canvas);
on('mousedown', (event) => {
store.preferGamepad = false;
this.pressedButtons.add(event.button);

@@ -41,2 +38,3 @@ this.queuedButtons.add(event.button);

on('mouseup', (event) => {
store.preferGamepad = false;
this.pressedButtons.delete(event.button);

@@ -46,2 +44,3 @@ this.queuedButtons.delete(event.button);

on('mousemove', (event) => {
store.preferGamepad = false;
this.pointerMovement.x += event.movementX;

@@ -51,27 +50,69 @@ this.pointerMovement.y += event.movementY;

on('wheel', (event) => {
store.preferGamepad = false;
const distance = event.deltaY;
this.scrollDistance += distance;
});
on = this.document.addEventListener.bind(this.document);
}
isPressed(button = exports.MouseButton.Left) {
return this.pressedButtons.has(button);
}
wasPressed(button = exports.MouseButton.Left) {
if (this.queuedButtons.has(button)) {
this.queuedButtons.delete(button);
return true;
parseButton(button) {
if (typeof button === 'string') {
if (mouseButtons.includes(button)) {
return mouseButtons.indexOf(button);
}
else {
throw new Error(`There is no mouse button called "${button}"!`);
}
}
return false;
else {
if (button < mouseButtons.length) {
return button;
}
else {
throw new Error(`There is no mouse button with the index ${button}!`);
}
}
}
getPointerMovement() {
const movement = this.pointerMovement;
this.pointerMovement = new Vector2(0, 0);
return movement;
button(button) {
const that = this;
button = this.parseButton(button);
return {
label: ['Left', 'Middle', 'Right'][button] + ' Mouse Button',
query() {
button = that.parseButton(button);
if (!this.trigger) {
if (that.queuedButtons.has(button)) {
that.queuedButtons.delete(button);
return true;
}
return false;
}
else {
return that.pressedButtons.has(button);
}
},
get trigger() {
delete this.trigger;
return this;
},
};
}
getScrollDistance() {
const distance = this.scrollDistance;
this.scrollDistance = 0;
return distance;
pointer() {
return {
label: 'Cursor',
query: () => {
const movement = this.pointerMovement;
this.pointerMovement = new Vector2(0, 0);
return movement;
},
};
}
wheel() {
return {
label: 'Mouse wheel',
query: () => {
const distance = this.scrollDistance;
this.scrollDistance = 0;
return distance;
},
};
}
lockPointer() {

@@ -88,5 +129,43 @@ this.canvas.requestPointerLock();

/**
* A map of all the supported key values (property names) and their respective
* aliases (property values) that can be used with the `Keyboard` class. The
* first alias for each key value will be used as a label.
*/
const keyMap = {
' ': ['Space', 'Spacebar', 'Space Bar'],
'AltGraph': ['Alt Gr'],
'ArrowDown': ['Down'],
'ArrowLeft': ['Left'],
'ArrowRight': ['Right'],
'ArrowUp': ['Up'],
'Backspace': ['Backspace'],
'Control': ['Ctrl', 'Ctl'],
'Delete': ['Delete', 'Del'],
'Enter': ['Enter', 'Return'],
'Escape': ['Escape', 'Esc'],
'Insert': ['Insert', 'Ins'],
'PageDown': ['Page Down', 'PgDown'],
'PageUp': ['Page Up', 'PgUp'],
'Tab': ['Tab'],
};
function findKeyValue(keyString) {
if (keyString.length === 1)
return keyString.toLowerCase();
Object.keys(keyMap).forEach(keyValue => {
keyMap[keyValue].forEach(key => {
if (keyString.toLowerCase() === key.toLowerCase()) {
keyString = keyValue;
}
});
});
return keyString;
}
function getKeyLabel(key) {
return key in keyMap ? keyMap[key][0] : (key.length === 1 ? key.toUpperCase() : key);
}
const arrowKeyTemplates = {
arrows: ['ArrowUp', 'ArrowLeft', 'ArrowDown', 'ArrowRight'],
wasd: ['W', 'A', 'S', 'D'],
arrows: ['Arrow keys', ['ArrowUp', 'ArrowLeft', 'ArrowDown', 'ArrowRight']],
wasd: ['WASD', ['W', 'A', 'S', 'D']],
};

@@ -100,47 +179,119 @@ class Keyboard {

this.document.addEventListener('keydown', (event) => {
const key = event.key.toLowerCase();
store.preferGamepad = false;
let key = event.key;
if (key === key.toUpperCase())
key = key.toLowerCase();
this.pressedKeys.add(key);
this.queuedKeys.add(key);
return false;
});
this.document.addEventListener('keyup', (event) => {
const key = event.key.toLowerCase();
store.preferGamepad = false;
let key = event.key;
if (key === key.toUpperCase())
key = key.toLowerCase();
this.pressedKeys.delete(key);
this.queuedKeys.delete(key);
return false;
});
}
isPressed(key) {
key = key.toLowerCase();
return this.pressedKeys.has(key);
key(key) {
const that = this;
key = findKeyValue(key);
return {
label: getKeyLabel(key),
query() {
return this.trigger ? that.pressedKeys.has(key) : that.queuedKeys.delete(key);
},
get trigger() {
delete this.trigger;
return this;
},
};
}
wasPressed(key) {
key = key.toLowerCase();
if (this.queuedKeys.has(key)) {
this.queuedKeys.delete(key);
return true;
directionalKeys(keys, label) {
let defaultLabel;
if (typeof keys === 'string') {
keys = keys.toLowerCase();
if (keys in arrowKeyTemplates) {
const template = arrowKeyTemplates[keys.toLowerCase()];
defaultLabel = template[0];
keys = template[1];
}
else {
throw new Error(`Directional key template "${keys}" not found!`);
}
}
return false;
}
getMovementVector(arrowKeys) {
if (typeof arrowKeys === 'string') {
arrowKeys = arrowKeys.toLowerCase();
if (arrowKeys in arrowKeyTemplates) {
arrowKeys = arrowKeyTemplates[arrowKeys];
else {
if (keys.length === 4) {
keys = keys.map(key => findKeyValue(key));
defaultLabel = keys.map(key => getKeyLabel(key)).join('');
}
else {
throw new Error(`Arrow key template "${arrowKeys}" not found!`);
throw new Error('Directional key templates have to consist of four keys!');
}
}
const vector = new Vector2();
if (this.isPressed(arrowKeys[0]))
vector.y -= 1;
if (this.isPressed(arrowKeys[1]))
vector.x -= 1;
if (this.isPressed(arrowKeys[2]))
vector.y += 1;
if (this.isPressed(arrowKeys[3]))
vector.x += 1;
return vector;
return {
label: label || defaultLabel,
query: () => {
const vector = new Vector2();
if (this.key(keys[0]).query())
vector.y -= 1;
if (this.key(keys[1]).query())
vector.x -= 1;
if (this.key(keys[2]).query())
vector.y += 1;
if (this.key(keys[3]).query())
vector.x += 1;
return vector;
},
};
}
}
/**
* A map of all the supported button numbers (array indices) and their
* respective aliases (the strings in the arrays) that can be used with the
* `Gamepad` class. The first alias for each button will be used as a label.
*/
const buttonMap = [
['A'],
['B'],
['X'],
['Y'],
['Left Bumper', 'LB'],
['Right Bumper', 'RB'],
['Left Trigger', 'LT'],
['Right Trigger', 'RT'],
['Back', 'View'],
['Start'],
['Left Stick'],
['Right Stick'],
['Up', 'DpadUp'],
['Down', 'DpadDown'],
['Left', 'DpadLeft'],
['Right', 'DpadRight'],
['Home', 'Guide', 'Xbox'],
];
function findButtonNumber(button) {
if (typeof button === 'number')
return button;
let buttonNumber = 0;
for (const buttonAliases of buttonMap) {
for (const buttonAlias of buttonAliases) {
if (button.toLowerCase() === buttonAlias.toLowerCase()) {
return buttonNumber;
}
}
buttonNumber++;
}
}
function getButtonLabel(button) {
return buttonMap[button][0];
}
const gamepadSticks = {
left: { xAxis: 0, yAxis: 1 },
right: { xAxis: 2, yAxis: 3 },
};
class Gamepad {

@@ -150,17 +301,15 @@ constructor(

this.pressedButtons = new Set();
this.gamepadTimestamp = 0;
this.window = win;
this.navigator = nav;
this.window.addEventListener('gamepadconnected', ({ gamepad }) => {
if (!this.isConnected())
if (!this.isConnected()) {
this.gamepadIndex = gamepad.index;
store.preferGamepad = true;
}
});
this.window.addEventListener('gamepaddisconnected', ({ gamepad }) => {
if (this.gamepadIndex === gamepad.index) {
const gamepads = this.navigator.getGamepads();
if (gamepads.length) {
this.gamepadIndex = this.navigator.getGamepads()[0].index;
}
else {
this.gamepadIndex = undefined;
}
this.gamepadIndex = undefined;
store.preferGamepad = false;
}

@@ -173,26 +322,102 @@ });

get gamepad() {
return this.navigator.getGamepads()[this.gamepadIndex];
const gamepad = this.navigator.getGamepads()[this.gamepadIndex];
if (gamepad.timestamp > this.gamepadTimestamp)
store.preferGamepad = true;
this.gamepadTimestamp = gamepad.timestamp;
return gamepad;
}
isPressed(button) {
return this.isConnected() && this.gamepad.buttons[button].pressed;
button(button) {
const that = this;
const buttonNumber = findButtonNumber(button);
return {
label: getButtonLabel(buttonNumber),
fromGamepad: true,
query() {
if (!that.isConnected())
return false;
if (!this.trigger) {
if (that.gamepad.buttons[buttonNumber].pressed) {
if (!that.pressedButtons.has(buttonNumber)) {
that.pressedButtons.add(buttonNumber);
return true;
}
}
else {
that.pressedButtons.delete(buttonNumber);
}
return false;
}
else {
return that.gamepad.buttons[buttonNumber].pressed;
}
},
get trigger() {
delete this.trigger;
return this;
},
};
}
wasPressed(button) {
if (this.isConnected()) {
if (this.gamepad.buttons[button].pressed) {
if (!this.pressedButtons.has(button)) {
this.pressedButtons.add(button);
return true;
}
stick(stick) {
if (typeof stick === 'string') {
if (stick in gamepadSticks) {
stick = gamepadSticks[stick];
}
else {
this.pressedButtons.delete(button);
throw new Error(`Gamepad stick "${stick}" not found!`);
}
}
return false;
return new Vector2(this.gamepad.axes[stick.xAxis], this.gamepad.axes[stick.yAxis]);
}
}
function and(...controls) {
if (controls.length < 2)
throw new Error('Less than two controls specified!');
return {
label: controls.map(control => control.label).join(' + '),
query: () => {
for (const control of controls) {
if (!control.query())
return false;
}
return true;
},
};
}
function or(...controls) {
if (controls.length < 2)
throw new Error('Less than two controls specified!');
return {
get label() {
return (controls.filter(control => control.fromGamepad).length === 0 ?
controls[0]
: store.preferGamepad ?
controls.filter(control => control.fromGamepad === true)[0]
: controls.filter(control => control.fromGamepad !== true)[0]).label;
},
query: () => {
let sampleQueryValue;
for (const control of controls) {
const queryValue = control.query();
sampleQueryValue = queryValue;
if (queryValue)
return queryValue;
}
if (typeof sampleQueryValue === 'boolean')
return false;
},
};
}
let store = {
preferGamepad: false,
};
exports.store = store;
exports.Mouse = Mouse;
exports.Keyboard = Keyboard;
exports.Gamepad = Gamepad;
exports.and = and;
exports.or = or;

@@ -199,0 +424,0 @@ Object.defineProperty(exports, '__esModule', { value: true });

@@ -6,2 +6,2 @@ /*!

*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.Contro={})}(this,function(e){"use strict";class t{constructor(e=0,t=0){this.x=e,this.y=t}}!function(e){e[e.Left=0]="Left",e[e.Middle=1]="Middle",e[e.Right=2]="Right"}(e.MouseButton||(e.MouseButton={}));const s={arrows:["ArrowUp","ArrowLeft","ArrowDown","ArrowRight"],wasd:["W","A","S","D"]};e.Mouse=class{constructor({canvas:e,doc:s=document}){this.pointerLocked=!1,this.pointerMovement=new t,this.pressedButtons=new Set,this.queuedButtons=new Set,this.scrollDistance=0,this.canvas=e,this.document=s;let n=this.canvas.addEventListener.bind(this.canvas);n("mousedown",e=>{this.pressedButtons.add(e.button),this.queuedButtons.add(e.button)}),n("mouseup",e=>{this.pressedButtons.delete(e.button),this.queuedButtons.delete(e.button)}),n("mousemove",e=>{this.pointerMovement.x+=e.movementX,this.pointerMovement.y+=e.movementY}),n("wheel",e=>{const t=e.deltaY;this.scrollDistance+=t}),n=this.document.addEventListener.bind(this.document)}isPressed(t=e.MouseButton.Left){return this.pressedButtons.has(t)}wasPressed(t=e.MouseButton.Left){return!!this.queuedButtons.has(t)&&(this.queuedButtons.delete(t),!0)}getPointerMovement(){const e=this.pointerMovement;return this.pointerMovement=new t(0,0),e}getScrollDistance(){const e=this.scrollDistance;return this.scrollDistance=0,e}lockPointer(){this.canvas.requestPointerLock()}unlockPointer(){this.document.exitPointerLock()}isPointerLocked(){return this.document.pointerLockElement===this.canvas}},e.Keyboard=class{constructor({doc:e=document}={}){this.pressedKeys=new Set,this.queuedKeys=new Set,this.document=e,this.document.addEventListener("keydown",e=>{const t=e.key.toLowerCase();this.pressedKeys.add(t),this.queuedKeys.add(t)}),this.document.addEventListener("keyup",e=>{const t=e.key.toLowerCase();this.pressedKeys.delete(t),this.queuedKeys.delete(t)})}isPressed(e){return e=e.toLowerCase(),this.pressedKeys.has(e)}wasPressed(e){return e=e.toLowerCase(),!!this.queuedKeys.has(e)&&(this.queuedKeys.delete(e),!0)}getMovementVector(e){if("string"==typeof e){if(!((e=e.toLowerCase())in s))throw new Error(`Arrow key template "${e}" not found!`);e=s[e]}const n=new t;return this.isPressed(e[0])&&(n.y-=1),this.isPressed(e[1])&&(n.x-=1),this.isPressed(e[2])&&(n.y+=1),this.isPressed(e[3])&&(n.x+=1),n}},e.Gamepad=class{constructor({win:e=window,nav:t=navigator}={}){this.pressedButtons=new Set,this.window=e,this.navigator=t,this.window.addEventListener("gamepadconnected",({gamepad:e})=>{this.isConnected()||(this.gamepadIndex=e.index)}),this.window.addEventListener("gamepaddisconnected",({gamepad:e})=>{this.gamepadIndex===e.index&&(this.navigator.getGamepads().length?this.gamepadIndex=this.navigator.getGamepads()[0].index:this.gamepadIndex=void 0)})}isConnected(){return void 0!==this.gamepadIndex&&this.gamepad.connected}get gamepad(){return this.navigator.getGamepads()[this.gamepadIndex]}isPressed(e){return this.isConnected()&&this.gamepad.buttons[e].pressed}wasPressed(e){if(this.isConnected())if(this.gamepad.buttons[e].pressed){if(!this.pressedButtons.has(e))return this.pressedButtons.add(e),!0}else this.pressedButtons.delete(e);return!1}},Object.defineProperty(e,"__esModule",{value:!0})});
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.Contro={})}(this,function(e){"use strict";function t(e){return 1===e.length?e.toLowerCase():(Object.keys(o).forEach(t=>{o[t].forEach(r=>{e.toLowerCase()===r.toLowerCase()&&(e=t)})}),e)}function r(e){return e in o?o[e][0]:1===e.length?e.toUpperCase():e}class s{constructor(e=0,t=0){this.x=e,this.y=t}}const n=["left","middle","right"];const o={" ":["Space","Spacebar","Space Bar"],AltGraph:["Alt Gr"],ArrowDown:["Down"],ArrowLeft:["Left"],ArrowRight:["Right"],ArrowUp:["Up"],Backspace:["Backspace"],Control:["Ctrl","Ctl"],Delete:["Delete","Del"],Enter:["Enter","Return"],Escape:["Escape","Esc"],Insert:["Insert","Ins"],PageDown:["Page Down","PgDown"],PageUp:["Page Up","PgUp"],Tab:["Tab"]},i={arrows:["Arrow keys",["ArrowUp","ArrowLeft","ArrowDown","ArrowRight"]],wasd:["WASD",["W","A","S","D"]]};const a=[["A"],["B"],["X"],["Y"],["Left Bumper","LB"],["Right Bumper","RB"],["Left Trigger","LT"],["Right Trigger","RT"],["Back","View"],["Start"],["Left Stick"],["Right Stick"],["Up","DpadUp"],["Down","DpadDown"],["Left","DpadLeft"],["Right","DpadRight"],["Home","Guide","Xbox"]],d={left:{xAxis:0,yAxis:1},right:{xAxis:2,yAxis:3}};let u={preferGamepad:!1};e.store=u,e.Mouse=class{constructor({canvas:e,doc:t=document}){this.pointerLocked=!1,this.pointerMovement=new s,this.pressedButtons=new Set,this.queuedButtons=new Set,this.scrollDistance=0,this.canvas=e,this.document=t;const r=this.canvas.addEventListener.bind(this.canvas);r("mousedown",e=>{u.preferGamepad=!1,this.pressedButtons.add(e.button),this.queuedButtons.add(e.button)}),r("mouseup",e=>{u.preferGamepad=!1,this.pressedButtons.delete(e.button),this.queuedButtons.delete(e.button)}),r("mousemove",e=>{u.preferGamepad=!1,this.pointerMovement.x+=e.movementX,this.pointerMovement.y+=e.movementY}),r("wheel",e=>{u.preferGamepad=!1;const t=e.deltaY;this.scrollDistance+=t})}parseButton(e){if("string"==typeof e){if(n.includes(e))return n.indexOf(e);throw new Error(`There is no mouse button called "${e}"!`)}if(e<n.length)return e;throw new Error(`There is no mouse button with the index ${e}!`)}button(e){const t=this;return e=this.parseButton(e),{label:["Left","Middle","Right"][e]+" Mouse Button",query(){return e=t.parseButton(e),this.trigger?t.pressedButtons.has(e):!!t.queuedButtons.has(e)&&(t.queuedButtons.delete(e),!0)},get trigger(){return delete this.trigger,this}}}pointer(){return{label:"Cursor",query:()=>{const e=this.pointerMovement;return this.pointerMovement=new s(0,0),e}}}wheel(){return{label:"Mouse wheel",query:()=>{const e=this.scrollDistance;return this.scrollDistance=0,e}}}lockPointer(){this.canvas.requestPointerLock()}unlockPointer(){this.document.exitPointerLock()}isPointerLocked(){return this.document.pointerLockElement===this.canvas}},e.Keyboard=class{constructor({doc:e=document}={}){this.pressedKeys=new Set,this.queuedKeys=new Set,this.document=e,this.document.addEventListener("keydown",e=>{u.preferGamepad=!1;let t=e.key;return t===t.toUpperCase()&&(t=t.toLowerCase()),this.pressedKeys.add(t),this.queuedKeys.add(t),!1}),this.document.addEventListener("keyup",e=>{u.preferGamepad=!1;let t=e.key;return t===t.toUpperCase()&&(t=t.toLowerCase()),this.pressedKeys.delete(t),this.queuedKeys.delete(t),!1})}key(e){const s=this;return e=t(e),{label:r(e),query(){return this.trigger?s.pressedKeys.has(e):s.queuedKeys.delete(e)},get trigger(){return delete this.trigger,this}}}directionalKeys(e,n){let o;if("string"==typeof e){if(!((e=e.toLowerCase())in i))throw new Error(`Directional key template "${e}" not found!`);{const t=i[e.toLowerCase()];o=t[0],e=t[1]}}else{if(4!==e.length)throw new Error("Directional key templates have to consist of four keys!");o=(e=e.map(e=>t(e))).map(e=>r(e)).join("")}return{label:n||o,query:()=>{const t=new s;return this.key(e[0]).query()&&(t.y-=1),this.key(e[1]).query()&&(t.x-=1),this.key(e[2]).query()&&(t.y+=1),this.key(e[3]).query()&&(t.x+=1),t}}}},e.Gamepad=class{constructor({win:e=window,nav:t=navigator}={}){this.pressedButtons=new Set,this.gamepadTimestamp=0,this.window=e,this.navigator=t,this.window.addEventListener("gamepadconnected",({gamepad:e})=>{this.isConnected()||(this.gamepadIndex=e.index,u.preferGamepad=!0)}),this.window.addEventListener("gamepaddisconnected",({gamepad:e})=>{this.gamepadIndex===e.index&&(this.gamepadIndex=void 0,u.preferGamepad=!1)})}isConnected(){return void 0!==this.gamepadIndex&&this.gamepad.connected}get gamepad(){const e=this.navigator.getGamepads()[this.gamepadIndex];return e.timestamp>this.gamepadTimestamp&&(u.preferGamepad=!0),this.gamepadTimestamp=e.timestamp,e}button(e){const t=this,r=function(e){if("number"==typeof e)return e;let t=0;for(const r of a){for(const s of r)if(e.toLowerCase()===s.toLowerCase())return t;t++}}(e);return{label:function(e){return a[e][0]}(r),fromGamepad:!0,query(){if(!t.isConnected())return!1;if(this.trigger)return t.gamepad.buttons[r].pressed;if(t.gamepad.buttons[r].pressed){if(!t.pressedButtons.has(r))return t.pressedButtons.add(r),!0}else t.pressedButtons.delete(r);return!1},get trigger(){return delete this.trigger,this}}}stick(e){if("string"==typeof e){if(!(e in d))throw new Error(`Gamepad stick "${e}" not found!`);e=d[e]}return new s(this.gamepad.axes[e.xAxis],this.gamepad.axes[e.yAxis])}},e.and=function(...e){if(e.length<2)throw new Error("Less than two controls specified!");return{label:e.map(e=>e.label).join(" + "),query:()=>{for(const t of e)if(!t.query())return!1;return!0}}},e.or=function(...e){if(e.length<2)throw new Error("Less than two controls specified!");return{get label(){return(0===e.filter(e=>e.fromGamepad).length?e[0]:u.preferGamepad?e.filter(e=>!0===e.fromGamepad)[0]:e.filter(e=>!0!==e.fromGamepad)[0]).label},query:()=>{let t;for(const r of e){const e=r.query();if(t=e,e)return e}if("boolean"==typeof t)return!1}}},Object.defineProperty(e,"__esModule",{value:!0})});
{
"name": "contro",
"version": "1.3.0",
"version": "2.0.0",
"description": "Game controls done right.",

@@ -13,7 +13,12 @@ "main": "dist/contro.esm.js",

"scripts": {
"lint": "npm run lint-ts && npm run lint-md",
"precommit": "npm run verify",
"verify": "run-s lint verify:test build",
"verify:test": "npm run test -- --reporter progress --forbid-pending --forbid-only",
"lint": "run-s lint-*",
"lint-ts": "tslint -p .",
"lint-md": "remark .",
"test": "nyc mocha src/**/*.spec.ts --require ts-node/register --watch-extensions ts",
"commit": "git-cz",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"generate-mapping-docs": "ts-node scripts/generate-mapping-docs.ts",
"build": "rollup -c && uglifyjs dist/contro.js --comments /^!/ -cm -o dist/contro.min.js",

@@ -41,2 +46,3 @@ "release": "semantic-release",

"@types/chai": "^4.0.10",
"@types/fs-extra": "^5.0.0",
"@types/mocha": "^2.2.44",

@@ -46,3 +52,8 @@ "@types/node": "^8.5.2",

"codecov": "^3.0.0",
"commitizen": "^2.9.6",
"cz-conventional-changelog": "^2.1.0",
"fs-extra": "^5.0.0",
"husky": "^0.14.3",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.2",
"nyc": "^11.4.1",

@@ -63,3 +74,8 @@ "remark-cli": "^4.0.0",

},
"dependencies": {}
"dependencies": {},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
}

@@ -15,82 +15,77 @@ <h1 align="center">

[![npm version][npm-version-badge]][npm-link]
[![monhtly downloads][npm-downloads-badge]][npm-link]
[![monthly downloads][npm-downloads-badge]][npm-link]
## Introducation
### What is Contro?
Contro is a **library** that offers **simple abstractions** on top of existing **Web input APIs** and allows game developers to easily implements controls for **keyboard**, **mouse** and **gamepad**.
Contro is a **library** that offers **simple abstractions** on top of existing **Web input APIs** and allows game developers to easily implement controls for **keyboard**, **mouse** and **gamepad**.
### Getting started
## Installation
The easiest way to include Contro in your application is using a CDN:
The easiest way to include Contro in your application is using the `unpkg` CDN:
```html
<script src="https://unpkg.com/contro"></script>
<script src="https://unpkg.com/contro@2"></script>
```
If you're using `npm`, you can also `npm install contro`.
If you're using `npm`, you can also install it using `npm i contro`.
### Creating your first control
## Usage
Imagine your game looked something like this ...
1. Import the Contro classes and functions using [Object destructuring][object-destructuring].
```js
/** Gameloop */
const canvas = document.querySelector('#game')
const renderer = new MyGameLibrary.Renderer(canvas)
const catWorld = new CatWorld()
function loop() {
renderer.render(catWorld)
requestAnimationFrame(loop)
}
loop()
const { Mouse, Keyboard, Gamepad, or, and } = Contro
// OR
import { Mouse, Keyboard, Gamepad, or, and } from 'contro'
```
... and you wanted to spawn a new cat whenever the user clicked.
2. Create instances of the components you want to use.
To accomplish this we need Contro's `Mouse` class. It's an abstraction on top of JavaScript's [`MouseEvent`'s](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) and the [Pointer Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API) optimized for being used within [game loop](https://en.wikipedia.org/wiki/Game_programming#Game_structure) functions.
Let's create a new `Mouse` instance and pass in our `canvas`, so Contro can register the required event listeners for us.
```js
const mouse = new Contro.Mouse({ canvas })
const keyboard = new Keyboard()
const gamepad = new Gamepad()
```
Now we can use our new `Mouse` instance to check whether the left mouse button is pressed.
3. Prepare the controls using the control methods and the operator functions `and` and `or`.
```js
if (mouse.isPressed(Contro.MouseButton.Left)) {
catWorld.spawnCat()
const controls = {
jump: or(gamepad.button('A').trigger, keyboard.key('Space').trigger),
menu: or(gamepad.button('Back').trigger, keyboard.key('Esc').trigger),
inventory: or(gamepad.button('LB').trigger, keyboard.key('E').trigger),
map: or(gamepad.button('RB').trigger, keyboard.key('M').trigger),
statusOverlay: or(gamepad.button('RB'), keyboard.key('Tab')),
}
```
If this was real code and you ran it game you'd notice something weird: Whenever you held down your left mouse button cats would keep spawning and spawning until you released the button again.
4. In your game loop, display the relevant `.label`'s and '`.query()` the controls.
This is because `Mouse.isPressed()` always returns the current state of the mouse button and that for every single frame (iteration of your game loop). We only want one cat to spawn when we click, so we have to use `Mouse.wasPressed()`.
```js
function gameLoop() {
// Update the UI to reflect the player's input device(s)
game.jumpButton.text = controls.jump.label
game.menuButton.text = controls.menu.label
// ...
Our final code looks like this:
// Query the controls and do something
if (controls.jump.query()) game.player.jump()
if (controls.menu.query()) game.openMenu()
game.statusOverlay.visible = controls.statusOverlay.query()
// ...
```js
/** Gameloop */
const canvas = document.querySelector('#game')
const renderer = new MyGameLibrary.Renderer(canvas)
const catWorld = new CatWorld()
const mouse = new Contro.Mouse()
function loop() {
if (mouse.wasPressed(Contro.MouseButton.Left)) {
catWorld.spawnCat()
}
renderer.render(catWorld)
requestAnimationFrame(loop)
requestAnimationFrame(gameLoop)
}
loop()
```
> Learn more on the [API Documentation](/docs/API.md).
Note that all of the code starting with `game.` is fictional and just serves as an example.
---
#### Not convinced yet? Check out [this demo][demo]!
> Wanna learn more? Check out [the docs][docs]!
[demo]: https://codepen.io/shroudedcode/pen/qpPqmB
[docs]: /docs/README.md
[object-destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
[build-link]: https://travis-ci.org/shroudedcode/contro

@@ -97,0 +92,0 @@ [build-badge]: https://img.shields.io/travis/shroudedcode/contro.svg?style=flat-square

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc