diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..19041f9 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -1116,6 +1118,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1155,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1175,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1186,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1241,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1263,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1278,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1287,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1302,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1318,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1404,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1438,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1459,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1486,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1509,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1531,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1607,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1624,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1660,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1723,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1734,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1760,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..19041f9 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -1116,6 +1118,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1155,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1175,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1186,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1241,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1263,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1278,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1287,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1302,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1318,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1404,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1438,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1459,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1486,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1509,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1531,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1607,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1624,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1660,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1723,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1734,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1760,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index de81597..56fe7af 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -128,6 +128,95 @@ }); }); + describe('touch vs pointer', function () + { + it('should call touchstart and pointerdown when touch event and pointer supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should not call touchstart or pointerdown when pointer event and touch supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.not.have.been.called; + expect(pointerSpy).to.not.have.been.called; + }); + + it('should call touchstart and pointerdown when touch event and pointer not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, false); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should call touchstart and pointerdown when pointer event and touch not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + pointer.interaction.supportsTouchEvents = false; + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + }); + describe('add/remove events', function () { let stub; @@ -283,7 +372,7 @@ expect(element.removeEventListener).to.have.been.calledWith('mouseover'); }); - it('should add and remove touch events to element', function () + it('should add and remove touch events to element without pointer events', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; @@ -306,6 +395,30 @@ expect(element.removeEventListener).to.have.been.calledWith('touchend'); expect(element.removeEventListener).to.have.been.calledWith('touchmove'); }); + + it('should add and remove touch events to element with pointer events', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); }); describe('onClick', function () @@ -1298,4 +1411,91 @@ expect(hit).to.equal(behind); }); }); + + describe('InteractionData properties', function () + { + it('isPrimary should be set for first touch only', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.touchstart(10, 10, 1); + expect(pointer.interaction.activeInteractionData[1]).to.exist; + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should be primary on touch start').to.be.true; + pointer.touchstart(13, 9, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should not be primary').to.be.false; + pointer.touchmove(10, 20, 1); + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should still be primary after move').to.be.true; + pointer.touchend(10, 10, 1); + pointer.touchmove(13, 29, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should still not be primary after first is done').to.be.false; + }); + }); + + describe('mouse events from pens', function () + { + it('should call mousedown handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousedown', eventSpy); + + pointer.pendown(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mousemove handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousemove', eventSpy); + + pointer.penmove(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mouseup handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mouseup', eventSpy); + + pointer.penup(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + }); }); diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f4619a1..84dd1f3 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -43,6 +43,104 @@ * @member {number} */ this.identifier = null; + + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + * @type {Boolean} + */ + this.isPrimary = false; + + /** + * Indicates which button was pressed on the mouse or pointer device to trigger the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + * @type {number} + */ + this.button = 0; + + /** + * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered. + * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + * @type {number} + */ + this.buttons = 0; + + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + * @type {number} + */ + this.width = 0; + + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + * @type {number} + */ + this.height = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX + * @type {number} + */ + this.tiltX = 0; + + /** + * The angle, in degrees, between the pointer device and the screen. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY + * @type {number} + */ + this.tiltY = 0; + + /** + * The type of pointer that triggered the event. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType + * @type {string} + */ + this.pointerType = null; + + /** + * Pressure applied by the pointing device during the event. A Touch's force property + * will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure + * @type {number} + */ + this.pressure = 0; + + /** + * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle + * @type {number} + */ + this.rotationAngle = 0; + + /** + * Twist of a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.twist = 0; + + /** + * Barrel pressure on a stylus pointer. + * @see https://w3c.github.io/pointerevents/#pointerevent-interface + * @type {number} + */ + this.tangentialPressure = 0; + } + + /** + * The unique identifier of the pointer. It will be the same as `identifier`. + * @readonly + * @member {number} + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId + */ + get pointerId() + { + return this.identifier; } /** @@ -61,4 +159,44 @@ { return displayObject.worldTransform.applyInverse(globalPos || this.global, point); } + + /** + * Copies properties from normalized event data. + * + * @param {Touch|MouseEvent|PointerEvent} event The normalized event data + * @private + */ + _copyEvent(event) + { + // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite + // it with "false" on later events when our shim for it on touch events might not be + // accurate + if (event.isPrimary) + { + this.isPrimary = true; + } + this.button = event.button; + this.buttons = event.buttons; + this.width = event.width; + this.height = event.height; + this.tiltX = event.tiltX; + this.tiltY = event.tiltY; + this.pointerType = event.pointerType; + this.pressure = event.pressure; + this.rotationAngle = event.rotationAngle; + this.twist = event.twist || 0; + this.tangentialPressure = event.tangentialPressure || 0; + } + + /** + * Resets the data for pooling. + * + * @private + */ + _reset() + { + // isPrimary is the only property that we really need to reset - everything else is + // guaranteed to be overwritten + this.isPrimary = false; + } } diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 729469c..19041f9 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -732,7 +732,6 @@ window.addEventListener('pointercancel', this.onPointerCancel, true); window.addEventListener('pointerup', this.onPointerUp, true); } - else { window.document.addEventListener('mousemove', this.onPointerMove, true); @@ -740,14 +739,17 @@ this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true); window.addEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); - } + // always look directly for touch events so that we can provide original data + // In a future version we should change this to being just a fallback and rely solely on + // PointerEvents whenever available + if (this.supportsTouchEvents) + { + this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true); } this.eventsAdded = true; @@ -793,14 +795,14 @@ this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true); this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true); window.removeEventListener('mouseup', this.onPointerUp, true); + } - if (this.supportsTouchEvents) - { - this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); - this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); - this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); - } + if (this.supportsTouchEvents) + { + this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true); + this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true); + this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true); } this.interactionDOMElement = null; @@ -1116,6 +1118,9 @@ */ onPointerDown(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); /** @@ -1150,9 +1155,10 @@ { this.emit('touchstart', interactionEvent); } - else if (event.pointerType === 'mouse') + // emit a mouse event for "pen" pointers, the way a browser would emit a fallback event + else if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); } @@ -1169,8 +1175,7 @@ */ processPointerDown(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; - + const data = interactionEvent.data; const id = interactionEvent.data.identifier; if (hit) @@ -1181,13 +1186,13 @@ } this.dispatchEvent(displayObject, 'pointerdown', interactionEvent); - if (e.type === 'touchstart' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchstart', interactionEvent); } - else if (e.type === 'mousedown' || e.pointerType === 'mouse') + else if (data.pointerType === 'mouse' || data.pointerType === 'pen') { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; if (isRightButton) { @@ -1236,9 +1241,9 @@ this.emit(cancelled ? 'pointercancel' : `pointerup${eventAppend}`, interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { - const isRightButton = event.button === 2 || event.which === 3; + const isRightButton = event.button === 2; this.emit(isRightButton ? `rightup${eventAppend}` : `mouseup${eventAppend}`, interactionEvent); } @@ -1258,6 +1263,9 @@ */ onPointerCancel(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, true, this.processPointerCancel); } @@ -1270,7 +1278,7 @@ */ processPointerCancel(interactionEvent, displayObject) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; @@ -1279,7 +1287,7 @@ delete displayObject.trackedPointers[id]; this.dispatchEvent(displayObject, 'pointercancel', interactionEvent); - if (e.type === 'touchcancel' || e.pointerType === 'touch') + if (data.pointerType === 'touch') { this.dispatchEvent(displayObject, 'touchcancel', interactionEvent); } @@ -1294,6 +1302,9 @@ */ onPointerUp(event) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && event.pointerType === 'touch') return; + this.onPointerComplete(event, false, this.processPointerUp); } @@ -1307,20 +1318,20 @@ */ processPointerUp(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; const trackingData = displayObject.trackedPointers[id]; - const isTouch = (e.type === 'touchend' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type.indexOf('mouse') === 0 || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); // Mouse only if (isMouse) { - const isRightButton = e.button === 2 || e.which === 3; + const isRightButton = data.button === 2; const flags = InteractionTrackingData.FLAGS; @@ -1393,6 +1404,9 @@ */ onPointerMove(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); if (events[0].pointerType === 'mouse') @@ -1424,7 +1438,7 @@ ); this.emit('pointermove', interactionEvent); if (event.pointerType === 'touch') this.emit('touchmove', interactionEvent); - if (event.pointerType === 'mouse') this.emit('mousemove', interactionEvent); + if (event.pointerType === 'mouse' || event.pointerType === 'pen') this.emit('mousemove', interactionEvent); } if (events[0].pointerType === 'mouse') @@ -1445,11 +1459,11 @@ */ processPointerMove(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; - const isTouch = (e.type === 'touchmove' || e.pointerType === 'touch'); + const isTouch = data.pointerType === 'touch'; - const isMouse = (e.type === 'mousemove' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); if (isMouse) { @@ -1472,6 +1486,9 @@ */ onPointerOut(originalEvent) { + // if we support touch events, then only use those for touch events, not pointer events + if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return; + const events = this.normalizeToPointerData(originalEvent); // Only mouse and pointer can call onPointerOut, so events will always be length 1 @@ -1492,7 +1509,7 @@ this.processInteractive(interactionEvent, this.renderer._lastObjectRendered, this.processPointerOverOut, false); this.emit('pointerout', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseout', interactionEvent); } @@ -1514,11 +1531,11 @@ */ processPointerOverOut(interactionEvent, displayObject, hit) { - const e = interactionEvent.data.originalEvent; + const data = interactionEvent.data; const id = interactionEvent.data.identifier; - const isMouse = (e.type === 'mouseover' || e.type === 'mouseout' || e.pointerType === 'mouse'); + const isMouse = (data.pointerType === 'mouse' || data.pointerType === 'pen'); let trackingData = displayObject.trackedPointers[id]; @@ -1590,7 +1607,7 @@ } this.emit('pointerover', interactionEvent); - if (event.pointerType === 'mouse') + if (event.pointerType === 'mouse' || event.pointerType === 'pen') { this.emit('mouseover', interactionEvent); } @@ -1607,19 +1624,25 @@ { const pointerId = event.pointerId; + let interactionData; + if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') { - return this.mouse; + interactionData = this.mouse; } else if (this.activeInteractionData[pointerId]) { - return this.activeInteractionData[pointerId]; + interactionData = this.activeInteractionData[pointerId]; } - - const interactionData = this.interactionDataPool.pop() || new InteractionData(); - - interactionData.identifier = pointerId; - this.activeInteractionData[pointerId] = interactionData; + else + { + interactionData = this.interactionDataPool.pop() || new InteractionData(); + interactionData.identifier = pointerId; + this.activeInteractionData[pointerId] = interactionData; + } + // copy properties from the event, so that we can make sure that touch/pointer specific + // data is available + interactionData._copyEvent(event); return interactionData; } @@ -1637,6 +1660,7 @@ if (interactionData) { delete this.activeInteractionData[pointerId]; + interactionData._reset(); this.interactionDataPool.push(interactionData); } } @@ -1699,7 +1723,10 @@ if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0; if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0; - if (typeof touch.isPrimary === 'undefined') touch.isPrimary = event.touches.length === 1; + if (typeof touch.isPrimary === 'undefined') + { + touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart'; + } if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1; if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1; if (typeof touch.tiltX === 'undefined') touch.tiltX = 0; @@ -1707,8 +1734,12 @@ if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch'; if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0; if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5; - if (typeof touch.rotation === 'undefined') touch.rotation = touch.rotationAngle || 0; - + touch.twist = 0; + touch.tangentialPressure = 0; + // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven + // support, and the fill ins are not quite the same + // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top + // left is not 0,0 on the page if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX; if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY; @@ -1729,7 +1760,8 @@ if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse'; if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID; if (typeof event.pressure === 'undefined') event.pressure = 0.5; - if (typeof event.rotation === 'undefined') event.rotation = 0; + event.twist = 0; + event.tangentialPressure = 0; // mark the mouse event as normalized, just so that we know we did it event.isNormalized = true; diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index de81597..56fe7af 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -128,6 +128,95 @@ }); }); + describe('touch vs pointer', function () + { + it('should call touchstart and pointerdown when touch event and pointer supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should not call touchstart or pointerdown when pointer event and touch supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.not.have.been.called; + expect(pointerSpy).to.not.have.been.called; + }); + + it('should call touchstart and pointerdown when touch event and pointer not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, false); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + + it('should call touchstart and pointerdown when pointer event and touch not supported', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const touchSpy = sinon.spy(function touchListen() { /* noop */ }); + const pointerSpy = sinon.spy(function pointerListen() { /* noop */ }); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + pointer.interaction.supportsTouchEvents = false; + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('touchstart', touchSpy); + graphics.on('pointerdown', pointerSpy); + + pointer.touchstart(10, 10, 0, true); + + expect(touchSpy).to.have.been.calledOnce; + expect(pointerSpy).to.have.been.calledOnce; + }); + }); + describe('add/remove events', function () { let stub; @@ -283,7 +372,7 @@ expect(element.removeEventListener).to.have.been.calledWith('mouseover'); }); - it('should add and remove touch events to element', function () + it('should add and remove touch events to element without pointer events', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; @@ -306,6 +395,30 @@ expect(element.removeEventListener).to.have.been.calledWith('touchend'); expect(element.removeEventListener).to.have.been.calledWith('touchmove'); }); + + it('should add and remove touch events to element with pointer events', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); }); describe('onClick', function () @@ -1298,4 +1411,91 @@ expect(hit).to.equal(behind); }); }); + + describe('InteractionData properties', function () + { + it('isPrimary should be set for first touch only', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const pointer = this.pointer = new MockPointer(stage); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + + pointer.touchstart(10, 10, 1); + expect(pointer.interaction.activeInteractionData[1]).to.exist; + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should be primary on touch start').to.be.true; + pointer.touchstart(13, 9, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should not be primary').to.be.false; + pointer.touchmove(10, 20, 1); + expect(pointer.interaction.activeInteractionData[1].isPrimary, + 'first touch should still be primary after move').to.be.true; + pointer.touchend(10, 10, 1); + pointer.touchmove(13, 29, 2); + expect(pointer.interaction.activeInteractionData[2].isPrimary, + 'second touch should still not be primary after first is done').to.be.false; + }); + }); + + describe('mouse events from pens', function () + { + it('should call mousedown handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousedown', eventSpy); + + pointer.pendown(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mousemove handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mousemove', eventSpy); + + pointer.penmove(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + + it('should call mouseup handler', function () + { + const stage = new PIXI.Container(); + const graphics = new PIXI.Graphics(); + const eventSpy = sinon.spy(); + const pointer = this.pointer = new MockPointer(stage, null, null, true); + + stage.addChild(graphics); + graphics.beginFill(0xFFFFFF); + graphics.drawRect(0, 0, 50, 50); + graphics.interactive = true; + graphics.on('mouseup', eventSpy); + + pointer.penup(10, 10); + + expect(eventSpy).to.have.been.calledOnce; + }); + }); }); diff --git a/test/interaction/MockPointer.js b/test/interaction/MockPointer.js index 7403d90..80eb76a 100644 --- a/test/interaction/MockPointer.js +++ b/test/interaction/MockPointer.js @@ -30,6 +30,7 @@ this.createdPointerEvent = true; } + this.activeTouches = []; this.stage = stage; this.renderer = new PIXI.CanvasRenderer(width || 100, height || 100); this.renderer.sayHello = () => { /* empty */ }; @@ -73,35 +74,110 @@ } /** + * [createEvent description] + * @param {string} eventType `type` of event + * @param {number} x pointer x position + * @param {number} y pointer y position + * @param {number} [identifier] pointer id for touch events + * @param {boolean} [asPointer] If it should be a PointerEvent from a mouse or touch + * @param {boolean} [onCanvas=true] If the event should be on the canvas (as opposed to a different element) + * @return {Event} Generated MouseEvent, TouchEvent, or PointerEvent + */ + createEvent(eventType, x, y, identifier, asPointer, onCanvas = true) + { + let event; + + if (eventType.startsWith('mouse')) + { + if (asPointer) + { + event = new PointerEvent(eventType.replace('mouse', 'pointer'), { + pointerType: 'mouse', + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + } + else + { + event = new MouseEvent(eventType, { + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + } + if (onCanvas) + { + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + } + else if (eventType.startsWith('touch')) + { + if (asPointer) + { + eventType = eventType.replace('touch', 'pointer').replace('start', 'down').replace('end', 'up'); + event = new PointerEvent(eventType, { + pointerType: 'touch', + pointerId: identifier || 0, + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + else + { + const touch = new Touch({ identifier: identifier || 0, target: this.renderer.view }); + + if (eventType.endsWith('start')) + { + this.activeTouches.push(touch); + } + else if (eventType.endsWith('end') || eventType.endsWith('leave')) + { + for (let i = 0; i < this.activeTouches.length; ++i) + { + if (this.activeTouches[i].identifier === touch.identifier) + { + this.activeTouches.splice(i, 1); + break; + } + } + } + event = new TouchEvent(eventType, { + preventDefault: sinon.stub(), + changedTouches: [touch], + touches: this.activeTouches, + }); + + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + } + else + { + event = new PointerEvent(eventType, { + pointerType: 'pen', + pointerId: identifier || 0, + clientX: x, + clientY: y, + preventDefault: sinon.stub(), + }); + Object.defineProperty(event, 'target', { value: this.renderer.view }); + } + + this.setPosition(x, y); + this.render(); + + return event; + } + + /** * @param {number} x - pointer x position * @param {number} y - pointer y position * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ mousemove(x, y, asPointer) { - let mouseEvent; - - if (asPointer) - { - mouseEvent = new PointerEvent('pointermove', { - pointerType: 'mouse', - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); - } - else - { - mouseEvent = new MouseEvent('mousemove', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); - } - - Object.defineProperty(mouseEvent, 'target', { value: this.renderer.view }); - this.setPosition(x, y); - this.render(); // mouseOverRenderer state should be correct, so mouse position to view rect const rect = new PIXI.Rectangle(0, 0, this.renderer.width, this.renderer.height); @@ -109,74 +185,102 @@ { if (!this.interaction.mouseOverRenderer) { - this.interaction.onPointerOver(new MouseEvent('mouseover', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - })); + this.interaction.onPointerOver(this.createEvent('mouseover', x, y, null, asPointer)); } - this.interaction.onPointerMove(mouseEvent); + this.interaction.onPointerMove(this.createEvent('mousemove', x, y, null, asPointer)); } else { - this.interaction.onPointerOut(new MouseEvent('mouseout', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - })); + this.interaction.onPointerOut(this.createEvent('mouseout', x, y, null, asPointer)); } } /** * @param {number} x - pointer x position * @param {number} y - pointer y position + * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ - click(x, y) + click(x, y, asPointer) { - this.mousedown(x, y); - this.mouseup(x, y); + this.mousedown(x, y, asPointer); + this.mouseup(x, y, asPointer); } /** * @param {number} x - pointer x position * @param {number} y - pointer y position + * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ - mousedown(x, y) + mousedown(x, y, asPointer) { - const mouseEvent = new MouseEvent('mousedown', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); - - Object.defineProperty(mouseEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerDown(mouseEvent); + this.interaction.onPointerDown(this.createEvent('mousedown', x, y, null, asPointer)); } /** * @param {number} x - pointer x position * @param {number} y - pointer y position * @param {boolean} [onCanvas=true] - if the event happend on the Canvas element or not + * @param {boolean} [asPointer] - if it should be a PointerEvent from a mouse */ - mouseup(x, y, onCanvas = true) + mouseup(x, y, onCanvas = true, asPointer = false) { - const mouseEvent = new MouseEvent('mouseup', { - clientX: x, - clientY: y, - preventDefault: sinon.stub(), - }); + this.interaction.onPointerUp(this.createEvent('mouseup', x, y, null, asPointer, onCanvas)); + } - if (onCanvas) - { - Object.defineProperty(mouseEvent, 'target', { value: this.renderer.view }); - } + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + tap(x, y, identifier, asPointer) + { + this.touchstart(x, y, identifier, asPointer); + this.touchend(x, y, identifier, asPointer); + } - this.setPosition(x, y); - this.render(); - this.interaction.onPointerUp(mouseEvent); + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchstart(x, y, identifier, asPointer) + { + this.interaction.onPointerDown(this.createEvent('touchstart', x, y, identifier, asPointer)); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchmove(x, y, identifier, asPointer) + { + this.interaction.onPointerMove(this.createEvent('touchmove', x, y, identifier, asPointer)); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchend(x, y, identifier, asPointer) + { + this.interaction.onPointerUp(this.createEvent('touchend', x, y, identifier, asPointer)); + } + + /** + * @param {number} x - pointer x position + * @param {number} y - pointer y position + * @param {number} [identifier] - pointer id + * @param {boolean} [asPointer] - if it should be a PointerEvent from a touch + */ + touchleave(x, y, identifier, asPointer) + { + this.interaction.onPointerOut(this.createEvent('touchleave', x, y, identifier, asPointer)); } /** @@ -184,10 +288,9 @@ * @param {number} y - pointer y position * @param {number} [identifier] - pointer id */ - tap(x, y, identifier) + pendown(x, y, identifier) { - this.touchstart(x, y, identifier); - this.touchend(x, y, identifier); + this.interaction.onPointerDown(this.createEvent('pointerdown', x, y, identifier, true)); } /** @@ -195,20 +298,9 @@ * @param {number} y - pointer y position * @param {number} [identifier] - pointer id */ - touchstart(x, y, identifier) + penmove(x, y, identifier) { - const touchEvent = new TouchEvent('touchstart', { - preventDefault: sinon.stub(), - changedTouches: [ - new Touch({ identifier: identifier || 0, target: this.renderer.view }), - ], - }); - - Object.defineProperty(touchEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerDown(touchEvent); + this.interaction.onPointerMove(this.createEvent('pointermove', x, y, identifier, true)); } /** @@ -216,41 +308,9 @@ * @param {number} y - pointer y position * @param {number} [identifier] - pointer id */ - touchend(x, y, identifier) + penup(x, y, identifier) { - const touchEvent = new TouchEvent('touchend', { - preventDefault: sinon.stub(), - changedTouches: [ - new Touch({ identifier: identifier || 0, target: this.renderer.view }), - ], - }); - - Object.defineProperty(touchEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerUp(touchEvent); - } - - /** - * @param {number} x - pointer x position - * @param {number} y - pointer y position - * @param {number} [identifier] - pointer id - */ - touchleave(x, y, identifier) - { - const touchEvent = new TouchEvent('touchleave', { - preventDefault: sinon.stub(), - changedTouches: [ - new Touch({ identifier: identifier || 0, target: this.renderer.view }), - ], - }); - - Object.defineProperty(touchEvent, 'target', { value: this.renderer.view }); - - this.setPosition(x, y); - this.render(); - this.interaction.onPointerOut(touchEvent); + this.interaction.onPointerUp(this.createEvent('pointerup', x, y, identifier, true)); } }