Use Font Standard instead of Font Awesome.
[featherfall2.git] / src / ext / gamepad.js
1 (function () {
2 "use strict";
3 /** DOM Event shim for Web Gamepad API
4 https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html
5
6 This adds three new custom events to the window object:
7
8 yuugamepadbuttondown, yuugamepadbuttonup
9 Dispatched when a button on a gamepad is pressed
10 or released.
11
12 .detail.gamepad - the Gamepad instance with the button
13 .detail.button - the numeric index of the button
14 .detail.mapped - true if the button is known to fit the
15 "standard" mapping, even if gamepad.mapping
16 says otherwise
17
18 yuugamepadaxismove
19 Dispatched when an axis on a gamepad changes.
20
21 .detail.gamepad - the Gamepad instance with the axis
22 .detail.axis - the numeric index of the axis
23 .detail.value - the value of the axis
24 .detail.mapped - true if the axis is known to fit the
25 "standard" mapping, even if gamepad.mapping
26 says otherwise
27
28 It also polyfills navigator.getGamepads.
29
30 Including this file (e.g. in a <script> tag) is all that's
31 necessary to enable these.
32
33 Because the underlying API is poll-based, events will be
34 dispatched at most at the animation refresh rate. Inputs that
35 exist for less than this time may be dropped.
36 */
37
38 function empty () { return []; }
39
40 if (!navigator.getGamepads)
41 navigator.getGamepads = (
42 navigator.webkitGetGamepads
43 || navigator.mozGetGamepads
44 || empty);
45 if (navigator.getGamepads === empty)
46 return;
47
48 var requestFrame = window.requestAnimationFrame
49 || window.mozRequestAnimationFrame
50 || window.webkitRequestAnimationFrame;
51
52 var PLATFORM = (navigator.platform || "Unknown") + ".";
53
54 var MAPPINGS = {
55 // Maps the logical controller button/axis (the index) to the
56 // equivalent standard controller button/axis (the value),
57 // based on ID.
58 //
59 // This needs to be keyed to the underlying platform as well
60 // because mapping varies based on the USB stack in question.
61 // In theory this can go as far as multiple drivers for one OS
62 // with different mappings, but most platforms / devices have
63 // one canonical driver.
64 'MacIntel.54c-268-PLAYSTATION(R)3 Controller': {
65 buttons: [8, 10, 11, 9, 12, 15, 13, 14, 6, 7,
66 4, 5, 3, 1, 0, 2, 16],
67 axes: [0, 1, 2, 3]
68 }
69 };
70
71 var states = {};
72
73 function isPressed (button) {
74 return isFinite(button) ? button > 0.1 : button.pressed;
75 }
76
77 function isAppropriateState (state, gamepad) {
78 return state
79 && state.id === gamepad.id
80 && state.buttons.length === gamepad.buttons.length
81 && state.axes.length === gamepad.axes.length;
82 }
83
84 function makeState (gamepad) {
85 var buttons = [];
86 for (var i = 0; i < gamepad.buttons.length; ++i)
87 buttons.push(isPressed(gamepad.buttons[i]));
88 return {
89 buttons: buttons,
90 axes: gamepad.axes.slice(),
91 id: gamepad.id
92 };
93 }
94
95 function getState (gamepad) {
96 var state = states[gamepad.index];
97 // Browsers that do not support the connected/disconnected
98 // events will silently swap in a new gamepad. Try to detect
99 // that and reset the state.
100 if (!isAppropriateState(state, gamepad))
101 state = states[gamepad.index] = makeState(gamepad);
102 return state;
103 }
104
105 function connected (event) {
106 states[event.gamepad.index] = makeState(event.gamepad);
107 }
108
109 function disconnected (event) {
110 delete states[event.gamepad.index];
111 }
112
113 function buttonEvent (gamepad, button, pressed) {
114 var mapping = MAPPINGS[PLATFORM + gamepad.id];
115 var mapped = gamepad.mapping === "standard" || !!mapping;
116 if (gamepad.mapping !== "standard" && mapping)
117 button = mapping.buttons[button];
118
119 return new CustomEvent(
120 pressed ? 'yuugamepadbuttondown' : 'yuugamepadbuttonup',
121 { detail: { gamepad: gamepad, mapped: mapped, button: button } });
122 }
123
124 function axisEvent (gamepad, axis, value) {
125 var mapping = MAPPINGS[PLATFORM + gamepad.id];
126 var mapped = gamepad.mapping === "standard" || !!mapping;
127 if (gamepad.mapping !== "standard" && mapping)
128 axis = mapping.axes[axis];
129
130 return new CustomEvent(
131 'yuugamepadaxismove',
132 { detail: { gamepad: gamepad, mapped: mapped,
133 axis: axis, value: value } });
134 }
135
136 function pumpGamepad (gamepad) {
137 var state = getState(gamepad);
138 for (var b = 0; b < gamepad.buttons.length; ++b) {
139 var wasPressed = isPressed(state.buttons[b]);
140 var nowPressed = isPressed(gamepad.buttons[b]);
141 if (wasPressed !== nowPressed) {
142 state.buttons[b] = nowPressed;
143 window.dispatchEvent(buttonEvent(gamepad, b, nowPressed));
144 }
145 }
146 for (var a = 0; a < gamepad.axes.length; ++a) {
147 if (gamepad.axes[a] !== state.axes[a]) {
148 var value = state.axes[a] = gamepad.axes[a];
149 window.dispatchEvent(axisEvent(gamepad, a, value));
150 }
151 }
152 }
153
154 function pump () {
155 var gamepads = navigator.getGamepads();
156 for (var i = 0; i < gamepads.length; ++i) {
157 var gamepad = gamepads[i];
158 if (!gamepad)
159 continue;
160 pumpGamepad(gamepad);
161 }
162 }
163
164 requestFrame.call(window, function _ () {
165 pump();
166 requestFrame.call(window, _);
167 });
168
169 window.addEventListener('gamepadconnected', connected);
170 window.addEventListener('gamepaddisconnected', disconnected);
171
172 })();