/**
   * @param {Evaporator} evaporator
   * @constructor
   */
  function EvaporationControl( evaporator ) {

    var thisControl = this;

    var label = new Text( StringUtils.format( pattern_0label, evaporationString ), { font: new PhetFont( 22 ) } );

    var slider = new HSlider( evaporator.evaporationRate, new Range( 0, evaporator.maxEvaporationRate ), {
      trackSize: new Dimension2( 200, 6 ),
      enabledProperty: evaporator.enabled,
      endDrag: function() { evaporator.evaporationRate.set( 0 ); }  // at end of drag, snap evaporation rate back to zero
    } );

    var tickFont = new PhetFont( 16 );
    slider.addMajorTick( 0, new Text( noneString, { font: tickFont } ) );
    slider.addMajorTick( evaporator.maxEvaporationRate, new Text( lotsString, { font: tickFont } ) );

    var content = new Node();
    content.addChild( label );
    content.addChild( slider );

    slider.left = label.right + 10;
    slider.centerY = label.centerY;

    Panel.call( thisControl, content,
      { xMargin: 15, yMargin: 8, fill: '#F0F0F0', stroke: 'gray', lineWidth: 1, resize: false } );
  }
    _.each( collection.collectionBoxes, function( collectionBox ) {
      var collectionBoxNode = isSingleCollectionMode ? new SingleCollectionBoxNode( collectionBox, toModelBounds ) : new MultipleCollectionBoxNode( collectionBox, toModelBounds );
      selfNode.collectionBoxNodes.push( collectionBoxNode );

      // TODO: can we fix this up somehow to be better? easier way to force height?
      // center box horizontally and put at bottom vertically in our holder
      function layoutBoxNode() {
        // compute correct offsets
        var offsetX = ( maximumBoxWidth - collectionBoxNode.width ) / 2;
        var offsetY = maximumBoxHeight - collectionBoxNode.height;

        // only apply these if they are different. otherwise we run into infinite recursion
        if ( collectionBoxNode.x !== offsetX || collectionBoxNode.y !== offsetY ) {
          collectionBoxNode.setTranslation( offsetX, offsetY );
        }
      }

      layoutBoxNode();

      // also position if its size changes in the future
      collectionBoxNode.addEventListener( 'bounds', layoutBoxNode );

      var collectionBoxHolder = new Node();
      // enforce consistent bounds of the maximum size. reason: we don't want switching between collections to alter the positions of the collection boxes
      collectionBoxHolder.addChild( new Rectangle( 0, 0, maximumBoxWidth, maximumBoxHeight, { visible: false, stroke: null } ) ); // TODO: Spacer node for Scenery?
      collectionBoxHolder.addChild( collectionBoxNode );
      selfNode.addChild( collectionBoxHolder );
      collectionBoxHolder.top = y;
      y += collectionBoxHolder.height + 15; // TODO: GeneralLayoutNode for Scenery?
    } );
    /**
     * {Dimension2} size
     * @constructor
     */
    function AtomicInteractionsIcon( size ) {
      Node.call( this );

      // background
      var backgroundRect = new Rectangle( 0, 0, size.width, size.height, 0, 0, {
        fill: 'black'
      } );
      this.addChild( backgroundRect );

      // create the two atoms under a parent node
      var atomRadius = size.width * 0.2;
      var gradient = new RadialGradient( 0, 0, 0, 0, 0, atomRadius )
        .addColorStop( 0, PARTICLE_COLOR )
        .addColorStop( 1, PARTICLE_COLOR.darkerColor( 0.5 ) );

      var atomsNode = new Node();
      atomsNode.addChild( new Circle( atomRadius, {
        fill: gradient,
        opacity: 0.85,
        centerX: -atomRadius * 0.7
      } ) );
      atomsNode.addChild( new Circle( atomRadius, {
        fill: gradient,
        opacity: 0.85,
        centerX: atomRadius * 0.7
      } ) );

      // position and add the two interacting atoms
      atomsNode.centerX = backgroundRect.width / 2;
      atomsNode.centerY = backgroundRect.height / 2;
      this.addChild( atomsNode );
    }
  /**
   * This convenience define the "readout pointer", which is an indicator that contains a textual indication of the
   * average atomic mass and also has a pointer on the top that can be used to indicate the position on a linear scale.
   * This node is set up such that the (0,0) point is at the top center of the node, which is where the point of the
   * pointer exists. This is done to make it easy to position the node under the mass indication line.
   *
   * @param {MixIsotopeModel} model
   */
  function ReadoutPointer( model ) {
    var node = new Node();

    this.model = model;
    // Add the triangular pointer. This is created such that the point of the triangle is at (0,0) for this node.

    var vertices = [ new Vector2( -TRIANGULAR_POINTER_WIDTH / 2, TRIANGULAR_POINTER_HEIGHT ),
      new Vector2( TRIANGULAR_POINTER_WIDTH / 2, TRIANGULAR_POINTER_HEIGHT ),
      new Vector2( 0, 0 )
    ];

    var triangle = new Path( Shape.polygon( vertices ), {
      fill: new Color( 0, 143, 212 ),
      lineWidth: 1
    } );
    node.addChild( triangle );

    var readoutText = new Text( '', {
      font: new PhetFont( 14 ),
      maxWidth: 0.9 * SIZE.width,
      maxHeight: 0.9 * SIZE.height
    } );

    var readoutPanel = new Panel( readoutText, {
      minWidth: SIZE.width,
      minHeight: SIZE.height,
      resize: false,
      cornerRadius: 2,
      lineWidth: 1,
      align: 'center',
      fill: 'white'
    } );

    readoutPanel.top = triangle.bottom;
    readoutPanel.centerX = triangle.centerX;
    node.addChild( readoutPanel );

    function updateReadout( averageAtomicMass ) {
      var weight;
      if ( model.showingNaturesMixProperty.get() ) {
        weight = AtomIdentifier.getStandardAtomicMass( model.selectedAtomConfig.protonCount );
      } else {
        weight = averageAtomicMass;
      }
      readoutText.setText( Util.toFixed( weight, NUMBER_DECIMALS ) + ' ' + amuString );
      readoutText.centerX = SIZE.width / 2;
    }

    // Observe the average atomic weight property in the model and update the textual readout whenever it changes.
    // Doesn't need unlink as it stays through out the sim life
    model.testChamber.averageAtomicMassProperty.link( function( averageAtomicMass ) {
      updateReadout( averageAtomicMass );
    } );

    return node;
  }
  var MAX_TITLE_WIDTH = 190; // empirically determined, supports translation

  /**
   * @param {Property<Object>} areaAndPerimeterProperty - An object containing values for area and perimeter
   * @param {Color} areaTextColor
   * @param {Color} perimeterTextColor
   * @param {Object} [options]
   * @constructor
   */
  function AreaAndPerimeterDisplay( areaAndPerimeterProperty, areaTextColor, perimeterTextColor, options ) {

    options = _.extend( {
      maxWidth: Number.POSITIVE_INFINITY
    }, options );

    var contentNode = new Node();
    var areaCaption = new Text( areaString, { font: DISPLAY_FONT } );
    var perimeterCaption = new Text( perimeterString, { font: DISPLAY_FONT } );
    var tempTwoDigitString = new Text( '999', { font: DISPLAY_FONT } );
    var areaReadout = new Text( '', { font: DISPLAY_FONT, fill: areaTextColor } );
    var perimeterReadout = new Text( '', { font: DISPLAY_FONT, fill: perimeterTextColor } );

    contentNode.addChild( areaCaption );
    perimeterCaption.left = areaCaption.left;
    perimeterCaption.top = areaCaption.bottom + 5;
    contentNode.addChild( perimeterCaption );
    contentNode.addChild( areaReadout );
    contentNode.addChild( perimeterReadout );
    var readoutsRightEdge = Math.max( perimeterCaption.right, areaCaption.right ) + 8 + tempTwoDigitString.width;

    areaAndPerimeterProperty.link( function( areaAndPerimeter ) {
      areaReadout.text = areaAndPerimeter.area;
      areaReadout.bottom = areaCaption.bottom;
      areaReadout.right = readoutsRightEdge;
      perimeterReadout.text = areaAndPerimeter.perimeter;
      perimeterReadout.bottom = perimeterCaption.bottom;
      perimeterReadout.right = readoutsRightEdge;
    } );

    // in support of translation, scale the content node if it's too big
    if ( contentNode.width > MAX_CONTENT_WIDTH ){
      contentNode.scale( MAX_CONTENT_WIDTH / contentNode.width );
    }

    this.expandedProperty = new Property( true );
    AccordionBox.call( this, contentNode, {
      cornerRadius: 3,
      titleNode: new Text( valuesString, { font: DISPLAY_FONT, maxWidth: MAX_TITLE_WIDTH } ),
      titleAlignX: 'left',
      contentAlign: 'left',
      fill: 'white',
      showTitleWhenExpanded: false,
      contentXMargin: 8,
      contentYMargin: 4,
      expandedProperty: this.expandedProperty,
      expandCollapseButtonOptions: {
        touchAreaXDilation: 5,
        touchAreaYDilation: 5
      }
    } );

    this.mutate( options );
  }
Example #6
0
  var createItem = function( solute ) {

    var node = new Node();
    var colorNode = new Rectangle( 0, 0, 20, 20, { fill: solute.maxColor, stroke: solute.maxColor.darkerColor() } );
    var textNode = new Text( solute.name, { font: new PhetFont( 20 ) } );
    node.addChild( colorNode );
    node.addChild( textNode );
    textNode.left = colorNode.right + 5;
    textNode.centerY = colorNode.centerY;

    return ComboBox.createItem( node, solute );
  };
    var createFace = function() {
      var leftEye = new Eyeball();
      var rightEye = new Eyeball();
      var face = new Node();
      face.addChild( leftEye.mutate( { y: 300 } ) );
      face.addChild( rightEye.mutate( { left: leftEye.right + leftEye.width, y: leftEye.y } ) );

      // var leftEyebrow = new Eyebrow();
      // var rightEyebrow = new Eyebrow();
      // face.addChild( leftEyebrow.mutate( { x: leftEye.x, y: leftEye.y - 30 } ) );
      // face.addChild( rightEyebrow.mutate( { x: rightEye.x, y: leftEye.y - 30, scale: new Vector2( -1, 1 ) } ) );
      return face;
    };
  function SquarePoolGrid( model, mvt ) {
    var self = this;
    Node.call( this );

    var fontOptions = {
      font: new PhetFont( 12 )
    };

    var leftEdgeOfGrid = model.poolDimensions.leftChamber.centerTop - model.poolDimensions.leftChamber.widthBottom / 2;
    var rightEdgeOfGrid = model.poolDimensions.rightChamber.centerTop + model.poolDimensions.rightChamber.widthTop / 2;
    this.addChild( new GridLinesNode( model.globalModel, mvt, leftEdgeOfGrid, model.poolDimensions.leftChamber.y, rightEdgeOfGrid, model.poolDimensions.leftChamber.y + model.poolDimensions.leftChamber.height + 0.3 ) );

    // Add the labels for meters
    var labelPosX = mvt.modelToViewX( ( model.poolDimensions.leftChamber.centerTop + model.poolDimensions.leftChamber.widthTop / 2 + model.poolDimensions.rightChamber.centerTop - model.poolDimensions.rightChamber.widthTop / 2 ) / 2 );
    var slantMultiplier = 0.45; // Empirically determined to make label line up in space between the pools.
    var depthLabelsMeters = new Node();
    for ( var depthMeters = 0; depthMeters <= model.poolDimensions.leftChamber.height; depthMeters++ ) {
      var metersText = new Text( depthMeters + ' ' + metersString, fontOptions );
      var metersLabelRect = new Rectangle( 0, 0, metersText.width + 5, metersText.height + 5, 10, 10, {fill: '#67a257'} );
      metersText.center = metersLabelRect.center;
      metersLabelRect.addChild( metersText );
      metersLabelRect.centerX = labelPosX + mvt.modelToViewX( depthMeters * slantMultiplier );
      metersLabelRect.centerY = mvt.modelToViewY( depthMeters + model.globalModel.skyGroundBoundY );
      depthLabelsMeters.addChild( metersLabelRect );
    }

    // Add the labels for feet, adjust for loop to limit number of labels.
    var depthLabelsFeet = new Node();
    for ( var depthFeet = 0; depthFeet <= model.poolDimensions.leftChamber.height * 3.3 + 1; depthFeet += 5 ) {
      var feetText = new Text( depthFeet + ' ' + feetString, fontOptions );
      var feetLabelRect = new Rectangle( 0, 0, feetText.width + 5, feetText.height + 5, 10, 10, {fill: '#67a257'} );
      feetText.center = feetLabelRect.center;
      feetLabelRect.addChild( feetText );
      feetLabelRect.centerX = labelPosX + mvt.modelToViewX( depthFeet / 3.3 * slantMultiplier );
      feetLabelRect.centerY = mvt.modelToViewY( depthFeet / 3.3 + model.globalModel.skyGroundBoundY );
      depthLabelsFeet.addChild( feetLabelRect );
    }

    this.addChild( depthLabelsMeters );
    this.addChild( depthLabelsFeet );

    model.globalModel.measureUnitsProperty.link( function( value ) {
      var metersVisible = (value !== 'english');
      depthLabelsMeters.visible = metersVisible;
      depthLabelsFeet.visible = !metersVisible;
    } );

    model.globalModel.isGridVisibleProperty.link( function( value ) {
      self.visible = value;
    } );
  }
 sensor.activeProperty.link( active => {
   if ( active ) {
     if ( backLayer.hasChild( temperatureAndColorSensorNode ) ) {
       backLayer.removeChild( temperatureAndColorSensorNode );
     }
     sensorLayer.addChild( temperatureAndColorSensorNode );
   }
   else {
     if ( sensorLayer.hasChild( temperatureAndColorSensorNode ) ) {
       sensorLayer.removeChild( temperatureAndColorSensorNode );
     }
     backLayer.addChild( temperatureAndColorSensorNode );
   }
 } );
  QUnit.test( 'sibling positioning', function( assert ) {

    const rootNode = new Node( { tagName: 'div' } );
    const display = new Display( rootNode ); // eslint-disable-line
    document.body.appendChild( display.domElement );

    // test bounds are set for basic input elements
    const buttonElement = new Rectangle( 5, 5, 5, 5, { tagName: 'button' } );
    const divElement = new Rectangle( 0, 0, 20, 20, { tagName: 'div', focusable: true } );
    const inputElement = new Rectangle( 10, 3, 25, 5, { tagName: 'input', inputType: 'range' } );

    rootNode.addChild( buttonElement );
    rootNode.addChild( divElement );
    rootNode.addChild( inputElement );

    // udpdate so the display to position elements
    display.updateDisplay();

    assert.ok( siblingBoundsCorrect( buttonElement ), 'button element child of root correctly positioned' );
    assert.ok( siblingBoundsCorrect( divElement ), 'div element child of root correctly positioned' );
    assert.ok( siblingBoundsCorrect( inputElement ), 'input element child of root correctly positioned' );

    // test that bounds are set correctly once we have a hierarchy and add transformations
    rootNode.removeChild( buttonElement );
    rootNode.removeChild( divElement );
    rootNode.removeChild( inputElement );

    rootNode.addChild( divElement );
    divElement.addChild( buttonElement );
    buttonElement.addChild( inputElement );

    // arbitrary transformations down the tree (should be propagated to input element)
    divElement.setCenter( new Vector2( 50, 50 ) );
    buttonElement.setScaleMagnitude( 0.89 );
    inputElement.setRotation( Math.PI / 4 );

    // udpdate so the display to position elements
    display.updateDisplay();
    assert.ok( siblingBoundsCorrect( buttonElement ), 'button element descendant correctly positioned' );
    assert.ok( siblingBoundsCorrect( inputElement ), 'input element descendant correctly positioned' );

    // when inner content of an element changes, its client bounds change - make sure that the element still matches
    // the Node
    buttonElement.innerHTML = 'Some Test';
    display.updateDisplay();
    assert.ok( siblingBoundsCorrect( buttonElement ), 'button element descendant correclty positioned after inner content changed' );

    // remove the display element so it doesn't interfere with qunit api
    document.body.removeChild( display.domElement );
  } );
  function ChamberPoolGrid( model, mvt ) {
    var self = this;
    Node.call( this );

    var fontOptions = {
      font: new PhetFont( 12 )
    };

    this.addChild( new GridLinesNode( model.globalModel, mvt, model.poolDimensions.leftChamber.x1, model.poolDimensions.leftOpening.y1,
      model.poolDimensions.rightOpening.x2, model.poolDimensions.leftChamber.y2 + 0.3 ) );

    // Add the labels for meters
    var labelPosX = mvt.modelToViewX( ( model.poolDimensions.leftChamber.x2 + model.poolDimensions.rightOpening.x1 ) / 2 );
    var depthLabelsMeters = new Node();
    for ( var depthMeters = 0; depthMeters <= model.poolDimensions.leftChamber.y2 - model.globalModel.skyGroundBoundY; depthMeters++ ) {
      var metersText = new Text( depthMeters + ' ' + metersString, fontOptions );
      var metersLabelRect = new Rectangle( 0, 0, metersText.width + 5, metersText.height + 5, 10, 10, {fill: '#67a257'} );
      metersText.center = metersLabelRect.center;
      metersLabelRect.addChild( metersText );
      metersLabelRect.centerX = labelPosX;
      metersLabelRect.centerY = mvt.modelToViewY( depthMeters + model.globalModel.skyGroundBoundY );
      depthLabelsMeters.addChild( metersLabelRect );
    }

    // Add the labels for feet, adjust for loop to limit number of labels.
    var depthLabelsFeet = new Node();
    for ( var depthFeet = 0; depthFeet <= ( model.poolDimensions.leftChamber.y2 - model.globalModel.skyGroundBoundY ) * 3.3 + 1; depthFeet += 5 ) {
      var feetText = new Text( depthFeet + ' ' + feetString, fontOptions );
      var feetLabelRect = new Rectangle( 0, 0, feetText.width + 5, feetText.height + 5, 10, 10, {fill: '#67a257'} );
      feetText.center = feetLabelRect.center;
      feetLabelRect.addChild( feetText );
      feetLabelRect.centerX = labelPosX;
      feetLabelRect.centerY = mvt.modelToViewY( depthFeet / 3.3 + model.globalModel.skyGroundBoundY );
      depthLabelsFeet.addChild( feetLabelRect );
    }

    this.addChild( depthLabelsMeters );
    this.addChild( depthLabelsFeet );

    model.globalModel.measureUnitsProperty.link( function( value ) {
      var metersVisible = (value !== 'english');
      depthLabelsMeters.visible = metersVisible;
      depthLabelsFeet.visible = !metersVisible;
    } );

    model.globalModel.isGridVisibleProperty.link( function( value ) {
      self.visible = value;
    } );

  }
  QUnit.test( 'change', assert => {

    const rootNode = new Node( { tagName: 'div' } );
    const display = new Display( rootNode ); // eslint-disable-line
    beforeTest( display );

    const a = new Rectangle( 0, 0, 20, 20, { tagName: 'input', inputType: 'range' } );

    let gotFocus = false;
    let gotChange = false;

    rootNode.addChild( a );

    a.addInputListener( {
      focus() {
        gotFocus = true;
      },
      change() {
        gotChange = true;
      },
      blur() {
        gotFocus = false;
      }
    } );

    a.accessibleInstances[ 0 ].peer.primarySibling.focus();
    assert.ok( gotFocus && !gotChange, 'focus first' );

    dispatchEvent( a.accessibleInstances[ 0 ].peer.primarySibling, 'change' );

    assert.ok( gotChange && gotFocus, 'a should have been an input' );

    afterTest( display );
  } );
Example #13
0
  function Arrow( model, x, y, rotation ) {
    Node.call( this, { x: x, y: y, rotation: (rotation / 180 * Math.PI) } );

    var arrow = new Node();
    var arrowShape = new Shape();
    var points = [
      [ 5, -30 ],
      [ 13, -30 ],
      [ 13, 13 ],
      [ -25, 13 ],
      [ -25, 17 ],
      [ -40, 8.5 ],
      [ -25, 0 ],
      [ -25, 5 ],
      [ 5, 5 ]
    ];

    arrowShape.moveTo( points[ 0 ][ 0 ], points[ 0 ][ 1 ] );
    _.each( points, function( element ) { arrowShape.lineTo( element[ 0 ], element[ 1 ] ); } );
    arrowShape.close();

    arrow.addChild( new Path( arrowShape, {
      stroke: "#000",
      fill: "#F00",
      lineWidth: 0.2
    } ) );
    this.addChild( arrow );
    model.currentProperty.link( function( current ) {
      // Scale the arrows based on the value of the current.
      // Exponential scaling algorithm.  Linear makes the changes too big.
      var scale = Math.pow( ( current * 0.1 ), 0.7 );
      arrow.matrix = Matrix3.scale( scale );
    } );
  }
Example #14
0
 var createItem = function( dataSet ) {
   var node = new Node();
   // label
   var textNode = new Text( dataSet.name, { font: LSRConstants.TEXT_FONT } );
   node.addChild( textNode );
   return ComboBox.createItem( node, dataSet );
 };
      model.savedQuadraticProperty.link( savedQuadratic => {

        // remove and dispose any previously-saved line
        if ( savedLineNode ) {
          allLinesParent.removeChild( savedLineNode );
          savedLineNode.dispose();
          savedLineNode = null;
        }

        if ( savedQuadratic ) {
          savedLineNode = new QuadraticNode(
            new Property( savedQuadratic ),
            model.graph.xRange,
            model.graph.yRange,
            model.modelViewTransform,
            viewProperties.equationForm,
            viewProperties.equationsVisibleProperty, {
              lineWidth: GQConstants.SAVED_QUADRATIC_LINE_WIDTH,
              preventVertexAndEquationOverlap: options.preventVertexAndEquationOverlap
            } );

          // Add it in the foreground, so the user can see it.
          // See https://github.com/phetsims/graphing-quadratics/issues/36
          allLinesParent.addChild( savedLineNode );
        }
      } );
  /**
   *
   * @param {DnaMolecule} dnaMolecule
   * @param {ModelViewTransform2} modelViewTransform
   * @param {number} backboneStrokeWidth
   * @param {boolean} showGeneBracketLabels
   * @constructor
   */
  function DnaMoleculeNode( dnaMolecule, modelViewTransform, backboneStrokeWidth, showGeneBracketLabels ) {
    Node.call( this );

    // Add the layers onto which the various nodes that represent parts of the dna, the hints, etc. are placed.
    var geneBackgroundLayer = new Node();
    this.addChild( geneBackgroundLayer );

    // Layers for supporting the 3D look by allowing the "twist" to be depicted.
    this.dnaBackboneLayer = new DnaMoleculeCanvasNode( dnaMolecule, modelViewTransform, backboneStrokeWidth, {
      canvasBounds: new Bounds2(
        dnaMolecule.getLeftEdgeXPosition(),
        dnaMolecule.getBottomEdgeYPosition() + modelViewTransform.viewToModelDeltaY( 10 ),
        dnaMolecule.getRightEdgeXPosition(),
        dnaMolecule.getTopEdgeYPosition() - modelViewTransform.viewToModelDeltaY( 10 )
      ),
      matrix: modelViewTransform.getMatrix()
    } );

    this.addChild( this.dnaBackboneLayer );

    // Put the gene backgrounds and labels behind everything.
    for ( var i = 0; i < dnaMolecule.getGenes().length; i++ ) {
      geneBackgroundLayer.addChild( new GeneNode(
        modelViewTransform,
        dnaMolecule.getGenes()[ i ],
        dnaMolecule,
        StringUtils.fillIn( geneString, { geneID: i + 1 } ),
        showGeneBracketLabels
      ) );
    }
  }
  /**
   * @param {UnderPressureModel} underPressureModel of the sim
   * @param {ModelViewTransform2 } modelViewTransform to convert between model and view co-ordinates
   * @param {number} poolLeftX is pool left x coordinate
   * @param {number} poolTopY is pool top y coordinate
   * @param {number} poolRightX is pool right x coordinate
   * @param {number} poolBottomY is pool bottom y coordinate
   * @param {number} poolHeight is height of the pool
   * @param {number} labelXPosition is label x position
   * @param {number} slantMultiplier is to make label line up in space between the pools
   * @constructor
   */
  function TrapezoidPoolGrid( underPressureModel, modelViewTransform, poolLeftX, poolTopY, poolRightX, poolBottomY,
                              poolHeight, labelXPosition, slantMultiplier ) {

    Node.call( this );
    var fontOptions = { font: new PhetFont( 12 ), maxWidth: 20 };

    // add grid line
    this.addChild( new GridLinesNode( underPressureModel.measureUnitsProperty, modelViewTransform, poolLeftX, poolTopY,
      poolRightX, poolBottomY ) );

    // Add the labels for meters
    var depthLabelsMeters = new Node();
    for ( var depthMeters = 0; depthMeters <= poolHeight; depthMeters++ ) {
      var metersText = new Text( StringUtils.format( valueWithUnitsPatternString, depthMeters, mString ), fontOptions );
      var metersLabelRect = new Rectangle( 0, 0, metersText.width + 5, metersText.height + 5, 10, 10,
        { fill: '#67a257' } );
      metersText.center = metersLabelRect.center;
      metersLabelRect.addChild( metersText );
      metersLabelRect.centerX = labelXPosition + modelViewTransform.modelToViewX( depthMeters * slantMultiplier );
      metersLabelRect.centerY = modelViewTransform.modelToViewY( -depthMeters );
      depthLabelsMeters.addChild( metersLabelRect );
    }

    // Add the labels for feet, adjust for loop to limit number of labels.
    var depthLabelsFeet = new Node();
    for ( var depthFeet = 0; depthFeet <= poolHeight * 3.3 + 1; depthFeet += 5 ) {
      var feetText = new Text( StringUtils.format( valueWithUnitsPatternString, depthFeet, ftString ), fontOptions );
      var feetLabelRect = new Rectangle( 0, 0, feetText.width + 5, feetText.height + 5, 10, 10, { fill: '#67a257' } );
      feetText.center = feetLabelRect.center;
      feetLabelRect.addChild( feetText );
      feetLabelRect.centerX = labelXPosition +
                              modelViewTransform.modelToViewDeltaX( depthFeet / 3.3 * slantMultiplier );
      feetLabelRect.centerY = modelViewTransform.modelToViewY( -depthFeet / 3.3 );
      depthLabelsFeet.addChild( feetLabelRect );
    }

    this.addChild( depthLabelsMeters );
    this.addChild( depthLabelsFeet );

    underPressureModel.measureUnitsProperty.link( function( measureUnits ) {
      depthLabelsFeet.visible = (measureUnits === 'english');
      depthLabelsMeters.visible = (measureUnits !== 'english');
    } );

    underPressureModel.isGridVisibleProperty.linkAttribute( this, 'visible' );
  }
  QUnit.test( 'focusin/focusout (focus/blur)', assert => {

    const rootNode = new Node( { tagName: 'div' } );
    const display = new Display( rootNode ); // eslint-disable-line
    beforeTest( display );

    const a = new Rectangle( 0, 0, 20, 20, { tagName: 'button' } );

    let aGotFocus = false;
    let aLostFocus = false;
    let bGotFocus = false;

    rootNode.addChild( a );

    a.addInputListener( {
      focus() {
        aGotFocus = true;
      },
      blur() {
        aLostFocus = true;
      }
    } );

    a.focus();
    assert.ok( aGotFocus, 'a should have been focused' );
    assert.ok( !aLostFocus, 'a should not blur' );

    const b = new Rectangle( 0, 0, 20, 20, { tagName: 'button' } );

    // TODO: what if b was child of a, make sure these events don't bubble!
    rootNode.addChild( b );

    b.addInputListener( {
      focus() {
        bGotFocus = true;
      }
    } );

    b.focus();

    assert.ok( bGotFocus, 'b should have been focused' );
    assert.ok( aLostFocus, 'a should have lost focused' );

    afterTest( display );
  } );
  /**
   * @constructor
   * @param {BASEModel} model
   * @param {BalloonModel} balloonModel
   * @param {BalloonNode} balloonNode 
   * @param {Bounds2} layoutBounds
   */
  function BalloonInteractionCueNode( model, balloonModel, balloonNode, layoutBounds ) {

    Node.call( this );

    // create the help node for the WASD and arrow keys, invisible except for on the initial balloon pick up
    var directionKeysParent = new Node();
    this.addChild( directionKeysParent );

    var wNode = this.createMovementKeyNode( 'up' );
    var aNode = this.createMovementKeyNode( 'left' );
    var sNode = this.createMovementKeyNode( 'down' );
    var dNode = this.createMovementKeyNode( 'right' );

    directionKeysParent.addChild( wNode );
    directionKeysParent.addChild( aNode );
    directionKeysParent.addChild( sNode );
    directionKeysParent.addChild( dNode );

    // add listeners to update visibility of nodes when location changes and when the wall is made
    // visible/invisible
    Property.multilink( [ balloonModel.locationProperty, model.wall.isVisibleProperty ], function( location, visible ) {

      // get the max x locations depending on if the wall is visible
      var centerXRightBoundary;
      if ( visible ) {
        centerXRightBoundary = PlayAreaMap.X_LOCATIONS.AT_WALL;
      }
      else {
        centerXRightBoundary = PlayAreaMap.X_BOUNDARY_LOCATIONS.AT_RIGHT_EDGE;
      }

      var balloonCenter = balloonModel.getCenter();
      aNode.visible = balloonCenter.x !== PlayAreaMap.X_BOUNDARY_LOCATIONS.AT_LEFT_EDGE;
      sNode.visible = balloonCenter.y !== PlayAreaMap.Y_BOUNDARY_LOCATIONS.AT_BOTTOM;
      dNode.visible = balloonCenter.x !== centerXRightBoundary;
      wNode.visible = balloonCenter.y !== PlayAreaMap.Y_BOUNDARY_LOCATIONS.AT_TOP;
    } );

    // place the direction cues relative to the balloon bounds
    var balloonBounds = balloonModel.bounds;
    wNode.centerBottom = balloonBounds.getCenterTop().plusXY( 0, -BALLOON_KEY_SPACING );
    aNode.rightCenter = balloonBounds.getLeftCenter().plusXY( -BALLOON_KEY_SPACING, 0 );
    sNode.centerTop = balloonBounds.getCenterBottom().plusXY( 0, BALLOON_KEY_SPACING + SHADOW_WIDTH );
    dNode.leftCenter = balloonBounds.getRightCenter().plusXY( BALLOON_KEY_SPACING + SHADOW_WIDTH, 0 );
  }
  var createItem = function( solute ) {
    var node = new Node();

    // color chip
    var soluteColor = solute.stockColor;
    var colorNode = new Rectangle( 0, 0, 20, 20, { fill: soluteColor, stroke: soluteColor.darkerColor() } );

    // label
    var textNode = new Text(
      StringUtils.format( pattern_0name_1pH, solute.name, Util.toFixed( solute.pH, PHScaleConstants.PH_COMBO_BOX_DECIMAL_PLACES ) ),
      { font: new PhetFont( 22 ) } );

    node.addChild( colorNode );
    node.addChild( textNode );
    textNode.left = colorNode.right + 5;
    textNode.centerY = colorNode.centerY;
    return ComboBox.createItem( node, solute );
  };
Example #21
0
  QUnit.test( 'Using a color instance', function( assert ) {
    var scene = new Node();

    var rect = new Rectangle( 0, 0, 100, 50 );
    assert.ok( rect.fill === null, 'Always starts with a null fill' );
    scene.addChild( rect );
    var color = new Color( 255, 0, 0 );
    rect.fill = color;
    color.setRGBA( 0, 255, 0, 1 );
  } );
Example #22
0
  function createTestNodeTree() { // eslint-disable-line no-unused-vars
    var node = new Node();
    node.addChild( new Node() );
    node.addChild( new Node() );
    node.addChild( new Node() );

    node.children[ 0 ].addChild( new Node() );
    node.children[ 0 ].addChild( new Node() );
    node.children[ 0 ].addChild( new Node() );
    node.children[ 0 ].addChild( new Node() );
    node.children[ 0 ].addChild( new Node() );

    node.children[ 0 ].children[ 1 ].addChild( new Node() );
    node.children[ 0 ].children[ 3 ].addChild( new Node() );
    node.children[ 0 ].children[ 3 ].addChild( new Node() );

    node.children[ 0 ].children[ 3 ].children[ 0 ].addChild( new Node() );

    return node;
  }
  /**
   * @param {Property.<number>} scaleProperty - Scale property for updating.
   * @param {Range} range - Working range of slider.
   * @param {number} step step of scale changes
   * @param {boolean} isIncrease flag for defining type of button
   * @constructor
   */
  function SliderButton( scaleProperty, range, step, isIncrease ) {

    // create default view
    var sample = new Node( {
      children: [
        new Rectangle( 0, 0, BUTTON_SIZE, BUTTON_SIZE, 2, 2, { fill: '#DBD485' } ),
        new Rectangle( 4, BUTTON_SIZE / 2 - 1, BUTTON_SIZE - 8, 2, { fill: 'black' } )
      ]
    } );

    // increase or decrease view
    if ( isIncrease ) {
      sample.addChild( new Rectangle( BUTTON_SIZE / 2 - 1, 4, 2, BUTTON_SIZE - 8, { fill: 'black' } ) );
    }

    RectangularPushButton.call( this, {
      content: sample,
      xMargin: 0,
      yMargin: 0,
      listener: function() {
        scaleProperty.value = Math.max(
          Math.min( scaleProperty.value + (isIncrease ? step : -step), range.max ),
          range.min );
      }
    } );

    var self = this;

    // add disabling effect for buttons
    if ( isIncrease ) {
      // plus button
      scaleProperty.link( function( scaleValue ) {
        self.enabled = (scaleValue !== range.max);
      } );
    }
    else {
      // minus button
      scaleProperty.link( function( scaleValue ) {
        self.enabled = (scaleValue !== range.min);
      } );
    }

    // Increase the touch area in all directions except toward the slider knob,
    // so that they won't interfere too much on touch devices
    var dilationSize = 15;
    var dilateTop = ( isIncrease ) ? dilationSize : 0;
    var dilateBottom = ( isIncrease ) ? 0 : dilationSize;
    this.touchArea = Shape.bounds( new Bounds2(
      this.localBounds.minX - dilationSize,
      this.localBounds.minY - dilateTop,
      this.localBounds.maxX + dilationSize,
      this.localBounds.maxY + dilateBottom ) );
  }
  /**
   * Convenience function for creating tick marks. This includes both the actual mark and the label.
   * @param {NumberAtom} isotopeConfig
   */
  function IsotopeTickMark( isotopeConfig ) {
    var node = new Node();

    // Create the tick mark itself.  It is positioned such that (0,0) is the center of the mark.
    var shape = new Line( 0, -TICK_MARK_LINE_HEIGHT / 2, 0, TICK_MARK_LINE_HEIGHT / 2, {
      lineWidth: TICK_MARK_LINE_WIDTH,
      stroke: 'black'
    } );
    node.addChild( shape );

    // Create the label that goes above the tick mark.
    var label = new RichText( ' <sup>' + isotopeConfig.massNumberProperty.get() + '</sup>' +
      AtomIdentifier.getSymbol( isotopeConfig.protonCountProperty.get() ), {
        font: new PhetFont( 12 )
      } );
    label.centerX = shape.centerX;
    label.bottom = shape.top;
    node.addChild( label );

    return node;
  }
Example #25
0
  QUnit.test( 'setting accessible order on nodes with no accessible content', function( assert ) {
    var rootNode = new Node();
    var display = new Display( rootNode ); // eslint-disable-line
    document.body.appendChild( display.domElement );

    var a = new Node( { tagName: 'div' } );
    var b = new Node();
    var c = new Node( { tagName: 'div' } );
    var d = new Node( { tagName: 'div' } );
    var e = new Node( { tagName: 'div' } );
    var f = new Node( { tagName: 'div' } );
    rootNode.addChild( a );
    a.addChild( b );
    b.addChild( c );
    b.addChild( e );
    c.addChild( d );
    c.addChild( f );
    a.accessibleOrder = [ e, c ];

    var divA = a.accessibleInstances[ 0 ].peer.primarySibling;
    var divC = c.accessibleInstances[ 0 ].peer.primarySibling;
    var divE = e.accessibleInstances[ 0 ].peer.primarySibling;

    assert.ok( divA.children[ 0 ] === divE, 'div E should be first child of div B' );
    assert.ok( divA.children[ 1 ] === divC, 'div C should be second child of div B' );
  } );
Example #26
0
  QUnit.test( 'Bounds and Visible Bounds', function( assert ) {
    var node = new Node();
    var rect = new Rectangle( 0, 0, 100, 50 );
    node.addChild( rect );

    assert.ok( node.visibleBounds.equals( new Bounds2( 0, 0, 100, 50 ) ), 'Visible Bounds Visible' );
    assert.ok( node.bounds.equals( new Bounds2( 0, 0, 100, 50 ) ), 'Complete Bounds Visible' );

    rect.visible = false;

    assert.ok( node.visibleBounds.equals( Bounds2.NOTHING ), 'Visible Bounds Invisible' );
    assert.ok( node.bounds.equals( new Bounds2( 0, 0, 100, 50 ) ), 'Complete Bounds Invisible' );
  } );
Example #27
0
    createSlopeToolIcon: function( width ) {

      var parentNode = new Node();

      // slope tool
      var slopeToolNode = new SlopeToolNode( new Property( Line.createSlopeIntercept( 1, 2, 0 ) ),
        ModelViewTransform2.createOffsetXYScaleMapping( Vector2.ZERO, 26, -26 ) );
      parentNode.addChild( slopeToolNode );

      // dashed line where the line would be, tweaked visually
      var lineNode = new Path( Shape.lineSegment( slopeToolNode.left + ( 0.4 * slopeToolNode.width ), slopeToolNode.bottom,
          slopeToolNode.right, slopeToolNode.top + ( 0.5 * slopeToolNode.height ) ),
        {
          lineWidth: 1,
          lineDash: [ 6, 6 ],
          stroke: 'black'
        } );
      parentNode.addChild( lineNode );

      parentNode.scale( width / parentNode.width );
      return parentNode;
    },
 /**
  * Creates and return a node containing the radio buttons that allow the user to select the display mode for the scale.
  *
  * @param {Property} displayModeProperty
  */
 function DisplayModeSelectionNode( displayModeProperty ) {
   var radioButtonRadius = 6;
   var LABEL_FONT = new PhetFont( 14 );
   var massNumberButton = new AquaRadioButton( displayModeProperty, DISPLAY_MODE.MASS_NUMBER,
     new Text( massNumberString, {
       font: LABEL_FONT,
       maxWidth: 125,
       fill: 'white'
     } ), { radius: radioButtonRadius } );
   var atomicMassButton = new AquaRadioButton( displayModeProperty, DISPLAY_MODE.ATOMIC_MASS,
     new Text( atomicMassString, {
       font: LABEL_FONT,
       maxWidth: 125,
       fill: 'white'
     } ), { radius: radioButtonRadius } );
   var displayButtonGroup = new Node();
   displayButtonGroup.addChild( massNumberButton );
   atomicMassButton.top = massNumberButton.bottom + 8;
   atomicMassButton.left = displayButtonGroup.left;
   displayButtonGroup.addChild( atomicMassButton );
   return displayButtonGroup;
 }
  QUnit.test( 'click extra', assert => {

    // create a node
    const a1 = new Node( {
      tagName: 'button'
    } );
    const root = new Node( { tagName: 'div' } );
    const display = new Display( root ); // eslint-disable-line
    beforeTest( display );

    root.addChild( a1 );
    assert.ok( a1.inputListeners.length === 0, 'no input accessible listeners on instantiation' );
    assert.ok( a1.labelContent === null, 'no label on instantiation' );

    // add a listener
    const listener = { click: function() { a1.labelContent = TEST_LABEL; } };
    a1.addInputListener( listener );
    assert.ok( a1.inputListeners.length === 1, 'accessible listener added' );

    // verify added with hasInputListener
    assert.ok( a1.hasInputListener( listener ) === true, 'found with hasInputListener' );

    // fire the event
    a1.accessibleInstances[ 0 ].peer.primarySibling.click();
    assert.ok( a1.labelContent === TEST_LABEL, 'click fired, label set' );

    // remove the listener
    a1.removeInputListener( listener );
    assert.ok( a1.inputListeners.length === 0, 'accessible listener removed' );

    // verify removed with hasInputListener
    assert.ok( a1.hasInputListener( listener ) === false, 'not found with hasInputListener' );

    // make sure event listener was also removed from DOM element
    // click should not change the label
    a1.labelContent = TEST_LABEL_2;
    assert.ok( a1.labelContent === TEST_LABEL_2, 'before click' );

    // setting the label redrew the pdom, so get a reference to the new dom element.
    a1.accessibleInstances[ 0 ].peer.primarySibling.click();
    assert.ok( a1.labelContent === TEST_LABEL_2, 'click should not change label' );

    // verify disposal removes accessible input listeners
    a1.addInputListener( listener );
    a1.dispose();

    // TODO: Since converting to use Node.inputListeners, we can't assume this anymore
    // assert.ok( a1.hasInputListener( listener ) === false, 'disposal removed accessible input listeners' );
    
    afterTest( display );
  } );
  QUnit.test( 'Global KeyStateTracker tests', assert => {

    const rootNode = new Node( { tagName: 'div' } );
    const display = new Display( rootNode ); // eslint-disable-line
    beforeTest( display );

    const a = new Node( { tagName:'button' } );
    const b = new Node( { tagName:'button' } );
    const c = new Node( { tagName:'button' } );
    const d = new Node( { tagName:'button' } );

    a.addChild( b );
    b.addChild( c );
    c.addChild( d );
    rootNode.addChild( a );

    const dPrimarySibling = d.accessibleInstances[ 0 ].peer.primarySibling;
    triggerDOMEvent( 'keydown', dPrimarySibling, KeyboardUtil.KEY_RIGHT_ARROW );

    assert.ok( Display.keyStateTracker.isKeyDown( KeyboardUtil.KEY_RIGHT_ARROW ), 'global keyStateTracker should be updated with right arrow key down' );

    afterTest( display );
  } );