diff --git a/forces-and-motion-basics_en.html b/forces-and-motion-basics_en.html index a3ed0372..65a115e3 100644 --- a/forces-and-motion-basics_en.html +++ b/forces-and-motion-basics_en.html @@ -46,6 +46,11 @@ "phet-io", "adapted-from-phet" ], + "simFeatures": { + "supportedRegionsAndCultures": [ + "usa" + ] + }, "simulation": true, "phet-io": { "validation": false, diff --git a/js/forces-and-motion-basics-main.js b/js/forces-and-motion-basics-main.js index b38ceb25..137312d9 100644 --- a/js/forces-and-motion-basics-main.js +++ b/js/forces-and-motion-basics-main.js @@ -14,10 +14,12 @@ import { Image } from '../../scenery/js/imports.js'; import Tandem from '../../tandem/js/Tandem.js'; import accelerationIcon_png from '../images/accelerationIcon_png.js'; import frictionIcon_png from '../images/frictionIcon_png.js'; -import motionIcon_png from '../images/motionIcon_png.js'; import tugIcon_png from '../images/tugIcon_png.js'; import ForcesAndMotionBasicsStrings from './ForcesAndMotionBasicsStrings.js'; import MotionScreen from './motion/MotionScreen.js'; +import PreferencesModelSingleton from './motion/PreferencesModelSingleton.js'; +import MassPlayerImages from './motion/view/MassPlayerImages.js'; +import MotionScreenIcon from './motion/view/MotionScreenIcon.js'; import NetForceModel from './netforce/model/NetForceModel.js'; import NetForceScreenView from './netforce/view/NetForceScreenView.js'; @@ -27,6 +29,7 @@ const forcesAndMotionBasicsTitleStringProperty = ForcesAndMotionBasicsStrings[ ' const tandem = Tandem.ROOT; const simOptions = { + preferencesModel: PreferencesModelSingleton, credits: { leadDesign: 'Ariel Paul, Noah Podolefsky', graphicArts: 'Mariah Hermsmeyer, Sharon Siman-Tov', @@ -63,9 +66,7 @@ simLauncher.launch( () => { const motionScreen = new MotionScreen( 'motion', motionScreenTandem, { name: ForcesAndMotionBasicsStrings.motionStringProperty, - homeScreenIcon: new ScreenIcon( new Image( motionIcon_png, { - tandem: motionScreenTandem.createTandem( 'icon' ) - } ), screenIconOptions ) + homeScreenIcon: new MotionScreenIcon( MassPlayerImages.MASS_PLAYER_PORTRAYALS, screenIconOptions, motionScreenTandem ) } ); const frictionScreen = new MotionScreen( 'friction', frictionScreenTandem, { diff --git a/js/motion/PreferencesModelSingleton.js b/js/motion/PreferencesModelSingleton.js new file mode 100644 index 00000000..e42cfaac --- /dev/null +++ b/js/motion/PreferencesModelSingleton.js @@ -0,0 +1,20 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * Preferences model as a singleton, so it can be accessed by the HumanTypeEnum. + * + * @author Luisa Vargas + */ + +import PreferencesModel from '../../../joist/js/preferences/PreferencesModel.js'; +import forcesAndMotionBasics from '../forcesAndMotionBasics.js'; +import MassPlayerImages from './view/MassPlayerImages.js'; + +const PreferencesModelSingleton = new PreferencesModel( { + localizationOptions: { + portrayals: MassPlayerImages.MASS_PLAYER_PORTRAYALS + } +} ); + +forcesAndMotionBasics.register( 'PreferencesModelSingleton', PreferencesModelSingleton ); +export default PreferencesModelSingleton; \ No newline at end of file diff --git a/js/motion/model/HumanTypeEnum.js b/js/motion/model/HumanTypeEnum.js new file mode 100644 index 00000000..87c873d8 --- /dev/null +++ b/js/motion/model/HumanTypeEnum.js @@ -0,0 +1,67 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * HumanTypeEnum identifies the human object type and sets the holding, standing, or sitting image for the human. + * + * @author Luisa Vargas + */ + +import MappedProperty from '../../../../axon/js/MappedProperty.js'; +import EnumerationValue from '../../../../phet-core/js/EnumerationValue.js'; +import Enumeration from '../../../../phet-core/js/Enumeration.js'; +import forcesAndMotionBasics from '../../forcesAndMotionBasics.js'; +import PreferencesModelSingleton from '../PreferencesModelSingleton.js'; + +class HumanTypeEnum extends EnumerationValue { + + static GIRL = new HumanTypeEnum( 'girl' ); + static MAN = new HumanTypeEnum( 'man' ); + + static enumeration = new Enumeration( HumanTypeEnum ); + + /** + * @param {string} humanType + */ + constructor( humanType ) { + super(); + + this.holdingImageProperty = new MappedProperty( PreferencesModelSingleton.localizationModel.regionAndCulturePortrayalProperty, { + map: portrayal => { + if ( humanType === 'girl' ) { + return portrayal.girlHolding; + } + else { + assert && assert( humanType === 'man', 'Human type must be girl, or man, but it is ', humanType ); + return portrayal.manHolding; + } + } + } ); + + this.standingImageProperty = new MappedProperty( PreferencesModelSingleton.localizationModel.regionAndCulturePortrayalProperty, { + map: portrayal => { + if ( humanType === 'girl' ) { + return portrayal.girlStanding; + } + else { + assert && assert( humanType === 'man', 'Human type must be girl, or man, but it is ', humanType ); + return portrayal.manStanding; + } + } + } ); + + this.sittingImageProperty = new MappedProperty( PreferencesModelSingleton.localizationModel.regionAndCulturePortrayalProperty, { + map: portrayal => { + if ( humanType === 'girl' ) { + return portrayal.girlSitting; + } + else { + assert && assert( humanType === 'man', 'Human type must be girl, or man, but it is ', humanType ); + return portrayal.manSitting; + } + } + } ); + } +} + +forcesAndMotionBasics.register( 'HumanTypeEnum', HumanTypeEnum ); +export default HumanTypeEnum; \ No newline at end of file diff --git a/js/motion/model/Item.js b/js/motion/model/Item.js index 9bda2521..750631ab 100644 --- a/js/motion/model/Item.js +++ b/js/motion/model/Item.js @@ -19,6 +19,7 @@ import IOType from '../../../../tandem/js/types/IOType.js'; import ObjectLiteralIO from '../../../../tandem/js/types/ObjectLiteralIO.js'; import ReferenceIO from '../../../../tandem/js/types/ReferenceIO.js'; import forcesAndMotionBasics from '../../forcesAndMotionBasics.js'; +import HumanTypeEnum from './HumanTypeEnum.js'; class Item extends PhetioObject { @@ -26,7 +27,7 @@ class Item extends PhetioObject { * Constructor for Item * * @param {MotionModel || NetForceModel} context - model context in which this item exists - * @param {string} name - string describing this type of item + * @param {string | HumanTypeEnum } name - string describing this type of item, or HumanTypeEnum of this human item * @param {Tandem} tandem * @param {image} image - image from the 'image!' plugin, representing the item * @param {number} mass - model mass of the item @@ -47,20 +48,36 @@ class Item extends PhetioObject { phetioState: false } ); - this.name = name; + this.name = typeof name === 'string' ? name : name.name.toLowerCase(); + + // Set the standing, sitting, and holding image properties if item is human + let standingImageProperty; + let sittingImageProperty; + let holdingImageProperty; + if ( name === HumanTypeEnum.GIRL ) { + standingImageProperty = HumanTypeEnum.GIRL.standingImageProperty; + sittingImageProperty = HumanTypeEnum.GIRL.sittingImageProperty; + holdingImageProperty = HumanTypeEnum.GIRL.holdingImageProperty; + } + else if ( name === HumanTypeEnum.MAN ) { + standingImageProperty = HumanTypeEnum.MAN.standingImageProperty; + sittingImageProperty = HumanTypeEnum.MAN.sittingImageProperty; + holdingImageProperty = HumanTypeEnum.MAN.holdingImageProperty; + } //Non-observable properties this.initialX = x; this.initialY = y; - this.image = image; this.mass = mass; this.pusherInset = pusherInset; - this.sittingImageNode = sittingImage; - this.holdingImage = holdingImage; this.context = context; this.mystery = mystery; this.homeScale = homeScale || 1.0; + this.imageProperty = typeof name === 'string' ? new Property( image ) : standingImageProperty; + this.sittingImageProperty = typeof name === 'string' ? new Property( sittingImage ) : sittingImageProperty; + this.holdingImageProperty = typeof name === 'string' ? new Property( holdingImage ) : holdingImageProperty; + // @public - the position of the item this.positionProperty = new Vector2Property( new Vector2( x, y ), { tandem: tandem.createTandem( 'positionProperty' ) @@ -112,17 +129,12 @@ class Item extends PhetioObject { this.context.directionProperty.link( direction => { //only change directions if on the board, and always choose one of left/right, and only for people - if ( this.onBoardProperty.get() && direction !== 'none' && sittingImage ) { + if ( this.onBoardProperty.get() && direction !== 'none' && ( name === HumanTypeEnum.GIRL || name === HumanTypeEnum.MAN ) ) { this.directionProperty.set( direction ); } } ); } - //For unknown reasons, the trash can is not centered when drawn, so we make up for it with a workaround here - get centeringOffset() { - return this.image === 'trashCan.png' ? 5 : 0; - } - // @public - Return true if the arms should be up (for a human) armsUp() { return this.context.draggingItems().length > 0 || this.context.isItemStackedAbove( this ); diff --git a/js/motion/model/MotionModel.js b/js/motion/model/MotionModel.js index 81683c86..03ebcf69 100644 --- a/js/motion/model/MotionModel.js +++ b/js/motion/model/MotionModel.js @@ -21,15 +21,10 @@ import crate_png from '../../../images/crate_png.js'; import fridge_png from '../../../images/fridge_png.js'; import mysteryObject01_png from '../../../images/mysteryObject01_png.js'; import waterBucket_png from '../../../images/waterBucket_png.js'; -import girlHolding_png from '../../../mipmaps/girlHolding_png.js'; -import girlSitting_png from '../../../mipmaps/girlSitting_png.js'; -import girlStanding_png from '../../../mipmaps/girlStanding_png.js'; -import manHolding_png from '../../../mipmaps/manHolding_png.js'; -import manSitting_png from '../../../mipmaps/manSitting_png.js'; -import manStanding_png from '../../../mipmaps/manStanding_png.js'; import trashCan_png from '../../../mipmaps/trashCan_png.js'; import forcesAndMotionBasics from '../../forcesAndMotionBasics.js'; import MotionConstants from '../MotionConstants.js'; +import HumanTypeEnum from './HumanTypeEnum.js'; import Item from './Item.js'; class MotionModel { @@ -235,8 +230,8 @@ class MotionModel { const fridge = new Item( this, 'fridge', tandem.createTandem( 'fridge' ), fridge_png, 200, leftmostItemXLeft, 437, 0.8, 1.1, 4 ); const crate1 = new Item( this, 'crate1', tandem.createTandem( 'crate1' ), crate_png, 50, leftmostItemXLeft + crate1Spacing, 507, 0.5 ); const crate2 = new Item( this, 'crate2', tandem.createTandem( 'crate2' ), crate_png, 50, leftmostItemXLeft + crate1Spacing + crate2Spacing, 507, 0.5 ); - const girl = new Item( this, 'girl', tandem.createTandem( 'girl' ), girlStanding_png, 40, leftmostItemXRight, 465, 0.6, 1.0, 4.2, girlSitting_png, girlHolding_png[ 1 ].img ); - const man = new Item( this, 'man', tandem.createTandem( 'man' ), manStanding_png, 80, leftmostItemXRight + manSpacing, 428, 0.6, 0.92, 5, manSitting_png, manHolding_png ); + const girl = new Item( this, HumanTypeEnum.GIRL, tandem.createTandem( 'girl' ), undefined, 40, leftmostItemXRight, 465, 0.6, 1.0, 4.2 ); + const man = new Item( this, HumanTypeEnum.MAN, tandem.createTandem( 'man' ), undefined, 80, leftmostItemXRight + manSpacing, 428, 0.6, 0.92, 5 ); const trashCan = new Item( this, 'trash', tandem.createTandem( 'trash' ), trashCan_png, 100, leftmostItemXRight + manSpacing + trashSpacing, 496, 0.7, 1.0, 5 ); const mysteryBox = new Item( this, 'mystery', tandem.createTandem( 'mystery' ), mysteryObject01_png, 50, leftmostItemXRight + manSpacing + trashSpacing + mysterySpacing, 513, 0.3, 1.0, undefined, undefined, undefined, true ); const bucket = new Item( this, 'bucket', tandem.createTandem( 'bucket' ), waterBucket_png, 100, leftmostItemXRight + manSpacing + trashSpacing + mysterySpacing + bucketSpacing, 547 + -35, 0.68, 1.0, 8 ); @@ -312,7 +307,7 @@ class MotionModel { for ( let i = 0; i < this.stackObservableArray.length; i++ ) { const size = this.view.getSize( this.stackObservableArray.get( i ) ); sumHeight += size.height; - this.stackObservableArray.get( i ).animateTo( this.view.layoutBounds.width / 2 - size.width / 2 + this.stackObservableArray.get( i ).centeringOffset, ( this.skateboard ? 334 : 360 ) - sumHeight, 'stack' );//TODO: factor out this code for layout, which is duplicated in MotionTab.topOfStack https://github.com/phetsims/tasks/issues/1129 + this.stackObservableArray.get( i ).animateTo( this.view.layoutBounds.width / 2 - size.width / 2, ( this.skateboard ? 334 : 360 ) - sumHeight, 'stack' );//TODO: factor out this code for layout, which is duplicated in MotionTab.topOfStack https://github.com/phetsims/tasks/issues/1129 } } diff --git a/js/motion/view/ItemNode.js b/js/motion/view/ItemNode.js index 00cead94..8fe72d70 100644 --- a/js/motion/view/ItemNode.js +++ b/js/motion/view/ItemNode.js @@ -14,6 +14,7 @@ import { Image, Node, Rectangle, SimpleDragHandler, Text } from '../../../../sce import Tandem from '../../../../tandem/js/Tandem.js'; import forcesAndMotionBasics from '../../forcesAndMotionBasics.js'; import ForcesAndMotionBasicsStrings from '../../ForcesAndMotionBasicsStrings.js'; +import PreferencesModelSingleton from '../PreferencesModelSingleton.js'; const pattern0MassUnitsKilogramsString = ForcesAndMotionBasicsStrings.pattern[ '0massUnitsKilograms' ]; @@ -27,13 +28,14 @@ class ItemNode extends Node { * @param {MotionModel} model the entire model for the containing screen * @param {MotionScreenView} motionView the entire view for the containing screen * @param {Item} item the corresponding to this ItemNode - * @param {Image} normalImage the phet.scenery.Image to show for this node - * @param {Image} sittingImage optional image for when the person is sitting down - * @param {Image} holdingImage optional image for when the person is holding an object + * @param {Property} normalImageProperty property for the phet.scenery.Image to show for this node + * @param {Property} sittingImageProperty property fot optional sitting image for when the person is sitting down + * @param {Property} holdingImageProperty property for optional holding image for when the person is holding an object * @param {Property} showMassesProperty property for whether the mass value should be shown * @param {Rectangle} itemToolbox - The toolbox that contains this item + * @param {Tandem} tandem */ - constructor( model, motionView, item, normalImage, sittingImage, holdingImage, showMassesProperty, itemToolbox, tandem ) { + constructor( model, motionView, item, normalImageProperty, sittingImageProperty, holdingImageProperty, showMassesProperty, itemToolbox, tandem ) { super( { cursor: 'pointer', @@ -50,24 +52,24 @@ class ItemNode extends Node { this.translate( item.positionProperty.get() ); //Create the node for the main graphic - const normalImageNode = new Image( normalImage, { tandem: tandem.createTandem( 'normalImageNode' ) } ); + const normalImageNode = new Image( normalImageProperty.value, { tandem: tandem.createTandem( 'normalImageNode' ) } ); this.normalImageNode = normalImageNode; // keep track of the sitting image to track its width for the pusher // @public (read-only) - this.sittingImageNode = new Image( sittingImage, { tandem: tandem.createTandem( 'sittingImageNode' ) } ); + this.sittingImageNode = new Image( sittingImageProperty.value, { tandem: tandem.createTandem( 'sittingImageNode' ) } ); //When the model changes, update the image position as well as which image is shown const updateImage = () => { // var centerX = normalImageNode.centerX; - if ( ( typeof holdingImage !== 'undefined' ) && ( item.armsUp() && item.onBoardProperty.get() ) ) { - normalImageNode.image = holdingImage; + if ( ( typeof holdingImageProperty.value !== 'undefined' ) && ( item.armsUp() && item.onBoardProperty.get() ) ) { + normalImageNode.image = holdingImageProperty.value; } - else if ( item.onBoardProperty.get() && typeof sittingImage !== 'undefined' ) { - normalImageNode.image = sittingImage; + else if ( item.onBoardProperty.get() && typeof sittingImageProperty.value !== 'undefined' ) { + normalImageNode.image = sittingImageProperty.value; } else { - normalImageNode.image = normalImage; + normalImageNode.image = normalImageProperty.value; } if ( this.labelNode ) { this.updateLabelPosition(); @@ -90,7 +92,7 @@ class ItemNode extends Node { const moveToStack = () => { item.onBoardProperty.set( true ); const imageWidth = item.getCurrentScale() * normalImageNode.width; - item.animateTo( motionView.layoutBounds.width / 2 - imageWidth / 2 + item.centeringOffset, motionView.topOfStack - this.height, 'stack' ); + item.animateTo( motionView.layoutBounds.width / 2 - imageWidth / 2, motionView.topOfStack - this.height, 'stack' ); model.stackObservableArray.add( item ); if ( model.stackObservableArray.length > 3 ) { model.spliceStackBottom(); @@ -250,6 +252,8 @@ class ItemNode extends Node { this.addChild( labelText ); showMassesProperty.link( showMasses => { labelText.visible = showMasses; } ); + + PreferencesModelSingleton.localizationModel.regionAndCulturePortrayalProperty.link( updateImage ); } diff --git a/js/motion/view/MassPlayerImages.js b/js/motion/view/MassPlayerImages.js new file mode 100644 index 00000000..b6c9931c --- /dev/null +++ b/js/motion/view/MassPlayerImages.js @@ -0,0 +1,19 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * MassPlayerImages contains an array of character sets, each representing a different region/culture. + * + * @author Luisa Vargas + */ + +import forcesAndMotionBasics from '../../forcesAndMotionBasics.js'; +import MassPlayerPortrayalUSA from './MassPlayerPortrayalUSA.js'; + +const MassPlayerImages = { + MASS_PLAYER_PORTRAYALS: [ + MassPlayerPortrayalUSA + ] +}; + +forcesAndMotionBasics.register( 'MassPlayerImages', MassPlayerImages ); +export default MassPlayerImages; \ No newline at end of file diff --git a/js/motion/view/MassPlayerPortrayal.js b/js/motion/view/MassPlayerPortrayal.js new file mode 100644 index 00000000..d686d9d9 --- /dev/null +++ b/js/motion/view/MassPlayerPortrayal.js @@ -0,0 +1,43 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * The MassPlayerPortrayal defines what is needed for each portrayal in Forces and Motion: Basics. + * + * @author Luisa Vargas + * + */ + +import RegionAndCulturePortrayal from '../../../../joist/js/preferences/RegionAndCulturePortrayal.js'; +import forcesAndMotionBasics from '../../forcesAndMotionBasics.js'; + +export default class MassPlayerPortrayal extends RegionAndCulturePortrayal { + + /** + * @param label { LocalizedStringProperty } + * @param girlHolding { HTMLImageElement } + * @param girlSitting { HTMLImageElement } + * @param girlStanding { HTMLImageElement } + * @param manHolding { HTMLImageElement } + * @param manSitting { HTMLImageElement } + * @param manStanding { HTMLImageElement } + * @param screenIcon { HTMLImageElement } + * @param queryParameterValue { string } + */ + constructor( label, + girlHolding, girlSitting, girlStanding, + manHolding, manSitting, manStanding, + screenIcon, queryParameterValue ) { + + super( label, queryParameterValue, {} ); + + this.girlHolding = girlHolding; + this.girlSitting = girlSitting; + this.girlStanding = girlStanding; + this.manHolding = manHolding; + this.manStanding = manStanding; + this.manSitting = manSitting; + this.screenIcon = screenIcon; + } +} + +forcesAndMotionBasics.register( 'MassPlayerPortrayal', MassPlayerPortrayal ); \ No newline at end of file diff --git a/js/motion/view/MassPlayerPortrayalUSA.js b/js/motion/view/MassPlayerPortrayalUSA.js new file mode 100644 index 00000000..b30a15c5 --- /dev/null +++ b/js/motion/view/MassPlayerPortrayalUSA.js @@ -0,0 +1,33 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * This file instantiates the USA region and culture portrayals. + * + * @author Luisa Vargas + * + */ + +import JoistStrings from '../../../../joist/js/JoistStrings.js'; +import { USA_REGION_AND_CULTURE_ID } from '../../../../joist/js/preferences/RegionAndCulturePortrayal.js'; +import motionIcon_png from '../../../images/motionIcon_png.js'; +import girlHolding_png from '../../../mipmaps/girlHolding_png.js'; +import girlSitting_png from '../../../mipmaps/girlSitting_png.js'; +import girlStanding_png from '../../../mipmaps/girlStanding_png.js'; +import manHolding_png from '../../../mipmaps/manHolding_png.js'; +import manSitting_png from '../../../mipmaps/manSitting_png.js'; +import manStanding_png from '../../../mipmaps/manStanding_png.js'; +import MassPlayerPortrayal from './MassPlayerPortrayal.js'; + +const MassPlayerPortrayalUSA = new MassPlayerPortrayal( + JoistStrings.preferences.tabs.localization.regionAndCulture.portrayalSets.unitedStatesOfAmericaStringProperty, + girlHolding_png[ 1 ].img, + girlSitting_png, + girlStanding_png, + manHolding_png, + manSitting_png, + manStanding_png, + motionIcon_png, + USA_REGION_AND_CULTURE_ID +); + +export default MassPlayerPortrayalUSA; \ No newline at end of file diff --git a/js/motion/view/MotionScreenIcon.js b/js/motion/view/MotionScreenIcon.js new file mode 100644 index 00000000..a22151ef --- /dev/null +++ b/js/motion/view/MotionScreenIcon.js @@ -0,0 +1,42 @@ +// Copyright 2024, University of Colorado Boulder + +/** + * MotionScreenIcon places all the screenIcons of different region and culture representations into a single Node. + * Each icon's visibility is controlled by the regionAndCulturePortrayalProperty. + * + * @author Luisa Vargas + * + */ + +import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; +import ScreenIcon from '../../../../joist/js/ScreenIcon.js'; +import { Node, Image } from '../../../../scenery/js/imports.js'; +import forcesAndMotionBasics from '../../forcesAndMotionBasics.js'; +import PreferencesModelSingleton from '../PreferencesModelSingleton.js'; + +export default class MotionScreenIcon extends ScreenIcon { + + /** + * @param { Array } portrayals + * @param options + * @param { Tandem } tandem + */ + constructor( portrayals, options, tandem ) { + + const motionScreenImages = portrayals.map( massPlayerPortrayal => { + return new Image( massPlayerPortrayal.screenIcon, { + visibleProperty: new DerivedProperty( [ PreferencesModelSingleton.localizationModel.regionAndCulturePortrayalProperty ], portrayal => { + return portrayal === massPlayerPortrayal; + } ), + tandem: tandem.createTandem( 'icon' ) + } ); + } ); + + const screenIconNode = new Node( { + children: motionScreenImages + } ); + super( screenIconNode, options ); + } +} + +forcesAndMotionBasics.register( 'MotionScreenIcon', MotionScreenIcon ); \ No newline at end of file diff --git a/js/motion/view/MotionScreenView.js b/js/motion/view/MotionScreenView.js index bf0da5c1..e53466c7 100644 --- a/js/motion/view/MotionScreenView.js +++ b/js/motion/view/MotionScreenView.js @@ -311,10 +311,12 @@ class MotionScreenView extends ScreenView { const toolboxNode = itemSide === 'left' ? leftItemToolboxNode : rightItemToolboxNode; const itemLayer = itemSide === 'left' ? leftItemLayer : rightItemLayer; const Constructor = item.bucket ? WaterBucketNode : ItemNode; + const sittingImageProperty = item.sittingImageProperty.value ? item.sittingImageProperty : item.imageProperty; + const holdingImageProperty = item.holdingImageProperty.value ? item.holdingImageProperty : item.imageProperty; const itemNode = new Constructor( model, this, item, - item.image, - item.sittingImageNode || item.image, - item.holdingImage || item.image, + item.imageProperty, + sittingImageProperty, + holdingImageProperty, model.showMassesProperty, toolboxNode, tandem.createTandem( item.name ) ); diff --git a/js/motion/view/WaterBucketNode.js b/js/motion/view/WaterBucketNode.js index 6f73e1c2..02b0d48b 100644 --- a/js/motion/view/WaterBucketNode.js +++ b/js/motion/view/WaterBucketNode.js @@ -25,15 +25,15 @@ class WaterBucketNode extends ItemNode { * @param {MotionModel} model the model for the entire 'motion', 'friction' or 'acceleration' screen * @param {MotionScreenView} motionView the view for the entire 'motion', 'friction' or 'acceleration' screen * @param {Item} item the model for the item itself - * @param {Image} image image to be shown when in the toolbox or being dragged - * @param {Image} imageSitting image to be shown if it is a sitting person - * @param {Image} imageHolding image to be shown if it is a sitting person holding their arms in the air + * @param {Image} imageProperty imageProperty holds image to be shown when in the toolbox or being dragged + * @param {Image} imageSittingProperty imageProperty holds image to be shown if it is a sitting person + * @param {Image} imageHoldingProperty imageProperty holds image to be shown if it is a sitting person holding their arms in the air * @param {Property} showMassesProperty boolean property of whether the masses should be shown * @param {Rectangle} toolboxNode parent toolbox for the WaterBucketNode * @param {Tandem} tandem */ - constructor( model, motionView, item, image, imageSitting, imageHolding, showMassesProperty, toolboxNode, tandem ) { - super( model, motionView, item, image, imageSitting, imageHolding, showMassesProperty, toolboxNode, tandem ); + constructor( model, motionView, item, imageProperty, imageSittingProperty, imageHoldingProperty, showMassesProperty, toolboxNode, tandem ) { + super( model, motionView, item, imageProperty, imageSittingProperty, imageHoldingProperty, showMassesProperty, toolboxNode, tandem ); this.item = item; const waterPathNode = new Path( Shape.lineSegment( new Vector2( 0, 0 ), new Vector2( 0, 18 ) ), { stroke: 'black', @@ -50,7 +50,7 @@ class WaterBucketNode extends ItemNode { //Metrics based on original image size of 98 pixels wide. const padX = 4.5; const padY = 9; - const s = image.width / 98.0; + const s = imageProperty.value.width / 98.0; const leftLineX = x => linear( 0, 1, ( 1 + padX ) * s, ( 10 + padX ) * s, x ); const leftLineY = x => linear( 0, 1, ( 9 - padY ) * s, ( 102 - padY ) * s, x ); diff --git a/package.json b/package.json index 0d353869..cbe3fe66 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,11 @@ "phet-io", "adapted-from-phet" ], + "simFeatures": { + "supportedRegionsAndCultures": [ + "usa" + ] + }, "simulation": true, "phet-io": { "validation": false,