Skip to content

Commit d990bbd

Browse files
committed
Merge pull request #2285 from juj/sdl2-touch-api-additions
Sdl2 touch api additions
2 parents 512b7bd + 2faa69a commit d990bbd

9 files changed

+386
-158
lines changed

src/library_browser.js

+30-16
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ mergeInto(LibraryManager.library, {
482482
mouseY: 0,
483483
mouseMovementX: 0,
484484
mouseMovementY: 0,
485+
touches: {},
486+
lastTouches: {},
485487

486488
calculateMouseEvent: function(event) { // event should be mousemove, mousedown or mouseup
487489
if (Browser.pointerLock) {
@@ -510,8 +512,9 @@ mergeInto(LibraryManager.library, {
510512
// Otherwise, calculate the movement based on the changes
511513
// in the coordinates.
512514
var rect = Module["canvas"].getBoundingClientRect();
513-
var x, y;
514-
515+
var cw = Module["canvas"].width;
516+
var ch = Module["canvas"].height;
517+
515518
// Neither .scrollX or .pageXOffset are defined in a spec, but
516519
// we prefer .scrollX because it is currently in a spec draft.
517520
// (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/)
@@ -522,26 +525,37 @@ mergeInto(LibraryManager.library, {
522525
// and we have no viable fallback.
523526
assert((typeof scrollX !== 'undefined') && (typeof scrollY !== 'undefined'), 'Unable to retrieve scroll position, mouse positions likely broken.');
524527
#endif
525-
if (event.type == 'touchstart' ||
526-
event.type == 'touchend' ||
527-
event.type == 'touchmove') {
528-
var t = event.touches.item(0);
529-
if (t) {
530-
x = t.pageX - (scrollX + rect.left);
531-
y = t.pageY - (scrollY + rect.top);
532-
} else {
533-
return;
528+
529+
if (event.type === 'touchstart' || event.type === 'touchend' || event.type === 'touchmove') {
530+
var touch = event.touch;
531+
if (touch === undefined) {
532+
return; // the "touch" property is only defined in SDL
533+
534534
}
535-
} else {
536-
x = event.pageX - (scrollX + rect.left);
537-
y = event.pageY - (scrollY + rect.top);
535+
var adjustedX = touch.pageX - (scrollX + rect.left);
536+
var adjustedY = touch.pageY - (scrollY + rect.top);
537+
538+
adjustedX = adjustedX * (cw / rect.width);
539+
adjustedY = adjustedY * (ch / rect.height);
540+
541+
var coords = { x: adjustedX, y: adjustedY };
542+
543+
if (event.type === 'touchstart') {
544+
Browser.lastTouches[touch.identifier] = coords;
545+
Browser.touches[touch.identifier] = coords;
546+
} else if (event.type === 'touchend' || event.type === 'touchmove') {
547+
Browser.lastTouches[touch.identifier] = Browser.touches[touch.identifier];
548+
Browser.touches[touch.identifier] = { x: adjustedX, y: adjustedY };
549+
}
550+
return;
538551
}
539552

553+
var x = event.pageX - (scrollX + rect.left);
554+
var y = event.pageY - (scrollY + rect.top);
555+
540556
// the canvas might be CSS-scaled compared to its backbuffer;
541557
// SDL-using content will want mouse coordinates in terms
542558
// of backbuffer units.
543-
var cw = Module["canvas"].width;
544-
var ch = Module["canvas"].height;
545559
x = x * (cw / rect.width);
546560
y = y * (ch / rect.height);
547561

src/library_sdl.js

+172-50
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// or otherwise).
1111

1212
var LibrarySDL = {
13-
$SDL__deps: ['$FS', '$PATH', '$Browser'],
13+
$SDL__deps: ['$FS', '$PATH', '$Browser', 'SDL_GetTicks'],
1414
$SDL: {
1515
defaults: {
1616
width: 320,
@@ -82,6 +82,8 @@ var LibrarySDL = {
8282

8383
DOMEventToSDLEvent: {},
8484

85+
TOUCH_DEFAULT_ID: 0, // Our default deviceID for touch events (we get nothing from the browser)
86+
8587
keyCodes: { // DOM code ==> SDL code. See https://developer.mozilla.org/en/Document_Object_Model_%28DOM%29/KeyboardEvent and SDL_keycode.h
8688
// For keys that don't have unicode value, we map DOM codes with the corresponding scan codes + 1024 (using "| 1 << 10")
8789
16: 225 | 1<<10, // shift
@@ -417,50 +419,104 @@ var LibrarySDL = {
417419
}
418420
},
419421

420-
touchX: 0, touchY: 0,
422+
// the browser sends out touchstart events with the whole group of touches
423+
// even if we received a previous touchstart for a specific touch identifier.
424+
// You can test this by pressing one finger to the screen, then another. You'll
425+
// receive two touchstart events, the first with a touches count of 1 the second
426+
// with a touches count of two.
427+
// SDL sends out a new touchstart event for only each newly started touch so to
428+
// emulate this, we keep track of previously started touches.
429+
downFingers: {},
421430
savedKeydown: null,
422431

423432
receiveEvent: function(event) {
424433
switch(event.type) {
425-
case 'touchstart':
434+
case 'touchstart': case 'touchmove': {
426435
event.preventDefault();
427-
var touch = event.touches[0];
428-
touchX = touch.pageX;
429-
touchY = touch.pageY;
430-
var event = {
431-
type: 'mousedown',
436+
437+
var touches = [];
438+
439+
// Clear out any touchstart events that we've already processed
440+
if (event.type === 'touchstart') {
441+
for (var i = 0; i < event.touches.length; i++) {
442+
var touch = event.touches[i];
443+
if (SDL.downFingers[touch.identifier] != true) {
444+
SDL.downFingers[touch.identifier] = true;
445+
touches.push(touch);
446+
}
447+
}
448+
} else {
449+
touches = event.touches;
450+
}
451+
452+
var firstTouch = touches[0];
453+
if (event.type == 'touchstart') {
454+
SDL.DOMButtons[0] = 1;
455+
}
456+
var mouseEventType;
457+
switch(event.type) {
458+
case 'touchstart': mouseEventType = 'mousedown'; break;
459+
case 'touchmove': mouseEventType = 'mousemove'; break;
460+
}
461+
var mouseEvent = {
462+
type: mouseEventType,
432463
button: 0,
433-
pageX: touchX,
434-
pageY: touchY
464+
pageX: firstTouch.clientX,
465+
pageY: firstTouch.clientY
435466
};
436-
SDL.DOMButtons[0] = 1;
437-
SDL.events.push(event);
438-
break;
439-
case 'touchmove':
440-
event.preventDefault();
441-
var touch = event.touches[0];
442-
touchX = touch.pageX;
443-
touchY = touch.pageY;
444-
event = {
445-
type: 'mousemove',
446-
button: 0,
447-
pageX: touchX,
448-
pageY: touchY
467+
SDL.events.push(mouseEvent);
468+
469+
for (var i = 0; i < touches.length; i++) {
470+
var touch = touches[i];
471+
SDL.events.push({
472+
type: event.type,
473+
touch: touch
474+
});
449475
};
450-
SDL.events.push(event);
451476
break;
452-
case 'touchend':
477+
}
478+
case 'touchend': {
453479
event.preventDefault();
454-
event = {
480+
481+
// Remove the entry in the SDL.downFingers hash
482+
// because the finger is no longer down.
483+
for(var i = 0; i < event.changedTouches.length; i++) {
484+
var touch = event.changedTouches[i];
485+
if (SDL.downFingers[touch.identifier] === true) {
486+
delete SDL.downFingers[touch.identifier];
487+
}
488+
}
489+
490+
var mouseEvent = {
455491
type: 'mouseup',
456492
button: 0,
457-
pageX: touchX,
458-
pageY: touchY
493+
pageX: event.changedTouches[0].clientX,
494+
pageY: event.changedTouches[0].clientY
459495
};
460496
SDL.DOMButtons[0] = 0;
461-
SDL.events.push(event);
497+
SDL.events.push(mouseEvent);
498+
499+
for (var i = 0; i < event.changedTouches.length; i++) {
500+
var touch = event.changedTouches[i];
501+
SDL.events.push({
502+
type: 'touchend',
503+
touch: touch
504+
});
505+
};
462506
break;
507+
}
463508
case 'mousemove':
509+
if (SDL.DOMButtons[0] === 1) {
510+
SDL.events.push({
511+
type: 'touchmove',
512+
touch: {
513+
identifier: 0,
514+
deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}},
515+
pageX: event.pageX,
516+
pageY: event.pageY
517+
}
518+
});
519+
}
464520
if (Browser.pointerLock) {
465521
// workaround for firefox bug 750111
466522
if ('mozMovementX' in event) {
@@ -502,13 +558,31 @@ var LibrarySDL = {
502558
};
503559
} else if (event.type == 'mousedown') {
504560
SDL.DOMButtons[event.button] = 1;
561+
SDL.events.push({
562+
type: 'touchstart',
563+
touch: {
564+
identifier: 0,
565+
deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}},
566+
pageX: event.pageX,
567+
pageY: event.pageY
568+
}
569+
});
505570
} else if (event.type == 'mouseup') {
506571
// ignore extra ups, can happen if we leave the canvas while pressing down, then return,
507572
// since we add a mouseup in that case
508573
if (!SDL.DOMButtons[event.button]) {
509574
return;
510575
}
511576

577+
SDL.events.push({
578+
type: 'touchend',
579+
touch: {
580+
identifier: 0,
581+
deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}},
582+
pageX: event.pageX,
583+
pageY: event.pageY
584+
}
585+
});
512586
SDL.DOMButtons[event.button] = 0;
513587
}
514588

@@ -600,6 +674,10 @@ var LibrarySDL = {
600674
event.handled = true;
601675

602676
switch (event.type) {
677+
case 'touchstart': case 'touchend': case 'touchmove': {
678+
Browser.calculateMouseEvent(event);
679+
break;
680+
}
603681
case 'keydown': case 'keyup': {
604682
var down = event.type === 'keydown';
605683
var code = event.keyCode;
@@ -690,20 +768,53 @@ var LibrarySDL = {
690768
if (event.type != 'mousemove') {
691769
var down = event.type === 'mousedown';
692770
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
771+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.timestamp, '0', 'i32') }}};
772+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.windowID, '0', 'i32') }}};
773+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.which, '0', 'i32') }}};
693774
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.button, 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3
694775
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.state, 'down ? 1 : 0', 'i8') }}};
695776
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.x, 'Browser.mouseX', 'i32') }}};
696777
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.y, 'Browser.mouseY', 'i32') }}};
697778
} else {
698779
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
699-
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.state, 'SDL.buttonState', 'i8') }}};
780+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.timestamp, '0', 'i32') }}};
781+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.windowID, '0', 'i32') }}};
782+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.which, '0', 'i32') }}};
783+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.state, 'SDL.buttonState', 'i32') }}};
700784
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.x, 'Browser.mouseX', 'i32') }}};
701785
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.y, 'Browser.mouseY', 'i32') }}};
702786
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.xrel, 'Browser.mouseMovementX', 'i32') }}};
703787
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.yrel, 'Browser.mouseMovementY', 'i32') }}};
704788
}
705789
break;
706790
}
791+
case 'touchstart': case 'touchend': case 'touchmove': {
792+
var touch = event.touch;
793+
var w = Module['canvas'].width;
794+
var h = Module['canvas'].height;
795+
var x = Browser.touches[touch.identifier].x / w;
796+
var y = Browser.touches[touch.identifier].y / h;
797+
var lx = Browser.lastTouches[touch.identifier].x / w;
798+
var ly = Browser.lastTouches[touch.identifier].y / h;
799+
var dx = x - lx;
800+
var dy = y - ly;
801+
if (touch['deviceID'] === undefined) touch.deviceID = SDL.TOUCH_DEFAULT_ID;
802+
if (dx === 0 && dy === 0 && event.type === 'touchmove') return; // don't send these if nothing happened
803+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
804+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.timestamp, '_SDL_GetTicks()', 'i32') }}};
805+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.touchId, 'touch.deviceID', 'i64') }}};
806+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.fingerId, 'touch.identifier', 'i64') }}};
807+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.x, 'x', 'float') }}};
808+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.y, 'y', 'float') }}};
809+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dx, 'dx', 'float') }}};
810+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dy, 'dy', 'float') }}};
811+
if (touch.force !== undefined) {
812+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'touch.force', 'float') }}};
813+
} else { // No pressure data, send a digital 0/1 pressure.
814+
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'event.type == "touchend" ? 0 : 1', 'float') }}};
815+
}
816+
break;
817+
}
707818
case 'unload': {
708819
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
709820
break;
@@ -948,14 +1059,17 @@ var LibrarySDL = {
9481059
SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs
9491060
_memset(SDL.keyboardState, 0, 0x10000);
9501061
// Initialize this structure carefully for closure
951-
SDL.DOMEventToSDLEvent['keydown'] = 0x300 /* SDL_KEYDOWN */;
952-
SDL.DOMEventToSDLEvent['keyup'] = 0x301 /* SDL_KEYUP */;
953-
SDL.DOMEventToSDLEvent['keypress'] = 0x303 /* SDL_TEXTINPUT */;
954-
SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */;
955-
SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */;
956-
SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */;
957-
SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */;
958-
SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */;
1062+
SDL.DOMEventToSDLEvent['keydown'] = 0x300 /* SDL_KEYDOWN */;
1063+
SDL.DOMEventToSDLEvent['keyup'] = 0x301 /* SDL_KEYUP */;
1064+
SDL.DOMEventToSDLEvent['keypress'] = 0x303 /* SDL_TEXTINPUT */;
1065+
SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */;
1066+
SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */;
1067+
SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */;
1068+
SDL.DOMEventToSDLEvent['touchstart'] = 0x700 /* SDL_FINGERDOWN */;
1069+
SDL.DOMEventToSDLEvent['touchend'] = 0x701 /* SDL_FINGERUP */;
1070+
SDL.DOMEventToSDLEvent['touchmove'] = 0x702 /* SDL_FINGERMOTION */;
1071+
SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */;
1072+
SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */;
9591073
// These are not technically DOM events; the HTML gamepad API is poll-based.
9601074
// However, we define them here, as the rest of the SDL code assumes that
9611075
// all SDL events originate as DOM events.
@@ -1025,7 +1139,7 @@ var LibrarySDL = {
10251139
},
10261140

10271141
SDL_SetVideoMode: function(width, height, depth, flags) {
1028-
['mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) {
1142+
['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) {
10291143
Module['canvas'].addEventListener(event, SDL.receiveEvent, true);
10301144
});
10311145

@@ -1476,20 +1590,28 @@ var LibrarySDL = {
14761590
return 0;
14771591
},
14781592

1479-
SDL_PeepEvents: function(events, numEvents, action, from, to) {
1593+
SDL_PeepEvents: function(events, requestedEventCount, action, from, to) {
14801594
switch(action) {
14811595
case 2: { // SDL_GETEVENT
1482-
assert(numEvents == 1);
1483-
var got = 0;
1484-
while (SDL.events.length > 0 && numEvents > 0) {
1485-
var type = SDL.DOMEventToSDLEvent[SDL.events[0].type];
1486-
if (type < from || type > to) break;
1487-
SDL.makeCEvent(SDL.events.shift(), events);
1488-
got++;
1489-
numEvents--;
1490-
// events += sizeof(..)
1596+
// We only handle 1 event right now
1597+
assert(requestedEventCount == 1);
1598+
1599+
var index = 0;
1600+
var retrievedEventCount = 0;
1601+
// this should look through the entire queue until it has filled up the events
1602+
// array
1603+
while (index < SDL.events.length && retrievedEventCount < requestedEventCount) {
1604+
var event = SDL.events[index];
1605+
var type = SDL.DOMEventToSDLEvent[event.type];
1606+
if (from <= type && type <= to) {
1607+
SDL.makeCEvent(event, events);
1608+
SDL.events.splice(index, 1);
1609+
retrievedEventCount++;
1610+
} else {
1611+
index++;
1612+
}
14911613
}
1492-
return got;
1614+
return retrievedEventCount;
14931615
}
14941616
default: throw 'SDL_PeepEvents does not yet support that action: ' + action;
14951617
}

0 commit comments

Comments
 (0)