Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ComboBoxListBox: use a standard scenery listener #462

Open
pixelzoom opened this issue Jan 22, 2019 · 7 comments
Open

ComboBoxListBox: use a standard scenery listener #462

pixelzoom opened this issue Jan 22, 2019 · 7 comments

Comments

@pixelzoom
Copy link
Contributor

Related to #445, in CombBoxListBox.

Sometime in the future, investigate replacing fireEmitter and selectionListener with a standard scenery listener (PressListener, FireListener,...)

@zepumph
Copy link
Member

zepumph commented Jan 28, 2019

This should be fixed generally by #463

@zepumph zepumph self-assigned this Oct 15, 2019
@zepumph
Copy link
Member

zepumph commented Oct 15, 2019

I looked into doing this and felt like I had got close, but found it problematic because FireListener attaches to the pointer, and such doesn't have event.currentTarget. Master is currently adding the listener to the Node, so we do get event.currentTarget.

Here is the patch I was thinking of

Index: js/ComboBoxListBox.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- js/ComboBoxListBox.js	(revision 0de96f862051f40be4cccb687f7a4bdc8ae42b45)
+++ js/ComboBoxListBox.js	(date 1571103073014)
@@ -9,10 +9,8 @@
   'use strict';
 
   // modules
-  const Action = require( 'AXON/Action' );
   const ComboBoxListItemNode = require( 'SUN/ComboBoxListItemNode' );
-  const Event = require( 'SCENERY/input/Event' );
-  const EventType = require( 'TANDEM/EventType' );
+  const FireListener = require( 'SCENERY/listeners/FireListener' );
   const KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
   const Panel = require( 'SUN/Panel' );
   const sun = require( 'SUN/sun' );
@@ -56,52 +54,27 @@
       assert && assert( options.xMargin > 0 && options.yMargin > 0,
         'margins must be > 0, xMargin=' + options.xMargin + ', yMargin=' + options.yMargin );
 
-      //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
-      // Pops down the list box and sets the property.value to match the chosen item.
-      const fireAction = new Action( event => {
+      const fireListener = new FireListener( {
+        fire: event => {
 
-        const listItemNode = event.currentTarget;
-        assert && assert( listItemNode instanceof ComboBoxListItemNode, 'expected a ComboBoxListItemNode' );
+          const listItemNode = event.currentTarget;
+          assert && assert( listItemNode instanceof ComboBoxListItemNode, 'expected a ComboBoxListItemNode' );
 
-        // hide the list
-        hideListBoxCallback();
+          // hide the list
+          hideListBoxCallback();
 
-        // prevent nodes (eg, controls) behind the list from receiving the event
-        event.abort();
+          // prevent nodes (eg, controls) behind the list from receiving the event
+          event.abort();
 
-        // set value based on which item was chosen in the list box
-        property.value = listItemNode.item.value;
-      }, {
-        parameters: [ { phetioPrivate: true, valueType: Event } ],
-
-        // phet-io
-        tandem: tandem.createTandem( 'fireAction' ),
-        phetioEventType: EventType.USER
-      } );
-
-      //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
-      // Handles selection from the list box.
-      const selectionListener = {
-
-        up( event ) {
-          fireAction.execute( event );
-        },
+          // set value based on which item was chosen in the list box
+          property.value = listItemNode.item.value;
 
-        // Handle keyup on each item in the list box, for a11y.
-        //TODO sun#447, scenery#931 we're using keyup because keydown fires continuously
-        keyup: event => {
-          if ( KeyboardUtil.KEY_ENTER === event.domEvent.keyCode || KeyboardUtil.KEY_SPACE === event.domEvent.keyCode ) {
-            fireAction.execute( event );
+          if ( event.isA11y() ) {
             focusButtonCallback();
           }
         },
-
-        // handle activation from an assistive device that may not use a keyboard (such as mobile VoiceOver)
-        click: event => {
-          fireAction.execute( event );
-          focusButtonCallback();
-        }
-      };
+        tandem: tandem.createTandem( 'fireListener' )
+      } );
 
       // Compute max item dimensions
       const maxItemWidth = _.maxBy( items, item => item.node.width ).node.width;
@@ -111,8 +84,9 @@
       const highlightWidth = maxItemWidth + options.xMargin;
       const highlightHeight = maxItemHeight + options.yMargin;
 
+      const listItemNodes = []; // {ComboBoxListItemNode[]}
+
       // Create a node for each item in the list, and attach a listener.
-      const listItemNodes = []; // {ComboBoxListItemNode[]}
       items.forEach( ( item, index ) => {
 
         // Create the list item node
@@ -129,7 +103,7 @@
         } );
         listItemNodes.push( listItemNode );
 
-        listItemNode.addInputListener( selectionListener );
+        listItemNode.addInputListener( fireListener );
       } );
 
       const content = new VBox( {
Index: js/listeners/FireListener.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- js/listeners/FireListener.js	(revision 2a4b2a83b2b0ce578e10c5b7a19c8d4c308d45bd)
+++ js/listeners/FireListener.js	(date 1571100448000)
@@ -12,6 +12,7 @@
   'use strict';
 
   const Emitter = require( 'AXON/Emitter' );
+  const Event = require( 'SCENERY/input/Event' );
   const EventType = require( 'TANDEM/EventType' );
   const inherit = require( 'PHET_CORE/inherit' );
   const PressListener = require( 'SCENERY/listeners/PressListener' );
@@ -49,7 +50,8 @@
     // @private {Emitter}
     this.firedEmitter = new Emitter( {
       tandem: options.tandem.createTandem( 'firedEmitter' ),
-      phetioEventType: EventType.USER
+      phetioEventType: EventType.USER,
+      parameters: [ { phetioPrivate: true, valueType: Event } ]
     } );
     this.firedEmitter.addListener( options.fire );
   }
@@ -69,7 +71,7 @@
       sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'FireListener fire' );
       sceneryLog && sceneryLog.InputListener && sceneryLog.push();
 
-      this.firedEmitter.emit();
+      this.firedEmitter.emit( event );
 
       sceneryLog && sceneryLog.InputListener && sceneryLog.pop();
     },
@@ -118,7 +120,7 @@
       PressListener.prototype.release.call( this, event, function() {
         // Notify after the rest of release is called in order to prevent it from triggering interrupt().
         if ( !self._fireOnDown && self.isHoveringProperty.value && !self.interrupted ) {
-          self.fire();
+          self.fire( event );
         }
         callback && callback();
       } );

@jonathanolson is there a way to get currentTarget from a PressListener or subtype?

@jessegreenberg
Copy link
Contributor

I encountered this in #556, using a Scenery listener that attaches itself to the Pointer will resolve that issue. That is important so that the Pointer doesn't snag other things if it moves off of the ComboBox.

@jessegreenberg
Copy link
Contributor

jessegreenberg commented Jan 13, 2020

I took a look and I also don't see a way to get the currentTarget (partly because we want FireListener to fire on up). But if we cannot have a currentTarget, we could have one FireListener per ComboBoxListItemNode, like this

diff --git a/js/ComboBoxListBox.js b/js/ComboBoxListBox.js
index acd18d41..f5970a1c 100644
--- a/js/ComboBoxListBox.js
+++ b/js/ComboBoxListBox.js
@@ -9,13 +9,11 @@ define( require => {
   'use strict';
 
   // modules
-  const Action = require( 'AXON/Action' );
   const ComboBoxListItemNode = require( 'SUN/ComboBoxListItemNode' );
-  const EventType = require( 'TANDEM/EventType' );
   const KeyboardUtils = require( 'SCENERY/accessibility/KeyboardUtils' );
   const merge = require( 'PHET_CORE/merge' );
   const Panel = require( 'SUN/Panel' );
-  const SceneryEvent = require( 'SCENERY/input/SceneryEvent' );
+  const FireListener = require( 'SCENERY/listeners/FireListener' );
   const sun = require( 'SUN/sun' );
   const Tandem = require( 'TANDEM/Tandem' );
   const VBox = require( 'SCENERY/nodes/VBox' );
@@ -57,53 +55,6 @@ define( require => {
       assert && assert( options.xMargin > 0 && options.yMargin > 0,
         'margins must be > 0, xMargin=' + options.xMargin + ', yMargin=' + options.yMargin );
 
-      //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
-      // Pops down the list box and sets the property.value to match the chosen item.
-      const fireAction = new Action( event => {
-
-        const listItemNode = event.currentTarget;
-        assert && assert( listItemNode instanceof ComboBoxListItemNode, 'expected a ComboBoxListItemNode' );
-
-        // hide the list
-        hideListBoxCallback();
-
-        // prevent nodes (eg, controls) behind the list from receiving the event
-        event.abort();
-
-        // set value based on which item was chosen in the list box
-        property.value = listItemNode.item.value;
-      }, {
-        parameters: [ { phetioPrivate: true, valueType: SceneryEvent } ],
-
-        // phet-io
-        tandem: tandem.createTandem( 'fireAction' ),
-        phetioEventType: EventType.USER
-      } );
-
-      //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
-      // Handles selection from the list box.
-      const selectionListener = {
-
-        up( event ) {
-          fireAction.execute( event );
-        },
-
-        // Handle keyup on each item in the list box, for a11y.
-        //TODO sun#447, scenery#931 we're using keyup because keydown fires continuously
-        keyup: event => {
-          if ( KeyboardUtils.KEY_ENTER === event.domEvent.keyCode || KeyboardUtils.KEY_SPACE === event.domEvent.keyCode ) {
-            fireAction.execute( event );
-            focusButtonCallback();
-          }
-        },
-
-        // handle activation from an assistive device that may not use a keyboard (such as mobile VoiceOver)
-        click: event => {
-          fireAction.execute( event );
-          focusButtonCallback();
-        }
-      };
-
       // Compute max item dimensions
       const maxItemWidth = _.maxBy( items, item => item.node.width ).node.width;
       const maxItemHeight = _.maxBy( items, item => item.node.height ).node.height;
@@ -130,7 +81,28 @@ define( require => {
         } );
         listItemNodes.push( listItemNode );
 
-        listItemNode.addInputListener( selectionListener );
+        //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
+        // Pops down the list box and sets the property.value to match the chosen item.
+        // const fireAction = new Action( event => {
+        const fireListener = new FireListener( {
+          fire: event => {
+
+            // hide the list
+            hideListBoxCallback();
+
+            // prevent nodes (eg, controls) behind the list from receiving the event
+            event.abort();
+
+            // set value based on which item was chosen in the list box
+            property.value = item.value;
+
+            if ( event.isA11y() ) {
+              focusButtonCallback();
+            }
+          }
+        } );
+
+        listItemNode.addInputListener( fireListener );
       } );
 
       const content = new VBox( {

@zepumph what do you think?

@jessegreenberg
Copy link
Contributor

@zepumph and I tried to do this today but couldn't finish because FireListener/PressListener responds to click events, but we need the ComboBoxListItemNode to fire on keyup events. Here is the patch we tried:

Index: js/ComboBoxListBox.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- js/ComboBoxListBox.js	(revision 57ca44eb9624ac1c547b3b2ecc754531824bf648)
+++ js/ComboBoxListBox.js	(date 1611094682271)
@@ -10,6 +10,7 @@
 import merge from '../../phet-core/js/merge.js';
 import KeyboardUtils from '../../scenery/js/accessibility/KeyboardUtils.js';
 import SceneryEvent from '../../scenery/js/input/SceneryEvent.js';
+import FireListener from '../../scenery/js/listeners/FireListener.js';
 import VBox from '../../scenery/js/nodes/VBox.js';
 import multiSelectionSoundPlayerFactory from '../../tambo/js/multiSelectionSoundPlayerFactory.js';
 import generalCloseSoundPlayer from '../../tambo/js/shared-sound-players/generalCloseSoundPlayer.js';
@@ -61,52 +62,35 @@
     assert && assert( options.xMargin > 0 && options.yMargin > 0,
       'margins must be > 0, xMargin=' + options.xMargin + ', yMargin=' + options.yMargin );
 
-    //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
     // Pops down the list box and sets the property.value to match the chosen item.
-    const fireAction = new Action( event => {
+    const fireListener = new FireListener( {
+      fire: event => {
 
-      const listItemNode = event.currentTarget;
-      assert && assert( listItemNode instanceof ComboBoxListItemNode, 'expected a ComboBoxListItemNode' );
+        let listItemNode = null;
+        for ( let i = event.trail.nodes.length - 1; i >= 0; i-- ) {
+          const trailNode = event.trail.nodes[ i ];
+          if ( trailNode instanceof ComboBoxListItemNode ) {
+            listItemNode = trailNode;
+            break;
+          }
+        }
+        assert && assert( listItemNode, 'Expected event to contain a ComboBoxListItemNode' );
 
-      // set value based on which item was chosen in the list box
-      property.value = listItemNode.item.value;
+        // set value based on which item was chosen in the list box
+        property.value = listItemNode.item.value;
 
-      // hide the list
-      hideListBoxCallback();
+        // hide the list
+        hideListBoxCallback();
 
-      // prevent nodes (eg, controls) behind the list from receiving the event
-      event.abort();
-    }, {
-      parameters: [ { phetioPrivate: true, valueType: SceneryEvent } ],
-
-      // phet-io
-      tandem: tandem.createTandem( 'fireAction' ),
-      phetioEventType: EventType.USER
-    } );
+        // prevent nodes (eg, controls) behind the list from receiving the event
+        event.abort();
 
-    //TODO sun#462 replace fireEmitter and selectionListener with a standard scenery listener
-    // Handles selection from the list box.
-    const selectionListener = {
-
-      up( event ) {
-        fireAction.execute( event );
-      },
-
-      // Handle keyup on each item in the list box, for a11y.
-      //TODO sun#447, scenery#931 we're using keyup because keydown fires continuously
-      keyup: event => {
-        if ( KeyboardUtils.KEY_ENTER === event.domEvent.keyCode || KeyboardUtils.KEY_SPACE === event.domEvent.keyCode ) {
-          fireAction.execute( event );
-          focusButtonCallback();
-        }
+        focusButtonCallback();
       },
 
-      // handle activation from an assistive device that may not use a keyboard (such as mobile VoiceOver)
-      click: event => {
-        fireAction.execute( event );
-        focusButtonCallback();
-      }
-    };
+      // phet-io
+      tandem: tandem.createTandem( 'fireListener' )
+    } );
 
     // Compute max item dimensions
     const maxItemWidth = _.maxBy( items, item => item.node.width ).node.width;
@@ -134,7 +118,7 @@
       } );
       listItemNodes.push( listItemNode );
 
-      listItemNode.addInputListener( selectionListener );
+      listItemNode.addInputListener( fireListener );
     } );
 
     const content = new VBox( {

We would like to see PressListener optionally fire from keydown/keyup events instead of click events so this kind of thing would work.

@zepumph
Copy link
Member

zepumph commented Jan 23, 2021

Over in phetsims/scenery#1137, we were able to get rid of one of these TODO links (4e6d4c7) because you can now focus elements even when input is mouse/touch. Focus highlights will only be shown when needed for keyboard.

@jonathanolson
Copy link
Contributor

is there a way to get currentTarget from a PressListener or subtype?

That seems like a SceneryEvent feature, but targetNode can also be used. Might be good to have a call if I can help!

@jonathanolson jonathanolson removed their assignment Jan 28, 2021
@zepumph zepumph removed their assignment Mar 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants