<commit_before>// Copyright 2022, University of Colorado Boulder

/**
 * PlayPauseStepButtonGroup has a play & pause button, optional step-forward button, and optional step-back button.
 * It's typically a subcomponent of TimeControlNode, and was originally an inner class of TimeControlNode.
 *
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> <<EMAIL>>
 * @author <NAME> (PixelZoom, Inc.)
 * @author <NAME> (PhET Interactive Simulations)
 */

import SceneryPhetConstants from '../SceneryPhetConstants.js';
import PlayPauseButton, { PlayPauseButtonOptions } from './PlayPauseButton.js';
import StepBackwardButton, { StepBackwardButtonOptions } from './StepBackwardButton.js';
import StepForwardButton, { StepForwardButtonOptions } from './StepForwardButton.js';
import sceneryPhet from '../sceneryPhet.js';
import { HBox, HBoxOptions } from '../../../scenery/js/imports.js';
import optionize, { combineOptions } from '../../../phet-core/js/optionize.js';
import Tandem from '../../../tandem/js/Tandem.js';
import DerivedProperty from '../../../axon/js/DerivedProperty.js';
import BooleanIO from '../../../tandem/js/types/BooleanIO.js';
import Vector2 from '../../../dot/js/Vector2.js';
import IProperty from '../../../axon/js/IProperty.js';

const DEFAULT_STEP_BUTTON_RADIUS = 15;
const DEFAULT_STEP_BUTTON_TOUCH_AREA_DILATION = 5;

type SelfOptions = {

  // if true, a StepForwardButton is included in the button group
  includeStepForwardButton?: boolean;

  // if true, a StepBackwardButton is included in the button group
  includeStepBackwardButton?: boolean;

  // horizontal space between Play/Pause and Step buttons
  playPauseStepXSpacing?: number;

  // options for button subcomponents
  playPauseButtonOptions?: Omit<PlayPauseButtonOptions, 'tandem' | 'phetioDocumentation'>;
  stepForwardButtonOptions?: Omit<StepForwardButtonOptions, 'tandem' | 'phetioDocumentation'>;
  stepBackwardButtonOptions?: Omit<StepBackwardButtonOptions, 'tandem' | 'phetioDocumentation'>;
};

export type PlayPauseStepButtonGroupOptions = SelfOptions & Omit<HBoxOptions, 'spacing' | 'children'>;

export default class PlayPauseStepButtonGroup extends HBox {

  private readonly playPauseButton: PlayPauseButton;
  private readonly disposePlayPauseStepButtonGroup: () => void;

  constructor( isPlayingProperty: IProperty<boolean>, providedOptions?: PlayPauseStepButtonGroupOptions ) {

    const options = optionize<PlayPauseStepButtonGroupOptions, SelfOptions, HBoxOptions>()( {

      // {boolean} - if true, a StepForwardButton is included in the button group
      includeStepForwardButton: true,

      // {boolean} - if true, a StepBackwardButton is included in the button group
      includeStepBackwardButton: false,

      // {number} horizontal space between Play/Pause and Step buttons
      playPauseStepXSpacing: 10,

      // Options for the PlayPauseButton
      playPauseButtonOptions: {
        radius: SceneryPhetConstants.DEFAULT_BUTTON_RADIUS,
        touchAreaDilation: 5
      },

      // Options for the StepBackwardButton
      stepBackwardButtonOptions: {
        radius: DEFAULT_STEP_BUTTON_RADIUS,
        touchAreaDilation: DEFAULT_STEP_BUTTON_TOUCH_AREA_DILATION
      },

      // Options for the StepForwardButton
      stepForwardButtonOptions: {
        radius: DEFAULT_STEP_BUTTON_RADIUS,
        touchAreaDilation: DEFAULT_STEP_BUTTON_TOUCH_AREA_DILATION
      },

      // HBoxOptions
      resize: false, // don't change layout if playPauseButton resizes with scaleFactorWhenNotPlaying

      // phet-io
      tandem: Tandem.REQUIRED,

      // pdom
      tagName: 'div', // so that it can receive descriptions
      appendDescription: true
    }, providedOptions );

    // by default, the step buttons are enabled when isPlayingProperty is false, but only create a PhET-iO instrumented
    // Property if it is going to be used
    if ( ( !options.stepForwardButtonOptions.enabledProperty ) || ( !options.stepBackwardButtonOptions.enabledProperty ) ) {
      const defaultEnabledProperty = DerivedProperty.not( isPlayingProperty, {
        tandem: options.tandem.createTandem( 'enabledProperty' ),
        phetioType: DerivedProperty.DerivedPropertyIO( BooleanIO )
      } );

      if ( !options.stepForwardButtonOptions.enabledProperty ) {
        options.stepForwardButtonOptions.enabledProperty = defaultEnabledProperty;
      }
      if ( !options.stepBackwardButtonOptions.enabledProperty ) {
        options.stepBackwardButtonOptions.enabledProperty = defaultEnabledProperty;
      }
    }

    const children = [];

    const playPauseButton = new PlayPauseButton( isPlayingProperty,
      combineOptions<PlayPauseButtonOptions>( {
        tandem: options.tandem.createTandem( 'playPauseButton' ),
        phetioDocumentation: 'Button to control the animation in the simulation. This will also stop the model from stepping.'
      }, options.playPauseButtonOptions ) );
    children.push( playPauseButton );

    let stepForwardButton: StepForwardButton | null = null;
    if ( options.includeStepForwardButton ) {
      stepForwardButton = new StepForwardButton(
        combineOptions<StepForwardButtonOptions>( {
          tandem: options.tandem.createTandem( 'stepForwardButton' ),
          phetioDocumentation: 'Progress the simulation a single model step forwards.'
        }, options.stepForwardButtonOptions ) );
      children.push( stepForwardButton );
    }
<commit_msg>Improve type safety and inference for DerivedProperty, see https://github.com/phetsims/axon/issues/395<commit_after>// Copyright 2022, University of Colorado Boulder

/**
 * PlayPauseStepButtonGroup has a play & pause button, optional step-forward button, and optional step-back button.
 * It's typically a subcomponent of TimeControlNode, and was originally an inner class of TimeControlNode.
 *
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> <<EMAIL>>
 * @author <NAME> (PixelZoom, Inc.)
 * @author <NAME> (PhET Interactive Simulations)
 */

import SceneryPhetConstants from '../SceneryPhetConstants.js';
import PlayPauseButton, { PlayPauseButtonOptions } from './PlayPauseButton.js';
import StepBackwardButton, { StepBackwardButtonOptions } from './StepBackwardButton.js';
import StepForwardButton, { StepForwardButtonOptions } from './StepForwardButton.js';
import sceneryPhet from '../sceneryPhet.js';
import { HBox, HBoxOptions } from '../../../scenery/js/imports.js';
import optionize, { combineOptions } from '../../../phet-core/js/optionize.js';
import Tandem from '../../../tandem/js/Tandem.js';
import DerivedProperty from '../../../axon/js/DerivedProperty.js';
import BooleanIO from '../../../tandem/js/types/BooleanIO.js';
import Vector2 from '../../../dot/js/Vector2.js';
import IProperty from '../../../axon/js/IProperty.js';

const DEFAULT_STEP_BUTTON_RADIUS = 15;
const DEFAULT_STEP_BUTTON_TOUCH_AREA_DILATION = 5;

type SelfOptions = {

  // if true, a StepForwardButton is included in the button group
  includeStepForwardButton?: boolean;

  // if true, a StepBackwardButton is included in the button group
  includeStepBackwardButton?: boolean;

  // horizontal space between Play/Pause and Step buttons
  playPauseStepXSpacing?: number;

  // options for button subcomponents
  playPauseButtonOptions?: Omit<PlayPauseButtonOptions, 'tandem' | 'phetioDocumentation'>;
  stepForwardButtonOptions?: Omit<StepForwardButtonOptions, 'tandem' | 'phetioDocumentation'>;
  stepBackwardButtonOptions?: Omit<StepBackwardButtonOptions, 'tandem' | 'phetioDocumentation'>;
};

export type PlayPauseStepButtonGroupOptions = SelfOptions & Omit<HBoxOptions, 'spacing' | 'children'>;

export default class PlayPauseStepButtonGroup extends HBox {

  private readonly playPauseButton: PlayPauseButton;
  private readonly disposePlayPauseStepButtonGroup: () => void;

  constructor( isPlayingProperty: IProperty<boolean>, providedOptions?: PlayPauseStepButtonGroupOptions ) {

    const options = optionize<PlayPauseStepButtonGroupOptions, SelfOptions, HBoxOptions>()( {

      // {boolean} - if true, a StepForwardButton is included in the button group
      includeStepForwardButton: true,

      // {boolean} - if true, a StepBackwardButton is included in the button group
      includeStepBackwardButton: false,

      // {number} horizontal space between Play/Pause and Step buttons
      playPauseStepXSpacing: 10,

      // Options for the PlayPauseButton
      playPauseButtonOptions: {
        radius: SceneryPhetConstants.DEFAULT_BUTTON_RADIUS,
        touchAreaDilation: 5
      },

      // Options for the StepBackwardButton
      stepBackwardButtonOptions: {
        radius: DEFAULT_STEP_BUTTON_RADIUS,
        touchAreaDilation: DEFAULT_STEP_BUTTON_TOUCH_AREA_DILATION
      },

      // Options for the StepForwardButton
      stepForwardButtonOptions: {
        radius: DEFAULT_STEP_BUTTON_RADIUS,
        touchAreaDilation: DEFAULT_STEP_BUTTON_TOUCH_AREA_DILATION
      },

      // HBoxOptions
      resize: false, // don't change layout if playPauseButton resizes with scaleFactorWhenNotPlaying

      // phet-io
      tandem: Tandem.REQUIRED,

      // pdom
      tagName: 'div', // so that it can receive descriptions
      appendDescription: true
    }, providedOptions );

    // by default, the step buttons are enabled when isPlayingProperty is false, but only create a PhET-iO instrumented
    // Property if it is going to be used
    if ( ( !options.stepForwardButtonOptions.enabledProperty ) || ( !options.stepBackwardButtonOptions.enabledProperty ) ) {
      const defaultEnabledProperty = DerivedProperty.not( isPlayingProperty, {
        tandem: options.tandem.createTandem( 'enabledProperty' ),
        phetioType: DerivedProperty.DerivedPropertyIO( BooleanIO )
      } );

      if ( !options.stepForwardButtonOptions.enabledProperty ) {

        options.stepForwardButtonOptions.enabledProperty = defaultEnabledProperty;
      }
      if ( !options.stepBackwardButtonOptions.enabledProperty ) {

        options.stepBackwardButtonOptions.enabledProperty = defaultEnabledProperty;
      }
    }

    const children = [];

    const playPauseButton = new PlayPauseButton( isPlayingProperty,
      combineOptions<PlayPauseButtonOptions>( {
        tandem: options.tandem.createTandem( 'playPauseButton' ),
        phetioDocumentation: 'Button to control the animation in the simulation. This will also stop the model from stepping.'
      }, options.playPauseButtonOptions ) );
    children.push( playPauseButton );

    let stepForwardButton: StepForwardButton | null = null;
    if ( options.includeStepForwardButton ) {
      stepForwardButton = new StepForwardButton(
        combineOptions<StepForwardButtonOptions>( {
          tandem: options.tandem.createTandem( 'stepForwardButton' ),
          phetioDocumentation: 'Progress the simulation a single model step forwards.'
        }, options.stepForwardButtonOptions ) );
      children.push( stepForwardButton );
    }

{'fraction_non_alphanumeric': 0.09505149002096054, 'fraction_numerical': 0.0024605850724505603, 'mean_word_length': 3.899107142857143, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 33, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4808533', 'n_tokens_mistral': 3111, 'n_tokens_neox': 3000, 'n_words': 776}