Skip to content

Commit

Permalink
Creating ReadoutListAccordionBox and factoring out the shared logic, …
Browse files Browse the repository at this point in the history
  • Loading branch information
AgustinVallejo committed Mar 12, 2024
1 parent 9769ba2 commit 4a42d93
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 167 deletions.
13 changes: 7 additions & 6 deletions js/buoyancy/view/BuoyancyApplicationsScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,12 @@ export default class BuoyancyApplicationsScreenView extends DensityBuoyancyScree
margin: MARGIN
} ) );

const densityBox = new DensityAccordionBox(
[], {
expandedProperty: model.densityExpandedProperty
} );
const displayOptionsNode = new DisplayOptionsNode( model );

const densityBox = new DensityAccordionBox( {
expandedProperty: model.densityExpandedProperty,
contentWidthMax: displayOptionsNode.width
} );

model.sceneProperty.link( scene => {
const materials = scene === Scene.BOTTLE ? [
Expand All @@ -263,12 +265,11 @@ export default class BuoyancyApplicationsScreenView extends DensityBuoyancyScree
model.boat.materialProperty
] : [];
assert && assert( materials.length > 0, 'unsupported Scene', scene );
densityBox.setMaterials( materials.map( material => {
densityBox.setReadout( materials.map( material => {
return { materialProperty: material };
} ) );
} );

const displayOptionsNode = new DisplayOptionsNode( model );

this.addChild( new AlignBox( new VBox( {
spacing: 10,
Expand Down
17 changes: 12 additions & 5 deletions js/buoyancy/view/BuoyancyExploreScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,33 @@ export default class BuoyancyExploreScreenView extends SecondaryMassScreenView<B
} );

// Materials are set in densityBox.setMaterials() below
const densityBox = new DensityAccordionBox(
[], {
const densityBox = new DensityAccordionBox( {
expandedProperty: model.densityExpandedProperty,
contentWidthMax: this.rightBox.content.width
} );

const submergedBox = new SubmergedAccordionBox( [ model.primaryMass ], model.gravityProperty, model.liquidMaterialProperty );
const submergedBox = new SubmergedAccordionBox( model.gravityProperty, model.liquidMaterialProperty, {
contentWidthMax: this.rightBox.content.width
} );

// Adjust the visibility after, since we want to size the box's location for its "full" bounds
// This instance lives for the lifetime of the simulation, so we don't need to remove this listener
model.secondaryMass.visibleProperty.link( visible => {
const masses = visible ? [ model.primaryMass, model.secondaryMass ] : [ model.primaryMass ];
densityBox.setMaterials( masses.map( ( mass, index ) => {
densityBox.setReadout( masses.map( ( mass, index ) => {
return {
materialProperty: mass.materialProperty,
customNameProperty: customExploreScreenFormatting.customNames[ index ],
customFormat: customExploreScreenFormatting.customFormats[ index ]
};
} ) );
submergedBox.setSubmergedVolumes( masses, customExploreScreenFormatting );
submergedBox.setReadout( masses.map( ( mass, index ) => {
return {
mass: mass,
customNameProperty: customExploreScreenFormatting.customNames[ index ],
customFormat: customExploreScreenFormatting.customFormats[ index ]
};
} ) );
} );

const rightSideVBox = new VBox( {
Expand Down
15 changes: 8 additions & 7 deletions js/buoyancy/view/BuoyancyLabScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,18 @@ export default class BuoyancyLabScreenView extends DensityBuoyancyScreenView<Buo
}
} );

const densityBox = new DensityAccordionBox(
[ { materialProperty: model.primaryMass.materialProperty } ], {
const densityBox = new DensityAccordionBox( {
expandedProperty: model.densityExpandedProperty,
contentWidthMax: this.rightBox.content.width
} );
densityBox.setReadout( [ { materialProperty: model.primaryMass.materialProperty } ] );

const submergedBox = new SubmergedAccordionBox( [ model.primaryMass ], model.gravityProperty, model.liquidMaterialProperty, {
setSubmergedVolumesOptions: {
customNames: [ DensityBuoyancyCommonStrings.blockAStringProperty ]
}
} );
const submergedBox = new SubmergedAccordionBox(
model.gravityProperty, model.liquidMaterialProperty, {
contentWidthMax: this.rightBox.content.width
} );
submergedBox.setReadout( [ { mass: model.primaryMass,
customNameProperty: DensityBuoyancyCommonStrings.blockAStringProperty } ] );

const rightSideVBox = new VBox( {
spacing: 10,
Expand Down
4 changes: 2 additions & 2 deletions js/buoyancy/view/BuoyancyShapesScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,11 @@ export default class BuoyancyShapesScreenView extends SecondaryMassScreenView<Bu
) ]
);

const densityBox = new DensityAccordionBox(
[ { materialProperty: model.materialProperty } ], {
const densityBox = new DensityAccordionBox( {
expandedProperty: model.densityExpandedProperty,
contentWidthMax: this.rightBox.content.width
} );
densityBox.setReadout( [ { materialProperty: model.materialProperty } ] );

const rightSideVBox = new VBox( {
spacing: 10,
Expand Down
90 changes: 14 additions & 76 deletions js/buoyancy/view/DensityAccordionBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,92 +9,33 @@
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import DynamicProperty from '../../../../axon/js/DynamicProperty.js';
import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import { HBox, RichText, RichTextOptions, Text, VBox } from '../../../../scenery/js/imports.js';
import { HBox, RichText, RichTextOptions } from '../../../../scenery/js/imports.js';
import Material from '../../common/model/Material.js';
import densityBuoyancyCommon from '../../densityBuoyancyCommon.js';
import DensityBuoyancyCommonStrings from '../../DensityBuoyancyCommonStrings.js';
import TinyEmitter from '../../../../axon/js/TinyEmitter.js';
import DensityBuoyancyCommonConstants from '../../common/DensityBuoyancyCommonConstants.js';
import StringUtils from '../../../../phetcommon/js/util/StringUtils.js';
import Utils from '../../../../dot/js/Utils.js';
import { combineOptions, optionize4 } from '../../../../phet-core/js/optionize.js';
import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js';
import { combineOptions } from '../../../../phet-core/js/optionize.js';
import ReadoutListAccordionBox, { CustomReadoutObject, ReadoutListAccordionBoxOptions } from './ReadoutListAccordionBox.js';

const DEFAULT_FONT = new PhetFont( 14 );
const HBOX_SPACING = 5;
const DEFAULT_CONTENT_WIDTH = ( 140 + HBOX_SPACING ) / 2;

type SetMaterialsOptions = {
// Arrays should correspond to the provided materialProperties
customNames?: TReadOnlyProperty<string>[] | null;
customFormats?: RichTextOptions[] | null;
};

type SelfOptions = {
// Provided to the constructor call of setMaterials()
setMaterialsOptions?: SetMaterialsOptions;

// Provide the ideal max content width for the accordion box content. This is used to apply maxWidths to the Texts of the readout.
contentWidthMax?: number;
};

type CustomMaterial = {
materialProperty: TReadOnlyProperty<Material>;
customNameProperty?: TReadOnlyProperty<string>;
customFormat?: RichTextOptions;
};

type DensityAccordionBoxOptions = SelfOptions & AccordionBoxOptions;

export default class DensityAccordionBox extends AccordionBox {

private readonly densityReadoutBox: VBox;
private cleanupEmitter = new TinyEmitter();

private readonly contentWidthMax: number;

export default class DensityAccordionBox extends ReadoutListAccordionBox {
public constructor(
customMaterials: CustomMaterial[],
providedOptions?: DensityAccordionBoxOptions
providedOptions?: ReadoutListAccordionBoxOptions
) {

const options = optionize4<DensityAccordionBoxOptions, SelfOptions, AccordionBoxOptions>()( {},
DensityBuoyancyCommonConstants.ACCORDION_BOX_OPTIONS, {
titleNode: new Text( DensityBuoyancyCommonStrings.densityStringProperty, {
font: DensityBuoyancyCommonConstants.TITLE_FONT,
maxWidth: 160
} ),
layoutOptions: { stretch: true },
setMaterialsOptions: {},
contentWidthMax: DEFAULT_CONTENT_WIDTH
}, providedOptions );

const densityReadout = new VBox( {
spacing: 5,
align: 'center'
} );

super( densityReadout, options );
super( DensityBuoyancyCommonStrings.densityStringProperty, providedOptions );

this.densityReadoutBox = densityReadout;
this.contentWidthMax = options.contentWidthMax;
this.setMaterials( customMaterials );
}

/**
* Overwrite the displayed densities with a new set of materialProperties.
*/
public setMaterials( customMaterials: CustomMaterial[] ): void {

// Clear the previous materials that may have been created.
this.cleanupEmitter.emit();
this.cleanupEmitter.removeAllListeners();
public override setReadout( customMaterials: CustomReadoutObject[] ): void {

const textOptions = {
font: DEFAULT_FONT,
maxWidth: ( this.contentWidthMax - HBOX_SPACING ) / 2
};
super.setReadout( customMaterials );

// Returns the filled in string for the material readout or '?' if the material is hidden
const getMysteryMaterialReadoutStringProperty = ( materialProperty: TReadOnlyProperty<Material> ) => new DerivedProperty(
Expand All @@ -112,9 +53,11 @@ export default class DensityAccordionBox extends AccordionBox {
} );
} );

this.densityReadoutBox.children = customMaterials.map( customMaterial => {
this.readoutBox.children = customMaterials.map( customMaterial => {

const materialProperty = customMaterial.materialProperty;
const materialProperty = customMaterial.materialProperty!;

assert && assert( materialProperty, 'materialProperty should be defined' );

// Get the custom name from the provided options, or create a dynamic property that derives from the material's name
const nameProperty = customMaterial.customNameProperty ?
Expand All @@ -123,13 +66,13 @@ export default class DensityAccordionBox extends AccordionBox {
derive: material => material.nameProperty
} );
const nameColonProperty = new DerivedProperty( [ nameProperty ], name => name + ': ' );
const labelText = new RichText( nameColonProperty, textOptions );
const labelText = new RichText( nameColonProperty, this.textOptions );

// Create the derived string property for the density readout
const densityDerivedStringProperty = getMysteryMaterialReadoutStringProperty( materialProperty );
const customFormat = customMaterial.customFormat ? customMaterial.customFormat : {};
const densityReadout = new RichText( densityDerivedStringProperty,
combineOptions<RichTextOptions>( {}, textOptions, customFormat ) );
combineOptions<RichTextOptions>( {}, this.textOptions, customFormat ) );

this.cleanupEmitter.addListener( () => {
densityDerivedStringProperty.dispose();
Expand All @@ -145,11 +88,6 @@ export default class DensityAccordionBox extends AccordionBox {
} );
} );
}

public override dispose(): void {
this.cleanupEmitter.emit();
super.dispose();
}
}

densityBuoyancyCommon.register( 'DensityAccordionBox', DensityAccordionBox );
91 changes: 91 additions & 0 deletions js/buoyancy/view/ReadoutListAccordionBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2019-2024, University of Colorado Boulder

/**
* An AccordionBox that displays the percentage of each material that is submerged.
*
* @author Agustín Vallejo
*/

import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import { RichTextOptions, Text, TextOptions, VBox } from '../../../../scenery/js/imports.js';
import Material from '../../common/model/Material.js';
import densityBuoyancyCommon from '../../densityBuoyancyCommon.js';
import TinyEmitter from '../../../../axon/js/TinyEmitter.js';
import DensityBuoyancyCommonConstants from '../../common/DensityBuoyancyCommonConstants.js';
import { optionize4 } from '../../../../phet-core/js/optionize.js';
import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js';
import Mass from '../../common/model/Mass.js';

const DEFAULT_FONT = new PhetFont( 14 );
const HBOX_SPACING = 5;
const DEFAULT_CONTENT_WIDTH = ( 140 + HBOX_SPACING ) / 2;

type SelfOptions = {
// Provide the ideal max content width for the accordion box content. This is used to apply maxWidths to the Texts of the readout.
contentWidthMax?: number;
};

export type CustomReadoutObject = {
mass?: Mass | null; // Masses to be passed to the SubmergedAccordionBox
materialProperty?: TReadOnlyProperty<Material> | null; // Materials to be passed to the DensityAccordionBox
customNameProperty?: TReadOnlyProperty<string>; // Optional: Custom name for the readout
customFormat?: RichTextOptions; // Optional: Custom format for the readout
};

export type ReadoutListAccordionBoxOptions = SelfOptions & AccordionBoxOptions;

export default class ReadoutListAccordionBox extends AccordionBox {

protected cleanupEmitter = new TinyEmitter();
protected textOptions: TextOptions = {};

protected readonly readoutBox: VBox;
protected readonly contentWidthMax: number;

public constructor(
titleStringProperty: TReadOnlyProperty<string>,
providedOptions?: ReadoutListAccordionBoxOptions
) {

const options = optionize4<ReadoutListAccordionBoxOptions, SelfOptions, AccordionBoxOptions>()( {},
DensityBuoyancyCommonConstants.ACCORDION_BOX_OPTIONS, {
titleNode: new Text( titleStringProperty, {
font: DensityBuoyancyCommonConstants.TITLE_FONT,
maxWidth: 160
} ),
layoutOptions: { stretch: true },
contentWidthMax: DEFAULT_CONTENT_WIDTH
}, providedOptions );

const readoutBox = new VBox( {
spacing: 5,
align: 'center'
} );

super( readoutBox, options );

this.readoutBox = readoutBox;
this.contentWidthMax = options.contentWidthMax;

this.textOptions = {
font: DEFAULT_FONT,
maxWidth: ( this.contentWidthMax - HBOX_SPACING ) / 2
};
}

/**
*/
public setReadout( customReadoutObjects: CustomReadoutObject[] ): void {
// Clear the previous materials that may have been created.
this.cleanupEmitter.emit();
this.cleanupEmitter.removeAllListeners();
}

public override dispose(): void {
this.cleanupEmitter.emit();
super.dispose();
}
}

densityBuoyancyCommon.register( 'ReadoutListAccordionBox', ReadoutListAccordionBox );
Loading

0 comments on commit 4a42d93

Please sign in to comment.