Reputation: 5444
This goes for other browsers too but primary targets are Firefox and Chrome.
I have registered all pointer event listeners and had to register mouse down and moue up listeners to get the button state changes ( button states in pointer events are not accurate or "chorded" )
But even if I do event.preventDefault() on all pointer:over,enter,down,up,out,and,leave events (except for pointerType mouse for down, as it will disable the mousedown and mouse up listeners) I have remaining issues:
Multi-touch (more than one finger) will be taken over and perform a page-zoom operation rendering multi-touch in my app useless.
If there are scrollbars on the page a single touch will be taken over by the touch pan/scroll operation.
The preventDefault() is not implemented properly with the operating system (ie: windows 10) and a pointer down over my element will also generate a synthetic mouse down and synthetically move the mouse to the touched location. I can ignore the synthetic down and up events with messy code) but it places the mouse in unexpected locations and prevents using the mouse and touch simultaneously as separate inputs when desired. (fixing this will likely require a flag on the DOM element itself and not on a per event basis as the internal event queue will place latency between the OS and the browser dispatched events - I have solved this issue in my native apps )
I am testing with the web developer console so I can log status - I don't know if this influences things.
I have the same touch pan/zoom lack of control on Chrome. The mouse-up/down behavior is significantly different enough on Chrome vs Firefox to necessitate a separate code path. PK
For the code junkies - note this is an interface device "front end" for a "webasm" (game or game of life) engine :)
The objective is to get a "clean raw" event stream for any pointer entering the canvas element ( that is not captured by another element's handler ) Mirror the state of all buttons on all supported devices with captured pointers until capture is lost. Also to be able to use the mouse and touch (and any other supported devices) as separate devices ignoring all synthetic events and desiring to turn off anything where one device I am listening to is emulated by another device I am listening to (so they don't fight). Also to fully control what is done with the events until capture of the event stream for a pointer is lost. I would like to capture all touches that go down after and while the "primary" touch goes down on our element for a touch device (tablet, panel, screen etc) and optionally let them "fall through" if they are not used our UI.
Below are the event handlers which are attached to a "canvas" element. They don't do anything but forward the data to the webasm application. Of course will be cleaned up after it works. This is Firefox code. :)
canvas.addEventListener('pointerover', this.onPeOED, false);
canvas.addEventListener('pointerenter', this.onPeOED, false);
canvas.addEventListener('pointerdown', this.onPeOED, false);
canvas.addEventListener('pointermove', this.onPeMove, false);
canvas.addEventListener('pointerup', this.onPeUOCL, false);
canvas.addEventListener('pointerout', this.onPeUOCL, false);
canvas.addEventListener('pointercancel', this.onPeUOCL, false);
canvas.addEventListener('pointerleave', this.onPeUOCL, false);
canvas.addEventListener('mousedown', this.onMouseDown, false);
canvas.addEventListener('mouseup', this.onMouseUp, false);
onPeOED: (function(e) {
var handled = 0;
var code = e.type.charCodeAt(7);
var target = e.currentTarget;
var posX = e.clientX;
var posY = e.clientY;
var flags = (1 << (9 + 16)); // fPointerInside
if(posX < 0 || posY < 0 || posX >= target.width || posY >= target.height) {
flags = 0;
}
// o == over, e == enter, d == down,
if(code == 100) { // 100 == 'd'
flags |= (1 << (10 + 16)); // in contact << 16
}
target._mousePointerId_ = -1;
switch(e.pointerType) {
case 'mouse':
target._mousePointerId_ = e.pointerId;
if(code == 100) { // 100 == 'd' handled by onMouseDown
target.setPointerCapture(e.pointerId);
return;
}
flags |= 0x0100; // UID_MOUSE << 8
break;
case 'pen':
flags |= 0x0200; // UID_STYLUS << 8
break;
case 'touch':
flags |= 0x0300; // UID_FINGER << 8
break;
default:
Module.print("unknown UID");
break;
}
if(code == 100) { // 100 == 'd'
target.setPointerCapture(e.pointerId);
}
// Module.print("ON uid " + e.pointerType + " " + e.type + " buttons " + e.which + " inside " + ((flags & (1 << (9 + 16))) != 0));
if(e.buttons != 0) {
flags |= (e.buttons << 16) | (1 << (10 + 16)); // fPointerInContact
}
handled = ccall('onPointerFlagsOn', 'number', EngineConnector.number_11_Sig,
// target type|flags time id screenX screenY pageX pageY targetX targetY pressure
[ target._CPPHandle_,
(code | flags),
e.timeStamp,
e.pointerId,
e.screenX,
e.screenY,
e.pageX,
e.pageY,
posX,
posY,
e.pressure
]);
e.preventDefault();
}),
onMouseDown: (function(e) {
var target = e.currentTarget;
var posX = e.clientX;
var posY = e.clientY;
// Module.print("mouse ME down " + target._mousePointerId_);
if(target._mousePointerId_ < 0 || posX < 0 || posY < 0 || posX >= target.width || posY >= target.height) {
Module.print("mouse ignored");
e.preventDefault();
return; // ignore downs while outide element
}
var wentDown = (e.buttons & (~(target._mouseButtons_)));
target._mouseButtons_ = e.buttons;
// Module.print("mouse down " + e.buttons + " went down " + wentDown);
handled = ccall('onPointerFlagsOn', 'number', EngineConnector.number_11_Sig,
// target type time id screenX screenY pageX pageY targetX targetY pressure
[ target._CPPHandle_,
// 'd' UID_MOUSE fPointerInside fPointerInContact
(100 | 0x0100 | (1 << (9 + 16)) | (1 << (10 + 16)) | (wentDown << 16)),
e.timeStamp,
e.pointerId,
e.screenX,
e.screenY,
e.pageX,
e.pageY,
posX,
posY,
e.pressure
]);
// e.preventDefault();
}),
fireButtonUp: (function(e, buttons) {
// Module.print("ME fire up true " + buttons );
var ptrId = e.currentTarget._mousePointerId_;
if(ptrId < 0) {
return;
}
ccall('onPointerFlagsOff', 'number', EngineConnector.number_11_Sig,
// target type time id screenX screenY pageX pageY targetX targetY pressure
[e.currentTarget._CPPHandle_,
(117 | 0x0100 | (buttons << 16)), // 117 == 'u' == 'up', 1 == UID_MOUSE
e.timeStamp,
ptrId,
e.screenX,
e.screenY,
e.pageX,
e.pageY,
e.clientX,
e.clientY,
0,
]);
}),
// up/leave
onMouseUp: (function(e) {
var target = e.currentTarget;
var posX = e.clientX;
var posY = e.clientY;
if(posX < 0 || posY < 0 || posX >= target.width || posY >= target.height) {
// we have to kill ALL the buttons
EngineConnector.fireButtonUp(e,target._mouseButtons_ | (1 << 10));
target._mouseButtons_ = 0;
} else {
var released = (target._mouseButtons_ & (~e.buttons)); // will leave the one we released
target._mouseButtons_ = e.buttons;
if(e.buttons == 0) {
released |= (1 << 10); // fPointerInContact
}
EngineConnector.fireButtonUp(e,(released | (1 << 9)));
}
e.preventDefault();
}),
onPeMove: (function(e) {
var target = e.currentTarget;
var posX = e.clientX;
var posY = e.clientY;
var flags = (1 << 9);
if(posX < 0 || posY < 0 || posX >= target.width || posY >= target.height) {
flags = 0;
}
var pType = e.pointerType.charCodeAt(0);
if(pType == 109) { // 'm' == 109
// MOUSE
if(target._mousePointerId_ < 0) {
e.preventDefault();
return;
}
} else {
Module.print("move !!!");
}
flags |= pType; // 'm', 'p', 't'
handled = ccall('onPointerMove', 'number', EngineConnector.number_11_Sig,
// target flags time id screenX screenY pageX pageY targetX targetY pressure
[ target._CPPHandle_,
flags,
e.timeStamp,
e.pointerId,
e.screenX,
e.screenY,
e.pageX,
e.pageY,
posX,
posY,
e.pressure
]);
}),
onPeUOCL: (function(e) {
var code = e.type.charCodeAt(7);
var target = e.currentTarget;
target._poinsePointerId_ = -1;
switch(e.pointerType) {
case 'mouse':
target._poinsePointerId_ = e.pointerId;
if(code == 117) { // 117 == 'u' mouseup handled by mouse event handler
return;
}
code |= 0x0100; // UID_MOUSE << 8
// let leave and cancel get reported
break;
case 'pen':
code |= 0x0200; // UID_STYLUS << 8
if(code == 117) {
code |= ((1 << 16) << e.which);
}
break;
case 'touch':
code |= 0x0300; // UID_FINGER << 8
if(code == 117) {
code |= ((1 << 16) << e.which);
}
break;
default:
Module.print("unknown UID");
break;
}
var posX = e.clientX;
var posY = e.clientY;
if(posX >= 0 && posY >= 0 || posX < target.width || posY < target.height) {
code |= (1 << (9 + 16));
}
// Module.print("off uid " + e.pointerType + " " + e.type + " buttons " + e.which + " inside " + ((code & (1 << (9 + 16))) != 0));
var handled = 0;
handled = ccall('onPointerFlagsOff', 'number', EngineConnector.number_11_Sig,
// target type time id screenX screenY pageX pageY targetX targetY pressure
[e.currentTarget._CPPHandle_,
code,
e.timeStamp,
e.pointerId,
e.screenX,
e.screenY,
e.pageX,
e.pageY,
posX,
posY,
e.pressure,
]);
e.preventDefault();
}),
A quote from Me when I was working on getting Microsoft to create DirectX almost 30 years ago - it's Deja Vous all over again :)
"For any system to be truly flexible one must be able to implement that system from scratch with the facilities it provides."
Upvotes: 3
Views: 1375
Reputation: 5444
I'm surprised no no-one mentioned this. The solution is canvas.setAttribute('style','touch-action: none');
https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action
BTW On Chrome works 99% properly.
Firefox will still pass though synthetic clicks which I have to put in nasty event timing code to filter out.
Both fail to disable Windows 10 Mouse from Touch emulation rendering it impossible to use the mouse and touch at the same time. I have not found a way for the user to disable this on windows 10. In any case I don't want to disable it system wide but only when tapping and touch/dragging on the canvas element.
Upvotes: 1