diff --git a/js/A11yButtonsHBox.js b/js/A11yButtonsHBox.js index a7d53e07..76e300e7 100644 --- a/js/A11yButtonsHBox.js +++ b/js/A11yButtonsHBox.js @@ -37,10 +37,11 @@ class A11yButtonsHBox extends HBox { const a11yButtons = []; if ( sim.preferencesManager ) { + const preferencesButton = new NavigationBarPreferencesButton( sim.preferencesManager, backgroundColorProperty, { - tandem: options.tandem.createTandem( 'navigationBarPreferencesButton' ) + tandem: options.tandem.createTandem( 'preferencesButton' ) } ); a11yButtons.push( preferencesButton ); diff --git a/js/Sim.js b/js/Sim.js index 4e200929..28f66019 100644 --- a/js/Sim.js +++ b/js/Sim.js @@ -529,7 +529,9 @@ class Sim extends PhetioObject { assert && assert( !options.simDisplayOptions.preferencesManager ); options.simDisplayOptions.preferencesManager = this.preferencesManager; - this.toolbar = new Toolbar( this ); + this.toolbar = new Toolbar( this, { + tandem: Tandem.GENERAL_VIEW.createTandem( 'toolbar' ) + } ); // when the Toolbar positions update, resize the sim to fit in the available space this.toolbar.rightPositionProperty.lazyLink( () => { diff --git a/js/preferences/AudioPreferencesPanel.js b/js/preferences/AudioPreferencesPanel.js index 8b19fb8f..663e1b39 100644 --- a/js/preferences/AudioPreferencesPanel.js +++ b/js/preferences/AudioPreferencesPanel.js @@ -6,9 +6,9 @@ * @author Jesse Greenberg (PhET Interactive Simulations) */ -import { HBox } from '../../../scenery/js/imports.js'; -import { Text } from '../../../scenery/js/imports.js'; -import { VBox } from '../../../scenery/js/imports.js'; +import merge from '../../../phet-core/js/merge.js'; +import { HBox, Text, VBox } from '../../../scenery/js/imports.js'; +import Tandem from '../../../tandem/js/Tandem.js'; import joist from '../joist.js'; import joistStrings from '../joistStrings.js'; import PreferencesDialog from './PreferencesDialog.js'; @@ -24,13 +24,20 @@ class AudioPreferencesTabPanel extends VBox { /** * @param {Object} audioModel - configuration for audio settings, see PreferencesManager * @param {BooleanProperty} enableToolbarProperty - whether the Toolbar is enabled + * @param {Object} [options] */ - constructor( audioModel, enableToolbarProperty ) { + constructor( audioModel, enableToolbarProperty, options ) { + + options = merge( { + tandem: Tandem.REQUIRED + }, options ); const panelChildren = []; if ( audioModel.supportsVoicing ) { - panelChildren.push( new VoicingPanelSection( audioModel, enableToolbarProperty ) ); + panelChildren.push( new VoicingPanelSection( audioModel, enableToolbarProperty, { + tandem: options.tandem.createTandem( 'voicingPanelSection' ) + } ) ); } if ( audioModel.supportsSound ) { @@ -41,7 +48,8 @@ class AudioPreferencesTabPanel extends VBox { const hideSoundToggle = audioModel.supportsVoicing !== audioModel.supportsSound; panelChildren.push( new SoundPanelSection( audioModel, { - includeTitleToggleSwitch: !hideSoundToggle + includeTitleToggleSwitch: !hideSoundToggle, + tandem: options.tandem.createTandem( 'soundPanelSection' ) } ) ); } @@ -52,7 +60,8 @@ class AudioPreferencesTabPanel extends VBox { const allAudioSwitch = new PreferencesToggleSwitch( audioModel.simSoundEnabledProperty, false, true, { labelNode: new Text( audioFeaturesString, PreferencesDialog.PANEL_SECTION_LABEL_OPTIONS ), - a11yLabel: audioFeaturesString + a11yLabel: audioFeaturesString, + tandem: options.tandem.createTandem( 'allAudioSwitch' ) } ); const soundEnabledListener = enabled => { @@ -74,6 +83,8 @@ class AudioPreferencesTabPanel extends VBox { // @private - for disposal this.disposeAudioPreferencesPanel = () => { + panelChildren.forEach( child => child.dispose() ); + allAudioSwitch.dispose(); audioModel.simSoundEnabledProperty.unlink( soundEnabledListener ); }; } diff --git a/js/preferences/GeneralPreferencesPanel.js b/js/preferences/GeneralPreferencesPanel.js index 2bfafaf0..b0208274 100644 --- a/js/preferences/GeneralPreferencesPanel.js +++ b/js/preferences/GeneralPreferencesPanel.js @@ -8,8 +8,8 @@ */ import merge from '../../../phet-core/js/merge.js'; -import { VoicingRichText } from '../../../scenery/js/imports.js'; -import { VBox } from '../../../scenery/js/imports.js'; +import { VBox, VoicingRichText } from '../../../scenery/js/imports.js'; +import Tandem from '../../../tandem/js/Tandem.js'; import joist from '../joist.js'; import joistStrings from '../joistStrings.js'; import PreferencesDialog from './PreferencesDialog.js'; @@ -23,17 +23,24 @@ class GeneralPreferencesPanel extends VBox { /** * @param {Object} generalModel - configuration for the Tab, see PreferencesManager for entries + * @param {Object} [options] */ - constructor( generalModel ) { - super( { + constructor( generalModel, options ) { + + options = merge( { align: 'left', spacing: 40, // pdom tagName: 'section', labelTagName: 'h2', - labelContent: 'General' - } ); + labelContent: 'General', + + // phet-io + tandem: Tandem.REQUIRED + }, options ); + + super( options ); const panelChildren = []; generalModel.simControls && panelChildren.push( new SimControlsPanelSection( generalModel.simControls ) ); diff --git a/js/preferences/InputPreferencesPanel.js b/js/preferences/InputPreferencesPanel.js index 1d5c5d59..c5e38b77 100644 --- a/js/preferences/InputPreferencesPanel.js +++ b/js/preferences/InputPreferencesPanel.js @@ -8,10 +8,8 @@ import merge from '../../../phet-core/js/merge.js'; import StringUtils from '../../../phetcommon/js/util/StringUtils.js'; -import { VoicingRichText } from '../../../scenery/js/imports.js'; -import { voicingUtteranceQueue } from '../../../scenery/js/imports.js'; -import { Node } from '../../../scenery/js/imports.js'; -import { Text } from '../../../scenery/js/imports.js'; +import { Node, Text, VoicingRichText, voicingUtteranceQueue } from '../../../scenery/js/imports.js'; +import Tandem from '../../../tandem/js/Tandem.js'; import joist from '../joist.js'; import joistStrings from '../joistStrings.js'; import PreferencesDialog from './PreferencesDialog.js'; @@ -32,17 +30,22 @@ class InputPreferencesPanel extends Node { /** * @param {Object} inputModel - see PreferencesManager + * @param {Object} [options] */ - constructor( inputModel ) { - super( { + constructor( inputModel, options ) { + options = merge( { // pdom tagName: 'div', labelTagName: 'h2', - labelContent: inputTitleString - } ); + labelContent: inputTitleString, + + tandem: Tandem.REQUIRED + }, options ); - const toggleSwitch = new PreferencesToggleSwitch( inputModel.gestureControlsEnabledProperty, false, true, { + super( options ); + + const gestureControlsEnabledSwitch = new PreferencesToggleSwitch( inputModel.gestureControlsEnabledProperty, false, true, { labelNode: new Text( gestureControlsString, PreferencesDialog.PANEL_SECTION_LABEL_OPTIONS ), descriptionNode: new VoicingRichText( gestureControlsDescriptionString, merge( {}, PreferencesDialog.PANEL_SECTION_CONTENT_OPTIONS, { lineWrap: 350, @@ -52,11 +55,12 @@ class InputPreferencesPanel extends Node { description: gestureControlsDescriptionString } ) } ) ), - a11yLabel: gestureControlsString + a11yLabel: gestureControlsString, + tandem: options.tandem.createTandem( 'gestureControlsEnabledSwitch' ) } ); const panelSection = new PreferencesPanelSection( { - titleNode: toggleSwitch + titleNode: gestureControlsEnabledSwitch } ); this.addChild( panelSection ); @@ -64,6 +68,19 @@ class InputPreferencesPanel extends Node { const alert = enabled ? gestureControlEnabledAlertString : gestureControlDisabledAlertString; voicingUtteranceQueue.addToBack( alert ); } ); + + // @private + this.disposeInputPreferencesPanel = () => { + gestureControlsEnabledSwitch.dispose(); + }; + } + + /** + * @public + */ + dispose() { + this.disposeInputPreferencesPanel(); + super.dispose(); } } diff --git a/js/preferences/NavigationBarPreferencesButton.js b/js/preferences/NavigationBarPreferencesButton.js index ac93e273..d1ce408a 100644 --- a/js/preferences/NavigationBarPreferencesButton.js +++ b/js/preferences/NavigationBarPreferencesButton.js @@ -9,6 +9,8 @@ import merge from '../../../phet-core/js/merge.js'; import { Path } from '../../../scenery/js/imports.js'; import userCogSolidShape from '../../../sherpa/js/fontawesome-5/userCogSolidShape.js'; +import Dialog from '../../../sun/js/Dialog.js'; +import PhetioCapsule from '../../../tandem/js/PhetioCapsule.js'; import Tandem from '../../../tandem/js/Tandem.js'; import joist from '../joist.js'; import JoistButton from '../JoistButton.js'; @@ -32,15 +34,19 @@ class NavigationBarPreferencesButton extends JoistButton { maxWidth: 25 } ); - let preferencesDialog = null; - super( icon, backgroundColorProperty, Tandem.OPT_OUT, { - listener: () => { - if ( !preferencesDialog ) { - preferencesDialog = new PreferencesDialog( preferencesModel, { - tandem: Tandem.OPT_OUT - } ); - } + assert && assert( !options.listener, 'PhetButton sets listener' ); + const preferencesDialogCapsule = new PhetioCapsule( tandem => { + return new PreferencesDialog( preferencesModel, { + tandem: tandem + } ); + }, [], { + tandem: options.tandem.createTandem( 'preferencesDialogCapsule' ), + phetioType: PhetioCapsule.PhetioCapsuleIO( Dialog.DialogIO ) + } ); + super( icon, backgroundColorProperty, options.tandem, { + listener: () => { + const preferencesDialog = preferencesDialogCapsule.getElement(); preferencesDialog.show(); preferencesDialog.focusSelectedTab(); }, diff --git a/js/preferences/PreferencesDialog.js b/js/preferences/PreferencesDialog.js index 7661d286..fbff2337 100644 --- a/js/preferences/PreferencesDialog.js +++ b/js/preferences/PreferencesDialog.js @@ -10,15 +10,14 @@ */ import EnumerationProperty from '../../../axon/js/EnumerationProperty.js'; -import EnumerationValue from '../../../phet-core/js/EnumerationValue.js'; import Enumeration from '../../../phet-core/js/Enumeration.js'; +import EnumerationValue from '../../../phet-core/js/EnumerationValue.js'; import merge from '../../../phet-core/js/merge.js'; import PhetFont from '../../../scenery-phet/js/PhetFont.js'; -import { KeyboardUtils } from '../../../scenery/js/imports.js'; -import { Node } from '../../../scenery/js/imports.js'; -import { Text } from '../../../scenery/js/imports.js'; +import { KeyboardUtils, Node, Text } from '../../../scenery/js/imports.js'; import Dialog from '../../../sun/js/Dialog.js'; import HSeparator from '../../../sun/js/HSeparator.js'; +import Tandem from '../../../tandem/js/Tandem.js'; import audioManager from '../audioManager.js'; import joist from '../joist.js'; import joistStrings from '../joistStrings.js'; @@ -83,6 +82,7 @@ class PreferencesDialog extends Dialog { // phet-io phetioDynamicElement: true, + tandem: Tandem.REQUIRED, // pdom positionInPDOM: true @@ -97,13 +97,19 @@ class PreferencesDialog extends Dialog { assert && assert( supportedTabs.length > 0, 'Trying to create a PreferencesDialog with no tabs, check PreferencesConfiguration' ); // the selected PreferencesTab, indicating which tab is visible in the Dialog - const selectedTabProperty = new EnumerationProperty( PreferencesTab.GENERAL ); + const selectedTabProperty = new EnumerationProperty( PreferencesTab.GENERAL, { + tandem: options.tandem.createTandem( 'selectedTabProperty' ) + } ); // the set of tabs you can can click to activate a tab panel - const preferencesTabs = new PreferencesTabs( supportedTabs, selectedTabProperty ); + const preferencesTabs = new PreferencesTabs( supportedTabs, selectedTabProperty, { + tandem: options.tandem.createTandem( 'preferencesTabs' ) + } ); // the panels of content with UI components to select preferences, only one is displayed at a time - const preferencesPanels = new PreferencesPanels( preferencesModel, supportedTabs, selectedTabProperty ); + const preferencesPanels = new PreferencesPanels( preferencesModel, supportedTabs, selectedTabProperty, { + tandem: options.tandem.createTandem( 'preferencesPanels' ) + } ); // visual separator between tabs and panels - as long as the widest separated content, which may change with i18n const tabPanelSeparator = new HSeparator( Math.max( preferencesPanels.width, preferencesTabs.width ), { lineWidth: 1 } ); @@ -138,6 +144,13 @@ class PreferencesDialog extends Dialog { } } } ); + + // @private + this.disposePreferencesDialog = () => { + preferencesTabs.dispose(); + selectedTabProperty.dispose(); + preferencesPanels.dispose(); + }; } /** @@ -155,6 +168,14 @@ class PreferencesDialog extends Dialog { focusSelectedPanel() { this.preferencesPanels.focusSelectedPanel(); } + + /** + * @public + */ + dispose() { + this.disposePreferencesDialog(); + super.dispose(); + } } // @public diff --git a/js/preferences/PreferencesPanels.js b/js/preferences/PreferencesPanels.js index c400b4a1..94558ef8 100644 --- a/js/preferences/PreferencesPanels.js +++ b/js/preferences/PreferencesPanels.js @@ -9,8 +9,9 @@ * @author Jesse Greenberg (PhET Interactive Simulations) */ -import { AlignGroup } from '../../../scenery/js/imports.js'; -import { Node } from '../../../scenery/js/imports.js'; +import merge from '../../../phet-core/js/merge.js'; +import { AlignGroup, Node } from '../../../scenery/js/imports.js'; +import Tandem from '../../../tandem/js/Tandem.js'; import joist from '../joist.js'; import AudioPreferencesPanel from './AudioPreferencesPanel.js'; import GeneralPreferencesPanel from './GeneralPreferencesPanel.js'; @@ -24,46 +25,59 @@ class PreferencesPanels extends Node { * @param {PreferencesManager} preferencesModel * @param {PreferencesTab[]} supportedTabs - list of Tabs supported by this Dialog * @param {EnumerationDeprecatedProperty.} selectedTabProperty + * @param {Object} [options] */ - constructor( preferencesModel, supportedTabs, selectedTabProperty ) { - super(); + constructor( preferencesModel, supportedTabs, selectedTabProperty, options ) { + + options = merge( { + tandem: Tandem.REQUIRED + }, options ); + super( options ); const panelAlignGroup = new AlignGroup( { matchVertical: false } ); - // @private {PreferencesPanel[]} + // @private {PreferencesPanelContainer[]} this.content = []; let generalPreferencesPanel = null; if ( supportedTabs.includes( PreferencesDialog.PreferencesTab.GENERAL ) ) { - generalPreferencesPanel = new GeneralPreferencesPanel( preferencesModel.generalModel ); + generalPreferencesPanel = new GeneralPreferencesPanel( preferencesModel.generalModel, { + tandem: options.tandem.createTandem( 'generalPreferencesPanel' ) + } ); const generalBox = panelAlignGroup.createBox( generalPreferencesPanel ); this.addChild( generalBox ); - this.content.push( new PreferencesPanel( generalPreferencesPanel, PreferencesDialog.PreferencesTab.GENERAL ) ); + this.content.push( new PreferencesPanelContainer( generalPreferencesPanel, PreferencesDialog.PreferencesTab.GENERAL ) ); } let visualPreferencesPanel = null; if ( supportedTabs.includes( PreferencesDialog.PreferencesTab.VISUAL ) ) { - visualPreferencesPanel = new VisualPreferencesPanel( preferencesModel.visualModel ); + visualPreferencesPanel = new VisualPreferencesPanel( preferencesModel.visualModel, { + tandem: options.tandem.createTandem( 'visualPreferencesPanel' ) + } ); const visualBox = panelAlignGroup.createBox( visualPreferencesPanel ); this.addChild( visualBox ); - this.content.push( new PreferencesPanel( visualPreferencesPanel, PreferencesDialog.PreferencesTab.VISUAL ) ); + this.content.push( new PreferencesPanelContainer( visualPreferencesPanel, PreferencesDialog.PreferencesTab.VISUAL ) ); } let audioPreferencesPanel = null; if ( supportedTabs.includes( PreferencesDialog.PreferencesTab.AUDIO ) ) { - audioPreferencesPanel = new AudioPreferencesPanel( preferencesModel.audioModel, preferencesModel.toolbarEnabledProperty ); + audioPreferencesPanel = new AudioPreferencesPanel( preferencesModel.audioModel, preferencesModel.toolbarEnabledProperty, { + tandem: options.tandem.createTandem( 'audioPreferencesPanel' ) + } ); const audioBox = panelAlignGroup.createBox( audioPreferencesPanel ); this.addChild( audioBox ); - this.content.push( new PreferencesPanel( audioPreferencesPanel, PreferencesDialog.PreferencesTab.AUDIO ) ); + this.content.push( new PreferencesPanelContainer( audioPreferencesPanel, PreferencesDialog.PreferencesTab.AUDIO ) ); } let inputPreferencesPanel = null; if ( supportedTabs.includes( PreferencesDialog.PreferencesTab.INPUT ) ) { - inputPreferencesPanel = new InputPreferencesPanel( preferencesModel.inputModel ); + inputPreferencesPanel = new InputPreferencesPanel( preferencesModel.inputModel, { + tandem: options.tandem.createTandem( 'inputPreferencesPanel' ) + } ); this.addChild( inputPreferencesPanel ); - this.content.push( new PreferencesPanel( inputPreferencesPanel, PreferencesDialog.PreferencesTab.INPUT ) ); + this.content.push( new PreferencesPanelContainer( inputPreferencesPanel, PreferencesDialog.PreferencesTab.INPUT ) ); } this.selectedTabProperty = selectedTabProperty; @@ -75,11 +89,27 @@ class PreferencesPanels extends Node { audioPreferencesPanel && ( audioPreferencesPanel.visible = tab === PreferencesDialog.PreferencesTab.AUDIO ); inputPreferencesPanel && ( inputPreferencesPanel.visible = tab === PreferencesDialog.PreferencesTab.INPUT ); } ); + + // @private + this.disposePreferencesPanel = () => { + generalPreferencesPanel && generalPreferencesPanel.dispose(); + visualPreferencesPanel && visualPreferencesPanel.dispose(); + audioPreferencesPanel && audioPreferencesPanel.dispose(); + inputPreferencesPanel && inputPreferencesPanel.dispose(); + }; + } + + /** + * @public + */ + dispose() { + this.disposePreferencesPanel(); + super.dispose(); } /** * @private - * @returns {PreferencesPanel} - the currently selected preferences panel + * @returns {PreferencesPanelContainer} - the currently selected preferences panel */ getSelectedContent() { for ( let i = 0; i < this.content.length; i++ ) { @@ -95,7 +125,7 @@ class PreferencesPanels extends Node { /** * Focus the selected panel. The panel should not be focusable until this is requested, so it is set to be * focusable before the focus() call. When focus is removed from the panel, it should become non-focusable - * again. That is handled in PreferencesPanel class. + * again. That is handled in PreferencesPanelContainer class. * @public */ focusSelectedPanel() { @@ -119,7 +149,7 @@ class PreferencesPanels extends Node { * An inner class that manages the panelContent and its value. A listener as added to the panel so that * whenever focus is lost from the panel, it is removed from the traversal order. */ -class PreferencesPanel extends Node { +class PreferencesPanelContainer extends Node { /** * @param {Node} panelContent diff --git a/js/preferences/PreferencesTabs.js b/js/preferences/PreferencesTabs.js index 7b1aff98..fab27e27 100644 --- a/js/preferences/PreferencesTabs.js +++ b/js/preferences/PreferencesTabs.js @@ -8,15 +8,9 @@ */ import Property from '../../../axon/js/Property.js'; +import merge from '../../../phet-core/js/merge.js'; import StringUtils from '../../../phetcommon/js/util/StringUtils.js'; -import { FocusHighlightPath } from '../../../scenery/js/imports.js'; -import { KeyboardUtils } from '../../../scenery/js/imports.js'; -import { Voicing } from '../../../scenery/js/imports.js'; -import { PressListener } from '../../../scenery/js/imports.js'; -import { Line } from '../../../scenery/js/imports.js'; -import { Node } from '../../../scenery/js/imports.js'; -import { Rectangle } from '../../../scenery/js/imports.js'; -import { Text } from '../../../scenery/js/imports.js'; +import { FocusHighlightPath, KeyboardUtils, Line, Node, PressListener, Rectangle, Text, Voicing } from '../../../scenery/js/imports.js'; import Tandem from '../../../tandem/js/Tandem.js'; import joist from '../joist.js'; import joistStrings from '../joistStrings.js'; @@ -34,14 +28,21 @@ class PreferencesTabs extends Node { /** * @param {PreferencesTab[]} supportedTabs - list of tabs the Dialog should include * @param {EnumerationDeprecatedProperty.} selectedPanelProperty + * @param {Object} [options] */ - constructor( supportedTabs, selectedPanelProperty ) { - super( { + constructor( supportedTabs, selectedPanelProperty, options ) { + options = merge( { + // pdom tagName: 'ul', ariaRole: 'tablist', - groupFocusHighlight: true - } ); + groupFocusHighlight: true, + + // phet-io + tandem: Tandem.REQUIRED + }, options ); + + super( options ); // @private {null|Node} - A reference to the selected and focusable tab content so that we can determine which // tab is next in order when cycling through with alternative input. @@ -52,13 +53,14 @@ class PreferencesTabs extends Node { // @private {Tab[]} this.content = []; - const addTabIfSupported = ( preferenceTab, titleString ) => { - _.includes( supportedTabs, preferenceTab ) && this.content.push( new Tab( titleString, selectedPanelProperty, preferenceTab ) ); + const addTabIfSupported = ( preferenceTab, titleString, tandemName ) => { + const tandem = options.tandem.createTandem( tandemName ); + _.includes( supportedTabs, preferenceTab ) && this.content.push( new Tab( titleString, selectedPanelProperty, preferenceTab, tandem ) ); }; - addTabIfSupported( PreferencesDialog.PreferencesTab.GENERAL, generalTitleString ); - addTabIfSupported( PreferencesDialog.PreferencesTab.VISUAL, visualTitleString ); - addTabIfSupported( PreferencesDialog.PreferencesTab.AUDIO, audioTitleString ); - addTabIfSupported( PreferencesDialog.PreferencesTab.INPUT, inputTitleString ); + addTabIfSupported( PreferencesDialog.PreferencesTab.GENERAL, generalTitleString, 'generalTab' ); + addTabIfSupported( PreferencesDialog.PreferencesTab.VISUAL, visualTitleString, 'visualTab' ); + addTabIfSupported( PreferencesDialog.PreferencesTab.AUDIO, audioTitleString, 'audioTab' ); + addTabIfSupported( PreferencesDialog.PreferencesTab.INPUT, inputTitleString, 'audioTab' ); for ( let i = 0; i < this.content.length; i++ ) { this.addChild( this.content[ i ] ); @@ -117,8 +119,9 @@ class PreferencesTabs extends Node { // @private this.disposePreferencesTabs = () => { - this.removeListener( keyboardListener ); + this.removeInputListener( keyboardListener ); selectedPanelProperty.unlink( selectedPanelListener ); + this.content.forEach( tab => tab.dispose() ); }; } @@ -154,7 +157,7 @@ class Tab extends Voicing( Node, 0 ) { * @param {EnumerationDeprecatedProperty.} property * @param {PreferencesDialog.PreferencesTab} value - PreferencesTab shown when this tab is selected */ - constructor( label, property, value ) { + constructor( label, property, value, tandem ) { const textNode = new Text( label, PreferencesDialog.TAB_OPTIONS ); @@ -178,7 +181,9 @@ class Tab extends Voicing( Node, 0 ) { innerContent: label, ariaRole: 'tab', focusable: true, - containerTagName: 'li' + containerTagName: 'li', + + tandem: tandem } ); // @public {PreferenceTab} @@ -188,7 +193,7 @@ class Tab extends Voicing( Node, 0 ) { title: label } ); - const buttonListener = new PressListener( { + const pressListener = new PressListener( { press: () => { property.set( value ); @@ -196,12 +201,12 @@ class Tab extends Voicing( Node, 0 ) { this.voicingSpeakNameResponse(); }, - // phet-io - opting out for now to get CT working - tandem: Tandem.OPT_OUT + // phet-io + tandem: tandem.createTandem( 'pressListener' ) } ); - this.addInputListener( buttonListener ); + this.addInputListener( pressListener ); - Property.multilink( [ property, buttonListener.isOverProperty ], ( selectedTab, isOver ) => { + Property.multilink( [ property, pressListener.isOverProperty ], ( selectedTab, isOver ) => { textNode.opacity = selectedTab === value ? 1 : isOver ? 0.8 : 0.6; @@ -210,6 +215,18 @@ class Tab extends Voicing( Node, 0 ) { underlineNode.visible = selectedTab === value; } ); + // @private + this.disposeTab = () => { + pressListener.dispose(); + }; + } + + /** + * @public + */ + dispose() { + this.disposeTab(); + super.dispose(); } } diff --git a/js/preferences/PreferencesToggleSwitch.js b/js/preferences/PreferencesToggleSwitch.js index 4a55c581..7f4addd1 100644 --- a/js/preferences/PreferencesToggleSwitch.js +++ b/js/preferences/PreferencesToggleSwitch.js @@ -50,8 +50,8 @@ class PreferencesToggleSwitch extends Node { trackFillRight: '#64bd5a' }, - // phet-io - opting out of Tandems for now - tandem: Tandem.OPT_OUT + // phet-io + tandem: Tandem.REQUIRED }, options ); assert && assert( options.labelNode === null || options.labelNode instanceof Node, 'labelNode is null or inserted as child' ); assert && assert( options.descriptionNode === null || options.descriptionNode instanceof Node, 'labelNode is null or inserted as child' ); @@ -75,8 +75,8 @@ class PreferencesToggleSwitch extends Node { voicingIgnoreVoicingManagerProperties: true, voicingNameResponse: options.a11yLabel, - // tandem - opting out of Tandems for now - tandem: Tandem.OPT_OUT + // tandem + tandem: options.tandem.createTandem( 'toggleSwitch' ) } ) ); this.addChild( toggleSwitch ); @@ -112,6 +112,19 @@ class PreferencesToggleSwitch extends Node { // only a label node, it goes to the left of the toggleSwitch options.labelNode.rightCenter = toggleSwitch.leftCenter.minusXY( options.labelSpacing, 0 ); } + + // @private + this.disposePreferencesToggleSwitch = () => { + toggleSwitch.dispose(); + }; + } + + /** + * @public + */ + dispose() { + this.disposePreferencesToggleSwitch(); + super.dispose(); } } diff --git a/js/preferences/SoundPanelSection.js b/js/preferences/SoundPanelSection.js index a0388205..714ac023 100644 --- a/js/preferences/SoundPanelSection.js +++ b/js/preferences/SoundPanelSection.js @@ -8,11 +8,7 @@ import merge from '../../../phet-core/js/merge.js'; import StringUtils from '../../../phetcommon/js/util/StringUtils.js'; -import { VoicingRichText } from '../../../scenery/js/imports.js'; -import { VoicingText } from '../../../scenery/js/imports.js'; -import { voicingUtteranceQueue } from '../../../scenery/js/imports.js'; -import { Text } from '../../../scenery/js/imports.js'; -import { VBox } from '../../../scenery/js/imports.js'; +import { Text, VBox, VoicingRichText, VoicingText, voicingUtteranceQueue } from '../../../scenery/js/imports.js'; import Checkbox from '../../../sun/js/Checkbox.js'; import soundManager from '../../../tambo/js/soundManager.js'; import Tandem from '../../../tandem/js/Tandem.js'; @@ -47,12 +43,15 @@ class SoundPanelSection extends PreferencesPanelSection { // PreferencesPanelSection. It is possible that the toggle for Sound can be redundant when Sound // is the only Audio feature supported. In that case, control of Sound should go through the // "All Audio" toggle. - includeTitleToggleSwitch: true + includeTitleToggleSwitch: true, + + // phet-io + tandem: Tandem.REQUIRED }, options ); const soundLabel = new Text( soundsLabelString, PreferencesDialog.PANEL_SECTION_LABEL_OPTIONS ); - const titleNode = new PreferencesToggleSwitch( soundManager.enabledProperty, false, true, { + const soundEnabledSwitch = new PreferencesToggleSwitch( soundManager.enabledProperty, false, true, { labelNode: soundLabel, descriptionNode: new VoicingText( soundDescriptionString, merge( {}, PreferencesDialog.PANEL_SECTION_CONTENT_OPTIONS, { readingBlockNameResponse: StringUtils.fillIn( labelledDescriptionPatternString, { @@ -63,13 +62,15 @@ class SoundPanelSection extends PreferencesPanelSection { toggleSwitchOptions: { visible: options.includeTitleToggleSwitch }, - a11yLabel: soundsLabelString + a11yLabel: soundsLabelString, + tandem: options.tandem.createTandem( 'soundEnabledSwitch' ) } ); let enhancedSoundContent = null; + let enhancedSoundCheckbox = null; if ( audioOptions.supportsEnhancedSound ) { const enahncedSoundLabel = new Text( extraSoundsLabelString, PreferencesDialog.PANEL_SECTION_CONTENT_OPTIONS ); - const enhancedSoundCheckbox = new Checkbox( enahncedSoundLabel, soundManager.enhancedSoundEnabledProperty, { + enhancedSoundCheckbox = new Checkbox( enahncedSoundLabel, soundManager.enhancedSoundEnabledProperty, { // pdom labelTagName: 'label', @@ -79,7 +80,7 @@ class SoundPanelSection extends PreferencesPanelSection { voicingNameResponse: extraSoundsLabelString, // phet-io - tandem: Tandem.OPT_OUT + tandem: options.tandem.createTandem( 'enhancedSoundCheckbox' ) } ); const enhancedSoundDescription = new VoicingRichText( extraSoundsDescriptionString, merge( {}, PreferencesDialog.PANEL_SECTION_CONTENT_OPTIONS, { @@ -102,7 +103,7 @@ class SoundPanelSection extends PreferencesPanelSection { } super( { - titleNode: titleNode, + titleNode: soundEnabledSwitch, contentNode: enhancedSoundContent } ); @@ -118,6 +119,21 @@ class SoundPanelSection extends PreferencesPanelSection { voicingUtteranceQueue.addToBack( alert ); this.alertDescriptionUtterance( alert ); } ); + + // @private + this.disposeSoundPanelSection = () => { + soundEnabledSwitch.dispose(); + enhancedSoundCheckbox && enhancedSoundCheckbox.dispose(); + }; + } + + + /** + * @public + */ + dispose() { + this.disposeSoundPanelSection(); + super.dispose(); } } diff --git a/js/preferences/VisualPreferencesPanel.js b/js/preferences/VisualPreferencesPanel.js index e53ec3a6..077d8d17 100644 --- a/js/preferences/VisualPreferencesPanel.js +++ b/js/preferences/VisualPreferencesPanel.js @@ -9,10 +9,8 @@ import merge from '../../../phet-core/js/merge.js'; import StringUtils from '../../../phetcommon/js/util/StringUtils.js'; -import { VoicingText } from '../../../scenery/js/imports.js'; -import { voicingUtteranceQueue } from '../../../scenery/js/imports.js'; -import { Node } from '../../../scenery/js/imports.js'; -import { Text } from '../../../scenery/js/imports.js'; +import { Node, Text, VoicingText, voicingUtteranceQueue } from '../../../scenery/js/imports.js'; +import Tandem from '../../../tandem/js/Tandem.js'; import joist from '../joist.js'; import joistStrings from '../joistStrings.js'; import PreferencesDialog from './PreferencesDialog.js'; @@ -30,18 +28,25 @@ class VisualPreferencesPanel extends Node { /** * @param {Object} visualModel - see PreferencesManager + * @param {Object} [options] */ - constructor( visualModel ) { - super( { + constructor( visualModel, options ) { + + options = merge( { // pdom tagName: 'div', labelTagName: 'h2', - labelContent: 'Visual' - } ); + labelContent: 'Visual', + + // phet-io + tandem: Tandem.REQUIRED + }, options ); + + super( options ); const label = new Text( interactiveHighlightsString, PreferencesDialog.PANEL_SECTION_LABEL_OPTIONS ); - const toggleSwitch = new PreferencesToggleSwitch( visualModel.interactiveHighlightsEnabledProperty, false, true, { + const interactiveHighlightsEnabledSwitch = new PreferencesToggleSwitch( visualModel.interactiveHighlightsEnabledProperty, false, true, { labelNode: label, descriptionNode: new VoicingText( interactiveHighlightsDescriptionString, merge( {}, PreferencesDialog.PANEL_SECTION_CONTENT_OPTIONS, { readingBlockNameResponse: StringUtils.fillIn( labelledDescriptionPatternString, { @@ -49,11 +54,12 @@ class VisualPreferencesPanel extends Node { description: interactiveHighlightsDescriptionString } ) } ) ), - a11yLabel: interactiveHighlightsString + a11yLabel: interactiveHighlightsString, + tandem: options.tandem.createTandem( 'interactiveHighlightsEnabledSwitch' ) } ); const panelSection = new PreferencesPanelSection( { - titleNode: toggleSwitch + titleNode: interactiveHighlightsEnabledSwitch } ); this.addChild( panelSection ); @@ -70,6 +76,7 @@ class VisualPreferencesPanel extends Node { // @private this.disposeVisualPreferencesPanel = () => { + interactiveHighlightsEnabledSwitch.dispose(); visualModel.interactiveHighlightsEnabledProperty.unlink( alertEnabledChange ); }; } diff --git a/js/preferences/VoicingPanelSection.js b/js/preferences/VoicingPanelSection.js index 6252b806..ea31a9bf 100644 --- a/js/preferences/VoicingPanelSection.js +++ b/js/preferences/VoicingPanelSection.js @@ -89,12 +89,17 @@ class VoicingPanelSection extends PreferencesPanelSection { /** * @param {Object} audioModel - configuration for audio settings, see PreferencesManager * @param {BooleanProperty} toolbarEnabledProperty - whether or not the Toolbar is enabled for use + * @param {Object} [options] */ - constructor( audioModel, toolbarEnabledProperty ) { + constructor( audioModel, toolbarEnabledProperty, options ) { + + options = merge( { + tandem: Tandem.REQUIRED + }, options ); // the checkbox is the title for the section and totally enables/disables the feature const voicingLabel = new Text( voicingLabelString, PreferencesDialog.PANEL_SECTION_LABEL_OPTIONS ); - const voicingSwitch = new PreferencesToggleSwitch( audioModel.voicingEnabledProperty, false, true, { + const voicingEnabledSwitch = new PreferencesToggleSwitch( audioModel.voicingEnabledProperty, false, true, { labelNode: voicingLabel, descriptionNode: new VoicingText( voicingDescriptionString, merge( {}, PreferencesDialog.PANEL_SECTION_CONTENT_OPTIONS, { readingBlockNameResponse: StringUtils.fillIn( labelledDescriptionPatternString, { @@ -102,14 +107,16 @@ class VoicingPanelSection extends PreferencesPanelSection { description: voicingDescriptionString } ) } ) ), - a11yLabel: voicingLabelString + a11yLabel: voicingLabelString, + tandem: options.tandem.createTandem( 'voicingEnabledSwitch' ) } ); // checkbox for the toolbar const quickAccessLabel = new Text( toolbarLabelString, PreferencesDialog.PANEL_SECTION_LABEL_OPTIONS ); - const toolbarSwitch = new PreferencesToggleSwitch( toolbarEnabledProperty, false, true, { + const toolbarEnabledSwitch = new PreferencesToggleSwitch( toolbarEnabledProperty, false, true, { labelNode: quickAccessLabel, - a11yLabel: toolbarLabelString + a11yLabel: toolbarLabelString, + tandem: options.tandem.createTandem( 'toolbarEnabledSwitch' ) } ); // Speech output levels @@ -129,9 +136,15 @@ class VoicingPanelSection extends PreferencesPanelSection { align: 'left', spacing: 5, children: [ - createCheckbox( objectDetailsLabelString, audioModel.voicingObjectResponsesEnabledProperty ), - createCheckbox( contextChangesLabelString, audioModel.voicingContextResponsesEnabledProperty ), - createCheckbox( helpfulHintsLabelString, audioModel.voicingHintResponsesEnabledProperty ) + createCheckbox( objectDetailsLabelString, audioModel.voicingObjectResponsesEnabledProperty, + options.tandem.createTandem( 'voicingObjectResponsesEnabledCheckbox' ) + ), + createCheckbox( contextChangesLabelString, audioModel.voicingContextResponsesEnabledProperty, + options.tandem.createTandem( 'voicingContextResponsesEnabledCheckbox' ) + ), + createCheckbox( helpfulHintsLabelString, audioModel.voicingHintResponsesEnabledProperty, + options.tandem.createTandem( 'voicingHintResponsesEnabledCheckbox' ) + ) ] } ); @@ -167,7 +180,7 @@ class VoicingPanelSection extends PreferencesPanelSection { voicingNameResponse: customizeVoiceString, // phet-io - tandem: Tandem.OPT_OUT + tandem: options.tandem.createTandem( 'expandCollapseButton' ) } ); const voiceOptionsContainer = new Node( { @@ -180,19 +193,19 @@ class VoicingPanelSection extends PreferencesPanelSection { voiceOptionsOpenProperty.toggle(); }, - // phet-io + // phet-io TODO: https://github.com/phetsims/joist/issues/744 tandem: Tandem.OPT_OUT } ); voiceOptionsLabel.addInputListener( voiceOptionsPressListener ); const content = new Node( { - children: [ speechOutputContent, toolbarSwitch, voiceOptionsContainer, voiceOptionsContent ] + children: [ speechOutputContent, toolbarEnabledSwitch, voiceOptionsContainer, voiceOptionsContent ] } ); // layout for section content, custom rather than using a LayoutBox because the voice options label needs // to be left aligned with other labels, while the ExpandCollapseButton extends to the left - toolbarSwitch.leftTop = speechOutputContent.leftBottom.plusXY( 0, 20 ); - voiceOptionsLabel.leftTop = toolbarSwitch.leftBottom.plusXY( 0, 20 ); + toolbarEnabledSwitch.leftTop = speechOutputContent.leftBottom.plusXY( 0, 20 ); + voiceOptionsLabel.leftTop = toolbarEnabledSwitch.leftBottom.plusXY( 0, 20 ); expandCollapseButton.leftCenter = voiceOptionsLabel.rightCenter.plusXY( 10, 0 ); voiceOptionsContent.leftTop = voiceOptionsLabel.leftBottom.plusXY( 0, 10 ); voiceOptionsOpenProperty.link( open => { voiceOptionsContent.visible = open; } ); @@ -201,7 +214,7 @@ class VoicingPanelSection extends PreferencesPanelSection { expandCollapseButton.focusHighlight = new FocusHighlightFromNode( voiceOptionsContainer ); super( { - titleNode: voicingSwitch, + titleNode: voicingEnabledSwitch, contentNode: content } ); @@ -251,7 +264,10 @@ class VoicingPanelSection extends PreferencesPanelSection { voiceList = englishVoices.slice( 0, 12 ); } - voiceComboBox = new VoiceComboBox( voiceList, audioModel.voiceProperty, phet.joist.sim.topLayer ); + // phet-io - for when creating the Archetype for the Capsule housing the preferencesDialog, we don't have a sim global. + const parent = phet.joist.sim.topLayer || new Node(); + + voiceComboBox = new VoiceComboBox( voiceList, audioModel.voiceProperty, parent ); voiceOptionsContent.addChild( voiceComboBox ); }; voicingManager.voicesChangedEmitter.addListener( voicesChangedListener ); @@ -288,6 +304,23 @@ class VoicingPanelSection extends PreferencesPanelSection { voicingUtteranceQueue.addToBack( alert ); this.alertDescriptionUtterance( alert ); } ); + + // @private + this.disposeVoicingPanelSection = () => { + voicingEnabledSwitch.dispose(); + expandCollapseButton.dispose(); + toolbarEnabledSwitch.dispose(); + speechOutputCheckboxes.children.forEach( child => child.dispose() ); + + }; + } + + /** + * @public + */ + dispose() { + this.disposeVoicingPanelSection(); + super.dispose(); } } @@ -297,7 +330,7 @@ class VoicingPanelSection extends PreferencesPanelSection { * @param {BooleanProperty} property * @returns {Checkbox} */ -const createCheckbox = ( labelString, property ) => { +const createCheckbox = ( labelString, property, tandem ) => { const labelNode = new Text( labelString, PreferencesDialog.PANEL_SECTION_CONTENT_OPTIONS ); return new Checkbox( labelNode, property, { @@ -309,7 +342,7 @@ const createCheckbox = ( labelString, property ) => { voicingNameResponse: labelString, // phet-io - tandem: Tandem.OPT_OUT + tandem: tandem } ); }; @@ -399,8 +432,18 @@ class VoiceComboBox extends ComboBox { * @param {SpeechSynthesisVoice[]} voices - list of voices to include from the voicingManager * @param {Property.} voiceProperty * @param {Node} parentNode - node that acts as a parent for the ComboBox list + * @param {Object} [options] */ - constructor( voices, voiceProperty, parentNode ) { + constructor( voices, voiceProperty, parentNode, options ) { + + options = merge( { + listPosition: 'above', + accessibleName: voiceLabelString, + + // phet-io, opt out because we would need to instrument voices, but those could change between runtimes. + tandem: Tandem.OPT_OUT + }, options ); + const items = []; if ( voices.length === 0 ) { @@ -421,13 +464,7 @@ class VoiceComboBox extends ComboBox { // voices voiceProperty.set( items[ 0 ].value ); - super( items, voiceProperty, parentNode, { - listPosition: 'above', - accessibleName: voiceLabelString, - - // phet-io - tandem: Tandem.OPT_OUT - } ); + super( items, voiceProperty, parentNode, options ); // voicing - responses for the button should always come through, regardless of user selection of // responses. As of 10/29/21, ComboBox will only read the name response (which are always read regardless) @@ -470,7 +507,7 @@ class VoicingPitchSlider extends Voicing( VBox, 0 ) { // voicing voicingNameResponse: labelString, - // phet-io + // phet-io TODO: https://github.com/phetsims/joist/issues/744 tandem: Tandem.OPT_OUT } ); diff --git a/js/toolbar/Toolbar.js b/js/toolbar/Toolbar.js index 2322a688..fd1de5f9 100644 --- a/js/toolbar/Toolbar.js +++ b/js/toolbar/Toolbar.js @@ -18,11 +18,8 @@ import NumberProperty from '../../../axon/js/NumberProperty.js'; import stepTimer from '../../../axon/js/stepTimer.js'; import Matrix3 from '../../../dot/js/Matrix3.js'; import { Shape } from '../../../kite/js/imports.js'; -import { voicingManager } from '../../../scenery/js/imports.js'; -import { voicingUtteranceQueue } from '../../../scenery/js/imports.js'; -import { Node } from '../../../scenery/js/imports.js'; -import { Path } from '../../../scenery/js/imports.js'; -import { Rectangle } from '../../../scenery/js/imports.js'; +import merge from '../../../phet-core/js/merge.js'; +import { Node, Path, Rectangle, voicingManager, voicingUtteranceQueue } from '../../../scenery/js/imports.js'; import ButtonNode from '../../../sun/js/buttons/ButtonNode.js'; import RoundPushButton from '../../../sun/js/buttons/RoundPushButton.js'; import Tandem from '../../../tandem/js/Tandem.js'; @@ -48,14 +45,20 @@ class Toolbar extends Node { /** * @param {Sim} sim + * @param {Object} [options] */ - constructor( sim ) { + constructor( sim, options ) { - super( { + options = merge( { // pdom - tagName: 'div' - } ); + tagName: 'div', + + // phet-io + tandem: Tandem.REQUIRED + }, options ); + + super( options ); // @private {BooleanProperty} - Whether or not the Toolbar is enabled (visible to the user) this.isEnabledProperty = sim.preferencesManager.toolbarEnabledProperty; @@ -89,7 +92,9 @@ class Toolbar extends Node { // @private {VoicingToolbarItem} - Contents for the Toolbar, currently only controls related to the voicing // feature. const voicingAlertManager = new VoicingToolbarAlertManager( sim.screenProperty ); - this.menuContent = new VoicingToolbarItem( voicingAlertManager, sim.lookAndFeel ); + this.menuContent = new VoicingToolbarItem( voicingAlertManager, sim.lookAndFeel, { + tandem: options.tandem.createTandem( 'menuContent' ) + } ); // icon for the openButton const chevronIcon = new DoubleChevron(); diff --git a/js/toolbar/VoicingToolbarItem.js b/js/toolbar/VoicingToolbarItem.js index 173fab49..71be0686 100644 --- a/js/toolbar/VoicingToolbarItem.js +++ b/js/toolbar/VoicingToolbarItem.js @@ -8,15 +8,10 @@ */ import BooleanProperty from '../../../axon/js/BooleanProperty.js'; +import merge from '../../../phet-core/js/merge.js'; import PlayStopButton from '../../../scenery-phet/js/buttons/PlayStopButton.js'; import PhetFont from '../../../scenery-phet/js/PhetFont.js'; -import { VoicingText } from '../../../scenery/js/imports.js'; -import { ReadingBlockHighlight } from '../../../scenery/js/imports.js'; -import { voicingManager } from '../../../scenery/js/imports.js'; -import { AlignGroup } from '../../../scenery/js/imports.js'; -import { HBox } from '../../../scenery/js/imports.js'; -import { Node } from '../../../scenery/js/imports.js'; -import { Text } from '../../../scenery/js/imports.js'; +import { AlignGroup, HBox, Node, ReadingBlockHighlight, Text, voicingManager, VoicingText } from '../../../scenery/js/imports.js'; import Tandem from '../../../tandem/js/Tandem.js'; import Utterance from '../../../utterance-queue/js/Utterance.js'; import joist from '../joist.js'; @@ -46,16 +41,21 @@ class VoicingToolbarItem extends Node { /** * @param {VoicingToolbarAlertManager} alertManager - generates the alert content when buttons are pressed * @param {LookAndFeel} lookAndFeel + * @param {Object} [options] */ - constructor( alertManager, lookAndFeel ) { - - super( { + constructor( alertManager, lookAndFeel, options ) { + options = merge( { // pdom tagName: 'section', labelTagName: 'h2', - labelContent: toolbarString - } ); + labelContent: toolbarString, + + // phet-io + tandem: Tandem.REQUIRED + }, options ); + + super( options ); const titleTextOptions = { font: new PhetFont( 14 ), @@ -76,7 +76,8 @@ class VoicingToolbarItem extends Node { a11yLabel: titleString, toggleSwitchOptions: { voicingUtteranceQueue: joistVoicingUtteranceQueue - } + }, + tandem: options.tandem.createTandem( 'muteSpeechSwitch' ) } ); // layout