Skip to content

Commit

Permalink
Track pointer capture events for working around when pointer capture …
Browse files Browse the repository at this point in the history
…doesn't work (e.g. Chrome outside of the iframe), see #1186

Adding interrupt to SimpleDragHandler pointer listener, see #1186

Adding pointer ID to mouseDown and mouseDownAction, see phetsims/natural-selection#264

Oops fix
  • Loading branch information
jonathanolson committed Apr 21, 2021
1 parent e346d03 commit 1e1bf32
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 11 deletions.
7 changes: 6 additions & 1 deletion js/input/BatchedDOMEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ define( require => {
}
}
else if ( this.type === BatchedDOMEvent.MOUSE_TYPE ) {
callback.call( input, input.pointFromEvent( domEvent ), domEvent );
if ( callback === input.mouseDown ) {
callback.call( input, null, input.pointFromEvent( domEvent ), domEvent );
}
else {
callback.call( input, input.pointFromEvent( domEvent ), domEvent );
}
}
else if ( this.type === BatchedDOMEvent.WHEEL_TYPE ) {
callback.call( input, domEvent );
Expand Down
36 changes: 35 additions & 1 deletion js/input/BrowserEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ define( require => {
'pointermove',
'pointerover',
'pointerout',
'pointercancel'
'pointercancel',
'gotpointercapture',
'lostpointercapture'
],

/**
Expand Down Expand Up @@ -426,6 +428,38 @@ define( require => {
sceneryLog && sceneryLog.OnInput && sceneryLog.pop();
},

/**
* Listener for window's gotpointercapture event.
* @private
*
* @param {Event} domEvent
*/
ongotpointercapture: function ongotpointercapture( domEvent ) {
sceneryLog && sceneryLog.OnInput && sceneryLog.OnInput( 'gotpointercapture' );
sceneryLog && sceneryLog.OnInput && sceneryLog.push();

// NOTE: Will be called without a proper 'this' reference. Do NOT rely on it here.
BrowserEvents.batchWindowEvent( domEvent, BatchedDOMEvent.POINTER_TYPE, 'gotPointerCapture', false );

sceneryLog && sceneryLog.OnInput && sceneryLog.pop();
},

/**
* Listener for window's lostpointercapture event.
* @private
*
* @param {Event} domEvent
*/
onlostpointercapture: function onlostpointercapture( domEvent ) {
sceneryLog && sceneryLog.OnInput && sceneryLog.OnInput( 'lostpointercapture' );
sceneryLog && sceneryLog.OnInput && sceneryLog.push();

// NOTE: Will be called without a proper 'this' reference. Do NOT rely on it here.
BrowserEvents.batchWindowEvent( domEvent, BatchedDOMEvent.POINTER_TYPE, 'lostPointerCapture', false );

sceneryLog && sceneryLog.OnInput && sceneryLog.pop();
},

/**
* Listener for window's MSPointerDown event.
* @private
Expand Down
87 changes: 80 additions & 7 deletions js/input/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ define( require => {
const KeyboardUtils = require( 'SCENERY/accessibility/KeyboardUtils' );
const merge = require( 'PHET_CORE/merge' );
const Mouse = require( 'SCENERY/input/Mouse' );
const NullableIO = require( 'TANDEM/types/NullableIO' );
const NumberIO = require( 'TANDEM/types/NumberIO' );
const Pen = require( 'SCENERY/input/Pen' );
const platform = require( 'PHET_CORE/platform' );
Expand Down Expand Up @@ -277,6 +278,7 @@ define( require => {
this.mouseUpAction = new Action( ( point, event ) => {
if ( !this.mouse ) { this.initMouse(); }
const pointChanged = this.mouse.up( point, event );
this.mouse.id = null;
this.upEvent( this.mouse, event, pointChanged );
}, {
phetioPlayback: true,
Expand All @@ -290,14 +292,16 @@ define( require => {
} );

// @private {Action} - Emits to the PhET-iO data stream.
this.mouseDownAction = new Action( ( point, event ) => {
this.mouseDownAction = new Action( ( id, point, event ) => {
if ( !this.mouse ) { this.initMouse(); }
this.mouse.id = id;
const pointChanged = this.mouse.down( point, event );
this.downEvent( this.mouse, event, pointChanged );
}, {
phetioPlayback: true,
tandem: options.tandem.createTandem( 'mouseDownAction' ),
parameters: [
{ name: 'id', phetioType: NullableIO( NumberIO ) },
{ name: 'point', phetioType: Vector2IO },
{ name: 'event', phetioType: EventIO }
],
Expand Down Expand Up @@ -530,6 +534,44 @@ define( require => {
phetioDocumentation: 'Emits when a pen is canceled'
} );

// @private {Action} - Emits to the PhET-iO data stream.
this.gotPointerCaptureAction = new Action( ( id, event ) => {
const pointer = this.findPointerById( id );

if ( pointer ) {
pointer.onGotPointerCapture();
}
}, {
phetioPlayback: true,
tandem: options.tandem.createTandem( 'gotPointerCaptureAction' ),
parameters: [
{ name: 'id', phetioType: NumberIO },
{ name: 'event', phetioType: DOMEventIO }
],
phetioEventType: EventType.USER,
phetioDocumentation: 'Emits when a pointer is captured (normally at the start of an interaction)',
phetioHighFrequency: true
} );

// @private {Action} - Emits to the PhET-iO data stream.
this.lostPointerCaptureAction = new Action( ( id, event ) => {
const pointer = this.findPointerById( id );

if ( pointer ) {
pointer.onLostPointerCapture();
}
}, {
phetioPlayback: true,
tandem: options.tandem.createTandem( 'lostPointerCaptureAction' ),
parameters: [
{ name: 'id', phetioType: NumberIO },
{ name: 'event', phetioType: DOMEventIO }
],
phetioEventType: EventType.USER,
phetioDocumentation: 'Emits when a pointer loses its capture (normally at the end of an interaction)',
phetioHighFrequency: true
} );

// wire up accessibility listeners on the display's root accessible DOM element.
if ( this.display._accessible ) {

Expand Down Expand Up @@ -1040,13 +1082,14 @@ define( require => {
* NOTE: This may also be called from the pointer event handler (pointerDown) or from things like fuzzing or
* playback. The event may be "faked" for certain purposes.
*
* @param {number|null} id
* @param {Vector2} point
* @param {Event} event
*/
mouseDown( point, event ) {
sceneryLog && sceneryLog.Input && sceneryLog.Input( 'mouseDown(' + Input.debugText( point, event ) + ');' );
mouseDown( id, point, event ) {
sceneryLog && sceneryLog.Input && sceneryLog.Input( 'mouseDown(' + id + ', ' + Input.debugText( point, event ) + ');' );
sceneryLog && sceneryLog.Input && sceneryLog.push();
this.mouseDownAction.execute( point, event );
this.mouseDownAction.execute( id, point, event );
sceneryLog && sceneryLog.Input && sceneryLog.pop();
}

Expand Down Expand Up @@ -1291,8 +1334,7 @@ define( require => {
switch( type ) {
case 'mouse':
// The actual event afterwards
this.mouseDown( point, event );
this.mouse.id = id;
this.mouseDown( id, point, event );
break;
case 'touch':
this.touchStart( id, point, event );
Expand Down Expand Up @@ -1321,7 +1363,6 @@ define( require => {
switch( type ) {
case 'mouse':
this.mouseUp( point, event );
this.mouse.id = null;
break;
case 'touch':
this.touchEnd( id, point, event );
Expand Down Expand Up @@ -1394,6 +1435,38 @@ define( require => {
}
}

/**
* Handles a gotpointercapture event, forwarding it to the proper logical event.
* @public (scenery-internal)
*
* @param {number} id
* @param {string} type
* @param {Vector2} point
* @param {Event} event
*/
gotPointerCapture( id, type, point, event ) {
sceneryLog && sceneryLog.Input && sceneryLog.Input( `gotPointerCapture('${id}',${Input.debugText( null, event )});` );
sceneryLog && sceneryLog.Input && sceneryLog.push();
this.gotPointerCaptureAction.execute( id, event );
sceneryLog && sceneryLog.Input && sceneryLog.pop();
}

/**
* Handles a lostpointercapture event, forwarding it to the proper logical event.
* @public (scenery-internal)
*
* @param {number} id
* @param {string} type
* @param {Vector2} point
* @param {Event} event
*/
lostPointerCapture( id, type, point, event ) {
sceneryLog && sceneryLog.Input && sceneryLog.Input( `lostPointerCapture('${id}',${Input.debugText( null, event )});` );
sceneryLog && sceneryLog.Input && sceneryLog.push();
this.lostPointerCaptureAction.execute( id, event );
sceneryLog && sceneryLog.Input && sceneryLog.pop();
}

/**
* Handles a pointerover event, forwarding it to the proper logical event.
* @public (scenery-internal)
Expand Down
2 changes: 1 addition & 1 deletion js/input/InputFuzzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ define( require => {
this.isMouseDown = false;
}
else {
this.display._input.mouseDown( this.mousePosition, domEvent );
this.display._input.mouseDown( null, this.mousePosition, domEvent );
this.isMouseDown = true;
}
},
Expand Down
27 changes: 27 additions & 0 deletions js/input/Pointer.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ define( require => {
// certain behavior for the life of the listener. Other listeners can observe the Intent on the Pointer and
// react accordingly
this._intent = null;

// @private {boolean}
this._pointerCaptured = false;
}

scenery.register( 'Pointer', Pointer );
Expand Down Expand Up @@ -315,6 +318,30 @@ define( require => {
},
get intent() { return this.getIntent(); },

/*
* This is called when a capture starts on this pointer. We request it on pointerstart, and if received, we should
* generally receive events outside the window.
* @public
*/
onGotPointerCapture() {
this._pointerCaptured = true;
},

/**
* This is called when a capture ends on this pointer. This happens normally when the user releases the pointer above
* the sim or outside, but also in cases where we have NOT received an up/end.
* @public
*
* See https://github.com/phetsims/scenery/issues/1186 for more information. We'll want to interrupt the pointer
* on this case regardless,
*/
onLostPointerCapture() {
if ( this._pointerCaptured ) {
this.interruptAll();
}
this._pointerCaptured = false;
},

/**
* Releases references so it can be garbage collected.
* @public
Expand Down
5 changes: 5 additions & 0 deletions js/input/SimpleDragHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ define( require => {
// mouse/touch move
move: function( event ) {
self.dragAction.execute( event.pointer.point, event );
},

// pointer interruption
interrupt: () => {
self.interrupt();
}
};
PhetioObject.call( this, options );
Expand Down
2 changes: 1 addition & 1 deletion js/listeners/ListenerTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ define( require => {
null );

display._input.validatePointers();
display._input.mouseDown( new Vector2( x, y ), domEvent );
display._input.mouseDown( null, new Vector2( x, y ), domEvent );
},

/**
Expand Down

0 comments on commit 1e1bf32

Please sign in to comment.