From 21f33ee6c3ccbe5c5deff2c938c1d429dd553665 Mon Sep 17 00:00:00 2001 From: samreid Date: Fri, 26 Feb 2021 07:39:43 -0700 Subject: [PATCH] Apply WebStorm formatting, see https://github.com/phetsims/phet-info/issues/155 --- js/model/ISLCModel.js | 50 +++++------ js/view/ISLCDragBoundsNode.js | 22 ++--- js/view/ISLCForceArrowNode.js | 40 ++++----- js/view/ISLCGridNode.js | 16 ++-- js/view/ISLCLegendNode.js | 24 +++--- js/view/ISLCObjectNode.js | 156 +++++++++++++++++----------------- js/view/ISLCPullerNode.js | 54 ++++++------ 7 files changed, 181 insertions(+), 181 deletions(-) diff --git a/js/model/ISLCModel.js b/js/model/ISLCModel.js index 3d46dd4..0cef347 100644 --- a/js/model/ISLCModel.js +++ b/js/model/ISLCModel.js @@ -22,7 +22,7 @@ const OBJECT_ONE = ISLCObjectEnum.OBJECT_ONE; const OBJECT_TWO = ISLCObjectEnum.OBJECT_TWO; class ISLCModel { - + /** * @param {number} forceConstant the appropriate force constant (e.g. G or k) * @param {ISLCObject} object1 - the first Mass or Charge object @@ -33,44 +33,44 @@ class ISLCModel { * @param {Object} [options] */ constructor( forceConstant, object1, object2, positionRange, tandem, options ) { - + options = merge( { snapObjectsToNearest: null, // {number|null} if defined, objects will snap to nearest value in model coordinates minSeparationBetweenObjects: 0.1 // in meters }, options ); - + assert && assert( object1.positionProperty.units === object2.positionProperty.units, 'units should be the same' ); - + // @public (read-only) this.leftObjectBoundary = positionRange.min; this.rightObjectBoundary = positionRange.max; - + // @public {Property.} - whether to display the force values this.showForceValuesProperty = new BooleanProperty( true, { tandem: tandem.createTandem( 'showForceValuesProperty' ), phetioDocumentation: 'Whether or not the force values should be displayed' } ); - + // @public this.object1 = object1; this.object2 = object2; - + // set the appropriate enum reference to each object. object1.enum = ISLCObjectEnum.OBJECT_ONE; object2.enum = ISLCObjectEnum.OBJECT_TWO; - + // @public // {Property.} - needed for adjusting alerts when an object moves as a result of a radius increase this.pushedObjectEnumProperty = new Property( null ); - + // @private this.snapObjectsToNearest = options.snapObjectsToNearest; this.minSeparationBetweenObjects = options.minSeparationBetweenObjects; this.forceConstant = forceConstant; - + // @public - emits an event when the model is updated by step this.stepEmitter = new Emitter(); - + // @public {Property.} - calculates the force based on changes to values and positions // objects are never destroyed, so forceProperty does not require disposal this.forceProperty = new DerivedProperty( [ @@ -84,7 +84,7 @@ class ISLCModel { units: 'N', phetioDocumentation: 'The force of one object on the other (in Newtons)' } ); - + // @private {Property.} - The distance between the two objects. Added for PhET-iO. this.separationProperty = new DerivedProperty( [ this.object1.positionProperty, @@ -95,45 +95,45 @@ class ISLCModel { units: object1.positionProperty.units, phetioDocumentation: 'The distance between the two objects\' centers' } ); - + const updateRange = object => { const maxPosition = this.getObjectMaxPosition( object ); const minPosition = this.getObjectMinPosition( object ); - + object.enabledRangeProperty.set( new Range( minPosition, maxPosition ) ); }; - + // pdom - necessary to reset the enabledRangeProperty to prevent object overlap, disposal not necessary // We need to update the available range for each object when the either's radius or position changes. Property.multilink( [ object1.positionProperty, object2.positionProperty ], () => { updateRange( object1 ); updateRange( object2 ); } ); - + // when sim is reset, we only reset the position properties of each object to their initial values // thus, there is no need to dispose of the listeners below this.object1.radiusProperty.link( () => { this.object1.radiusLastChanged = true; this.object2.radiusLastChanged = false; - + // update range if radius changed with "constant radius" setting (which didn't trigger other model updates) updateRange( object1 ); updateRange( object2 ); } ); - + this.object2.radiusProperty.link( () => { this.object2.radiusLastChanged = true; this.object1.radiusLastChanged = false; - + // update range if radius changed with "constant radius" setting (which didn't trigger other model updates) updateRange( object2 ); updateRange( object1 ); } ); - + // wire up logic to update the state of the pushedObjectEnumProperty const createPushedPositionListener = objectEnum => { return () => { - + // This conditional should only be hit if the mass has changed in addition to the position. Since the object's // valueProperty would be set in the previous frame, and then this frame's step function would update the // position. @@ -145,11 +145,11 @@ class ISLCModel { } }; }; - + // lazy link so we don't have a strange initial condition even though we haven't moved the pushers. object1.positionProperty.lazyLink( createPushedPositionListener( ISLCObjectEnum.OBJECT_ONE ) ); object2.positionProperty.lazyLink( createPushedPositionListener( ISLCObjectEnum.OBJECT_TWO ) ); - + // when the mass is lessened, there is no way that pushed an object, so set to null const massChangedListener = ( newMass, oldMass ) => { if ( oldMass > newMass ) { @@ -158,7 +158,7 @@ class ISLCModel { }; object1.valueProperty.link( massChangedListener ); object2.valueProperty.link( massChangedListener ); - + // reset after step is complete. this.stepEmitter.addListener( () => { this.object1.onStepEnd(); @@ -408,7 +408,7 @@ class ISLCModel { /** * Get whether or not the position of a mass was most recently changed based on the other pushing it. * @public - * + * * @returns {boolean} */ massWasPushed() { diff --git a/js/view/ISLCDragBoundsNode.js b/js/view/ISLCDragBoundsNode.js index 8add7da..0062cf8 100644 --- a/js/view/ISLCDragBoundsNode.js +++ b/js/view/ISLCDragBoundsNode.js @@ -14,7 +14,7 @@ import Node from '../../../scenery/js/nodes/Node.js'; import inverseSquareLawCommon from '../inverseSquareLawCommon.js'; class ISLCDragBoundsNode extends Node { - + /** * @param {ISLCModel} model * @param {Bounds2} layoutBounds @@ -22,29 +22,29 @@ class ISLCDragBoundsNode extends Node { * @param {Object} [options] */ constructor( model, layoutBounds, modelViewTransform, options ) { - + options = merge( { lineWidth: 2, object1Stroke: 'blue', object2Stroke: 'red' }, options ); - + super( options ); - + // Show the min/max positions for dragging the objects const verticalMin = layoutBounds.minY; const verticalMax = layoutBounds.height; const object1LineOptions = { stroke: options.object1Stroke, lineWidth: options.lineWidth }; const object2LineOptions = { stroke: options.object1Stroke, lineWidth: options.lineWidth }; - + // vertical lines (drawn from yMin to yMax) that will be positioned according to the draggable limits of each object const object1MinLine = new Line( 0, verticalMin, 0, verticalMax, object1LineOptions ); const object1MaxLine = new Line( 0, verticalMin, 0, verticalMax, object1LineOptions ); const object2MinLine = new Line( 0, verticalMin, 0, verticalMax, object2LineOptions ); const object2MaxLine = new Line( 0, verticalMin, 0, verticalMax, object2LineOptions ); - + this.children = [ object1MinLine, object2MinLine, object1MaxLine, object2MaxLine ]; - + let object1MinX; let object1MaxX; let object2MinX; @@ -55,20 +55,20 @@ class ISLCDragBoundsNode extends Node { model.object2.positionProperty, model.object2.radiusProperty ]; - + Property.multilink( objectProperties, () => { object1MinX = modelViewTransform.modelToViewX( model.getObjectMinPosition( model.object1 ) ); object1MinLine.x1 = object1MinX; object1MinLine.x2 = object1MinX; - + object1MaxX = modelViewTransform.modelToViewX( model.getObjectMaxPosition( model.object1 ) ); object1MaxLine.x1 = object1MaxX; object1MaxLine.x2 = object1MaxX; - + object2MinX = modelViewTransform.modelToViewX( model.getObjectMinPosition( model.object2 ) ); object2MinLine.x1 = object2MinX; object2MinLine.x2 = object2MinX; - + object2MaxX = modelViewTransform.modelToViewX( model.getObjectMaxPosition( model.object2 ) ); object2MaxLine.x1 = object2MaxX; object2MaxLine.x2 = object2MaxX; diff --git a/js/view/ISLCForceArrowNode.js b/js/view/ISLCForceArrowNode.js index 71a5239..fb6d32f 100644 --- a/js/view/ISLCForceArrowNode.js +++ b/js/view/ISLCForceArrowNode.js @@ -31,7 +31,7 @@ const ARROW_LENGTH = 8; // empirically determined const TEXT_OFFSET = 10; // empirically determined to make sure text does not go out of bounds class ISLCForceArrowNode extends Node { - + /** * @param {Range} arrowForceRange - the range in force magnitude * @param {Bounds2} layoutBounds @@ -41,21 +41,21 @@ class ISLCForceArrowNode extends Node { * @param {Object} [options] */ constructor( arrowForceRange, layoutBounds, label, otherObjectLabel, tandem, options ) { - + options = merge( { defaultDirection: DefaultDirection.LEFT, attractNegative: true, // if true, arrows will point towards each other if force is negative arrowNodeLineWidth: 0.25, - + // label options arrowLabelFont: new PhetFont( 16 ), arrowLabelFill: '#fff', arrowLabelStroke: null, forceReadoutDecimalPlaces: ISLCConstants.DECIMAL_NOTATION_PRECISION, // number of decimal places in force readout - + // arrow node arguments forceArrowHeight: 150, - + // arrow node options maxArrowWidth: 15, // max width of the arrow when when redrawn, in view coordinates - used in mapping function minArrowWidth: 0, // Some ISLC sims support an object value of zero, setting this to zero supports this case. @@ -65,21 +65,21 @@ class ISLCForceArrowNode extends Node { arrowStroke: null, arrowFill: '#fff', backgroundFill: 'black', - + // arrow mapping function options // By default, only use a single mapping function to go from force to arrow width, but with this option and // those below use two. mapArrowWidthWithTwoFunctions: false, - + // only if mapArrowWidthWithTwoFunctions is true forceThresholdPercent: 0, // the percent to switch mappings from the min to the main linear function. thresholdArrowWidth: 1 // This default is used by GFL(B) as a good in between the min/max arrow widths. }, options ); - + options.tandem = tandem; super( options ); - + // @private this.layoutBounds = layoutBounds; this.defaultDirection = options.defaultDirection; @@ -88,24 +88,24 @@ class ISLCForceArrowNode extends Node { this.otherObjectLabel = otherObjectLabel; this.scientificNotationMode = false; this.attractNegative = options.attractNegative; - + assert && options.mapArrowWidthWithTwoFunctions && assert( options.forceThresholdPercent !== 0, 'set forceThresholdPercent to map arrow width with two functions' ); - + const forceThreshold = arrowForceRange.min + ( arrowForceRange.getLength() * options.forceThresholdPercent ); - + // Maps the force value to the desired width of the arrow in view coordinates. This mapping can be done // two ways. The first is with a single function (when `options.mapArrowWidthWithTwoFunctions` is set to false). // If that is the case, this is the only mapping function. This is to support single mapping in CL and multi mapping // in GFL(B). See https://github.com/phetsims/inverse-square-law-common/issues/76 for details on the design. const mainForceToArrowWidthFunction = new LinearFunction( forceThreshold, arrowForceRange.max, options.mapArrowWidthWithTwoFunctions ? options.thresholdArrowWidth : options.minArrowWidth, options.maxArrowWidth, false ); - + // When `options.mapArrowWidthWithTwoFunctions` is true, this function will be used to map the arrow width // from the minimum to a specified percentage of the force range, see options.forceThresholdPercent. const minTwoForceToArrowWidthFunction = new LinearFunction( arrowForceRange.min, forceThreshold, options.minArrowWidth, options.thresholdArrowWidth, false ); - + /** * Map a force value to an arrow width * @param {number} forceValue @@ -115,7 +115,7 @@ class ISLCForceArrowNode extends Node { const linearFunction = forceValue < forceThreshold ? minTwoForceToArrowWidthFunction : mainForceToArrowWidthFunction; return linearFunction( forceValue ); }; - + // @public (read-only) - for layout, the label for the arrow this.arrowText = new RichText( '', { font: options.arrowLabelFont, @@ -128,7 +128,7 @@ class ISLCForceArrowNode extends Node { phetioDocumentation: 'This text updates from the model as the force changes, and cannot be edited.', textPropertyOptions: { phetioReadOnly: true } } ); - + // @private - tip and tail set in redrawArrow this.arrow = new ArrowNode( 0, -options.forceArrowHeight, 200, -options.forceArrowHeight, merge( { lineWidth: options.arrowNodeLineWidth, @@ -136,14 +136,14 @@ class ISLCForceArrowNode extends Node { fill: options.arrowFill, tandem: tandem.createTandem( 'arrowNode' ) }, _.pick( options, [ 'headHeight', 'headWidth', 'tailWidth' ] ) ) ); - + // @private - this.arrowTextBackground = new Rectangle( 0, 0, 1000, 1000, { fill: options.backgroundFill, opacity: .3 } ); + this.arrowTextBackground = new Rectangle( 0, 0, 1000, 1000, { fill: options.backgroundFill, opacity: .3 } ); this.addChild( this.arrowTextBackground ); - + this.addChild( this.arrowText ); this.addChild( this.arrow ); - + this.y = 0; } diff --git a/js/view/ISLCGridNode.js b/js/view/ISLCGridNode.js index a92d42e..6192273 100644 --- a/js/view/ISLCGridNode.js +++ b/js/view/ISLCGridNode.js @@ -14,7 +14,7 @@ import Path from '../../../scenery/js/nodes/Path.js'; import inverseSquareLawCommon from '../inverseSquareLawCommon.js'; class ISLCGridNode extends Path { - + /** * @param {number} deltaX - position step for the object in model coordinates * @param {Bounds2} layoutBounds - layout bounds of the ScreenView @@ -22,29 +22,29 @@ class ISLCGridNode extends Path { * @param {Object} [options] */ constructor( deltaX, layoutBounds, modelViewTransform, options ) { - + options = merge( { stroke: 'rgba( 0, 0, 0, 0.6 )' }, options ); - + const gridShape = new Shape(); - + // subtract 1 so grid aligns with model, see https://github.com/phetsims/inverse-square-law-common/issues/49 let gridPosition = modelViewTransform.viewToModelX( layoutBounds.minX - 1 ); const rightBoundary = modelViewTransform.viewToModelX( layoutBounds.maxX ); while ( gridPosition <= rightBoundary ) { - + // grid position in view coords const viewPosition = modelViewTransform.modelToViewX( gridPosition ); - + // draw the grid line gridShape.moveTo( viewPosition, layoutBounds.top ); gridShape.lineTo( viewPosition, layoutBounds.bottom ); - + // move to the next position gridPosition += deltaX; } - + super( gridShape, { stroke: options.stroke, lineWidth: 1 diff --git a/js/view/ISLCLegendNode.js b/js/view/ISLCLegendNode.js index 7f3b0b4..e04b524 100644 --- a/js/view/ISLCLegendNode.js +++ b/js/view/ISLCLegendNode.js @@ -15,23 +15,23 @@ import Text from '../../../scenery/js/nodes/Text.js'; import inverseSquareLawCommon from '../inverseSquareLawCommon.js'; class ISLCLegendNode extends Node { - + /** * @param {number} width (in view coordinates) * @param {string} labelString * @param {Object} [options] */ constructor( width, labelString, options ) { - + options = merge( { fill: 'rgb(0,255,0)', fontSize: 14, maxWidth: 85 }, options ); - + super(); this.center.subtractXY( 0, 10 ); - + // @public (read-only) - layout for this type is often relative to this line this.legendArrowLine = new ArrowNode( 0, 100, width, 100, { fill: options.fill, @@ -42,9 +42,9 @@ class ISLCLegendNode extends Node { lineWidth: 1, doubleHead: true } ); - + this.addChild( this.legendArrowLine ); - + // create left and right end lines const endLinesBottom = this.legendArrowLine.bottom + 2.5; const endLinesTop = endLinesBottom - 10; @@ -52,26 +52,26 @@ class ISLCLegendNode extends Node { stroke: options.fill, lineWidth: 1.25 }; - + const leftEndLine = new Line( this.legendArrowLine.left, endLinesBottom, this.legendArrowLine.left, endLinesTop, endLinesOptions ); const rightEndLine = new Line( this.legendArrowLine.right, endLinesBottom, this.legendArrowLine.right, endLinesTop, endLinesOptions ); - + this.legendArrowLine.addChild( leftEndLine ); this.legendArrowLine.addChild( rightEndLine ); - + const legendLabel = new Text( labelString, { fill: options.fill, fontSize: 14, maxWidth: 65 } ); - + this.addChild( legendLabel ); this.mutate( options ); - + // positioning legendLabel.centerX = this.localBounds.centerX; legendLabel.bottom = this.localBounds.maxY - 18; - + this.legendArrowLine.centerX = this.localBounds.centerX; this.legendArrowLine.bottom = this.localBounds.maxY; } diff --git a/js/view/ISLCObjectNode.js b/js/view/ISLCObjectNode.js index df38240..1d7b304 100644 --- a/js/view/ISLCObjectNode.js +++ b/js/view/ISLCObjectNode.js @@ -75,34 +75,34 @@ class ISLCObjectNode extends Node { * @mixes AccessibleSlider */ constructor( model, object, layoutBounds, modelViewTransform, alertManager, forceDescriber, positionDescriber, config ) { - + config = merge( { label: null, // {string} @required otherObjectLabel: null, // {string} @required defaultDirection: DefaultDirection.LEFT, - + // {boolean} - if true, arrows will point towards each other if forces is negative. Used by the puller and arrow nodes attractNegative: false, snapToNearest: null, // {number} if present, object node will snap to the nearest snapToNearest value on drag stepSize: null, // {number} step size when moving the object keyboard. By default based on snapToNearest, see below. - + arrowColor: '#66f', // color of vertical line y: 250, - + forceArrowHeight: 150, // height of arrow in view coordinates - + objectColor: null, // {{string}} @required - description of sphere for self-voicing content - + // phet-io tandem: Tandem.REQUIRED, - + // {Property[]} - Properties that need to be monitored to successfully update this Node's PDOM descriptions additionalA11yDependencies: [] }, config ); - + // separate call because of the use of a config value from the above defaults config = merge( { - + // options passed to ISLCForceArrowNode, filled in below arrowNodeOptions: { attractNegative: config.attractNegative, @@ -110,57 +110,57 @@ class ISLCObjectNode extends Node { forceArrowHeight: config.forceArrowHeight, forceReadoutDecimalPlaces: ISLCConstants.DECIMAL_NOTATION_PRECISION // number of decimal places in force readout }, - + // options for the RichText label on the object circle labelOptions: { fill: 'black', font: new PhetFont( { size: 12 } ), - + pickable: false, maxWidth: LABEL_MAX_WIDTH, centerX: LABEL_CENTER_X, - + tandem: config.tandem.createTandem( 'labelText' ) }, - + // options passed to the PullerNode, filled in below pullerNodeOptions: { attractNegative: config.attractNegative } }, config ); - + // use snapToNearest if stepSize is not provided if ( config.stepSize === null ) { assert && assert( config.snapToNearest, 'snapToNearest is required if stepSize is not provided.' ); config.stepSize = config.snapToNearest * 2; } - + assert && assert( config.label, 'required param' ); assert && assert( config.otherObjectLabel, 'required param' ); assert && assert( alertManager instanceof ISLCAlertManager ); - + super( { containerTagName: 'div', tandem: config.tandem } ); - + this.accessibleName = PositionDescriber.getObjectLabelPositionText( config.label ); - + // @protected this.layoutBounds = layoutBounds; this.objectModel = object; this.model = model; // used in abstract method implementations by children. this.modelViewTransform = modelViewTransform; - + // @private this.forceDescriber = forceDescriber; - + // @public - which object this instance is (one or two) this.enum = object === model.object1 ? ISLCObjectEnum.OBJECT_ONE : ISLCObjectEnum.OBJECT_TWO; - + // the full range of force for the arrow node (note: this is distinct) const arrowForceRange = new Range( model.getMinForceMagnitude(), model.getMaxForce() ); - + // @protected - arrow node this.arrowNode = new ISLCForceArrowNode( arrowForceRange, @@ -170,23 +170,23 @@ class ISLCObjectNode extends Node { config.tandem.createTandem( 'forceDisplayNode' ), config.arrowNodeOptions ); - + // PROTOTYPE a11y code for self-voicing features if ( phet.chipper.queryParameters.supportsSelfVoicing ) { const arrowHitListener = () => { let objectResponse; if ( model.showForceValuesProperty.get() ) { if ( ISLCQueryParameters.selfVoicingVersion === 1 ) { - + // custom self voicing string objectResponse = forceDescriber.getSelfVoicingForceVectorMagnitudeText( config.label, config.otherObjectLabel ); } else { - + // string directly from PDOM strings objectResponse = forceDescriber.getForceVectorMagnitudeText( config.label, config.otherObjectLabel ); } - + // for the self-voicing (regardless of version), we always want to include arrow size // description in the self-voicing content objectResponse = StringUtils.fillIn( forceArrowSizePatternString, { @@ -195,19 +195,19 @@ class ISLCObjectNode extends Node { } ); } else { - + // custom response for self voicing when force values are hidden objectResponse = forceDescriber.getSelfVoicingQualitativeForceVectorText( config.otherObjectLabel ); } - + const helpText = StringUtils.fillIn( summaryInteractionHintPatternString, { massOrCharge: 'mass' } ); - + const response = levelSpeakerModel.collectResponses( objectResponse, null, helpText ); phet.joist.sim.selfVoicingUtteranceQueue.addToBack( response ); }; - + // @public (read-only) - wraps the arrow node that receives hit detection // anywhere within so that this.selfVoicingWrapper = new SelfVoicingWrapperNode( this.arrowNode, { @@ -217,42 +217,42 @@ class ISLCObjectNode extends Node { } } ); } - + // set y position for the arrow this.arrowNode.y = config.y - config.forceArrowHeight; - + // @private - the puller node this.pullerNode = new ISLCPullerNode( new Range( model.getMinForce(), model.getMaxForce() ), config.pullerNodeOptions ); - + if ( config.defaultDirection === DefaultDirection.RIGHT ) { this.pullerNode.scale( -1, 1 ); } - + // @protected - a parent node that applies the drag handler this.dragNode = new Node( { cursor: 'pointer' } ); - + // the 'object' - a shaded circle const radius = modelViewTransform.modelToViewDeltaX( object.radiusProperty.get() ); - + // @protected - the object this.objectCircle = new Circle( radius ); - + // PROTOTYPE a11y code, to support self-voicing features if ( phet.chipper.queryParameters.supportsSelfVoicing ) { assert && assert( config.objectColor, 'required param, if testing self voicing features' ); - + this.addInputListener( new SelfVoicingInputListener( { onFocusIn: () => { - + // special behavior if the hit is from a keyboard const interactionHint = selfVoicingLevelsMoveSpheresHintString; const objectResponse = positionDescriber.getSelfVoicingDistanceDescription( config.label, config.otherObjectLabel ); - + if ( phet.chipper.queryParameters.supportsSelfVoicing ) { const response = levelSpeakerModel.collectResponses( objectResponse, null, interactionHint ); phet.joist.sim.selfVoicingUtteranceQueue.addToBack( response ); @@ -261,31 +261,31 @@ class ISLCObjectNode extends Node { highlightTarget: this } ) ); } - + this.dragNode.addChild( this.pullerNode ); this.dragNode.addChild( this.objectCircle ); - + // @public - for ruler regions // Small black dot where vertical arrow line connects to the object this.centerPoint = new Circle( 2, { fill: '#000' } ); this.dragNode.addChild( this.centerPoint ); - + this.labelText = new RichText( config.label, config.labelOptions ); - + this.dragNode.addChild( this.labelText ); this.labelText.boundsProperty.lazyLink( () => { this.labelText.centerX = this.objectCircle.centerX; } ); - + this.addChild( this.dragNode ); - + // @private this.y = config.y; // TODO: is this needed? - + // Added for PhET-iO as a way to hide the dashed lines. const centerOfMassLineNode = new Node( { tandem: config.tandem.createTandem( 'centerOfMassLineNode' ) } ); this.addChild( centerOfMassLineNode ); - + // The marker line, connecting the arrow to the object. The first one is for the shadow so that // it is visible on top of the object const markerLineShape = new Shape(); @@ -304,15 +304,15 @@ class ISLCObjectNode extends Node { lineWidth: 2 } ); centerOfMassLineNode.addChild( markerLineShapeTop ); - + let clickOffset; - + let oldPosition = object.positionProperty.get(); let previousSeparation = model.separationProperty.get(); - + // reusable utterance to prevent a pile-up of alerts while the object moves const separationUtterance = new SelfVoicingUtterance(); - + // @public - so that events can be forwarded to this DragListener in the // case of alternative input const selfVoicingDragUtterance = new SelfVoicingUtterance( { @@ -325,22 +325,22 @@ class ISLCObjectNode extends Node { start: event => { clickOffset = this.dragNode.globalToParentPoint( event.pointer.point ).x - event.target.x; object.isDraggingProperty.value = true; - + oldPosition = object.positionProperty.get(); - + if ( phet.chipper.queryParameters.supportsSelfVoicing ) { - + // the initial dragging alert does not use the utterance because it must be assertive and // should interrupt any other utterance being spoken // special behavior if the hit is from a keyboard const interactionHint = selfVoicingLevelsMoveSpheresHintString; const distanceDescription = positionDescriber.getSelfVoicingDistanceDescription( config.label, config.otherObjectLabel ); - + const objectResponse = StringUtils.fillIn( '{{grabbed}}. {{response}}', { grabbed: grabbedString, response: distanceDescription } ); - + const response = levelSpeakerModel.collectResponses( objectResponse, null, interactionHint ); const dragStartUtterance = new SelfVoicingUtterance( { alert: response @@ -349,39 +349,39 @@ class ISLCObjectNode extends Node { } }, drag: event => { - + // drag position relative to the pointer pointer start position and convert to model coordinates let x = modelViewTransform.viewToModelX( this.globalToParentPoint( event.pointer.point ).x - clickOffset ); - + // absolute drag bounds based on model // see method descriptions for details const xMax = model.getObjectMaxPosition( object ); const xMin = model.getObjectMinPosition( object ); - + // apply limitations and update position x = Math.max( Math.min( x, xMax ), xMin ); // limited value of x (by boundary) in model coordinates - + // snapToGrid method dynamically checks whether to snap or not object.positionProperty.set( model.snapToGrid( x ) ); - + if ( phet.chipper.queryParameters.supportsSelfVoicing ) { const distanceDescription = positionDescriber.getSelfVoicingDistanceDescriptionWithoutLabel( config.otherObjectLabel ); - + // only speak something if the positions have changed during drag if ( oldPosition !== object.positionProperty.get() ) { const forceChangeText = this.forceDescriber.getVectorChangeText( this.objectModel ); - + if ( model.separationProperty.get() < previousSeparation ) { separationUtterance.alert = 'Closer'; } else { separationUtterance.alert = 'Farther away'; } - + phet.joist.sim.selfVoicingUtteranceQueue.addToBack( separationUtterance ); previousSeparation = model.separationProperty.get(); oldPosition = object.positionProperty.get(); - + selfVoicingDragUtterance.alert = levelSpeakerModel.collectResponses( distanceDescription, forceChangeText ); phet.joist.sim.selfVoicingUtteranceQueue.addToBack( selfVoicingDragUtterance ); } @@ -393,31 +393,31 @@ class ISLCObjectNode extends Node { tandem: config.tandem.createTandem( 'dragListener' ) } ); this.dragNode.addInputListener( this.dragListener ); - + const boundRedrawForce = this.redrawForce.bind( this ); model.showForceValuesProperty.lazyLink( boundRedrawForce ); object.radiusProperty.lazyLink( boundRedrawForce ); object.valueProperty.lazyLink( boundRedrawForce ); model.forceProperty.lazyLink( boundRedrawForce ); - + object.baseColorProperty.link( baseColor => { this.updateGradient( baseColor ); if ( config.attractNegative ) { markerLineShapeTop.stroke = getUpdatedFill( object.valueProperty.get() ); } } ); - + // on reset, no objects are destroyed and properties are set to initial values // no need to dispose of any of the below listeners object.positionProperty.link( property => { - + // position this node and its force arrow with label const transformedValue = modelViewTransform.modelToViewX( property ); this.x = transformedValue; this.arrowNode.x = transformedValue; this.redrawForce(); } ); - + const accessibleSliderOptions = { keyboardStep: config.stepSize, shiftKeyboardStep: config.snapToNearest, @@ -434,15 +434,15 @@ class ISLCObjectNode extends Node { endDrag: () => { object.isDraggingProperty.value = false; this.redrawForce(); - + const distanceDescription = positionDescriber.getSelfVoicingDistanceDescriptionWithoutLabel( config.otherObjectLabel ); - + // only speak force change if it has changed let forceChangeText = ''; if ( oldPosition !== object.positionProperty.get() ) { forceChangeText = this.forceDescriber.getVectorChangeText( this.objectModel ); } - + if ( phet.chipper.queryParameters.supportsSelfVoicing ) { const response = levelSpeakerModel.collectResponses( distanceDescription, forceChangeText ); phet.joist.sim.selfVoicingUtteranceQueue.addToBack( response ); @@ -454,12 +454,12 @@ class ISLCObjectNode extends Node { return positionChanged ? forceDescriber.getVectorChangeText( object ) : forceDescriber.getPositionUnchangedAlertText( object ); }, a11yCreateAriaValueText: positionDescriber.getPositionAriaValueTextCreator( this.enum ), - + // This object's PDOM description also depends on the position of the other object, so include it here. a11yDependencies: config.additionalA11yDependencies.concat( object === model.object1 ? [ model.object2.positionProperty ] : [ model.object1.positionProperty ] ) }; - + // pdom - initialize the accessible slider, which makes this Node act like an accessible range input this.initializeAccessibleSlider( object.positionProperty, @@ -467,7 +467,7 @@ class ISLCObjectNode extends Node { new BooleanProperty( true ), // always enabled accessibleSliderOptions ); - + // for layering purposes, we assume that the ScreenView will add the arrow node and label - by the // time the sim is stepped, make sure that the arrows are added to the view if ( assert ) { @@ -475,7 +475,7 @@ class ISLCObjectNode extends Node { if ( this.arrowNode.parents.length === 0 ) { throw new Error( 'ArrowNode should be added to the view in inverse-square-law-common sim screen view' ); } - + // no need to keep checking model.stepEmitter.removeListener( checkForArrowAdded ); }; diff --git a/js/view/ISLCPullerNode.js b/js/view/ISLCPullerNode.js index 011a699..cfb5b0e 100644 --- a/js/view/ISLCPullerNode.js +++ b/js/view/ISLCPullerNode.js @@ -25,13 +25,13 @@ import inverseSquareLawCommon from '../inverseSquareLawCommon.js'; const IMAGE_SCALE = 0.45; class ISLCPullerNode extends Node { - + /** * @param {RangeWithValue} forceRange - range of forces, used for determining the visible pullImage * @param {Object} [options] */ constructor( forceRange, options ) { - + options = merge( { ropeLength: 50, shadowMinWidth: 32, @@ -40,110 +40,110 @@ class ISLCPullerNode extends Node { displayShadow: true, atomicScale: false }, options ); - + super(); - + let pullImages = ISLCPullerImages.pullImages; let pushImages = ISLCPullerImages.pushImages; let zeroForceImage = ISLCPullerImages.zeroForceImage; - + // set atomic pullers if on the atomic screen if ( options.atomicScale ) { pullImages = ISLCPullerImages.atomicPullImages; pushImages = ISLCPullerImages.atomicPushImages; zeroForceImage = ISLCPullerImages.atomicZeroForceImage; } - + // @private this.pullerPusherImages = pullImages; - + // @public this.touchAreaBounds = new Bounds2( 0, 0, 0, 0 ); - + // used to ensure that small non-zero forces do not map to the zero force puller (see lines 130-132) let zeroForceIndex = null; - + // if in coulomb's law, add pusher and zero force images in proper order if ( options.attractNegative ) { zeroForceIndex = pushImages.length; this.pullerPusherImages = pushImages.concat( zeroForceImage ).concat( pullImages ); } - + // function that maps the visible image to the model force value const forceToImage = new LinearFunction( forceRange.min, forceRange.max, 0, this.pullerPusherImages.length - 1, true ); - + // function to dynamically move the position of the shadow under the puller const indexToShadowOffset = new LinearFunction( 0, this.pullerPusherImages.length - 1, -4, 6 ); - + // function that maps the size of the shadow to the force value const forceToShadowWidth = new LinearFunction( forceRange.min, forceRange.max, options.shadowMinWidth, options.shadowMaxWidth, true ); - + // parent node for all puller images and the rope const pullerGroupNode = new Node(); - + // the optional shadow node under the pullers - a circle scaled down vertically to look elliptical const shadowNode = new Circle( 10, { fill: '#777', scale: new Vector2( 1, 0.20 ) } ); - + // create each of the puller/pusher image nodes const images = []; let i; for ( i = 0; i < this.pullerPusherImages.length; i++ ) { const pullerImage = new Image( this.pullerPusherImages[ i ] ); - + // puller images are much larger than pushers, so we need to scale it down pullerImage.scale( IMAGE_SCALE, IMAGE_SCALE ); - + images.push( pullerImage ); } - + pullerGroupNode.addChild( new Path( Shape.lineSegment( -options.ropeLength, 0, 0, 0 ), { stroke: '#666', lineWidth: 2 } ) ); - + // set the layout for the images for ( i = 0; i < this.pullerPusherImages.length; i++ ) { pullerGroupNode.addChild( images[ i ] ); images[ i ].bottom = 42; images[ i ].right = -options.ropeLength; images[ i ].setVisible( false ); - + // the pullImages grow in width as they animate, but their hands stay in the same position, so make sure that // they are still grabbing the rope images[ i ].right += 0.1 * images[ i ].width; } - + // shadow first so it is behind the pullers options.displayShadow && this.addChild( shadowNode ); this.addChild( pullerGroupNode ); const self = this; // @public - set the visibility of the image corresponding to the current force value this.setPull = function( force, offsetX ) { - + if ( options.attractNegative ) { force *= -1; } - + // from the force value, get an index for the visible image let index = Utils.roundSymmetric( forceToImage( force ) ); - + if ( force !== 0 && index === zeroForceIndex ) { index += ( force > 0 ) ? 1 : -1; } - + for ( let i = 0; i < this.pullerPusherImages.length; i++ ) { images[ i ].setVisible( i === index ); } pullerGroupNode.x = -offsetX; - + // scale the shadow and place it under the visible image shadowNode.radius = forceToShadowWidth( force ) / 2; shadowNode.right = images[ index ].right - offsetX + Utils.roundSymmetric( indexToShadowOffset( index ) ); shadowNode.centerY = images[ index ].bottom; - + // configure pointer area // NOTE: the rope is not included in the draggable node, so we expose the puller bounds here for setting // the touch and mouse areas in ISLCObjectNode