phetsims / scenery

Scenery is an HTML5 scene graph.
MIT License
53 stars 12 forks source link

PressListener: overPointers incorrect when no `exit` event #1021

Open zepumph opened 4 years ago

zepumph commented 4 years ago

From working on https://github.com/phetsims/projectile-motion/issues/209. I have a background node that goes behind a keypad, and listens for a press so that it can hide the whole keypad layer (including the aforementioned barrier rectangle). So to review the interaction:

The issue is that when trying to use a PressListener for this (specifically a FireListener) there is a buggy edge case where the background rectangle closes and the listener does not receive an exit event. Therefore the mouse (or pointer) is still part of that ObservableArray when I try to reopen the keypad layer and I get an enter event again.

A quick way to reproduce is to . . .

patch ```diff Index: js/phet-io/projectile-motion-phet-io-elements-baseline.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- js/phet-io/projectile-motion-phet-io-elements-baseline.js (revision 9fc36a1b556b6808be4144e2ecfb8dea3aaa03ad) +++ js/phet-io/projectile-motion-phet-io-elements-baseline.js (date 1574467631809) @@ -1783,7 +1783,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.dragScreen.view.normalMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -1793,7 +1793,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.dragScreen.view.normalMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -4630,7 +4630,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.dragScreen.view.slowMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -4640,7 +4640,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.dragScreen.view.slowMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -5449,7 +5449,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.dragScreen.view.vectorsPanel.vectorsDisplayRadioButtonGroup.components.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -5459,7 +5459,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.dragScreen.view.vectorsPanel.vectorsDisplayRadioButtonGroup.components.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -5592,7 +5592,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.dragScreen.view.vectorsPanel.vectorsDisplayRadioButtonGroup.total.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -5602,7 +5602,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.dragScreen.view.vectorsPanel.vectorsDisplayRadioButtonGroup.total.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -6242,7 +6242,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.general.barrierRectangle.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -6252,7 +6252,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.general.barrierRectangle.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -7750,7 +7750,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.general.navigationBar.phetButton.phetMenu.aboutMenuItem.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -7760,7 +7760,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.general.navigationBar.phetButton.phetMenu.aboutMenuItem.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -7841,7 +7841,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.general.navigationBar.phetButton.phetMenu.screenshotMenuItem.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -7851,7 +7851,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.general.navigationBar.phetButton.phetMenu.screenshotMenuItem.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -8491,7 +8491,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.dragScreenLargeButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -8501,7 +8501,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.dragScreenLargeButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -8647,7 +8647,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.dragScreenSmallButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -8657,7 +8657,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.dragScreenSmallButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -8803,7 +8803,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.introScreenLargeButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -8813,7 +8813,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.introScreenLargeButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -8959,7 +8959,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.introScreenSmallButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -8969,7 +8969,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.introScreenSmallButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -9115,7 +9115,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.labScreenLargeButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -9125,7 +9125,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.labScreenLargeButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -9271,7 +9271,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.labScreenSmallButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -9281,7 +9281,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.labScreenSmallButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -9518,7 +9518,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.vectorsScreenLargeButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -9528,7 +9528,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.vectorsScreenLargeButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -9674,7 +9674,7 @@ "phetioTypeName": "NodeIO" }, "projectileMotion.homeScreen.view.vectorsScreenSmallButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -9684,7 +9684,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.homeScreen.view.vectorsScreenSmallButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -11585,7 +11585,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.introScreen.view.normalMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -11595,7 +11595,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.introScreen.view.normalMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -13067,7 +13067,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.introScreen.view.slowMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -13077,7 +13077,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.introScreen.view.slowMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -16472,6 +16472,45 @@ "phetioStudioControl": true, "phetioTypeName": "NodeIO" }, + "projectileMotion.labScreen.view.keypadLayer.clickOutsideFireListener.firedEmitter": { + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", + "phetioDynamicElement": false, + "phetioDynamicElementArchetype": false, + "phetioEventType": "USER", + "phetioFeatured": false, + "phetioHighFrequency": false, + "phetioPlayback": false, + "phetioReadOnly": false, + "phetioState": false, + "phetioStudioControl": true, + "phetioTypeName": "EmitterIO>" + }, + "projectileMotion.labScreen.view.keypadLayer.clickOutsideFireListener.pressAction": { + "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", + "phetioDynamicElement": false, + "phetioDynamicElementArchetype": false, + "phetioEventType": "USER", + "phetioFeatured": false, + "phetioHighFrequency": false, + "phetioPlayback": false, + "phetioReadOnly": false, + "phetioState": false, + "phetioStudioControl": true, + "phetioTypeName": "ActionIO" + }, + "projectileMotion.labScreen.view.keypadLayer.clickOutsideFireListener.releaseAction": { + "phetioDocumentation": "Executes whenever a release occurs. The arguments are:
  1. event: NullableIO
", + "phetioDynamicElement": false, + "phetioDynamicElementArchetype": false, + "phetioEventType": "USER", + "phetioFeatured": false, + "phetioHighFrequency": false, + "phetioPlayback": false, + "phetioReadOnly": false, + "phetioState": false, + "phetioStudioControl": true, + "phetioTypeName": "ActionIO>" + }, "projectileMotion.labScreen.view.keypadLayer.enterButton": { "phetioDocumentation": "", "phetioDynamicElement": false, @@ -18150,7 +18189,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.labScreen.view.normalMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -18160,7 +18199,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.labScreen.view.normalMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -23701,7 +23740,7 @@ "phetioTypeName": "NumberDisplayIO" }, "projectileMotion.labScreen.view.projectilePanel.customControl.altitudeControl.numberDisplay.fireListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -23711,7 +23750,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.labScreen.view.projectilePanel.customControl.altitudeControl.numberDisplay.fireListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -24078,7 +24117,7 @@ "phetioTypeName": "NumberDisplayIO" }, "projectileMotion.labScreen.view.projectilePanel.customControl.diameterControl.numberDisplay.fireListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -24088,7 +24127,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.labScreen.view.projectilePanel.customControl.diameterControl.numberDisplay.fireListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -24455,7 +24494,7 @@ "phetioTypeName": "NumberDisplayIO" }, "projectileMotion.labScreen.view.projectilePanel.customControl.dragCoefficientControl.numberDisplay.fireListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -24465,7 +24504,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.labScreen.view.projectilePanel.customControl.dragCoefficientControl.numberDisplay.fireListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -24832,7 +24871,7 @@ "phetioTypeName": "NumberDisplayIO" }, "projectileMotion.labScreen.view.projectilePanel.customControl.gravityControl.numberDisplay.fireListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -24842,7 +24881,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.labScreen.view.projectilePanel.customControl.gravityControl.numberDisplay.fireListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -25209,7 +25248,7 @@ "phetioTypeName": "NumberDisplayIO" }, "projectileMotion.labScreen.view.projectilePanel.customControl.massControl.numberDisplay.fireListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -25219,7 +25258,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.labScreen.view.projectilePanel.customControl.massControl.numberDisplay.fireListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -35609,7 +35648,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.labScreen.view.slowMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -35619,7 +35658,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.labScreen.view.slowMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -38456,7 +38495,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.vectorsScreen.view.normalMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -38466,7 +38505,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.vectorsScreen.view.normalMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -40094,7 +40133,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.vectorsScreen.view.slowMotionRadioButton.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -40104,7 +40143,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.vectorsScreen.view.slowMotionRadioButton.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -41017,7 +41056,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.vectorsScreen.view.vectorsPanel.totalOrComponentsRadioButtonGroup.components.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -41027,7 +41066,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.vectorsScreen.view.vectorsPanel.totalOrComponentsRadioButtonGroup.components.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", @@ -41160,7 +41199,7 @@ "phetioTypeName": "PropertyIO" }, "projectileMotion.vectorsScreen.view.vectorsPanel.totalOrComponentsRadioButtonGroup.total.inputListener.firedEmitter": { - "phetioDocumentation": "A function that executes. No arguments.", + "phetioDocumentation": "A function that executes. The arguments are:
  1. event: NullableIO
", "phetioDynamicElement": false, "phetioDynamicElementArchetype": false, "phetioEventType": "USER", @@ -41170,7 +41209,7 @@ "phetioReadOnly": false, "phetioState": false, "phetioStudioControl": true, - "phetioTypeName": "EmitterIO<>" + "phetioTypeName": "EmitterIO>" }, "projectileMotion.vectorsScreen.view.vectorsPanel.totalOrComponentsRadioButtonGroup.total.inputListener.pressAction": { "phetioDocumentation": "Executes whenever a press occurs. The first argument when executing can be used to convey info about the Event. The arguments are:
  1. event: EventIO
", Index: js/lab/view/KeypadLayer.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- js/lab/view/KeypadLayer.js (revision 9fc36a1b556b6808be4144e2ecfb8dea3aaa03ad) +++ js/lab/view/KeypadLayer.js (date 1574464747734) @@ -10,7 +10,6 @@ 'use strict'; // modules - const DownUpListener = require( 'SCENERY/input/DownUpListener' ); const inherit = require( 'PHET_CORE/inherit' ); const Keypad = require( 'SCENERY_PHET/keypad/Keypad' ); const merge = require( 'PHET_CORE/merge' ); @@ -18,6 +17,7 @@ const Panel = require( 'SUN/Panel' ); const PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' ); const Plane = require( 'SCENERY/nodes/Plane' ); + const FireListener = require( 'SCENERY/listeners/FireListener' ); const projectileMotion = require( 'PROJECTILE_MOTION/projectileMotion' ); const ProjectileMotionConstants = require( 'PROJECTILE_MOTION/common/ProjectileMotionConstants' ); const Rectangle = require( 'SCENERY/nodes/Rectangle' ); @@ -61,13 +61,16 @@ Plane.call( this, options ); - // @private clicking outside the keypad cancels the edit - this.clickOutsideListener = new DownUpListener( { - down: function( event ) { + // @private - clicking outside the keypad cancels the edit + this.clickOutsideFireListener = new FireListener( { + fire: function( event ) { if ( event.trail.lastNode() === self ) { self.cancelEdit(); } - } + }, + fireOnDown: true, + tandem: options.tandem.createTandem( 'clickOutsideFireListener' ), + phetioDocumentation: 'listener responsible for hiding the KeypadLayer when space outside of the keypad is pressed.' } ); // @private these will be set when the client calls beginEdit @@ -207,7 +210,7 @@ this.visible = true; // keypadLayer lasts for the lifetime of the sim, so listeners don't need to be disposed - this.addInputListener( this.clickOutsideListener ); + this.addInputListener( this.clickOutsideFireListener ); // execute client-specific hook options.onBeginEdit && options.onBeginEdit(); @@ -224,7 +227,7 @@ // hide the keypad this.visible = false; - this.removeInputListener( this.clickOutsideListener ); + this.removeInputListener( this.clickOutsideFireListener ); // execute client-specific hook this.onEndEdit && this.onEndEdit(); Index: js/phet-io/projectile-motion-phet-io-types.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- js/phet-io/projectile-motion-phet-io-types.js (revision 9fc36a1b556b6808be4144e2ecfb8dea3aaa03ad) +++ js/phet-io/projectile-motion-phet-io-types.js (date 1574467631846) @@ -389,6 +389,35 @@ "supertype": "ActionIO<>", "typeName": "EmitterIO<>" }, + "EmitterIO>": { + "documentation": "Emits when an event occurs and calls added listeners.", + "events": [ + "emitted" + ], + "methodOrder": [], + "methods": { + "addListener": { + "documentation": "Adds a listener which will be called when the emitter emits.", + "parameterTypes": [ + "FunctionIO(NullableIO)=>VoidIO" + ], + "returnType": "VoidIO" + }, + "emit": { + "documentation": "Emits a single event to all listeners.", + "invocableForReadOnlyElements": false, + "parameterTypes": [ + "NullableIO" + ], + "returnType": "VoidIO" + } + }, + "parameterTypes": [ + "NullableIO" + ], + "supertype": "ActionIO>", + "typeName": "EmitterIO>" + }, "EnumerationIO(SLOW|NORMAL)": { "documentation": "Possible values: SLOW,NORMAL.", "events": [], @@ -504,6 +533,18 @@ "supertype": "ObjectIO", "typeName": "FunctionIO(NullableIO,NullableIO>)=>VoidIO" }, + "FunctionIO(NullableIO)=>VoidIO": { + "documentation": "Wrapper for the built-in JS function type.
Arguments: NullableIO
Return Type: VoidIO", + "events": [], + "methodOrder": [], + "methods": {}, + "parameterTypes": [ + "NullableIO", + "VoidIO" + ], + "supertype": "ObjectIO", + "typeName": "FunctionIO(NullableIO)=>VoidIO" + }, "FunctionIO(NullableIO,NullableIO>)=>VoidIO": { "documentation": "Wrapper for the built-in JS function type.
Arguments: NullableIO, NullableIO>
Return Type: VoidIO", "events": [], ```

@jonathanolson do you think there is an easy way detect this case and clear the overPointers list?

For now I will not go ahead with the conversion to FireListener, but I think this blocks phet-io publication of Projectile Motion because DownUpListener has not had much in the way of phet-io design, especially compared to the PressListener hierarchy.

@ariel-phet please prioritize.

zepumph commented 4 years ago

To support this same work, above I added the event to be passed through in the fire callback. Please let me know if you feel like that wasn't a good change.

jonathanolson commented 4 years ago

So the "over" pointers is something tricky in general, and there may be better ways for handling it. But in this case, Just a addInputListener( { down: ... } ) is probably better, and would sidestep the assertion issue in this case.

jonathanolson commented 4 years ago

Additionally, the general case is somewhat difficult performance-wise. To ensure full correctness, scenery would need to check what a pointer was over on every scene-graph change (and then immediately sending events). That isn't viable performance-wise.

zepumph commented 4 years ago

I think I understand, but don't you feel like this is a pretty basic use case that we should have some scenery listener for. I guess I feel like we can do "better" than just a down listener. Perhaps I am wrong though. For example we need phet-io support for that click to end up in the data stream (at least that is what we have been doing for other listeners.

jonathanolson commented 4 years ago

For example we need phet-io support for that click to end up in the data stream (at least that is what we have been doing for other listeners.

Wouldn't the Scenery input event show up in the data stream? And then the result of closing whatever was closed?

I guess I feel like we can do "better" than just a down listener.

What would be the "better" option?

jonathanolson commented 4 years ago

I've also added a clearOverPointers() method above that will help resolve these issues (where it can be called once the node is removed and expected to be removed for more than a frame).

zepumph commented 4 years ago

I used that and it worked like a charm! Can you explain to me why it didn't work to call "interrupt" in this case? Shouldn't that reset this state (over pointers list) for us so that we don't need to have another public function (aka a "gotcha" to remember).

jonathanolson commented 4 years ago

Interrupting it doesn't change whether pointers are over it, and is likely to be called WHILE pointers are over it, so it could almost cause the opposite issue (missed enter).

zepumph commented 4 years ago

Ahh yes, that makes a lot of sense to me. Alright I'm ready to close. Thanks @jonathanolson, I really really appreciate the help.

pixelzoom commented 4 years ago

Reopening. This is still a problem, and I ran into it in https://github.com/phetsims/scenery/issues/1021, where @jonathanolson said in https://github.com/phetsims/equality-explorer/issues/159#issuecomment-689762790:

I believe this is probably related to https://github.com/phetsims/scenery/issues/1021, is there a point where a node with an input listener is removed? pressListener.clearOverPointers() is the current workaround.

If we're converting to new input listeners, then shouldn't this be fixed? Is the "clearOverPointers" workaround really an acceptable solution? It sure seems like we should be able to remove an input listener without things breaking.

pixelzoom commented 4 years ago

Labeling with high priority because I'm in the middle of converting my sims to new listeners (e.g. phetsims/equality-explorer#159) and we're about to convert common-code (https://github.com/phetsims/scenery/issues/1078).

zepumph commented 3 years ago

Sorry I lost track of this issue. Over to @jonathanolson to recommend a more robust solution than clearOverPointers.

jbphet commented 3 years ago

I ran into this in phetsims/projectile-motion#255, and I agree that this seems pretty fragile. A more robust solution would be nice.

jonathanolson commented 3 years ago

I'm going to be brainstorming a potentially better approach for tracking "over" pointers to nodes... I agree the whole system to this sounds fragile.

Many systems might include a performance hit if implemented (but given our validatePointers overhead, maybe some would be better).

zepumph commented 3 years ago

https://github.com/phetsims/scenery/issues/1116#issuecomment-788312270 brought up two potential discussions for solving this further:

  1. Create a fully general system for over pointers that is outside of input.js and baked into Node. @jonathanolson is worried about performance (since it will be on every Node), and also about DAG support. Would this be opt in, or for every node?
  2. Factoring out over pointer logic from PressListener for use in other spots.

@jonathanolson to write some more notes in here.

zepumph commented 3 years ago

@jonathanolson, has our work in branchChangeEvents helped to fix this problem? Now we get exit events when things are turned inputEnabled:false (so also enabled:false). Should branchChangeEvents support firing exit events in other cases too?

jonathanolson commented 1 year ago

has our work in branchChangeEvents helped to fix this problem? Now we get exit events when things are turned inputEnabled:false (so also enabled:false). Should branchChangeEvents support firing exit events in other cases too?

I think it minorly helps mitigate.

My understanding is that it's easiest to trigger this when you removeChild a Node, and we're not firing branchChangeEvents for that. It might be a performance issue to try to detect and fire that.

Removing review label, since it doesn't seem like it's a review of existing code (still leaving assigned).