From 8a223c02ecad13fe2fdc4ced17c800c3586e568a Mon Sep 17 00:00:00 2001 From: Jonathan Olson Date: Wed, 3 Oct 2018 21:27:23 -0600 Subject: [PATCH 1/3] Assorted PressListener improvements --- js/listeners/DragListener.js | 3 +- js/listeners/FireListener.js | 2 - js/listeners/PressListener.js | 445 +++++++++++++++++++--------------- 3 files changed, 248 insertions(+), 202 deletions(-) diff --git a/js/listeners/DragListener.js b/js/listeners/DragListener.js index 2833671fd..0765b45d3 100644 --- a/js/listeners/DragListener.js +++ b/js/listeners/DragListener.js @@ -119,7 +119,8 @@ define( function( require ) { // {Tandem} - For instrumenting tandem: Tandem.required, - phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60 + // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60 + phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly }, options ); // Initialize with the alias isUserControlledProperty => isPressedProperty diff --git a/js/listeners/FireListener.js b/js/listeners/FireListener.js index fd421e031..4e9df9fd6 100644 --- a/js/listeners/FireListener.js +++ b/js/listeners/FireListener.js @@ -3,8 +3,6 @@ /** * A listener for common button usage, providing the fire() method/callback and helpful properties. * - * TODO: name (because ButtonListener was taken). Can we rename the old ButtonListener and have this be ButtonListener? - * * For example usage, see scenery/examples/input.html. Usually you can just pass a fire callback and things work. * * @author Jonathan Olson diff --git a/js/listeners/PressListener.js b/js/listeners/PressListener.js index 10c5ea283..3a536408d 100644 --- a/js/listeners/PressListener.js +++ b/js/listeners/PressListener.js @@ -84,42 +84,63 @@ define( function( require ) { // started. canStartPress: _.constant( true ), - // a11y {number} - how long something should 'look' pressed after an accessible click input event + // {number} (a11y) - How long something should 'look' pressed after an accessible click input event a11yLooksPressedInterval: 100, - // a11y {function} - called at the end of a press ("click") that was a result of a keyboard action. This will not - // be called for a mouse/pointer interaction. - onAccessibleClick: null, + // {function} (a11y) - Called at the end of a press ("click") that was a result of a keyboard action. This will + // not be called for a mouse/pointer interaction. + accessibleClick: _.noop, // {Tandem} - For instrumenting tandem: Tandem.required, - phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60 + // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60 + phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly }, options ); assert && assert( typeof options.mouseButton === 'number' && options.mouseButton >= 0 && options.mouseButton % 1 === 0, 'mouseButton should be a non-negative integer' ); assert && assert( options.pressCursor === null || typeof options.pressCursor === 'string', 'pressCursor should either be a string or null' ); - assert && assert( options.press === null || typeof options.press === 'function', - 'The press callback, if provided, should be a function' ); - assert && assert( options.release === null || typeof options.release === 'function', - 'The release callback, if provided, should be a function' ); - assert && assert( options.drag === null || typeof options.drag === 'function', - 'The drag callback, if provided, should be a function' ); + assert && assert( typeof options.press === 'function', + 'The press callback should be a function' ); + assert && assert( typeof options.release === 'function', + 'The release callback should be a function' ); + assert && assert( typeof options.drag === 'function', + 'The drag callback should be a function' ); assert && assert( options.targetNode === null || options.targetNode instanceof Node, 'If provided, targetNode should be a Node' ); assert && assert( typeof options.attach === 'boolean', 'attach should be a boolean' ); - assert && assert( options.onAccessibleClick === null || typeof options.onAccessibleClick === 'function', - 'If provided, onAccessibleClick should be a function' ); - assert && assert( options.a11yLooksPressedInterval === null || typeof options.a11yLooksPressedInterval === 'number', - 'If provided, a11yLooksPressedInterval should be a number' ); + assert && assert( typeof options.accessibleClick === 'function', + 'accessibleClick should be a function' ); + assert && assert( typeof options.a11yLooksPressedInterval === 'number', + 'a11yLooksPressedInterval should be a number' ); // @private {number} - Unique global ID for this listener this._id = globalID++; sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' construction' ); + // @private {number} + this._mouseButton = options.mouseButton; + this._a11yLooksPressedInterval = options.a11yLooksPressedInterval; + + // @private {string|null} + this._pressCursor = options.pressCursor; + + // @private {function} + this._pressListener = options.press; + this._releaseListener = options.release; + this._dragListener = options.drag; + this._canStartPress = options.canStartPress; + this._accessibleClickListener = options.accessibleClick; + + // @private {Node|null} + this._targetNode = options.targetNode; + + // @private {boolean} + this._attach = options.attach; + // @public {ObservableArray.} - Contains all pointers that are over our button. Tracked by adding with // 'enter' events and removing with 'exit' events. this.overPointers = new ObservableArray(); @@ -154,18 +175,6 @@ define( function( require ) { // @public {boolean} [read-only] - Whether the last press was interrupted. Will be valid until the next press. this.interrupted = false; - // @private - Stored options, see options for documentation. - this._mouseButton = options.mouseButton; - this._pressCursor = options.pressCursor; - this._pressListener = options.press; - this._releaseListener = options.release; - this._dragListener = options.drag; - this._targetNode = options.targetNode; - this._attach = options.attach; - this._canStartPress = options.canStartPress; - this._a11yLooksPressedInterval = options.a11yLooksPressedInterval; // used for a11y - this._onAccessibleClick = options.onAccessibleClick; // used for a11y - // @private {boolean} - Whether our pointer listener is referenced by the pointer (need to have a flag due to // handling disposal properly). this._listeningToPointer = false; @@ -178,16 +187,16 @@ define( function( require ) { // properties) this._isHighlightedListener = this.invalidateHighlighted.bind( this ); - // @public (read-only) {BooleanProperty} - Whether or not a press is being processed from an a11y click input event. + // @public {BooleanProperty} [read-only] - Whether or not a press is being processed from an a11y click input event. this.a11yClickingProperty = new BooleanProperty( false ); - // @public (read-only) {BooleanProperty} - This Property was added for a11y. It tracks whether or not the button + // @public {BooleanProperty} [read-only] - This Property was added for a11y. It tracks whether or not the button // should "look" down. This will be true if downProperty is true or if an a11y click is in progress. For an a11y // click, the listeners are fired right away but the button will look down for as long as a11yLooksPressedInterval. // See PressListener.click() for more details. this.looksPressedProperty = DerivedProperty.or( [ this.a11yClickingProperty, this.isPressedProperty ] ); - // @private {function|null } - When a11y clicking begins, this will be added to a timeout so that the + // @private {function|null} - When a11y clicking begins, this will be added to a timeout so that the // a11yClickingProperty is updated after some delay. This is required since an assistive device (like a switch) may // send "click" events directly instead of keydown/keyup pairs. If a click initiates while already in progress, // this listener will be removed to start the timeout over. null until timout is added. @@ -195,69 +204,10 @@ define( function( require ) { // @private {Object} - The listener that gets added to the pointer when we are pressed this._pointerListener = { - /** - * Called with 'up' events from the pointer (part of the listener API) - * @public (scenery-internal) - * - * @param {Event} event - */ - up: function( event ) { - sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + self._id + ' pointer up' ); - sceneryLog && sceneryLog.InputListener && sceneryLog.push(); - - assert && assert( event.pointer === self.pointer ); - - self.release(); - - sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); - }, - - /** - * Called with 'cancel' events from the pointer (part of the listener API) - * @public (scenery-internal) - * - * @param {Event} event - */ - cancel: function( event ) { - sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + self._id + ' pointer cancel' ); - sceneryLog && sceneryLog.InputListener && sceneryLog.push(); - - assert && assert( event.pointer === self.pointer ); - - self.interrupt(); // will mark as interrupted and release() - - sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); - }, - - /** - * Called with 'move' events from the pointer (part of the listener API) - * @public (scenery-internal) - * - * @param {Event} event - */ - move: function( event ) { - sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + self._id + ' pointer move' ); - sceneryLog && sceneryLog.InputListener && sceneryLog.push(); - - assert && assert( event.pointer === self.pointer ); - - self.drag( event ); - - sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); - }, - - /** - * Called when the pointer needs to interrupt its current listener (usually so another can be added). - * @public (scenery-internal) - */ - interrupt: function() { - sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + self._id + ' pointer interrupt' ); - sceneryLog && sceneryLog.InputListener && sceneryLog.push(); - - self.interrupt(); - - sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); - } + up: this.pointerUp.bind( this ), + cancel: this.pointerCancel.bind( this ), + move: this.pointerMove.bind( this ), + interrupt: this.pointerInterrupt.bind( this ) }; // @public {Object} - the collection of event listeners that can be added to a Node through @@ -269,7 +219,7 @@ define( function( require ) { blur: this.blur.bind( this ) }; - // @private {Emitter} - emitted on press event + // @private {Emitter} - Emitted on press event this._pressedEmitter = new Emitter( { tandem: options.tandem.createTandem( 'pressedEmitter' ), phetioInstanceDocumentation: 'Emits whenever a press occurs. The first argument when emitting can be ' + @@ -279,30 +229,11 @@ define( function( require ) { phetioType: EmitterIO( [ EventIO, VoidIO, VoidIO ] ) } ); - this._pressedEmitter.addListener( function( event, targetNode, callback ) { - - targetNode = targetNode || self._targetNode; + // The main implementation of "press" handling is implemented as a callback to the emitter, so things are nested + // nicely for phet-io. + this._pressedEmitter.addListener( this.onPress.bind( this ) ); - // Set self properties before the property change, so they are visible to listeners. - self.pointer = event.pointer; - self.pressedTrail = targetNode ? targetNode.getUniqueTrail() : event.trail.subtrailTo( event.currentTarget, false ); - - self.interrupted = false; // clears the flag (don't set to false before here) - - self.pointer.addInputListener( self._pointerListener, self._attach ); - self._listeningToPointer = true; - - self.pointer.cursor = self._pressCursor; - - self.isPressedProperty.value = true; - - // Notify after everything else is set up - self._pressListener( event, self ); - - callback && callback(); - } ); - - // @private - emitted on release event + // @private {Emitter} - Emitted on release event this._releasedEmitter = new Emitter( { tandem: options.tandem.createTandem( 'releasedEmitter' ), phetioInstanceDocumentation: 'Emits whenever a release occurs.', @@ -311,26 +242,9 @@ define( function( require ) { phetioType: EmitterIO( [ VoidIO ] ) } ); - this._releasedEmitter.addListener( function( callback ) { - - assert && assert( self.isPressed, 'This listener is not pressed' ); - - self.pointer.removeInputListener( self._pointerListener ); - self._listeningToPointer = false; - - self.pointer.cursor = null; - - // Unset self properties after the property change, so they are visible to listeners beforehand. - self.pointer = null; - self.pressedTrail = null; - - self.isPressedProperty.value = false; - - // Notify after the rest of release is called in order to prevent it from triggering interrupt(). - self._releaseListener( self ); - - callback && callback(); - } ); + // The main implementation of "release" handling is implemented as a callback to the emitter, so things are nested + // nicely for phet-io. + this._releasedEmitter.addListener( this.onRelease.bind( this ) ); // update isOverProperty (not a DerivedProperty because we need to hook to passed-in properties) this.overPointers.lengthProperty.link( this.invalidateOver.bind( this ) ); @@ -379,60 +293,6 @@ define( function( require ) { return this.pressedTrail.lastNode(); }, - /** - * Called with 'down' events (part of the listener API). - * @public (scenery-internal) - * - * NOTE: Do not call directly. See the press method instead. - * - * @param {Event} event - */ - down: function( event ) { - sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' down' ); - sceneryLog && sceneryLog.InputListener && sceneryLog.push(); - - this.press( event ); - - sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); - }, - - /** - * Called with 'enter' events (part of the listener API). - * @public (scenery-internal) - * - * NOTE: Do not call directly. - * - * @param {Event} event - */ - enter: function( event ) { - sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' enter' ); - sceneryLog && sceneryLog.InputListener && sceneryLog.push(); - - this.overPointers.push( event.pointer ); - - sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); - }, - - /** - * Called with 'exit' events (part of the listener API). - * @public (scenery-internal) - * - * NOTE: Do not call directly. - * - * @param {Event} event - */ - exit: function( event ) { - sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' exit' ); - sceneryLog && sceneryLog.InputListener && sceneryLog.push(); - - // NOTE: We don't require the pointer to be included here, since we may have added the listener after the 'enter' - // was fired. See https://github.com/phetsims/area-model-common/issues/159 for more details. This may be a - // no-op, which ObservableArray allows. - this.overPointers.remove( event.pointer ); - - sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); - }, - /** * Returns whether a press can be started with a particular event. * @public @@ -506,6 +366,7 @@ define( function( require ) { * * This can be called from the outside to release the press without the pointer having actually fired any 'up' * events. If the cancel/interrupt behavior is more preferable, call interrupt() on this listener instead. + * * @param {function} [callback] - called at the end of the release */ release: function( callback ) { @@ -589,6 +450,193 @@ define( function( require ) { }, /** + * Internal code executed as the first step of a press. + * @private + * + * @param {Event} event + * @param {Node} [targetNode] - If provided, will take the place of the targetNode for this call. Useful for + * forwarded presses. + * @param {function} [callback] - to be run at the end of the function, but only on success + */ + onPress: function( event, targetNode, callback ) { + targetNode = targetNode || this._targetNode; + + // Set this properties before the property change, so they are visible to listeners. + this.pointer = event.pointer; + this.pressedTrail = targetNode ? targetNode.getUniqueTrail() : event.trail.subtrailTo( event.currentTarget, false ); + + this.interrupted = false; // clears the flag (don't set to false before here) + + this.pointer.addInputListener( this._pointerListener, this._attach ); + this._listeningToPointer = true; + + this.pointer.cursor = this._pressCursor; + + this.isPressedProperty.value = true; + + // Notify after everything else is set up + this._pressListener( event, this ); + + callback && callback(); + }, + + /** + * Internal code executed as the first step of a release. + * @private + * + * @param {function} [callback] - called at the end of the release + */ + onRelease: function( callback ) { + assert && assert( this.isPressed, 'This listener is not pressed' ); + + this.pointer.removeInputListener( this._pointerListener ); + this._listeningToPointer = false; + + this.pointer.cursor = null; + + // Unset this properties after the property change, so they are visible to listeners beforehand. + this.pointer = null; + this.pressedTrail = null; + + this.isPressedProperty.value = false; + + // Notify after the rest of release is called in order to prevent it from triggering interrupt(). + this._releaseListener( this ); + + callback && callback(); + }, + + /** + * Called with 'down' events (part of the listener API). + * @public (scenery-internal) + * + * NOTE: Do not call directly. See the press method instead. + * + * @param {Event} event + */ + down: function( event ) { + sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' down' ); + sceneryLog && sceneryLog.InputListener && sceneryLog.push(); + + this.press( event ); + + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); + }, + + /** + * Called with 'enter' events (part of the listener API). + * @public (scenery-internal) + * + * NOTE: Do not call directly. + * + * @param {Event} event + */ + enter: function( event ) { + sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' enter' ); + sceneryLog && sceneryLog.InputListener && sceneryLog.push(); + + this.overPointers.push( event.pointer ); + + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); + }, + + /** + * Called with 'exit' events (part of the listener API). + * @public (scenery-internal) + * + * NOTE: Do not call directly. + * + * @param {Event} event + */ + exit: function( event ) { + sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' exit' ); + sceneryLog && sceneryLog.InputListener && sceneryLog.push(); + + // NOTE: We don't require the pointer to be included here, since we may have added the listener after the 'enter' + // was fired. See https://github.com/phetsims/area-model-common/issues/159 for more details. This may be a + // no-op, which ObservableArray allows. + this.overPointers.remove( event.pointer ); + + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); + }, + + /** + * Called with 'up' events from the pointer (part of the listener API) + * @public (scenery-internal) + * + * NOTE: Do not call directly. + * + * @param {Event} event + */ + pointerUp: function( event ) { + sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' pointer up' ); + sceneryLog && sceneryLog.InputListener && sceneryLog.push(); + + assert && assert( event.pointer === this.pointer ); + + this.release(); + + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); + }, + + /** + * Called with 'cancel' events from the pointer (part of the listener API) + * @public (scenery-internal) + * + * NOTE: Do not call directly. + * + * @param {Event} event + */ + pointerCancel: function( event ) { + sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' pointer cancel' ); + sceneryLog && sceneryLog.InputListener && sceneryLog.push(); + + assert && assert( event.pointer === this.pointer ); + + this.interrupt(); // will mark as interrupted and release() + + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); + }, + + /** + * Called with 'move' events from the pointer (part of the listener API) + * @public (scenery-internal) + * + * NOTE: Do not call directly. + * + * @param {Event} event + */ + pointerMove: function( event ) { + sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' pointer move' ); + sceneryLog && sceneryLog.InputListener && sceneryLog.push(); + + assert && assert( event.pointer === this.pointer ); + + this.drag( event ); + + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); + }, + + /** + * Called when the pointer needs to interrupt its current listener (usually so another can be added). + * @public (scenery-internal) + * + * NOTE: Do not call directly. + */ + pointerInterrupt: function() { + sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'PressListener#' + this._id + ' pointer interrupt' ); + sceneryLog && sceneryLog.InputListener && sceneryLog.push(); + + this.interrupt(); + + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); + }, + + /** + * Click listener, called when this is treated as an accessible input listener. + * @public - In general not needed to be public, but just used in edge cases to get proper click logic for a11y. + * @a11y + * * Handle the click event from DOM for a11y, see PressListener.a11yListener for more information in this usage. * Clicks by setting the over and pressed Properties behind a timeout. When assistive technology is * used, the browser may not receive 'down' or 'up' events on buttons - only a single 'click' event. For a11y we @@ -596,9 +644,6 @@ define( function( require ) { * * This will fire listeners immediately, but adds a delay for the a11yClickingProperty so that you can make a * button look pressed from a single DOM click event. For example usage, see sun/ButtonModel.looksPressedProperty. - * - * @public - In general not needed to be public, but just used in edge cases to get proper click logic for a11y. - * @a11y */ click: function() { if ( this.canClick() ) { @@ -613,7 +658,7 @@ define( function( require ) { this.isPressedProperty.value = false; // call the a11y click specific listener - this._onAccessibleClick && this._onAccessibleClick(); + this._accessibleClickListener(); // if we are already clicking, remove the previous timeout - this assumes that clearTimeout is a noop if the // listener is no longer attached @@ -628,20 +673,22 @@ define( function( require ) { }, /** - * On focus, button should look 'over'. - * @private + * Focus listener, called when this is treated as an accessible input listener. + * @public (scenery-internal) * @a11y */ focus: function() { + // On focus, button should look 'over'. this.isFocusedProperty.value = true; }, /** - * On blur, the button should no longer look 'over'. - * @private + * Blur listener, called when this is treated as an accessible input listener. + * @public (scenery-internal) * @a11y */ blur: function() { + // On blur, the button should no longer look 'over'. this.isFocusedProperty.value = false; }, From a060fac08771e39b97343d355469e7310a06a765 Mon Sep 17 00:00:00 2001 From: Jonathan Olson Date: Thu, 4 Oct 2018 00:06:41 -0600 Subject: [PATCH 2/3] Fixing isUserControlledProperty --- js/listeners/DragListener.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/js/listeners/DragListener.js b/js/listeners/DragListener.js index 0765b45d3..bda42ef49 100644 --- a/js/listeners/DragListener.js +++ b/js/listeners/DragListener.js @@ -113,9 +113,6 @@ define( function( require ) { // passing release(), as the drag start hasn't been fully processed at that point. end: null, - // {Property.|null} - An alias for isPressedListener that makes more sense for dragging. - isUserControlledProperty: null, - // {Tandem} - For instrumenting tandem: Tandem.required, @@ -123,12 +120,6 @@ define( function( require ) { phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly }, options ); - // Initialize with the alias isUserControlledProperty => isPressedProperty - if ( options.isUserControlledProperty ) { - assert && assert( !options.isPressedProperty ); - options.isPressedProperty = options.isUserControlledProperty; - } - assert && assert( typeof options.allowTouchSnag === 'boolean', 'allowTouchSnag should be a boolean' ); assert && assert( typeof options.applyOffset === 'boolean', 'applyOffset should be a boolean' ); assert && assert( typeof options.trackAncestors === 'boolean', 'trackAncestors should be a boolean' ); @@ -141,7 +132,6 @@ define( function( require ) { assert && assert( options.offsetLocation === null || typeof options.offsetLocation === 'function', 'offsetLocation, if provided, should be a function' ); assert && assert( options.start === null || typeof options.start === 'function', 'start, if provided, should be a function' ); assert && assert( options.end === null || typeof options.end === 'function', 'end, if provided, should be a function' ); - assert && assert( options.isUserControlledProperty === null || options.isUserControlledProperty instanceof Property, 'isUserControlledProperty, if provided, should be a Property' ); assert && assert( options.tandem instanceof Tandem, 'The provided tandem should be a Tandem' ); assert && assert( From 4628d4eabb1bcd25bbdef7a7d9e057cd1f4c870c Mon Sep 17 00:00:00 2001 From: Jonathan Olson Date: Thu, 4 Oct 2018 01:39:55 -0600 Subject: [PATCH 3/3] Some cleanup so overrides are typed the same --- js/listeners/DragListener.js | 11 +++++++++-- js/listeners/FireListener.js | 30 ++++++++++++++++++------------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/js/listeners/DragListener.js b/js/listeners/DragListener.js index bda42ef49..ee4c658c4 100644 --- a/js/listeners/DragListener.js +++ b/js/listeners/DragListener.js @@ -215,9 +215,10 @@ define( function( require ) { * @param {Event} event * @param {Node} [targetNode] - If provided, will take the place of the targetNode for this call. Useful for * forwarded presses. + * @param {function} [callback] - to be run at the end of the function, but only on success * @returns {boolean} success - Returns whether the press was actually started */ - press: function( event, targetNode ) { + press: function( event, targetNode, callback ) { var self = this; sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'DragListener press' ); sceneryLog && sceneryLog.InputListener && sceneryLog.push(); @@ -236,6 +237,8 @@ define( function( require ) { // Notify after positioning and other changes self._start && self._start( event, self ); + callback && callback(); + sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); } ); @@ -251,8 +254,10 @@ define( function( require ) { * * This can be called from the outside to stop the drag without the pointer having actually fired any 'up' * events. If the cancel/interrupt behavior is more preferable, call interrupt() on this listener instead. + * + * @param {function} [callback] - called at the end of the release */ - release: function() { + release: function( callback ) { var self = this; sceneryLog && sceneryLog.InputListener && sceneryLog.InputListener( 'DragListener release' ); @@ -263,6 +268,8 @@ define( function( require ) { // Notify after the rest of release is called in order to prevent it from triggering interrupt(). self._end && self._end( self ); + + callback && callback(); } ); sceneryLog && sceneryLog.InputListener && sceneryLog.pop(); diff --git a/js/listeners/FireListener.js b/js/listeners/FireListener.js index 4e9df9fd6..dc14b0540 100644 --- a/js/listeners/FireListener.js +++ b/js/listeners/FireListener.js @@ -25,31 +25,31 @@ define( function( require ) { */ function FireListener( options ) { options = _.extend( { - // {Function|null} - Called as fire() when the button is fired. - fire: null, + // {Function} - Called as fire() when the button is fired. + fire: _.noop, // {boolean} - If true, the button will fire when the button is pressed. If false, the button will fire when the // button is released while the pointer is over the button. fireOnDown: false, + // {Tandem} tandem: Tandem.optional }, options ); - assert && assert( options.fire === null || typeof options.fire === 'function', - 'The fire callback, if provided, should be a function' ); + assert && assert( typeof options.fire === 'function', 'The fire callback should be a function' ); assert && assert( typeof options.fireOnDown === 'boolean', 'fireOnDown should be a boolean' ); PressListener.call( this, options ); - // @private - See options for documentation. + // @private {boolean} this._fireOnDown = options.fireOnDown; - // @private - for PhET-iO events + // @private {Emitter} this.firedEmitter = new Emitter( { tandem: options.tandem.createTandem( 'firedEmitter' ), phetioEventType: 'user' } ); - options.fire && this.firedEmitter.addListener( options.fire ); + this.firedEmitter.addListener( options.fire ); } scenery.register( 'FireListener', FireListener ); @@ -81,18 +81,21 @@ define( function( require ) { * be used to determine whether this will actually start a press. * * @param {Event} event + * @param {Node} [targetNode] - If provided, will take the place of the targetNode for this call. Useful for + * forwarded presses. + * @param {function} [callback] - to be run at the end of the function, but only on success * @returns {boolean} success - Returns whether the press was actually started */ - press: function( event ) { + press: function( event, targetNode, callback ) { var self = this; - var success = PressListener.prototype.press.call( this, event, undefined, function( success ) { + return PressListener.prototype.press.call( this, event, targetNode, function() { // This function is only called on success if ( self._fireOnDown ) { self.fire( event ); } + callback && callback(); } ); - return success; }, /** @@ -103,15 +106,18 @@ define( function( require ) { * NOTE: This can be safely called externally in order to force a release of this button (no actual 'up' event is * needed). If the cancel/interrupt behavior is more preferable (will not fire the button), then call interrupt() * on this listener instead. + * + * @param {function} [callback] - called at the end of the release */ - release: function() { + release: function( callback ) { var self = this; - PressListener.prototype.release.call( this, function() { + PressListener.prototype.release.call( this, 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(); } + callback && callback(); } ); } } );