Skip to content

Commit

Permalink
convert to typescript, use VoidFunction, #38
Browse files Browse the repository at this point in the history
  • Loading branch information
zepumph committed Aug 2, 2023
1 parent 70bda31 commit e5d99a2
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 106 deletions.
2 changes: 1 addition & 1 deletion js/model/Particle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Particle extends PhetioObject {

// Used in view, integer value, higher means further back.
public readonly zLayerProperty: TProperty<number>;
private readonly disposeParticle: () => void;
private readonly disposeParticle: VoidFunction;

// Assigned by other parties as a way to clean up animations.
public particleAtomRemovalListener: null | ( ( userControlled: boolean ) => void ) = null;
Expand Down
6 changes: 3 additions & 3 deletions js/model/ParticleAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ class ParticleAtom extends PhetioObject {
* Extract an arbitrary instance of the specified particle, assuming one exists.
*/
public extractParticle( particleType: ParticleTypeString ): Particle {
let particle = null;
let particle: Particle | null = null;
switch( particleType ) {
case 'proton':
if ( this.protons.length > 0 ) {
Expand Down Expand Up @@ -432,7 +432,7 @@ class ParticleAtom extends PhetioObject {
this.removeParticle( particle );
}

return particle as unknown as Particle;
return particle!;
}

public extractParticleClosestToCenter( particleType: ParticleTypeString ): Particle {
Expand Down Expand Up @@ -629,7 +629,7 @@ class ParticleAtom extends PhetioObject {
/**
* Change the nucleon type of the provided particle to the other nucleon type.
*/
public changeNucleonType( particle: Particle, animateAndRemoveParticle: () => void ): Animation {
public changeNucleonType( particle: Particle, animateAndRemoveParticle: VoidFunction ): Animation {
assert && assert( this.containsParticle( particle ), 'ParticleAtom does not contain this particle ' + particle.id );
assert && assert( particle.type === 'proton' || particle.type === 'neutron', 'Particle type must be a proton or a neutron.' );

Expand Down
79 changes: 45 additions & 34 deletions js/view/AtomNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
import Property from '../../../axon/js/Property.js';
import Vector2 from '../../../dot/js/Vector2.js';
import { Shape } from '../../../kite/js/imports.js';
import merge from '../../../phet-core/js/merge.js';
import PhetColorScheme from '../../../scenery-phet/js/PhetColorScheme.js';
import PhetFont from '../../../scenery-phet/js/PhetFont.js';
import { Node, Path, Text } from '../../../scenery/js/imports.js';
import { Node, NodeOptions, Path, Text } from '../../../scenery/js/imports.js';
import Tandem from '../../../tandem/js/Tandem.js';
import AtomIdentifier from '../AtomIdentifier.js';
import shred from '../shred.js';
import ShredStrings from '../ShredStrings.js';
import ElectronCloudView from './ElectronCloudView.js';
import ElectronShellView from './ElectronShellView.js';
import ParticleAtom from '../model/ParticleAtom.js';
import ModelViewTransform2 from '../../../phetcommon/js/view/ModelViewTransform2.js';
import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js';
import optionize from '../../../phet-core/js/optionize.js';

const minusSignIonString = ShredStrings.minusSignIon;
const neutralAtomString = ShredStrings.neutralAtom;
Expand All @@ -30,35 +33,49 @@ const unstableString = ShredStrings.unstable;
// constants
const ELEMENT_NAME_FONT_SIZE = 22;

type ElectronShellDepiction = 'orbits' | 'cloud';

type SelfOptions = {
showCenterX?: boolean;
showElementNameProperty?: TReadOnlyProperty<boolean>;
showNeutralOrIonProperty?: TReadOnlyProperty<boolean>;
showStableOrUnstableProperty?: TReadOnlyProperty<boolean>;
electronShellDepictionProperty?: TReadOnlyProperty<ElectronShellDepiction>;
};

type AtomNodeOptions = SelfOptions & NodeOptions;

class AtomNode extends Node {
private readonly atom: ParticleAtom;
private readonly modelViewTransform: ModelViewTransform2;
private readonly elementNameText: Text;
private readonly ionIndicatorText: Text;
private readonly stabilityIndicatorText: Text;
private readonly disposeAtomNode: VoidFunction;

/**
* @param {ParticleAtom} particleAtom Model that represents the atom, including particle positions
* @param {ModelViewTransform2} modelViewTransform Model-View transform
* @param {Object} [options]
* @param particleAtom Model that represents the atom, including particle positions
* @param modelViewTransform Model-View transform
* @param providedOptions
*/
constructor( particleAtom, modelViewTransform, options ) {

options = merge( {
showCenterX: true,
showElementNameProperty: new Property( true ),
showNeutralOrIonProperty: new Property( true ),
showStableOrUnstableProperty: new Property( true ),
electronShellDepictionProperty: new Property( 'orbits' ),
tandem: Tandem.REQUIRED
},
options
);
public constructor( particleAtom: ParticleAtom, modelViewTransform: ModelViewTransform2, providedOptions?: AtomNodeOptions ) {
const options = optionize<AtomNodeOptions, SelfOptions, NodeOptions>()( {
showCenterX: true,
showElementNameProperty: new Property( true ),
showNeutralOrIonProperty: new Property( true ),
showStableOrUnstableProperty: new Property( true ),
electronShellDepictionProperty: new Property( 'orbits' ),
tandem: Tandem.REQUIRED
}, providedOptions );

super();

// @private
this.atom = particleAtom;
this.modelViewTransform = modelViewTransform;

// Create the X where the nucleus goes.
let countListener = null;
let atomCenterMarker = null;
let countListener: VoidFunction | null = null;
let atomCenterMarker: Path | null = null;
if ( options.showCenterX ) {
const sizeInPixels = modelViewTransform.modelToViewDeltaX( 20 );
const center = modelViewTransform.modelToViewPosition( particleAtom.positionProperty.get() );
Expand All @@ -77,7 +94,7 @@ class AtomNode extends Node {

// Make the marker invisible if any nucleons are present.
countListener = () => {
atomCenterMarker.visible = particleAtom.getWeight() === 0;
atomCenterMarker!.visible = particleAtom.getWeight() === 0;
};
particleAtom.electronCountProperty.link( countListener );
particleAtom.neutronCountProperty.link( countListener );
Expand All @@ -94,7 +111,7 @@ class AtomNode extends Node {
} );
this.addChild( electronCloud );

const updateElectronShellDepictionVisibility = depiction => {
const updateElectronShellDepictionVisibility = ( depiction: ElectronShellDepiction ) => {
electronShell.visible = depiction === 'orbits';
electronCloud.visible = depiction === 'cloud';
};
Expand All @@ -104,7 +121,7 @@ class AtomNode extends Node {
particleAtom.positionProperty.get().plus( new Vector2( 0, particleAtom.innerElectronShellRadius * 0.60 ) )
);

// @private - Create the textual readout for the element name.
// Create the textual readout for the element name.
this.elementNameText = new Text( '', {
font: new PhetFont( ELEMENT_NAME_FONT_SIZE ),
fill: PhetColorScheme.RED_COLORBLIND,
Expand All @@ -131,15 +148,15 @@ class AtomNode extends Node {
// Hook up update listeners.
particleAtom.protonCountProperty.link( updateElementName );

const updateElementNameVisibility = visible => {
const updateElementNameVisibility = ( visible: boolean ) => {
this.elementNameText.visible = visible;
};
options.showElementNameProperty.link( updateElementNameVisibility );

const ionIndicatorTextTranslation = modelViewTransform.modelToViewPosition( particleAtom.positionProperty.get().plus(
new Vector2( particleAtom.outerElectronShellRadius * 1.05, 0 ).rotated( Math.PI * 0.3 ) ) );

// @private - Create the textual readout for the ion indicator, set by trial and error.
// Create the textual readout for the ion indicator, set by trial and error.
this.ionIndicatorText = new Text( '', {
font: new PhetFont( 20 ),
fill: 'black',
Expand Down Expand Up @@ -176,7 +193,7 @@ class AtomNode extends Node {

particleAtom.protonCountProperty.link( updateIonIndicator );
particleAtom.electronCountProperty.link( updateIonIndicator );
const updateIonIndicatorVisibility = visible => {
const updateIonIndicatorVisibility = ( visible: boolean ) => {
this.ionIndicatorText.visible = visible;
};
options.showNeutralOrIonProperty.link( updateIonIndicatorVisibility );
Expand All @@ -185,7 +202,6 @@ class AtomNode extends Node {
const stabilityIndicatorTextCenterPos = modelViewTransform.modelToViewPosition( particleAtom.positionProperty.get().plus(
new Vector2( 0, -particleAtom.innerElectronShellRadius * 0.60 ) ) );

// @private
this.stabilityIndicatorText = new Text( '', {
font: new PhetFont( 20 ),
fill: 'black',
Expand Down Expand Up @@ -216,12 +232,11 @@ class AtomNode extends Node {
// Add the listeners that control the label content and visibility.
particleAtom.protonCountProperty.link( updateStabilityIndicator );
particleAtom.neutronCountProperty.link( updateStabilityIndicator );
const updateStabilityIndicatorVisibility = visible => {
const updateStabilityIndicatorVisibility = ( visible: boolean ) => {
this.stabilityIndicatorText.visible = visible;
};
options.showStableOrUnstableProperty.link( updateStabilityIndicatorVisibility );

// @private
this.disposeAtomNode = () => {

electronCloud.dispose();
Expand Down Expand Up @@ -251,11 +266,7 @@ class AtomNode extends Node {
this.mutate( options );
}

/**
* @public
* @override
*/
dispose() {
public override dispose(): void {
this.disposeAtomNode();
super.dispose();
}
Expand Down
2 changes: 1 addition & 1 deletion js/view/ElectronCloudView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type ElectronCloudViewOptions = CircleOptions;

class ElectronCloudView extends Circle {
private extractedElectron: Particle | null;
private readonly disposeElectronCloudView: () => void;
private readonly disposeElectronCloudView: VoidFunction;


public constructor( atom: ParticleAtom, modelViewTransform: ModelViewTransform2, providedOptions?: ElectronCloudViewOptions ) {
Expand Down
2 changes: 1 addition & 1 deletion js/view/ElectronShellView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import optionize, { EmptySelfOptions } from '../../../phet-core/js/optionize.js'
const LINE_DASH = [ 4, 5 ];

class ElectronShellView extends Node {
private readonly disposeElectronShellView: () => void;
private readonly disposeElectronShellView: VoidFunction;

public constructor( atom: ParticleAtom, modelViewTransform: ModelViewTransform2, providedOptions?: NodeOptions ) {
const options = optionize<NodeOptions, EmptySelfOptions, NodeOptions>()( {
Expand Down
55 changes: 28 additions & 27 deletions js/view/InteractiveSchematicAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,50 @@
* Node that depicts an interactive atom where the user can add subatomic particles from a set of buckets.
*/

import merge from '../../../phet-core/js/merge.js';
import BucketFront from '../../../scenery-phet/js/bucket/BucketFront.js';
import BucketHole from '../../../scenery-phet/js/bucket/BucketHole.js';
import { Node } from '../../../scenery/js/imports.js';
import { Node, NodeOptions } from '../../../scenery/js/imports.js';
import BuildAnAtomModel from '../../../build-an-atom/js/common/model/BuildAnAtomModel.js';
import Tandem from '../../../tandem/js/Tandem.js';
import shred from '../shred.js';
import AtomNode from './AtomNode.js';
import BucketDragListener from './BucketDragListener.js';
import ParticleView from './ParticleView.js';
import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js';
import ModelViewTransform2 from '../../../phetcommon/js/view/ModelViewTransform2.js';
import optionize from '../../../phet-core/js/optionize.js';

// constants
const NUM_NUCLEON_LAYERS = 5; // This is based on max number of particles, may need adjustment if that changes.
type SelfOptions = {
highContrastProperty?: null | TReadOnlyProperty<boolean>;
};

type InteractiveSchematicAtomOptions = SelfOptions & NodeOptions;

class InteractiveSchematicAtom extends Node {
private readonly disposeInteractiveSchematicAtom: VoidFunction;

/**
* @param {BuildAnAtomModel} model
* @param {ModelViewTransform2} modelViewTransform
* @param {Object} [options]
*/
constructor( model, modelViewTransform, options ) {
options = merge( {
public constructor( model: BuildAnAtomModel, modelViewTransform: ModelViewTransform2, providedOptions?: InteractiveSchematicAtomOptions ) {
const options = optionize<InteractiveSchematicAtomOptions, SelfOptions, NodeOptions>()( {

// {Property.<boolean>|null} - property that can be used to turn on high-contrast particles
highContrastProperty: null,

tandem: Tandem.REQUIRED
}, options );
}, providedOptions );

super();

const particleViews = []; // remember all the particleViews when using in dispose
const particleViews: ParticleView[] = []; // remember all the particleViews when using in dispose

// Add the node that depicts the textual labels, the electron shells, and the center X marker.
const atomNode = new AtomNode( model.particleAtom, modelViewTransform, {
showElementNameProperty: model.showElementNameProperty,
showNeutralOrIonProperty: model.showNeutralOrIonProperty,
showStableOrUnstableProperty: model.showStableOrUnstableProperty,

// @ts-expect-error - once BuildAnAtomModel is converted to TypeScript, then this won't be a stringProperty anymore
electronShellDepictionProperty: model.electronShellDepictionProperty,
tandem: options.tandem.createTandem( 'atomNode' )
} );
Expand All @@ -51,7 +57,7 @@ class InteractiveSchematicAtom extends Node {
_.each( model.buckets, bucket => this.addChild( new BucketHole( bucket, modelViewTransform ) ) );

// Add the layers where the nucleons will be maintained.
const nucleonLayers = [];
const nucleonLayers: Node[] = [];
_.times( NUM_NUCLEON_LAYERS, () => {
const nucleonLayer = new Node();
nucleonLayers.push( nucleonLayer );
Expand All @@ -75,26 +81,27 @@ class InteractiveSchematicAtom extends Node {
particleViews.push( particleView );

// Add a listener that adjusts a nucleon's z-order layering.
nucleon.zLayerProperty.link( zLayer => {
nucleon.zLayerProperty.link( ( zLayer: number ) => {
assert && assert( nucleonLayers.length > zLayer,
'zLayer for nucleon exceeds number of layers, max number may need increasing.' );

// Determine whether nucleon view is on the correct layer.
let onCorrectLayer = false;
nucleonLayers[ zLayer ].children.forEach( particleView => {
if ( particleView.particle === nucleon ) {
if ( ( particleView as ParticleView ).particle === nucleon ) {
onCorrectLayer = true;
}
} );

if ( !onCorrectLayer ) {

// Remove particle view from its current layer.
let particleView = null;
let particleView: ParticleView | null = null;
for ( let layerIndex = 0; layerIndex < nucleonLayers.length && particleView === null; layerIndex++ ) {
for ( let childIndex = 0; childIndex < nucleonLayers[ layerIndex ].children.length; childIndex++ ) {
if ( nucleonLayers[ layerIndex ].children[ childIndex ].particle === nucleon ) {
particleView = nucleonLayers[ layerIndex ].children[ childIndex ];
const currentParticleView = nucleonLayers[ layerIndex ].children[ childIndex ] as ParticleView;
if ( currentParticleView.particle === nucleon ) {
particleView = currentParticleView;
nucleonLayers[ layerIndex ].removeChildAt( childIndex );
break;
}
Expand All @@ -103,7 +110,7 @@ class InteractiveSchematicAtom extends Node {

// Add the particle view to its new layer.
assert && assert( particleView !== null, 'Particle view not found during relayering' );
nucleonLayers[ zLayer ].addChild( particleView );
nucleonLayers[ zLayer ].addChild( particleView! );
}
} );
} );
Expand All @@ -122,15 +129,15 @@ class InteractiveSchematicAtom extends Node {
const updateElectronVisibility = () => {
electronLayer.getChildren().forEach( electronNode => {
electronNode.visible = model.electronShellDepictionProperty.get() === 'orbits' ||
!model.particleAtom.electrons.includes( electronNode.particle );
!model.particleAtom.electrons.includes( ( electronNode as ParticleView ).particle );
} );
};
model.particleAtom.electrons.lengthProperty.link( updateElectronVisibility );
model.electronShellDepictionProperty.link( updateElectronVisibility );

// Add the front portion of the buckets. This is done separately from the bucket holes for layering purposes.
const bucketGroupTandem = options.tandem.createTandem( 'bucketFronts' ).createGroupTandem( 'bucketFront' );
const bucketFrontsAndDragHandlers = []; // keep track for disposal
const bucketFrontsAndDragHandlers: { dispose: VoidFunction }[] = []; // keep track for disposal
_.each( model.buckets, bucket => {
const bucketFront = new BucketFront( bucket, modelViewTransform, { tandem: bucketGroupTandem.createNextTandem() } );
this.addChild( bucketFront );
Expand All @@ -143,8 +150,6 @@ class InteractiveSchematicAtom extends Node {
bucketFrontsAndDragHandlers.push( bucketFront );
bucketFrontsAndDragHandlers.push( bucketDragListener );
} );

// @private
this.disposeInteractiveSchematicAtom = () => {
particleViews.forEach( particleView => particleView.dispose() );
bucketFrontsAndDragHandlers.forEach( bucketItem => bucketItem.dispose() );
Expand All @@ -156,11 +161,7 @@ class InteractiveSchematicAtom extends Node {
this.mutate( options );
}

/**
* @public
* @override
*/
dispose() {
public override dispose(): void {
this.disposeInteractiveSchematicAtom();
super.dispose();
}
Expand Down
Loading

0 comments on commit e5d99a2

Please sign in to comment.