phetsims / build-a-nucleus

"Build a Nucleus" is an educational simulation in HTML5, by PhET Interactive Simulations.
GNU General Public License v3.0
0 stars 5 forks source link

Add an undo button to the Chart Intro screen #189

Closed Nancy-Salpepi closed 1 year ago

Nancy-Salpepi commented 1 year ago

Test device MacBook Air M1 chip

Operating System 13.5.1

Browser Safari 16.6

Problem description For https://github.com/phetsims/qa/issues/977, I think it would be useful to have an undo button on the Chart Intro screen like there is on the Decay screen. Sometimes, after pressing the Decay button, my eyes are focused on the Nuclide Chart panel and I miss what is happening to the particles.

It looks like there is room next to the Decay button for it (but not sure how dynamic locale would affect that).

Screenshot 2023-09-13 at 2 51 16 PM
zepumph commented 1 year ago

@ariel-phet recommends an investigation to see if this is pretty straight forward (like an hour or two). Most likely the undo button would go right next to the decay button (maybe on the right). You still would only undo the most recent decay (so pressing the button n times quickly doesn't stack up for a larger undo).

zepumph commented 1 year ago

Looking into this now for a bit.

zepumph commented 1 year ago

Here is a messy patch proof-of-concept. I definitely think this is possible, but we are going to want to work out some of the quirks here.

```diff Subject: [PATCH] fix type check typo --- Index: js/common/model/BANModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/BANModel.ts b/js/common/model/BANModel.ts --- a/js/common/model/BANModel.ts (revision 5ba8c7d6b38fa5c9f030c7966eb18d55e5edbfc4) +++ b/js/common/model/BANModel.ts (date 1695066240854) @@ -225,6 +225,7 @@ public addNucleonImmediatelyToAtom( particleType: ParticleType ): void { const particle = new BANParticle( particleType.particleTypeString ); this.addParticle( particle ); + particle.positionProperty.value = this.getParticleDestination( particleType, particle ); this.particleAtom.addParticle( particle ); } Index: js/common/view/BANScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/view/BANScreenView.ts b/js/common/view/BANScreenView.ts --- a/js/common/view/BANScreenView.ts (revision 5ba8c7d6b38fa5c9f030c7966eb18d55e5edbfc4) +++ b/js/common/view/BANScreenView.ts (date 1695065486156) @@ -454,6 +454,34 @@ */ protected abstract addParticleView( particle: Particle ): void; + + /** + * Remove a nucleon of a given particleType from the atom immediately. + */ + private removeNucleonImmediatelyFromAtom( particleType: ParticleType ): void { + const particleToRemove = this.model.particleAtom.extractParticle( particleType.particleTypeString ); + this.animateAndRemoveParticle( particleToRemove ); + } + + /** + * Restore the particleAtom to have the nucleon numbers before a decay occurred. + */ + protected restorePreviousNucleonNumber( particleType: ParticleType, oldNucleonNumber: number ): void { + const newNucleonNumber = particleType === ParticleType.PROTON ? + this.model.particleAtom.protonCountProperty.value : + this.model.particleAtom.neutronCountProperty.value; + const nucleonNumberDifference = oldNucleonNumber - newNucleonNumber; + + for ( let i = 0; i < Math.abs( nucleonNumberDifference ); i++ ) { + if ( nucleonNumberDifference > 0 ) { + this.model.addNucleonImmediatelyToAtom( particleType ); + } + else if ( nucleonNumberDifference < 0 ) { + this.removeNucleonImmediatelyFromAtom( particleType ); + } + } + } + /** * Given a decayType, conduct that decay on the model's ParticleAtom. */ Index: js/chart-intro/view/ChartIntroScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/ChartIntroScreenView.ts b/js/chart-intro/view/ChartIntroScreenView.ts --- a/js/chart-intro/view/ChartIntroScreenView.ts (revision 5ba8c7d6b38fa5c9f030c7966eb18d55e5edbfc4) +++ b/js/chart-intro/view/ChartIntroScreenView.ts (date 1695065712344) @@ -201,11 +201,32 @@ // Whether to show a special highlight for magic-numbered nuclides in the charts. this.showMagicNumbersProperty = new BooleanProperty( false ); + // Store the current nucleon numbers. + let oldProtonNumber: number; + let oldNeutronNumber: number; + // Create the nuclideChartAccordionBox. this.nuclideChartAccordionBox = new NuclideChartAccordionBox( this.model.particleAtom.protonCountProperty, this.model.particleAtom.neutronCountProperty, - this.model.selectedNuclideChartProperty, this.model.decayEquationModel, this.decayAtom.bind( this ), - this.showMagicNumbersProperty, this.model.hasIncomingParticlesProperty, { + this.model.selectedNuclideChartProperty, this.model.decayEquationModel, ( decayType: DecayType | null ) => { + oldProtonNumber = this.model.particleAtom.protonCountProperty.value; + oldNeutronNumber = this.model.particleAtom.neutronCountProperty.value; + this.decayAtom( decayType ); + }, + this.showMagicNumbersProperty, this.model.hasIncomingParticlesProperty, () => { + this.restorePreviousNucleonNumber( ParticleType.PROTON, oldProtonNumber ); + this.restorePreviousNucleonNumber( ParticleType.NEUTRON, oldNeutronNumber ); + + // Remove all particles in the outgoingParticles array from the particles array. + [ ...this.model.outgoingParticles ].forEach( particle => { + this.model.removeParticle( particle ); + } ); + this.model.outgoingParticles.clear(); + this.model.particleAnimations.clear(); + + // Clear all active animations. + this.model.particleAtom.clearAnimations(); + }, { minWidth: periodicTableAndIsotopeSymbol.width } ); Index: js/chart-intro/view/NuclideChartAccordionBox.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts --- a/js/chart-intro/view/NuclideChartAccordionBox.ts (revision 5ba8c7d6b38fa5c9f030c7966eb18d55e5edbfc4) +++ b/js/chart-intro/view/NuclideChartAccordionBox.ts (date 1695065752112) @@ -27,6 +27,7 @@ import buildANucleus from '../../buildANucleus.js'; import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.js'; +import ReturnButton from '../../../../scenery-phet/js/buttons/ReturnButton.js'; type NuclideChartAccordionBoxOptions = AccordionBoxOptions; @@ -37,6 +38,7 @@ decayEquationModel: DecayEquationModel, decayAtom: ( decayType: DecayType | null ) => void, showMagicNumbersProperty: TReadOnlyProperty, hasIncomingParticlesProperty: TReadOnlyProperty, + undoDecay: VoidFunction, providedOptions?: NuclideChartAccordionBoxOptions ) { const options = @@ -81,7 +83,7 @@ const decayEquationNode = new DecayEquationNode( decayEquationModel, zoomInNuclideChartNode.width / 2 ); // Create the 'Decay' button. - const decayPushButton = new TextPushButton( BuildANucleusStrings.screen.decayStringProperty, { + const decayButton = new TextPushButton( BuildANucleusStrings.screen.decayStringProperty, { enabledProperty: new DerivedProperty( [ decayEquationModel.currentCellModelProperty, hasIncomingParticlesProperty @@ -97,16 +99,28 @@ maxWidth: 150, listener: () => { + undoDecayButton.visible = true; // Do the given decay on the atom, if there is a decay. const decayType = decayEquationModel.currentCellModelProperty.value?.decayType; decayAtom( decayType || null ); } } ); + + // Create and add the undo decay button. + const undoDecayButton = new ReturnButton( { + iconOptions: { scale: 0.7 }, + visible: false, + listener: () => { + undoDecayButton.visible = false; + undoDecay(); + } + } ); + // Position the focused chart and the decay button together. const focusedChartAndButtonVBox = new VBox( { children: [ - decayPushButton, + new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ), focusedNuclideChartNode ], spacing: 10, Index: js/decay/view/DecayScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/decay/view/DecayScreenView.ts b/js/decay/view/DecayScreenView.ts --- a/js/decay/view/DecayScreenView.ts (revision 5ba8c7d6b38fa5c9f030c7966eb18d55e5edbfc4) +++ b/js/decay/view/DecayScreenView.ts (date 1695065486144) @@ -255,33 +255,6 @@ return nucleon.positionProperty.value.distance( atomPositionProperty.value ) < NUCLEON_CAPTURE_RADIUS; } - /** - * Restore the particleAtom to have the nucleon numbers before a decay occurred. - */ - private restorePreviousNucleonNumber( particleType: ParticleType, oldNucleonNumber: number ): void { - const newNucleonNumber = particleType === ParticleType.PROTON ? - this.model.particleAtom.protonCountProperty.value : - this.model.particleAtom.neutronCountProperty.value; - const nucleonNumberDifference = oldNucleonNumber - newNucleonNumber; - - for ( let i = 0; i < Math.abs( nucleonNumberDifference ); i++ ) { - if ( nucleonNumberDifference > 0 ) { - this.model.addNucleonImmediatelyToAtom( particleType ); - } - else if ( nucleonNumberDifference < 0 ) { - this.removeNucleonImmediatelyFromAtom( particleType ); - } - } - } - - /** - * Remove a nucleon of a given particleType from the atom immediately. - */ - private removeNucleonImmediatelyFromAtom( particleType: ParticleType ): void { - const particleToRemove = this.model.particleAtom.extractParticle( particleType.particleTypeString ); - this.animateAndRemoveParticle( particleToRemove ); - } - protected override reset(): void { this.symbolAccordionBox.reset(); this.showElectronCloudCheckbox.reset();
zepumph commented 1 year ago

Fixed a disposal bug when undoing alpha decay (endedEmitter=>finishEmitter)

```diff Subject: [PATCH] https://github.com/phetsims/build-a-nucleus/issues/189 --- Index: js/common/model/BANModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/BANModel.ts b/js/common/model/BANModel.ts --- a/js/common/model/BANModel.ts (revision 50898f61cdd5b0c039e4ef60907fb07417da0389) +++ b/js/common/model/BANModel.ts (date 1695223369109) @@ -225,6 +225,7 @@ public addNucleonImmediatelyToAtom( particleType: ParticleType ): void { const particle = new BANParticle( particleType.particleTypeString ); this.addParticle( particle ); + particle.positionProperty.value = this.getParticleDestination( particleType, particle ); this.particleAtom.addParticle( particle ); } Index: js/common/view/BANScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/view/BANScreenView.ts b/js/common/view/BANScreenView.ts --- a/js/common/view/BANScreenView.ts (revision 50898f61cdd5b0c039e4ef60907fb07417da0389) +++ b/js/common/view/BANScreenView.ts (date 1695223402367) @@ -454,6 +454,33 @@ */ protected abstract addParticleView( particle: Particle ): void; + /** + * Remove a nucleon of a given particleType from the atom immediately. + */ + private removeNucleonImmediatelyFromAtom( particleType: ParticleType ): void { + const particleToRemove = this.model.particleAtom.extractParticle( particleType.particleTypeString ); + this.animateAndRemoveParticle( particleToRemove ); + } + + /** + * Restore the particleAtom to have the nucleon numbers before a decay occurred. + */ + protected restorePreviousNucleonNumber( particleType: ParticleType, oldNucleonNumber: number ): void { + const newNucleonNumber = particleType === ParticleType.PROTON ? + this.model.particleAtom.protonCountProperty.value : + this.model.particleAtom.neutronCountProperty.value; + const nucleonNumberDifference = oldNucleonNumber - newNucleonNumber; + + for ( let i = 0; i < Math.abs( nucleonNumberDifference ); i++ ) { + if ( nucleonNumberDifference > 0 ) { + this.model.addNucleonImmediatelyToAtom( particleType ); + } + else if ( nucleonNumberDifference < 0 ) { + this.removeNucleonImmediatelyFromAtom( particleType ); + } + } + } + /** * Given a decayType, conduct that decay on the model's ParticleAtom. */ Index: js/chart-intro/view/ChartIntroScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/ChartIntroScreenView.ts b/js/chart-intro/view/ChartIntroScreenView.ts --- a/js/chart-intro/view/ChartIntroScreenView.ts (revision 50898f61cdd5b0c039e4ef60907fb07417da0389) +++ b/js/chart-intro/view/ChartIntroScreenView.ts (date 1695228794833) @@ -201,11 +201,32 @@ // Whether to show a special highlight for magic-numbered nuclides in the charts. this.showMagicNumbersProperty = new BooleanProperty( false ); + // Store the current nucleon numbers. + let oldProtonNumber: number; + let oldNeutronNumber: number; + // Create the nuclideChartAccordionBox. this.nuclideChartAccordionBox = new NuclideChartAccordionBox( this.model.particleAtom.protonCountProperty, this.model.particleAtom.neutronCountProperty, - this.model.selectedNuclideChartProperty, this.model.decayEquationModel, this.decayAtom.bind( this ), - this.showMagicNumbersProperty, this.model.hasIncomingParticlesProperty, { + this.model.selectedNuclideChartProperty, this.model.decayEquationModel, ( decayType: DecayType | null ) => { + oldProtonNumber = this.model.particleAtom.protonCountProperty.value; + oldNeutronNumber = this.model.particleAtom.neutronCountProperty.value; + this.decayAtom( decayType ); + }, + this.showMagicNumbersProperty, this.model.hasIncomingParticlesProperty, () => { + this.restorePreviousNucleonNumber( ParticleType.PROTON, oldProtonNumber ); + this.restorePreviousNucleonNumber( ParticleType.NEUTRON, oldNeutronNumber ); + + // Remove all particles in the outgoingParticles array from the particles array. + [ ...this.model.outgoingParticles ].forEach( particle => { + this.model.removeParticle( particle ); + } ); + this.model.outgoingParticles.clear(); + this.model.particleAnimations.clear(); + + // Clear all active animations. + this.model.particleAtom.clearAnimations(); + }, { minWidth: periodicTableAndIsotopeSymbol.width } ); @@ -338,7 +359,7 @@ duration: FADE_ANINIMATION_DURATION, easing: Easing.LINEAR } ); - endedEmitterListener && fadeAnimation.endedEmitter.addListener( endedEmitterListener ); + endedEmitterListener && fadeAnimation.finishEmitter.addListener( endedEmitterListener ); this.model.particleAnimations.push( fadeAnimation ); fadeAnimation.start(); } Index: js/chart-intro/view/NuclideChartAccordionBox.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts --- a/js/chart-intro/view/NuclideChartAccordionBox.ts (revision 50898f61cdd5b0c039e4ef60907fb07417da0389) +++ b/js/chart-intro/view/NuclideChartAccordionBox.ts (date 1695228666857) @@ -27,6 +27,7 @@ import buildANucleus from '../../buildANucleus.js'; import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.js'; +import ReturnButton from '../../../../scenery-phet/js/buttons/ReturnButton.js'; type NuclideChartAccordionBoxOptions = AccordionBoxOptions; @@ -37,6 +38,7 @@ decayEquationModel: DecayEquationModel, decayAtom: ( decayType: DecayType | null ) => void, showMagicNumbersProperty: TReadOnlyProperty, hasIncomingParticlesProperty: TReadOnlyProperty, + undoDecay: VoidFunction, providedOptions?: NuclideChartAccordionBoxOptions ) { const options = @@ -81,7 +83,7 @@ const decayEquationNode = new DecayEquationNode( decayEquationModel, zoomInNuclideChartNode.width / 2 ); // Create the 'Decay' button. - const decayPushButton = new TextPushButton( BuildANucleusStrings.screen.decayStringProperty, { + const decayButton = new TextPushButton( BuildANucleusStrings.screen.decayStringProperty, { enabledProperty: new DerivedProperty( [ decayEquationModel.currentCellModelProperty, hasIncomingParticlesProperty @@ -97,16 +99,27 @@ maxWidth: 150, listener: () => { + undoDecayButton.visible = true; // Do the given decay on the atom, if there is a decay. const decayType = decayEquationModel.currentCellModelProperty.value?.decayType; decayAtom( decayType || null ); } } ); + // Create and add the undo decay button. + const undoDecayButton = new ReturnButton( { + iconOptions: { scale: 0.7 }, + visible: false, + listener: () => { + undoDecayButton.visible = false; + undoDecay(); + } + } ); + // Position the focused chart and the decay button together. const focusedChartAndButtonVBox = new VBox( { children: [ - decayPushButton, + new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ), focusedNuclideChartNode ], spacing: 10, Index: js/decay/view/DecayScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/decay/view/DecayScreenView.ts b/js/decay/view/DecayScreenView.ts --- a/js/decay/view/DecayScreenView.ts (revision 50898f61cdd5b0c039e4ef60907fb07417da0389) +++ b/js/decay/view/DecayScreenView.ts (date 1695223369137) @@ -257,33 +257,6 @@ return nucleon.positionProperty.value.distance( atomPositionProperty.value ) < NUCLEON_CAPTURE_RADIUS; } - /** - * Restore the particleAtom to have the nucleon numbers before a decay occurred. - */ - private restorePreviousNucleonNumber( particleType: ParticleType, oldNucleonNumber: number ): void { - const newNucleonNumber = particleType === ParticleType.PROTON ? - this.model.particleAtom.protonCountProperty.value : - this.model.particleAtom.neutronCountProperty.value; - const nucleonNumberDifference = oldNucleonNumber - newNucleonNumber; - - for ( let i = 0; i < Math.abs( nucleonNumberDifference ); i++ ) { - if ( nucleonNumberDifference > 0 ) { - this.model.addNucleonImmediatelyToAtom( particleType ); - } - else if ( nucleonNumberDifference < 0 ) { - this.removeNucleonImmediatelyFromAtom( particleType ); - } - } - } - - /** - * Remove a nucleon of a given particleType from the atom immediately. - */ - private removeNucleonImmediatelyFromAtom( particleType: ParticleType ): void { - const particleToRemove = this.model.particleAtom.extractParticle( particleType.particleTypeString ); - this.animateAndRemoveParticle( particleToRemove ); - } - protected override reset(): void { this.symbolAccordionBox.reset(); this.showElectronCloudCheckbox.reset();
zepumph commented 1 year ago

I see @Luisav1 committed above, and there is one TODO. @Luisav1, can you please take a look at this patch and see if it removes the TODO in a good enough way?


Subject: [PATCH] https://github.com/phetsims/build-a-nucleus/issues/189
---
Index: js/common/view/BANScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/view/BANScreenView.ts b/js/common/view/BANScreenView.ts
--- a/js/common/view/BANScreenView.ts   (revision b7546c115774d65bf6680a55bc914bf370b3b4e2)
+++ b/js/common/view/BANScreenView.ts   (date 1695310287833)
@@ -177,16 +177,14 @@
     // hideUndoButtonEmitter in a beta decay
     // TODO: hacky, what's a better way to 'defer' the mass number property here only? https://github.com/phetsims/build-a-nucleus/issues/189
     let oldMassNumber: number;
-    let olderMassNumber: number;

     // Hide the undo decay button if anything in the nucleus changes.
-    Multilink.multilink( [ this.model.particleAtom.massNumberProperty, this.model.userControlledProtons.lengthProperty,
+    Multilink.lazyMultilink( [ this.model.particleAtom.massNumberProperty, this.model.userControlledProtons.lengthProperty,
       this.model.incomingProtons.lengthProperty, this.model.incomingNeutrons.lengthProperty,
       this.model.userControlledNeutrons.lengthProperty ], massNumber => {
-      if ( !( olderMassNumber === massNumber ) ) {
+      if ( oldMassNumber !== massNumber ) {
         BANScreenView.hideUndoButtonEmitter.emit();
       }
-      olderMassNumber = oldMassNumber;
       oldMassNumber = massNumber;
     } );
zepumph commented 1 year ago

This issue is that we alter the atom mass counts async after animating the change, but only for the chart intro. Even if we didn't have the emitter I'm unsure how we would make this work well.

https://github.com/phetsims/build-a-nucleus/blob/b7546c115774d65bf6680a55bc914bf370b3b4e2/js/common/view/BANScreenView.ts#L657-L663 @Luisav1 perhaps we should pair on this.

zepumph commented 1 year ago

Ahhh, @Luisav1 helped me understand the problem better, it is about the chart intro screen's fade in/out, not the nucleon type change.

@Luisav1 here is a patch that cleans up some of the workarounds in your commit, and adds a TODO where we should try out deferring.

```diff Subject: [PATCH] use 30 for touch areas, https://github.com/phetsims/build-a-nucleus/issues/197 --- Index: js/common/view/BANScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/view/BANScreenView.ts b/js/common/view/BANScreenView.ts --- a/js/common/view/BANScreenView.ts (revision a28678768af543e8dfb79f2b40807facb4eeb232) +++ b/js/common/view/BANScreenView.ts (date 1695315727515) @@ -173,21 +173,11 @@ particle.dispose(); } ); - // Store the previous mass number and mass number two sim states prior so massNumberProperty changes does not fire - // hideUndoButtonEmitter in a beta decay - // TODO: hacky, what's a better way to 'defer' the mass number property here only? https://github.com/phetsims/build-a-nucleus/issues/189 - let oldMassNumber: number; - let olderMassNumber: number; - // Hide the undo decay button if anything in the nucleus changes. Multilink.multilink( [ this.model.particleAtom.massNumberProperty, this.model.userControlledProtons.lengthProperty, this.model.incomingProtons.lengthProperty, this.model.incomingNeutrons.lengthProperty, this.model.userControlledNeutrons.lengthProperty ], massNumber => { - if ( !( olderMassNumber === massNumber ) ) { - BANScreenView.hideUndoButtonEmitter.emit(); - } - olderMassNumber = oldMassNumber; - oldMassNumber = massNumber; + BANScreenView.hideUndoButtonEmitter.emit(); } ); // Create the particleAtomNode but add it in subclasses so particles are in top layer. Index: js/chart-intro/view/ChartIntroScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/ChartIntroScreenView.ts b/js/chart-intro/view/ChartIntroScreenView.ts --- a/js/chart-intro/view/ChartIntroScreenView.ts (revision a28678768af543e8dfb79f2b40807facb4eeb232) +++ b/js/chart-intro/view/ChartIntroScreenView.ts (date 1695315796518) @@ -387,6 +387,7 @@ const particleToEmit = super.betaDecay( betaDecayType, this.model.miniParticleAtom ); this.createMiniParticleView( particleToEmit ); + // TODO: defer mass notifications through the fade in and out so it doesn't seem like a mass change. https://github.com/phetsims/build-a-nucleus/issues/189 // Animate the NucleonShellView. const newNucleonType = nucleonTypeToChange === ParticleType.PROTON ? ParticleType.NEUTRON : ParticleType.PROTON; Index: js/chart-intro/view/NuclideChartAccordionBox.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts --- a/js/chart-intro/view/NuclideChartAccordionBox.ts (revision a28678768af543e8dfb79f2b40807facb4eeb232) +++ b/js/chart-intro/view/NuclideChartAccordionBox.ts (date 1695313321620) @@ -114,7 +114,6 @@ iconOptions: { scale: 0.7 }, visible: false, listener: () => { - BANScreenView.hideUndoButtonEmitter.emit(); undoDecay(); } } ); Index: js/decay/view/DecayScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/decay/view/DecayScreenView.ts b/js/decay/view/DecayScreenView.ts --- a/js/decay/view/DecayScreenView.ts (revision a28678768af543e8dfb79f2b40807facb4eeb232) +++ b/js/decay/view/DecayScreenView.ts (date 1695312127526) @@ -99,7 +99,6 @@ iconOptions: { scale: 0.7 }, visible: false, listener: () => { - BANScreenView.hideUndoButtonEmitter.emit(); this.undoDecay( oldProtonNumber, oldNeutronNumber ); } } );
zepumph commented 1 year ago

Taking a look now.

zepumph commented 1 year ago

Ok. @Luisav1, the weirdness is in that we were depending on the a model step to add the new particle to the atoms. Is there a way that we could not animate it and then not rely on the animation ending emitter?

Right now the hack I exchanged your hack for is stepping the model, but there must be a way to just add the particle immediately to the particle atom from the stack. Isn't that how we support the initial count query parameters?

Luisav1 commented 1 year ago

@zepumph You're right! In the commit above we're now using addNucleonImmediatelyToAtom() to add the new particle and I use fadeAnimation still to fade it in.

For the removal of the old particle though it's harder since we want to fade it out before removal so removeNucleonImmediatelyFromAtom() wouldn't work as it is. So I first fade the particle out then use animateAndRemoveParticle() to remove the particle at the end of the fade animation.

zepumph commented 1 year ago

But the removal doesn't matter too much since it is sync removed from the particleAtom, (which is where the mass is changed). Right?

Luisav1 commented 1 year ago

Oh right, yes, that's correct. The old particle is removed from the particleAtom with extractParticle(), followed almost immediately by addNucleonImmediatelyToAtom. Which is why even though the mass number changes, it is rapid enough that nothing in the particleAtom changes afterward to cause the undo button to hide again.

Luisav1 commented 1 year ago

Based on a slack conversation of the buttons placement: Ariel Paul 6:54 PM I think the undo button is working well. I cannot say I love the placement. Things just feel a bit unbalanced. I wonder if it would work to center the decay button above the partial chart. I think the undo button would still fit within the panel. Or alternately move the undo button to be more in line with the decay equation? So center the Decay button above the partial chart (instead of the left alignment it has now, and then move the undo button above the decay button, more associated with the decay equation).

Here are some very rough patches (since they both have issues with stringTest=dynamic) for the undo button placement.

Patch and picture of the undo button beside the decay button: image

```diff Subject: [PATCH] Undo button to the right of centered decay button. --- Index: js/chart-intro/view/NuclideChartAccordionBox.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts --- a/js/chart-intro/view/NuclideChartAccordionBox.ts (revision c7c1b169e5ebbcbfaada6435d7c284c38c1e19b0) +++ b/js/chart-intro/view/NuclideChartAccordionBox.ts (date 1695417345652) @@ -11,7 +11,7 @@ import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js'; import NuclideChartAndNumberLines from './NuclideChartAndNumberLines.js'; import BuildANucleusStrings from '../../BuildANucleusStrings.js'; -import { HBox, Text, VBox } from '../../../../scenery/js/imports.js'; +import { HBox, Rectangle, Text, VBox } from '../../../../scenery/js/imports.js'; import NuclideChartLegendNode from './NuclideChartLegendNode.js'; import { SelectedChartType } from '../model/ChartIntroModel.js'; import BANConstants from '../../common/BANConstants.js'; @@ -74,6 +74,9 @@ } ); const focusedNuclideChartNode = new FocusedNuclideChartNode( protonCountProperty, neutronCountProperty, focusedChartTransform, showMagicNumbersProperty ); + //focusedNuclideChartNode.bounds.dilateX( 10 ); + const focusedChartRectangle = new Rectangle( focusedNuclideChartNode.bounds.dilateX( 11 ) ); + focusedChartRectangle.addChild( focusedNuclideChartNode ); const zoomInNuclideChartNode = new ZoomInNuclideChartNode( protonCountProperty, neutronCountProperty, zoomInChartTransform, showMagicNumbersProperty ); @@ -97,7 +100,7 @@ fontSize: 14 }, minWidth: 80, - maxWidth: 150, + maxWidth: 120, listener: () => { // Do the given decay on the atom, if there is a decay. @@ -122,12 +125,20 @@ // Position the focused chart and the decay button together. const focusedChartAndButtonVBox = new VBox( { children: [ - new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ), - focusedNuclideChartNode + new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: true } ), + focusedChartRectangle ], spacing: 10, align: 'center' } ); + undoDecayButton.visibleProperty.link( visible => { + if ( visible ) { + focusedChartAndButtonVBox.setAlign( 'right' ); + } + else { + focusedChartAndButtonVBox.setAlign( 'center' ); + } + } ); // Update the view in the accordion box depending on the selectedNuclideChartProperty. selectedNuclideChartProperty.link( selectedNuclideChart => { ```

Patch and picture of undo button on top of decay button: image

```diff Subject: [PATCH] Undo button centered above decay button and to the right of the decay equation node. --- Index: js/chart-intro/view/NuclideChartAccordionBox.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts --- a/js/chart-intro/view/NuclideChartAccordionBox.ts (revision c7c1b169e5ebbcbfaada6435d7c284c38c1e19b0) +++ b/js/chart-intro/view/NuclideChartAccordionBox.ts (date 1695428821322) @@ -11,7 +11,7 @@ import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js'; import NuclideChartAndNumberLines from './NuclideChartAndNumberLines.js'; import BuildANucleusStrings from '../../BuildANucleusStrings.js'; -import { HBox, Text, VBox } from '../../../../scenery/js/imports.js'; +import { HBox, Rectangle, Text, VBox } from '../../../../scenery/js/imports.js'; import NuclideChartLegendNode from './NuclideChartLegendNode.js'; import { SelectedChartType } from '../model/ChartIntroModel.js'; import BANConstants from '../../common/BANConstants.js'; @@ -31,6 +31,7 @@ import BANScreenView from '../../common/view/BANScreenView.js'; type NuclideChartAccordionBoxOptions = AccordionBoxOptions; +const X_SPACING_BETWEEN_CHARTS = 10; class NuclideChartAccordionBox extends AccordionBox { @@ -122,19 +123,36 @@ // Position the focused chart and the decay button together. const focusedChartAndButtonVBox = new VBox( { children: [ - new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ), + decayButton, focusedNuclideChartNode ], spacing: 10, align: 'center' } ); + // Add the undo decay button and decayEquationNode together in an HBox. + const decayEquationAndUndoButtonHBox = new HBox( { + children: [ decayEquationNode, undoDecayButton ], + spacing: 10, + align: 'bottom', + excludeInvisibleChildrenFromBounds: true, + + // Such that the undo button appears centered above the decayButton but is still in line with the decayEquationNode. + preferredWidth: zoomInNuclideChartNode.width + + ( focusedChartAndButtonVBox.width / 2 ) + + ( undoDecayButton.width / 2 ) + + X_SPACING_BETWEEN_CHARTS + } ); + // TODO: why doesn't this work by using the decayEquationAndUndoButtonHBox directly? + const hBoxBounds = new Rectangle( decayEquationAndUndoButtonHBox.bounds ); + hBoxBounds.addChild( decayEquationAndUndoButtonHBox ); + // Update the view in the accordion box depending on the selectedNuclideChartProperty. selectedNuclideChartProperty.link( selectedNuclideChart => { zoomInNuclideChartNode.visible = selectedNuclideChart === 'zoom'; focusedChartAndButtonVBox.visible = selectedNuclideChart === 'zoom'; - decayEquationNode.visible = selectedNuclideChart === 'zoom'; + hBoxBounds.visible = selectedNuclideChart === 'zoom'; nuclideChartAndNumberLines.visible = selectedNuclideChart === 'partial'; } ); @@ -148,13 +166,13 @@ nuclideChartAndNumberLines, focusedChartAndButtonVBox ], - spacing: 10, + spacing: X_SPACING_BETWEEN_CHARTS, align: 'top', excludeInvisibleChildrenFromBounds: true } ); const contentVBox = new VBox( { children: [ - decayEquationNode, + hBoxBounds, chartsHBox, nuclideChartLegendNode ], ```

I think with string lengths varying for 'Decay', it would be nicer to have the undo button be above the Decay button.

Luisav1 commented 1 year ago

Meeting with @zepumph, there are three things we should know and try for the button placement.

  1. This is the farthest to the right that the decay equation can go, it takes up a lot of space: image
  2. From second patch in comment above, try moving decay button and undo button up to be in line with decay equation. image Patch:
Subject: [PATCH] undo button and decay button up with the decay equation
---
Index: js/chart-intro/view/NuclideChartAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts
--- a/js/chart-intro/view/NuclideChartAccordionBox.ts   (revision cc8933b1601bd97b681f55a22367255042dc2824)
+++ b/js/chart-intro/view/NuclideChartAccordionBox.ts   (date 1695676587100)
@@ -11,7 +11,7 @@
 import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js';
 import NuclideChartAndNumberLines from './NuclideChartAndNumberLines.js';
 import BuildANucleusStrings from '../../BuildANucleusStrings.js';
-import { HBox, Text, VBox } from '../../../../scenery/js/imports.js';
+import { HBox, Rectangle, Text, VBox } from '../../../../scenery/js/imports.js';
 import NuclideChartLegendNode from './NuclideChartLegendNode.js';
 import { SelectedChartType } from '../model/ChartIntroModel.js';
 import BANConstants from '../../common/BANConstants.js';
@@ -31,6 +31,7 @@
 import BANScreenView from '../../common/view/BANScreenView.js';

 type NuclideChartAccordionBoxOptions = AccordionBoxOptions;
+const X_SPACING_BETWEEN_CHARTS = 10;

 class NuclideChartAccordionBox extends AccordionBox {

@@ -119,22 +120,29 @@
     } );
     BANScreenView.hideUndoButtonEmitter.addListener( () => { undoDecayButton.visible = false; } );

-    // Position the focused chart and the decay button together.
-    const focusedChartAndButtonVBox = new VBox( {
-      children: [
-        new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ),
-        focusedNuclideChartNode
-      ],
+    const undoDecayAndDecayButtonVBox = new VBox( {
+      children: [ undoDecayButton, decayButton ],
+      spacing: 5,
+      align: 'center',
+      excludeInvisibleChildrenFromBounds: false
+    } );
+
+    // Add the undo decay button and decayEquationNode together in an HBox.
+    const decayEquationAndButtonsHBox = new HBox( {
+      children: [ decayEquationNode, undoDecayAndDecayButtonVBox ],
       spacing: 10,
       align: 'center'
     } );
+    // TODO: why doesn't this work by using the decayEquationAndButtonsHBox directly?
+    const hBoxBounds = new Rectangle( decayEquationAndButtonsHBox.bounds );
+    hBoxBounds.addChild( decayEquationAndButtonsHBox );

     // Update the view in the accordion box depending on the selectedNuclideChartProperty.
     selectedNuclideChartProperty.link( selectedNuclideChart => {

       zoomInNuclideChartNode.visible = selectedNuclideChart === 'zoom';
-      focusedChartAndButtonVBox.visible = selectedNuclideChart === 'zoom';
-      decayEquationNode.visible = selectedNuclideChart === 'zoom';
+      focusedNuclideChartNode.visible = selectedNuclideChart === 'zoom';
+      hBoxBounds.visible = selectedNuclideChart === 'zoom';

       nuclideChartAndNumberLines.visible = selectedNuclideChart === 'partial';
     } );
@@ -146,15 +154,16 @@
         // done for the "Stable" text in the DecayEquationNode.
         zoomInNuclideChartNode,
         nuclideChartAndNumberLines,
-        focusedChartAndButtonVBox
+        focusedNuclideChartNode
       ],
-      spacing: 10,
-      align: 'top',
+      spacing: X_SPACING_BETWEEN_CHARTS,
+      align: 'center',
       excludeInvisibleChildrenFromBounds: true
     } );
+    decayEquationAndButtonsHBox.preferredWidth = chartsHBox.bounds.width;
     const contentVBox = new VBox( {
       children: [
-        decayEquationNode,
+        hBoxBounds,
         chartsHBox,
         nuclideChartLegendNode
       ],

  1. The button placement to the left of the decay button: image Patch:

Subject: [PATCH] undo button to left of decay button
---
Index: js/chart-intro/view/NuclideChartAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts
--- a/js/chart-intro/view/NuclideChartAccordionBox.ts   (revision cc8933b1601bd97b681f55a22367255042dc2824)
+++ b/js/chart-intro/view/NuclideChartAccordionBox.ts   (date 1695673652575)
@@ -11,7 +11,7 @@
 import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js';
 import NuclideChartAndNumberLines from './NuclideChartAndNumberLines.js';
 import BuildANucleusStrings from '../../BuildANucleusStrings.js';
-import { HBox, Text, VBox } from '../../../../scenery/js/imports.js';
+import { HBox, Rectangle, Text, VBox } from '../../../../scenery/js/imports.js';
 import NuclideChartLegendNode from './NuclideChartLegendNode.js';
 import { SelectedChartType } from '../model/ChartIntroModel.js';
 import BANConstants from '../../common/BANConstants.js';
@@ -74,6 +74,8 @@
       } );
     const focusedNuclideChartNode = new FocusedNuclideChartNode(
       protonCountProperty, neutronCountProperty, focusedChartTransform, showMagicNumbersProperty );
+    const focusedChartRectangle = new Rectangle( focusedNuclideChartNode.bounds.dilateX( 11 ) );
+    focusedChartRectangle.addChild( focusedNuclideChartNode );
     const zoomInNuclideChartNode = new ZoomInNuclideChartNode(
       protonCountProperty, neutronCountProperty, zoomInChartTransform, showMagicNumbersProperty );

@@ -97,7 +99,7 @@
         fontSize: 14
       },
       minWidth: 80,
-      maxWidth: 150,
+      maxWidth: 120,
       listener: () => {

         // Do the given decay on the atom, if there is a decay.
@@ -122,12 +124,20 @@
     // Position the focused chart and the decay button together.
     const focusedChartAndButtonVBox = new VBox( {
       children: [
-        new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ),
-        focusedNuclideChartNode
+        new HBox( { children: [ undoDecayButton, decayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: true } ),
+        focusedChartRectangle
       ],
       spacing: 10,
       align: 'center'
     } );
+    undoDecayButton.visibleProperty.link( visible => {
+      if ( visible ) {
+        focusedChartAndButtonVBox.setAlign( 'left' );
+      }
+      else {
+        focusedChartAndButtonVBox.setAlign( 'center' );
+      }
+    } );

     // Update the view in the accordion box depending on the selectedNuclideChartProperty.
     selectedNuclideChartProperty.link( selectedNuclideChart => {
zepumph commented 1 year ago

I think My favorite is 3' where the decay and undo buttons are still centered:

image

image


Subject: [PATCH] update TODO, https://github.com/phetsims/phet-io-wrappers/issues/507
---
Index: js/chart-intro/view/NuclideChartAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/chart-intro/view/NuclideChartAccordionBox.ts b/js/chart-intro/view/NuclideChartAccordionBox.ts
--- a/js/chart-intro/view/NuclideChartAccordionBox.ts   (revision cc8933b1601bd97b681f55a22367255042dc2824)
+++ b/js/chart-intro/view/NuclideChartAccordionBox.ts   (date 1695682409317)
@@ -122,7 +122,7 @@
     // Position the focused chart and the decay button together.
     const focusedChartAndButtonVBox = new VBox( {
       children: [
-        new HBox( { children: [ decayButton, undoDecayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ),
+        new HBox( { children: [ undoDecayButton, decayButton ], spacing: 5, excludeInvisibleChildrenFromBounds: false } ),
         focusedNuclideChartNode
       ],
       spacing: 10,
zepumph commented 1 year ago

Let's touch base with @ariel-phet.

Luisav1 commented 1 year ago

Meeting with @ariel-phet, we will be going with the last option where they are just swapped from their current version. But also want to bring in some internationalization maxWidth's.

Luisav1 commented 1 year ago

Move undo button to left of decay button and decrease decay button maxWidth in commit above. Closing.