diff --git a/js/common/view/BothHandsInteractionListener.ts b/js/common/view/BothHandsInteractionListener.ts index 568f5ef0..2e4ece6c 100644 --- a/js/common/view/BothHandsInteractionListener.ts +++ b/js/common/view/BothHandsInteractionListener.ts @@ -17,16 +17,42 @@ import ratioAndProportion from '../../ratioAndProportion.js'; import RAPRatioTuple from '../model/RAPRatioTuple.js'; import RatioTerm from '../model/RatioTerm.js'; import rapConstants from '../rapConstants.js'; -import getKeyboardInputSnappingMapper from './getKeyboardInputSnappingMapper.js'; +import getKeyboardInputSnappingMapper, { KeyboardInputMapper } from './getKeyboardInputSnappingMapper.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Property from '../../../../axon/js/Property.js'; +import Range from '../../../../dot/js/Range.js'; +import BoundarySoundClip from './sound/BoundarySoundClip.js'; +import TickMarkBumpSoundClip from './sound/TickMarkBumpSoundClip.js'; +import SceneryEvent from '../../../../scenery/js/input/SceneryEvent.js'; const TOTAL_RANGE = rapConstants.TOTAL_RATIO_TERM_VALUE_RANGE; class BothHandsInteractionListener { + private targetNode: Node; + private antecedentInteractedWithProperty: BooleanProperty; + private consequentInteractedWithProperty: BooleanProperty; + private enabledRatioTermsRangeProperty: Property; + private tickMarkRangeProperty: Property; + private ratioTupleProperty: Property; + private keyboardStep: number; + private shiftKeyboardStep: number; + private boundarySoundClip: BoundarySoundClip; + private tickMarkBumpSoundClip: TickMarkBumpSoundClip; + private ratioLockedProperty: Property; + private targetRatioProperty: Property; + private inProportionProperty: Property; + private onInput: ( knockOutOfLock?: boolean ) => void; + private antecedentMapKeyboardInput: KeyboardInputMapper; + private consequentMapKeyboardInput: KeyboardInputMapper; + private isBeingInteractedWithProperty: Property; + private jumpToZeroWhileLockedEmitter: Emitter<[]>; + private playBoundarySoundOnKeyup: boolean; + /** * @param {Object} config */ - constructor( config ) { + constructor( config: any ) { config = merge( { @@ -126,16 +152,16 @@ class BothHandsInteractionListener { * @param {boolean} increment - if the value is being incremented, as opposed to decremented. * @private */ - onValueIncrementDecrement( tupleField, inputMapper, increment ) { + onValueIncrementDecrement( tupleField: 'antecedent' | 'consequent', inputMapper: KeyboardInputMapper, increment: boolean ) { this.isBeingInteractedWithProperty.value = true; - const currentTuple = this.ratioTupleProperty.value[ tupleField ]; + const currentValueFromTuple = this.ratioTupleProperty.value[ tupleField ]; const changeAmount = globalKeyStateTracker.shiftKeyDown ? this.shiftKeyboardStep : this.keyboardStep; const valueDelta = changeAmount * ( increment ? 1 : -1 ); // Because this interaction uses the keyboard, snap to the keyboard step to handle the case where the hands were // previously moved via mouse/touch. See https://github.com/phetsims/ratio-and-proportion/issues/156 - const newValue = inputMapper( currentTuple + valueDelta, currentTuple, globalKeyStateTracker.shiftKeyDown ? this.shiftKeyboardStep : this.keyboardStep, this.inProportionProperty.value ); + const newValue = inputMapper( currentValueFromTuple + valueDelta, currentValueFromTuple, globalKeyStateTracker.shiftKeyDown, this.inProportionProperty.value ); const newRatioTuple = tupleField === 'antecedent' ? this.ratioTupleProperty.value.withAntecedent( newValue ) : this.ratioTupleProperty.value.withConsequent( newValue ); this.ratioTupleProperty.value = newRatioTuple.constrainFields( this.enabledRatioTermsRangeProperty.value ); @@ -156,7 +182,7 @@ class BothHandsInteractionListener { * @public * @param {SceneryEvent} sceneryEvent */ - keydown( sceneryEvent ) { + keydown( sceneryEvent: SceneryEvent ) { if ( sceneryEvent.target === this.targetNode ) { @@ -164,7 +190,8 @@ class BothHandsInteractionListener { // their behavior during scenery event dispatch sceneryEvent.pointer.reserveForKeyboardDrag(); - const domEvent = sceneryEvent.domEvent; + assert && assert( sceneryEvent.domEvent, 'dom event expected' ); + const domEvent = sceneryEvent.domEvent as Event; const key = KeyboardUtils.getEventCode( domEvent ); if ( key === KeyboardUtils.KEY_DOWN_ARROW ) { @@ -188,8 +215,14 @@ class BothHandsInteractionListener { // for number keys 0-9, jump both values to that tick mark number. This value changes based on the tickMarkRangeProperty for ( let i = 0; i <= 9; i++ ) { if ( KeyboardUtils.getNumberFromCode( domEvent ) === i && + + // @ts-ignore !domEvent.getModifierState( 'Control' ) && + + // @ts-ignore !domEvent.getModifierState( 'Shift' ) && + + // @ts-ignore !domEvent.getModifierState( 'Alt' ) ) { this.isBeingInteractedWithProperty.value = true; @@ -246,11 +279,12 @@ class BothHandsInteractionListener { * @public * @param {SceneryEvent} sceneryEvent */ - keyup( sceneryEvent ) { + keyup( sceneryEvent: SceneryEvent ) { if ( sceneryEvent.target === this.targetNode ) { - const domEvent = sceneryEvent.domEvent; + assert && assert( sceneryEvent.domEvent, 'domEvent expected' ); + const domEvent = sceneryEvent.domEvent as Event; if ( KeyboardUtils.isAnyKeyEvent( domEvent, [ KeyboardUtils.KEY_DOWN_ARROW, KeyboardUtils.KEY_UP_ARROW ] ) ) { this.handleBoundarySoundOnInput( this.ratioTupleProperty.value.consequent ); @@ -270,8 +304,8 @@ class BothHandsInteractionListener { * Handle boundary sound output based on an input for this interaction * @param {number} newValue */ - handleBoundarySoundOnInput( newValue ) { - this.boundarySoundClip.onStartInteraction( newValue ); + handleBoundarySoundOnInput( newValue: number ) { + this.boundarySoundClip.onStartInteraction(); this.boundarySoundClip.onInteract( newValue ); this.boundarySoundClip.onEndInteraction( newValue ); } diff --git a/js/common/view/RAPScreenView.ts b/js/common/view/RAPScreenView.ts index 69618121..2202984b 100644 --- a/js/common/view/RAPScreenView.ts +++ b/js/common/view/RAPScreenView.ts @@ -84,7 +84,7 @@ class RAPScreenView extends ScreenView { * @param {Tandem} tandem * @param {Object} [options] */ - constructor( model: RAPModel, tandem: Tandem, options: any ) { + constructor( model: RAPModel, tandem: Tandem, options?: any ) { options = merge( { tandem: tandem, diff --git a/js/common/view/getKeyboardInputSnappingMapper.ts b/js/common/view/getKeyboardInputSnappingMapper.ts index 1375596c..887f97aa 100644 --- a/js/common/view/getKeyboardInputSnappingMapper.ts +++ b/js/common/view/getKeyboardInputSnappingMapper.ts @@ -14,18 +14,24 @@ import Utils from '../../../../dot/js/Utils.js'; import ratioAndProportion from '../../ratioAndProportion.js'; import rapConstants from '../rapConstants.js'; +type KeyboardInputMapper = { + ( newValue: number, oldVaue: number, useShiftKeyStep: boolean, alreadyInProportion: boolean ): number, + reset: () => void +} + + /** * @param {function():number} getIdealValue - get the ideal target value * @param {number} keyboardStep * @param {number} shiftKeyboardStep - * @returns {function(newValue: number, oldValue:number, useShiftKeyStep:boolean, alreadyInProportion:boolean):number} - returns a function that returns the snap/conserved value + * @returns {KeyboardInputMapper} - returns a function that returns the snap/conserved value */ -function getKeyboardInputSnappingMapper( getIdealValue, keyboardStep, shiftKeyboardStep ) { +function getKeyboardInputSnappingMapper( getIdealValue: () => number, keyboardStep: number, shiftKeyboardStep: number ) { // keep track of the remainder for next input post-process let remainder = 0; - const snappingFunction = ( newValue, oldValue, useShiftKeyStep, alreadyInProportion ) => { + const snappingFunction: KeyboardInputMapper = ( newValue, oldValue, useShiftKeyStep, alreadyInProportion ) => { // Don't conserve the snap for page up/down or home/end keys, just basic movement changes. const applyConservationSnap = rapConstants.toFixed( Math.abs( newValue - oldValue ) ) <= shiftKeyboardStep && // eslint-disable-line bad-sim-text newValue > rapConstants.NO_SUCCESS_VALUE_THRESHOLD && @@ -36,8 +42,7 @@ function getKeyboardInputSnappingMapper( getIdealValue, keyboardStep, shiftKeybo if ( remainder === 0 ) { const snapToKeyboardStep = useShiftKeyStep ? shiftKeyboardStep : keyboardStep; newValue = rapConstants.toFixed( // eslint-disable-line bad-sim-text - Utils.roundSymmetric( newValue / snapToKeyboardStep ) * snapToKeyboardStep, - Utils.numberOfDecimalPlaces( snapToKeyboardStep ) ); + Utils.roundSymmetric( newValue / snapToKeyboardStep ) * snapToKeyboardStep ); } // If we are in the case where we want to potentially snap to the value that would yield the in-proportion state. @@ -71,4 +76,5 @@ function getKeyboardInputSnappingMapper( getIdealValue, keyboardStep, shiftKeybo } ratioAndProportion.register( 'getKeyboardInputSnappingMapper', getKeyboardInputSnappingMapper ); +export { KeyboardInputMapper }; export default getKeyboardInputSnappingMapper; \ No newline at end of file diff --git a/js/common/view/sound/BoundarySoundClip.ts b/js/common/view/sound/BoundarySoundClip.ts index 5aaa2a31..2f8e02d3 100644 --- a/js/common/view/sound/BoundarySoundClip.ts +++ b/js/common/view/sound/BoundarySoundClip.ts @@ -26,7 +26,7 @@ class BoundarySoundClip extends SoundClip { * @param {Range} verticalRange - the total range that the vertical position can take * @param {Object} [options] */ - constructor( verticalRange: Range, options: any ) { + constructor( verticalRange: Range, options?: any ) { super( boundarySound, options ); // @private @@ -47,7 +47,7 @@ class BoundarySoundClip extends SoundClip { * @param {number} [horizontalPosition] * @param {Range} [horizontalRange] - the horizontal range can change based on view scaling */ - onInteract( verticalPosition: number, horizontalPosition: number, horizontalRange: Range ) { + onInteract( verticalPosition: number, horizontalPosition?: number, horizontalRange?: Range ) { if ( this.lastYPosition !== verticalPosition && ( verticalPosition === this.verticalRange.min || verticalPosition === this.verticalRange.max ) ) { @@ -55,7 +55,7 @@ class BoundarySoundClip extends SoundClip { } this.lastYPosition = verticalPosition; - if ( horizontalPosition ) { + if ( horizontalPosition && horizontalRange ) { if ( this.lastXPosition !== horizontalPosition && ( horizontalPosition === horizontalRange.min || horizontalPosition === horizontalRange.max ) ) { diff --git a/js/common/view/sound/InProportionSoundGenerator.ts b/js/common/view/sound/InProportionSoundGenerator.ts index c9db36e3..2c5adaca 100644 --- a/js/common/view/sound/InProportionSoundGenerator.ts +++ b/js/common/view/sound/InProportionSoundGenerator.ts @@ -46,7 +46,7 @@ class InProportionSoundGenerator extends SoundClip { * an on/off Property for the SoundGenerator, see below. * @param {Object} [options] */ - constructor( model: RAPModel, enabledControlProperty: Property, options: any ) { + constructor( model: RAPModel, enabledControlProperty: Property, options?: any ) { options = merge( { initialOutputLevel: 0.5 diff --git a/js/common/view/sound/MovingInProportionSoundGenerator.ts b/js/common/view/sound/MovingInProportionSoundGenerator.ts index 9917a270..25d04c4d 100644 --- a/js/common/view/sound/MovingInProportionSoundGenerator.ts +++ b/js/common/view/sound/MovingInProportionSoundGenerator.ts @@ -23,7 +23,7 @@ class MovingInProportionSoundGenerator extends SoundGenerator { * @param {RAPModel} model * @param {Object} [options] */ - constructor( model: RAPModel, options: any ) { + constructor( model: RAPModel, options?: any ) { options = merge( { initialOutputLevel: 0.13 }, options ); diff --git a/js/common/view/sound/StaccatoFrequencySoundGenerator.ts b/js/common/view/sound/StaccatoFrequencySoundGenerator.ts index 959e6163..05bd81b6 100644 --- a/js/common/view/sound/StaccatoFrequencySoundGenerator.ts +++ b/js/common/view/sound/StaccatoFrequencySoundGenerator.ts @@ -8,6 +8,7 @@ */ import dotRandom from '../../../../../dot/js/dotRandom.js'; +import Range from '../../../../../dot/js/Range.js'; import LinearFunction from '../../../../../dot/js/LinearFunction.js'; import merge from '../../../../../phet-core/js/merge.js'; import SoundClip from '../../../../../tambo/js/sound-generators/SoundClip.js'; @@ -51,12 +52,16 @@ const staccatoSounds = [ [ gSound, g001Sound, g002Sound ] ]; +type LinearFunctionStub = ( x: number ) => number; + class StaccatoFrequencySoundGenerator extends SoundGenerator { private inProportionProperty: Property; private fitnessProperty: Property; private staccatoSoundClips: SoundClip[][]; + private timeLinearFunction: LinearFunctionStub; + private timeSinceLastPlay: number; /** * @param {Property.} fitnessProperty @@ -97,7 +102,7 @@ class StaccatoFrequencySoundGenerator extends SoundGenerator { fitnessRange.max, 500, 120, - true ); + true ) as LinearFunctionStub; // @private - in ms, keep track of the amount of time that has passed since the last staccato sound played this.timeSinceLastPlay = 0; @@ -108,7 +113,7 @@ class StaccatoFrequencySoundGenerator extends SoundGenerator { * @param {number} dt * @public */ - step( dt ) { + step( dt: number ) { const newFitness = this.fitnessProperty.value; // If fitness is less than zero, make sure enough time has past that it will play a sound immediately. diff --git a/js/common/view/sound/TickMarkBumpSoundClip.ts b/js/common/view/sound/TickMarkBumpSoundClip.ts index 76ad3599..7e1d32de 100644 --- a/js/common/view/sound/TickMarkBumpSoundClip.ts +++ b/js/common/view/sound/TickMarkBumpSoundClip.ts @@ -9,19 +9,26 @@ import SoundClip from '../../../../../tambo/js/sound-generators/SoundClip.js'; import tickMarkCrossBumpSound from '../../../../../tambo/sounds/general-soft-click_mp3.js'; import ratioAndProportion from '../../../ratioAndProportion.js'; +import NumberProperty from '../../../../../axon/js/NumberProperty.js'; +import Range from '../../../../../dot/js/Range.js'; // This value was copied from similar sound work done in Waves Intro const MIN_INTER_CLICK_TIME = 33.3; // min time between clicking sounds, in milliseconds, empirically determined class TickMarkBumpSoundClip extends SoundClip { + private tickMarkRangeProperty: NumberProperty; + private positionRange: Range; + private timeOfLastClick: number; + private lastValue: null | number; + /** * @param {NumberProperty} tickMarkRangeProperty - serves as the divisor of the position range to yield position * where bump sounds should occur. * @param {Range} positionRange - the total range in position * @param {Object} [options] */ - constructor( tickMarkRangeProperty, positionRange, options ) { + constructor( tickMarkRangeProperty: NumberProperty, positionRange: Range, options?: any ) { super( tickMarkCrossBumpSound, options ); // @private @@ -38,24 +45,27 @@ class TickMarkBumpSoundClip extends SoundClip { * Call this when an interaction occurs that could potentially cause a tick mark sound to play. * * @public - * @param currentValue + * @param {number} currentValue */ - onInteract( currentValue ) { + onInteract( currentValue: number ) { + + if ( this.lastValue !== null ) { - // handle the sound as desired for mouse/touch style input (for vertical changes) - for ( let i = 0; i < this.tickMarkRangeProperty.value; i++ ) { - const tickValue = ( i / this.positionRange.getLength() ) / this.tickMarkRangeProperty.value; + // handle the sound as desired for mouse/touch style input (for vertical changes) + for ( let i = 0; i < this.tickMarkRangeProperty.value; i++ ) { + const tickValue = ( i / this.positionRange.getLength() ) / this.tickMarkRangeProperty.value; - // Not at max or min, crossed a tick mark value - if ( currentValue !== this.positionRange.min && currentValue !== this.positionRange.max && - this.lastValue < tickValue && currentValue >= tickValue || this.lastValue > tickValue && currentValue <= tickValue ) { + // Not at max or min, crossed a tick mark value + if ( currentValue !== this.positionRange.min && currentValue !== this.positionRange.max && + this.lastValue < tickValue && currentValue >= tickValue || this.lastValue > tickValue && currentValue <= tickValue ) { - // if enough time has passed since the last change - if ( phet.joist.elapsedTime - this.timeOfLastClick >= MIN_INTER_CLICK_TIME ) { - this.play(); - this.timeOfLastClick = phet.joist.elapsedTime; + // if enough time has passed since the last change + if ( phet.joist.elapsedTime - this.timeOfLastClick >= MIN_INTER_CLICK_TIME ) { + this.play(); + this.timeOfLastClick = phet.joist.elapsedTime; + } + break; } - break; } } diff --git a/js/common/view/sound/ViewSounds.ts b/js/common/view/sound/ViewSounds.ts index 005ce81b..222831da 100644 --- a/js/common/view/sound/ViewSounds.ts +++ b/js/common/view/sound/ViewSounds.ts @@ -29,8 +29,8 @@ class ViewSounds { readonly grabSoundClip: SoundClip; readonly releaseSoundClip: SoundClip; - readonly boundarySoundClip: SoundClip; - readonly tickMarkBumpSoundClip: SoundClip; + readonly boundarySoundClip: BoundarySoundClip; + readonly tickMarkBumpSoundClip: TickMarkBumpSoundClip; /** * @param {NumberProperty} tickMarkRangeProperty @@ -39,7 +39,7 @@ class ViewSounds { * @param {Object} [options] */ constructor( tickMarkRangeProperty: NumberProperty, tickMarkViewProperty: Property, - playTickMarkBumpSoundProperty: BooleanProperty, options: any ) { + playTickMarkBumpSoundProperty: BooleanProperty, options?: any ) { options = merge( { addSoundOptions: {