// # The MIT License (MIT) // # // # Copyright (c) 2016 Microsoft. All rights reserved. // # // # Permission is hereby granted, free of charge, to any person obtaining a copy // # of this software and associated documentation files (the "Software"), to deal // # in the Software without restriction, including without limitation the rights // # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // # copies of the Software, and to permit persons to whom the Software is // # furnished to do so, subject to the following conditions: // # // # The above copyright notice and this permission notice shall be included in // # all copies or substantial portions of the Software. // # // # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // # THE SOFTWARE. require(['apphost'], function (appHost) { 'use strict'; var _GAMEPAD_A_BUTTON_INDEX = 0; var _GAMEPAD_B_BUTTON_INDEX = 1; var _GAMEPAD_DPAD_UP_BUTTON_INDEX = 12; var _GAMEPAD_DPAD_DOWN_BUTTON_INDEX = 13; var _GAMEPAD_DPAD_LEFT_BUTTON_INDEX = 14; var _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX = 15; var _GAMEPAD_A_KEY = 'GamepadA'; var _GAMEPAD_B_KEY = 'GamepadB'; var _GAMEPAD_DPAD_UP_KEY = 'GamepadDPadUp'; var _GAMEPAD_DPAD_DOWN_KEY = 'GamepadDPadDown'; var _GAMEPAD_DPAD_LEFT_KEY = 'GamepadDPadLeft'; var _GAMEPAD_DPAD_RIGHT_KEY = 'GamepadDPadRight'; var _GAMEPAD_LEFT_THUMBSTICK_UP_KEY = 'GamepadLeftThumbStickUp'; var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY = 'GamepadLeftThumbStickDown'; var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY = 'GamepadLeftThumbStickLeft'; var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY = 'GamepadLeftThumbStickRight'; var _GAMEPAD_A_KEYCODE = 0; var _GAMEPAD_B_KEYCODE = 27; var _GAMEPAD_DPAD_UP_KEYCODE = 38; var _GAMEPAD_DPAD_DOWN_KEYCODE = 40; var _GAMEPAD_DPAD_LEFT_KEYCODE = 37; var _GAMEPAD_DPAD_RIGHT_KEYCODE = 39; var _GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE = 38; var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE = 40; var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE = 37; var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE = 39; var _THUMB_STICK_THRESHOLD = 0.75; var _leftThumbstickUpPressed = false; var _leftThumbstickDownPressed = false; var _leftThumbstickLeftPressed = false; var _leftThumbstickRightPressed = false; var _dPadUpPressed = false; var _dPadDownPressed = false; var _dPadLeftPressed = false; var _dPadRightPressed = false; var _gamepadAPressed = false; var _gamepadBPressed = false; // The set of buttons on the gamepad we listen for. var ProcessedButtons = [ _GAMEPAD_DPAD_UP_BUTTON_INDEX, _GAMEPAD_DPAD_DOWN_BUTTON_INDEX, _GAMEPAD_DPAD_LEFT_BUTTON_INDEX, _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX, _GAMEPAD_A_BUTTON_INDEX, _GAMEPAD_B_BUTTON_INDEX ]; var _ButtonPressedState = {}; _ButtonPressedState.getgamepadA = function () { return _gamepadAPressed; }; _ButtonPressedState.setgamepadA = function (newPressedState) { raiseKeyEvent(_gamepadAPressed, newPressedState, _GAMEPAD_A_KEY, _GAMEPAD_A_KEYCODE, false, true); _gamepadAPressed = newPressedState; }; _ButtonPressedState.getgamepadB = function () { return _gamepadBPressed; }; _ButtonPressedState.setgamepadB = function (newPressedState) { raiseKeyEvent(_gamepadBPressed, newPressedState, _GAMEPAD_B_KEY, _GAMEPAD_B_KEYCODE); _gamepadBPressed = newPressedState; }; _ButtonPressedState.getleftThumbstickUp = function () { return _leftThumbstickUpPressed; }; _ButtonPressedState.setleftThumbstickUp = function (newPressedState) { raiseKeyEvent(_leftThumbstickUpPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_UP_KEY, _GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE, true); _leftThumbstickUpPressed = newPressedState; }; _ButtonPressedState.getleftThumbstickDown = function () { return _leftThumbstickDownPressed; }; _ButtonPressedState.setleftThumbstickDown = function (newPressedState) { raiseKeyEvent(_leftThumbstickDownPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE, true); _leftThumbstickDownPressed = newPressedState; }; _ButtonPressedState.getleftThumbstickLeft = function () { return _leftThumbstickLeftPressed; }; _ButtonPressedState.setleftThumbstickLeft = function (newPressedState) { raiseKeyEvent(_leftThumbstickLeftPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE, true); _leftThumbstickLeftPressed = newPressedState; }; _ButtonPressedState.getleftThumbstickRight = function () { return _leftThumbstickRightPressed; }; _ButtonPressedState.setleftThumbstickRight = function (newPressedState) { raiseKeyEvent(_leftThumbstickRightPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE, true); _leftThumbstickRightPressed = newPressedState; }; _ButtonPressedState.getdPadUp = function () { return _dPadUpPressed; }; _ButtonPressedState.setdPadUp = function (newPressedState) { raiseKeyEvent(_dPadUpPressed, newPressedState, _GAMEPAD_DPAD_UP_KEY, _GAMEPAD_DPAD_UP_KEYCODE, true); _dPadUpPressed = newPressedState; }; _ButtonPressedState.getdPadDown = function () { return _dPadDownPressed; }; _ButtonPressedState.setdPadDown = function (newPressedState) { raiseKeyEvent(_dPadDownPressed, newPressedState, _GAMEPAD_DPAD_DOWN_KEY, _GAMEPAD_DPAD_DOWN_KEYCODE, true); _dPadDownPressed = newPressedState; }; _ButtonPressedState.getdPadLeft = function () { return _dPadLeftPressed; }; _ButtonPressedState.setdPadLeft = function (newPressedState) { raiseKeyEvent(_dPadLeftPressed, newPressedState, _GAMEPAD_DPAD_LEFT_KEY, _GAMEPAD_DPAD_LEFT_KEYCODE, true); _dPadLeftPressed = newPressedState; }; _ButtonPressedState.getdPadRight = function () { return _dPadRightPressed; }; _ButtonPressedState.setdPadRight = function (newPressedState) { raiseKeyEvent(_dPadRightPressed, newPressedState, _GAMEPAD_DPAD_RIGHT_KEY, _GAMEPAD_DPAD_RIGHT_KEYCODE, true); _dPadRightPressed = newPressedState; }; var times = {}; function throttle(key) { var time = times[key] || 0; var now = new Date().getTime(); if ((now - time) >= 200) { //times[key] = now; return true; } return false; } function resetThrottle(key) { times[key] = new Date().getTime(); } var isElectron = navigator.userAgent.toLowerCase().indexOf('electron') !== -1; function allowInput() { // This would be nice but always seems to return true with electron if (!isElectron && document.hidden) { /* eslint-disable-line compat/compat */ return false; } if (appHost.getWindowState() === 'Minimized') { return false; } return true; } function raiseEvent(name, key, keyCode) { if (!allowInput()) { return; } var event = document.createEvent('Event'); event.initEvent(name, true, true); event.key = key; event.keyCode = keyCode; (document.activeElement || document.body).dispatchEvent(event); } function clickElement(elem) { if (!allowInput()) { return; } elem.click(); } function raiseKeyEvent(oldPressedState, newPressedState, key, keyCode, enableRepeatKeyDown, clickonKeyUp) { // No-op if oldPressedState === newPressedState if (newPressedState === true) { // button down var fire = false; // always fire if this is the initial down press if (oldPressedState === false) { fire = true; resetThrottle(key); } else if (enableRepeatKeyDown) { fire = throttle(key); } if (fire && keyCode) { raiseEvent('keydown', key, keyCode); } } else if (newPressedState === false && oldPressedState === true) { resetThrottle(key); // button up if (keyCode) { raiseEvent('keyup', key, keyCode); } if (clickonKeyUp) { clickElement(document.activeElement || window); } } } var inputLoopTimer; function runInputLoop() { // Get the latest gamepad state. var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ for (var i = 0, len = gamepads.length; i < len; i++) { var gamepad = gamepads[i]; if (!gamepad) { continue; } // Iterate through the axes var axes = gamepad.axes; var leftStickX = axes[0]; var leftStickY = axes[1]; if (leftStickX > _THUMB_STICK_THRESHOLD) { // Right _ButtonPressedState.setleftThumbstickRight(true); } else if (leftStickX < -_THUMB_STICK_THRESHOLD) { // Left _ButtonPressedState.setleftThumbstickLeft(true); } else if (leftStickY < -_THUMB_STICK_THRESHOLD) { // Up _ButtonPressedState.setleftThumbstickUp(true); } else if (leftStickY > _THUMB_STICK_THRESHOLD) { // Down _ButtonPressedState.setleftThumbstickDown(true); } else { _ButtonPressedState.setleftThumbstickLeft(false); _ButtonPressedState.setleftThumbstickRight(false); _ButtonPressedState.setleftThumbstickUp(false); _ButtonPressedState.setleftThumbstickDown(false); } // Iterate through the buttons to see if Left thumbstick, DPad, A and B are pressed. var buttons = gamepad.buttons; for (var j = 0, len = buttons.length; j < len; j++) { if (ProcessedButtons.indexOf(j) !== -1) { if (buttons[j].pressed) { switch (j) { case _GAMEPAD_DPAD_UP_BUTTON_INDEX: _ButtonPressedState.setdPadUp(true); break; case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX: _ButtonPressedState.setdPadDown(true); break; case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX: _ButtonPressedState.setdPadLeft(true); break; case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX: _ButtonPressedState.setdPadRight(true); break; case _GAMEPAD_A_BUTTON_INDEX: _ButtonPressedState.setgamepadA(true); break; case _GAMEPAD_B_BUTTON_INDEX: _ButtonPressedState.setgamepadB(true); break; default: // No-op break; } } else { switch (j) { case _GAMEPAD_DPAD_UP_BUTTON_INDEX: if (_ButtonPressedState.getdPadUp()) { _ButtonPressedState.setdPadUp(false); } break; case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX: if (_ButtonPressedState.getdPadDown()) { _ButtonPressedState.setdPadDown(false); } break; case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX: if (_ButtonPressedState.getdPadLeft()) { _ButtonPressedState.setdPadLeft(false); } break; case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX: if (_ButtonPressedState.getdPadRight()) { _ButtonPressedState.setdPadRight(false); } break; case _GAMEPAD_A_BUTTON_INDEX: if (_ButtonPressedState.getgamepadA()) { _ButtonPressedState.setgamepadA(false); } break; case _GAMEPAD_B_BUTTON_INDEX: if (_ButtonPressedState.getgamepadB()) { _ButtonPressedState.setgamepadB(false); } break; default: // No-op break; } } } } } // Schedule the next one inputLoopTimer = requestAnimationFrame(runInputLoop); } function startInputLoop() { if (!inputLoopTimer) { runInputLoop(); } } function stopInputLoop() { cancelAnimationFrame(inputLoopTimer); inputLoopTimer = undefined; } function isGamepadConnected() { var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ for (var i = 0, len = gamepads.length; i < len; i++) { var gamepad = gamepads[i]; if (gamepad && gamepad.connected) { return true; } } return false; } function onFocusOrGamepadAttach(e) { /* eslint-disable-next-line compat/compat */ if (isGamepadConnected() && document.hasFocus()) { console.log('Gamepad connected! Starting input loop'); startInputLoop(); } } function onFocusOrGamepadDetach(e) { /* eslint-disable-next-line compat/compat */ if (!isGamepadConnected() || !document.hasFocus()) { console.log('Gamepad disconnected! No other gamepads are connected, stopping input loop'); stopInputLoop(); } else { console.log('Gamepad disconnected! There are gamepads still connected.'); } } // Event listeners for any change in gamepads' state. window.addEventListener('gamepaddisconnected', onFocusOrGamepadDetach); window.addEventListener('gamepadconnected', onFocusOrGamepadAttach); window.addEventListener('blur', onFocusOrGamepadDetach); window.addEventListener('focus', onFocusOrGamepadAttach); onFocusOrGamepadAttach(); // The gamepadInputEmulation is a string property that exists in JavaScript UWAs and in WebViews in UWAs. // It won't exist in Win8.1 style apps or browsers. if (window.navigator && typeof window.navigator.gamepadInputEmulation === 'string') { // We want the gamepad to provide gamepad VK keyboard events rather than moving a // mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events // and provide input to the DOM navigator.getGamepads API. window.navigator.gamepadInputEmulation = 'gamepad'; } });