/**
   * @param {EnergySkateParkBasicsModel} model
   * @param {Object} [options]
   * @constructor
   */
  function EnergySkateParkBasicsScreenView( model, options ) {

    var view = this;
    ScreenView.call( view, { layoutBounds: new Bounds2( 0, 0, 834, 504 ) } );

    var modelPoint = new Vector2( 0, 0 );
    // earth is 70px high in stage coordinates
    var viewPoint = new Vector2( this.layoutBounds.width / 2, this.layoutBounds.height - BackgroundNode.earthHeight );
    var scale = 50;
    var modelViewTransform = ModelViewTransform2.createSinglePointScaleInvertedYMapping( modelPoint, viewPoint, scale );
    this.modelViewTransform = modelViewTransform;

    this.availableModelBoundsProperty = new Property();
    this.availableModelBoundsProperty.linkAttribute( model, 'availableModelBounds' );

    // The background
    this.backgroundNode = new BackgroundNode( this.layoutBounds );
    this.addChild( this.backgroundNode );

    this.gridNode = new GridNode( model.property( 'gridVisible' ), modelViewTransform );
    this.addChild( this.gridNode );

    var pieChartLegend = new PieChartLegend(
      model.skater,
      model.clearThermal.bind( model ),
      model.property( 'pieChartVisible' ), {
        tandem: options.tandem
      } );
    this.addChild( pieChartLegend );

    this.controlPanel = new EnergySkateParkBasicsControlPanel( model, {
      tandem: options.tandem,
      massSliderPhETIOID: options.massSliderPhETIOID
    } );
    this.addChild( this.controlPanel );
    this.controlPanel.right = this.layoutBounds.width - 5;
    this.controlPanel.top = 5;

    // For the playground screen, show attach/detach toggle buttons
    if ( model.draggableTracks ) {
      var property = model.draggableTracks ? new Property( true ) :
                     new DerivedProperty( [ model.property( 'scene' ) ], function( scene ) { return scene === 2; } );
      this.attachDetachToggleButtons = new AttachDetachToggleButtons( model.property( 'detachable' ), property, this.controlPanel.contentWidth, {
        top: this.controlPanel.bottom + 5,
        centerX: this.controlPanel.centerX
      } );
      this.addChild( this.attachDetachToggleButtons );
    }

    var containsAbove = function( bounds, x, y ) {
      return bounds.minX <= x && x <= bounds.maxX && y <= bounds.maxY;
    };

    // Determine if the skater is onscreen or offscreen for purposes of highlighting the 'return skater' button.
    // Don't check whether the skater is underground since that is a rare case (only if the user is actively dragging a
    // control point near y=0 and the track curves below) and the skater will pop up again soon, see the related
    // flickering problem in #206
    var onscreenProperty = new DerivedProperty( [ model.skater.positionProperty ], function( position ) {
      if ( !view.availableModelBounds ) {
        return true;
      }
      return view.availableModelBounds && containsAbove( view.availableModelBounds, position.x, position.y );
    } );

    var barGraphBackground = new BarGraphBackground( model.skater, model.property( 'barGraphVisible' ), model.clearThermal.bind( model ),
      { tandem: options.tandem } );
    this.addChild( barGraphBackground );

    if ( !model.draggableTracks ) {
      this.sceneSelectionPanel = new SceneSelectionPanel( model, this, modelViewTransform, {
        tandem: options.tandem
      } );// layout done in layout bounds
      this.addChild( this.sceneSelectionPanel );
    }

    // Put the pie chart legend to the right of the bar chart, see #60, #192
    pieChartLegend.mutate( { top: barGraphBackground.top, left: barGraphBackground.right + 8 } );

    var playProperty = new Property( !model.property( 'paused' ).value );
    model.property( 'paused' ).link( function( paused ) {
      playProperty.set( !paused );
    } );
    playProperty.link( function( playing ) {
      model.property( 'paused' ).set( !playing );
    } );
    var playPauseButton = new PlayPauseButton( playProperty, { phetioID: 'playPauseButton' } ).mutate( { scale: 0.6 } );

    // Make the Play/Pause button bigger when it is showing the pause button, see #298
    var pauseSizeIncreaseFactor = 1.35;
    playProperty.lazyLink( function( isPlaying ) {
      playPauseButton.scale( isPlaying ? ( 1 / pauseSizeIncreaseFactor ) : pauseSizeIncreaseFactor );
    } );

    var stepButton = new StepForwardButton( function() { model.manualStep(); }, playProperty, {
      phetioID: options.tandem.createTandem( 'stepButton' )
    } );

    // Make the step button the same size as the pause button.
    stepButton.mutate( { scale: playPauseButton.height / stepButton.height } );
    model.property( 'paused' ).linkAttribute( stepButton, 'enabled' );

    this.addChild( playPauseButton.mutate( {
      centerX: this.layoutBounds.centerX,
      bottom: this.layoutBounds.maxY - 15
    } ) );
    this.addChild( stepButton.mutate( { left: playPauseButton.right + 15, centerY: playPauseButton.centerY } ) );

    this.resetAllButton = new ResetAllButton( {
      listener: model.reset.bind( model ),
      scale: 0.85,
      centerX: this.controlPanel.centerX,

      // Align vertically with other controls, see #134
      centerY: (modelViewTransform.modelToViewY( 0 ) + this.layoutBounds.maxY) / 2 + 8,

      phetioID: options.tandem.createTandem( 'resetAllButton' )
    } );
    this.addChild( this.resetAllButton );

    // The button to return the skater
    this.returnSkaterButton = new RectangularPushButton( {
      content: new Text( controlsRestartSkaterString, {
        maxWidth: 100
      } ),
      listener: model.returnSkater.bind( model ),
      centerY: this.resetAllButton.centerY,
      // X updated in layoutBounds since the reset all button can move horizontally
      phetioID: options.tandem.createTandem( 'returnSkaterButton' )
    } );

    // Disable the return skater button when the skater is already at his initial coordinates
    model.skater.linkAttribute( 'moved', view.returnSkaterButton, 'enabled' );
    this.addChild( this.returnSkaterButton );

    this.addChild( new PlaybackSpeedControl( model.property( 'speed' ), {
      slowSpeedRadioButtonPhETIOID: options.tandem.createTandem( 'slowSpeedRadioButton' ),
      normalSpeedRadioButtonPhETIOID: options.tandem.createTandem( 'normalSpeedRadioButton' )
    } ).mutate( {
      left: stepButton.right + 20,
      centerY: playPauseButton.centerY
    } ) );

    var speedometerNode = new GaugeNode(
      // Hide the needle in for the background of the GaugeNode
      new Property( null ), propertiesSpeedString,
      {
        min: 0,
        max: 20
      },
      {
        // enable/disable updates based on whether the speedometer is visible
        updateEnabledProperty: model.property( 'speedometerVisible' ),
        pickable: false
      } );
    model.property( 'speedometerVisible' ).linkAttribute( speedometerNode, 'visible' );
    speedometerNode.centerX = this.layoutBounds.centerX;
    speedometerNode.top = this.layoutBounds.minY + 5;
    this.addChild( speedometerNode );

    // Layer which will contain all of the tracks
    var trackLayer = new Node();

    // Switch between selectable tracks
    if ( !model.draggableTracks ) {

      var trackNodes = model.tracks.map( function( track ) {
        return new TrackNode( model, track, modelViewTransform, view.availableModelBoundsProperty );
      } ).getArray();

      trackNodes.forEach( function( trackNode ) {
        trackLayer.addChild( trackNode );
      } );

      model.property( 'scene' ).link( function( scene ) {
        trackNodes[ 0 ].visible = (scene === 0);
        trackNodes[ 1 ].visible = (scene === 1);
        trackNodes[ 2 ].visible = (scene === 2);
      } );
    }
    else {

      var addTrackNode = function( track ) {

        var trackNode = new TrackNode( model, track, modelViewTransform, view.availableModelBoundsProperty );
        trackLayer.addChild( trackNode );

        // When track removed, remove its view
        var itemRemovedListener = function( removed ) {
          if ( removed === track ) {
            trackLayer.removeChild( trackNode );
            model.tracks.removeItemRemovedListener( itemRemovedListener );// Clean up memory leak
          }
        };
        model.tracks.addItemRemovedListener( itemRemovedListener );

        return trackNode;
      };

      // Create the tracks for the track toolbox
      var interactiveTrackNodes = model.tracks.map( addTrackNode ).getArray();

      // Add a panel behind the tracks
      var padding = 10;

      var trackCreationPanel = new Rectangle(
        (interactiveTrackNodes[ 0 ].left - padding / 2),
        (interactiveTrackNodes[ 0 ].top - padding / 2),
        (interactiveTrackNodes[ 0 ].width + padding),
        (interactiveTrackNodes[ 0 ].height + padding),
        6,
        6, {
          fill: 'white',
          stroke: 'black'
        } );
      this.addChild( trackCreationPanel );

      model.tracks.addItemAddedListener( addTrackNode );

      var xTip = 20;
      var yTip = 8;
      var xControl = 12;
      var yControl = -5;

      var createArrowhead = function( angle, tail ) {
        var headWidth = 10;
        var headHeight = 10;
        var directionUnitVector = Vector2.createPolar( 1, angle );
        var orthogonalUnitVector = directionUnitVector.perpendicular();
        var tip = directionUnitVector.times( headHeight ).plus( tail );
        return new Path( new Shape().moveToPoint( tail ).lineToPoint( tail.plus( orthogonalUnitVector.times( headWidth / 2 ) ) ).lineToPoint( tip ).lineToPoint( tail.plus( orthogonalUnitVector.times( -headWidth / 2 ) ) ).lineToPoint( tail ).close(),
          { fill: 'black' } );
      };

      var rightCurve = new Path( new Shape().moveTo( 0, 0 ).quadraticCurveTo( -xControl, yControl, -xTip, yTip ),
        { stroke: 'black', lineWidth: 3 } );
      var arrowHead = createArrowhead( Math.PI - Math.PI / 3, new Vector2( -xTip, yTip ) );

      var clearButtonEnabledProperty = model.property( 'clearButtonEnabled' );
      clearButtonEnabledProperty.link( function( clearButtonEnabled ) {
        rightCurve.stroke = clearButtonEnabled ? 'black' : 'gray';
        arrowHead.fill = clearButtonEnabled ? 'black' : 'gray';
      } );

      var clearButton = new EraserButton( {
        iconWidth: 30,
        baseColor: new Color( 221, 210, 32 ),
        phetioID: 'playgroundScreen.clearTracksButton'
      } );
      clearButtonEnabledProperty.linkAttribute( clearButton, 'enabled' );
      clearButton.addListener( function() {model.clearTracks();} );

      this.addChild( clearButton.mutate( { left: 5, centerY: trackCreationPanel.centerY } ) );
    }

    this.addChild( trackLayer );

    // Check to see if WebGL was prevented by a query parameter
    var allowWebGL = phet.chipper.getQueryParameter( 'webgl' ) !== 'false';

    // Use WebGL where available, but not on IE, due to https://github.com/phetsims/energy-skate-park-basics/issues/277
    // and https://github.com/phetsims/scenery/issues/285
    var webGLSupported = Util.isWebGLSupported && allowWebGL;
    var renderer = webGLSupported ? 'webgl' : null;

    var skaterNode = new SkaterNode(
      model.skater,
      this,
      modelViewTransform,
      model.getClosestTrackAndPositionAndParameter.bind( model ),
      model.getPhysicalTracks.bind( model ),
      renderer
    );

    var gaugeNeedleNode = new GaugeNeedleNode( model.skater.property( 'speed' ), {
      min: 0,
      max: 20
    }, {
      renderer: renderer
    } );
    model.property( 'speedometerVisible' ).linkAttribute( gaugeNeedleNode, 'visible' );
    gaugeNeedleNode.x = speedometerNode.x;
    gaugeNeedleNode.y = speedometerNode.y;
    this.addChild( gaugeNeedleNode );
    this.addChild( new BarGraphForeground( model.skater, barGraphBackground, model.property( 'barGraphVisible' ), renderer ) );
    this.addChild( skaterNode );

    var pieChartNode = renderer === 'webgl' ?
                       new PieChartWebGLNode( model.skater, model.property( 'pieChartVisible' ), modelViewTransform ) :
                       new PieChartNode( model.skater, model.property( 'pieChartVisible' ), modelViewTransform );
    this.addChild( pieChartNode );

    // Buttons to return the skater when she is offscreen, see #219
    var iconScale = 0.4;
    var returnSkaterToStartingPointButton = new RectangularPushButton( {
      content: new Image( skaterIconImage, { scale: iconScale } ),

      // green means "go" since the skater will likely start moving at this point
      baseColor: EnergySkateParkColorScheme.kineticEnergy,
      listener: model.returnSkater.bind( model ),
      phetioID: options.tandem.createTandem( 'returnSkaterToPreviousStartingPositionButton' )
    } );

    var returnSkaterToGroundButton = new RectangularPushButton( {
      content: new Image( skaterIconImage, { scale: iconScale } ),
      centerBottom: modelViewTransform.modelToViewPosition( model.skater.startingPosition ),
      baseColor: '#f4514e', // red for stop, since the skater will be stopped on the ground.
      listener: function() { model.skater.resetPosition(); },
      phetioID: options.tandem.createTandem( 'returnSkaterToGroundButton' )
    } );

    this.addChild( returnSkaterToStartingPointButton );
    this.addChild( returnSkaterToGroundButton );

    // When the skater goes off screen, make the "return skater" button big
    onscreenProperty.link( function( skaterOnscreen ) {
      var buttonsVisible = !skaterOnscreen;
      returnSkaterToGroundButton.visible = buttonsVisible;
      returnSkaterToStartingPointButton.visible = buttonsVisible;

      if ( buttonsVisible ) {

        // Put the button where the skater will appear.  Nudge it up a bit so the mouse can hit it from the drop site,
        // without being moved at all (to simplify repeat runs).
        var viewPosition = modelViewTransform.modelToViewPosition( model.skater.startingPosition ).plusXY( 0, 5 );
        returnSkaterToStartingPointButton.centerBottom = viewPosition;

        // If the return skater button went offscreen, move it back on the screen, see #222
        if ( returnSkaterToStartingPointButton.top < 5 ) {
          returnSkaterToStartingPointButton.top = 5;
        }
      }
    } );

    // For debugging the visible bounds
    if ( showAvailableBounds ) {
      this.viewBoundsPath = new Path( null, { pickable: false, stroke: 'red', lineWidth: 10 } );
      this.addChild( this.viewBoundsPath );
    }
  }
    /**
     * @param {Vertex} startVertex
     * @param {Vertex} endVertex
     * @param {number} chargePathLength
     * @param {Tandem} tandem
     * @param {Object} [options]
     */
    constructor( startVertex, endVertex, chargePathLength, tandem, options ) {
      assert && assert( startVertex !== endVertex, 'startVertex cannot be the same as endVertex' );
      assert && assert( typeof chargePathLength === 'number', 'charge path length should be a number' );
      assert && assert( chargePathLength > 0, 'charge path length must be positive' );

      options = _.extend( {
        canBeDroppedInToolbox: true, // In the CCK: Basics intro screen, CircuitElements can't be dropped into the toolbox
        interactive: true, // In CCK: Black Box Study, CircuitElements in the black box cannot be manipulated
        isSizeChangedOnViewChange: true,
        insideTrueBlackBox: false,
        isMetallic: false, // Metallic items can have their voltage read directly (unshielded)
        isFlammable: false,
        tandem: tandem,
        isCurrentReentrant: false
      }, options );

      super( options );

      // @public (read-only) {number} unique identifier for looking up corresponding views
      this.id = index++;

      // @public (read-only) {number} track the time of creation so it can't be dropped in the toolbox for 0.5 seconds
      // see https://github.com/phetsims/circuit-construction-kit-common/issues/244
      this.creationTime = phet.joist.elapsedTime;

      // @public (read-only) flammable circuit elements can catch on fire
      this.isFlammable = options.isFlammable;

      // @public (read-only) metallic circuit elements behave like exposed wires--sensor values can be read directly on the
      // resistor. For instance, coins and paper clips and wires are metallic and can have their values read directly.
      this.isMetallic = options.isMetallic;

      // @public (read-only) {boolean} - whether the size changes when changing from lifelike/schematic, used to determine
      // whether the highlight region should be changed.  True for everything except the switch.
      this.isSizeChangedOnViewChange = options.isSizeChangedOnViewChange;

      // @public (read-only) {number} - whether it is possible to drop the CircuitElement in the toolbox
      this.canBeDroppedInToolbox = options.canBeDroppedInToolbox;

      // @public {Property.<Vertex>} - the Vertex at the origin of the CircuitElement, may change when CircuitElements are
      // connected
      this.startVertexProperty = new Property( startVertex );

      // @public {Property.<Vertex>} - the Vertex at the end of the CircuitElement, may change when CircuitElements are
      // connected
      this.endVertexProperty = new Property( endVertex );

      // @public {NumberProperty} - the flowing current, in amps.
      this.currentProperty = new NumberProperty( 0, {
        reentrant: options.isCurrentReentrant
      } );

      // @public (read-only) {BooleanProperty} - true if the CircuitElement can be edited and dragged
      this.interactiveProperty = new BooleanProperty( options.interactive );

      // @public {BooleanProperty} - whether the circuit element is inside the true black box, not inside the user-created
      // black box, on the interface or outside of the black box
      this.insideTrueBlackBoxProperty = new BooleanProperty( options.insideTrueBlackBox );

      // @public {boolean} - true if the charge layout must be updated (each element is visited every frame to check this)
      this.chargeLayoutDirty = true;

      // @public (read-only) {Emitter} - indicate when this CircuitElement has been connected to another CircuitElement
      this.connectedEmitter = new Emitter();

      // @public (read-only) {Emitter} - indicate when the CircuitElement has been moved to the front in z-ordering
      this.moveToFrontEmitter = new Emitter();

      // @public (read-only) {Emitter} - indicate when an adjacent Vertex has moved to front, so that the corresponding
      // Node can move to front too
      this.vertexSelectedEmitter = new Emitter();

      // @public (read-only) {Emitter} - indicate when either Vertex has moved
      this.vertexMovedEmitter = new Emitter();

      // @public (read-only) {Emitter} - indicate when the circuit element has started being dragged, when it is created
      // in the toolbox
      this.startDragEmitter = new Emitter( { validators: [ { valueType: Event } ] } );

      // @public (read-only) {Emitter} - indicate when the circuit element has been disposed
      this.disposeEmitterCircuitElement = new Emitter();

      // @private {function} - Signify that a Vertex moved
      this.vertexMovedListener = this.emitVertexMoved.bind( this );

      // @private {function} - stored for disposal
      this.linkVertexListener = this.linkVertex.bind( this );

      this.startPositionProperty.link( this.vertexMovedListener );
      this.endPositionProperty.link( this.vertexMovedListener );
      this.startVertexProperty.lazyLink( this.linkVertexListener );
      this.endVertexProperty.lazyLink( this.linkVertexListener );

      // @public (read-only by clients, writable-by-subclasses) {number} the distance the charges must take to get to the
      // other side of the component. This is typically the distance between vertices, but not for light bulbs.  This
      // value is constant, except for (a) wires which can have their length changed and (b) LightBulbs whose path
      // length changes when switching between LIFELIKE |SCHEMATIC
      this.chargePathLength = chargePathLength;

      // The ammeter update is called after items are disposed but before corresponding views are disposed, so we must
      // take care not to display current for any items that are pending deletion.
      // See https://github.com/phetsims/circuit-construction-kit-common/issues/418
      this.circuitElementDisposed = false;
    }
Example #3
0
  /**
   * @constructor
   */
  function ButtonsView() {

    ScreenView.call( this );

    // Message area, for outputting test messages
    var messagePrefix = 'Message: ';
    var messageText = new Text( messagePrefix, {
      font: new Font( { size: 20 } ),
      bottom: this.layoutBounds.height - 5,
      left:   this.layoutBounds.minX + 10
    } );
    this.addChild( messageText );
    var message = function( text ) {
      messageText.text = messagePrefix + text;
    };

    //===================================================================================
    // Radio buttons
    //===================================================================================

    var radioButtonProperty = new Property( 'TWO' );
    radioButtonProperty.lazyLink( function( value ) {
      message( 'Radio button ' + value + ' pressed' );
    } );
    var radioButtonContent = [
      { value: 'ONE', node: new Text( 'ONE', { font: BUTTON_FONT } ) },
      { value: 'TWO', node: new Text( 'TWO', { font: BUTTON_FONT } ) },
      { value: 'THREE', node: new Text( 'THREE', { font: BUTTON_FONT } ) },
      { value: 'FOUR', node: new Text( 'FOUR', { font: BUTTON_FONT } ) }
    ];
    var radioButtonGroup = new RadioButtonGroup( radioButtonProperty, radioButtonContent, {
      orientation: 'vertical',
      selectedLineWidth: 4
    } );
    var radioButtonPanel = new Panel( radioButtonGroup, {
      stroke: 'black',
      left: this.layoutBounds.left + 15,
      top:  this.layoutBounds.top + 15
    } );
    this.addChild( radioButtonPanel );

    //===================================================================================
    // Pseudo-3D buttons A, B, C, D, E
    //===================================================================================

    var buttonAFireCount = 0;
    var buttonA = new RectangularPushButton( {
      content: new Text( '--- A ---', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button A pressed ' + ( ++buttonAFireCount ) + 'x' ); }
    } );

    var buttonB = new RectangularPushButton( {
      content: new Text( '--- B ---', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button B pressed' ); },
      baseColor: new Color( 250, 0, 0 )
    } );

    var buttonC = new RectangularPushButton( {
      content: new Text( '--- C ---', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button C pressed' ); },
      baseColor: 'rgb( 204, 102, 204 )'
    } );

    var buttonD = new RoundPushButton( {
      content: new Text( '--- D ---', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button D pressed' ); }
    } );

    var buttonE = new RoundPushButton( {
      content: new Text( '--- E ---', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button E pressed' ); },
      baseColor: new Color( 245, 184, 0 )
    } );

    var pseudo3DButtonsBox = new HBox( {
      children: [ buttonA, buttonB, buttonC, buttonD, buttonE ],
      spacing: 10,
      left: radioButtonPanel.right + 25,
      top:  this.layoutBounds.top + 15
    } );

    this.addChild( pseudo3DButtonsBox );

    //===================================================================================
    // Flat buttons labeled 1, 2, 3, 4
    //===================================================================================

    var button1 = new RectangularPushButton( {
      content: new Text( '-- 1 --', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button 1 pressed' ); },
      baseColor: 'rgb( 204, 102, 204 )',
      buttonAppearanceStrategy: RectangularButtonView.flatAppearanceStrategy
    } );

    var button2 = new RectangularPushButton( {
      content: new Text( '-- 2 --', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button 2 pressed' ); },
      baseColor: '#A0D022',
      buttonAppearanceStrategy: RectangularButtonView.flatAppearanceStrategy,
      lineWidth: 1,
      stroke: '#202020'
    } );

    var button3 = new RoundPushButton( {
      content: new Text( '- 3 -', { font: BUTTON_FONT } ),
      listener: function() { message( 'Button 3 pressed ' ); },
      buttonAppearanceStrategy: RoundButtonView.flatAppearanceStrategy
    } );

    var button4 = new RoundPushButton( {
      content: new Text( '-- 4 --', { font: BUTTON_FONT, fill: 'white' } ),
      listener: function() { message( 'Button 4 pressed ' ); },
      baseColor: '#CC3300',
      buttonAppearanceStrategy: RoundButtonView.flatAppearanceStrategy
    } );

    var flatButtonsBox = new HBox( {
      children: [ button1, button2, button3, button4 ],
      spacing: 10,
      left: pseudo3DButtonsBox.left,
      top: pseudo3DButtonsBox.bottom + 30
    } );
    this.addChild( flatButtonsBox );

    //===================================================================================
    // Fire! Go! Help! buttons - these demonstrate more colors and sizes of buttons
    //===================================================================================

    var fireButton = new RoundPushButton( {
      content: new Text( 'Fire!', { font: BUTTON_FONT } ),
      listener: function() { message( 'Fire button pressed' ); },
      baseColor: 'orange',
      stroke: 'black',
      lineWidth: 0.5
    } );

    var goButton = new RoundPushButton( {
      content: new Text( 'Go!', { font: BUTTON_FONT } ),
      listener: function() { message( 'Go button pressed' ); },
      baseColor: new Color( 0, 163, 0 ),
      minXPadding: 10
    } );

    var helpButton = new RoundPushButton( {
      content: new Text( 'Help', { font: BUTTON_FONT } ),
      listener: function() { message( 'Help button pressed' ); },
      baseColor: new Color( 244, 154, 194 ),
      minXPadding: 10
    } );

    var actionButtonsBox = new HBox( {
      children: [ fireButton, goButton, helpButton ],
      spacing: 15,
      left: flatButtonsBox.left,
      top: flatButtonsBox.bottom + 25
    } );
    this.addChild( actionButtonsBox );

    //===================================================================================
    // Buttons with fire-on-hold turned on
    //===================================================================================

    var fireOnHeldCount = 0;
    var fireQuicklyWhenHeldButton = new RectangularPushButton( {
      content: new Text( 'Press and hold to test (fast fire)', { font: BUTTON_CAPTION_FONT } ),
      listener: function() { message( 'Fast held button fired ' + ( ++fireOnHeldCount ) + 'x' ); },
      baseColor: new Color( 114, 132, 62 ),
      fireOnHold: true,
      fireOnHoldDelay: 100,
      fireOnHoldInterval: 50,
      left: buttonE.left,
      top: buttonE.bottom + 10
    } );

    var fireSlowlyOnHoldCount = 0;
    var fireSlowlyWhenHeldButton = new RectangularPushButton( {
      content: new Text( 'Press and hold to test (slow fire)', { font: BUTTON_CAPTION_FONT } ),
      listener: function() { message( 'Slow held button fired ' + ( ++fireSlowlyOnHoldCount ) + 'x' ); },
      baseColor: new Color( 147, 92, 120 ),
      fireOnHold: true,
      fireOnHoldDelay: 600,
      fireOnHoldInterval: 300,
      left: fireQuicklyWhenHeldButton.left,
      top: fireQuicklyWhenHeldButton.bottom + 10
    } );

    var heldButtonsBox = new VBox( {
      children: [ fireQuicklyWhenHeldButton, fireSlowlyWhenHeldButton ],
      spacing: 10,
      left: flatButtonsBox.right + 20,
      top:  flatButtonsBox.top
    } );

    this.addChild( heldButtonsBox );

    //===================================================================================
    // Miscellaneous other button examples
    //===================================================================================

    var fireOnDownCount = 0;
    var fireOnDownButton = new RectangularPushButton( {
      content: new Text( 'Fire on Down Button', { font: BUTTON_FONT } ),
      listener: function() { message( 'Fire on down button pressed ' + ( ++fireOnDownCount ) + 'x' ); },
      baseColor: new Color( 255, 255, 61 ),
      fireOnDown: true,
      stroke: 'black',
      lineWidth: 1
    } );

    var htmlButton = new HTMLPushButton( 'HTML <em>button</em> <b>example</b>', {
      listener: function() { message( 'HTML button pressed' ); },
      baseColor: new Color( 64, 225, 0 )
    } );

    // transparent button with something behind it
    var rectangleNode = new Rectangle( 0, 0, 25, 50, { fill: 'red' } );
    var transparentButton = new RectangularPushButton( {
      content: new Text( 'Transparent Button', { font: BUTTON_FONT } ),
      listener: function() { message( 'Transparent button pressed' ); },
      baseColor: new Color( 255, 255, 0, 0.7 ),
      center: rectangleNode.center
    } );
    var transparentParent = new Node( { children: [ rectangleNode, transparentButton ] } );

    var miscButtonsBox = new VBox( {
      children: [ fireOnDownButton, htmlButton, transparentParent ],
      spacing: 15,
      left: actionButtonsBox.left,
      top: actionButtonsBox.bottom + 25
    } );
    this.addChild( miscButtonsBox );

    //===================================================================================
    // Toggle buttons
    //===================================================================================

    // Demonstrate using arbitrary values for toggle button.  Wrap in extra
    // quotes so it is clear that it is a string in the debugging UI.
    var roundToggleButtonProperty = new Property( 'off' );
    roundToggleButtonProperty.lazyLink( function( value ) {
      message( 'Round sticky toggle button state changed to: ' + value );
    } );
    var roundStickyToggleButton = new RoundStickyToggleButton( 'off', 'on', roundToggleButtonProperty, {
      baseColor: new Color( 255, 0, 0 )
    } );

    var rectangularToggleButtonProperty = new Property( false );
    rectangularToggleButtonProperty.lazyLink( function( value ) {
      message( 'Rectangular sticky toggle button state changed to: ' + value );
    } );
    var booleanRectangularStickyToggleButton = new BooleanRectangularStickyToggleButton( rectangularToggleButtonProperty, {
      baseColor: new Color( 0, 200, 200 ),
      centerX: roundStickyToggleButton.centerX,
      top: roundStickyToggleButton.bottom + 10,
      minWidth: 50,
      minHeight: 35
    } );

    var toggleButtonsBox = new VBox( {
      children: [ roundStickyToggleButton, booleanRectangularStickyToggleButton ],
      spacing: 15,
      left: miscButtonsBox.right + 25,
      top: miscButtonsBox.top
    } );
    this.addChild( toggleButtonsBox );

    //===================================================================================
    // Momentary buttons
    //===================================================================================

    // round
    var roundOnProperty = new Property( false );
    roundOnProperty.lazyLink( function( on ) { message( 'RoundMomentaryButton on=' + on ); } );
    var roundMomentaryButton = new RoundMomentaryButton( false, true, roundOnProperty, {
      baseColor: '#D76958',
      left: roundStickyToggleButton.right+ 10,
      centerY: roundStickyToggleButton.centerY
    } );

    // rectangular
    var rectangularOnProperty = new Property( false );
    rectangularOnProperty.lazyLink( function( on ) { message( 'RectangularMomentaryButton on=' + on ); } );
    var rectangularMomentaryButton = new RectangularMomentaryButton( false, true, rectangularOnProperty, {
      baseColor: '#724C35',
      minWidth: 50,
      minHeight: 40,
      centerX: roundMomentaryButton.centerX,
      top: roundMomentaryButton.bottom + 10
    } );

    var momentaryButtonsBox = new VBox( {
      children: [ roundMomentaryButton, rectangularMomentaryButton ],
      spacing: 15,
      left: toggleButtonsBox.right + 25,
      top: toggleButtonsBox.top
    } );
    this.addChild( momentaryButtonsBox );

    //===================================================================================
    // Enable/Disable buttons
    //===================================================================================

    //TODO Shouldn't all of these buttons be able to observe buttonEnabledProperty?
    // Set up a property for testing button enable/disable.
    var buttonsEnabledProperty = new Property( true );
    buttonsEnabledProperty.link( function( enabled ) {
      radioButtonGroup.enabled = enabled;
      buttonA.enabled = enabled;
      buttonB.enabled = enabled;
      buttonC.enabled = enabled;
      buttonD.enabled = enabled;
      buttonE.enabled = enabled;
      button1.enabled = enabled;
      button2.enabled = enabled;
      button3.enabled = enabled;
      button4.enabled = enabled;
      fireButton.enabled = enabled;
      goButton.enabled = enabled;
      helpButton.enabled = enabled;
      fireOnDownButton.enabled = enabled;
      htmlButton.enabled = enabled;
      transparentButton.enabled = enabled;
      roundStickyToggleButton.enabled = enabled;
      booleanRectangularStickyToggleButton.enabled = enabled;
      fireQuicklyWhenHeldButton.enabled = enabled;
      fireSlowlyWhenHeldButton.enabled = enabled;
      rectangularMomentaryButton.enabled = enabled;
      roundMomentaryButton.enabled = enabled;
    } );
    var disableEnableButton = new BooleanRectangularToggleButton(
      new Text( 'Disable Buttons', { font: BUTTON_CAPTION_FONT } ),
      new Text( 'Enable Buttons', { font: BUTTON_CAPTION_FONT } ),
      buttonsEnabledProperty, {
        baseColor: new Color( 204, 255, 51 ),
        right: this.layoutBounds.right - 40,
        bottom: this.layoutBounds.bottom - 40
      }
    );
    this.addChild( disableEnableButton );

    // Add a button to set alternative color scheme.
    var changeButtonColorsButton = new RectangularPushButton( {
        content: new Text( 'Change Some Button Colors', { font: BUTTON_CAPTION_FONT } ),
        listener: function() {
          buttonA.baseColor = new Color( _.random( 0, 255 ), _.random( 0, 255 ), _.random( 0, 255 ) );
          buttonD.baseColor = new Color( _.random( 0, 255 ), _.random( 0, 255 ), _.random( 0, 255 ) );
          button1.baseColor = new Color( _.random( 0, 255 ), _.random( 0, 255 ), _.random( 0, 255 ) );
          button3.baseColor = new Color( _.random( 0, 255 ), _.random( 0, 255 ), _.random( 0, 255 ) );
          message( 'Button colors changed' );
        },
        right: disableEnableButton.right,
        bottom: disableEnableButton.top - 15
      }
    );
    this.addChild( changeButtonColorsButton );
  }