diff --git a/js/common/view/labels/ArrowObjectSceneLabelsNode.ts b/js/common/view/labels/ArrowObjectSceneLabelsNode.ts index 33711d78..96c1f0fa 100644 --- a/js/common/view/labels/ArrowObjectSceneLabelsNode.ts +++ b/js/common/view/labels/ArrowObjectSceneLabelsNode.ts @@ -9,23 +9,24 @@ import DerivedProperty from '../../../../../axon/js/DerivedProperty.js'; import Vector2 from '../../../../../dot/js/Vector2.js'; import geometricOptics from '../../../geometricOptics.js'; -import geometricOpticsStrings from '../../../geometricOpticsStrings.js'; -import LabelNode, { LabelNodeOptions } from './LabelNode.js'; import VisibleProperties from '../VisibleProperties.js'; import ModelViewTransform2 from '../../../../../phetcommon/js/view/ModelViewTransform2.js'; import Bounds2 from '../../../../../dot/js/Bounds2.js'; import IReadOnlyProperty from '../../../../../axon/js/IReadOnlyProperty.js'; import ArrowObjectScene from '../../model/ArrowObjectScene.js'; import IProperty from '../../../../../axon/js/IProperty.js'; -import { OpticalImageType } from '../../model/OpticalImageType.js'; import GOSceneLabelsNode, { GOSceneLabelsNodeOptions } from './GOSceneLabelsNode.js'; import ArrowImage from '../../model/ArrowImage.js'; -import StringUtils from '../../../../../phetcommon/js/util/StringUtils.js'; import BooleanProperty from '../../../../../axon/js/BooleanProperty.js'; import ArrowObject from '../../model/ArrowObject.js'; import Optic from '../../model/Optic.js'; -import Property from '../../../../../axon/js/Property.js'; +import OpticalObjectLabelNode, { OpticalObjectLabelNodeOptions } from './OpticalObjectLabelNode.js'; +import LabelNode, { LabelNodeOptions } from './LabelNode.js'; import optionize from '../../../../../phet-core/js/optionize.js'; +import { OpticalImageType } from '../../model/OpticalImageType.js'; +import Property from '../../../../../axon/js/Property.js'; +import StringUtils from '../../../../../phetcommon/js/util/StringUtils.js'; +import geometricOpticsStrings from '../../../geometricOpticsStrings.js'; type SelfOptions = { isBasicsVersion: boolean; @@ -71,14 +72,14 @@ class ArrowObjectSceneLabelsNode extends GOSceneLabelsNode { // Image labels ------------------------------------------------------------------------------------ const image2Label = new ArrowImageLabelNode( scene.arrowImage2, scene.optic, zoomTransformProperty, - lightPropagationEnabledProperty, visibleProperties.secondPointVisibleProperty, + visibleProperties.secondPointVisibleProperty, lightPropagationEnabledProperty, visibleProperties.virtualImageVisibleProperty ); this.addChild( image2Label ); const image1Label = new ArrowImageLabelNode( scene.arrowImage1, scene.optic, zoomTransformProperty, - lightPropagationEnabledProperty, new BooleanProperty( true ), visibleProperties.virtualImageVisibleProperty, { + new BooleanProperty( true ), lightPropagationEnabledProperty, visibleProperties.virtualImageVisibleProperty, { - // Use numbering in the full version of the sim, or in the basics version if Image 2 is visible. + // Use numbering in the full version of the sim, or in the basics version if Image 2 is made visible. isNumberedProperty: new DerivedProperty( [ image2Label.visibleProperty ], ( image2LabelVisible: boolean ) => ( !providedOptions.isBasicsVersion || image2LabelVisible ) ) @@ -92,14 +93,8 @@ class ArrowObjectSceneLabelsNode extends GOSceneLabelsNode { } } -type ArrowObjectLabelNodeSelfOptions = { - isNumberedProperty?: IReadOnlyProperty; -}; - -type ArrowObjectLabelNodeOptions = ArrowObjectLabelNodeSelfOptions & LabelNodeOptions; - // Label for an arrow object. -class ArrowObjectLabelNode extends LabelNode { +class ArrowObjectLabelNode extends OpticalObjectLabelNode { /** * @param arrowObject @@ -110,11 +105,7 @@ class ArrowObjectLabelNode extends LabelNode { constructor( arrowObject: ArrowObject, optic: Optic, zoomTransformProperty: IReadOnlyProperty, - providedOptions?: ArrowObjectLabelNodeOptions ) { - - const options = optionize( { - isNumberedProperty: new BooleanProperty( true ) - }, providedOptions ); + providedOptions?: OpticalObjectLabelNodeOptions ) { // If the arrow points up, position the label below the optical axis. // Otherwise, position the label below the arrow's tip. @@ -124,22 +115,7 @@ class ArrowObjectLabelNode extends LabelNode { ( arrowPosition.y > opticPosition.y ) ? new Vector2( arrowPosition.x, opticPosition.y ) : arrowPosition ); - super( '', labelPositionProperty, zoomTransformProperty, options ); - - options.isNumberedProperty.link( ( isNumbered: boolean ) => { - if ( isNumbered ) { - - // Object N - this.setText( StringUtils.fillIn( geometricOpticsStrings.label.objectN, { - objectNumber: arrowObject.opticalObjectNumber - } ) ); - } - else { - - // Object - this.setText( geometricOpticsStrings.label.object ); - } - } ); + super( arrowObject.opticalObjectNumber, labelPositionProperty, zoomTransformProperty, providedOptions ); } } @@ -171,9 +147,13 @@ class ArrowImageLabelNode extends LabelNode { const options = optionize( { isNumberedProperty: new BooleanProperty( true ), - visibleProperty: new DerivedProperty( - [ lightPropagationEnabledProperty, arrowObjectVisibleProperty, arrowImage.visibleProperty, - arrowImage.opticalImageTypeProperty, virtualImageVisibleProperty ], + visibleProperty: new DerivedProperty( [ + lightPropagationEnabledProperty, + arrowObjectVisibleProperty, + arrowImage.visibleProperty, + arrowImage.opticalImageTypeProperty, + virtualImageVisibleProperty + ], ( lightPropagationEnabled: boolean, arrowObjectVisible: boolean, arrowImageVisible: boolean, opticalImageType: OpticalImageType, virtualImageVisible: boolean ) => ( lightPropagationEnabled && arrowObjectVisible && arrowImageVisible && ( opticalImageType === 'real' || virtualImageVisible ) ) diff --git a/js/common/view/labels/FramedObjectSceneLabelsNode.ts b/js/common/view/labels/FramedObjectSceneLabelsNode.ts index 210bc14a..90c0d6dd 100644 --- a/js/common/view/labels/FramedObjectSceneLabelsNode.ts +++ b/js/common/view/labels/FramedObjectSceneLabelsNode.ts @@ -8,7 +8,6 @@ import DerivedProperty from '../../../../../axon/js/DerivedProperty.js'; import geometricOptics from '../../../geometricOptics.js'; -import geometricOpticsStrings from '../../../geometricOpticsStrings.js'; import LabelNode from './LabelNode.js'; import VisibleProperties from '../VisibleProperties.js'; import ModelViewTransform2 from '../../../../../phetcommon/js/view/ModelViewTransform2.js'; @@ -18,6 +17,9 @@ import FramedObjectScene from '../../model/FramedObjectScene.js'; import { OpticalImageType } from '../../model/OpticalImageType.js'; import IProperty from '../../../../../axon/js/IProperty.js'; import GOSceneLabelsNode, { GOSceneLabelsNodeOptions } from './GOSceneLabelsNode.js'; +import OpticalObjectLabelNode from './OpticalObjectLabelNode.js'; +import BooleanProperty from '../../../../../axon/js/BooleanProperty.js'; +import geometricOpticsStrings from '../../../geometricOpticsStrings.js'; class FramedObjectSceneLabelsNode extends GOSceneLabelsNode { @@ -38,17 +40,21 @@ class FramedObjectSceneLabelsNode extends GOSceneLabelsNode { super( scene.optic, visibleProperties, zoomTransformProperty, modelVisibleBoundsProperty, providedOptions ); - // Object label ------------------------------------------------------------------------------------ + const isNumberedProperty = new BooleanProperty( false, { + validValues: [ false ] + } ); - // Object - const objectLabelString = geometricOpticsStrings.label.object; + // Object label ------------------------------------------------------------------------------------ const objectLabelPositionProperty = new DerivedProperty( [ scene.framedObject.boundsProperty ], ( bounds: Bounds2 ) => bounds.centerTop ); - const objectLabel = new LabelNode( objectLabelString, objectLabelPositionProperty, zoomTransformProperty ); + const objectLabel = new OpticalObjectLabelNode( scene.framedObject.opticalObjectNumber, + objectLabelPositionProperty, zoomTransformProperty, { + isNumberedProperty: isNumberedProperty + } ); this.addChild( objectLabel ); // Image label ------------------------------------------------------------------------------------ @@ -59,8 +65,12 @@ class FramedObjectSceneLabelsNode extends GOSceneLabelsNode { ); const imageLabel = new LabelNode( '', imageLabelPositionProperty, zoomTransformProperty, { - visibleProperty: new DerivedProperty( [ lightPropagationEnabledProperty, scene.framedImage1.visibleProperty, - scene.framedImage1.opticalImageTypeProperty, visibleProperties.virtualImageVisibleProperty ], + visibleProperty: new DerivedProperty( [ + lightPropagationEnabledProperty, + scene.framedImage1.visibleProperty, + scene.framedImage1.opticalImageTypeProperty, + visibleProperties.virtualImageVisibleProperty + ], ( lightPropagationEnabled: boolean, imageVisible: boolean, opticalImageType: OpticalImageType, virtualImageVisible: boolean ) => ( lightPropagationEnabled && imageVisible && ( opticalImageType === 'real' || virtualImageVisible ) ) ) diff --git a/js/common/view/labels/LightObjectSceneLabelsNode.ts b/js/common/view/labels/LightObjectSceneLabelsNode.ts index 6c744395..ba8b2b21 100644 --- a/js/common/view/labels/LightObjectSceneLabelsNode.ts +++ b/js/common/view/labels/LightObjectSceneLabelsNode.ts @@ -10,17 +10,15 @@ import DerivedProperty from '../../../../../axon/js/DerivedProperty.js'; import Vector2 from '../../../../../dot/js/Vector2.js'; import geometricOptics from '../../../geometricOptics.js'; import geometricOpticsStrings from '../../../geometricOpticsStrings.js'; -import LabelNode, { LabelNodeOptions } from './LabelNode.js'; +import LabelNode from './LabelNode.js'; import VisibleProperties from '../VisibleProperties.js'; import ModelViewTransform2 from '../../../../../phetcommon/js/view/ModelViewTransform2.js'; import Bounds2 from '../../../../../dot/js/Bounds2.js'; import IReadOnlyProperty from '../../../../../axon/js/IReadOnlyProperty.js'; import LightObjectScene from '../../model/LightObjectScene.js'; import GOSceneLabelsNode, { GOSceneLabelsNodeOptions } from './GOSceneLabelsNode.js'; -import StringUtils from '../../../../../phetcommon/js/util/StringUtils.js'; import LightObject from '../../model/LightObject.js'; -import BooleanProperty from '../../../../../axon/js/BooleanProperty.js'; -import optionize from '../../../../../phet-core/js/optionize.js'; +import OpticalObjectLabelNode, { OpticalObjectLabelNodeOptions } from './OpticalObjectLabelNode.js'; type SelfOptions = { isBasicsVersion: boolean; @@ -73,14 +71,10 @@ class LightObjectSceneLabelsNode extends GOSceneLabelsNode { } } -type LightObjectLabelNodeSelfOptions = { - isNumberedProperty?: IReadOnlyProperty; -}; - -type LightObjectLabelNodeOptions = LightObjectLabelNodeSelfOptions & LabelNodeOptions; +type LightObjectLabelNodeOptions = OpticalObjectLabelNodeOptions; // Label for a light object. -class LightObjectLabelNode extends LabelNode { +class LightObjectLabelNode extends OpticalObjectLabelNode { /** * @param lightObject @@ -91,31 +85,12 @@ class LightObjectLabelNode extends LabelNode { zoomTransformProperty: IReadOnlyProperty, providedOptions?: LightObjectLabelNodeOptions ) { - const options = optionize( { - isNumberedProperty: new BooleanProperty( true ) - }, providedOptions ); - // Position the label below the light, slightly to the left of center (determined empirically) const labelPositionProperty = new DerivedProperty( [ lightObject.boundsProperty ], ( bounds: Bounds2 ) => new Vector2( bounds.centerX - 15, bounds.top ) ); - super( '', labelPositionProperty, zoomTransformProperty, options ); - - options.isNumberedProperty.link( ( isNumbered: boolean ) => { - if ( isNumbered ) { - - // Object N - this.setText( StringUtils.fillIn( geometricOpticsStrings.label.objectN, { - objectNumber: lightObject.opticalObjectNumber - } ) ); - } - else { - - // Object - this.setText( geometricOpticsStrings.label.object ); - } - } ); + super( lightObject.opticalObjectNumber, labelPositionProperty, zoomTransformProperty, providedOptions ); } } diff --git a/js/common/view/labels/OpticalObjectLabelNode.ts b/js/common/view/labels/OpticalObjectLabelNode.ts new file mode 100644 index 00000000..c390b273 --- /dev/null +++ b/js/common/view/labels/OpticalObjectLabelNode.ts @@ -0,0 +1,65 @@ +// Copyright 2021-2022, University of Colorado Boulder + +/** + * OpticalObjectLabelNode is the base class of labeling optical objects. + * It can label them as simply 'Object', or it can number them like 'Object 1'. + * Numbering is dynamic to support PhET-iO. + * + * @author Chris Malley (PixelZoom, Inc.) + */ +import LabelNode, { LabelNodeOptions } from './LabelNode.js'; +import geometricOpticsStrings from '../../../geometricOpticsStrings.js'; +import geometricOptics from '../../../geometricOptics.js'; +import StringUtils from '../../../../../phetcommon/js/util/StringUtils.js'; +import BooleanProperty from '../../../../../axon/js/BooleanProperty.js'; +import optionize from '../../../../../phet-core/js/optionize.js'; +import IReadOnlyProperty from '../../../../../axon/js/IReadOnlyProperty.js'; +import Vector2 from '../../../../../dot/js/Vector2.js'; +import ModelViewTransform2 from '../../../../../phetcommon/js/view/ModelViewTransform2.js'; + +type SelfOptions = { + + // Whether the object should be numbered, like 'Object 1' + isNumberedProperty?: IReadOnlyProperty; +}; + +export type OpticalObjectLabelNodeOptions = SelfOptions & LabelNodeOptions; + +class OpticalObjectLabelNode extends LabelNode { + + /** + * @param objectNumber + * @param labelPositionProperty + * @param zoomTransformProperty + * @param providedOptions + */ + constructor( objectNumber: number, + labelPositionProperty: IReadOnlyProperty, + zoomTransformProperty: IReadOnlyProperty, + providedOptions?: OpticalObjectLabelNodeOptions ) { + + const options = optionize( { + isNumberedProperty: new BooleanProperty( true ) + }, providedOptions ); + + super( '', labelPositionProperty, zoomTransformProperty, options ); + + options.isNumberedProperty.link( ( isNumbered: boolean ) => { + if ( isNumbered ) { + + // Object N + this.setText( StringUtils.fillIn( geometricOpticsStrings.label.objectN, { + objectNumber: objectNumber + } ) ); + } + else { + + // Object + this.setText( geometricOpticsStrings.label.object ); + } + } ); + } +} + +geometricOptics.register( 'OpticalObjectLabelNode', OpticalObjectLabelNode ); +export default OpticalObjectLabelNode; \ No newline at end of file