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.js1683
1 files changed, 1309 insertions, 374 deletions
diff --git a/src/library_sdl.js b/src/library_sdl.js
index 4342838e..ca2045d4 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -10,7 +10,7 @@
// or otherwise).
var LibrarySDL = {
- $SDL__deps: ['$FS', '$Browser'],
+ $SDL__deps: ['$FS', '$PATH', '$Browser'],
$SDL: {
defaults: {
width: 320,
@@ -21,18 +21,21 @@ var LibrarySDL = {
version: null,
surfaces: {},
+ // A pool of freed canvas elements. Reusing them avoids GC pauses.
+ canvasPool: [],
events: [],
fonts: [null],
// The currently preloaded audio elements ready to be played
audios: [null],
+ rwops: [null],
// The currently playing audio element. There's only one music track.
music: {
audio: null,
volume: 1.0
},
mixerFrequency: 22050,
- mixerFormat: 0x8010, // AUDIO_S16LSB
+ mixerFormat: {{{ cDefine('AUDIO_S16LSB') }}}, //0x8010, // AUDIO_S16LSB
mixerNumChannels: 2,
mixerChunkSize: 1024,
channelMinimumNumber: 0,
@@ -40,13 +43,41 @@ var LibrarySDL = {
GL: false, // Set to true if we call SDL_SetVideoMode with SDL_OPENGL, and if so, we do not create 2D canvases&contexts for blitting
// Note that images loaded before SDL_SetVideoMode will not get this optimization
+ // all possible GL attributes, with their default value
+ glAttributes: {
+ 0: 3, /* SDL_GL_RED_SIZE */
+ 1: 3, /* SDL_GL_GREEN_SIZE */
+ 2: 2, /* SDL_GL_BLUE_SIZE */
+ 3: 0, /* SDL_GL_ALPHA_SIZE */
+ 4: 0, /* SDL_GL_BUFFER_SIZE */
+ 5: 1, /* SDL_GL_DOUBLEBUFFER */
+ 6: 16, /* SDL_GL_DEPTH_SIZE */
+ 7: 0, /* SDL_GL_STENCIL_SIZE */
+ 8: 0, /* SDL_GL_ACCUM_RED_SIZE */
+ 9: 0, /* SDL_GL_ACCUM_GREEN_SIZE */
+ 10: 0, /* SDL_GL_ACCUM_BLUE_SIZE */
+ 11: 0, /* SDL_GL_ACCUM_ALPHA_SIZE */
+ 12: 0, /* SDL_GL_STEREO */
+ 13: 0, /* SDL_GL_MULTISAMPLEBUFFERS */
+ 14: 0, /* SDL_GL_MULTISAMPLESAMPLES */
+ 15: 1, /* SDL_GL_ACCELERATED_VISUAL */
+ 16: 0, /* SDL_GL_RETAINED_BACKING */
+ 17: 0, /* SDL_GL_CONTEXT_MAJOR_VERSION */
+ 18: 0 /* SDL_GL_CONTEXT_MINOR_VERSION */
+ },
+
keyboardState: null,
keyboardMap: {},
+ canRequestFullscreen: false,
+ isRequestingFullscreen: false,
+
textInput: false,
startTime: null,
+ initFlags: 0, // The flags passed to SDL_Init
buttonState: 0,
+ modState: 0,
DOMButtons: [0, 0, 0],
DOMEventToSDLEvent: {},
@@ -123,110 +154,39 @@ var LibrarySDL = {
120: 27,
121: 28,
122: 29, // Z
- 44: 54, // comma
- 46: 55, // period
- 47: 56, // slash
- 49: 30, // 1
- 50: 31,
- 51: 32,
- 52: 33,
- 53: 34,
- 54: 35,
- 55: 36,
- 56: 37,
- 57: 38, // 9
- 48: 39, // 0
- 13: 40, // return
- 9: 43, // tab
- 27: 41, // escape
- 32: 44, // space
- 92: 49, // backslash
+ 49: 30, // 1
+ 50: 31,
+ 51: 32,
+ 52: 33,
+ 53: 34,
+ 54: 35,
+ 55: 36,
+ 56: 37,
+ 57: 38, // 9
+ 48: 39, // 0
+ 13: 40, // return
+ 27: 41, // escape
+ 8: 42, // backspace
+ 9: 43, // tab
+ 32: 44, // space
+ 61: 46, // equals
+ 91: 47, // left bracket
+ 93: 48, // right bracket
+ 92: 49, // backslash
+ 59: 51, // ;
+ 96: 52, // apostrophe
+ 44: 54, // comma
+ 46: 55, // period
+ 47: 56, // slash
305: 224, // ctrl
308: 226, // alt
},
-
- structs: {
- Rect: Runtime.generateStructInfo([
- ['i32', 'x'], ['i32', 'y'], ['i32', 'w'], ['i32', 'h'],
- ]),
- PixelFormat: Runtime.generateStructInfo([
- ['i32', 'format'],
- ['void*', 'palette'], ['i8', 'BitsPerPixel'], ['i8', 'BytesPerPixel'],
- ['i8', 'padding1'], ['i8', 'padding2'],
- ['i32', 'Rmask'], ['i32', 'Gmask'], ['i32', 'Bmask'], ['i32', 'Amask'],
- ['i8', 'Rloss'], ['i8', 'Gloss'], ['i8', 'Bloss'], ['i8', 'Aloss'],
- ['i8', 'Rshift'], ['i8', 'Gshift'], ['i8', 'Bshift'], ['i8', 'Ashift']
- ]),
- KeyboardEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['i8', 'state'],
- ['i8', 'repeat'],
- ['i8', 'padding2'],
- ['i8', 'padding3'],
- ['i32', 'keysym']
- ]),
- keysym: Runtime.generateStructInfo([
- ['i32', 'scancode'],
- ['i32', 'sym'],
- ['i16', 'mod'],
- ['i32', 'unicode']
- ]),
- TextInputEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['b256', 'text'],
- ]),
- MouseMotionEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['i8', 'state'],
- ['i8', 'padding1'],
- ['i8', 'padding2'],
- ['i8', 'padding3'],
- ['i32', 'x'],
- ['i32', 'y'],
- ['i32', 'xrel'],
- ['i32', 'yrel']
- ]),
- MouseButtonEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'windowID'],
- ['i8', 'button'],
- ['i8', 'state'],
- ['i8', 'padding1'],
- ['i8', 'padding2'],
- ['i32', 'x'],
- ['i32', 'y']
- ]),
- ResizeEvent: Runtime.generateStructInfo([
- ['i32', 'type'],
- ['i32', 'w'],
- ['i32', 'h']
- ]),
- AudioSpec: Runtime.generateStructInfo([
- ['i32', 'freq'],
- ['i16', 'format'],
- ['i8', 'channels'],
- ['i8', 'silence'],
- ['i16', 'samples'],
- ['i32', 'size'],
- ['void*', 'callback'],
- ['void*', 'userdata']
- ]),
- version: Runtime.generateStructInfo([
- ['i8', 'major'],
- ['i8', 'minor'],
- ['i8', 'patch']
- ])
- },
-
loadRect: function(rect) {
return {
- x: {{{ makeGetValue('rect + SDL.structs.Rect.x', '0', 'i32') }}},
- y: {{{ makeGetValue('rect + SDL.structs.Rect.y', '0', 'i32') }}},
- w: {{{ makeGetValue('rect + SDL.structs.Rect.w', '0', 'i32') }}},
- h: {{{ makeGetValue('rect + SDL.structs.Rect.h', '0', 'i32') }}}
+ x: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.x, '0', 'i32') }}},
+ y: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.y, '0', 'i32') }}},
+ w: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.w, '0', 'i32') }}},
+ h: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.h, '0', 'i32') }}}
};
},
@@ -241,66 +201,79 @@ var LibrarySDL = {
},
translateColorToCSSRGBA: function(rgba) {
- return 'rgba(' + ((rgba >> 24)&255) + ',' + ((rgba >> 16)&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba&255)/255) + ')';
+ return 'rgba(' + (rgba&0xff) + ',' + (rgba>>8 & 0xff) + ',' + (rgba>>16 & 0xff) + ',' + (rgba>>>24)/0xff + ')';
},
translateRGBAToCSSRGBA: function(r, g, b, a) {
- return 'rgba(' + r + ',' + g + ',' + b + ',' + (a/255) + ')';
+ return 'rgba(' + (r&0xff) + ',' + (g&0xff) + ',' + (b&0xff) + ',' + (a&0xff)/255 + ')';
},
translateRGBAToColor: function(r, g, b, a) {
- return (r << 24) + (g << 16) + (b << 8) + a;
+ return r | g << 8 | b << 16 | a << 24;
},
makeSurface: function(width, height, flags, usePageCanvas, source, rmask, gmask, bmask, amask) {
flags = flags || 0;
- var surf = _malloc(14*Runtime.QUANTUM_SIZE); // SDL_Surface has 14 fields of quantum size
- var buffer = _malloc(width*height*4); // TODO: only allocate when locked the first time
- var pixelFormat = _malloc(18*Runtime.QUANTUM_SIZE);
+ var surf = _malloc({{{ C_STRUCTS.SDL_Surface.__size__ }}}); // SDL_Surface has 15 fields of quantum size
+ var pixelFormat = _malloc({{{ C_STRUCTS.SDL_PixelFormat.__size__ }}});
flags |= 1; // SDL_HWSURFACE - this tells SDL_MUSTLOCK that this needs to be locked
//surface with SDL_HWPALETTE flag is 8bpp surface (1 byte)
var is_SDL_HWPALETTE = flags & 0x00200000;
var bpp = is_SDL_HWPALETTE ? 1 : 4;
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} // SDL_Surface.flags
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*1', '0', 'pixelFormat', 'void*') }}} // SDL_Surface.format TODO
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*2', '0', 'width', 'i32') }}} // SDL_Surface.w
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*3', '0', 'height', 'i32') }}} // SDL_Surface.h
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*4', '0', 'width * bpp', 'i32') }}} // SDL_Surface.pitch, assuming RGBA or indexed for now,
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.flags, 'flags', 'i32') }}}; // SDL_Surface.flags
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.format, 'pixelFormat', 'void*') }}};// SDL_Surface.format TODO
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.w, 'width', 'i32') }}}; // SDL_Surface.w
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.h, 'height', 'i32') }}}; // SDL_Surface.h
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pitch, 'width * bpp', 'i32') }}}; // SDL_Surface.pitch, assuming RGBA or indexed for now,
// since that is what ImageData gives us in browsers
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*5', '0', 'buffer', 'void*') }}} // SDL_Surface.pixels
- {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*6', '0', '0', 'i32*') }}} // SDL_Surface.offset
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pixels, '0', 'void*') }}}; // SDL_Surface.pixels, lazily initialized inside of SDL_LockSurface
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect, '0', 'i32*') }}}; // SDL_Surface.offset
+
+ {{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.refcount, '1', 'i32') }}};
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.format', '0', '-2042224636', 'i32') }}} // SDL_PIXELFORMAT_RGBA8888
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.palette', '0', '0', 'i32') }}} // TODO
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BitsPerPixel', '0', 'bpp * 8', 'i8') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BytesPerPixel', '0', 'bpp', 'i8') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.format, cDefine('SDL_PIXELFORMAT_RGBA8888'), 'i32') }}};// SDL_PIXELFORMAT_RGBA8888
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.palette, '0', 'i32') }}};// TODO
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BitsPerPixel, 'bpp * 8', 'i8') }}};
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BytesPerPixel, 'bpp', 'i8') }}};
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Rmask', '0', 'rmask || 0x000000ff', 'i32') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Gmask', '0', 'gmask || 0x0000ff00', 'i32') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Bmask', '0', 'bmask || 0x00ff0000', 'i32') }}}
- {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Amask', '0', 'amask || 0xff000000', 'i32') }}}
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Rmask, 'rmask || 0x000000ff', 'i32') }}};
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Gmask, 'gmask || 0x0000ff00', 'i32') }}};
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Bmask, 'bmask || 0x00ff0000', 'i32') }}};
+ {{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Amask, 'amask || 0xff000000', 'i32') }}};
// Decide if we want to use WebGL or not
var useWebGL = (flags & 0x04000000) != 0; // SDL_OPENGL
SDL.GL = SDL.GL || useWebGL;
var canvas;
if (!usePageCanvas) {
- canvas = document.createElement('canvas');
+ if (SDL.canvasPool.length > 0) {
+ canvas = SDL.canvasPool.pop();
+ } else {
+ canvas = document.createElement('canvas');
+ }
canvas.width = width;
canvas.height = height;
} else {
canvas = Module['canvas'];
}
- var ctx = Browser.createContext(canvas, useWebGL, usePageCanvas);
+
+ var webGLContextAttributes = {
+ antialias: ((SDL.glAttributes[13 /*SDL_GL_MULTISAMPLEBUFFERS*/] != 0) && (SDL.glAttributes[14 /*SDL_GL_MULTISAMPLESAMPLES*/] > 1)),
+ depth: (SDL.glAttributes[6 /*SDL_GL_DEPTH_SIZE*/] > 0),
+ stencil: (SDL.glAttributes[7 /*SDL_GL_STENCIL_SIZE*/] > 0)
+ };
+
+ var ctx = Browser.createContext(canvas, useWebGL, usePageCanvas, webGLContextAttributes);
+
SDL.surfaces[surf] = {
width: width,
height: height,
canvas: canvas,
ctx: ctx,
surf: surf,
- buffer: buffer,
+ buffer: 0,
pixelFormat: pixelFormat,
alpha: 255,
flags: flags,
@@ -308,7 +281,7 @@ var LibrarySDL = {
usePageCanvas: usePageCanvas,
source: source,
- isFlagSet: function (flag) {
+ isFlagSet: function(flag) {
return flags & flag;
}
};
@@ -354,14 +327,64 @@ var LibrarySDL = {
},
freeSurface: function(surf) {
- _free(SDL.surfaces[surf].buffer);
- _free(SDL.surfaces[surf].pixelFormat);
+ var refcountPointer = surf + {{{ C_STRUCTS.SDL_Surface.refcount }}};
+ var refcount = {{{ makeGetValue('refcountPointer', '0', 'i32') }}};
+ if (refcount > 1) {
+ {{{ makeSetValue('refcountPointer', '0', 'refcount - 1', 'i32') }}};
+ return;
+ }
+
+ var info = SDL.surfaces[surf];
+ if (!info.usePageCanvas && info.canvas) SDL.canvasPool.push(info.canvas);
+ if (info.buffer) _free(info.buffer);
+ _free(info.pixelFormat);
_free(surf);
SDL.surfaces[surf] = null;
},
+ touchX: 0, touchY: 0,
+ savedKeydown: null,
+
receiveEvent: function(event) {
switch(event.type) {
+ case 'touchstart':
+ event.preventDefault();
+ var touch = event.touches[0];
+ touchX = touch.pageX;
+ touchY = touch.pageY;
+ var event = {
+ type: 'mousedown',
+ button: 0,
+ pageX: touchX,
+ pageY: touchY
+ };
+ SDL.DOMButtons[0] = 1;
+ SDL.events.push(event);
+ break;
+ case 'touchmove':
+ event.preventDefault();
+ var touch = event.touches[0];
+ touchX = touch.pageX;
+ touchY = touch.pageY;
+ event = {
+ type: 'mousemove',
+ button: 0,
+ pageX: touchX,
+ pageY: touchY
+ };
+ SDL.events.push(event);
+ break;
+ case 'touchend':
+ event.preventDefault();
+ event = {
+ type: 'mouseup',
+ button: 0,
+ pageX: touchX,
+ pageY: touchY
+ };
+ SDL.DOMButtons[0] = 0;
+ SDL.events.push(event);
+ break;
case 'mousemove':
if (Browser.pointerLock) {
// workaround for firefox bug 750111
@@ -373,13 +396,22 @@ var LibrarySDL = {
if (event['movementX'] == 0 && event['movementY'] == 0) {
// ignore a mousemove event if it doesn't contain any movement info
// (without pointer lock, we infer movement from pageX/pageY, so this check is unnecessary)
- return false;
+ event.preventDefault();
+ return;
}
}
// fall through
case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel':
+ // If we preventDefault on keydown events, the subsequent keypress events
+ // won't fire. However, it's fine (and in some cases necessary) to
+ // preventDefault for keys that don't generate a character. Otherwise,
+ // preventDefault is the right thing to do in general.
+ if (event.type !== 'keydown' || (!SDL.unicode && !SDL.textInput) || (event.keyCode === 8 /* backspace */ || event.keyCode === 9 /* tab */)) {
+ event.preventDefault();
+ }
+
if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel') {
- var button = (event.type == 'DOMMouseScroll' ? event.detail : -event.wheelDelta) > 0 ? 4 : 3;
+ var button = Browser.getMouseWheelDelta(event) > 0 ? 4 : 3;
var event2 = {
type: 'mousedown',
button: button,
@@ -396,16 +428,48 @@ var LibrarySDL = {
} else if (event.type == 'mousedown') {
SDL.DOMButtons[event.button] = 1;
} else if (event.type == 'mouseup') {
- if (!SDL.DOMButtons[event.button]) return false; // ignore extra ups, can happen if we leave the canvas while pressing down, then return,
- // since we add a mouseup in that case
+ // ignore extra ups, can happen if we leave the canvas while pressing down, then return,
+ // since we add a mouseup in that case
+ if (!SDL.DOMButtons[event.button]) {
+ return;
+ }
+
SDL.DOMButtons[event.button] = 0;
}
- if (event.type == 'keypress' && !SDL.textInput) {
- break;
+ // We can only request fullscreen as the result of user input.
+ // Due to this limitation, we toggle a boolean on keydown which
+ // SDL_WM_ToggleFullScreen will check and subsequently set another
+ // flag indicating for us to request fullscreen on the following
+ // keyup. This isn't perfect, but it enables SDL_WM_ToggleFullScreen
+ // to work as the result of a keypress (which is an extremely
+ // common use case).
+ if (event.type === 'keydown') {
+ SDL.canRequestFullscreen = true;
+ } else if (event.type === 'keyup') {
+ if (SDL.isRequestingFullscreen) {
+ Module['requestFullScreen'](true, true);
+ SDL.isRequestingFullscreen = false;
+ }
+ SDL.canRequestFullscreen = false;
}
- SDL.events.push(event);
+ // SDL expects a unicode character to be passed to its keydown events.
+ // Unfortunately, the browser APIs only provide a charCode property on
+ // keypress events, so we must backfill in keydown events with their
+ // subsequent keypress event's charCode.
+ if (event.type === 'keypress' && SDL.savedKeydown) {
+ // charCode is read-only
+ SDL.savedKeydown.keypressCharCode = event.charCode;
+ SDL.savedKeydown = null;
+ } else if (event.type === 'keydown') {
+ SDL.savedKeydown = event;
+ }
+
+ // Don't push keypress events unless SDL_StartTextInput has been called.
+ if (event.type !== 'keypress' || SDL.textInput) {
+ SDL.events.push(event);
+ }
break;
case 'mouseout':
// Un-press all pressed mouse buttons, because we might miss the release outside of the canvas
@@ -420,6 +484,7 @@ var LibrarySDL = {
SDL.DOMButtons[i] = 0;
}
}
+ event.preventDefault();
break;
case 'blur':
case 'visibilitychange': {
@@ -430,6 +495,7 @@ var LibrarySDL = {
keyCode: SDL.keyboardMap[code]
});
}
+ event.preventDefault();
break;
}
case 'unload':
@@ -438,26 +504,77 @@ var LibrarySDL = {
// Force-run a main event loop, since otherwise this event will never be caught!
Browser.mainLoop.runner();
}
- return true;
+ return;
case 'resize':
SDL.events.push(event);
+ // manually triggered resize event doesn't have a preventDefault member
+ if (event.preventDefault) {
+ event.preventDefault();
+ }
break;
}
if (SDL.events.length >= 10000) {
Module.printErr('SDL event queue full, dropping events');
SDL.events = SDL.events.slice(0, 10000);
}
- return false;
+ return;
+ },
+
+ handleEvent: function(event) {
+ if (event.handled) return;
+ event.handled = true;
+
+ switch (event.type) {
+ case 'keydown': case 'keyup': {
+ var down = event.type === 'keydown';
+ var code = event.keyCode;
+ if (code >= 65 && code <= 90) {
+ code += 32; // make lowercase for SDL
+ } else {
+ code = SDL.keyCodes[event.keyCode] || event.keyCode;
+ }
+
+ {{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}};
+ // TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED
+ SDL.modState = ({{{ makeGetValue('SDL.keyboardState', '1248', 'i8') }}} ? 0x0040 | 0x0080 : 0) | // KMOD_LCTRL & KMOD_RCTRL
+ ({{{ makeGetValue('SDL.keyboardState', '1249', 'i8') }}} ? 0x0001 | 0x0002 : 0) | // KMOD_LSHIFT & KMOD_RSHIFT
+ ({{{ makeGetValue('SDL.keyboardState', '1250', 'i8') }}} ? 0x0100 | 0x0200 : 0); // KMOD_LALT & KMOD_RALT
+
+ if (down) {
+ SDL.keyboardMap[code] = event.keyCode; // save the DOM input, which we can use to unpress it during blur
+ } else {
+ delete SDL.keyboardMap[code];
+ }
+
+ break;
+ }
+ case 'mousedown': case 'mouseup':
+ if (event.type == 'mousedown') {
+ // SDL_BUTTON(x) is defined as (1 << ((x)-1)). SDL buttons are 1-3,
+ // and DOM buttons are 0-2, so this means that the below formula is
+ // correct.
+ SDL.buttonState |= 1 << event.button;
+ } else if (event.type == 'mouseup') {
+ SDL.buttonState &= ~(1 << event.button);
+ }
+ // fall through
+ case 'mousemove': {
+ Browser.calculateMouseEvent(event);
+ break;
+ }
+ }
},
makeCEvent: function(event, ptr) {
if (typeof event === 'number') {
// This is a pointer to a native C event that was SDL_PushEvent'ed
- _memcpy(ptr, event, SDL.structs.KeyboardEvent.__size__); // XXX
+ _memcpy(ptr, event, {{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}}); // XXX
return;
}
- switch(event.type) {
+ SDL.handleEvent(event);
+
+ switch (event.type) {
case 'keydown': case 'keyup': {
var down = event.type === 'keydown';
//Module.print('Received key event: ' + event.keyCode);
@@ -473,72 +590,68 @@ var LibrarySDL = {
} else {
scan = SDL.scanCodes[key] || key;
}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}
- //{{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.which', '1', 'i32') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.state', 'down ? 1 : 0', 'i8') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.repeat', '0', 'i8') }}} // TODO
-
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.scancode', 'scan', 'i32') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.sym', 'key', 'i32') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.mod', '0', 'i32') }}}
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.unicode', 'key', 'i32') }}}
- var code = SDL.keyCodes[event.keyCode] || event.keyCode;
- {{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}};
- if (down) {
- SDL.keyboardMap[code] = event.keyCode; // save the DOM input, which we can use to unpress it during blur
- } else {
- delete SDL.keyboardMap[code];
- }
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.state, 'down ? 1 : 0', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.repeat, '0', 'i8') }}}; // TODO
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.scancode, 'scan', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.sym, 'key', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.mod, 'SDL.modState', 'i16') }}};
+ // some non-character keys (e.g. backspace and tab) won't have keypressCharCode set, fill in with the keyCode.
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.unicode, 'event.keypressCharCode || key', 'i32') }}};
break;
}
case 'keypress': {
- {{{ makeSetValue('ptr', 'SDL.structs.TextInputEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
// Not filling in windowID for now
var cStr = intArrayFromString(String.fromCharCode(event.charCode));
for (var i = 0; i < cStr.length; ++i) {
- {{{ makeSetValue('ptr', 'SDL.structs.TextInputEvent.text + i', 'cStr[i]', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.text + ' + i', 'cStr[i]', 'i8') }}};
}
break;
}
- case 'mousedown': case 'mouseup':
- if (event.type == 'mousedown') {
- // SDL_BUTTON(x) is defined as (1 << ((x)-1)). SDL buttons are 1-3,
- // and DOM buttons are 0-2, so this means that the below formula is
- // correct.
- SDL.buttonState |= 1 << event.button;
- } else if (event.type == 'mouseup') {
- SDL.buttonState &= ~(1 << event.button);
- }
- // fall through
- case 'mousemove': {
- Browser.calculateMouseEvent(event);
+ case 'mousedown': case 'mouseup': case 'mousemove': {
if (event.type != 'mousemove') {
var down = event.type === 'mousedown';
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.button', 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.state', 'down ? 1 : 0', 'i8') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.x', 'Browser.mouseX', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.y', 'Browser.mouseY', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.button, 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.state, 'down ? 1 : 0', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.x, 'Browser.mouseX', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.y, 'Browser.mouseY', 'i32') }}};
} else {
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.state', 'SDL.buttonState', 'i8') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.x', 'Browser.mouseX', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.y', 'Browser.mouseY', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.xrel', 'Browser.mouseMovementX', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.yrel', 'Browser.mouseMovementY', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.state, 'SDL.buttonState', 'i8') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.x, 'Browser.mouseX', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.y, 'Browser.mouseY', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.xrel, 'Browser.mouseMovementX', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.yrel, 'Browser.mouseMovementY', 'i32') }}};
}
break;
}
case 'unload': {
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
break;
}
case 'resize': {
- {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.ResizeEvent.w', 'event.w', 'i32') }}};
- {{{ makeSetValue('ptr', 'SDL.structs.ResizeEvent.h', 'event.h', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.w, 'event.w', 'i32') }}};
+ {{{ 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;
@@ -566,7 +679,7 @@ var LibrarySDL = {
// since the browser engine handles that for us. Therefore, in JS we just
// maintain a list of channels and return IDs for them to the SDL consumer.
allocateChannels: function(num) { // called from Mix_AllocateChannels and init
- if (SDL.numChannels && SDL.numChannels >= num) return;
+ if (SDL.numChannels && SDL.numChannels >= num && num != 0) return;
SDL.numChannels = num;
SDL.channels = [];
for (var i = 0; i < num; i++) {
@@ -597,30 +710,143 @@ 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: 1, // SDL_ENABLE
+ 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() {
if (SDL.version === null) {
- SDL.version = _malloc(SDL.structs.version.__size__);
- {{{ makeSetValue('SDL.version + SDL.structs.version.major', '0', '1', 'i8') }}}
- {{{ makeSetValue('SDL.version + SDL.structs.version.minor', '0', '3', 'i8') }}}
- {{{ makeSetValue('SDL.version + SDL.structs.version.patch', '0', '0', 'i8') }}}
+ SDL.version = _malloc({{{ C_STRUCTS.SDL_version.__size__ }}});
+ {{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.major, '0', '1', 'i8') }}};
+ {{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.minor, '0', '3', 'i8') }}};
+ {{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.patch, '0', '0', 'i8') }}};
}
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.onkeydown = SDL.receiveEvent;
- document.onkeyup = SDL.receiveEvent;
- document.onkeypress = SDL.receiveEvent;
- document.onblur = SDL.receiveEvent;
+ document.addEventListener("keydown", SDL.receiveEvent);
+ document.addEventListener("keyup", SDL.receiveEvent);
+ document.addEventListener("keypress", SDL.receiveEvent);
+ window.addEventListener("blur", SDL.receiveEvent);
document.addEventListener("visibilitychange", SDL.receiveEvent);
}
- window.onunload = 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);
// Initialize this structure carefully for closure
@@ -632,6 +858,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
},
@@ -646,11 +878,11 @@ var LibrarySDL = {
SDL_GetVideoInfo: function() {
// %struct.SDL_VideoInfo = type { i32, i32, %struct.SDL_PixelFormat*, i32, i32 } - 5 fields of quantum size
var ret = _malloc(5*Runtime.QUANTUM_SIZE);
- {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*0', '0', '0', 'i32') }}} // TODO
- {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*1', '0', '0', 'i32') }}} // TODO
- {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*2', '0', '0', 'void*') }}}
- {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*3', '0', 'Module["canvas"].width', 'i32') }}}
- {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*4', '0', 'Module["canvas"].height', 'i32') }}}
+ {{{ makeSetValue('ret+Runtime.QUANTUM_SIZE*0', '0', '0', 'i32') }}}; // TODO
+ {{{