aboutsummaryrefslogtreecommitdiff
path: root/src/library_sdl.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/library_sdl.js')
-rw-r--r--src/library_sdl.js254
1 files changed, 230 insertions, 24 deletions
diff --git a/src/library_sdl.js b/src/library_sdl.js
index 04a66351..c46364ff 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -75,6 +75,7 @@ var LibrarySDL = {
textInput: false,
startTime: null,
+ initFlags: 0, // The flags passed to SDL_Init
buttonState: 0,
modState: 0,
DOMButtons: [0, 0, 0],
@@ -639,6 +640,21 @@ var LibrarySDL = {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}};
break;
}
+ case 'joystick_button_up': case 'joystick_button_down': {
+ var state = event.type === 'joystick_button_up' ? 0 : 1;
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}};
+ break;
+ }
+ case 'joystick_axis_motion': {
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}};
+ break;
+ }
default: throw 'Unhandled SDL event: ' + event.type;
}
},
@@ -695,7 +711,109 @@ var LibrarySDL = {
for (var i = 0; i < num; i++) {
console.log(' diagonal ' + i + ':' + [data[i*surfData.width*4 + i*4 + 0], data[i*surfData.width*4 + i*4 + 1], data[i*surfData.width*4 + i*4 + 2], data[i*surfData.width*4 + i*4 + 3]]);
}
- }
+ },
+
+ // Joystick helper methods and state
+
+ joystickEventState: 0,
+ lastJoystickState: {}, // Map from SDL_Joystick* to their last known state. Required to determine if a change has occurred.
+ // Maps Joystick names to pointers. Allows us to avoid reallocating memory for
+ // joystick names each time this function is called.
+ joystickNamePool: {},
+ recordJoystickState: function(joystick, state) {
+ // Standardize button state.
+ var buttons = new Array(state.buttons.length);
+ for (var i = 0; i < state.buttons.length; i++) {
+ buttons[i] = SDL.getJoystickButtonState(state.buttons[i]);
+ }
+
+ SDL.lastJoystickState[joystick] = {
+ buttons: buttons,
+ axes: state.axes.slice(0),
+ timestamp: state.timestamp,
+ index: state.index,
+ id: state.id
+ };
+ },
+ // Retrieves the button state of the given gamepad button.
+ // Abstracts away implementation differences.
+ // Returns 'true' if pressed, 'false' otherwise.
+ getJoystickButtonState: function(button) {
+ if (typeof button === 'object') {
+ // Current gamepad API editor's draft (Firefox Nightly)
+ // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-GamepadButton
+ return button.pressed;
+ } else {
+ // Current gamepad API working draft (Firefox / Chrome Stable)
+ // http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface
+ return button > 0;
+ }
+ },
+ // Queries for and inserts controller events into the SDL queue.
+ queryJoysticks: function() {
+ for (var joystick in SDL.lastJoystickState) {
+ var state = SDL.getGamepad(joystick - 1);
+ var prevState = SDL.lastJoystickState[joystick];
+ // Check only if the timestamp has differed.
+ // NOTE: Timestamp is not available in Firefox.
+ if (typeof state.timestamp !== 'number' || state.timestamp !== prevState.timestamp) {
+ var i;
+ for (i = 0; i < state.buttons.length; i++) {
+ var buttonState = SDL.getJoystickButtonState(state.buttons[i]);
+ // NOTE: The previous state already has a boolean representation of
+ // its button, so no need to standardize its button state here.
+ if (buttonState !== prevState.buttons[i]) {
+ // Insert button-press event.
+ SDL.events.push({
+ type: buttonState ? 'joystick_button_down' : 'joystick_button_up',
+ joystick: joystick,
+ index: joystick - 1,
+ button: i
+ });
+ }
+ }
+ for (i = 0; i < state.axes.length; i++) {
+ if (state.axes[i] !== prevState.axes[i]) {
+ // Insert axes-change event.
+ SDL.events.push({
+ type: 'joystick_axis_motion',
+ joystick: joystick,
+ index: joystick - 1,
+ axis: i,
+ value: state.axes[i]
+ });
+ }
+ }
+
+ SDL.recordJoystickState(joystick, state);
+ }
+ }
+ },
+ // Converts the double-based browser axis value [-1, 1] into SDL's 16-bit
+ // value [-32768, 32767]
+ joystickAxisValueConversion: function(value) {
+ // Ensures that 0 is 0, 1 is 32767, and -1 is 32768.
+ return Math.ceil(((value+1) * 32767.5) - 32768);
+ },
+
+ getGamepads: function() {
+ var fcn = navigator.getGamepads || navigator.webkitGamepads || navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads;
+ if (fcn !== undefined) {
+ // The function must be applied on the navigator object.
+ return fcn.apply(navigator);
+ } else {
+ return [];
+ }
+ },
+
+ // Helper function: Returns the gamepad if available, or null if not.
+ getGamepad: function(deviceIndex) {
+ var gamepads = SDL.getGamepads();
+ if (gamepads.length > deviceIndex && deviceIndex >= 0) {
+ return gamepads[deviceIndex];
+ }
+ return null;
+ },
},
SDL_Linked_Version: function() {
@@ -708,8 +826,10 @@ var LibrarySDL = {
return SDL.version;
},
- SDL_Init: function(what) {
+ SDL_Init: function(initFlags) {
SDL.startTime = Date.now();
+ SDL.initFlags = initFlags;
+
// capture all key events. we just keep down and up, but also capture press to prevent default actions
if (!Module['doNotCaptureKeyboard']) {
document.addEventListener("keydown", SDL.receiveEvent);
@@ -718,6 +838,15 @@ var LibrarySDL = {
window.addEventListener("blur", SDL.receiveEvent);
document.addEventListener("visibilitychange", SDL.receiveEvent);
}
+
+ if (initFlags & 0x200) {
+ // SDL_INIT_JOYSTICK
+ // Firefox will not give us Joystick data unless we register this NOP
+ // callback.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=936104
+ addEventListener("gamepadconnected", function() {});
+ }
+
window.addEventListener("unload", SDL.receiveEvent);
SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs
_memset(SDL.keyboardState, 0, 0x10000);
@@ -730,6 +859,12 @@ var LibrarySDL = {
SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */;
SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */;
SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */;
+ // These are not technically DOM events; the HTML gamepad API is poll-based.
+ // However, we define them here, as the rest of the SDL code assumes that
+ // all SDL events originate as DOM events.
+ SDL.DOMEventToSDLEvent['joystick_axis_motion'] = 0x600 /* SDL_JOYAXISMOTION */;
+ SDL.DOMEventToSDLEvent['joystick_button_down'] = 0x603 /* SDL_JOYBUTTONDOWN */;
+ SDL.DOMEventToSDLEvent['joystick_button_up'] = 0x604 /* SDL_JOYBUTTONUP */;
return 0; // success
},
@@ -1189,6 +1324,11 @@ var LibrarySDL = {
},
SDL_PollEvent: function(ptr) {
+ if (SDL.initFlags & 0x200 && SDL.joystickEventState) {
+ // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured
+ // to automatically query for events, query for joystick events.
+ SDL.queryJoysticks();
+ }
if (SDL.events.length === 0) return 0;
if (ptr) {
SDL.makeCEvent(SDL.events.shift(), ptr);
@@ -1237,11 +1377,11 @@ var LibrarySDL = {
surfData.colors = new Uint8Array(256 * 3); //256 RGB colors
}
- for (var i = firstColor; i < firstColor + nColors; i++) {
- var index = i *3;
+ for (var i = 0; i < nColors; ++i) {
+ var index = (firstColor + i) * 3;
surfData.colors[index] = {{{ makeGetValue('colors', 'i*4', 'i8', null, true) }}};
- surfData.colors[index +1] = {{{ makeGetValue('colors', 'i*4 +1', 'i8', null, true) }}};
- surfData.colors[index +2] = {{{ makeGetValue('colors', 'i*4 +2', 'i8', null, true) }}};
+ surfData.colors[index + 1] = {{{ makeGetValue('colors', 'i*4 + 1', 'i8', null, true) }}};
+ surfData.colors[index + 2] = {{{ makeGetValue('colors', 'i*4 + 2', 'i8', null, true) }}};
}
return 1;
@@ -1301,12 +1441,12 @@ var LibrarySDL = {
IMG_Load_RW: function(rwopsID, freeSrc) {
try {
// stb_image integration support
- var cleanup = function() {
+ function cleanup() {
if (rwops && freeSrc) _SDL_FreeRW(rwopsID);
};
function addCleanup(func) {
var old = cleanup;
- cleanup = function() {
+ cleanup = function added_cleanup() {
old();
func();
}
@@ -1481,7 +1621,7 @@ var LibrarySDL = {
SDL.audio.buffer = _malloc(SDL.audio.bufferSize);
// Create a callback function that will be routinely called to ask more audio data from the user application.
- SDL.audio.caller = function() {
+ SDL.audio.caller = function SDL_audio_caller() {
if (!SDL.audio) {
return;
}
@@ -1495,7 +1635,7 @@ var LibrarySDL = {
SDL.audio.audioOutput['mozSetup'](SDL.audio.channels, SDL.audio.freq); // use string attributes on mozOutput for closure compiler
SDL.audio.mozBuffer = new Float32Array(totalSamples);
SDL.audio.nextPlayTime = 0;
- SDL.audio.pushAudio = function(ptr, size) {
+ SDL.audio.pushAudio = function SDL_audio_pushAudio(ptr, size) {
var mozBuffer = SDL.audio.mozBuffer;
// The input audio data for SDL audio is either 8-bit or 16-bit interleaved across channels, output for Mozilla Audio Data API
// needs to be Float32 interleaved, so perform a sample conversion.
@@ -1862,7 +2002,7 @@ var LibrarySDL = {
audio.frequency = info.audio.frequency;
// TODO: handle N loops. Behavior matches Mix_PlayMusic
audio.loop = loops != 0;
- audio['onended'] = function() { // TODO: cache these
+ audio['onended'] = function SDL_audio_onended() { // TODO: cache these
channelInfo.audio = null;
if (SDL.channelFinished) {
Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel);
@@ -1889,7 +2029,7 @@ var LibrarySDL = {
source.loop = false;
source.buffer = context.createBuffer(numChannels, 1, audio.frequency);
var jsNode = context.createJavaScriptNode(2048, numChannels, numChannels);
- jsNode.onaudioprocess = function(event) {
+ jsNode.onaudioprocess = function jsNode_onaudioprocess(event) {
var buffers = new Array(numChannels);
for (var i = 0; i < numChannels; ++i) {
buffers[i] = event.outputBuffer.getChannelData(i);
@@ -2375,37 +2515,103 @@ var LibrarySDL = {
// Joysticks
- SDL_NumJoysticks: function() { return 0; },
+ SDL_NumJoysticks: function() {
+ var count = 0;
+ var gamepads = SDL.getGamepads();
+ // The length is not the number of gamepads; check which ones are defined.
+ for (var i = 0; i < gamepads.length; i++) {
+ if (gamepads[i] !== undefined) count++;
+ }
+ return count;
+ },
- SDL_JoystickName: function(deviceIndex) { return 0; },
+ SDL_JoystickName: function(deviceIndex) {
+ var gamepad = SDL.getGamepad(deviceIndex);
+ if (gamepad) {
+ var name = gamepad.id;
+ if (SDL.joystickNamePool.hasOwnProperty(name)) {
+ return SDL.joystickNamePool[name];
+ }
+ return SDL.joystickNamePool[name] = allocate(intArrayFromString(name), 'i8', ALLOC_NORMAL);
+ }
+ return 0;
+ },
- SDL_JoystickOpen: function(deviceIndex) { return 0; },
+ SDL_JoystickOpen: function(deviceIndex) {
+ var gamepad = SDL.getGamepad(deviceIndex);
+ if (gamepad) {
+ // Use this as a unique 'pointer' for this joystick.
+ var joystick = deviceIndex+1;
+ SDL.recordJoystickState(joystick, gamepad);
+ return joystick;
+ }
+ return 0;
+ },
- SDL_JoystickOpened: function(deviceIndex) { return 0; },
+ SDL_JoystickOpened: function(deviceIndex) {
+ return SDL.lastJoystickState.hasOwnProperty(deviceIndex+1) ? 1 : 0;
+ },
- SDL_JoystickIndex: function(joystick) { return 0; },
+ SDL_JoystickIndex: function(joystick) {
+ // joystick pointers are simply the deviceIndex+1.
+ return joystick - 1;
+ },
- SDL_JoystickNumAxes: function(joystick) { return 0; },
+ SDL_JoystickNumAxes: function(joystick) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad) {
+ return gamepad.axes.length;
+ }
+ return 0;
+ },
SDL_JoystickNumBalls: function(joystick) { return 0; },
SDL_JoystickNumHats: function(joystick) { return 0; },
- SDL_JoystickNumButtons: function(joystick) { return 0; },
+ SDL_JoystickNumButtons: function(joystick) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad) {
+ return gamepad.buttons.length;
+ }
+ return 0;
+ },
- SDL_JoystickUpdate: function() {},
+ SDL_JoystickUpdate: function() {
+ SDL.queryJoysticks();
+ },
- SDL_JoystickEventState: function(state) { return 0; },
+ SDL_JoystickEventState: function(state) {
+ if (state < 0) {
+ // SDL_QUERY: Return current state.
+ return SDL.joystickEventState;
+ }
+ return SDL.joystickEventState = state;
+ },
- SDL_JoystickGetAxis: function(joystick, axis) { return 0; },
+ SDL_JoystickGetAxis: function(joystick, axis) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad && gamepad.axes.length > axis) {
+ return SDL.joystickAxisValueConversion(gamepad.axes[axis]);
+ }
+ return 0;
+ },
SDL_JoystickGetHat: function(joystick, hat) { return 0; },
SDL_JoystickGetBall: function(joystick, ball, dxptr, dyptr) { return -1; },
- SDL_JoystickGetButton: function(joystick, button) { return 0; },
+ SDL_JoystickGetButton: function(joystick, button) {
+ var gamepad = SDL.getGamepad(joystick - 1);
+ if (gamepad && gamepad.buttons.length > button) {
+ return SDL.getJoystickButtonState(gamepad.buttons[button]) ? 1 : 0;
+ }
+ return 0;
+ },
- SDL_JoystickClose: function(joystick) {},
+ SDL_JoystickClose: function(joystick) {
+ delete SDL.lastJoystickState[joystick];
+ },
// Misc