' + ''; window.open( 'data:text/html;charset=utf-8,' + encodeURIComponent( htmlContent ) ); }, toStringWithChildren: function( mutateRoot, rootName ) { rootName = rootName || 'scene'; var rootNode = this._rootNode; var result = ''; var nodes = this._rootNode.getTopologicallySortedNodes().slice( 0 ).reverse(); // defensive slice, in case we store the order somewhere function name( node ) { return node === rootNode ? rootName : ( ( node.constructor.name ? node.constructor.name.toLowerCase() : '(node)' ) + node.id ); } _.each( nodes, function( node ) { if ( result ) { result += '\n'; } if ( mutateRoot && node === rootNode ) { var props = rootNode.getPropString( ' ', false ); var mutation = ( props ? ( '\n' + props + '\n' ) : '' ); if ( mutation !== '' ) { result += rootName + '.mutate( {' + mutation + '} )'; } else { // bleh. strip off the last newline result = result.slice( 0, -1 ); } } else { result += 'var ' + name( node ) + ' = ' + node.toString( '', false ); } _.each( node.children, function( child ) { result += '\n' + name( node ) + '.addChild( ' + name( child ) + ' );'; } ); } ); return result; }, /** * Will attempt to call callback( {string} dataURI ) with the rasterization of the entire Display's DOM structure, * used for internal testing. Will call-back null if there was an error * * Only tested on recent Chrome and Firefox, not recommended for general use. Guaranteed not to work for IE <= 10. * * See https://github.com/phetsims/scenery/issues/394 for some details. */ foreignObjectRasterization: function( callback ) { // Scan our drawable tree for Canvases. We'll rasterize them here (to data URLs) so we can replace them later in // the HTML tree (with images) before putting that in the foreignObject. That way, we can actually display // things rendered in Canvas in our rasterization. var canvasUrlMap = {}; function scanForCanvases( drawable ) { if ( drawable.blocks ) { // we're a backbone _.each( drawable.blocks, function( childDrawable ) { scanForCanvases( childDrawable ); } ); } else if ( drawable.firstDrawable && drawable.lastDrawable ) { // we're a block for ( var childDrawable = drawable.firstDrawable; childDrawable !== drawable.lastDrawable; childDrawable = childDrawable.nextDrawable ) { scanForCanvases( childDrawable ); } scanForCanvases( drawable.lastDrawable ); // wasn't hit in our simplified (and safer) loop if ( drawable.domElement && drawable.domElement instanceof window.HTMLCanvasElement ) { canvasUrlMap[ drawable.canvasId ] = drawable.domElement.toDataURL(); } } } scanForCanvases( this._rootBackbone ); // Create a new document, so that we can (1) serialize it to XHTML, and (2) manipulate it independently. // Inspired by http://cburgmer.github.io/rasterizeHTML.js/ var doc = document.implementation.createHTMLDocument( '' ); doc.documentElement.innerHTML = this.domElement.outerHTML; doc.documentElement.setAttribute( 'xmlns', doc.documentElement.namespaceURI ); // Replace each with an that has src=canvas.toDataURL() and the same style var displayCanvases = doc.documentElement.getElementsByTagName( 'canvas' ); displayCanvases = Array.prototype.slice.call( displayCanvases ); // don't use a live HTMLCollection copy! for ( var i = 0; i < displayCanvases.length; i++ ) { var displayCanvas = displayCanvases[ i ]; var cssText = displayCanvas.style.cssText; var displayImg = doc.createElement( 'img' ); var src = canvasUrlMap[ displayCanvas.id ]; assert && assert( src, 'Must have missed a toDataURL() on a Canvas' ); displayImg.src = src; displayImg.setAttribute( 'style', cssText ); displayCanvas.parentNode.replaceChild( displayImg, displayCanvas ); } var displayWidth = this.width; var displayHeight = this.height; var completeFunction = function() { Display.elementToSVGDataURL( doc.documentElement, displayWidth, displayHeight, callback ); }; // Convert each 's xlink:href so that it's a data URL with the relevant data, e.g. // // gets replaced with a data URL. // See https://github.com/phetsims/scenery/issues/573 var replacedImages = 0; // Count how many images get replaced. We'll decrement with each finished image. var hasReplacedImages = false; // Whether any images are replaced var displaySVGImages = Array.prototype.slice.call( doc.documentElement.getElementsByTagName( 'image' ) ); for ( var j = 0; j < displaySVGImages.length; j++ ) { var displaySVGImage = displaySVGImages[ j ]; var currentHref = displaySVGImage.getAttribute( 'xlink:href' ); if ( currentHref.slice( 0, 5 ) !== 'data:' ) { replacedImages++; hasReplacedImages = true; (function() { // Closure variables need to be stored for each individual SVG image. var refImage = new window.Image(); var svgImage = displaySVGImage; refImage.onload = function() { // Get a Canvas var refCanvas = document.createElement( 'canvas' ); refCanvas.width = refImage.width; refCanvas.height = refImage.height; var refContext = refCanvas.getContext( '2d' ); // Draw the (now loaded) image into the Canvas refContext.drawImage( refImage, 0, 0 ); // Replace the 's href with the Canvas' data. svgImage.setAttribute( 'xlink:href', refCanvas.toDataURL() ); // If it's the last replaced image, go to the next step if ( --replacedImages === 0 ) { completeFunction(); } assert && assert( replacedImages >= 0 ); }; refImage.onerror = function() { // NOTE: not much we can do, leave this element alone. // If it's the last replaced image, go to the next step if ( --replacedImages === 0 ) { completeFunction(); } assert && assert( replacedImages >= 0 ); }; // Kick off loading of the image. refImage.src = currentHref; })(); } } // If no images are replaced, we need to call our callback through this route. if ( !hasReplacedImages ) { completeFunction(); } }, popupRasterization: function() { this.foreignObjectRasterization( window.open ); } }, Events.prototype ), { /** * Takes a given DOM element, and asynchronously renders it to a string that is a data URL representing an SVG * file. * @public * * @param {HTMLElement} domElement * @param {number} width - The width of the output SVG * @param {number} height - The height of the output SVG * @param {function} callback - Called as callback( url: {string} ), where the URL will be the encoded SVG file. */ elementToSVGDataURL: function( domElement, width, height, callback ) { var canvas = document.createElement( 'canvas' ); var context = canvas.getContext( '2d' ); canvas.width = width; canvas.height = height; // Serialize it to XHTML that can be used in foreignObject (HTML can't be) var xhtml = new window.XMLSerializer().serializeToString( domElement ); // Create an SVG container with a foreignObject. var data = '' + '' + '
' + xhtml + '
' + '
' + '
'; // Load an with the SVG data URL, and when loaded draw it into our Canvas var img = new window.Image(); img.onload = function() { context.drawImage( img, 0, 0 ); callback( canvas.toDataURL() ); // Endpoint here }; img.onerror = function() { callback( null ); }; // We can't btoa() arbitrary unicode, so we need another solution, // see https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22 var uint8array = new window.TextEncoderLite( 'utf-8' ).encode( data ); var base64 = window.fromByteArray( uint8array ); // turn it to base64 and wrap it in the data URL format img.src = 'data:image/svg+xml;base64,' + base64; }, set focus( value ) { this.focusProperty.value = value; }, get focus() { return this.focusProperty.value; } } ); Display.customCursors = { 'scenery-grab-pointer': [ 'grab', '-moz-grab', '-webkit-grab', 'pointer' ], 'scenery-grabbing-pointer': [ 'grabbing', '-moz-grabbing', '-webkit-grabbing', 'pointer' ] }; // Each Display has an axon Property to indicate which component is focused (or null, if no scenery node is focused). // By passing the tandem and phetioValueType, PhET-iO is able to interoperate (save/restore/control/observe) the focus rectangle // When focused, the value has this type: { display: {Display}, trail: {Trail} } Display.focusProperty = new Property( null, { // Make this a static tandem so that it can be added to instance proxies correctly (batched and then flushed when the // listener is added). tandem: Tandem.createStaticTandem( 'display' ).createTandem( 'focusProperty' ), phetioValueType: TFocus } ); // @public {Emitter} - Fires when we detect an input event that would be considered a "user gesture" by Chrome, so // that we can trigger browser actions that are only allowed as a result. // See https://github.com/phetsims/scenery/issues/802 and https://github.com/phetsims/vibe/issues/32 for more // information. Display.userGestureEmitter = new Emitter(); return Display; } ); // Copyright 2015, University of Colorado Boulder /** * Creates the namespace for this repository. * * @author Chris Malley (PixelZoom, Inc.) */ define( 'VIBE/vibe',['require','PHET_CORE/Namespace'],function( require ) { 'use strict'; // modules var Namespace = require( 'PHET_CORE/Namespace' ); return new Namespace( 'vibe' ); } ); define('audio',{load: function(id){throw new Error("Dynamic load not allowed: " + id);}}); define("audio!VIBE/empty.mp3", function(){ return [{base64:'data:audio/mpeg;base64,//s0wAAAAAABLgAAACAAACXAAAAE//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////s0wD2AAAABLgAAACAAACXAAAAE//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAA//s0wHsAAAABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wLiAAAABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wPYADQABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//s0wP+AD2ABLgAAACAAACXAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'} ];}); // Copyright 2013-2015, University of Colorado Boulder /** * Type for loading and playing sounds, works on multiple platforms and supports embedded base64 data. This uses Web * Audio when available, primarily because the webkit platforms were failing with cross-domain errors when attempting to * load audio data from embedded data URIs. This was occurring in mid-September 2013. Simplification may be possible * if the cross-domain issue goes away at some point in the future. */ define( 'VIBE/Sound',['require','SCENERY/display/Display','PHET_CORE/inherit','PHET_CORE/platform','AXON/Property','VIBE/vibe','audio!VIBE/empty.mp3'],function( require ) { 'use strict'; // modules var Display = require( 'SCENERY/display/Display' ); var inherit = require( 'PHET_CORE/inherit' ); var platform = require( 'PHET_CORE/platform' ); var Property = require( 'AXON/Property' ); var vibe = require( 'VIBE/vibe' ); // sounds var empty = require( 'audio!VIBE/empty.mp3' ); // global property that allows all audio to be turned on/off, see #11 var audioEnabledProperty = new Property( true ); // Set up a single audio context that will be used by all sounds when using Web Audio API. var audioContext; if ( 'AudioContext' in window ) { audioContext = new AudioContext(); } else if ( 'webkitAudioContext' in window ) { audioContext = new webkitAudioContext(); // eslint-disable-line no-undef } // Controls volume if Web Audio API supported if ( audioContext ) { var gainNode = audioContext.createGain(); gainNode.connect( audioContext.destination ); gainNode.gain.value = phet.chipper.queryParameters.audioVolume; } /** * @param {Array} soundInfoArray An array of 'soundInfo' objects. Each soundInfo object includes *either* a url that * points to the sound to be played *or* a base64-encoded version of the sound data. The array is generally used to * hold multiple formats for a given sound (e.g. mp3 and ogg). * @constructor */ function Sound( soundInfoArray ) { var self = this; // For backward compatibility with earlier versions, support the case where a single soundInfo object is passed in. var localSoundInfoArray = soundInfoArray; if ( !( soundInfoArray instanceof Array ) ) { localSoundInfoArray = new Array( soundInfoArray ); } // Parameter checking. localSoundInfoArray.forEach( function( soundInfo ) { if ( typeof( soundInfo ) !== 'object' || ( typeof( soundInfo.base64 ) === 'undefined' && typeof( soundInfo.url ) === 'undefined' ) ) { throw new Error( 'Error with soundInfo object: Does not contain a necessary value.' ); } } ); this.sound = document.createElement( 'audio' ); var supportedFormatFound = false; var soundInfo = null; for ( var i = 0; i < localSoundInfoArray.length && !supportedFormatFound; i++ ) { soundInfo = localSoundInfoArray[ i ]; // Identify the audio format. var audioFormat; if ( soundInfo.url ) { audioFormat = 'audio/' + soundInfo.url.slice( soundInfo.url.lastIndexOf( '.' ) + 1, soundInfo.url.lastIndexOf( '?' ) >= 0 ? soundInfo.url.lastIndexOf( '?' ) : soundInfo.url.length ); } else { audioFormat = soundInfo.base64.slice( soundInfo.base64.indexOf( ':' ) + 1, soundInfo.base64.indexOf( ';' ) ); } // Determine whether this audio format is supported (doesn't exist for phantomjs) if ( this.sound.canPlayType && this.sound.canPlayType( audioFormat ) ) { // This one is supported, so fall out of the loop to the next section. supportedFormatFound = true; } else { if ( i === localSoundInfoArray.length - 1 ) { console.log( 'Warning: No supported audio formats found, sound will not be played.' ); } } } // Load the sound. if ( supportedFormatFound ) { if ( audioContext ) { var arrayBuff; if ( soundInfo.base64 ) { // We're working with base64 data, so we need to decode it. The regular expression removes the mime header. var soundData = ( soundInfo.base64 ? soundInfo.base64 : this.sound.getAttribute( 'src' )).replace( new RegExp( '^.*,' ), '' ); var byteChars = window.atob( soundData ); var byteArray = new window.Uint8Array( byteChars.length ); for ( var j = 0; j < byteArray.length; j++ ) { byteArray[ j ] = byteChars.charCodeAt( j ); // need check to make sure this cast doesn't give problems? } arrayBuff = byteArray.buffer; audioContext.decodeAudioData( arrayBuff, function( audioData ) { self.audioBuffer = audioData; }, function() { console.log( 'Error: Unable to decode audio data.' ); } ); } else { // Load sound via URL. var request = new XMLHttpRequest(); request.open( 'GET', soundInfo.url, true ); request.responseType = 'arraybuffer'; request.onload = function() { // Decode the audio data asynchronously audioContext.decodeAudioData( request.response, function( audioData ) { self.audioBuffer = audioData; }, function() { console.log( 'Error loading and decoding sound, sound name: ' + soundInfo.url ); } ); }; request.onerror = function() { console.log( 'Error occurred on attempt to load sound data.' ); }; request.send(); } } else { // Web Audio API is not available, so insert the sound into the DOM and // use HTML5 audio. this.sound.setAttribute( 'src', soundInfo.base64 ? soundInfo.base64 : soundInfo.url ); this.sound.load(); } } } vibe.register( 'Sound', Sound ); inherit( Object, Sound, { /** * Plays the sound using the Web Audio API if available or HTML5 audio if not. * @public */ play: function() { if ( !Sound.audioEnabledProperty.get() ) { return; } if ( audioContext ) { // Use the Web Audio API. The following 'if' clause is necessary to be sure that the audio has finished // loading, see https://github.com/phetsims/vibe/issues/20. if ( this.audioBuffer ) { this.soundSource = audioContext.createBufferSource(); this.soundSource.buffer = this.audioBuffer; this.soundSource.connect( gainNode ); if ( typeof this.soundSource.start === 'function' ) { this.soundSource.start( 0 ); } else if ( typeof this.soundSource.noteOn === 'function' ) { this.soundSource.noteOn( 0 ); } } } else { // Use the HTML5 API (doesn't exist for phantomjs) this.sound.play && this.sound.play(); } }, stop: function() { if ( audioContext && this.soundSource ) { // use Web Audio API this.soundSource.stop(); } else { // use HTML5 audio (doesn't exist for phantomjs) this.sound.pause && this.sound.pause(); this.sound.currentTime = 0; } } }, { // @public, @static, global control for audio on/off audioEnabledProperty: audioEnabledProperty } ); // If an audio context was created, it means that we are using Web Audio. Many browsers have adopted policies to // prevent a web page from being able to play a sound before a user interacts with it, so the following code was // necessary to essentially detect when the user starts interacting with the sim and enable the audio context, which // in turn enables the ability to produce sound. if ( audioContext ) { // function to remove the listeners, used to avoid code duplication var removeUserInteractionListeners = function(){ window.removeEventListener( 'touchstart', resumeAudioContext, false ); if ( Display.userGestureEmitter.hasListener( resumeAudioContext ) ) { Display.userGestureEmitter.removeListener( resumeAudioContext ); } }; // listener that resumes the audio context var resumeAudioContext = function(){ if ( audioContext.state !== 'running' ) { // tell the audio context to resume audioContext.resume() .then( function(){ removeUserInteractionListeners(); } ) .catch( function(){ var errorMessage = 'error when trying to resume audio context, err = ' + err; console.error( errorMessage ); assert && alert( errorMessage ); } ); } else { // audio context is already running, no need to listen anymore removeUserInteractionListeners(); } }; // listen for a touchstart - this only works to resume the audio context on iOS devices (as of this writing) window.addEventListener( 'touchstart', resumeAudioContext, false ); // listen for other user gesture events Display.userGestureEmitter.addListener( resumeAudioContext ); } return Sound; } ); define("audio!VEGAS/ding", function(){ return [{base64:'data:audio/mpeg;base64,SUQzAwAAAAAAW1RDT04AAAAOAAAAc291bmQgZWZmZWN0c1RJVDIAAAAFAAAARGluZ1RZRVIAAAAFAAAAMjAxMFREUkMAAAAFAAAAMjAxMFRQRTEAAAAMAAAASm9obiBCbGFuY2//82DEABgBBqwNQ2AAIMzmgQAABBaIiIiFxEREQuZylFjlJpSlFixztWLDgwMDByEQwAACBQeNksSye+2+/8EAfPg+D4OBguD4fg+D7wTB8P8Tg+H/+XB99APv/1AgGPgh4gBCXBAEHf//y4PvkstbDYShQADEBQJlzbEEijYLFliL7Ti7rIoXdx+nazz/8jCsOlbL5HE0KHpFOsv/82LELyvrytZfmZACpsofyLZj9MZUZoC2ESUsSsKwpSxFQW1iOKZOlI2SR/p/0DpqcOpJIrZqkf/UssEQRpP1yCSbFVo+gK+ITmwvxs/zotILcmTxXHc6KJmTRBh0l4xNTEgJuNI3NTpecxMnMUvSJgtsv6/mJkK+eUXTVGlSSSSrYyYyPlkiSl///WQiBlAeEQIEAAtiBE8RY2Se//NixA8i+zKKZ9GoAJI/59RsiiSBPLPooVIl9nUTq6PYyRdFEunSIDGi4RdiSgYJX4HJgeBh4IgNAEXCXC8XzySX717rZl20XayS1P3/Uk0jyAhsoGEBaIXJYsk8TRCkPrzhDiq3UrnCXI4cQuEP3AqFgFnqBKfgKAYgJqeNv///////VmQy5ES1AgArRAZAZvqImVFOr//Wo2b1Pf/zYsQTInsubkSfbUQjVk30tSTrIt06VMglY1k7g0B5kka4S3pEDLQ34l1JGd6rc/6Z+fU1aKKJl0UU1M//+zus1KYWqAwRDMBt4gpSWUkTWjmTO2uqkYomZcHWHQALhRAaCIBigHaBpFAUA0AUZIpIpMpS//+g////r9nFkl85////1hMgBzGCJRlJ02X//6LbVXk0ZIn51fYwSW3/82DEGSNrLmBCn61EjWT8a+uaAUvQCAwYKQwxowAGBwBK9ndnrP7wxx79l3X6bqVqOG3//rUiiowGbDmAYiwgAWHgI/GARQjxdEObrSf6aSRqWCKmQyoWTAEBdAJBaBi8QcBrpCIBgaAODc4R6LmKqaSSL//6T////9aAhESbirtdEeqbQ84tJrS48r///4RwIgMMXUdXO0EwSAf/82LEGiWDLlQg2Kw+BZoQ8RqHfAcDADBmtAD6IEgAkAYfAK3Jgi6aNknrR/U6/qKSf//smXC446xEATA+BhULmBgqAqHyCzxkiqTxJt/6nWpArk4SYlALKwMDgJwMKYRwMFMbgNJ4iAAAJhZMJ5GWHSULnn//0X////9MZQeqvf/1f6vZ17J1aSAXhDpGsYpGqDTl1av/R0jBwkBg//NixBQioy5UCKAsjBqAIPbGeEyAYA4BjfFUC/jAWB+GgCxjsJ9ZcT/rRb6/nH//+ifLxOk0ISgJAYBiMI0BgHAAKKRMxMi6Tqv/7mBmTBAxSAWJgYCgKAwKoGM1SAGD8FAEgPiSEkgblRFaH//v////1lIEgEFB393//3v84lDKS7KFAAANztkQKBgADFYc6M21H2Sa1+3qq/9v6P/zYsQZILMuXxynq0D9UhAIQkLlXchKMAQBswdyJTWuAxAKDQbaO4nXTMkfR/0q/3//+pdM0HPDAAGfUAA0xBYyCFxMRgTBv/+ukiopjqDFoJgADB4QAzCUQMHAECQCE4k8XXRrV//3f////0BCwwP///9rPq+pGh3YQKRCIE4Cilo2KjpetH9NFqC9+r+39u4qpKIPK/S+ioCogif/82DEJhwbMmU8n2tCPugxDHhnSUKaDW/7dXfrf//7s0mSUDPwHKkA4YkYbuajnD2t//1OgbkUGbDLAXzDJAMIIcAJhkoUTiKX//9f////zovSr//6P7FqAMV++IFIiEHhQ+pNpGRoju2gy9S6tWr/7f1bwY0la+kigALgKYnOyfLBCFlAzZFC+aIJ/+32b2f//90ll4uh8QGTC0H/82LERBxrLmJcp2tCe4ZE+6RASC1/+ylLOHSwNcMcCEFiMwMdHACVSElJIxf///////76yWf///+vRQExMHiBSIwArMT6MuVTc1b17N12/1p2+39W9MIVLAUpa6gsYuFqH8iFxRtEibMy/+m39fV//+pazpcJsMuAZVOQDRwFxkwUC+IQEU//3ZM8Vx4EbBiEDAYBAwNOAMygwQqR//NixGIdoy5eXJ9rQmMTZH/////VurdlKUurkOPf///5FRDAIIDoUA6BwUMQ4iJQd/WzW+v9X/t/fmRIANCHmb1aI6A4FAkDXuAcDrjPFFRmmz/9v/pI///dBpDR+BIGgLwUDHQDHUX0ioQYl2//1JmBfIAKDAWBAEh2BkO6AZKBwj8iDlxF///////X19FiW/+6uz+LtdZXY9Fw2f/zYsR7HxMuUTqnq0D6VYAYGgchcsiqi6gmbHfUYs3rV///+s4sIQJBYQJvIcNsDAQAQDFGQsAvMAIgRh9BkyCE+Xzif/UikkiRYioXDAaGMALAYQ0iaZkRpMqX//rOHSUEjCQQAGDwGRywBKwhoQ2S83///////+pAlv0Zr0jXJotYm5euZullVUoAPkICABibAaOAnjQ3NEW1NUr/82DEjh7rLkwCsCuGd/W3+r//rVCYBgDgCCEosoMRAYAAFgYg1QgdXATgMAaEERmidL5mpf/qWs6XCHhyAGSUAA0bCDmhMGYjggifqb9d2TPFscIhEIRAPLIBSRIMamyL///7/////Js9//6dGKa6LH1p9+xdwADB2CwLSRZzKNXPdJv/////ZMtAmAgCwMA30PWANAAAgHIGDWX/82LEoR6rMlWGsCuE2B4IDOAaAoOhFqI8oGpsef/uzSZH4IAKBg1xgDHUZYzPnhCYdzff/WgmYF8XIM2TgGEhODeEnC+aN////////yAv//1u0S/j+lqiKhgyNiBZhSQAiAANsWGiLOIfKhqWWOFRFlrUzJevq///qrzCMwKA9Vge5JwCgamEQn+axQMoGIEBkcVoOeRQ0N1P///s//NixLYd4ypMBLArhP//9aSSyuTog4D8GAuOMQrnS8Yl1D//uybpEyJvAxWoUARU2Z////////9IwT//tpRng27xlIvMHzKUKWl70AYQB6MAA08TAsfEESaJ8upNU12dTW3s/7r1q/9W8GFAYBVnkRaaUAJmFUVaaFQBYIkAfcbBJlxRmh/b91epJL//6l5gQ8MgAcPwAxUGmaGB0f/zYsTOHzsuSFSnqUCUIg///TQcuEDFmClwBbgEqBuaBv///b4p+3/1r7dRneIlCqFnBZ15BTYDIAcIJoWaDB4nIkycIGTZkeOInpu6Xvd/+3/71LZAAMEAQo/JgggAUwBQQDCEGlN1wDoDAwDDAQuUsIy6k3//+r9//Z0TEpFkXwJA8DDslAyIARziibGJBiXb//rSRLhTCAACPgL/82DE4R6yUkx8r6lAQ+BSAi3FpL//////+n1dSLe1f12fXapaDMu7WN2Wg+i/d6dSdV1LUlOUlnEehLYA4D0FsQM1wtDFpGRK5iaon1G61rpd0VIMl/qR+r9dSbHAGDAQAAUHeReZgEAZGGKSCcGwGAAQzC5gTuOAgZucN///+///1LuiWyKh9gNAA4HBYVEiZmXiia///oOaiOj/82LE9SZ8OjwSr6tBP0AJOYBytHOKpsj//////+lX+m2pFnVSRWjVZVdSVTvquq6KT60XRRUi6/regec9qRboJmtCq95yQlUCAIwkAHZhl0i5CLLKTLSreij1Op2/6n//Xq6VQCgEBIvFlSYRgGABmF6caazgC4DS4N9GSJEnzNJf/33/X///WtaBNiEAGvuANOCJmhgmXDR///qW//NixOslVEo8AJ+rQWBfGgA5qFmTAW///r/b/ruyMXS0V14dmzBdjBVDhUVJAKUYKAMGINAkAkLqxRR3EgYGZGHlmpummmYd3////SZIugmBAAoOgNiQbnAYAQIAYCAsgYjfGgfQBHgYNCwN5RHRDTAqJGr//////92RNi6QIEABAwy0QBiyOcYzUvE9///pEeOEAACj0VV////////zYMTlHjpOSFynqUD/r3+w+z4RAH5HS4u83nxZc1hldjyXfVs9CoTjRypJpDIKFjZu8Cc6AwbA+DBYfsThGkUPlxqzRMwnE9ZpZ16v//6TmAYGAGDyFpAjYEwBAYAQngYpuwgdeBFgY4kFjYegLLGUJwv3//////1LVRTJ0W4DsEQcfGAT6zAonf//6kzAohIQJwNUm////63/b//zYsT7JORKPAC1R3laTVLT/wToD/pl0oh/0y/Knshdc0X7m33WkRyo7MZLZWJyFpAtIyX3z9gdCwDG4Aoc8R0IWMCfOny8fcu2ZkFpNoNVb+v/6kzIfQGCoBYcQK6GwgKA3AyFUZA4pAjAYqAsDEGDJjmFwvq9J/1f3f/Wv/W6SzhTDiANL2AYQEXMDA2QZ///6yMUJ+Cgx2//////82LE9yRsgjwAtQd4/9+XRq915qM0stnT8r1Uk53k2bpVidipmYjFRpUog7VI6qj2dYyIAqE8B4Cgt+C6RNkGLhAyYdRsp03dTKZmUm32f/+taJkGoABA5C4YLnQBABgYDwcAYrl1gdLAeAYYAYWyE6kSNUkUv///V1f/3ZEyMSHBhEDEqbAaIpBkaJkf///5YLgEQ2CwRdv///////NixPUjBHo8ELUFdP1uiq77cPX+a5zxcPIq2xLvXY+ZDlSJrZOjZ0qbmTkhEbliYKZwXIiHuLJBuwMDYChG4WOiPyJlsmCbJhR5S3SQsvSWui+9X+pf2QTBoAoBQFgceLaGigGA4Aw9qNA8UBEABCh+gy5FCfNzT+3X/9Sn6v/VVQPnhkwOI3BQmN4nzdRmv//+tBMiAQIRmTiP///zYMT5JYRqOAC1R3n///9Pfsig92gl//J0z/ts55EJfXyqox7VDczleEq5qWW+DUtDPSbsbHBY6BJIr1ULCAWHwBgCAwFj45wuY1I1RcK2eZ1rN6m6bv0msr9+qdUWQmBIDBMAYOqITBbIBoFoGP2a4HpQAoKRgvIYY8E+Xzi//ofV9N+m3/6lmBZDqAYnQFlBTUg6B7///UUyWP/zYsTyJJRiOAC1B3kICRF2//////s2g6lXQPmSlTnJ5Z6AwxzIqzF9dYW2bThlN09c7Z6QqdVmOCSW0g24XsoJJ5IJAMGAPQxSQMjhlDxPmKKM66aZ5ZkmzKWiptu92+3pskagmAQA4LgXUhiYA0AQGAYGoGH/CIHSALIGJDBikXMQ1TOi//ft/9e//90TIWaBm54KIS2iyziX////82LE7yUsKjgStQd51nicEyPf////+n7U9P9MuL+/Xbsy7PX+HlKsMzpnBJrbc2ScEnFg5iFXrHPcZ4CyMeAp81UBw8oC76BzJXyk2prChtf0Ket3NdZ76k6TuwrwIgei2DSD8gKgjAwdTJA15BBAQAAUmOQTZMIJt+u/a/btf0v/6k2IuBgSBGHvmjugtf//+tlj6Kzv//EnflXk//NixOojjGI8KLUHeVceA2iWpVwV70iBohekcAmOQYHirBp56lFx5wCqh0cqDQDKaHADAOBEPmDFIoozZPJkTK5dMy2s3SWkmg9nZmU66627v63qROE0Bg2ASGQBqiGAYGQFgY/sigbugbgHUAJAxBguMghcOq9vUbr7ek3//9N00iyIkEbAtR04zmrf//+oh////9bf+6Xr91PbtP/zYMTrHopKRFLQbHjPhr5XxQOHsfZNCbctjN1zGLYd5hYKoNEYdIBkBg3IPCUdFekDjMxxV2EOGgMEgjAIAJAwCgGEKjSHWQc1l0n5NmKZs7a1UkVNtXt9b+qxkM4AMEkMvBq0G+gYDwRAYrnxgbsgPAFMQwULhIkVDBaX/+ttf2//+t0TYc0DStQcdNmUipX///dQlb////Q//f/zYsT/JvSCNBC1B3h2fM+u1TP7ERGYVJDv5HPQoMuZmUMjzlMmhEUN3JAhrhxHetYCVyGCAxEWVQmAeqF1TEFNCBHIHeQqtfmtCMsKLRD09CK9sivE/s5P+uCEbpxAzH0HAD2To1QkBAME18D/gGEZlImygz2WnQLhp9RcRdNW9aaaHTfqQ6f6BmAIERpoPY3XUgs3ZN9SCCD1N1r/82LE8yTcYjQAtQd5xqkTxOHuTLv/aX/S8v07zdGzTupQhGfxYOCkVoVSyPnlCvstBCLVkWFiYCl8PxOH2dsTZ2yxyHIXYxBri7GuNcQfRXSLXWw9h7X2vvu4joPxSYXZfQ+7kQhyI15/eqSV7yqWZI0ybh+xAhiO+P7o20YujomjyxoMCkN/mmIL++ICsYHOO8eRIDg8eMd7w4b9//NixOwfIkpMTNDqXI396V3jev//E8g2MZ7/d169+OlLwLFijPva68zbf/QPksBhKcWFQ4LDh4scWQbeAmGBLJ8cJ2WyWJZPiscHkWKKtwr3jA81ZT0jq95levO18M3xhY7ezDktr+/BAAAAAHjz3///HkAIAABh5cPD4GkruqoGUnUBLRp1VI+fPnz2LrOvi1twYjDWtawX0JXSPv/zYMT/N7NWZbbb2W19CtauqwYvtb4hPsu4Tt7sxVEP0fUuVWjUapozSymdjMZhk0XdV2Ka5ZrU11qiphjVmh7Pf+vE14C5vtCrw1yzNytcKtQ1yaNKZmYWgWtVUlVpVYq4bVUHckiILHCLU5wqKizasWKDRVYYlgKf+IKKElChAMdBTRmSbOoJXtWi9XR09etsySRfMTX/9xlSeP/zYsSvJAMafbZ+kUXIJ90kPzAiNzQwE1MIbmK9TDv46teYjKlb/9JLSU9bay6/1JOp8zAhkmy6kv//+tTmR7T5AjqdkpV2VThzuqp/5CgrYRMJXmxrv5OmpQ4gBgXoX0DaByDcmS2WjRI8pTspmf361/3/1e9dDgZgCbkyKZi9hJpkBIWkC3kQL5cQX//upv+r//+iWAKYW5FWvV//82LErxriRlRMp2FEuu/9+n///////9fq1/+pter2+rU33t1I0U2qrXRqqTMkP7ht9AiC6kfNCKhnuHyBwqAKLL8u67sT+xc7ny/upj/M95/zf/+8Pz7+u/vvf/XP/91ogEBqzpnKYpgKBxj1wpwIBgE1EHDtKx5q//60vp/9Vf/6y4A24hhuu7Jff//51///////2RVug/6S/vkx//NgxNMbNCJEEqdjQZbXTyL03sZz11KOW2RuGOOafmTDMHRikchZmOzqrpGNKNFILUxBTUVVKjIAMnIcQMBABAbMB0I0iNJtj6zRV1rUezE/Q1fZVDsh+ukLwHAUJciIpEIgIAwEo9Az+ASDHRnh9lwvnzz/p/rR+pS/6kn//k2A4DIzaCb75Ay87/0////h/fdebJSKUyNwcWny//NixPUihII0KuyHeCF5ZPPh9/y/2QiJZqZWJSsOxO6SZq+yKSeQvOa5EIehFSGf4QGCwEg4GISX6d2alVHK71/eWFJly/9253Xf7///dZZZfu/zf/3+f+tqpBAaQbDTzJIGO/ijbwhccYhXKhutBan1q6/++rXV//9ZNAEYRsi7qt/tV/r5w2///+r9l/Zd7Wp1VphCoksN1eKVvf/zYsT2IZR+PBSwR+h5DpfdkzYr22JT6DCOVXNhLOLphWftYGJNmtF5OFZB4REDBCh1EQDJkIoDCwA8OaHXFnEWIccL44DyBWpM5w5Q1G26qkm100e38SICQRBcIsoMbAEATAwmzLA1OAnCw4VqOcXa0m+um1Snt3b/1f+vrQBEAIbKbaV/s/9aWgv/////0RvpY5iu5XYzPLCkgJj/82LE/yTkgjQI7Ed477Qk83qeunrryGsVQcIhjD0GJEGYfcdxZDHgxC0GIBwW0CE1BCAdqgM/wHQMGQGRri5iKjPF0gJFlIHzOtN0mZOm6TP6061t7616bEOBQJIwBkhGQNQLgYHJ/gZwgahfAY8hhNmCDt/1rb7/7Vv/+jqJUEAFSfObG/5My/pcX/z////8a/WNRymQtB6GDuKr//NgxPskxHI0KLCH6erWGvFlyyzUyOn4TIqd2MSwIAUMwpiKxx7HV8DMzihWpCkBuCXVEgDNgGEA4DwW/hfIZcnyADuNzEmTJM4ePWXsim13b21btrq1oF8BgHJADMggNrgYZohgY8AKgSAQLGRAny4g/9f/3Xu2tf/+p8vBaAKkivS5/OYP+Rr/+iH//v8W39XC27nT6bsU+L8R//NixPcjVHo0ALBH6RuecyJfl62Ozf51MZmZu9qyZhvlaVPl+6zla07s+0ZS3duysgMUAyepQMACQMsjniCZBRjyqURxn3WUyup5erMDZL+pP9G1desyJkBgGIyw5olEGwwGGR1oBxxAUAaKMSxXPMtX//9a66v/n/+YA3cVHU22pNPof1fr//9+zf/77/9R27x+kzxWLw27tT48V//zYsT5I7yCNAqwTeh8/x4K7fxlFV5i3MllvP2InQgP7QcxZeGS6zTZ+EyKwDGxJzJkiiEDD6BACwBwb6AwAklhtkDOk6VyCMgTZMIos08mpNnZdUwd7+69/cmAcB0kidGNBqAQCirwMlAChCUhxdOLTbb//3969f//zEIAFkHU/+Ut/uPv/r//P68xp+/qblTgbPepo/qpFXTMjgr/82LE+iQ8gjAArY1c/N4yFDDJN8YQFFiIDYHydehqGQaMEdXNBQhzJaFJQVEg0QMTAogtSQgW8jNnBnjAji4X0zMsrRWeUmceggumgieWtSbeyWgkylLG4BYFhFzQnw1YBg/bWAo2gRuSBPmjuh/+te/fq9fr/1NRSDVAkzPmZtZ82JK8pGi//k///9LU2WBsAIPOuzreTCRS3Yr///NgxPkitII0ALBH6He3fXzdy66P238XF5jdknY/XeO/2f9tjvbbPJHYWTTbaEaSyzj7TEFNRVVVKSrAAiJSAYAEP8IKCjEuSTk8WqCC7JoqZF66SV+rbapX+RwKAcJkmiBByIGCFjYAScQvsOaTx56ei/2svtt/r+p/V3sXAv8kmzS5ZdzcvIsi3m9///zP1A5wbJP3Qq6vMmYy//NixP0lBIIwALBN6Opwo94nU6e0/LXZ6Rwi0BVX2CyHahGXmQOnVcO6BOGAVkxBTUUzLjk5LjOqqqqqqqqqqqqqqqqqqqqqqqoFbxZD+p5QIoy98N0dmU4V2YAlbo5HHNOA5rzNVa/nA008kXhTQaekAp0hgwZ8lCigx9qvq6lVsh9FDb9//1h/z9YgOKJKShe9Ao6hlCCFf8XLMf/zYsTzIPx2ODSwR+mLWXeBLCJJcPpVrcPFLEOFQuGQnS4BxdptpnqdYwOtcaogoAYJxLgRAGDYOC2Q6RjkiLmhSL7GZ0wdtRspk0UOt620u6lW61JAiBKUDcjA2cDBM38DDcAcGxAssihNlw4bt/t7t/q1f/V/Ng4EPGglbsa9/N/wr7+l/l++zy6OpzAUZe07IJI64pAhoEPhwrr/82LE4xzhykj82GpYa4XI9FUUhEoiAlMWo4oEpu0NUCURGrxCBGCZdCgRGMQQJ0xBTUUzLjk5LjOqqqqqqqqqqqqqqqqqqmbP70D7cHRS/Tn/NVJmwZmImHFxjJCwmbKZy12Naw0lJE1JIDBW9BYGhpA3y0fZBv1+r7rrWh7Lr1bav5wOKBuYqZOAeGC8mYJzYafJodQz0ahUXeB0//NgxP8kRII0ErBH6J91b7nCytMktkVaH2MAgAaKFj9AuUMhAcpo0wxC0z5WKUgAMXIYQUGMAQAcjiaFyjJlEjSkTzIE6pzZ2d1LepT636k2Qpa0n3og4EJeLpMiOgMAzCQGg8BsIzROpMr9bqPTJqLII/3s3Z/fRVoMpECoA1qUZMn3olsqolrKskT9D/t+C//Xpe6kZWO5kytZ//NixOUdccpEVMhqXDzH26d4grM+39Z8nX+w1TuIW+Vz6oyfkoUk2X68SKRo1RevcFeX/6Gm9p5VpwM1oaABgPBCAsLeCCD4HNHgtplA85XJ9Z83ZA3MmVTTmmj5gdN/VXXlwGAUPl0xF0BFsYDRCCZMlq/7//U/12R3U39eriCow2SpH9zrL+aB++v3//wvTzhN00as60Phx37FWf/zYsT/JwR6MBSwjen2ghsRcRNxcPl/rvtWRVMyqaNyBdGPolwkx0YbJrFpGzzzV81FCcOJRhtdaFUDKAN4DAkEsHBaBIAYXPiul4jTYihbPKNJxNetJFk1H6t3WmqvV0KnzIHAca6IBrCwWIC69vbu3WtS/1/r0qn+tWvUH+J7WlWZYe8vqd8v/7//7oKmygJyZRD/3fef+BZ9IIX/82DE8yPsgiwAsE3oTGZ5zDvLX13W9nR/7l/0QjdVqB6nd9aSN5W2fzrTNL1N0EXZ0m6ZlSoDYsMsAYJIAQGgRABODTOl5NRXRLBs8wpIIVUdFd2daVXZqD71woBw8kXhqhNi4DAvSKIqX/rXVv667W771sr9/1iE552tWur2zr5/u+d7s8zyJgleRwc1bNij7t+4K1oivMtcbXb/82LE8iLMejAAsE3ph+33IfsMr2zJ5dZDxMkD4MlHs7axh2QvxkvPXGkN0uQIDk5k1KUIGV4J4GBUAwDQJQ9ggAk4zJXOmRXOHTcyNmWfRT6NbalVOkmtV/r6goB1BIwGfAQwkA4HpBE221v+tbq9fpV1fU1P/6QeiO9SSTmfma+HRHpl878vi7/Wcp74gDoRmSA7O2mlM646NvpL//NixPYi7HowALBN6XJn573Dj93Gl2S9yVTtDN9m/5DpIueXs2S0ig5qfNtiSfYgzzckE7Kc1SaYADIeDsDAqAQQAFLEDHpMvpENNFGiFdZ906lUDB6tBk6v1r+iDgCoOgRcAhGIEgBiwF9aSCDrfVavUr7st9kl9VKtv/nBECJOpGvsglwPX8jAer9cir/9flgprROZd+8Jn3BM/P/zYsT6JOSCMAiwTegzmfzzL+57R6DEsWy1MRF8GUSCbwQTMDGFOhuF3YWDMGgCojckfjE4FFhA4FpU8rmxqWVrlm9ds0kRp/u7+3as55f+97yw7//+v//x/X/rMmB25uw/ZgVhokR4HBuYXMM/zDeq1pvdm7VY//b5oNZZ/R0nczal7v+69G/X71/9Omlz0NdT3NnkZWOdb2gUzxT/82DE9iIscjgUsEfpV/LNtM/Vsq3uZ72Ppe1s10WcwhBrWdNIwjyyiy8ikaISenaaNBiiKGtbJwVVTEFNRTMuOTkuM1VVVSKCcdAH3lZgAEnk+jySsOigw1GLQV3ZjmR2sgz+pX1+sPCpaJsAizhQhjZPsmpTfXr0HWzvQorXXQ+nX30+qZibS2z27px/vVikfWasCX/WiKLQATD/82LE/CZsgiwA6o14hlcNlhAfYYYllpdk0MOJkwEOhwLsEYbUEGpFRVgjOmxGTEFNRTMuOTkuM6qqqqqqqqqqqqqqqglDSnqHKxUNBdL+HW9oZbu8CQgsMYC7jqSnmIO166xAqlsZAQpYOHw2S2ifZKqi9Stmof639L1f2S6VAQgJIFgvf37bMd39vurC/3e70O1P/////lRvXkYu//NixOUdedJAXNjqNJxZXT5iOztfxbJH3v73xJa3WC0NoahNPCthhxmbytUctRWYADS2HUSkK+F2j+XxGRFDYhxdUfOFA3MJcNlMYG6aVOipdSNatFezt0QcA1CxeAoncKCaHkrmj26tlIevS39CjRUz1U79TrbsMWkgtNraMV8Gt8kE+Uu6vQiqbWGMhOvR/ZXlTujEObV1Z38Z/v/zYsTpHmnOQFzgal1d7hEp6S2UDmz+/EqwrzT3ym52sSA6kzY2VpoMBol3vMeHmMk6ct5SctfkiogViICqGBn5GIFgREwUBQXS+MeRpfOKKBqssn6KKK2Sd1rQqdTpvZkG0vdqYUAArTBJDILCDKjo1aF9tbXvay9SVbJs3VQr66Dc+Ok+pfTo2FX/PyWXx/L9eEuTio9ToQCQkTL/82DE/yfMeiwUsI3paXlsd5MajUJVXa+/a/mulPvHvEoa1nZufIVEkdx6QP5maQsiUlGVzCNo6RvUV1iqEqHVBZAwMRikDJdYmBBvGW9pZZZrW8L9u13D94Z4b1lvHC9j/67/dc/965rD8P+ZRh1vCnCkCkxPhgmjKv5r/X17mmopHmObd/RkzixgCBsaSXzq+lN/1W2hn6Htf/7/82LE7iRcejAIsE3p7fbnX0acvS88q4lX0QYYcKAGZ1DmjmWFSq5QoJQZPgasOEzcL6h2QSSgaEIMUQMYFRQumgoYW+Q4sBCqTEFNRTMuOTkuM6qqqqqqqqqqqqoY02gd5OChLxPrfgWXSm+ERoALx55/eRfOiOedFRVY3AWhoMFJCG4Zd+JfbtRT+IjOW5FKmozzfrtzn2Ua4xSU//NixOwl3IIwEunHeLrBRhQuIijWvio3i49C5VcOgMibDQu9BoeHQiA2hRQnbQCGbAGBwADYCBYCC3i4iVKpofJ8xm6jrGikjI4t7IoLQ1alI7rZ6tVRZDaTiJqPQGCMuDl0IsSR4+yCLt+9bqT6Vfvuyn1WSPopd0EqyGpqWeu/3NT3RKF9bUV1U1bcv0/7/WXO6GSY/bU6IqIeh//zYsTQGCjyQDTQalwW6glQyOt6ItMq13yJ02+rDuv+52qnVxilza5uVji0PsHTcOh3laWKTEFNRaqqqiB/aB8CyaH2dSums61nhhZ/uy7Xbutu2zUV+7oZiLlKxUKYfkBhpcg1EALBAXGmky06HEx41TuhCMrNO55OykO6oq9sqPwgPQyCvQfSOPyVijiX6NVYz+xTliFzgae9VB3/82DE/yWcgjBSqI3sIqHvNt1IJigu0qowlIOmxZwkIPBskhAPiVThIJVC4rVMQU1FVQEj1IECECgvXtdfWTN8P9jqr6qUY9V9V4zUMKbdLEX5dqVMtApxgjndmEa0c5D1NcpqbLdXHmVXHVa1lFNBTQpsK6KyOyeCuBfCn/iC/8I4FNCfOxN6bz4z4vhXBuLum5HYnYmhTbD5cf//82LE8CAZ4jw0xUqcFf+JvDvxFf50Jt3AobF/EFNihQ2Kwd5vxQq38KDYoKiVTEFNRVWgLGWWWGSsFBAwTo7K1ljpLLLWsoSIhLHShdAbJhSRDIeLlCMkc1Grq0k1VlVk0l0DZMQkRU4XKI0DpRq6urVWVSTSXg20rWWOksssMlZZY6O//+bK1ljoR//LZZZUM1llR0llljkrLLKj//NixPoikTotTHjyXd//zZWssdJZZcyUMFBAwQdAf8GRUVEZgGRUVEfxYWFkKkxBTUUzLjk5LjOqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv/zYsT6IouBEKQaRxSqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo='}, {base64:'data:audio/ogg;base64,T2dnUwACAAAAAAAAAADxMwAAAAAAAPfiprMBHgF2b3JiaXMAAAAAASJWAAAAAAAAN7AAAAAAAACpAU9nZ1MAAAAAAAAAAAAA8TMAAAEAAAAG963WDnX////////////////FA3ZvcmJpcx0AAABYaXBoLk9yZyBsaWJWb3JiaXMgSSAyMDA3MDYyMgQAAAATAAAAR0VOUkU9c291bmQgZWZmZWN0cwoAAABUSVRMRT1EaW5nCQAAAERBVEU9MjAxMBIAAABBUlRJU1Q9Sm9obiBCbGFuY28BBXZvcmJpcyJCQ1YBAEAAABhCECoFrWOOOsgVIYwZoqBCyinHHULQIaMkQ4g6xjXHGGNHuWSKQsmB0JBVAABAAACkHFdQckkt55xzoxhXzHHoIOecc+UgZ8xxCSXnnHOOOeeSco4x55xzoxhXDnIpLeecc4EUR4pxpxjnnHOkHEeKcagY55xzbTG3knLOOeecc+Ygh1JyrjXnnHOkGGcOcgsl55xzxiBnzHHrIOecc4w1t9RyzjnnnHPOOeecc84555xzjDHnnHPOOeecc24x5xZzrjnnnHPOOeccc84555xzIDRkFQCQAACgoSiK4igOEBqyCgDIAAAQQHEUR5EUS7Ecy9EkDQgNWQUAAAEACAAAoEiGpEiKpViOZmmeJnqiKJqiKquyacqyLMuy67ouEBqyCgBIAABQURTFcBQHCA1ZBQBkAAAIYCiKoziO5FiSpVmeB4SGrAIAgAAABAAAUAxHsRRN8STP8jzP8zzP8zzP8zzP8zzP8zzP8zwNCA1ZBQAgAAAAgihkGANCQ1YBAEAAAAghGhlDnVISXAoWQhwRQx1CzkOppYPgKYUlY9JTrEEIIXzvPffee++B0JBVAAAQAABhFDiIgcckCCGEYhQnRHGmIAghhOUkWMp56CQI3YMQQrice8u59957IDRkFQAACADAIIQQQgghhBBCCCmklFJIKaaYYoopxxxzzDHHIIMMMuigk046yaSSTjrKJKOOUmsptRRTTLHlFmOttdacc69BKWOMMcYYY4wxxhhjjDHGGCMIDVkFAIAAABAGGWSQQQghhBRSSCmmmHLMMcccA0JDVgEAgAAAAgAAABxFUiRHciRHkiTJkixJkzzLszzLszxN1ERNFVXVVW3X9m1f9m3f1WXf9mXb1WVdlmXdtW1d1l1d13Vd13Vd13Vd13Vd13Vd14HQkFUAgAQAgI7kOI7kOI7kSI6kSAoQGrIKAJABABAAgKM4iuNIjuRYjiVZkiZplmd5lqd5mqiJHhAasgoAAAQAEAAAAAAAgKIoiqM4jiRZlqZpnqd6oiiaqqqKpqmqqmqapmmapmmapmmapmmapmmapmmapmmapmmapmmapmmapmkCoSGrAAAJAAAdx3EcR3Ecx3EkR5IkIDRkFQAgAwAgAABDURxFcizHkjRLszzL00TP9FxRNnVTV20gNGQVAAAIACAAAAAAAADHczzHczzJkzzLczzHkzxJ0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRNA0JDVgIAZAAAEJOQSk6xV0YpxiS0XiqkFJPUe6iYYkw67alCBikHuYdKIaWg094ypZBSDHunmELIGOqhg5AxhbDX2nPPvfceCA1ZEQBEAQAAxiDGEGPIMSYlgxIxxyRkUiLnnJROSialpFZazKSEmEqLkXNOSiclk1JaC6llkkprJaYCAAACHAAAAiyEQkNWBABRAACIMUgppBRSSjGnmENKKceUY0gp5ZxyTjnHmHQQKucYdA5KpJRyjjmnnHMSMgeVcw5CJp0AAIAABwCAAAuh0JAVAUCcAACAkHOKMQgRYxBCCSmFUFKqnJPSQUmpg5JSSanFklKMlXNSOgkpdRJSKinFWFKKLaRUY2kt19JSjS3GnFuMvYaUYi2p1Vpaq7nFWHOLNffIOUqdlNY6Ka2l1mpNrdXaSWktpNZiaS3G1mLNKcacMymthZZiK6nF2GLLNbWYc2kt1xRjzynGnmusucecgzCt1ZxayznFmHvMseeYcw+Sc5Q6Ka11UlpLrdWaWqs1k9Jaaa3GkFqLLcacW4sxZ1JaLKnFWFqKMcWYc4st19BarinGnFOLOcdag5Kx9l5aqznFmHuKreeYczA2x547SrmW1nourfVecy5C1tyLaC3n1GoPKsaec87B2NyDEK3lnGrsPcXYe+45GNtz8K3W4FvNRcicg9C5+KZ7MEbV2oPMtQiZcxA66CJ08Ml4lGoureVcWus91hp8zTkI0VruKcbeU4u9156bsL0HIVrLPcXYg4ox+JpzMDrnYlStwcecg5C1FqF7L0rnIJSqtQeZa1Ay1yJ08MXooIsvAABgwAEAIMCEMlBoyIoAIE4AgEHIOaUYhEopCKGElEIoKVWMSciYg5IxJ6WUUloIJbWKMQiZY1Iyx6SEEloqJbQSSmmplNJaKKW1llqMKbUWQymphVJaK6W0llqqMbVWY8SYlMw5KZljUkoprZVSWqsck5IxKKmDkEopKcVSUouVc1Iy6Kh0EEoqqcRUUmmtpNJSKaXFklJsKcVUW4u1hlJaLKnEVlJqMbVUW4sx14gxKRlzUjLnpJRSUiultJY5J6WDjkrmoKSSUmulpBQz5qR0DkrKIKNSUootpRJTKKW1klJspaTWWoy1ptRaLSW1VlJqsZQSW4sx1xZLTZ2U1koqMYZSWmsx5ppaizGUElspKcaSSmytxZpbbDmGUlosqcRWSmqx1ZZja7Hm1FKNKbWaW2y5xpRTj7X2nFqrNbVUY2ux5lhbb7XWnDsprYVSWislxZhai7HFWHMoJbaSUmylpBhbbLm2FmMPobRYSmqxpBJjazHmGFuOqbVaW2y5ptRirbX2HFtuPaUWa4ux5tJSjTXX3mNNORUAADDgAAAQYEIZKDRkJQAQBQAAGMMYYxAapZxzTkqDlHPOScmcgxBCSplzEEJIKXNOQkotZc5BSKm1UEpKrcUWSkmptRYLAAAocAAACLBBU2JxgEJDVgIAUQAAiDFKMQahMUYp5yA0xijFGIRKKcack1ApxZhzUDLHnINQSuaccxBKCSGUUkpKIYRSSkmpAACAAgcAgAAbNCUWByg0ZEUAEAUAABhjnDPOIQqdpc5SJKmj1lFrKKUaS4ydxlZ767nTGnttuTeUSo2p1o5ry7nV3mlNPbccCwAAO3AAADuwEAoNWQkA5AEAEMYoxZhzzhmFGHPOOecMUow555xzijHnnIMQQsWYc85BCCFzzjkIoYSSOecchBBK6JyDUEoppXTOQQihlFI65yCEUkopnXMQSimllAIAgAocAAACbBTZnGAkqNCQlQBAHgAAYAxCzklprWHMOQgt1dgwxhyUlGKLnIOQUou5RsxBSCnGoDsoKbUYbPCdhJRaizkHk1KLNefeg0iptZqDzj3VVnPPvfecYqw1595zLwAAd8EBAOzARpHNCUaCCg1ZCQDkAQAQCCnFmHPOGaUYc8w554xSjDHmnHOKMcacc85BxRhjzjkHIWPMOecghJAx5pxzEELonHMOQgghdM45ByGEEDrnoIMQQgidcxBCCCGEAgCAChwAAAJsFNmcYCSo0JCVAEA4AAAAIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEELonHPOOeecc84555xzzjnnnHPOOScAyLfCAcD/wcYZVpLOCkeDCw1ZCQCEAwAACkEopWIQSiklkk46KZ2TUEopkYNSSumklFJKCaWUUkoIpZRSSggdlFJCKaWUUkoppZRSSimllFI6KaWUUkoppZTKOSmlk1JKKaVEzkkpIZRSSimlhFJKKaWUUkoppZRSSimllFJKKaWEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQCALgbHAAgEmycYSXprHA0uNCQlQBASAAAoBRzjkoIKZSQUqiYoo5CKSmkUkoKEWPOSeochVBSKKmDyjkIpaSUQiohdc5BByWFkFIJIZWOOugolFBSKiWU0jkopYQUSkoplZBCSKl0lFIoJZWUQiohlVJKSCWVEEoKnaRUSgqppFRSCJ10kEInJaSSSgqpk5RSKiWllEpKJXRSQioppRBCSqmUEEpIKaVOUkmppBRCKCGFlFJKJaWSSkohlVRCCaWklFIooaRUUkoppZJSKQAA4MABACDACDrJqLIIG0248AAUGrISACADAECUdNZpp0kiCDFFmScNKcYgtaQswxBTkonxFGOMOShGQw4x5JQYF0oIoYNiPCaVQ8pQUbm31DkFxRZjfO+xFwEAAAgCAASEBAAYICiYAQAGBwgjBwIdAQQObQCAgQiZCQwKocFBJgA8QERIBQCJCYrShS4IIYJ0EWTxwIUTN5644YQObRAAAAAAABAA8AEAkFAAERHRzFVYXGBkaGxwdHh8gIQEAAAAAAAIAHwAACQiQERENHMVFhcYGRobHB0eHyAhAQAAAAAAAAAAQEBAAAAAAAAgAAAAQEBPZ2dTAACAZQAAAAAAAPEzAAACAAAAmjC16zZvSltJRD08NTI7Njw2PTpDOEBMSj86ODg+ZGJHPT07P11iajJeZUlCXGZjYVtZXGJeY2FcYV1cVbDnoYt2vgQAAnAf4OM68HwAgCwWfHi3uiXm1zRXBxS8Me8mYFVR0baOFnZnR10JcIKcWENNUF5RWtV5LhZ30kIppJCeuCd+3J4O02aoCgAAgN6sjVTmjvpoyP7vde/t6ZHcQCQmDs5XPz+83QBM19vRP+MW2phG4AEAoCWB/x0AADyozcQVL/9nOcpSNoenXwAIA+jAAACgjK875biAAAAMACj2tACnHjN5DPeDGACb2RaA+58DAHr6/OyNZLo/yszPr6NsASDRipIAgI0Bu/wIAIAMa8Ag2sy7cenwoo6Qpeuff8K9El8JAABATQ9TAADg0X4oAAAAAAAA8Mv5R7oNGZC7PZJVBwoLALzKvjcBEAD+ufxdTK77o7zy8zS6EkCiyTEAwMaAXf6SAACBxp0KACpVggTMlQYAAABQNQkAAOZjEgAAAFbFabNdyFqJr6k20QD4MQYAgF8T3in83IcisT9XvnnzsOsKWgxgTRvYZasCAAR85sNVAAAAoHxLAACY+kgAAAD2yw90saBpxADJVRY5ZxTvCQDA0QFAqwEe+vvchyP0z/HKG6XrjdoL4NDBLr8AAACFP+sAAAAA7tsEAID3cwAAAPhz9YTunjCoTeCHL7MbAADwAtQAPsr77EMR659x5UY3WGsAEk0uAMCaDlZbAgjQdQYAAABg5RUAAACANgIAwLidAAAAwJf8dXoHABwPAFQBXmr7+0TE+mfM/OBG7Rs1Azh0sMufAABIQWnauwAAAAB6FwAA3u0KAAAAGgE0AADgy9cDAA1eKvv7ZMT781H84KYQJCr9BACs0sFqAMgAwkulAAAAAK8LAQBgNwIAAEBCTQ2gQQUAAB56+v6zJNKfT+C16YtTJCrHAACrAKstAWRguDo1AAAAgNc2AAAIpQIAAED9IoAGOD8BAAilC8I2BcwP3un6+2dR6M+XyGvTd7tBovIBAHAosMsfAAABkCs7AgAAAH5vAQAAdwQAAACjpgywAADwcQUAnun6++dRpD/XK3+B+l5ZT669AFYpsNoOgABBvXYAAAAAfp8GAIBTWwUAAOBPUWexlhOZ0efiLZkUdABIftn6/fOI9M8s8vx3dhKVCwCwUWCXXwAAgKCqagAAAADfywAA8NUBAAAAPtkQAUth3wB4B+4A3ln6fU4U+mcy8dq06E6uGcCaAqstAhAEoznrAgAAAPi2BQCAu20BAABghKaTdSRAAhwHAHS0RjclztolDP5Z+n0miuqfI4raMhWfXHsBrCmw2gYgwJGLuwAAAAC+OwEA4GQ9AAAAVCR51ACeB+gc5b9eehVqCTf+Wfp9ThT651CiFprSGoBE5RgAYM0CqwAEwdYJmlHHAQALbgAAAABcdSgAAC8qAAAAHJAAzxUALEXc2R4R3re/7/sBvkn6fSYi/SvgCQSJygMAWGWBVQCC4GSHcQAAAIDeFQCAlx0AAABWKE3FiEKDDgAKpcaRi0G79xZ+Wfp9Jgr9C4o3mrok1w6ANQusDgCEAblsAwAAAICNAgCwWwAAAEBb9k1UFgnQNvjvE4AHC0XJbvXsO8iRTk7GHln6fk4S6Z+J4rXhi9e6GUCicgwAsMoCqwAE4fkUTcNEa5ZyoVGqzlYAAAAA2hQAgPP2AAAAcK+//iwLkFpTWiCCqKpjGLdcweWQCv5o+vscf6R/TuANvnS1ngASlQ8AgDULrBYAMtyEy3SiaFAUFH15RwAAAIA5SQEAyGEAAAAIUrMWqQDAASwW/e8prdH1ICLxWZ25Xln6fT4K/fkIr01fmCW59gJorAKA4MiHDgAAgJctAAAAsDT2XTS1CNRaHC1Xgc8IAJg+zns1eq04Z+ER+nYAfln6fjIJ/XPAa1MoSFQuAEBjdQAgGDapHAAA4L0NAAAAdmjSBRfnRCw9wL+KAAAHY/GMLjrb+wU/AZ5J+vuciPWvSB46icoHAECjATEysBMAAOBx7EpbFEDs6A34NwLAXxMAAArgFCZjnG63s4qMOKkAfln6+2xE+heSNwg4cu0GsArgg7CRZykAAAAA3wEAABzbUSoBxHvn6AYFEPBq7BKdN2l1TXek7+M+afp9MorqnyN5bYoMco0AVhngQ4yYmAIAAADwagAAwKn5MHXEqh1z6DUh7aKoKgA6gMUzPn2rLTeLM9tcVL6Y+vtMlNE/n314Vfcbrq0ngESTBwCwCrAKwIv2wLeu02UOEQMXV/rGEAAAAOA7BAAAf9kCAADAiGTw8w7BrwaAJsv82fPscuPHotMrQ17i770bPfohVDDfMTf1anatzt8GcwB+uPp9JknrX/zNq7r/8LoDYNQOgFWA1QIwT+JV63GgrX3qZUKj+LYFAAAA4D4FAAC+WgEAAOB9Fzqkg42ghQCQpnYuXqs8L7NKlKeT6WpIIYF5JtIL+r2LkEs3iS0aO0wC/t54+vs0Cv0LRdRSydZINLkAAKt00IBsbKwVAAAAYO4UAACKLhntQdyW2dqidzX/xUIB/BEAAAuSLJxNlZcwPWF9YUyqMF8BHln6fRpF9c/hvDYFiiQqHwAADR+ElcjTCQAAENUjvCtnRGpHwreFCrAVABguwDmebZLCZyz3EpyB7tz1Az5Z+vtFpH+FiwTqjdoBsArgg7CRz1IAAAAAsAMAgOta5zBkzxAKLqv5EKOGElAZANjAEzDKIhve+SzuqQUeSfp+Ihn9C85rE4NcI4BVAIDwEGMSAAAAQN8FAACVLnesklKLgWGOpfokpnCMCFhyA9CfGGBLZF99Fd6I+j2UjP4F57Ujq/UAkKg8AIAGIOtNiD3uVgkCwUHI4C4AAACHgyD3JXiNAQBtoWiLv2cU4StX5V6sXoyZH56Y+uzDSdr/88RE1a72TgCM2g2g4Ruq9snSMX04Elen89bysg4ipKYAAAAo85CxTt/cDPoAwO8SAIMD8AnsOC0k6BdIGuYMaLTAobhqbLcQoWGV0Mk3bCkYuanyJ76o+txvaev/vCzlqu4X6xqAUYsANHyDJ/HlU2N7xNLJ+7n7cEI+DvGLJwIAABhrYg1lnuQ61gDA7zEARjtgcd/PnCpsXjjQFVw/4+qbnLH6Zm6lA8WVwKLw0BBGPITi4iEF1qj6x8OF+P+LmOQgFn+nEUCiyQMAWAVoGDz5v8sT3pL22OjEVqYZmK2MLg0BAAAAf20BAEA0D8KgyR+HI7sAfgMAlB7f1etexBWDXZcw9j/MHadPV6HsbCIp1J7LSbdelfMH094+l/SiAAStN53Welen/dYMAHgnAGiQ+HIsL5tN7QTNSDMdBgAA+NQdALgOABoAsJDBW/RNaq4ADLUqSfNWoz0AwPQB6JTrpO3krZFQPf//grnz2Z1jtTsWl+Y4BHcMAQB+T5jEDNlHs9o4oNX/uSdWu/O/I+g8HhuOj3PMNh6PLY+Pc2MejwPq4xzzLdXu/2d/11OkAQS3woAZ/bQPAIDDATRVN+/bG+v07vNy68u7EueDBztFvemVGV5YqQLwpCm1XmjcXa7qdCpdnjwJeX//dCJfrVae++kUwVd1NvVXSnzgeYh/OoXs59H5s79in11nU38p3QN11v1Lukj6eziK7p/DeHHyTADk2g2gATDPp5q8e2odaJmxWHIqHToQAQCAV4TKwlFIxiM24F4NABwr0eErO67c7T1A5B9zN1+VSip0AZ5Y+j2UTPoXjNee9E4CyDUCaACyR9bOLlXqedbeTYP4PAUAAKDuDEvpbHZbgmErAACYzCiO+9WatlWho7Z1VzfaB164+nsgSvs/bwledTa5UwASTc4AAA2Ceex/fJ6aLyQlHBqhQa+GkKsGAAAAWRmNPmpXr2AXAKgAANCoYsBpC4cr4ArhoCS5kyKmC4+63eU9VuasqIsjvOrlCKYAvqj6zwOlyP8zCbm9TV8BMGo3gFUAGYrFcyvnF4drdKl9uS2PDrYKv2sSAAAAACkAAFDiNh5TaSKHhGjZKQCgIKEFvVtAbCvZXLp0lSwxUiVal/67Mi4xzYlgNJHlUzAKFVLNeawA3qj698111v8zSUl1Nv4GBgCMWgRgFYBgtv1kTF661V/s6eWa5yA44uvT2SvHrVHiUthxSyoAAABA3wUAAL8DOC17YPHKfVpuxuRpgXWM0GkIVepNRLiGYfU+6Vi3jEdVPrcAvoj61wMl9v8cJqqy8xUHkGjyAAAa1FCzj3t6sv2TrT1B3Br0Riq53AMAAET0HF5UTkBa7qq/mQC8xgBoAOxnwWB6E5aJc4gdnhE4rK76kKU6dEeQRq9evdB7jy03zTcH5J6o+uzNmYz/F0xU7V6fARg1APDBi8WXj23NHutD07mIbZ7TPbJIBgAAeNhhDuVVty+b9w8ArYFF8Qz05f+G1RJv/T2DgkMabBCvkprtjt1fCyu+YJmi4XLmKQB+qPrdnNn6f8FE1e7WrisAoCAB+GBFez4a7Kzd8s6jFiWGmwJ9TQIAAJillX7tk+z2angArQEF42uxVP3+3rn24dAqSTSP3JyIYNB8em16WD8Yr5WkiawMW36Y+uwDmbX/8yZE1e6865oWAAoGgA+esC53FCdXO+fM2+rxuqpQqf8WAAAAxHOvg/HAfC32SQDwXIAXzPoGSrnjVXV1uxVIexoOP2rjVYcyEiezKbz8o95c3Rlqvpj6981VKf/nhclVvcnTCCgoAL5Ytfjb2b+cuO7Y3tXKh6F7CeGfTQQAAKhU+PXm5mXEMkPrU4BBAdBAC6E0N2UQZSSVxt7gbLgsjYFJtxZYgS8Yv+KZ+CsnSEn5MopeJRj+p/rsRR/x//OVcars5IojUNASgE8w246b63+6EmxH17eXXtycOD6wBQAA4LfvSb2hF71cZbQ12gEMCuCAyX5Ma9lqOUSpS+UOzfKugAKfE1OuInMptNevzehHzawDvqd6Hsp8/Z+PYG+VV+C0RKIRDwCgQRiqdstyf3BpbuXv/6cHu+wxyIURAACARqOMlRhB5HMRr7YAAOdTAGhgwRkYYibQyeqoDMlyLW1RCzJoO6GVJlcYNazDxA5Tgrk3nUQEXqj6uxBz/J8noVT1xlczoKABQINIeDyT8r012O1OXN9mhEJpowMPJwMAAJCMM1mf1eLokDzEwloBQBkAoFMjrzVoc0pTYbjxn1JyK8jYI9PHbU0k3rVELaCx7Kgwww/gBh6oejxc5Pp/hokOs/GVAKNGAJRgHntkcujZdDjx1riLkU8GDfqdAgAAUN7DO9PEynlsPtgVwAJAX4uRS1Y30l3uQfFSozSUHCdZkSNcMfRJKYekj9dyAJK9VOUAfqj63JsKb//nhYmq3uSuKwwA5AaAL161/6hHC7Ofd4NA76zc1mJD/bMMAADg4D8PnFK2Id01jATAAnIpvW0eykVZ7upK95Kxy2jWZRSqTwNdPyZpW8TbO6ginidKRX1rY76o+uyFep7/z5BStZu8GnMBowYAPsEStt3ULl12g+PXp8+2m/Yh+JfjAAAAOUkDSUkTqACBLwFgdOA90MnWA5bIL9f0XRLrtGw+NuyMh+fe5H/SALmXbCi52O0XMU9nZ1MABNiLAAAAAAAA8TMAAAMAAAD31fYTFGNcX2NiYGJgY2pnZWBnX3dxc2sUfnj6u1CU8/88MblqN3QFoKAlAEqwavHC7VNra/fVPhxajCy3hyJa9gAAALKxe2AUYZMOoLraMgANgFaepVfEKBWNHnruduLkyBqLzV6xoTBgNUs27mmWkgtbmBLnzXlZJ7gBHpj6XkhZ/s8neHsbuQCMmgBQ2NaEJfVP52MQ3j+h83QrmOoI9dIqAACAmNhkXceZHAfs24UXgAZgPqGig6SwCS1WcVY3l/OPtTSkZy5eZlJ1orh/kV7V3Vmw2gC+mPrH4WqO//PAlKps6AoHCgqAT/Cq/XH0ypp+2lrEcPvLDBPS+BfjAAAA8Vn0I/FQqD3W1MoAVgDoSCjNfNksVs+LxcQNBgtqIG0tGdic5/FFtHG7SuG5ya4GeYKCDZ64+uxB3ez/PDC5dm3kagIUtARA5alY30N7B0fbzxONyxXf+ukZuD0AAEBG0ojOeWB8Yi7264sAYwDowFiMsPFRCIJcxggZNZ3fpSQhQ01qrty6euHb6+nVM36V6qZf7vajG/6I+tyDTo7/88BEvW5DO80SkGjyAACouMfeaW8977J90NuehfZMijqTH8YAAACyn5eDg/bwd3fZAYwBsKBzGtPerHGfldKF3W32uFuPuKprkHTxfRIyca+UWKlNBJESeAQCfqn6n0Ptfv6fB5KrdpVXAgoaAFDZieLH/XT8LgazsW7QftgMwviX4wAAAFGHxhdzqlIkZuiJAMYAkAcdGYQqUafuebkqzhVKuh+fonoc4k2418J8dcqas89KYZEVt0IAXpj6HlR9/J8HkuytZL8DAQCjlgB8Mdve03X7FWO+dFuV0bZR9KLYAwAADOR0sbWtsVHprrpRAAwFcBA4ym1ZqrSL7ssSSfM9JPEmlt74+3eFt1frGrlxqmRk9ZYrsTtLZgaemPrHocLL/7lIbm8lVwMKBU0AqLjHfmHwvhk0tfL3SitIlOjUv6sAAABfXGT2e0cQtQQbV1UAOoAFEuixmrfduBa2ud2GXkO9nKZDz1SnFAzsmB7Re/V22uPkepnbsTNeuHpX6nn/n4txmt6Kdl1CADBINLkAAFS2pxwE2aSN7/5wU3XWxjybBvanBQAA4OQ9G54l/DC9dl8D6ABIgArjENdGUdy7t7iD3niJEIXLwdVGVaJBUz3vaQT0xi3ne28z3wF+mPrsQVXW/3l6FR1sp/GKAIwSAGVWT2KQ0H1wTq6YeLIl1sS1S7DGBgAAQO/PN+myCEpzLXOspgAjgDb01kxniLklhQU7vOI9lnRkQ7pF5VCP/glMZXn8VWaDSm9+yRbfXVwq8J/mmVUAfpj616FCyf95evJaZw/3CwCgYABQxpJ2tfrqomxb3f/oLA+WHqeiJrQAAABK/iFO+8GeJMih8GYEoAC26MHzQEZsxvufgdc7R3wGq7Hq8kjT1zZExyBL12WDWr4/k24lz/VfMEsEGV6o+sdBIfZ/flbJbg/2CwDAqABQ2UX7U6npdte+S7MFb7XejV3joTAAAMC1EMZbSso0sg5zmAAKgOJj47UEOVscvck8dRDvlxMtvzQTtSfjVV+PSt/QRRiVn9RvtVln1dOb4mEFnqj63NWqjf/zPUR79vYLLAAFCYCKeeyq/WhJHIvKCn352twe1tkAAACEQ9ueBtOF5gYy3y6AAtDLex+NU9Mi/ZDlDXnC/JUmIjlWFM7V+hwgtSWIGR4ljX1C07S1fikGHpn6n+2mtP/zyJTs7eHV1B8YDQAqVqzctD21X7w+tqbpq7F+vX6lUloAAAB7o3BQ3BsPq0QddYAC8Pq/Lx/5TM7SqvV/E/Di+PE53IpsTLR4HeYpVseJvtQR3LNTbV33XvZY5qltEV6o+phWM//PI5PTHXbXAIwKABUr2lvjj9+HgVxkLs4Pg6EKHooAAACYhpIOfMNxACbNTwAsAC56542eqft3SvTOTRqea63OAfFA9VwfLzjzu41St8s515dMbO9S4HADXpj6O1Ra+z+/XZyqnXnsJMAoARjqWTVx+95Q8mSbtaHRtm2ObSOJsM4CAACQ+Qz1Vb03272WDwALeiuj05nSdxpiq4dB9LFD52fb55Sqb/zF1a5ZzlFcZhUWk7wgHSk1aIxGkQWdjCvcy8wPkSW19FzkU99bawH+l3pXs2z/59EOr9c4r3oBFAwAQ8I2FLda+14fRheWhCZonliXgQMtAACAXbKYqkYT5dRh6hICWNBiA96i8eLy3fVWXy0pf/gohPMWV570WbvR9fN3OoXP7Bo5lYwA/57g3NCVbOEuP7RlP8TnCOe6AN6X+m0nKf/ntsXbc170AjAqAIaKXfWs7QddVYtBtPhEJGQLOQIAAKiOOI6yBR0oKONhACzgFV5EK6WKGCOvpIAZLUgBup2TyxcXaYCumbDXc+fzxeRmGr9SZ9VEemeAaFgtq7oKvF6KvlLEnsTVFwUVUwved/qFKNYf5iKqtpCbKdHW2Z5iJ++KMeSMgR5k2Rb9ZxqhZLAA5/HHAADG76JWTvu5Sd3hcM8Z8gGFechThUl4JB/lpLIj07htWpWQnFJCtDFi7SisLZloQJlJx3Sv0FRR1ZCip7eeOhy0AN6n+YaEOAa8NSAHAAAA7MIqDgAA'} ];}); define("audio!VEGAS/boing", function(){ return [{base64:'data:audio/mpeg;base64,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'}, {base64:'data:audio/ogg;base64,T2dnUwACAAAAAAAAAAAnMwAAAAAAAL6vNEQBHgF2b3JiaXMAAAAAAQDuAgAAAAAA/////wAAAAC4AU9nZ1MAAAAAAAAAAAAAJzMAAAEAAAAr7+6+D4D/////////////////kQN2b3JiaXMdAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAwNzA2MjIDAAAAEwAAAEdFTlJFPXNvdW5kIGVmZmVjdHMLAAAAVElUTEU9Ym9pbmcpAAAAQVJUSVNUPXJlZWx3b3JsZHN0dWRpb3MsIG1vZGlmaWVkIGJ5IFBoRVQBBXZvcmJpcyZCQ1YBAAgAAIAiTBjEgNCQVQAAEAAAoKw3lnvIvffee4GoRxR7iL333nvjrEfQeoi599577r2nGnvLvffecyA0ZBUAAAQAgCkImnLgQuq99x4Z5hFRGirHvfceGYWJMJQZhT2V2lrrIZPcQuo95x4IDVkFAAACAEAIIYQUUkghhRRSSCGFFFJIKaWYYooppphiyimnHHPMMccggw466KSTUEIJKaRQSiqppJRSSi3WWnPuvQfdc+9B+CCEEEIIIYQQQgghhBBCCEJDVgEAIAAABEIIIWQQQgghhBRSSCGmmGLKKaeA0JBVAAAgAIAAAAAASZEUy7EczdEczfEczxElURIl0TIt01I1UzM9VVRF1VRVV1VdXXdt1XZt1ZZt11Zt1XZt1VZtWbZt27Zt27Zt27Zt27Zt27ZtIDRkFQAgAQCgIzmSIymSIimS4ziSBISGrAIAZAAABACgKIrjOI7kSI4laZJmeZZniZqomZroqZ4KhIasAgAAAQAEAAAAAADgeIrneI5neZLneI5neZqnaZqmaZqmaZqmaZqmaZqmaZqmaZqmaZqmaZqmaZqmaZqmaZqmaZqmaZqmaUBoyCoAQAIAQMdxHMdxHMdxHEdyJAcIDVkFAMgAAAgAQFIkx3IsR3M0x3M8R3REx3RMyZRUybVcCwgNWQUAAAIACAAAAAAAQBMsRVM8x5M8zxM1z9M0zRNNUTRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRN0zRNUxSB0JBVAAAEAAAhnWaWaoAIM5BhIDRkFQCAAAAAGKEIQwwIDVkFAAAEAACIoeQgmtCa8805DprloKkUm9PBiVSbJ7mpmJtzzjnnnGzOGeOcc84pypnFoJnQmnPOSQyapaCZ0JpzznkSmwetqdKac84Z55wOxhlhnHPOadKaB6nZWJtzzlnQmuaouRSbc86JlJsntblUm3POOeecc84555xzzqlenM7BOeGcc86J2ptruQldnHPO+WSc7s0J4ZxzzjnnnHPOOeecc84JQkNWAQBAAAAEYdgYxp2CIH2OBmIUIaYhkx50jw6ToDHIKaQejY5GSqmDUFIZJ6V0gtCQVQAAIAAAhBBSSCGFFFJIIYUUUkghhhhiiCGnnHIKKqikkooqyiizzDLLLLPMMsusw84667DDEEMMMbTSSiw11VZjjbXmnnOuOUhrpbXWWiullFJKKaUgNGQVAAACAEAgZJBBBhmFFFJIIYaYcsopp6CCCggNWQUAAAIACAAAAPAkzxEd0REd0REd0REd0REdz/EcURIlURIl0TItUzM9VVRVV3ZtWZd127eFXdh139d939eNXxeGZVmWZVmWZVmWZVmWZVmWZQlCQ1YBACAAAABCCCGEFFJIIYWUYowxx5yDTkIJgdCQVQAAIACAAAAAAEdxFMeRHMmRJEuyJE3SLM3yNE/zNNETRVE0TVMVXdEVddMWZVM2XdM1ZdNVZdV2Zdm2ZVu3fVm2fd/3fd/3fd/3fd/3fd/XdSA0ZBUAIAEAoCM5kiIpkiI5juNIkgSEhqwCAGQAAAQAoCiO4jiOI0mSJFmSJnmWZ4maqZme6amiCoSGrAIAAAEABAAAAAAAoGiKp5iKp4iK54iOKImWaYmaqrmibMqu67qu67qu67qu67qu67qu67qu67qu67qu67qu67qu67qu67pAaMgqAEACAEBHciRHciRFUiRFciQHCA1ZBQDIAAAIAMAxHENSJMeyLE3zNE/zNNETPdEzPVV0RRcIDVkFAAACAAgAAAAAAMCQDEuxHM3RJFFSLdVSNdVSLVVUPVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVdU0TdM0gdCQlQAAEAAADTr4GnvJmMSSe2iMQgx665hzjnrNjCLIcewQM4h5C5UjBHmNmUSIcSA0ZEUAEAUAABiDHEPMIeecpE5S5Jyj0lFqnHOUOkodpRRryrWjVGJLtTbOOUodpYxSyrW02lFKtaYaCwAACHAAAAiwEAoNWREARAEAEAghpZBSSCnmnHIOKaWcY84hppRzyjnlnIPSSamcc9I5KZFSyjnlnHLOSemcVM45KZ2EAgAAAhwAAAIshEJDVgQAcQIADsfxPEnTRFHSNFH0TNF1PdF0XUnTTFMTRVXVRFFVTVe1bdFUZVvSNNPURFFVNVFUVVE1bdlUVdv2TNOWTdfVbVFVdVu2bWF4bdv3PdO0bVFVbd10XVt3bdn3ZVvXjUfTTFMTRVfVRFF1TVfVbVN1bV0TRdcVVVeWRdWVZVeWdV+VZd3XRNF1RdWUXVF1ZVuVXd92ZVn3Tdf1dVWWhV+VZeG3dV0Ybt83nlFVdV+VXd9XZdkXbt02ftv3hWfSNNPURNFVNdFUXdNVdd10XdvWRNF1RVe1ZdFUXdmVbd9XXdn2NVF0XdFVZVl0VVlWZdn3XVn2dVFVfVuVZd9XXdn3bd8XhtnWfeF0XV1XZdkXVln2fdvXleXWdeH4TNO2TdfVddN1fd/2dWeZdV34Rdf1fVWWfWO1ZV/4hd+p+8bxjKqq66rtCr8qy8KwC7vz3L4vlHXb+G3dZ9y+j/Hj/MaRa9vCMeu2c9y+riy/8zN+ZVh6pmnbpuv6uum6vi/rujHcvq8UVdXXVVs2htWVheMWfuPYfeE4Rtf1fVWWfWO1ZWHYfd94fmF4nte2jeH2fcps60YffJ/yzLqN7fvGcvs653eOzvAMCQAAGHAAAAgwoQwUGrIiAIgTAGAQcg4xBSFSDEIIIaUOQkoRYxAy56RkzEkJpaQWSkktYgxC5piUzDkpoZSWQikthRJaC6XEFkpprbVWa2ot1hBKa6GUGEMpLabWakyt1RoxBiFzTkrmnJRSSmuhlNYy56h0DlLqIKSUUmqxpBRj5ZyUDDoqHYSUSioxlZRiDKnEVlKKtaRUY2ux5RZjzqGUFksqsZWUYm0x5RhjzDliDELmnJTMOSmhlNZKSS1WzknpIKSUOSippBRjKSnFzDlJHYSUOugolZRiTC3FFkqJraRUYympxRZjzi3FWENJLZaUYi0pxdhizLnFllsHobWQSoyhlBhbjDm31moNpcRYUoq1pFRjjLX2GGPOoZQYSyo1lpRibTX22mKsObWWa2qx5hZjz7Xl1mvOvafWak2x5dpizD3mGGTNuQcPQmuhlBZDKTG21mptMeYcSomtpFRjKSnWGGPOLdbaQykxlpRiLSnVGmPMOdbYa2ot1xZjz6nFmmvOwceYY08t1hxjzD3FlmvNufeaW5AFAAAMOAAABJhQBgoNWQkARAEAEIQoxRiEBiHGnJPQIMSYc1IqxpyDkErFmHMQSsqcg1BKSplzEEpJKZSSSkqthVJKSqm1AgAAChwAAAJs0JRYHKDQkJUAQCoAgMFxLMvzRFE1ZdmxJM8TRdNUVdt2LMvzRNE0VdW2Lc8TRdNUVdfVdcvzRNFUVdV1dd0TRdVUVdeVZd/3RNE0VdV1Zdn3TdN0VdeVZdv2fdM0Vdd1ZVm2fWF1VdeVZdvWbWNYVdd1Zdm2bV05bt3WdeEXhmGY2rru+74vDMfwTAMAwBMcAIAKbFgd4aRoLLDQkJUAQAYAAGEMQgYhhQxCSCGFlEJIKSUAAGDAAQAgwIQyUGjISgAgFQAAIMRaa6211lpiqbXWWmuttYZKa6211lprrbXWWmuttdZaa6211lprrbXWWmuttdZaa621lFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkopFQDoV+EA4P9gw+oIJ0VjgYWGrAQAwgEAAGOUYgw66SSk1DDlGIRSUkmllUYx5yCUklJKrVXOSUilpdZai7FyTkpJKbUWW4wdhJRaai3GGGPsIKSUWmsxxhhjKKWlGGOsMdZaQ0mptRhjjDXXWlJqLcZaa62595JSizHGXGvuuZfWYqy15pxzzj21FmOtNefcc/CptRhjzrX33ntQrcVYa645B+F7AQDcDQ4AEAk2zrCSdFY4GlxoyEoAICQAgECIMcaccw5CCCFESjHmnHMQQgghhEgpxpxzDkIIIYSQMeaccxBCCKGUUjLGnHMOQggllFBK5pxzEEIIoZRSSsmccw5CCCGUUkopHXQQQgihlFJKKaVzDkIIoZRSSimlhBBCKKWUUkoppZQQQgillFJKKaWUEkIIpZRSSimllFJCCKGUUkoppaRSSgihlFJKKaWUUkoJIZRSSimllFJKKaGEUkoppZRSSikllFBKKaWUUkoqpRQAAHDgAAAQYASdZFRZhI0mXHgACg1ZCQAAAQAgzlpsKUZGMecghsggxCCGCinFnLUMKYMcpkwphJSVzjGGiJMWWwsVAwAAQBAAQCBkAoECKDCQAQAHCAlSAEBhgaFDhAgQo8DAuLi0AQAIQmSGSEQsBokJ1UBRMR0ALC4w5ANAhsZG2sUFdBnggi7uOhBCEIIQxOIACkjAwQk3PPGGJ9zgBJ2iUgcBAAAAACAAAA8AAMcGEBHRHEeHxwdIiMgISUkAAAAAADgAwAcAwGECREQ0x9Hh8QESIjJCUhIAAAAAAAAAAAAEBAQAAAAAAAIAAAAEBE9nZ1MAAMAEAQAAAAAAJzMAAAIAAAClaNU7RxYXFhYXGUZAQD8+QT1EO0FAOj1HPz9BPD8+PD4+Nz0+Ojs8PTo/Pz47Pjw/PD05PTk/O0A+Pjw6PTo7PT09Oz08PDo/Ojw8NL6OJ6f6KwQGKJ52Lk4B3tEQgvwPAFTO3M7m+rNDNAcUb04316kAaXQ3dLkCbMr0clv/7BBIoLizN9eqgCniokXpAIzS4vLB/6wQ9QDFjaPMdTog00txygSkrlB6vP7MEKQwUAg6W9wg8IXIAfL+D3yujHTinzEEHCiuYMG+k0LbZdyaW7rvRQBaemLBC1uZ8MnpfyhYSrNAwg8AIHIBtwFgebi8t/OuBIUhCBvffO8q78y2CMTeRBzXP1YB1PyvRsCgVBNY5txSgKXL1EkPnktalQORBvySnv8DqRq5g1nQv28GgBICr3fcnbs3G8pL23+T+voD81kp+p8KMdcPZYr+V0FRrt8FFt53f/cuAL5buvMrxAUeffr9P1inrZQSG3tyJIBQkABNwlRaN3+9A8FDKIFKOgCClkZ/cAfW8jKtCFUAUrZQxJP+GjUmJwA+a3qGRQhAIKTv/yLZyCgh/AAAURjgKAEoh5iueWZrC1VfEQKG69kfhy3ao22ttrrN0SYEhlY3qhOGtv5jSAD+W7qqC54u8Mnzv5Ni5Aiz6Y8VkwCUcADOfvQrHpvgtDyu92PYlR6lJyDdCZa1cXIp1S9cysUsFbVtzRBhAB5cOrkdrRwftZ3+B1IqdxEbH8YBQAxD6GeqUTR0L0eLq7coYyzQWsT51E9uD92nFN5Pv7fwfipTnH7jMcWxXRIAnkvaKjOhF/jg73/ByCgg/AAAVgiQNwAgjT/ehTyiaiSNZYiOPX3OXIODSEzlcGNa2DWfLtpZcImfo6SSBF5bunEdl9fxuYT//RdRuvIUSAsA0CWw5wCwMpm7eh9Z4LSJhWBsCa3P3VlQuWbSYr61BK50AU+SiyZUM4PU9W3VaBoA3lt6kgsuLvBpvv87jJQSG/dvnwNAFIKi5JypRwbRq5HX55ERZkg6eLSy2NrN8DxMbs5Ne+aludeZkDsebLqSK263+CX9/u+kNpIJscC4o0gA0YEyluUaUemhiwbmQ77F6vV6/QXE89MMh/MqYt/vH6e3q3WSYjr91LI/AJ5LevNVqAqA34THf5Fi5Chx+sMxAQDdAJBICA9bq1RPNUDv98wTB6VCYMAiOEhnnZ0/ZrPOIs8YKfv+MrJ/EgA+S3opDZXX8TA//52UyhNik6PqdAC8EKR3dPK1M0gvomvODmJtg7Qc+mKfNNz036pzFy2mzkU/EU4BXnt62VEoLCB+Kf/8d1JXdhAbfux0AKIQxPpUk5ljDcGjdgUZ7Z2sni6qtPmLDhIiwcw9EfM7VimiLrkcAF5rehNFGAUEl/bzv6JUjYwCKwAAGgCiBPY9AMpbpAtBgxH1EuSmO7307bIJNkRW5qNXmSkpaZJdoVRUimPt5ZfPveT7rhIAvkr6VL3QJQg8+P1fqDwlhB8AwBUC+GgA6iIabfBDi8hYcWlECNuNGuTssgx5StbzVB9hzZ0n70QuqPKZUhIJfpv6DJ1QJBDSL/77r0hlBBB+AAArBPCMABjaCm86vLewpS9sgm9zJ9hEkiUTD3JLt/KIeB1j6oMakstLvdMDvlp6ElEocqD4tL//ixQjpUBaAIASAnimAxDDlCZ7wCgiqyoDRKieWptgJidZJL61PaOzTdHvIKumrbw3Ga/BNgB+S/qyilAaQPwy3v+dGiMjzAJ7HABEIbAu0dy/997VA/XHybsFIR47Z3JRyHlI6Ng5IC5GjaJYjogEahSeirrnvVAqcOnl/PPf43apjADCDwAQhQCcJQCHsdl7wAwXGx+wbfKsc6Op2L9swVp4mWA8uRZV9pg5zJtB4FAeSvoIk1ACFJf2/m+2KZWjxGKJ/qVMgCix1GFNmFMxhXfHP56J+QEafQIix2glwV5EF6qQsiUT8iaQYvEeAN6KugwvQqOA+Gi+/zuLkRFiAXF0AJQQHJicK88eyqG8pp5zPzAczquIxdAbeAjJmlKPQtIP3frFM1hWA75Zug9oYQpQ+Gzj/V+WlcpRYBdw8q8WAFEIUpFQf++iCuohdS3HTArZb0yF9H7p3x3UMIwSf/8mKolRI1UBPmq65FekbtXD+/KvBevKLmZxoOtNgJJYuu3oms5AVzzsmB4wGSMee5xlhrgZkmVxFAByvFVVmcOlBta/FgAeWrptxAsJ4Pjg818wUgosGiPvL4BI0ZQtI4T0GzgU28zfi8h1INUGlkT7WUX6jch3z7zjzzgC3mk6h3boW/UxxuE/gUovXuwTNLGAKLE9tNvOI8lGoVSO+1VPzJKZUur15t8jw3nn4/vjNAtxQezTBWN7Ad5JmjeyI+MCH9rv/866cpRYJNQAJBKEa5d0lfT/FeiBeOko0Tn396iiI+cnM/dQrTc8phl4scrFz1hU7BsAnlnaNzpi5nV8DL//OyqjxKIAOW4AUWImmKTC+A+rAFs8YddZZRzoRoCyrp6ky7gthw2mb1yUMRUBAF5ZWjc2w7yGl/H4b6RUjhKLB1bnDESJdXjUIPbOEBVaIL2y7Cz9tmh0I4yqVpmzss20u9mn6q8VQSQAfknaN7Ej92v4jZ//Oio+iMUALCDsANrsybloNgqIeM6DV45aW9dKTIq/+uam3w9nyCwqNimGkhtXinEA3kmaNjwQ/4hPLv8NlV3ETIDuGqASpoeb2m+kobpCFY9af1rtf1j8O/JePOprtP9hy1Ffo/3t1/4HkTomAP552ocd+NqoB89/LVDpxezErh4SRIm/6zlVSGawhXtKOLoGMkH4iUU5sw67qJVGUnVXr4jk3mZlBwA+WZo2ccacK7xG+P3fLetKD2LRoMcGECVyrzGW/6vfNRLj8l3qVVfj9UakGsM/Q9XxbtcePxpjdqNLl5YOJwDeWVo2umD2K3zMcfvvpGrkCLFYoCMBlRi4juP9PhKF8nxxoxHjlXL0xVPFM+P1dGHkndpR8Xilrtf3/U6PBADeWVo2uiPXBT5n+v3f2FZGiUVC0oAosdFFvnR+CQql2tXWgfNISjtrO0uvvBzlcU5BofnvMla9mlysWqR6AH5J6sMW0r1VL/bfYaQHLRc1iPlegihhEojr1OU0gAv6AR8kGmNRbOKY52Szn0QOo9jFwyi+4hGNYrYA3knqw2e4L/CbcvzvpBg5SiwO9MgnAFFiEY//SFaNhwJohDUDs2yz1esNU1avV43MR7PfpmiImIt+3qbyBABeSVo3OpLVNXwOz//O4a70IBYDtrcBiRBoZMw9+l9BQRttbiFiWqReeXQRHx4oLCnOYOdVMtzxmNxJbADeWerDdrJ3i09p/x1GjhKLQMceCYgSTSV9Ofv1pIAeLt1QO7od3S4wkrJ7HttZ2yP/AVsrDrIXe4OKNVtrVAK+WVqHTcj7Oj75/HcYKSEWiW4jAVHCqiff/e/tCtDHXr/OzY2l5za4ILk52SxIOhW0rt6JQPtMS4AoAQDeWGobdKxfx2/mP/9dvf3KE2KRsN9NACRC0C+hgk1zjDGCki2qqzpWXd1sNUbVc1tsbMSWQHKZ9XIY4RkS3kk6h4rQJyA+ef53GDlCLDA2JisSECUi87vyr0sZoISssyubWsq5ojWFUhdS2kRIMviqB+eDCgQAfllqAyvDfoFf3P4HGBklFjNWHQmIEmvsYe+lf1MUXKKXHkkxB2SKXlGcvNSKrJpV+d2f97J7D/TGqqniAT5aug+MSL2O33j+d1TuIBaJPTIBwgxczmOtnqZphxD6hb/wOIe4O47mEg//rHNGcUZxyUDE+JxgAf5IWjcahWMDimv4/He2lSfMooadbw9ASZjgvda4tqgA8WALwwaqkMvGBvTZDz11jh22tfZZF0SmPJOBpdQvAJ5JWobPpPcSL+3x30yM9CAWC2NrgMioVi35XugtKBAXqxp7EBmUal9YUh5MaK7obiJ+zSP0bvBmK0gAvlm6bqQKUwHig+e/zlL5g1gMODoWUBIposkalh2bCqj+N5uIxeICKbWcUNEP20QlR0Jm0IiQm5r9rvRGfkoFAN6JOnMz7JfhITz+i5RKD2IRdEd904AokZR/n800pyv4TVJDcjDpnN5g4s6C75eLfrLax//fYjWysr+tOhMAXmlah81k51J9cPvXEpU7iH2gewyAKOG963nevD0rUMNRGXUmM0hyxMz4H5sCquVLy6k8kw9zc1lz/zqJogBeWbpuXBGaAoaP5v3fUTlCLBrDfC0gSoiyv9Pv/kIEWjlt6tmXRPtZT6i0y5n15MRGefmDtEr7g73mqQG+mToGF9JzWT64/RcqdxCLGWQdCURGQmx7fAcmAbzmG3pwEdqeVymQ5EnvWxuu9H1Hb9G7UgYOp8kDfmm6brQLUwHBZ/H+L2uVUWCR0HXXgCgE6tX9+78/VFcP5dxgVQ4zbefctrM8ZJyOeCUQR8hrpVBoQ+fuAJ5Jugx0oVwAePD+b6iYEosJMG5IoBJ/EsywkocnEdDoutmDattANIs4vD/LRcEr+cNu1iQMWuRqvgFeWVqGJuxzTX3w/m8wcoTHi8XOPU4AUQiaOMtRPxwAjgvxyYicq5Aa+zpsXTX7ekaGpSUr+rXuCN2iCj5Zum6sCVMDis/p+V+olAKLge3tAEQJp8394r/YoEJ5mod8oQqI51YO3w+hqDDdKISBBpnPGNZ14TsPmQDeaToHF0HgpfrVvP8rUPmDWQQ2CUAUDkhf2jF6y0kAPLw5kUynx1mKJ4oSHffXwWE06EO7YTCg/DDpMKJRfmm6DTZhNgHVY3r+a4GKKbEosRuvZkDYwde17+Wm5ipOCMMTW50N2g+Dtg3Tz7SwBCf4+R0WZxDjzqEBAP54umxYhVkD4h7c/guVo8yioccTgCih1b7R99WYI4AmoRYtG5pQ3ScgJKhxSnozyaB65dUnTe3oP3kAXlmah6/k/yU+pud/oVJKLGbI+khAlOh4N+a2tTmAVvnP7KYO94cHDhzQFzW3sFrJrqvO9/3gLVlCBjAcAL5peg0rQtsA1Wfz+NcSlR60WCTskQmIEpG+JNt7C6GAnvl3tu2s7cwuawSfV6KUoDdwS/JX3RXp3H31AD5pOrOdfF1Xr+j9r6WRih7MrtFmCYgOTCJea1+ehgDUcKr2jinpWa4iohnkCzwkdJ7LZHtKKnvzZlCZAF5JOvJdmN8A+J3Pf0elB7FYtK0BokQOcf/He0dWKarR6Vwq5IIq5Nk8+NoJYZVoBOUyLMPtVG4mkQDeWXoNFKEWUHzw+e+ojBILxHiuigRYiR3rN0u0yYslIINpfKylXmmr1RMpa2mSL/RqyqqcGtjSPsOyHou6CwDeWdraM3a/wm+8/wuVHsQi0HUNIEo0lf+WypmGAC6HdR7VS3msPMzZ4d192ykjUqup0aB4+HB1l8QDvml65YuQA+he1fNfQ8UHLRaJ6NuVACXxqyfH2WNGxMFz6p1NgO44PraCx8KwhMP6ItqLcHlh46Y5zh4A/kg6N0zCvAHFR9vvf92iMkosGuZXAyBKSJW89DiOqECMeOazNzY2NjQ3YjhgXlY3qDo9VVTE86hm1jYAT2dnUwAAwAwCAAAAAAAnMwAAAwAAAIIv215CQEFAPDw/QDs/Pz9APTxAPTs8PT8+Pj4/P0A9PDw+PD5BP0BBPztAPjk/OkI/PT9BQEE6QT48Qj1ART0+PUI/QUA/PnlaBifG95r6mD7/LVgqR5nFDI4aIJFoTeJdn/2CChCdHlpG9ZVD61pq8Wj5tDRao7bkjqdsQLj029KrB2IbAJ5pum1QhVkDDI/R3v9lXVsZpeUisT1RAESJ2KIcZ45DXfDSkedeShndzRf4URndHpKhrXUK+zcgsY/auXs3SDwAXknaBl2YLQB8Sp//IrnSg9g11sgaEB1c6f8+k5YmJgCFP71aJUsZmMtFZ4zDfYq5zkXb6WdPwaeeMUzs9e4BAD5J2ocmeK6p1/D+18JIZZQPiwPrZgpAlLBIW8L9RmtS0Ni0ruNlmRBBJVq2KJ4dC6tkkeYzSskwYTULAF5Zum5YhUMHgqvNx39RXRklFgPGc80AI0GMNUy4O2gBzfbASds0o0Vn1ktMT5EJLnqYe1rtApdsqlAECV5Z2uYXzH0pfEzPfylU+KDFIuDImABEiU9rJsk3/0oBL+bHlAMZx0iBIw3x7SyHdikfs58rEsO7xqZLqm5+A35peg6bMO26+tXm6Z8yYuegxaIEIgDRwReTxCXJew0hAMeXvvDHTMhtcC4RupA4nRjAt5/Au/o3zgJbleMKZwAeeXpuJAnTB1QDOP0nUfHB9MDBb6GevH7zzXPvALVaXN2/PXtLfGcZHQ+v2GkJotbjDo9ScCYIWn6SAD55ugwt2P1KvYb3P81s5yAWM3ojI4Eo0VvJOfMc6wVw9fto1dTDInmwSG1uSRqJzK0/F3YUXnsKrVRye9m/Az55um90JK3rwmvsw78bFT2YxSTIrgElMZrfOb52rwqUO/3oZQWS+gPzd5ZHnAypzCqrDu/dyTJCfQgU63sEAN5oOvM79rswXs3t74mdg1hUh1Y3ICr+0Vle8qekHQg5Z08wmSH3ORsjN11ElYCPb8g+O7Tnqiog0nWWqMIYAL5I2od27H4hfYz9+HtQwQcvF6eA+xloJK6WaW18YRsKZIZFr1mrt1haXQO1vqbSqFQa9yNfQy9rgpGqyJTvPR4+WXpsMGLXdenDPP1Lw87FLAZgHkCUSGKM99vWbzWgIgerNkgkNa7m3YFeXHQZbic14AfFexAp6/2pIs0CXmm6D5sxzxU+n27/NOxczOIE0AFEBzn2cC4tleNUoCv5jFVSHFtTZuvYHsmevKD9yBh6lfNP1LUNLsIEPml6D42I/RpexeGvubBz6HKR7GIBJWG9HUea3tCiB6ozc1U5ZOYSDydx23Zs0T6iTsdWj/MjY/GXT57yxr0jCd6Iugzs5PclPvc+/T2xczCLOsDOgEDC5FC3hpltFFBnv0gz1Ys7Wnx1dUW13frd+0i5HzVRlwa5TEW5LAFeaboOzmT3lfBxPP5dFnYuZrHQOQ9AFAIr1caZaOsDKBm9pdOmtmlsGleRTR3PkqNLsbX4q+7/BQetDR55umV3OJd4jXP55Ymdg1kMyFoJsBIp7RgSb0u6AyJ+jgcnk8mauq8iBVR5ZZ+5nSEY9YdJaYEraQaZJH6Juocr6b7C54/bLx7YOWi0CJAJEB2s3Vc5P5OmaYcQFDP9JMz6qhyeBGd2f3ROjrh0lAlcjfPWBs2XMwEeabplmzAdQOHzuPyrtFahg1gkWk8ZgJJIq+K/c/JF1wKPNVM4HfOYXTybHuj4LCMexIlKTqZSwqfLxI7vKQGeaLrkRrJ5Ha9yn/6lTSpwMIuBzvtJACvRaG6Xt2obAh5lIc2uI/Y1my37CyZP9ZGVVQ47GxIzySby9MaVBZ5ZetqddK6rz+3y7zCr6EHLxbL9nDZAlAjEWQvyeVMQHdGWp5RkjZOT7gugbb1ppjgLJfsyTk83h1CeLjEBvll6W01oOwA+7dO/CxU9iEWi6yICEB1Euo56i+09BjqK/5ul6Jy+w7lH5Tg0keY+qZKRW/jtp9grLnOkDgD+aHoETTjcQNTVXP6dqPigxaKGURkBKIkKkiyyx4EW0Fjdq+MGgY5jk5bYShJtyk9KQb0r5/XR9q7GEycqGgAeabryo3DsAOrXcPl3qlExZRYHzOcAysB4jP30fzRXpaTyVWUusXqN7BtQkQ+TzHLwpkKI8FyTHXwDrC3CEwC+aXrlqzA/QNSSnrd/LaxTV3YwiwH19wmgElcr/HfJw8sh1vBfrfWJtxcQL33y58Vz6UljnimNaduNA2cBij4EPol6kBN2vy78xu2fhp2DWAQrF4CRMBX2P9ZjXajCcTymbE/mjDiPxHbQfzO8j39KPyUCfrk/sshNdbcJAH55emYW5H0pPcLc33uVnYtZHNidiwC4jLVr72mPWVR77x17zKaZ5MUiM9YONpQrnj7ic+KiuK8yo3QzHj55euSKMJ9A1C/n8p8kbcUHsWjMbg6AQOKr9o4x5GsNwKt92dCESjwM/PRajcfjFknUpYt7MOspqQ9dAN6IuoYL5lypjzFOfw/sHDRenEDWlQ0wEojbb23fWaEcUadrumvYJFeP9N8sqbRFhftwOIQoXfprEMlwZlAaPnm6DK7Y91L4zfvyi5u0dg5ikXAAECVc7HoXu6ymKxC2cy7JcetzQhh6fWDZ/MOwlbqpm1YEGoYqGRQSXng6Byey9zo+x9gvHqvJdg5iMY3AzvcngJLotmJelxaJQBPLC0k+GaSsPT0vYLqcYo6LCGQrlswpA9OgQZveSLrmZsx7YX0cx1+01+xQXSwW3eYABEqHiJ7/mcYYoZUkXnnBy2pYYtfPOL+hHISotkYcSZGI95ASstmiGE8yAd54ug1fyJ4r6cPe31NqOxezGGiRkUCXiLHa/bMsw0RwLVKv3I/oHDV0nih7Y2FsShBQszJUmXA7U3mXDmGZAL5Yug4uyO/S+LzL8Y+NHaLjRQUAIFCktvpibrqXRADcil2S3SlzpKHSLB3c9Bo9b4axstiU9f+qmM7V2JSWsAD+aLoNLvBurU9nf9zYIcrigGYXKKVWw7p8LsvSO6CHzl5/fOw8CITqQe6QfZvjXOEgW7GXSucIQgaU7CpGmP4AAP5HWjayCBq6MD7fjr90sHPocnEL2DkA7yD28qhjPXOO0Br75Ynq6I7jIxGh8F89z9j1WT3bmuMhIGgdyc07BP5ouuZ2zLOVPm+n3xM7yCweYHsBVayN2Edeit0Az+ceOGoi6jklusA9bSGZJsvyC9EZxUtKr5/8omoB/mi6DSzwbaRP9/kXbuwQjxYlQA2UYhNburZ/dtMBSdnWlrIbGSZvJB0RnzBNjLkxC4eSsbcbMyIcyokWm9sIAL54euUa6bmGXy2df8GzZQfNogYsoBRiej/7f+4WnCDef/7/JMYhe0AJlelY2oa4zhPda+kDijTRLpeE4ekD3ni65lZ4N9Jv99gvOtg5lMUpFj0Aw1iRtn6j6bMFcHYvqi6ursqShZpa7voK+8eJdIjTgr7Kzb8JXng6cwvm3UofY+73wQ6axSSWOQHRY+jUdbHHfq5APFU2fBAZ0MweaVU2Evty6yppw//W3Llle7S1pqvLNn0J3oi6WQtjv8Rv2+GXNnYos5gh61iAlDC5z2vSY6bBAOQ4dBB1EFPMKot+EX8UXo7L32DpJKeI67PKTv53mvM76d9KjzMPv2gu7FBlMc3Euj0A5dCRiP778E+rgJDr2WlyTGOvRlODgGmFlERfeBGwSTvz4GrPlcpHXY8OAN5ouqIV2hZXG+dfwFynzplZ1CExCVCKmP3bM+85bYQqeEkzISnbblScA66iO2rOW1jIQEXLmVyWqI3a9cupCT5ZeoKJMa4bvzj/4lmcnYtZLDrnMgBGgbR3wwh/sAOg+7xtrlRD5E4dL3evtV21U0pW432r2gJWVBXaigK+aNpyK9na4sM+/aKJHerlYqD36AA0ir8k/WL+rUoHOvWyBpKOkKheaVb/uFPIYjf7lXiIwQujZzyZVpIyXAqeefrAE7ZdSb81xz9u7KBZBOo6AiBQGkuXtJuchBDAid3noQjGxyJlao+RYSHeoveYtvxqSb/l28QuLYktnIIOAP5ouvEzdl2TPsc8/OI2s3Ow86JEj5eTAESJ7yyv0Y+0lYigWEDTtmLLkbbDben126I1me53ypSMEmUnSNThggQeWXqGXZgeAOkK9k/bpq7QxePddN6/bQESiRP/F9v3ICo0qWYjG3RVO2mz+3ZymNxjbi9GhF9fKpui5CRiqMYVAD5pehAL2bqUPlo5/9Je2KG6WASydgBExlVtZr4QRb1Ahw2lrW/PSmbixTqkiL0bTseWk1p/2s4UjwYeabrbM/K5Zry0w99TqnYOGi1KDn0lQDFtfoMNdvUgpYSwYza0wvEjkc7+xEruOaXNHIwdLvm0Qf/K4cn7mCckAP5YunAz9rsyPrTTLx5BKXYOYjfm+bcFgJX43Ju13q+7AtQ6D3qXE1nTCPOoSjZVC6HH+G64RN5ROItdWH8Evmi6qxPmuWa8jP3yxM5FLAJzXVYHsBJWtqZpfwfNIyWjeVdG7Kea/Vbv9uCEsJm82EPOc4DMZOnDBrkAHlja7Rn7bKWPU86/6Ei2Q81iWnqPLgKQKA5bb87VpmmQculxaW+VPY3IxvPrI48cRnMk49+sLBTJlJKrJSml3L4N/lg6swfpH63f5jn8oinFDtXF/IAFwCu2m/qP/097VwjB+Kg1Nt4qiOC38lik3lRp5Fx8mHYZKZTE17ydA953mvO7oOVL48V+aWPnYIQzAEAxCqATcCeTc1Jaee6E0wYw8+VElro/hb6KHDPVXkB0JCXsINPqzGj559TZJQD+d1qCVTDBLR7T6dc9YgeUxZvBfB2AJNJvdLPk5UxcG16yH2nUoRw7knJS0X2HYftcWcp1SXDp+qileMynckWi6UTVAQB+eLoMToJJr/Ab++Ngh5jFAByA6KCPYNd/tmWKAnpgxMs6LJMNZOEDj5KfZXP+n8Jvy+T/PWLRcMkOLpaePng6ghXzXRifJ9z9AiY5M4sqEA0gOnQZviz5myYkHhy5CTJwyR4k3V4Rl6Ghzbt9h9uagVndoktpLTSvAwD+h1rDFftfSi/27YccmcWDbtmAqLSlKfVs8vMugMl58QrWAmYvD6RkQPVI9xoGmi8WXaKkTNI2ZtK4agEAvni6DY6C6AafI55/AUfJDni0aIAFSmmSY8w1/FiCQrXKXOrNSbgip3+T2ZFKKEQpVI4x812osriUPqRoUgCLYQYAXog6MivmGaWPM64/N3ZAWbwBkEApZtSh5pzt8wSgq+SpqN3a6HziXqEXQDNt5LOo3rAdId3EXIn8ZXIxGPgB/nfacwvmv2a8zMO3H7JkZs6CGrugSzrn+/nX4HN2Bf6HF/b/SFBcJxW+4xRmjbW3SdGOtqPJyGK5dfm11lpHBwB+aNpmF0FBF9LH7fgL2OyM0EwdgJEgUZqPYFon7m9RIJa/zarZCyyHm01VHkP/lkuburcI6Fa6VrN+orQGnGUBflg6Mgvm2Vgfz336BRw2iwOwEpSkb1CvbV2WIQB2jU+17YteDOOntpO261JHeUiWSVi65GV9r+s7X0Yi+wMAT2dnUwAEtlcCAAAAAAAnMwAABAAAAIhEZMYTPD9CQD5GOz5BP0M4Qj4/QD47Mx5omnOHILaxPtjPY86ZWdwV22wCkYi/tznvnUtbATyZTApZxpXZiJfNHSrb9LYq8K7XyJDeRtWTikCCBj5oOq0Zcy6Mj7Pvvn2To0eLOtlEAGVwv8UcL/UlGwe8fSxz4aixfVvsJqh3V9GwRxMcRRnKb2CDSROfRpI4AL5XmgcWQYkb49fbvn2Tk5eLagE7A0Ypf9rT28ZyNAH3HfPbKRBygiPJLeSf2dXrN/20iQcW3BKPqteLt363M2sYAF5X6rkF82yMjz2O377JyePFR+g0AxLF4xzhXIuZFaiNbVclzK6Vh2r2QowtamVBk+GK8YR9C+fCnBHjyTh4uwTeaLqqK/bdGh/bfm5yVhYzsABupD+xQrruI+kAqtdvbR/MCDGeD1aur2Yxz2rvNeZeDDUZAq3jgeCNUKYCAJ5nmtSF9G6kV7FvP5McdST8AADR0xIAsK5LlHWUa5Yt+lYAoqsjDlvRhNsS0Rp/00PPCl7LRHCQEL/OmkTbBbF3pexPLgC+aLroBdO31sXp229yVBY1urYAVpETCe1/t1Q6ADNLFQsJvUA37UEkfIWxE/utsh0qz055MuDcp772Fz5IWvmFcW2Ma8/TL3i27KAu5weY6wQ0DqyJ++g9BhpAy7XvWmiCMJcGI9Y4gtHkbSvlNC1KX0kaKX5NYagA3mdayB1zX1jXnLdv3+SoLCaJ3g6AUpr2nv5rTlIcHJmz3ira+8VGc6DMuAr5uNdf60cgFpVlbRXpDQ/d3CMCEgD+hzqUkeHe4GM7/NwLO2AWkajrGsB62o35o/IHVEEu837bVQcgCjG1+e1tiFM90XhKjZ70XPFzMClD6hNaAQA+Z5r4keyMxjXLft4TO6gIPwBAVJRgBGC7ZVSJ5tWrhDFGYIxSYEydLx1DZBGm2QMZDS7qqmEaW9tohLNSgag8vacE/jfawUx6huQx7ds3doAXi4GdYxJAehiqJZVU9AjEiKlXojk+3s3AJUXYjYqzZ+6EgWOgAMlFmBw+aLqCjnk21jrH5dvvNTuoLJbe6ykBokFo8vN1HyqKN08+R0Zs2++RaNDtkjFyLKVp7t5oMv35KC+iUeqzd6bnCwD+V7pbRTDXUXpM+/a9sEM1g1wkVl0DRImjRTmyTUUdvKx2Wtu3aNknPl0mRYGnPzBiSnTsZED1mSzc6d1KAp5GOkhPtm+lz6fzt0/sUD0sqgc7x6sAWIlrbzq3R1sFKB/GmxxQHyV0Ljr667Zvf3pk7BsvzIA7leYQN0QYAV422lVH2rfW4+n87Rs7h5eLFxhjARJJTRTfIW0GdZw2S3QpP9xcTZshXsSSxTaZFI+RIpzBm+v5SRvN15fXFQD+VrrYUTDnjfFxxr59UuGLF4sBuQ2QKJKoL1nXPlwblIwWCQVsDeq6omNryig/esWTMPLTKtkumoNJVOkLAD5VmhWBfC+l13H59k3FFy8Wr8BuAEoi9MvaToZTgGLdN7i6ek5tTwItFq9Ka6JfkkdDrBtyk5QUvjcA3hS6QCFgCAh+3b6/Xe+XVhlRFj3ADKDAKdp86zEENOYI9kT7i89tbESwBW/M31k8b28A'} ];}); define("audio!VEGAS/trumpet", function(){ return [{base64:'data:audio/mpeg;base64,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'}, {base64:'data:audio/ogg;base64,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'} ];}); define("audio!VEGAS/cheer", function(){ return [{base64:'data:audio/mpeg;base64,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'}, {base64:'data:audio/ogg;base64,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'} ];}); // Copyright 2013-2015, University of Colorado Boulder /** * Audio player for the various sounds that are commonly used in PhET games. * * @author John Blanco */ define( 'VEGAS/GameAudioPlayer',['require','PHET_CORE/inherit','VIBE/Sound','VEGAS/vegas','audio!VEGAS/ding','audio!VEGAS/boing','audio!VEGAS/trumpet','audio!VEGAS/cheer'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Sound = require( 'VIBE/Sound' ); var vegas = require( 'VEGAS/vegas' ); // audio var dingSound = require( 'audio!VEGAS/ding' ); var boingSound = require( 'audio!VEGAS/boing' ); var trumpetSound = require( 'audio!VEGAS/trumpet' ); var cheerSound = require( 'audio!VEGAS/cheer' ); // constants var CORRECT_ANSWER = new Sound( dingSound ); var WRONG_ANSWER = new Sound( boingSound ); var IMPERFECT_SCORE = new Sound( trumpetSound ); var PERFECT_SCORE = new Sound( cheerSound ); /** * @param {Property.} soundEnabledProperty * @constructor */ function GameAudioPlayer( soundEnabledProperty ) { this.soundEnabledProperty = soundEnabledProperty; // @private } vegas.register( 'GameAudioPlayer', GameAudioPlayer ); return inherit( Object, GameAudioPlayer, { /** * play the sound that indicates a correct answer * @public */ correctAnswer: function() { if ( this.soundEnabledProperty.value ) { CORRECT_ANSWER.play(); } }, /** * play the sound that indicates an incorrect answer * @public */ wrongAnswer: function() { if ( this.soundEnabledProperty.value ) { WRONG_ANSWER.play(); } }, /** * play the sound that indicates that the user completed the game but didn't earn any points * @public */ gameOverZeroScore: function() { if ( this.soundEnabledProperty.value ) { WRONG_ANSWER.play(); } }, /** * play the sound that indicates that the user finished the game and got some correct and some incorrect answers * @public */ gameOverImperfectScore: function() { if ( this.soundEnabledProperty.value ) { IMPERFECT_SCORE.play(); } }, /** * play the sound that indicates that the user finished the game and got a perfact score * @public */ gameOverPerfectScore: function() { if ( this.soundEnabledProperty.value ) { PERFECT_SCORE.play(); } } } ); } ); define("string!VEGAS/pattern.0hours.1minutes.2seconds",function(){return window.phet.chipper.strings.get("VEGAS/pattern.0hours.1minutes.2seconds");}); define("string!VEGAS/pattern.0minutes.1seconds",function(){return window.phet.chipper.strings.get("VEGAS/pattern.0minutes.1seconds");}); // Copyright 2013-2015, University of Colorado Boulder /** * Game timer, keeps track of the elapsed time in the game using "wall clock" time. The frame rate of this clock is * sufficient for displaying a game timer in "seconds", but not for driving smooth animation. * * @author Chris Malley (PixelZoom, Inc.) */ define( 'VEGAS/GameTimer',['require','PHET_CORE/inherit','AXON/Property','PHETCOMMON/util/StringUtils','PHET_CORE/Timer','VEGAS/vegas','string!VEGAS/pattern.0hours.1minutes.2seconds','string!VEGAS/pattern.0minutes.1seconds'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Property = require( 'AXON/Property' ); var StringUtils = require( 'PHETCOMMON/util/StringUtils' ); var Timer = require( 'PHET_CORE/Timer' ); var vegas = require( 'VEGAS/vegas' ); // strings var pattern0Hours1Minutes2SecondsString = require( 'string!VEGAS/pattern.0hours.1minutes.2seconds' ); var pattern0Minutes1SecondsString = require( 'string!VEGAS/pattern.0minutes.1seconds' ); /** * @constructor */ function GameTimer() { // @public seconds since the timer was started this.elapsedTimeProperty = new Property( 0 ); this.isRunningProperty = new Property( false ); this.intervalId = null; // @private } vegas.register( 'GameTimer', GameTimer ); return inherit( Object, GameTimer, { // @public reset: function(){ this.elapsedTimeProperty.reset(); this.isRunningProperty.reset(); }, /** * Starts the timer. This is a no-op if the timer is already running. * @public */ start: function() { if ( !this.isRunningProperty.value ) { var self = this; self.elapsedTimeProperty.value = 0; self.intervalId = Timer.setInterval( function() { //TODO will this be accurate, or should we compute elapsed time and potentially skip some time values? self.elapsedTimeProperty.value = self.elapsedTimeProperty.value + 1; }, 1000 ); // fire once per second self.isRunningProperty.value = true; } }, /** * Stops the timer. This is a no-op if the timer is already stopped. * @public */ stop: function() { if ( this.isRunningProperty.value ) { Timer.clearInterval( this.intervalId ); this.intervalId = null; this.isRunningProperty.value = false; } }, /** * Convenience function for restarting the timer. * @public */ restart: function() { this.stop(); this.start(); } }, { /** * Formats a value representing seconds into H:MM:SS (localized). * @param {number} time in seconds * @returns {string} * @public * @static */ formatTime: function( time ) { var hours = Math.floor( time / 3600 ); var minutes = Math.floor( (time - (hours * 3600)) / 60 ); var seconds = Math.floor( time - (hours * 3600) - (minutes * 60) ); var minutesString = ( minutes > 9 || hours === 0 ) ? minutes : ( '0' + minutes ); var secondsString = ( seconds > 9 ) ? seconds : ( '0' + seconds ); if ( hours > 0 ) { return StringUtils.format( pattern0Hours1Minutes2SecondsString, hours, minutesString, secondsString ); } else { return StringUtils.format( pattern0Minutes1SecondsString, minutesString, secondsString ); } } } ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * Indicates a user's progress on a level by illuminating stars. * * @author John Blanco * @author Sam Reid */ define( 'VEGAS/ProgressIndicator',['require','PHET_CORE/inherit','SCENERY_PHET/StarNode','SCENERY/nodes/HBox','VEGAS/vegas'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var StarNode = require( 'SCENERY_PHET/StarNode' ); var HBox = require( 'SCENERY/nodes/HBox' ); var vegas = require( 'VEGAS/vegas' ); /** * @param {number} numStars * @param {Property.} scoreProperty * @param {number} perfectScore * @param {Object} [options] * @constructor */ function ProgressIndicator( numStars, scoreProperty, perfectScore, options ) { options = _.extend( { starOuterRadius: 10, starInnerRadius: 5, starFilledLineWidth: 1.5, starEmptyLineWidth: 1.5 }, options ); HBox.call( this, { spacing: 3, children: [] } ); var self = this; // Update visibility of filled and half-filled stars based on score. // TODO: Could be rewritten to use deltas if it needs to animate scoreProperty.link( function( score ) { assert && assert( score <= perfectScore ); var children = []; var proportion = score / perfectScore; var numFilledStars = Math.floor( proportion * numStars ); var starOptions = { outerRadius: options.starOuterRadius, innerRadius: options.starInnerRadius, filledLineWidth: options.starFilledLineWidth, emptyLineWidth: options.starEmptyLineWidth }; for ( var i = 0; i < numFilledStars; i++ ) { children.push( new StarNode( _.extend( { value: 1 }, starOptions ) ) ); } var remainder = proportion * numStars - numFilledStars; if ( remainder > 1E-6 ) { children.push( new StarNode( _.extend( { value: remainder }, starOptions ) ) ); } var numEmptyStars = numStars - children.length; for ( i = 0; i < numEmptyStars; i++ ) { children.push( new StarNode( _.extend( { value: 0 }, starOptions ) ) ); } self.children = children; } ); this.mutate( options ); } vegas.register( 'ProgressIndicator', ProgressIndicator ); return inherit( HBox, ProgressIndicator ); } ); // Copyright 2014-2015, University of Colorado Boulder /** * Button for selecting a game level. * Also depicts the progress made on each level. * * @author John Blanco * @author Chris Malley */ define( 'VEGAS/LevelSelectionButton',['require','DOT/Dimension2','VEGAS/GameTimer','PHET_CORE/inherit','SCENERY/nodes/Node','SCENERY_PHET/PhetFont','VEGAS/ProgressIndicator','SCENERY/nodes/Rectangle','SUN/buttons/RectangularPushButton','SCENERY/nodes/Text','VEGAS/vegas','TANDEM/Tandem'],function( require ) { 'use strict'; // modules var Dimension2 = require( 'DOT/Dimension2' ); var GameTimer = require( 'VEGAS/GameTimer' ); var inherit = require( 'PHET_CORE/inherit' ); var Node = require( 'SCENERY/nodes/Node' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var ProgressIndicator = require( 'VEGAS/ProgressIndicator' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' ); var Text = require( 'SCENERY/nodes/Text' ); var vegas = require( 'VEGAS/vegas' ); var Tandem = require( 'TANDEM/Tandem' ); // constants var SCALING_TOLERANCE = 1E-4; // Empirically chosen as something the human eye is unlikely to notice. /** * @param {Node} icon Scenery node that appears on the button above the progress indicator, scaled to fit * @param {number} numStars Number of stars to show in the progress indicator at the bottom of the button * @param {function} fireFunction Called when the button fires * @param {Property.} scoreProperty * @param {number} perfectScore * @param {Object} [options] * @constructor */ function LevelSelectionButton( icon, numStars, fireFunction, scoreProperty, perfectScore, options ) { assert && assert( icon instanceof Node ); assert && assert( typeof numStars === 'number' ); Node.call( this ); options = _.extend( { // button size and appearance buttonWidth: 150, buttonHeight: 150, cornerRadius: 10, baseColor: 'rgb( 242, 255, 204 )', buttonXMargin: 10, buttonYMargin: 10, // progress indicator (stars) progressIndicatorProportion: 0.2, // percentage of the button height occupied by the progress indicator, (0,0.5] progressIndicatorMinXMargin: 10, progressIndicatorMinYMargin: 5, iconToProgressIndicatorYSpace: 10, // best time (optional) bestTimeProperty: null, // null if no best time || {Property.} best time in seconds bestTimeVisibleProperty: null, // null || Property.} controls visibility of best time bestTimeFill: 'black', bestTimeFont: new PhetFont( 24 ), bestTimeYSpacing: 10, // vertical space between drop shadow and best time // Tandem tandem: Tandem.tandemRequired() }, options ); assert && assert( options.progressIndicatorProportion > 0 && options.progressIndicatorProportion <= 0.5, 'progressIndicatorProportion value out of range' ); var maxContentWidth = options.buttonWidth - 2 * options.buttonXMargin; // Progress indicator (stars), scaled to fit var progressIndicatorBackground = new Rectangle( 0, 0, maxContentWidth, options.buttonHeight * options.progressIndicatorProportion, options.cornerRadius, options.cornerRadius, { fill: 'white', stroke: 'black', lineWidth: 1, pickable: false } ); var progressIndicator = new ProgressIndicator( numStars, scoreProperty, perfectScore, { pickable: false, starDiameter: options.buttonWidth / ( numStars + 1 ) } ); progressIndicator.scale( Math.min( ( progressIndicatorBackground.width - 2 * options.progressIndicatorMinXMargin ) / progressIndicator.width, ( progressIndicatorBackground.height - 2 * options.progressIndicatorMinYMargin ) / progressIndicator.height ) ); // Icon, scaled and padded to fit and to make the button size correct. var iconSize = new Dimension2( maxContentWidth, options.buttonHeight - progressIndicatorBackground.height - 2 * options.buttonYMargin - options.iconToProgressIndicatorYSpace ); var adjustedIcon = LevelSelectionButton.createSizedImageNode( icon, iconSize ); adjustedIcon.pickable = false; // TODO: is this needed? // Assemble the content. var contentNode = new Node(); if ( progressIndicatorBackground.width > adjustedIcon.width ) { adjustedIcon.centerX = progressIndicatorBackground.centerX; } else { progressIndicatorBackground.centerX = adjustedIcon.centerX; } progressIndicatorBackground.top = adjustedIcon.bottom + options.iconToProgressIndicatorYSpace; progressIndicator.center = progressIndicatorBackground.center; contentNode.addChild( adjustedIcon ); contentNode.addChild( progressIndicatorBackground ); contentNode.addChild( progressIndicator ); // Create the button var buttonOptions = { content: contentNode, xMargin: options.buttonXMargin, yMargin: options.buttonYMargin, baseColor: options.baseColor, cornerRadius: options.cornerRadius, listener: fireFunction, // TODO: if LevelSelectionButton changes to inheritance, this will have to change, see https://github.com/phetsims/vegas/issues/56 tandem: options.tandem.createTandem( 'button' ) }; var button = new RectangularPushButton( buttonOptions ); this.addChild( button ); // Best time (optional), centered below the button, does not move when button is pressed if ( options.bestTimeProperty ) { var bestTimeNode = new Text( '', { font: options.bestTimeFont, fill: options.bestTimeFill } ); this.addChild( bestTimeNode ); options.bestTimeProperty.link( function( bestTime ) { bestTimeNode.text = ( bestTime ? GameTimer.formatTime( bestTime ) : '' ); bestTimeNode.centerX = button.centerX; bestTimeNode.top = button.bottom + options.bestTimeYSpacing; } ); if ( options.bestTimeVisibleProperty ) { options.bestTimeVisibleProperty.linkAttribute( bestTimeNode, 'visible' ); } } // Pass options to parent class this.mutate( options ); } vegas.register( 'LevelSelectionButton', LevelSelectionButton ); return inherit( Node, LevelSelectionButton, {}, { /** * Creates a new the same dimensions as size with the specified icon. The icon will be scaled to fit, and a * background with the specified size may be added to ensure the bounds of the returned node are correct. * @public * * @param {Node} icon * @param {Dimension2} size * @returns {Node} */ createSizedImageNode: function( icon, size ) { icon.scale( Math.min( size.width / icon.bounds.width, size.height / icon.bounds.height ) ); if ( Math.abs( icon.bounds.width - size.width ) < SCALING_TOLERANCE && Math.abs( icon.bounds.height - size.height ) < SCALING_TOLERANCE ) { // The aspect ratio of the icon matched that of the specified size, so no padding is necessary. return icon; } // else padding is needed in either the horizontal or vertical direction. var background = Rectangle.dimension( size, { fill: null } ); icon.center = background.center; background.addChild( icon ); return background; } } ); } ); // Copyright 2014-2015, University of Colorado Boulder /** * A derived property that maps sticky toggle button model states to the values needed by the button view. */ define( 'SUN/buttons/ToggleButtonInteractionStateProperty',['require','AXON/DerivedProperty','PHET_CORE/inherit','SUN/sun'],function( require ) { 'use strict'; // modules var DerivedProperty = require( 'AXON/DerivedProperty' ); var inherit = require( 'PHET_CORE/inherit' ); var sun = require( 'SUN/sun' ); /** * @param {ButtonModel} buttonModel * @constructor */ function ToggleButtonInteractionStateProperty( buttonModel ) { DerivedProperty.call( this, [ buttonModel.overProperty, buttonModel.downProperty, buttonModel.enabledProperty ], function( over, down, enabled ) { return !enabled ? 'disabled' : over && !(down ) ? 'over' : down ? 'pressed' : 'idle'; } ); } sun.register( 'ToggleButtonInteractionStateProperty', ToggleButtonInteractionStateProperty ); return inherit( DerivedProperty, ToggleButtonInteractionStateProperty ); } ); // Copyright 2014-2015, University of Colorado Boulder /** * Model for a toggle button that changes value on each "up" event when the button is released. * * @author Sam Reid (PhET Interactive Simulations) * @author John Blanco (PhET Interactive Simulations) */ define( 'SUN/buttons/ToggleButtonModel',['require','SUN/buttons/ButtonModel','AXON/Emitter','PHET_CORE/inherit','SUN/sun'],function( require ) { 'use strict'; // modules var ButtonModel = require( 'SUN/buttons/ButtonModel' ); var Emitter = require( 'AXON/Emitter' ); var inherit = require( 'PHET_CORE/inherit' ); var sun = require( 'SUN/sun' ); /** * @param {Object} valueOff - value when the button is in the off state * @param {Object} valueOn - value when the button is in the on state * @param {Property} property - axon Property that can be either valueOff or valueOn. * @constructor */ function ToggleButtonModel( valueOff, valueOn, property ) { var self = this; // @private this.valueOff = valueOff; this.valueOn = valueOn; this.valueProperty = property; ButtonModel.call( this ); // phet-io support this.startedCallbacksForToggledEmitter = new Emitter(); this.endedCallbacksForToggledEmitter = new Emitter(); // Behaves like a push button (with fireOnDown:false), but toggles its state when the button is released. var downListener = function( down ) { if ( self.enabledProperty.get() && self.overProperty.get() ) { if ( !down ) { self.toggle(); } } }; this.downProperty.link( downListener ); // @private // @private - dispose items specific to this instance this.disposeToggleButtonModel = function() { self.downProperty.unlink( downListener ); }; } sun.register( 'ToggleButtonModel', ToggleButtonModel ); return inherit( ButtonModel, ToggleButtonModel, { // @public dispose: function() { this.disposeToggleButtonModel(); ButtonModel.prototype.dispose.call( this ); }, // @public toggle: function() { assert && assert( this.valueProperty.value === this.valueOff || this.valueProperty.value === this.valueOn ); var oldValue = this.valueProperty.value; var newValue = this.valueProperty.value === this.valueOff ? this.valueOn : this.valueOff; this.startedCallbacksForToggledEmitter.emit2( oldValue, newValue ); this.valueProperty.value = newValue; this.endedCallbacksForToggledEmitter.emit(); } } ); } ); // Copyright 2016, University of Colorado Boulder /** * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) */ define( 'SUN/buttons/TToggleButton',['require','ifphetio!PHET_IO/assertions/assertInstanceOfTypes','ifphetio!PHET_IO/phetioInherit','SUN/sun','SCENERY/nodes/TNode','ifphetio!PHET_IO/events/toEventOnEmit'],function( require ) { 'use strict'; // modules var assertInstanceOfTypes = require( 'ifphetio!PHET_IO/assertions/assertInstanceOfTypes' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var sun = require( 'SUN/sun' ); var TNode = require( 'SCENERY/nodes/TNode' ); var toEventOnEmit = require( 'ifphetio!PHET_IO/events/toEventOnEmit' ); /** * Wrapper type for phet/sun's ToggleButton class. * @param {function} phetioValueType - phet-io type wrapper like TString, TNumber, etc. If loaded by phet (not phet-io) * it will be the function returned by the 'ifphetio!' plugin. * @constructor */ function TToggleButton( phetioValueType ) { var validateTandems = phet.phetio && phet.phetio.queryParameters.phetioValidateTandems; var TToggleButtonImpl = function TToggleButtonImpl( toggleButton, phetioID ) { assert && assert( !!phetioValueType || !validateTandems, 'phetioValueType must be specified' ); TNode.call( this, toggleButton, phetioID ); assertInstanceOfTypes( toggleButton, [ phet.sun.ToggleButton, phet.sceneryPhet.PlayPauseButton, phet.sun.RoundStickyToggleButton, phet.sun.RectangularToggleButton, phet.sun.RoundMomentaryButton, phet.sun.BooleanRoundToggleButton ] ); var model = toggleButton.toggleButtonModel || toggleButton.buttonModel; // Handle BooleanRoundStickyToggleButton too // Both StickyToggleButtonModel and ToggleButtonModel send the args in this order: oldValue, newValue toEventOnEmit( model.startedCallbacksForToggledEmitter, model.endedCallbacksForToggledEmitter, 'user', phetioID, this.constructor, 'toggled', function( oldValue, newValue ) { return { oldValue: phetioValueType.toStateObject( oldValue ), newValue: phetioValueType.toStateObject( newValue ) }; } ); }; return phetioInherit( TNode, 'TToggleButton', TToggleButtonImpl, {}, { documentation: 'A button that toggles state (in/out) when pressed', events: [ 'toggled' ] } ); } sun.register( 'TToggleButton', TToggleButton ); return TToggleButton; } ); // Copyright 2014-2015, University of Colorado Boulder /** * A rectangular toggle button that switches the value of a property between 2 values. * * @author John Blanco (PhET Interactive Simulations) * @author Sam Reid (PhET Interactive Simulations) */ define( 'SUN/buttons/RectangularToggleButton',['require','PHET_CORE/inherit','SUN/buttons/RectangularButtonView','SUN/sun','SUN/buttons/ToggleButtonInteractionStateProperty','SUN/buttons/ToggleButtonModel','TANDEM/Tandem','SUN/buttons/TToggleButton'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var RectangularButtonView = require( 'SUN/buttons/RectangularButtonView' ); var sun = require( 'SUN/sun' ); var ToggleButtonInteractionStateProperty = require( 'SUN/buttons/ToggleButtonInteractionStateProperty' ); var ToggleButtonModel = require( 'SUN/buttons/ToggleButtonModel' ); var Tandem = require( 'TANDEM/Tandem' ); var TToggleButton = require( 'SUN/buttons/TToggleButton' ); /** * @param {Object} valueOff - value when the button is in the off state * @param {Object} valueOn - value when the button is in the on state * @param {Property} property - axon Property that can be either valueOff or valueOn * @param {Object} [options] * @constructor */ function RectangularToggleButton( valueOff, valueOn, property, options ) { options = _.extend( { tandem: Tandem.tandemRequired(), phetioType: TToggleButton( property.phetioValueType ) }, options ); // @public (phet-io) this.toggleButtonModel = new ToggleButtonModel( valueOff, valueOn, property, options ); RectangularButtonView.call( this, this.toggleButtonModel, new ToggleButtonInteractionStateProperty( this.toggleButtonModel ), options ); } sun.register( 'RectangularToggleButton', RectangularToggleButton ); return inherit( RectangularButtonView, RectangularToggleButton ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * Shows one node if the property is true or another node if the property is false. * Used to indicate boolean state. * * @author Sam Reid (PhET Interactive Simulations) * @author Chris Malley (PixelZoom, Inc.) */ define( 'SUN/ToggleNode',['require','PHET_CORE/inherit','TANDEM/Tandem','SCENERY/nodes/Node','SUN/sun'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Tandem = require( 'TANDEM/Tandem' ); var Node = require( 'SCENERY/nodes/Node' ); var sun = require( 'SUN/sun' ); /** * @param {Node} trueNode * @param {Node} falseNode * @param {Property.} booleanProperty * @param {Object} [options] * @constructor */ function ToggleNode( trueNode, falseNode, booleanProperty, options ) { options = _.extend( { tandem: Tandem.tandemRequired() }, options ); Node.call( this ); // align centers of the nodes, see https://github.com/phetsims/sun/issues/272 falseNode.center = trueNode.center; this.addChild( falseNode ); this.addChild( trueNode ); booleanProperty.link( function( value ) { trueNode.setVisible( value ); falseNode.setVisible( !value ); // a11y - toggle visibility of accessible content for assistive technologies trueNode.setAccessibleContentDisplayed( value ); falseNode.setAccessibleContentDisplayed( !value ); } ); this.mutate( options ); } sun.register( 'ToggleNode', ToggleNode ); return inherit( Node, ToggleNode ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * This toggle button uses a boolean property and a trueNode and falseNode to display its content. */ define( 'SUN/buttons/BooleanRectangularToggleButton',['require','PHET_CORE/inherit','SUN/buttons/RectangularToggleButton','SUN/sun','SUN/ToggleNode','TANDEM/Tandem'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var RectangularToggleButton = require( 'SUN/buttons/RectangularToggleButton' ); var sun = require( 'SUN/sun' ); var ToggleNode = require( 'SUN/ToggleNode' ); var Tandem = require( 'TANDEM/Tandem' ); /** * @param {Node} trueNode * @param {Node} falseNode * @param booleanProperty * @param {Object} [options] * @constructor */ function BooleanRectangularToggleButton( trueNode, falseNode, booleanProperty, options ) { options = _.extend( { tandem: Tandem.tandemRequired() }, options ); //TODO ToggleNode links to booleanProperty, must be cleaned up in dispose assert && assert( !options.content, 'options.content cannot be set' ); options.content = new ToggleNode( trueNode, falseNode, booleanProperty, { tandem: options.tandem.createTandem( 'toggleNode' ) } ); RectangularToggleButton.call( this, false, true, booleanProperty, options ); } sun.register( 'BooleanRectangularToggleButton', BooleanRectangularToggleButton ); return inherit( RectangularToggleButton, BooleanRectangularToggleButton ); } ); // Copyright 2014-2016, University of Colorado Boulder /** * Button for toggling sound on and off. * * @author John Blanco * @author Chris Malley (PixelZoom, Inc.) * @author Sam Reid */ define( 'SCENERY_PHET/buttons/SoundToggleButton',['require','SUN/FontAwesomeNode','PHET_CORE/inherit','SCENERY/nodes/Node','SCENERY/nodes/Path','KITE/Shape','SCENERY_PHET/SceneryPhetA11yStrings','SUN/buttons/BooleanRectangularToggleButton','SCENERY_PHET/sceneryPhet','SCENERY_PHET/accessibility/AriaHerald','SCENERY_PHET/PhetColorScheme'],function( require ) { 'use strict'; // modules var FontAwesomeNode = require( 'SUN/FontAwesomeNode' ); var inherit = require( 'PHET_CORE/inherit' ); var Node = require( 'SCENERY/nodes/Node' ); var Path = require( 'SCENERY/nodes/Path' ); var Shape = require( 'KITE/Shape' ); var SceneryPhetA11yStrings = require( 'SCENERY_PHET/SceneryPhetA11yStrings' ); var BooleanRectangularToggleButton = require( 'SUN/buttons/BooleanRectangularToggleButton' ); var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' ); var AriaHerald = require( 'SCENERY_PHET/accessibility/AriaHerald' ); var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' ); // constants var WIDTH = 45; var HEIGHT = 45; var MARGIN = 4; var X_WIDTH = WIDTH * 0.25; // Empirically determined. /** * * @param {Property.} property * @param {Object} [options] * @constructor */ function SoundToggleButton( property, options ) { // Tandem.indicateUninstrumentedCode(); // see https://github.com/phetsims/phet-io/issues/986 // Tandem required by the parent class, further customization is probably not necessary var soundOffNode = new Node(); var soundOnNode = new FontAwesomeNode( 'volume_up' ); var contentScale = ( WIDTH - ( 2 * MARGIN ) ) / soundOnNode.width; soundOnNode.scale( contentScale ); soundOffNode.addChild( new FontAwesomeNode( 'volume_off', { scale: contentScale } ) ); var soundOffX = new Path( new Shape().moveTo( 0, 0 ).lineTo( X_WIDTH, X_WIDTH ).moveTo( 0, X_WIDTH ).lineTo( X_WIDTH, 0 ), { stroke: 'black', lineWidth: 3, left: soundOffNode.width + 5, centerY: soundOffNode.centerY } ); soundOffNode.addChild( soundOffX ); BooleanRectangularToggleButton.call( this, soundOnNode, soundOffNode, property, _.extend( { baseColor: PhetColorScheme.PHET_LOGO_YELLOW, minWidth: WIDTH, minHeight: HEIGHT, xMargin: MARGIN, yMargin: MARGIN, // a11y tagName: 'button', accessibleLabel: SceneryPhetA11yStrings.soundToggleLabelString }, options ) ); // dilate the focus highlight bounds to give the button some space this.focusHighlight = new Shape.bounds( this.localBounds.dilated( 5 ) ); var self = this; // accessibility input listener - must be removed in dispose var clickListener = this.addAccessibleInputListener( { click: function( event ) { self.buttonModel.toggle(); } } ); // accessible attribute lets user know when the toggle is pressed, linked lazily so that an alert isn't triggered // on construction and must be unlinked in dispose var pressedListener = function( value ) { self.setAccessibleAttribute( 'aria-pressed', !value ); var alertString = value ? SceneryPhetA11yStrings.simSoundOnString : SceneryPhetA11yStrings.simSoundOffString; AriaHerald.announcePolite( alertString ); }; property.lazyLink( pressedListener ); self.setAccessibleAttribute( 'aria-pressed', !property.get() ); // @private - make eligible for garbage collection this.disposeSoundToggleButton = function() { self.removeAccessibleInputListener( clickListener ); property.unlink( pressedListener ); }; } sceneryPhet.register( 'SoundToggleButton', SoundToggleButton ); return inherit( BooleanRectangularToggleButton, SoundToggleButton, { /** * Make eligible for garbage collection. * @public */ dispose: function() { this.disposeSoundToggleButton(); BooleanRectangularToggleButton.prototype.dispose.call( this ); } } ); } ); define("string!EXPRESSION_EXCHANGE/chooseYourLevel",function(){return window.phet.chipper.strings.get("EXPRESSION_EXCHANGE/chooseYourLevel");}); // Copyright 2016, University of Colorado Boulder /** * A node that fills most of the screen and allows the user to select the game level that they wish to play. * * @author John Blanco */ define( 'EXPRESSION_EXCHANGE/game/view/LevelSelectionNode',['require','EXPRESSION_EXCHANGE/expressionExchange','EXPRESSION_EXCHANGE/game/model/EEGameModel','EXPRESSION_EXCHANGE/common/EESharedConstants','PHET_CORE/inherit','VEGAS/LevelSelectionButton','SCENERY/nodes/Node','SCENERY_PHET/PhetFont','AXON/Property','SCENERY_PHET/buttons/ResetAllButton','SCENERY_PHET/buttons/SoundToggleButton','SCENERY/nodes/Text','DOT/Vector2','string!EXPRESSION_EXCHANGE/chooseYourLevel'],function( require ) { 'use strict'; // modules var expressionExchange = require( 'EXPRESSION_EXCHANGE/expressionExchange' ); var EEGameModel = require( 'EXPRESSION_EXCHANGE/game/model/EEGameModel' ); var EESharedConstants = require( 'EXPRESSION_EXCHANGE/common/EESharedConstants' ); var inherit = require( 'PHET_CORE/inherit' ); var LevelSelectionButton = require( 'VEGAS/LevelSelectionButton' ); var Node = require( 'SCENERY/nodes/Node' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var Property = require( 'AXON/Property' ); var ResetAllButton = require( 'SCENERY_PHET/buttons/ResetAllButton' ); var SoundToggleButton = require( 'SCENERY_PHET/buttons/SoundToggleButton' ); var Text = require( 'SCENERY/nodes/Text' ); var Vector2 = require( 'DOT/Vector2' ); // strings var chooseYourLevelString = require( 'string!EXPRESSION_EXCHANGE/chooseYourLevel' ); // constants var CONTROL_BUTTON_TOUCH_AREA_DILATION = 4; /** * @param {Function} startLevelFunction - Function used to initiate a game level, will be called with a zero-based * index value. * @param {Function} resetFunction - Function to reset game and scores. * @param {Property.} soundEnabledProperty * @param {Array.} iconNodes - Set of iconNodes to use on the buttons, sizes should be the same, length of array * must match number of levels. * @param {Array.>} scores - Current scores, used to decide which stars to illuminate on the level * start buttons, length must match number of levels. * @param {Object} [options] - See code below for options and default values. * @constructor */ function LevelSelectionNode( startLevelFunction, resetFunction, soundEnabledProperty, iconNodes, scores, options ) { Node.call( this ); options = _.extend( { // defaults numLevels: EEGameModel.NUMBER_OF_LEVELS, titleString: chooseYourLevelString, maxTitleWidth: 500, numStarsOnButtons: EEGameModel.CHALLENGES_PER_LEVEL, perfectScore: EEGameModel.MAX_SCORE_PER_LEVEL, buttonBackgroundColor: '#EDA891', numButtonRows: 2, controlsInset: 10, layoutBoundsProperty: new Property( EESharedConstants.LAYOUT_BOUNDS ), buttonScale: 0.8 }, options ); // Verify parameters assert && assert( iconNodes.length === options.numLevels && scores.length === options.numLevels, 'Number of game levels doesn\'t match length of provided arrays' ); // title var title = new Text( options.titleString, { font: new PhetFont( 30 ), maxWidth: options.maxTitleWidth } ); this.addChild( title ); // add the buttons function createLevelStartFunction( level ) { return function() { startLevelFunction( level ); }; } var buttons = new Array( options.numLevels ); for ( var i = 0; i < options.numLevels; i++ ) { buttons[ i ] = new LevelSelectionButton( iconNodes[ i ], options.numStarsOnButtons, createLevelStartFunction( i ), scores[ i ], options.perfectScore, { baseColor: options.buttonBackgroundColor, scale: options.buttonScale } ); this.addChild( buttons[ i ] ); } // sound on/off button var soundToggleButton = new SoundToggleButton( soundEnabledProperty, { touchAreaXDilation: CONTROL_BUTTON_TOUCH_AREA_DILATION, touchAreaYDilation: CONTROL_BUTTON_TOUCH_AREA_DILATION } ); this.addChild( soundToggleButton ); // Reset button. var resetButton = new ResetAllButton( { listener: resetFunction, radius: EESharedConstants.RESET_ALL_BUTTON_RADIUS, touchAreaDilation: EESharedConstants.RESET_ALL_BUTTON_TOUCH_AREA_DILATION } ); this.addChild( resetButton ); // Layout var numColumns = options.numLevels / options.numButtonRows; var buttonSpacingX = buttons[ 0 ].width * 1.2; // Note: Assumes all buttons are the same size. var buttonSpacingY = buttons[ 0 ].height * 1.2; // Note: Assumes all buttons are the same size. var initialLayoutBounds = options.layoutBoundsProperty.get(); var firstButtonOrigin = new Vector2( initialLayoutBounds.width / 2 - ( numColumns - 1 ) * buttonSpacingX / 2, initialLayoutBounds.height * 0.5 - ( ( options.numButtonRows - 1 ) * buttonSpacingY ) / 2 ); for ( var row = 0; row < options.numButtonRows; row++ ) { for ( var col = 0; col < numColumns; col++ ) { var buttonIndex = row * numColumns + col; buttons[ buttonIndex ].centerX = firstButtonOrigin.x + col * buttonSpacingX; buttons[ buttonIndex ].centerY = firstButtonOrigin.y + row * buttonSpacingY; } } title.centerX = initialLayoutBounds.width / 2; title.centerY = buttons[ 0 ].top / 2; resetButton.bottom = initialLayoutBounds.height - options.controlsInset; soundToggleButton.bottom = initialLayoutBounds.height - options.controlsInset; // have the reset and volume buttons have floating X positions options.layoutBoundsProperty.link( function( layoutBounds ) { resetButton.right = layoutBounds.maxX - options.controlsInset; soundToggleButton.left = layoutBounds.minX + options.controlsInset; } ); } expressionExchange.register( 'LevelSelectionNode', LevelSelectionNode ); return inherit( Node, LevelSelectionNode ); } ); // Copyright 2016, University of Colorado Boulder /** * main view for the Expression Exchange 'Game' screen * * @author John Blanco */ define( 'EXPRESSION_EXCHANGE/game/view/EEGameScreenView',['require','EXPRESSION_EXCHANGE/game/view/EEGameLevelIconFactory','EXPRESSION_EXCHANGE/game/view/EEGameLevelView','EXPRESSION_EXCHANGE/game/model/EEGameModel','EXPRESSION_EXCHANGE/common/EESharedConstants','EXPRESSION_EXCHANGE/expressionExchange','VEGAS/GameAudioPlayer','PHET_CORE/inherit','JOIST/ScreenView','EXPRESSION_EXCHANGE/game/view/LevelSelectionNode'],function( require ) { 'use strict'; // modules var EEGameLevelIconFactory = require( 'EXPRESSION_EXCHANGE/game/view/EEGameLevelIconFactory' ); var EEGameLevelView = require( 'EXPRESSION_EXCHANGE/game/view/EEGameLevelView' ); var EEGameModel = require( 'EXPRESSION_EXCHANGE/game/model/EEGameModel' ); var EESharedConstants = require( 'EXPRESSION_EXCHANGE/common/EESharedConstants' ); var expressionExchange = require( 'EXPRESSION_EXCHANGE/expressionExchange' ); var GameAudioPlayer = require( 'VEGAS/GameAudioPlayer' ); var inherit = require( 'PHET_CORE/inherit' ); var ScreenView = require( 'JOIST/ScreenView' ); var LevelSelectionNode = require( 'EXPRESSION_EXCHANGE/game/view/LevelSelectionNode' ); // constants var SCREEN_CHANGE_TIME = 1000; // milliseconds /** * @param {EEGameLevel} gameModel * @constructor */ function EEGameScreenView( gameModel ) { var self = this; ScreenView.call( this, { layoutBounds: EESharedConstants.LAYOUT_BOUNDS } ); // @private {EEGameModel} this.gameModel = gameModel; // create the audio play for the game sounds var gameAudioPlayer = new GameAudioPlayer( gameModel.soundEnabledProperty ); // consolidate the level scores into an array for the level selection node var levelScoreProperties = []; gameModel.gameLevels.forEach( function( gameLevelModel ) { levelScoreProperties.push( gameLevelModel.scoreProperty ); } ); // create the icons used on the level selection buttons var levelSelectionButtonIcons = []; _.times( EEGameModel.NUMBER_OF_LEVELS, function( level ) { levelSelectionButtonIcons.push( EEGameLevelIconFactory.createIcon( level ) ); } ); // add the node that allows the user to choose a game level to play var levelSelectionNode = new LevelSelectionNode( function( level ) { gameModel.selectLevel( level ); }, function() { gameModel.reset(); }, gameModel.soundEnabledProperty, levelSelectionButtonIcons, levelScoreProperties, { layoutBoundsProperty: this.visibleBoundsProperty, centerX: this.layoutBounds.centerX } ); this.addChild( levelSelectionNode ); // currently displayed level or level selection node var nodeInViewport = levelSelectionNode; // create the game level views and add them to the main game play node this.gameLevelViews = []; gameModel.gameLevels.forEach( function( levelModel ) { var gameLevelView = new EEGameLevelView( gameModel, levelModel, self.layoutBounds, self.visibleBoundsProperty, gameAudioPlayer ); gameLevelView.visible = false; // will be made visible when the corresponding level is activated self.gameLevelViews.push( gameLevelView ); self.addChild( gameLevelView ); } ); // hook up the animations for moving between level selection and game play gameModel.currentLevelProperty.lazyLink( function( newLevel, oldLevel ) { var slideDistance = self.visibleBoundsProperty.get().width; var incomingViewNode = newLevel === null ? levelSelectionNode : self.gameLevelViews[ newLevel.levelNumber ]; var outgoingViewNode = oldLevel === null ? levelSelectionNode : self.gameLevelViews[ oldLevel.levelNumber ]; var outgoingNodeDestinationX; var incomingNodeStartX; // prevent interaction during animation incomingViewNode.pickable = false; outgoingViewNode.pickable = false; // determine how the incoming and outgoing nodes should move if ( newLevel === null ) { // level selection screen is coming in, which is a left-to-right motion incomingNodeStartX = self.layoutBounds.minX - slideDistance; outgoingNodeDestinationX = self.layoutBounds.minX + slideDistance; } else { // a game level node is coming in, which is a right-to-left motion incomingNodeStartX = self.layoutBounds.minX + slideDistance; outgoingNodeDestinationX = self.layoutBounds.minX - slideDistance; } // move out the old node new TWEEN.Tween( { x: nodeInViewport.x } ) .to( { x: outgoingNodeDestinationX }, SCREEN_CHANGE_TIME ) .easing( TWEEN.Easing.Cubic.InOut ) .start( phet.joist.elapsedTime ) .onStart( function() { outgoingViewNode.inViewportProperty && outgoingViewNode.inViewportProperty.set( false ); } ) .onUpdate( function() { nodeInViewport.x = this.x; } ) .onComplete( function() { outgoingViewNode.visible = false; } ); // move in the new node incomingViewNode.x = incomingNodeStartX; incomingViewNode.visible = true; new TWEEN.Tween( { x: incomingViewNode.x } ) .to( { x: self.layoutBounds.minX }, SCREEN_CHANGE_TIME ) .easing( TWEEN.Easing.Cubic.InOut ) .start( phet.joist.elapsedTime ) .onUpdate( function() { incomingViewNode.x = this.x; } ) .onComplete( function() { nodeInViewport = incomingViewNode; nodeInViewport.pickable = true; nodeInViewport.inViewportProperty && nodeInViewport.inViewportProperty.set( true ); } ); } ); } expressionExchange.register( 'EEGameScreenView', EEGameScreenView ); return inherit( ScreenView, EEGameScreenView, { /** * step the view, needed for animations * @param {number} dt * @public */ step: function( dt ) { var currentLevel = this.gameModel.currentLevelProperty.get(); if ( currentLevel !== null ) { this.gameLevelViews[ currentLevel.levelNumber ].step( dt ); } } } ); } ); define("string!EXPRESSION_EXCHANGE/game",function(){return window.phet.chipper.strings.get("EXPRESSION_EXCHANGE/game");}); // Copyright 2016, University of Colorado Boulder /** * The 'Game' screen in the Expression Exchange simulation. Conforms to the contract specified in joist/Screen. * * @author John Blanco */ define( 'EXPRESSION_EXCHANGE/game/EEGameScreen',['require','EXPRESSION_EXCHANGE/game/view/EEGameIconNode','EXPRESSION_EXCHANGE/game/view/EEGameScreenView','EXPRESSION_EXCHANGE/game/model/EEGameModel','EXPRESSION_EXCHANGE/common/EESharedConstants','EXPRESSION_EXCHANGE/expressionExchange','PHET_CORE/inherit','JOIST/Screen','AXON/Property','string!EXPRESSION_EXCHANGE/game'],function( require ) { 'use strict'; // modules var EEGameIconNode = require( 'EXPRESSION_EXCHANGE/game/view/EEGameIconNode' ); var EEGameScreenView = require( 'EXPRESSION_EXCHANGE/game/view/EEGameScreenView' ); var EEGameModel = require( 'EXPRESSION_EXCHANGE/game/model/EEGameModel' ); var EESharedConstants = require( 'EXPRESSION_EXCHANGE/common/EESharedConstants' ); var expressionExchange = require( 'EXPRESSION_EXCHANGE/expressionExchange' ); var inherit = require( 'PHET_CORE/inherit' ); var Screen = require( 'JOIST/Screen' ); var Property = require( 'AXON/Property' ); // strings var gameString = require( 'string!EXPRESSION_EXCHANGE/game' ); /** * @constructor */ function EEGameScreen() { var options = { name: gameString, backgroundColorProperty: new Property( EESharedConstants.GAME_SCREEN_BACKGROUND_COLOR ), homeScreenIcon: new EEGameIconNode() }; Screen.call( this, function() { return new EEGameModel(); }, function( model ) { return new EEGameScreenView( model ); }, options ); } expressionExchange.register( 'EEGameScreen', EEGameScreen ); return inherit( Screen, EEGameScreen ); } ); // Copyright 2016, University of Colorado Boulder /** * icon node for 'Negatives' screen * * @author John Blanco */ define( 'EXPRESSION_EXCHANGE/negatives/view/EENegativesIconNode',['require','EXPRESSION_EXCHANGE/common/EESharedConstants','EXPRESSION_EXCHANGE/expressionExchange','SCENERY/nodes/HBox','PHET_CORE/inherit','SCENERY_PHET/MathSymbolFont','SCENERY_PHET/PhetFont','SCENERY/nodes/Rectangle','JOIST/Screen','SCENERY_PHET/RichText','SCENERY/nodes/Text'],function( require ) { 'use strict'; // modules var EESharedConstants = require( 'EXPRESSION_EXCHANGE/common/EESharedConstants' ); var expressionExchange = require( 'EXPRESSION_EXCHANGE/expressionExchange' ); var HBox = require( 'SCENERY/nodes/HBox' ); var inherit = require( 'PHET_CORE/inherit' ); var MathSymbolFont = require( 'SCENERY_PHET/MathSymbolFont' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var Screen = require( 'JOIST/Screen' ); var RichText = require( 'SCENERY_PHET/RichText' ); var Text = require( 'SCENERY/nodes/Text' ); // constants var ICON_SIZE = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE; var BACKGROUND_COLOR = EESharedConstants.NON_GAME_SCREENS_BACKGROUND_COLOR; var FONT_SIZE = 100; var MATH_FONT = new MathSymbolFont( FONT_SIZE ); var TEXT_FONT = new PhetFont( FONT_SIZE ); /** * @constructor */ function EENegativesIconNode() { // create the background Rectangle.call( this, 0, 0, ICON_SIZE.width, ICON_SIZE.height, { fill: BACKGROUND_COLOR } ); // create and add the equation node var equationNode = new HBox( { children: [ new Text( '3', { font: TEXT_FONT } ), new RichText( 'x2', { font: MATH_FONT, supScale: 0.5 } ), new Text( ' \u2212 ', { font: TEXT_FONT } ), new RichText( 'x2', { font: MATH_FONT, supScale: 0.5 } ) ], align: 'bottom' } ); equationNode.centerX = ICON_SIZE.width / 2; equationNode.centerY = ICON_SIZE.height * 0.45; this.addChild( equationNode ); } expressionExchange.register( 'EENegativesIconNode', EENegativesIconNode ); return inherit( Rectangle, EENegativesIconNode ); } ); define("string!EXPRESSION_EXCHANGE/negatives",function(){return window.phet.chipper.strings.get("EXPRESSION_EXCHANGE/negatives");}); // Copyright 2016, University of Colorado Boulder /** * The 'Explore' screen in the Expression Exchange simulation. Conforms to the contract specified in joist/Screen. * * @author John Blanco */ define( 'EXPRESSION_EXCHANGE/negatives/EENegativesScreen',['require','EXPRESSION_EXCHANGE/common/enum/AllowedRepresentations','EXPRESSION_EXCHANGE/common/enum/CoinTermCreatorSetID','EXPRESSION_EXCHANGE/common/EESharedConstants','EXPRESSION_EXCHANGE/negatives/view/EENegativesIconNode','EXPRESSION_EXCHANGE/expressionExchange','EXPRESSION_EXCHANGE/common/view/ExpressionExplorationScreenView','EXPRESSION_EXCHANGE/common/model/ExpressionManipulationModel','PHET_CORE/inherit','JOIST/Screen','AXON/Property','string!EXPRESSION_EXCHANGE/negatives'],function( require ) { 'use strict'; // modules var AllowedRepresentations = require( 'EXPRESSION_EXCHANGE/common/enum/AllowedRepresentations' ); var CoinTermCreatorSetID = require( 'EXPRESSION_EXCHANGE/common/enum/CoinTermCreatorSetID' ); var EESharedConstants = require( 'EXPRESSION_EXCHANGE/common/EESharedConstants' ); var EENegativesIconNode = require( 'EXPRESSION_EXCHANGE/negatives/view/EENegativesIconNode' ); var expressionExchange = require( 'EXPRESSION_EXCHANGE/expressionExchange' ); var ExpressionExplorationScreenView = require( 'EXPRESSION_EXCHANGE/common/view/ExpressionExplorationScreenView' ); var ExpressionManipulationModel = require( 'EXPRESSION_EXCHANGE/common/model/ExpressionManipulationModel' ); var inherit = require( 'PHET_CORE/inherit' ); var Screen = require( 'JOIST/Screen' ); var Property = require( 'AXON/Property' ); // strings var negativesString = require( 'string!EXPRESSION_EXCHANGE/negatives' ); /** * @constructor */ function EENegativesScreen() { var options = { name: negativesString, backgroundColorProperty: new Property( EESharedConstants.NON_GAME_SCREENS_BACKGROUND_COLOR ), homeScreenIcon: new EENegativesIconNode() }; Screen.call( this, function() { return new ExpressionManipulationModel( { allowedRepresentations: AllowedRepresentations.VARIABLES_ONLY } ); }, function( model ) { return new ExpressionExplorationScreenView( model, CoinTermCreatorSetID.VARIABLES ); }, options ); } expressionExchange.register( 'EENegativesScreen', EENegativesScreen ); return inherit( Screen, EENegativesScreen ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * Highlight node for navigation bar screen buttons, phet button, etc. * * @author Sam Reid (PhET Interactive Simulations) * @author Chris Malley (PixelZoom, Inc.) */ define( 'JOIST/HighlightNode',['require','SCENERY/util/Color','SCENERY/nodes/HBox','PHET_CORE/inherit','SCENERY/util/LinearGradient','SCENERY/nodes/Rectangle','JOIST/joist'],function( require ) { 'use strict'; // modules var Color = require( 'SCENERY/util/Color' ); var HBox = require( 'SCENERY/nodes/HBox' ); var inherit = require( 'PHET_CORE/inherit' ); var LinearGradient = require( 'SCENERY/util/LinearGradient' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var joist = require( 'JOIST/joist' ); /** * @param {number} width * @param {number} height * @param {Object} [options] * @constructor */ function HighlightNode( width, height, options ) { options = _.extend( { fill: 'white', highlightWidth: 1, pickable: false }, options ); var innerColor = options.fill; var outerColor = Color.toColor( innerColor ).withAlpha( 0 ); // transparent var barOptions = { fill: new LinearGradient( 0, 0, 0, height ) .addColorStop( 0, outerColor ) .addColorStop( 0.5, innerColor ) .addColorStop( 1, outerColor ) }; var leftBar = new Rectangle( 0, 0, options.highlightWidth, height, barOptions ); var rightBar = new Rectangle( 0, 0, options.highlightWidth, height, barOptions ); options.children = [ leftBar, rightBar ]; options.spacing = width; HBox.call( this, options ); } joist.register( 'HighlightNode', HighlightNode ); return inherit( HBox, HighlightNode ); } ); // Copyright 2016, University of Colorado Boulder /** * Wrapper type for JustButton * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) * @author Andrea Lin (PhET Interactive Simulations) */ define( 'JOIST/TJoistButton',['require','SCENERY/nodes/TNode','JOIST/joist','ifphetio!PHET_IO/assertions/assertInstanceOf','ifphetio!PHET_IO/phetioInherit','ifphetio!PHET_IO/events/toEventOnEmit'],function( require ) { 'use strict'; // modules var TNode = require( 'SCENERY/nodes/TNode' ); var joist = require( 'JOIST/joist' ); // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var toEventOnEmit = require( 'ifphetio!PHET_IO/events/toEventOnEmit' ); /** * @param {JoistButton} joistButton * @param {string} phetioID * @constructor */ function TJoistButton( joistButton, phetioID ) { assertInstanceOf( joistButton, phet.joist.JoistButton ); TNode.call( this, joistButton, phetioID ); // Add button fire events to the data stream. toEventOnEmit( joistButton.buttonModel.startedCallbacksForFiredEmitter, joistButton.buttonModel.endedCallbacksForFiredEmitter, 'user', phetioID, this.constructor, 'fired' ); } phetioInherit( TNode, 'TJoistButton', TJoistButton, {}, { documentation: 'The buttons used in the home screen and navigation bar', events: [ 'fired' ] } ); joist.register( 'TJoistButton', TJoistButton ); return TJoistButton; } ); // Copyright 2014-2015, University of Colorado Boulder /** * Base class for Joist buttons such as the "home" button and "PhET" button that show custom highlighting on mouseover. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/JoistButton',['require','SCENERY/nodes/Node','PHET_CORE/inherit','KITE/Shape','JOIST/HighlightNode','SUN/buttons/ButtonListener','SUN/buttons/PushButtonInteractionStateProperty','SUN/buttons/PushButtonModel','AXON/Property','JOIST/joist','JOIST/TJoistButton'],function( require ) { 'use strict'; // modules var Node = require( 'SCENERY/nodes/Node' ); var inherit = require( 'PHET_CORE/inherit' ); var Shape = require( 'KITE/Shape' ); var HighlightNode = require( 'JOIST/HighlightNode' ); var ButtonListener = require( 'SUN/buttons/ButtonListener' ); var PushButtonInteractionStateProperty = require( 'SUN/buttons/PushButtonInteractionStateProperty' ); var PushButtonModel = require( 'SUN/buttons/PushButtonModel' ); var Property = require( 'AXON/Property' ); var joist = require( 'JOIST/joist' ); var TJoistButton = require( 'JOIST/TJoistButton' ); /** * @param {Node} content - the scenery node to render as the content of the button * @param {Property.} navigationBarFillProperty - the color of the navbar, as a string. * @param {Tandem} tandem * @param {Object} [options] Unused in client code. * @constructor */ function JoistButton( content, navigationBarFillProperty, tandem, options ) { options = _.extend( { cursor: 'pointer', // {string} listener: null, // {function} //Customization for the highlight region, see overrides in HomeButton and PhetButton highlightExtensionWidth: 0, highlightExtensionHeight: 0, highlightCenterOffsetX: 0, highlightCenterOffsetY: 0 }, options ); // @public (phet-io) - Button model this.buttonModel = new PushButtonModel( options ); // Create both highlights and only make the one visible that corresponds to the color scheme var createHighlight = function( fill ) { return new HighlightNode( content.width + options.highlightExtensionWidth, content.height + options.highlightExtensionHeight, { centerX: content.centerX + options.highlightCenterOffsetX, centerY: content.centerY + options.highlightCenterOffsetY, fill: fill, pickable: false } ); }; // Highlight against the black background var brightenHighlight = createHighlight( 'white' ); // Highlight against the white background var darkenHighlight = createHighlight( 'black' ); Node.call( this, { children: [ content, brightenHighlight, darkenHighlight ] } ); // Button interactions var interactionStateProperty = new PushButtonInteractionStateProperty( this.buttonModel ); // @protected this.interactionStateProperty = interactionStateProperty; // Update the highlights based on whether the button is highlighted and whether it is against a light or dark background. Property.multilink( [ interactionStateProperty, navigationBarFillProperty ], function( interactionState, navigationBarFill ) { var useDarkenHighlight = navigationBarFill !== 'black'; brightenHighlight.visible = !useDarkenHighlight && (interactionState === 'over' || interactionState === 'pressed'); darkenHighlight.visible = useDarkenHighlight && (interactionState === 'over' || interactionState === 'pressed'); } ); this.addInputListener( new ButtonListener( this.buttonModel ) ); // eliminate interactivity gap between label and button this.mouseArea = this.touchArea = Shape.bounds( this.bounds ); this.mutate( _.omit( options, 'tandem' ) ); // Rename to TJoistButton tandem.addInstance( this, TJoistButton ); } joist.register( 'JoistButton', JoistButton ); return inherit( Node, JoistButton ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * The Home button that appears in the navigation bar. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/HomeButton',['require','SUN/FontAwesomeNode','PHET_CORE/inherit','JOIST/JoistButton','SCENERY/nodes/Node','AXON/Property','SCENERY/nodes/Rectangle','JOIST/joist'],function( require ) { 'use strict'; // modules var FontAwesomeNode = require( 'SUN/FontAwesomeNode' ); var inherit = require( 'PHET_CORE/inherit' ); var JoistButton = require( 'JOIST/JoistButton' ); var Node = require( 'SCENERY/nodes/Node' ); var Property = require( 'AXON/Property' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var joist = require( 'JOIST/joist' ); /** * @param {number} navBarHeight * @param {Property.} navigationBarFillProperty - the color of the navbar, as a string. * @param {Tandem} tandem * @param {Object} [options] * @constructor */ function HomeButton( navBarHeight, navigationBarFillProperty, tandem, options ) { options = _.extend( { highlightExtensionWidth: 4, listener: null }, options ); var homeIcon = new FontAwesomeNode( 'home' ); // scale so that the icon is slightly taller than screen button icons, value determined empirically, see joist#127 homeIcon.setScaleMagnitude( 0.48 * navBarHeight / homeIcon.height ); // transparent background, size determined empirically so that highlight is the same size as highlight on screen buttons var background = new Rectangle( 0, 0, homeIcon.width + 12, navBarHeight ); homeIcon.center = background.center; var content = new Node( { children: [ background, homeIcon ] } ); JoistButton.call( this, content, navigationBarFillProperty, tandem, options ); Property.multilink( [ this.interactionStateProperty, navigationBarFillProperty ], function( interactionState, navigationBarFill ) { if ( navigationBarFill === 'black' ) { homeIcon.fill = interactionState === 'pressed' ? 'gray' : 'white'; } else { homeIcon.fill = interactionState === 'pressed' ? '#444' : '#222'; } } ); } joist.register( 'HomeButton', HomeButton ); return inherit( JoistButton, HomeButton ); } ); // Copyright 2016, University of Colorado Boulder /** * Wrapper type for NavigationBarScreenButton * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) */ define( 'JOIST/TNavigationBarScreenButton',['require','JOIST/joist','SCENERY/nodes/TNode','ifphetio!PHET_IO/assertions/assertInstanceOf','ifphetio!PHET_IO/phetioInherit','ifphetio!PHET_IO/events/toEventOnEmit'],function( require ) { 'use strict'; // modules var joist = require( 'JOIST/joist' ); var TNode = require( 'SCENERY/nodes/TNode' ); // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var toEventOnEmit = require( 'ifphetio!PHET_IO/events/toEventOnEmit' ); /** * @param {NavigationBarScreenButton} navigationBarScreenButton * @param {string} phetioID * @constructor */ function TNavigationBarScreenButton( navigationBarScreenButton, phetioID ) { assertInstanceOf( navigationBarScreenButton, phet.joist.NavigationBarScreenButton ); TNode.call( this, navigationBarScreenButton, phetioID ); // Send a message on the data stream when the button is pressed. toEventOnEmit( navigationBarScreenButton.buttonModel.startedCallbacksForFiredEmitter, navigationBarScreenButton.buttonModel.endedCallbacksForFiredEmitter, 'user', phetioID, this.constructor, 'fired' ); } phetioInherit( TNode, 'TNavigationBarScreenButton', TNavigationBarScreenButton, {}, { documentation: 'A pressable button in the simulation\'s navigation bar', events: [ 'fired' ] } ); joist.register( 'TNavigationBarScreenButton', TNavigationBarScreenButton ); return TNavigationBarScreenButton; } ); // Copyright 2013-2015, University of Colorado Boulder /** * Button for a single screen in the navigation bar, shows the text and the navigation bar icon. * * @author Sam Reid (PhET Interactive Simulations) * @author Jonathan Olson * @author Chris Malley (PixelZoom, Inc.) */ define( 'JOIST/NavigationBarScreenButton',['require','SUN/buttons/ButtonListener','AXON/DerivedProperty','JOIST/HighlightNode','PHET_CORE/inherit','SCENERY/nodes/Node','SCENERY_PHET/PhetFont','AXON/Property','SUN/buttons/PushButtonModel','SCENERY/nodes/Rectangle','SCENERY/nodes/Text','DOT/Util','SCENERY/nodes/VBox','JOIST/joist','TANDEM/Tandem','SCENERY_PHET/PhetColorScheme','JOIST/TNavigationBarScreenButton'],function( require ) { 'use strict'; // modules var ButtonListener = require( 'SUN/buttons/ButtonListener' ); var DerivedProperty = require( 'AXON/DerivedProperty' ); var HighlightNode = require( 'JOIST/HighlightNode' ); var inherit = require( 'PHET_CORE/inherit' ); var Node = require( 'SCENERY/nodes/Node' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var Property = require( 'AXON/Property' ); var PushButtonModel = require( 'SUN/buttons/PushButtonModel' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var Text = require( 'SCENERY/nodes/Text' ); var Util = require( 'DOT/Util' ); var VBox = require( 'SCENERY/nodes/VBox' ); var joist = require( 'JOIST/joist' ); var Tandem = require( 'TANDEM/Tandem' ); var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' ); var TNavigationBarScreenButton = require( 'JOIST/TNavigationBarScreenButton' ); // constants var HIGHLIGHT_SPACING = 4; /** * Create a nav bar. Layout assumes all of the screen widths are the same. * @param {Property.} navigationBarFillProperty - the color of the navbar, as a string. * @param {Property.} screenIndexProperty * @param {Array.} screens - all of the available sim content screens (excluding the home screen) * @param {Screen} screen * @param {number} navBarHeight * @param {Object} [options] * @constructor */ function NavigationBarScreenButton( navigationBarFillProperty, screenIndexProperty, screens, screen, navBarHeight, options ) { assert && assert( screen.name, 'name is required for screen ' + screens.indexOf( screen ) ); assert && assert( screen.navigationBarIcon, 'navigationBarIcon is required for screen ' + screen.name ); function clicked() { screenIndexProperty.value = screens.indexOf( screen ); } options = _.extend( { cursor: 'pointer', textDescription: screen.name + ' Screen: Button', tandem: Tandem.tandemRequired(), maxButtonWidth: null // {number|null} the maximum width of the button, causes text and/or icon to be scaled down if necessary }, options ); Node.call( this ); // icon var icon = new Node( { children: [ screen.navigationBarIcon ], // wrap in case this icon is used in multiple place (eg, home screen and navbar) maxHeight: 0.625 * navBarHeight, tandem: options.tandem.createTandem( 'icon' ) } ); // Is this button's screen selected? var selectedProperty = new DerivedProperty( [ screenIndexProperty ], function( screenIndex ) { return screenIndex === screens.indexOf( screen ); } ); // @public (phet-io) - create the button model, needs to be public so that PhET-iO wrappers can hook up to it if needed this.buttonModel = new PushButtonModel( { listener: clicked } ); this.addInputListener( new ButtonListener( this.buttonModel ) ); options.tandem.addInstance( this, TNavigationBarScreenButton ); var text = new Text( screen.name, { font: new PhetFont( 10 ), tandem: options.tandem.createTandem( 'text' ) } ); var box = new VBox( { children: [ icon, text ], pickable: false, spacing: Math.max( 0, 12 - text.height ), // see https://github.com/phetsims/joist/issues/143 usesOpacity: true, // hint, since we change its opacity maxHeight: navBarHeight } ); // add a transparent overlay for input handling and to size touchArea/mouseArea var overlay = new Rectangle( 0, 0, box.width, box.height, { center: box.center } ); // highlights var highlightWidth = overlay.width + ( 2 * HIGHLIGHT_SPACING ); var brightenHighlight = new HighlightNode( highlightWidth, overlay.height, { center: box.center, fill: 'white' } ); var darkenHighlight = new HighlightNode( highlightWidth, overlay.height, { center: box.center, fill: 'black' } ); this.addChild( box ); this.addChild( overlay ); this.addChild( brightenHighlight ); this.addChild( darkenHighlight ); // manage interaction feedback Property.multilink( [ selectedProperty, this.buttonModel.downProperty, this.buttonModel.overProperty, navigationBarFillProperty ], function update( selected, down, over, navigationBarFill ) { var useDarkenHighlights = ( navigationBarFill !== 'black' ); // Color match yellow with the PhET Logo var selectedTextColor = useDarkenHighlights ? 'black' : PhetColorScheme.PHET_LOGO_YELLOW; var unselectedTextColor = useDarkenHighlights ? 'gray' : 'white'; text.fill = selected ? selectedTextColor : unselectedTextColor; box.opacity = selected ? 1.0 : ( down ? 0.65 : 0.5 ); brightenHighlight.visible = !useDarkenHighlights && ( over || down ); darkenHighlight.visible = useDarkenHighlights && ( over || down ); } ); // Constrain text and icon width, if necessary if ( options.maxButtonWidth && ( this.width > options.maxButtonWidth ) ) { text.maxWidth = icon.maxWidth = options.maxButtonWidth - ( this.width - box.width ); // adjust the overlay overlay.setRect( 0, 0, box.width, overlay.height ); overlay.center = box.center; // adjust the highlights brightenHighlight.spacing = darkenHighlight.spacing = overlay.width + ( 2 * HIGHLIGHT_SPACING ); brightenHighlight.center = darkenHighlight.center = box.center; assert && assert( Util.toFixed( this.width, 0 ) === Util.toFixed( options.maxButtonWidth, 0 ) ); } this.mutate( _.omit( options, 'tandem' ) ); } joist.register( 'NavigationBarScreenButton', NavigationBarScreenButton ); return inherit( Node, NavigationBarScreenButton ); } ); // Copyright 2017, University of Colorado Boulder /** * Single location of all accessibility strings used in joist. These * strings are not meant to be translatable yet. Rosetta needs some work to * provide translators with context for these strings, and we want to receive * some community feedback before these strings are submitted for translation. * * @author Jesse Greenberg */ define( 'JOIST/JoistA11yStrings',['require','JOIST/joist'],function( require ) { 'use strict'; var joist = require( 'JOIST/joist' ); var JoistA11yStrings = { // dialogs hotKeysAndHelpString: 'Keyboard Shortcuts', closeString: 'Close', // navigation bar simResourcesAndToolsString: 'Sim Resources and Tools', // PhET menu phetString: 'PhET Menu' }; if ( phet.chipper.queryParameters.stringTest === 'xss' ) { for ( var key in JoistA11yStrings ) { JoistA11yStrings[ key ] += ''; } } // verify that object is immutable, without the runtime penalty in production code if ( assert ) { Object.freeze( JoistA11yStrings ); } joist.register( 'JoistA11yStrings', JoistA11yStrings ); return JoistA11yStrings; } ); // Copyright 2014-2015, University of Colorado Boulder /** * Scans through potential event properties on an object to detect prefixed forms, and returns the first match. * * E.g. currently: * phetCore.detectPrefixEvent( document, 'fullscreenchange' ) === 'webkitfullscreenchange' * * @author Jonathan Olson */ define( 'PHET_CORE/detectPrefixEvent',['require','PHET_CORE/phetCore'],function( require ) { 'use strict'; var phetCore = require( 'PHET_CORE/phetCore' ); // @returns the best String str where obj['on'+str] !== undefined, or returns undefined if that is not available function detectPrefixEvent( obj, name, isEvent ) { if ( obj[ 'on' + name ] !== undefined ) { return name; } // Chrome planning to not introduce prefixes in the future, hopefully we will be safe if ( obj[ 'on' + 'moz' + name ] !== undefined ) { return 'moz' + name; } if ( obj[ 'on' + 'Moz' + name ] !== undefined ) { return 'Moz' + name; } // some prefixes seem to have all-caps? if ( obj[ 'on' + 'webkit' + name ] !== undefined ) { return 'webkit' + name; } if ( obj[ 'on' + 'ms' + name ] !== undefined ) { return 'ms' + name; } if ( obj[ 'on' + 'o' + name ] !== undefined ) { return 'o' + name; } return undefined; } phetCore.register( 'detectPrefixEvent', detectPrefixEvent ); return detectPrefixEvent; } ); // Copyright 2014-2015, University of Colorado Boulder /** * Utilities for full-screen support * * @author Jonathan Olson */ define( 'JOIST/FullScreen',['require','PHET_CORE/platform','PHET_CORE/detectPrefix','PHET_CORE/detectPrefixEvent','AXON/Property','JOIST/joist'],function( require ) { 'use strict'; // modules var platform = require( 'PHET_CORE/platform' ); var detectPrefix = require( 'PHET_CORE/detectPrefix' ); var detectPrefixEvent = require( 'PHET_CORE/detectPrefixEvent' ); var Property = require( 'AXON/Property' ); var joist = require( 'JOIST/joist' ); // get prefixed (and properly capitalized) property names var requestFullscreenPropertyName = detectPrefix( document.body, 'requestFullscreen' ) || detectPrefix( document.body, 'requestFullScreen' ); // Firefox capitalization var exitFullscreenPropertyName = detectPrefix( document, 'exitFullscreen' ) || detectPrefix( document, 'cancelFullScreen' ); // Firefox var fullscreenElementPropertyName = detectPrefix( document, 'fullscreenElement' ) || detectPrefix( document, 'fullScreenElement' ); // Firefox capitalization var fullscreenEnabledPropertyName = detectPrefix( document, 'fullscreenEnabled' ) || detectPrefix( document, 'fullScreenEnabled' ); // Firefox capitalization var fullscreenChangeEvent = detectPrefixEvent( document, 'fullscreenchange' ); // required capitalization workaround for now if ( fullscreenChangeEvent === 'msfullscreenchange' ) { fullscreenChangeEvent = 'MSFullscreenChange'; } var FullScreen = { // @public (joist-internal) isFullScreen: function() { return !!document[ fullscreenElementPropertyName ]; }, // @public (joist-internal) isFullScreenEnabled: function() { return document[ fullscreenEnabledPropertyName ] && !platform.safari7; }, // @public (joist-internal) enterFullScreen: function( sim ) { if ( !platform.ie9 && !platform.ie10 ) { sim.display.domElement[ requestFullscreenPropertyName ] && sim.display.domElement[ requestFullscreenPropertyName ](); } else if ( typeof window.ActiveXObject !== 'undefined' ) { // Older IE. var wscript = new window.ActiveXObject( 'WScript.Shell' ); if ( wscript !== null ) { wscript.SendKeys( '{F11}' ); } } }, // @public (joist-internal) exitFullScreen: function() { document[ exitFullscreenPropertyName ] && document[ exitFullscreenPropertyName ](); }, // @public (joist-internal) toggleFullScreen: function( sim ) { if ( FullScreen.isFullScreen() ) { FullScreen.exitFullScreen(); } else { FullScreen.enterFullScreen( sim ); } }, isFullScreenProperty: new Property( false ) }; // update isFullScreenProperty on potential changes document.addEventListener( fullscreenChangeEvent, function( evt ) { FullScreen.isFullScreenProperty.set( FullScreen.isFullScreen() ); } ); joist.register( 'FullScreen', FullScreen ); return FullScreen; } ); // Copyright 2014-2015, University of Colorado Boulder /** * General dialog type * * @author Jonathan Olson * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/Dialog',['require','PHET_CORE/inherit','KITE/Shape','SCENERY/nodes/Node','SCENERY/nodes/Path','SCENERY/input/Input','SUN/Panel','SUN/buttons/RectangularPushButton','JOIST/joist','JOIST/JoistA11yStrings','SCENERY_PHET/accessibility/AriaHerald','SCENERY/accessibility/AccessibilityUtil','JOIST/FullScreen','TANDEM/Tandem'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Shape = require( 'KITE/Shape' ); var Node = require( 'SCENERY/nodes/Node' ); var Path = require( 'SCENERY/nodes/Path' ); var Input = require( 'SCENERY/input/Input' ); var Panel = require( 'SUN/Panel' ); var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' ); var joist = require( 'JOIST/joist' ); var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' ); var AriaHerald = require( 'SCENERY_PHET/accessibility/AriaHerald' ); var AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' ); var FullScreen = require( 'JOIST/FullScreen' ); var Tandem = require( 'TANDEM/Tandem' ); var closeString = JoistA11yStrings.closeString; /** * @param {Node} content - The content to display inside the dialog (not including the title) * @param {Object} [options] * @constructor */ function Dialog( content, options ) { var self = this; options = _.extend( { // Dialog-specific options modal: false, // {boolean} modal dialogs prevent interaction with the rest of the sim while open title: null, // {Node} title to be displayed at top titleAlign: 'center', // horizontal alignment of the title: {string} left, right or center titleSpacing: 20, // {number} how far the title is placed above the content hasCloseButton: true, // whether to put a close 'X' button is upper-right corner // {function} which sets the dialog's position in global coordinates. called as // layoutStrategy( dialog, simBounds, screenBounds, scale ) layoutStrategy: Dialog.DEFAULT_LAYOUT_STRATEGY, // pass through to Panel options cornerRadius: 10, // {number} radius of the dialog's corners resize: true, // {boolean} whether to resize if content's size changes fill: 'white', // {string|Color} stroke: 'black', // {string|Color} backgroundPickable: true, xMargin: 20, yMargin: 20, closeButtonBaseColor: '#d00', closeButtonMargin: 5, // {number} how far away should the close button be from the panel border tandem: Tandem.tandemRequired(), // a11y options tagName: 'div', ariaRole: 'dialog', focusOnCloseNode: null // {Node} receives focus on close, if null focus returns to element that had focus on open }, options ); // @private (read-only) this.isModal = options.modal; // see https://github.com/phetsims/joist/issues/293 assert && assert( this.isModal, 'Non-modal dialogs not currently supported' ); // @private - whether the dialog is showing this.isShowing = false; var dialogContent = new Node( { children: [ content ] } ); if ( options.title ) { var titleNode = options.title; dialogContent.addChild( titleNode ); var updateTitlePosition = function() { switch( options.titleAlign ) { case 'center': titleNode.centerX = content.centerX; break; case 'left': titleNode.left = content.left; break; case 'right': titleNode.right = content.right; break; default: throw new Error( 'unknown titleAlign for Dialog: ' + options.titleAlign ); } titleNode.bottom = content.top - options.titleSpacing; }; if ( options.resize ) { content.on( 'bounds', updateTitlePosition ); titleNode.on( 'localBounds', updateTitlePosition ); } updateTitlePosition(); } Panel.call( this, dialogContent, options ); if ( options.hasCloseButton ) { var crossSize = 10; var crossNode = new Path( new Shape().moveTo( 0, 0 ).lineTo( crossSize, crossSize ).moveTo( 0, crossSize ).lineTo( crossSize, 0 ), { stroke: '#fff', lineWidth: 3 } ); var closeButton = new RectangularPushButton( { content: crossNode, baseColor: options.closeButtonBaseColor, xMargin: 5, yMargin: 5, listener: function() { // This setTimeout call is a workaround until hide and dispose are separated. It should be removed once // https://github.com/phetsims/joist/issues/424 is complete. setTimeout( function() { self.hide(); }, 0 ); }, accessibleFire: function() { // the active element must be focused after the Dialog is hidden, so this must also be wrapped in a // timeout. This should also be removed once https://github.com/phetsims/joist/issues/424 is complete. setTimeout( function() { self.focusActiveElement(); }, 0 ); }, tandem: options.tandem.createTandem( 'closeButton' ), // a11y options tagName: 'button', accessibleLabel: closeString } ); this.addChild( closeButton ); var updateClosePosition = function() { closeButton.right = dialogContent.right + options.xMargin - options.closeButtonMargin; closeButton.top = dialogContent.top - options.xMargin + options.closeButtonMargin; // place the focus highlight, and make it a bit bigger than the closeButton.focusHighlight = Shape.bounds( crossNode.bounds.dilated( 10 ) ); }; //TODO memory leak, see https://github.com/phetsims/joist/issues/357 if ( options.resize ) { dialogContent.on( 'bounds', updateClosePosition ); if ( options.title ) { options.title.on( 'bounds', updateClosePosition ); } } updateClosePosition(); } var sim = window.phet.joist.sim; // @private this.updateLayout = function() { options.layoutStrategy( self, sim.boundsProperty.value, sim.screenBoundsProperty.value, sim.scaleProperty.value ); }; this.updateLayout(); // @private this.sim = sim; // a11y - set the order of content for accessibility, title before content this.accessibleOrder = [ titleNode, dialogContent ]; // a11y - set the aria labelledby and describedby relations so that whenever focus enters the dialog, the title // and description content are read in full content.tagName && content.setAriaDescribesNode( this ); if ( options.title ) { options.title.tagName && options.title.setAriaLabelsNode( this ); } // @private (a11y) - the active element when the dialog is shown, tracked so that focus can be restored on close this.activeElement = options.focusOnCloseNode || null; // @private - add the input listeners for accessibility when the dialog is shown // the listeners must be added when shown because all listeners are removed // when the dialog is hidden var escapeListener; this.addAccessibleInputListeners = function() { // close the dialog when the user presses 'escape' escapeListener = this.addAccessibleInputListener( { keydown: function( event ) { if ( event.keyCode === Input.KEY_ESCAPE ) { event.preventDefault(); self.hide(); self.focusActiveElement(); } else if ( event.keyCode === Input.KEY_TAB && FullScreen.isFullScreen() ) { // prevent a particular bug in Windows 7/8.1 Firefox where focus gets trapped in the document // when the navigation bar is hidden and there is only one focusable element in the DOM // see https://bugzilla.mozilla.org/show_bug.cgi?id=910136 var activeElement = document.activeElement; var noNextFocusable = AccessibilityUtil.getNextFocusable() === activeElement; var noPreviousFocusable = AccessibilityUtil.getPreviousFocusable() === activeElement; if ( noNextFocusable && noPreviousFocusable ) { event.preventDefault(); } } } } ); }; // @private - remove listeners so that the dialog is eligible for garbage collection // called every time the dialog is hidden this.disposeDialog = function() { options.tandem.removeInstance( this ); self.sim.resizedEmitter.removeListener( self.updateLayout ); self.removeAccessibleInputListener( escapeListener ); if ( options.hasCloseButton ) { closeButton.dispose(); } }; } joist.register( 'Dialog', Dialog ); // @private Dialog.DEFAULT_LAYOUT_STRATEGY = function( dialog, simBounds, screenBounds, scale ) { // The size is set in the Sim.topLayer, but we need to update the location here dialog.center = simBounds.center.times( 1.0 / scale ); }; return inherit( Panel, Dialog, { // @public show: function() { if ( !this.isShowing ) { window.phet.joist.sim.showPopup( this, this.isModal ); this.isShowing = true; this.sim.resizedEmitter.addListener( this.updateLayout ); // a11y - add the listeners that will close the dialog on this.addAccessibleInputListeners(); // a11y - store the currently active element, set before hiding views so that document.activeElement // isn't blurred this.activeElement = this.activeElement || document.activeElement; // a11y - hide all ScreenView content from assistive technology when the dialog is shown this.setAccessibleViewsHidden( true ); // In case the window size has changed since the dialog was hidden, we should try layout out again. // See https://github.com/phetsims/joist/issues/362 this.updateLayout(); } }, /** * This function acts as a dispose function for a dialog, and it is assumed that a new Dialog will be created before * it is shown. * @public */ hide: function() { if ( this.isShowing ) { window.phet.joist.sim.hidePopup( this, this.isModal ); this.isShowing = false; // dispose dialog - a new one will be created on show() this.disposeDialog(); // a11y - when the dialog is hidden, unhide all ScreenView content from assistive technology this.setAccessibleViewsHidden( false ); } }, /** * Hide or show all accessible content related to the sim ScreenViews, navigation bar, and alert content. Instead * of using setHidden, we have to remove the subtree of accessible content from each view element in order to * prevent an IE11 bug where content remains invisible in the accessibility tree, see * https://github.com/phetsims/john-travoltage/issues/247 * * @param {boolean} hidden */ setAccessibleViewsHidden: function( hidden ) { for ( var i = 0; i < this.sim.screens.length; i++ ) { this.sim.screens[ i ].view.accessibleContentDisplayed = !hidden; } this.sim.navigationBar.accessibleContentDisplayed = !hidden; // workaround for a strange Edge bug where this child of the navigation bar remains hidden, // see https://github.com/phetsims/a11y-research/issues/30 this.sim.navigationBar.keyboardHelpButton.accessibleHidden = hidden; // clear the aria-live alert content from the DOM AriaHerald.clearAll(); }, /** * If there is an active element, focus it. Should almost always be closed after the Dialog has been closed. * * @public * @a11y */ focusActiveElement: function() { this.activeElement && this.activeElement.focus(); } } ); } ); define("string!JOIST/credits.title",function(){return window.phet.chipper.strings.get("JOIST/credits.title");}); define("string!JOIST/credits.leadDesign",function(){return window.phet.chipper.strings.get("JOIST/credits.leadDesign");}); define("string!JOIST/credits.softwareDevelopment",function(){return window.phet.chipper.strings.get("JOIST/credits.softwareDevelopment");}); define("string!JOIST/credits.team",function(){return window.phet.chipper.strings.get("JOIST/credits.team");}); define("string!JOIST/credits.contributors",function(){return window.phet.chipper.strings.get("JOIST/credits.contributors");}); define("string!JOIST/credits.qualityAssurance",function(){return window.phet.chipper.strings.get("JOIST/credits.qualityAssurance");}); define("string!JOIST/credits.graphicArts",function(){return window.phet.chipper.strings.get("JOIST/credits.graphicArts");}); define("string!JOIST/credits.translation",function(){return window.phet.chipper.strings.get("JOIST/credits.translation");}); define("string!JOIST/credits.thanks",function(){return window.phet.chipper.strings.get("JOIST/credits.thanks");}); // Copyright 2015, University of Colorado Boulder /** * Displays the credits section in the About dialog * * @author Sam Reid (PhET Interactive Simulations) * @author Chris Malley (PixelZoom, Inc.) * @author Jonathan Olson */ define( 'JOIST/CreditsNode',['require','SCENERY/nodes/VBox','SCENERY/nodes/Text','PHET_CORE/inherit','SCENERY_PHET/PhetFont','SCENERY_PHET/MultiLineText','PHETCOMMON/util/StringUtils','SCENERY/nodes/VStrut','JOIST/joist','string!JOIST/credits.title','string!JOIST/credits.leadDesign','string!JOIST/credits.softwareDevelopment','string!JOIST/credits.team','string!JOIST/credits.contributors','string!JOIST/credits.qualityAssurance','string!JOIST/credits.graphicArts','string!JOIST/credits.translation','string!JOIST/credits.thanks'],function( require ) { 'use strict'; // modules var VBox = require( 'SCENERY/nodes/VBox' ); var Text = require( 'SCENERY/nodes/Text' ); var inherit = require( 'PHET_CORE/inherit' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var MultiLineText = require( 'SCENERY_PHET/MultiLineText' ); var StringUtils = require( 'PHETCOMMON/util/StringUtils' ); var VStrut = require( 'SCENERY/nodes/VStrut' ); var joist = require( 'JOIST/joist' ); // strings var creditsTitleString = require( 'string!JOIST/credits.title' ); var creditsLeadDesignString = require( 'string!JOIST/credits.leadDesign' ); var creditsSoftwareDevelopmentString = require( 'string!JOIST/credits.softwareDevelopment' ); var creditsTeamString = require( 'string!JOIST/credits.team' ); var creditsContributorsString = require( 'string!JOIST/credits.contributors' ); var creditsQualityAssuranceString = require( 'string!JOIST/credits.qualityAssurance' ); var creditsGraphicArtsString = require( 'string!JOIST/credits.graphicArts' ); var creditsTranslationString = require( 'string!JOIST/credits.translation' ); var creditsThanksString = require( 'string!JOIST/credits.thanks' ); /** * Creates node that displays the credits. * @param {Object} credits - see implementation herein for supported {string} fields * @param tandem * @param {Object} [options] - Passed to VBox * @constructor */ function CreditsNode( credits, tandem, options ) { var titleFont = new PhetFont( { size: 14, weight: 'bold' } ); var font = new PhetFont( 12 ); var multiLineTextOptions = { font: font, align: 'left' }; var children = []; var addTandemToOptions = function( tandemName ) { return _.extend( { tandem: tandem.createTandem(tandemName) }, multiLineTextOptions ); }; // Credits children.push( new Text( creditsTitleString, { font: titleFont, // a11y tagName: 'h2', accessibleLabel: creditsTitleString } ) ); if ( credits.leadDesign ) { children.push( new MultiLineText( StringUtils.format( creditsLeadDesignString, '\u202a' + credits.leadDesign + '\u202c' ), addTandemToOptions('creditsLeadDesignString') ) ); } if ( credits.softwareDevelopment ) { children.push( new MultiLineText( StringUtils.format( creditsSoftwareDevelopmentString, '\u202a' + credits.softwareDevelopment + '\u202c' ), addTandemToOptions('creditsSoftwareDevelopmentString') ) ); } if ( credits.team ) { children.push( new MultiLineText( StringUtils.format( creditsTeamString, '\u202a' + credits.team + '\u202c' ), addTandemToOptions('creditsTeamString') ) ); } if ( credits.contributors ) { children.push( new MultiLineText( StringUtils.format( creditsContributorsString, '\u202a' + credits.contributors + '\u202c' ), addTandemToOptions('creditsContributorsString') ) ); } if ( credits.qualityAssurance ) { children.push( new MultiLineText( StringUtils.format( creditsQualityAssuranceString, '\u202a' + credits.qualityAssurance + '\u202c' ), addTandemToOptions('creditsQualityAssuranceString') ) ); } if ( credits.graphicArts ) { children.push( new MultiLineText( StringUtils.format( creditsGraphicArtsString, '\u202a' + credits.graphicArts + '\u202c' ), addTandemToOptions('creditsGraphicArtsString') ) ); } //TODO obtain translation credit from strings file, see https://github.com/phetsims/joist/issues/163 // Translation if ( credits.translation ) { if ( children.length > 0 ) { children.push( new VStrut( 10 ) ); } children.push( new Text( creditsTranslationString, { font: titleFont, tagName: 'h2', accessibleLabel: creditsTranslationString } ) ); children.push( new MultiLineText( credits.translation, addTandemToOptions('creditsTranslationString') ) ); } // Thanks if ( credits.thanks ) { if ( children.length > 0 ) { children.push( new VStrut( 10 ) ); } children.push( new Text( creditsThanksString, { font: titleFont, tagName: 'h2', accessibleLabel: creditsThanksString } ) ); children.push( new MultiLineText( credits.thanks, addTandemToOptions('creditsThanksString') ) ); } VBox.call( this, _.extend( { align: 'left', spacing: 1, children: children }, options ) ); this.disposeCreditsNode = function() { children.forEach( function( child ) { child.dispose && child.dispose(); } ); }; } joist.register( 'CreditsNode', CreditsNode ); return inherit( VBox, CreditsNode, { dispose: function() { this.disposeCreditsNode(); VBox.prototype.dispose.call( this ); } } ); } ); // Copyright 2014-2015, University of Colorado Boulder /** * Push button with text on a rectangle. * * @author John Blanco (PhET Interactive Simulations) */ define( 'SUN/buttons/TextPushButton',['require','PHET_CORE/inherit','SUN/buttons/RectangularPushButton','SUN/sun','SCENERY/nodes/Text','TANDEM/Tandem'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' ); var sun = require( 'SUN/sun' ); var Text = require( 'SCENERY/nodes/Text' ); var Tandem = require( 'TANDEM/Tandem' ); /** * @param {string} text * @param {Object} [options] * @constructor */ function TextPushButton( text, options ) { options = _.extend( { textFill: 'black', maxTextWidth: null, tandem: Tandem.tandemRequired(), // a11y accessibleLabel: text }, options ); var textNode = new Text( text, { font: options.font, fill: options.textFill, maxWidth: options.maxTextWidth, tandem: options.tandem.createTandem( 'textNode' ) } ); RectangularPushButton.call( this, _.extend( { content: textNode }, options ) ); } sun.register( 'TextPushButton', TextPushButton ); return inherit( RectangularPushButton, TextPushButton ); } ); // Copyright 2015, University of Colorado Boulder /** * A spinnable busy indicator, to indicate something behind the scenes is in progress (but with no indication of how * far along it is). * * The actual rectangles/circles/etc. (called elements in the documentation) stay in fixed positions, but their fill is * changed to give the impression of rotation. * * @author Jonathan Olson */ define( 'SCENERY_PHET/SpinningIndicatorNode',['require','PHET_CORE/inherit','SCENERY/nodes/Node','SCENERY/nodes/Rectangle','SCENERY/nodes/Circle','SCENERY/util/Color','SCENERY_PHET/sceneryPhet','TANDEM/Tandem'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Node = require( 'SCENERY/nodes/Node' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var Circle = require( 'SCENERY/nodes/Circle' ); var Color = require( 'SCENERY/util/Color' ); var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' ); var Tandem = require( 'TANDEM/Tandem' ); /** * @param {Object} options * @constructor */ function SpinningIndicatorNode( options ) { Tandem.indicateUninstrumentedCode(); // default options options = _.extend( { indicatorSize: 15, // {number} - The width/height taken up by the indicator. indicatorSpeed: 1, // {number} - A multiplier for how fast/slow the indicator will spin. elementFactory: SpinningIndicatorNode.rectangleFactory, // {function( options ) => {Node}} - To create the elements elementQuantity: 16, // {number} - How many elements should exist activeColor: 'rgba(0,0,0,1)', // {string|Color} - The active "mostly visible" color at the lead. inactiveColor: 'rgba(0,0,0,0.15)' // {string|Color} - The inactive "mostly invisible" color at the tail. }, options ); this.options = options; Node.call( this, options ); this.indicatorRotation = Math.PI * 2; // @private Current angle of rotation (starts at 2pi so our modulo opreation is safe below) // parse the colors (if necessary) so we can quickly interpolate between the two this.activeColor = new Color( options.activeColor ); // @private this.inactiveColor = new Color( options.inactiveColor ); // @private // @private the angle between each element this.angleDelta = 2 * Math.PI / options.elementQuantity; // @private create and add all of the elements this.elements = []; var angle = 0; for ( var i = 0; i < options.elementQuantity; i++ ) { var element = options.elementFactory( this.options ); // push the element to the outside of the circle element.right = options.indicatorSize / 2; // center it vertically, so it can be rotated nicely into place element.centerY = 0; // rotate each element by its specific angle element.rotate( angle, true ); angle += this.angleDelta; this.elements.push( element ); this.addChild( element ); } this.step( 0 ); // initialize colors } sceneryPhet.register( 'SpinningIndicatorNode', SpinningIndicatorNode ); return inherit( Node, SpinningIndicatorNode, { // @public step: function( dt ) { // increment rotation based on DT this.indicatorRotation += dt * 10.0 * this.options.indicatorSpeed; // update each element var angle = this.indicatorRotation; for ( var i = 0; i < this.elements.length; i++ ) { // a number from 0 (active head) to 1 (inactive tail). var ratio = Math.pow( ( angle / ( 2 * Math.PI ) ) % 1, 0.5 ); // Smoother transition, mapping our ratio from [0,0.2] => [1,0] and [0.2,1] => [0,1]. // Otherwise, elements can instantly switch from one color to the other, which is visually displeasing. if ( ratio < 0.2 ) { ratio = 1 - ratio * 5; } else { ratio = ( ratio - 0.2 ) * 10 / 8; } // Fill it with the interpolated color var red = ratio * this.inactiveColor.red + ( 1 - ratio ) * this.activeColor.red; var green = ratio * this.inactiveColor.green + ( 1 - ratio ) * this.activeColor.green; var blue = ratio * this.inactiveColor.blue + ( 1 - ratio ) * this.activeColor.blue; var alpha = ratio * this.inactiveColor.alpha + ( 1 - ratio ) * this.activeColor.alpha; this.elements[i].fill = new Color( red, green, blue, alpha ); // And rotate to the next element (in the opposite direction, so our motion is towards the head) angle -= this.angleDelta; } } }, { // @static Factory method for creating rectangular-shaped elements, sized to fit. rectangleFactory: function( options ) { return new Rectangle( 0, 0, options.indicatorSize * 0.175, 1.2 * options.indicatorSize / options.elementQuantity ); }, // @static Factory method for creating circle-shaped elements, sized to fit. circleFactory: function( options ) { return new Circle( 0.8 * options.indicatorSize / options.elementQuantity ); } } ); } ); // Copyright 2015, University of Colorado Boulder /** * Make the package.json contents available to the simulation, so it can access the version, sim name, etc. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/packageJSON',['require','JOIST/joist','text!REPOSITORY/package.json'],function( require ) { 'use strict'; // modules var joist = require( 'JOIST/joist' ); // strings var packageString = require( 'text!REPOSITORY/package.json' ); var packageJSON = JSON.parse( packageString ); packageJSON.version = window.getVersionForBrand( phet.chipper.brand, packageJSON.version ); joist.register( 'packageJSON', packageJSON ); return packageJSON; } ); // Copyright 2015, University of Colorado Boulder /** * Object representing a simulation version, with optional build timestamp information (conceptually part of the version * for potential version comparisons). * * @author Jonathan Olson */ define( 'JOIST/SimVersion',['require','PHET_CORE/inherit','JOIST/joist'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var joist = require( 'JOIST/joist' ); /** * @param {Object} version - major/minor/maintenance are required * @constructor */ function SimVersion( version ) { assert && assert( version.major !== undefined ); assert && assert( version.minor !== undefined ); assert && assert( version.maintenance !== undefined ); this.major = version.major; // @public - {number} major - Major version number this.minor = version.minor; // @public - {number} minor - Major version number this.maintenance = version.maintenance; // @public - {number} maintenance - Major version number this.suffix = version.suffix; // @public - {string} [suffix] - Optional suffix (like 'dev.3') this.buildTimestamp = version.buildTimestamp; // @public - {string} [buildTimestamp] - Optional build timestamp, // like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp) } joist.register( 'SimVersion', SimVersion ); return inherit( Object, SimVersion, { /** * Compares versions, returning -1 if this version is before the passed in version, 0 if equal, or 1 if this version * is after. * * This function only compares major/minor/maintenance, leaving suffix/buildTimestamp comparisons for the client * for now. * * @param {SimVersion} version * @public */ compare: function( version ) { if ( this.major < version.major ) { return -1; } if ( this.major > version.major ) { return 1; } if ( this.minor < version.minor ) { return -1; } if ( this.minor > version.minor ) { return 1; } if ( this.maintenance < version.maintenance ) { return -1; } if ( this.maintenance > version.maintenance ) { return 1; } return 0; // equal }, /** * @returns {boolean|*} * @public */ get isSimNotPublished() { return this.major < 1 || // e.g. 0.0.0-dev.1 ( this.major === 1 && // e.g. 1.0.0-dev.1 this.minor === 0 && this.maintenance === 0 && this.suffix ); }, /** * @returns {string} * @public */ toString: function() { return this.major + '.' + this.minor + '.' + this.maintenance + ( this.suffix ? '-' + this.suffix : '' ); } }, { /** * @param {string} versionString - e.g. '1.0.0', '1.0.1-dev.3', etc. * @param {string} [buildTimestamp] - Optional build timestamp, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp) * @public */ parse: function( versionString, buildTimestamp ) { var matches = versionString.match( /(\d+)\.(\d+)\.(\d+)(-(.+))?/ ); if ( !matches ) { throw new Error( 'could not parse version: ' + versionString ); } return new SimVersion( { major: parseInt( matches[ 1 ], 10 ), minor: parseInt( matches[ 2 ], 10 ), maintenance: parseInt( matches[ 3 ], 10 ), suffix: matches[ 5 ], buildTimestamp: buildTimestamp } ); } } ); } ); // Copyright 2015, University of Colorado Boulder /** * Creates the namespace for this repository. * * @author Chris Malley (PixelZoom, Inc.) */ define( 'BRAND/../../js/brand',['require','PHET_CORE/Namespace'],function( require ) { 'use strict'; // modules var Namespace = require( 'PHET_CORE/Namespace' ); return new Namespace( 'brand' ); } ); define("string!JOIST/termsPrivacyAndLicensing",function(){return window.phet.chipper.strings.get("JOIST/termsPrivacyAndLicensing");}); define("string!JOIST/translation.credits.link",function(){return window.phet.chipper.strings.get("JOIST/translation.credits.link");}); define("string!JOIST/thirdParty.credits.link",function(){return window.phet.chipper.strings.get("JOIST/thirdParty.credits.link");}); // Copyright 2002-2014, University of Colorado Boulder // Returns branding information for the simulations, see https://github.com/phetsims/brand/issues/1 define( 'BRAND/Brand',['require','BRAND/../../js/brand','string!JOIST/termsPrivacyAndLicensing','string!JOIST/translation.credits.link','string!JOIST/thirdParty.credits.link'],function( require ) { 'use strict'; // modules var brand = require( 'BRAND/../../js/brand' ); // strings var termsPrivacyAndLicensingString = require( 'string!JOIST/termsPrivacyAndLicensing' ); var translationCreditsLinkString = require( 'string!JOIST/translation.credits.link' ); var thirdPartyCreditsLinkString = require( 'string!JOIST/thirdParty.credits.link' ); // Documentation for all properties is available in brand/adapted-from-phet/js/Brand.js var Brand = { id: 'phet', name: 'PhET\u2122 Interactive Simulations', // no i18n copyright: 'Copyright © 2002-{{year}} University of Colorado Boulder', // no i18n isPhetApp: phet.chipper.queryParameters[ 'phet-app' ] || phet.chipper.queryParameters[ 'phet-android-app' ], getLinks: function( simName, locale ) { return [ { text: termsPrivacyAndLicensingString, url: 'https://phet.colorado.edu/en/licensing/html' }, { text: translationCreditsLinkString, url: 'https://phet.colorado.edu/translation-credits?simName=' + encodeURIComponent( simName ) + '&locale=' + encodeURIComponent( locale ) }, { text: thirdPartyCreditsLinkString, url: 'https://phet.colorado.edu/third-party-credits?simName=' + encodeURIComponent( simName ) + '&locale=' + encodeURIComponent( locale ) + '#' + simName } ]; } }; brand.register( 'Brand', Brand ); return Brand; } ); // Copyright 2015, University of Colorado Boulder /** * A singleton type/object for handling checking whether our simulation is up-to-date, or whether there is an * updated version. See https://github.com/phetsims/joist/issues/189 * * It exposes its current state (for UIs to hook into), and a check() function used to start checking the version. * * @author Jonathan Olson */ define( 'JOIST/UpdateCheck',['require','PHET_CORE/inherit','AXON/Property','JOIST/packageJSON','JOIST/SimVersion','BRAND/Brand','JOIST/joist'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Property = require( 'AXON/Property' ); var packageJSON = require( 'JOIST/packageJSON' ); // parse name/version out of the package.json var SimVersion = require( 'JOIST/SimVersion' ); var Brand = require( 'BRAND/Brand' ); var joist = require( 'JOIST/joist' ); var simName = packageJSON.name; var simVersion = SimVersion.parse( packageJSON.version, phet.chipper.buildTimestamp ); var requestProtocolString = ( 'https:' === document.location.protocol ? 'https:' : 'http:' ); // NOTE: singleton type! function UpdateCheck() { // @public (joist-internal) this.stateProperty = new Property( 'unchecked', { validValues: [ 'up-to-date', // Simulation version is equal to or greater than the currently published version. 'out-of-date', // Simulation version is less than currently published version (or equal but has a suffix) 'checking', // Request to server sent out, has not processed reply yet. 'offline', // Last attempt to check failed, most likely offline 'unchecked' // No attempt as been made to check the version against the latest online. ] } ); // @public (joist-internal) {SimVersion} will be filled in by check() if applicable this.latestVersionProperty = new Property( null ); this.ourVersion = simVersion; // @public (joist-internal) {SimVersion} version of the sim that is running this.timeoutCallback = this.timeout.bind( this ); // @public (joist-internal) } inherit( Object, UpdateCheck, { // @public - Whether we actually allow checking for updates, or showing any update-related UIs. // If it's not PhET-branded OR if it is phet-io or in the phet-app, do not check for updates areUpdatesChecked: Brand.id === 'phet' && !Brand.isPhetApp && phet.chipper.queryParameters.yotta, // @public - The URL to be used for "New version available" clicks updateURL: 'https://phet.colorado.edu/html-sim-update' + '?simulation=' + encodeURIComponent( simName ) + '&version=' + encodeURIComponent( simVersion.toString() ) + '&buildTimestamp=' + encodeURIComponent( '' + phet.chipper.buildTimestamp ), // @private - Valid only if state === 'checking', the timeout ID of our timeout listener timeoutId: -1, // @private - How many ms before we time out (set to 'offline') timeoutMilliseconds: 15000, // @private - Clears our timeout listener. clearTimeout: function() { window.clearTimeout( this.timeoutId ); }, // @private - Sets our timeout listener. setTimeout: function() { this.timeoutId = window.setTimeout( this.timeoutCallback, this.timeoutMilliseconds ); }, // @public - If we are checking, it resets our timeout timer to timeoutMilliseconds resetTimeout: function() { if ( this.stateProperty.value === 'checking' ) { this.clearTimeout(); this.setTimeout(); } }, // @private - What happens when we actually time out. timeout: function() { this.stateProperty.value = 'offline'; }, /** * @public - Kicks off the version checking request (if able), resulting in state changes. */ check: function() { var self = this; if ( !this.areUpdatesChecked || ( self.stateProperty.value !== 'unchecked' && self.stateProperty.value !== 'offline' ) ) { return; } // If our sim's version indicates it hasn't been published, don't attempt to send a request for now if ( this.ourVersion.isSimNotPublished ) { self.stateProperty.value = 'up-to-date'; return; } var req = new XMLHttpRequest(); if ( 'withCredentials' in req ) { // we'll be able to send the proper type of request, so we mark ourself as checking self.stateProperty.value = 'checking'; self.setTimeout(); req.onload = function() { self.clearTimeout(); try { var data = JSON.parse( req.responseText ); if ( data.error ) { console.log( 'Update check failure: ' + data.error ); self.stateProperty.value = 'offline'; } else { if ( self.updateURL ) { self.updateURL = data.updateURL; } self.latestVersion = SimVersion.parse( data.latestVersion, data.buildTimestamp ); if ( data.state === 'out-of-date' || data.state === 'up-to-date' ) { self.stateProperty.value = data.state; } else { console.log( 'Failed to get proper state: ' + data.state ); self.stateProperty.value = 'offline'; } } } catch( e ) { self.stateProperty.value = 'offline'; } }; req.onerror = function() { self.clearTimeout(); self.stateProperty.value = 'offline'; }; req.open( 'post', requestProtocolString + '//phet.colorado.edu/services/check-html-updates', true ); // enable CORS req.send( JSON.stringify( { api: '1.0', simulation: simName, locale: phet.joist.sim.locale, currentVersion: self.ourVersion.toString(), buildTimestamp: phet.chipper.buildTimestamp } ) ); } } } ); var singleton = new UpdateCheck(); joist.register( 'UpdateCheck', singleton ); return singleton; } ); // Copyright 2015, University of Colorado Boulder /** * Text that has a handler set up to open a link in a new window when clicked. * * @author Jonathan Olson */ define( 'JOIST/LinkText',['require','PHET_CORE/inherit','SCENERY/nodes/Text','SCENERY/input/ButtonListener','JOIST/joist'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Text = require( 'SCENERY/nodes/Text' ); var ButtonListener = require( 'SCENERY/input/ButtonListener' ); var joist = require( 'JOIST/joist' ); function LinkText( text, url, options ) { // defaults options = _.extend( { handleEvent: false // whether the up in the click event should be handled, e.g. to prevent the About dialog closing. }, options ); Text.call( this, text, _.extend( { fill: 'rgb(27,0,241)', // blue, like a typical hypertext link cursor: 'pointer', // a11y tagName: 'a', accessibleLabel: text }, options ) ); this.addInputListener( new ButtonListener( { fire: function( event ) { options.handleEvent && event.handle(); if ( !window.phet || !phet.chipper || !phet.chipper.queryParameters || phet.chipper.queryParameters.allowLinks ) { var newWindow = window.open( url, '_blank' ); // open in a new window/tab newWindow.focus(); } } } ) ); // a11y - open the link in the new tab when activated with a keyboard this.setAccessibleAttribute( 'href', url ); this.setAccessibleAttribute( 'target', '_blank' ); } joist.register( 'LinkText', LinkText ); return inherit( Text, LinkText ); } ); define("string!JOIST/updates.upToDate",function(){return window.phet.chipper.strings.get("JOIST/updates.upToDate");}); define("string!JOIST/updates.outOfDate",function(){return window.phet.chipper.strings.get("JOIST/updates.outOfDate");}); define("string!JOIST/updates.checking",function(){return window.phet.chipper.strings.get("JOIST/updates.checking");}); define("string!JOIST/updates.offline",function(){return window.phet.chipper.strings.get("JOIST/updates.offline");}); define("string!JOIST/updates.newVersionAvailable",function(){return window.phet.chipper.strings.get("JOIST/updates.newVersionAvailable");}); define("string!JOIST/updates.yourCurrentVersion",function(){return window.phet.chipper.strings.get("JOIST/updates.yourCurrentVersion");}); define("string!JOIST/updates.getUpdate",function(){return window.phet.chipper.strings.get("JOIST/updates.getUpdate");}); define("string!JOIST/updates.noThanks",function(){return window.phet.chipper.strings.get("JOIST/updates.noThanks");}); // Copyright 2015, University of Colorado Boulder /** * UI parts for update-related dialogs */ define( 'JOIST/UpdateNodes',['require','SCENERY/nodes/HBox','SCENERY/nodes/VBox','SCENERY/nodes/Text','SCENERY/nodes/Rectangle','PHETCOMMON/util/StringUtils','SCENERY_PHET/PhetFont','SUN/FontAwesomeNode','SUN/buttons/TextPushButton','SCENERY_PHET/SpinningIndicatorNode','SCENERY/nodes/VStrut','JOIST/UpdateCheck','JOIST/LinkText','JOIST/joist','string!JOIST/updates.upToDate','string!JOIST/updates.outOfDate','string!JOIST/updates.checking','string!JOIST/updates.offline','string!JOIST/updates.newVersionAvailable','string!JOIST/updates.yourCurrentVersion','string!JOIST/updates.getUpdate','string!JOIST/updates.noThanks'],function( require ) { 'use strict'; // modules var HBox = require( 'SCENERY/nodes/HBox' ); var VBox = require( 'SCENERY/nodes/VBox' ); var Text = require( 'SCENERY/nodes/Text' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var StringUtils = require( 'PHETCOMMON/util/StringUtils' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var FontAwesomeNode = require( 'SUN/FontAwesomeNode' ); var TextPushButton = require( 'SUN/buttons/TextPushButton' ); var SpinningIndicatorNode = require( 'SCENERY_PHET/SpinningIndicatorNode' ); var VStrut = require( 'SCENERY/nodes/VStrut' ); var UpdateCheck = require( 'JOIST/UpdateCheck' ); var LinkText = require( 'JOIST/LinkText' ); var joist = require( 'JOIST/joist' ); // strings var updatesUpToDateString = require( 'string!JOIST/updates.upToDate' ); var updatesOutOfDateString = require( 'string!JOIST/updates.outOfDate' ); var updatesCheckingString = require( 'string!JOIST/updates.checking' ); var updatesOfflineString = require( 'string!JOIST/updates.offline' ); var updatesNewVersionAvailableString = require( 'string!JOIST/updates.newVersionAvailable' ); var updatesYourCurrentVersionString = require( 'string!JOIST/updates.yourCurrentVersion' ); var updatesGetUpdateString = require( 'string!JOIST/updates.getUpdate' ); var updatesNoThanksString = require( 'string!JOIST/updates.noThanks' ); var updateTextFont = new PhetFont( 14 ); // Maximum width of the resulting update items var MAX_WIDTH = 550; var UpdateNodes = { /** * "Checking" state node. With two size options (if options.big == true, it will be bigger) * * @param {Object} [options] - passed to the Node * @returns {Node} the Checking node, with step( dt ) and stepListener (bound to the node itself) * @public (joist-internal) */ createCheckingNode: function( options ) { var spinningIndicatorNode = new SpinningIndicatorNode( { indicatorSize: options.big ? 24 : 18 } ); var checkingNode = new HBox( _.extend( { spacing: options.big ? 10 : 8, maxWidth: MAX_WIDTH, children: [ spinningIndicatorNode, new Text( updatesCheckingString, { font: new PhetFont( options.big ? 16 : 14 ), fontWeight: options.big ? 'bold' : 'normal' } ) ], // a11y tagName: 'p', accessibleLabel: updatesCheckingString }, options ) ); checkingNode.step = function( dt ) { if ( UpdateCheck.stateProperty === 'checking' ) { spinningIndicatorNode.step( dt ); } }; checkingNode.stepListener = checkingNode.step.bind( checkingNode ); return checkingNode; }, /** * "Up-to-date" state node * @param {Object} [options] - passed to the Node * @returns {Node} * @public (joist-internal) */ createUpToDateNode: function( options ) { return new HBox( _.extend( { spacing: 8, maxWidth: MAX_WIDTH, children: [ new Rectangle( 0, 0, 20, 20, 5, 5, { fill: '#5c3', scale: options.big ? 1.2 : 1, children: [ new FontAwesomeNode( 'check', { fill: '#fff', scale: 0.38, centerX: 10, centerY: 10 } ) ] } ), new Text( updatesUpToDateString, { font: new PhetFont( options.big ? 16 : 14 ), fontWeight: options.big ? 'bold' : 'normal' } ) ], // a11y tagName: 'p', accessibleLabel: updatesUpToDateString }, options ) ); }, /** * "Out-of-date" state node for the "About" dialog. * @param {Object} [options] - passed to the Node * @returns {Node} * @public (joist-internal) */ createOutOfDateAboutNode: function( options ) { return new HBox( _.extend( { spacing: 8, cursor: 'pointer', maxWidth: MAX_WIDTH, children: [ new FontAwesomeNode( 'warning_sign', { fill: '#E87600', scale: 0.5 } ), // "safety orange", according to Wikipedia new LinkText( updatesOutOfDateString, UpdateCheck.updateURL, { font: updateTextFont } ) ], // a11y tagName: 'div' }, options ) ); }, /** * "Out-of-date" state node for the "Check for udpate" dialog. * @param {UpdateDialog} dialog - the dialog, so that it can be closed with the "No thanks..." button * @param {string} ourVersionString * @param {string} latestVersionString * @param {Object} [options] - passed to the Node * @returns {Node} * @public (joist-internal) */ createOutOfDateDialogNode: function( dialog, ourVersionString, latestVersionString, options ) { return new VBox( _.extend( { spacing: 15, maxWidth: MAX_WIDTH, children: [ new VBox( { spacing: 5, align: 'left', children: [ new Text( StringUtils.format( updatesNewVersionAvailableString, latestVersionString ), { font: new PhetFont( 16 ), fontWeight: 'bold' } ), new Text( StringUtils.format( updatesYourCurrentVersionString, ourVersionString ), { font: updateTextFont } ) ] } ), new HBox( { spacing: 25, children: [ new TextPushButton( updatesGetUpdateString, { baseColor: '#6f6', font: updateTextFont, listener: function() { if ( !window.phet || !phet.chipper || !phet.chipper.queryParameters || phet.chipper.queryParameters.allowLinks ) { var newWindow = window.open( UpdateCheck.updateURL, '_blank' ); // open in a new window/tab newWindow && newWindow.focus(); } } } ), new TextPushButton( updatesNoThanksString, { baseColor: '#ddd', font: updateTextFont, listener: function() { dialog.hide(); // Closing the dialog is handled by the Dialog listener itself, no need to add code to close it here. } } ) ] } ) ] }, options ) ); }, /** * "Offline" state node * @param {Object} [options] - passed to the Node * @returns {Node} * @public (joist-internal) */ createOfflineNode: function( options ) { return new HBox( _.extend( { spacing: 0, maxWidth: MAX_WIDTH, children: [ new VStrut( 20 ), // spacer to match layout of other nodes new Text( updatesOfflineString, { font: new PhetFont( options.big ? 16 : 14 ), fontWeight: options.big ? 'bold' : 'normal' } ) ], // a11y tagName: 'p', accessibleLabel: updatesOfflineString }, options ) ); } }; joist.register( 'UpdateNodes', UpdateNodes ); return UpdateNodes; } ); // Copyright 2016, University of Colorado Boulder /** * * @author Andrew Adare (PhET Interactive Simulations) */ define( 'JOIST/TDialog',['require','ifphetio!PHET_IO/assertions/assertInstanceOf','JOIST/joist','ifphetio!PHET_IO/phetioInherit','SUN/TPanel'],function( require ) { 'use strict'; // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var joist = require( 'JOIST/joist' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var TPanel = require( 'SUN/TPanel' ); /** * @constructor * Wrapper type for phet/joist's Dialog * @param {Dialog} dialog - instance of Dialog * @param {string} phetioID - identifier string */ function TDialog( dialog, phetioID ) { TPanel.call( this, dialog, phetioID ); assertInstanceOf( dialog, phet.sun.Panel ); } phetioInherit( TPanel, 'TDialog', TDialog, {}, { documentation: 'A dialog panel' } ); joist.register( 'TDialog', TDialog ); return TDialog; } ); define("string!JOIST/versionPattern",function(){return window.phet.chipper.strings.get("JOIST/versionPattern");}); // Copyright 2013-2015, University of Colorado Boulder /** * Shows the About dialog. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/AboutDialog',['require','SCENERY/nodes/VBox','SCENERY/nodes/Text','SCENERY/nodes/Node','PHET_CORE/inherit','SCENERY/input/ButtonListener','SCENERY_PHET/PhetFont','PHETCOMMON/util/StringUtils','SCENERY/nodes/VStrut','JOIST/Dialog','PHET_CORE/Timer','JOIST/CreditsNode','JOIST/UpdateNodes','JOIST/UpdateCheck','JOIST/LinkText','SCENERY_PHET/RichText','SCENERY_PHET/MultiLineText','JOIST/packageJSON','JOIST/joist','JOIST/TDialog','string!JOIST/versionPattern'],function( require ) { 'use strict'; // modules var VBox = require( 'SCENERY/nodes/VBox' ); var Text = require( 'SCENERY/nodes/Text' ); var Node = require( 'SCENERY/nodes/Node' ); var inherit = require( 'PHET_CORE/inherit' ); var ButtonListener = require( 'SCENERY/input/ButtonListener' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var StringUtils = require( 'PHETCOMMON/util/StringUtils' ); var VStrut = require( 'SCENERY/nodes/VStrut' ); var Dialog = require( 'JOIST/Dialog' ); var Timer = require( 'PHET_CORE/Timer' ); var CreditsNode = require( 'JOIST/CreditsNode' ); var UpdateNodes = require( 'JOIST/UpdateNodes' ); var UpdateCheck = require( 'JOIST/UpdateCheck' ); var LinkText = require( 'JOIST/LinkText' ); var RichText = require( 'SCENERY_PHET/RichText' ); var MultiLineText = require( 'SCENERY_PHET/MultiLineText' ); var packageJSON = require( 'JOIST/packageJSON' ); var joist = require( 'JOIST/joist' ); var TDialog = require( 'JOIST/TDialog' ); // strings var versionPatternString = require( 'string!JOIST/versionPattern' ); // Maximum width of elements in the dialog var MAX_WIDTH = 550; /** * @param {string} name - The name of the simulation * @param {string} version - The version of the simulation * @param {string} credits - The credits for the simulation, or falsy to show no credits * @param {Brand} Brand * @param {string} locale - The locale string * @param {Node} phetButton - The PhET button in the navigation bar, receives focus when this dialog is closed * @param {Tandem} tandem * @constructor */ function AboutDialog( name, version, credits, Brand, locale, phetButton, tandem ) { var self = this; this.aboutDialogTandem = tandem; var children = []; var titleText = new Text( name, { font: new PhetFont( 28 ), maxWidth: MAX_WIDTH, tagName: 'h1', accessibleLabel: name } ); children.push( titleText ); var versionString = StringUtils.format( versionPatternString, version ); children.push( new Text( versionString, { font: new PhetFont( 20 ), maxWidth: MAX_WIDTH, tagName: 'p', accessibleLabel: versionString } ) ); if ( phet.chipper.buildTimestamp ) { children.push( new Text( phet.chipper.buildTimestamp, { font: new PhetFont( 13 ), maxWidth: MAX_WIDTH, tagName: 'p', accessibleLabel: phet.chipper.buildTimestamp } ) ); } if ( UpdateCheck.areUpdatesChecked ) { var positionOptions = { left: 0, top: 0 }; var checkingNode = UpdateNodes.createCheckingNode( positionOptions ); var upToDateNode = UpdateNodes.createUpToDateNode( positionOptions ); var outOfDateNode = UpdateNodes.createOutOfDateAboutNode( positionOptions ); var offlineNode = UpdateNodes.createOfflineNode( positionOptions ); // @private - Listener that should be called every frame where we are shown, with {number} dt as a single parameter. this.updateStepListener = checkingNode.stepListener; // @private - Listener that should be called whenever our update state changes (while we are displayed) this.updateVisibilityListener = function( state ) { checkingNode.visible = state === 'checking'; upToDateNode.visible = state === 'up-to-date'; outOfDateNode.visible = state === 'out-of-date'; offlineNode.visible = state === 'offline'; // a11y - make update content visible/invisible for screen readers by explicitly removing content // from the DOM, necessary because AT will ready hidden content in a Dialog. checkingNode.accessibleContentDisplayed = checkingNode.visible; upToDateNode.accessibleContentDisplayed = upToDateNode.visible; outOfDateNode.accessibleContentDisplayed = outOfDateNode.visible; offlineNode.accessibleContentDisplayed = offlineNode.visible; }; children.push( new Node( { children: [ checkingNode, upToDateNode, outOfDateNode, offlineNode ], maxWidth: MAX_WIDTH } ) ); } children.push( new VStrut( 15 ) ); // Show the brand name, if it exists if ( Brand.name ) { children.push( new RichText( Brand.name, { font: new PhetFont( 16 ), supScale: 0.5, supYOffset: 2, maxWidth: MAX_WIDTH, // a11y tagName: 'h2', accessibleLabel: Brand.name } ) ); } // Show the brand copyright statement, if it exists if ( Brand.copyright ) { var year = phet.chipper.buildTimestamp ? // defined for built versions phet.chipper.buildTimestamp.split( '-' )[0] : // e.g. "2017-04-20 19:04:59 UTC" -> "2017" new Date().getFullYear(); // in requirejs mode var copyright = StringUtils.fillIn( Brand.copyright, { year: year } ); children.push( new Text( copyright, { font: new PhetFont( 12 ), maxWidth: MAX_WIDTH, // a11y tagName: 'p', accessibleLabel: copyright } ) ); } // Optional additionalLicenseStatement, used in phet-io if ( Brand.additionalLicenseStatement ) { this.additionalLicenseStatement = new MultiLineText( Brand.additionalLicenseStatement, { font: new PhetFont( 10 ), fill: 'gray', align: 'left', maxWidth: MAX_WIDTH, tandem: tandem.createTandem( 'additionalLicenseStatement' ) } ); children.push( this.additionalLicenseStatement ); } // Add credits for specific brands if ( credits && ( Brand.id === 'phet' || Brand.id === 'phet-io' ) ) { children.push( new VStrut( 15 ) ); this.creditsNode = new CreditsNode( credits, tandem.createTandem( 'creditsNode' ), { maxWidth: MAX_WIDTH } ); children.push( this.creditsNode ); } // Show any links identified in the brand var links = Brand.getLinks( packageJSON.name, locale ); if ( links && links.length > 0 ) { children.push( new VStrut( 15 ) ); for ( var i = 0; i < links.length; i++ ) { var link = links[ i ]; children.push( new LinkText( link.text, link.url, { font: new PhetFont( 14 ), maxWidth: MAX_WIDTH } ) ); } } var content = new VBox( { align: 'left', spacing: 5, children: children, // a11y tagName: 'div' } ); Dialog.call( this, content, { modal: true, hasCloseButton: true, tandem: tandem.createSupertypeTandem(), focusOnCloseNode: phetButton, xMargin: 25, yMargin: 25 } ); // a11y - set label association so the title is read when focus enters the dialog titleText.setAriaLabelsNode( this ); // close it on a click this.addInputListener( new ButtonListener( { fire: self.hide.bind( self ) } ) ); tandem.addInstance( this, TDialog); } joist.register( 'AboutDialog', AboutDialog ); return inherit( Dialog, AboutDialog, { /** * Show the dialog * @public */ show: function() { if ( UpdateCheck.areUpdatesChecked ) { UpdateCheck.resetTimeout(); // Fire off a new update check if we were marked as offline or unchecked before, and we handle updates. if ( UpdateCheck.stateProperty.value === 'offline' || UpdateCheck.stateProperty.value === 'unchecked' ) { UpdateCheck.check(); } // Hook up our spinner listener when we're shown Timer.addStepListener( this.updateStepListener ); // Hook up our visibility listener UpdateCheck.stateProperty.link( this.updateVisibilityListener ); } Dialog.prototype.show.call( this ); }, /** * Hide the dialog ( basically disposing it because a new one is created is the dialog is opened again ) * @public */ hide: function() { // When hidden, this dialog is as good as disposed because it is never shown again if ( this.isShowing ) { Dialog.prototype.hide.call( this ); if ( UpdateCheck.areUpdatesChecked ) { // Disconnect our visibility listener UpdateCheck.stateProperty.unlink( this.updateVisibilityListener ); // Disconnect our spinner listener when we're hidden Timer.removeStepListener( this.updateStepListener ); } // Tandems should be removed at disposal. The 'hide' function is used as dispose for the AboutDialog this.aboutDialogTandem.removeInstance( this ); this.creditsNode && this.creditsNode.dispose(); this.additionalLicenseStatement && this.additionalLicenseStatement.dispose(); } } } ); } ); // Copyright 2016, University of Colorado Boulder /** * * @author Andrew Adare (PhET Interactive Simulations) */ define( 'JOIST/TOptionsDialog',['require','JOIST/TDialog','ifphetio!PHET_IO/assertions/assertInstanceOf','JOIST/joist','ifphetio!PHET_IO/phetioInherit'],function( require ) { 'use strict'; // modules var TDialog = require( 'JOIST/TDialog' ); // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var joist = require( 'JOIST/joist' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); /** * @constructor * Wrapper type for phet/joist's OptionsDialog * @param {Dialog} dialog - instance of OptionsDialog * @param {string} phetioID - identifier string */ function TOptionsDialog( dialog, phetioID ) { assertInstanceOf( dialog, phet.sun.Panel ); TDialog.call( this, dialog, phetioID ); } phetioInherit( TDialog, 'TOptionsDialog', TOptionsDialog, {}, { documentation: 'A dialog panel' } ); joist.register( 'TOptionsDialog', TOptionsDialog ); return TOptionsDialog; } ); define("string!JOIST/options.title",function(){return window.phet.chipper.strings.get("JOIST/options.title");}); // Copyright 2014-2017, University of Colorado Boulder /** * Shows an Options dialog that consists of sim-global options. * * @author Jonathan Olson */ define( 'JOIST/OptionsDialog',['require','SCENERY/nodes/Text','PHET_CORE/inherit','SCENERY_PHET/PhetFont','JOIST/Dialog','JOIST/joist','TANDEM/Tandem','JOIST/TOptionsDialog','string!JOIST/options.title'],function( require ) { 'use strict'; // modules var Text = require( 'SCENERY/nodes/Text' ); var inherit = require( 'PHET_CORE/inherit' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var Dialog = require( 'JOIST/Dialog' ); var joist = require( 'JOIST/joist' ); var Tandem = require( 'TANDEM/Tandem' ); var TOptionsDialog = require( 'JOIST/TOptionsDialog' ); // strings var optionsTitleString = require( 'string!JOIST/options.title' ); /** * @param {Node} optionsNode * @param {Object} [options] * @constructor */ function OptionsDialog( optionsNode, options ) { options = _.extend( { title: new Text( optionsTitleString, { font: new PhetFont( 30 ), maxWidth: 400 } ), titleAlign: 'center', modal: true, hasCloseButton: true, tandem: Tandem.tandemRequired() }, options ); var thisTandem = options.tandem; options.tandem = options.tandem.createSupertypeTandem(); Dialog.call( this, optionsNode, options ); thisTandem.addInstance( this, TOptionsDialog ); this.disposeOptionsDialog = function() { thisTandem.removeInstance( this ); }; } joist.register( 'OptionsDialog', OptionsDialog ); return inherit( Dialog, OptionsDialog, { /** * Override Dialog's hide function to properly dispose what needs to be disposed on hide. * @public */ hide: function() { this.disposeOptionsDialog(); Dialog.prototype.hide.call( this ); } }, { DEFAULT_FONT: new PhetFont( 15 ), DEFAULT_SPACING: 10 } ); } ); // Copyright 2015, University of Colorado Boulder /** * Shows a fixed-size dialog that displays the current update status * * @author Jonathan Olson */ define( 'JOIST/UpdateDialog',['require','SCENERY/nodes/Node','PHET_CORE/inherit','SCENERY/input/ButtonListener','JOIST/Dialog','PHET_CORE/Timer','JOIST/UpdateNodes','JOIST/UpdateCheck','JOIST/joist'],function( require ) { 'use strict'; // modules var Node = require( 'SCENERY/nodes/Node' ); var inherit = require( 'PHET_CORE/inherit' ); var ButtonListener = require( 'SCENERY/input/ButtonListener' ); var Dialog = require( 'JOIST/Dialog' ); var Timer = require( 'PHET_CORE/Timer' ); var UpdateNodes = require( 'JOIST/UpdateNodes' ); var UpdateCheck = require( 'JOIST/UpdateCheck' ); var joist = require( 'JOIST/joist' ); /** * @param {PhETButton} phetButton - PhET button in the navigation bar, receives focus when this dialog is closed */ function UpdateDialog( phetButton ) { assert && assert( UpdateCheck.areUpdatesChecked, 'Updates need to be checked for UpdateDialog to be created' ); var self = this; var positionOptions = { centerX: 0, centerY: 0, big: true }; var checkingNode = UpdateNodes.createCheckingNode( positionOptions ); var upToDateNode = UpdateNodes.createUpToDateNode( positionOptions ); var outOfDateNode = new Node(); var offlineNode = UpdateNodes.createOfflineNode( positionOptions ); function updateOutOfDateNode() { // fallback size placeholder for version var latestVersionString = UpdateCheck.latestVersion ? UpdateCheck.latestVersion.toString() : 'x.x.xx'; var ourVersionString = UpdateCheck.ourVersion.toString(); // a11y - dialog content contained in parent div so ARIA roles can be applied to all children outOfDateNode.tagName = 'div'; outOfDateNode.children = [ UpdateNodes.createOutOfDateDialogNode( self, ourVersionString, latestVersionString, positionOptions ) ]; } updateOutOfDateNode(); // @private - Listener that should be called every frame where we are shown, with {number} dt as a single parameter. this.updateStepListener = checkingNode.stepListener; // Listener that should be called whenever our update state changes (while we are displayed) this.updateVisibilityListener = function( state ) { if ( state === 'out-of-date' ) { updateOutOfDateNode(); } checkingNode.visible = state === 'checking'; upToDateNode.visible = state === 'up-to-date'; outOfDateNode.visible = state === 'out-of-date'; offlineNode.visible = state === 'offline'; // a11y - update visibility of update nodes for screen readers by adding/removing content from the DOM, // necessary because screen readers will read hidden content in a Dialog checkingNode.accessibleContentDisplayed = checkingNode.visible; upToDateNode.accessibleContentDisplayed = upToDateNode.visible; outOfDateNode.accessibleContentDisplayed = outOfDateNode.visible; offlineNode.accessibleContentDisplayed = offlineNode.visible; }; var content = new Node( { children: [ checkingNode, upToDateNode, outOfDateNode, offlineNode ], // a11y tagName: 'div' } ); Dialog.call( this, content, { modal: true, hasCloseButton: true, // margins large enough to make space for close button xMargin: 30, yMargin: 30, // a11y focusOnCloseNode: phetButton } ); // close it on a click this.addInputListener( new ButtonListener( { fire: self.hide.bind( self ) } ) ); } joist.register( 'UpdateDialog', UpdateDialog ); return inherit( Dialog, UpdateDialog, { // @public (joist-internal) show: function() { if ( UpdateCheck.areUpdatesChecked ) { UpdateCheck.resetTimeout(); // Fire off a new update check if we were marked as offline or unchecked before, and we handle updates. if ( UpdateCheck.stateProperty.value === 'offline' || UpdateCheck.stateProperty === 'unchecked' ) { UpdateCheck.check(); } // Hook up our spinner listener when we're shown Timer.addStepListener( this.updateStepListener ); // Hook up our visibility listener UpdateCheck.stateProperty.link( this.updateVisibilityListener ); } Dialog.prototype.show.call( this ); }, // @public (joist-internal) hide: function() { // when hiding, dispose the dialog because a new one is created when the dialog opens again if ( this.isShowing ) { Dialog.prototype.hide.call( this ); if ( UpdateCheck.areUpdatesChecked ) { // Disconnect our visibility listener UpdateCheck.stateProperty.unlink( this.updateVisibilityListener ); // Disconnect our spinner listener when we're hidden Timer.removeStepListener( this.updateStepListener ); } } } } ); } ); // Copyright 2016, University of Colorado Boulder /** * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) */ define( 'SUN/TMenuItem',['require','ifphetio!PHET_IO/assertions/assertInstanceOf','ifphetio!PHET_IO/phetioInherit','SUN/sun','SCENERY/nodes/TNode','ifphetio!PHET_IO/events/toEventOnEmit'],function( require ) { 'use strict'; // modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var sun = require( 'SUN/sun' ); var TNode = require( 'SCENERY/nodes/TNode' ); var toEventOnEmit = require( 'ifphetio!PHET_IO/events/toEventOnEmit' ); /** * Wrapper type for phet/sun's MenuItem * @param menuItem * @param phetioID * @constructor */ function TMenuItem( menuItem, phetioID ) { assertInstanceOf( menuItem, phet.scenery.Node ); TNode.call( this, menuItem, phetioID ); // MenuItem from Sun, it is defined in PhetMenu.js and does not have its own type toEventOnEmit( menuItem.startedCallbacksForFiredEmitter, menuItem.endedCallbacksForFiredEmitter, 'user', phetioID, this.constructor, 'fired' ); } phetioInherit( TNode, 'TMenuItem', TMenuItem, {}, { documentation: 'The item buttons shown in a popup menu', events: [ 'fired' ] } ); sun.register( 'TMenuItem', TMenuItem ); return TMenuItem; } ); // Copyright 2017, University of Colorado Boulder /** * Class for an item that is listed in the PhetMenu * See PhetMenu.js for more information * * @author - Michael Kauzmann (PhET Interactive Simulations) */ define( 'SUN/MenuItem',['require','PHET_CORE/inherit','SUN/sun','SUN/FontAwesomeNode','AXON/Emitter','SCENERY_PHET/PhetFont','SCENERY/input/ButtonListener','SCENERY/nodes/Rectangle','SCENERY/nodes/Node','SCENERY/nodes/Text','TANDEM/Tandem','SCENERY/accessibility/AccessibilityUtil','SUN/TMenuItem'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var sun = require( 'SUN/sun' ); var FontAwesomeNode = require( 'SUN/FontAwesomeNode' ); var Emitter = require( 'AXON/Emitter' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var ButtonListener = require( 'SCENERY/input/ButtonListener' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var Node = require( 'SCENERY/nodes/Node' ); var Text = require( 'SCENERY/nodes/Text' ); var Tandem = require( 'TANDEM/Tandem' ); var AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' ); // phet-io modules var TMenuItem = require( 'SUN/TMenuItem' ); // the check mark used for toggle-able menu items var CHECK_MARK_NODE = new FontAwesomeNode( 'check', { fill: 'rgba(0,0,0,0.7)', scale: 0.4 } ); // constants var FONT_SIZE = 18; var HIGHLIGHT_COLOR = '#a6d2f4'; var MAX_ITEM_WIDTH = 400; var CHECK_PADDING = 2; // padding between the check and text var CHECK_OFFSET = CHECK_MARK_NODE.width + CHECK_PADDING; // offset that includes the check mark's width and padding var LEFT_X_MARGIN = 2; var RIGHT_X_MARGIN = 5; var Y_MARGIN = 3; var CORNER_RADIUS = 5; /** * @param {Number} width - the width of the menu item * @param {Number} height - the height of the menu item * @param {Function} closeCallback - called when closing the dialog that the menu item opened * @param {String} text * @param {Function} callback * @param {Object} [options] * @constructor */ function MenuItem( width, height, closeCallback, text, callback, options ) { var self = this; // Extend the object with defaults. options = _.extend( { tandem: Tandem.tandemRequired(), textFill: 'black', // a11y tagName: 'button', focusAfterCallback: false // whether or not next focusable element should receive focus after the callback }, options ); Node.call( this ); var textNode = new Text( text, { font: new PhetFont( FONT_SIZE ), fill: options.textFill, maxWidth: MAX_ITEM_WIDTH, tandem: options.tandem.createTandem( 'textNode' ) } ); var highlight = new Rectangle( 0, 0, width + LEFT_X_MARGIN + RIGHT_X_MARGIN + CHECK_OFFSET, height + Y_MARGIN + Y_MARGIN, CORNER_RADIUS, CORNER_RADIUS ); this.addChild( highlight ); this.addChild( textNode ); textNode.left = highlight.left + LEFT_X_MARGIN + CHECK_OFFSET; // text is left aligned textNode.centerY = highlight.centerY; // @public (phet-io) this.startedCallbacksForFiredEmitter = new Emitter(); this.endedCallbacksForFiredEmitter = new Emitter(); this.addInputListener( { enter: function() { highlight.fill = HIGHLIGHT_COLOR; }, exit: function() { highlight.fill = null; } } ); var fire = function( event ) { self.startedCallbacksForFiredEmitter.emit(); closeCallback( event ); callback( event ); self.endedCallbacksForFiredEmitter.emit(); }; this.addInputListener( new ButtonListener( { fire: fire } ) ); // @public (sun) this.separatorBefore = options.separatorBefore; // if there is a check-mark property, add the check mark and hook up visibility changes var checkListener; if ( options.checkedProperty ) { var checkMarkWrapper = new Node( { children: [ CHECK_MARK_NODE ], right: textNode.left - CHECK_PADDING, centerY: textNode.centerY } ); checkListener = function( isChecked ) { checkMarkWrapper.visible = isChecked; }; options.checkedProperty.link( checkListener ); this.addChild( checkMarkWrapper ); } // a11y - activate the item when selected with the keyboard var clickListener = this.addAccessibleInputListener( { click: function( event ) { fire(); // limit search of next focusable to root accessible HTML element var rootElement = phet.joist.display.accessibleDOMElement; options.focusAfterCallback && AccessibilityUtil.getNextFocusable( rootElement ).focus(); } } ); this.mutate( { cursor: 'pointer', tandem: options.tandem, phetioType: TMenuItem, // a11y parentContainerTagName: 'li', parentContainerAriaRole: 'none', // this is required for JAWS to handle focus correctly, see https://github.com/phetsims/john-travoltage/issues/225 accessibleLabel: text, ariaRole: 'menuitem', tagName: options.tagName } ); // @private - dispose the menu item this.disposeMenuItem = function() { if ( options.checkedProperty ) { options.checkedProperty.unlink( checkListener ); } self.removeAccessibleInputListener( clickListener ); }; } sun.register( 'MenuItem', MenuItem ); return inherit( Node, MenuItem, { // @public - dispose the menu item when it will no longer be used. dispose: function() { this.disposeMenuItem(); Node.prototype.dispose.call( this ); } } ); } ); // Copyright 2015, University of Colorado Boulder /** * Generate a rasterized screenshot for a simulation using scenery's built-in machinery. * Used in phet-io as well as PhetMenu (optionally) * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/ScreenshotGenerator',['require','PHET_CORE/inherit','SCENERY/util/CanvasContextWrapper','JOIST/joist'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var CanvasContextWrapper = require( 'SCENERY/util/CanvasContextWrapper' ); var joist = require( 'JOIST/joist' ); /** * * @constructor */ function ScreenshotGenerator() { } joist.register( 'ScreenshotGenerator', ScreenshotGenerator ); return inherit( Object, ScreenshotGenerator, {}, { /** * Given a sim, generate a screenshot as a data url * @param {Sim} sim * @param {string} [mimeType] - String for the image mimeType, defaults to 'image/png' * @returns {string} dataURL * @public */ generateScreenshot: function( sim, mimeType ) { // Default to PNG mimeType = mimeType || 'image/png'; // set up our Canvas with the correct background color var canvas = document.createElement( 'canvas' ); canvas.width = sim.display.width; canvas.height = sim.display.height; var context = canvas.getContext( '2d' ); context.fillStyle = sim.display.domElement.style.backgroundColor; context.fillRect( 0, 0, canvas.width, canvas.height ); var wrapper = new CanvasContextWrapper( canvas, context ); sim.rootNode.renderToCanvasSubtree( wrapper ); // get the data URL in PNG format var dataURL = canvas.toDataURL( mimeType ); return dataURL; } } ); } ); // Copyright 2016, University of Colorado Boulder /** * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) */ define( 'JOIST/TPhetMenu',['require','JOIST/joist','SCENERY/nodes/TNode','ifphetio!PHET_IO/assertions/assertInstanceOf','ifphetio!PHET_IO/phetioInherit'],function( require ) { 'use strict'; // modules var joist = require( 'JOIST/joist' ); var TNode = require( 'SCENERY/nodes/TNode' ); // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); /** * Wrapper type for phet/joist's PhetMenu * @param phetMenu * @param phetioID * @constructor */ function TPhetMenu( phetMenu, phetioID ) { TNode.call( this, phetMenu, phetioID ); assertInstanceOf( phetMenu, phet.joist.PhetMenu ); } phetioInherit( TNode, 'TPhetMenu', TPhetMenu, {}, { documentation: 'The PhET Menu in the bottom right of the screen', event: [ 'fired' ] } ); joist.register( 'TPhetMenu', TPhetMenu ); return TPhetMenu; } ); define("string!JOIST/menuItem.options",function(){return window.phet.chipper.strings.get("JOIST/menuItem.options");}); define("string!JOIST/menuItem.about",function(){return window.phet.chipper.strings.get("JOIST/menuItem.about");}); define("string!JOIST/menuItem.mailInputEventsLog",function(){return window.phet.chipper.strings.get("JOIST/menuItem.mailInputEventsLog");}); define("string!JOIST/menuItem.outputInputEventsLog",function(){return window.phet.chipper.strings.get("JOIST/menuItem.outputInputEventsLog");}); define("string!JOIST/menuItem.phetWebsite",function(){return window.phet.chipper.strings.get("JOIST/menuItem.phetWebsite");}); define("string!JOIST/menuItem.reportAProblem",function(){return window.phet.chipper.strings.get("JOIST/menuItem.reportAProblem");}); define("string!JOIST/menuItem.screenshot",function(){return window.phet.chipper.strings.get("JOIST/menuItem.screenshot");}); define("string!JOIST/menuItem.fullscreen",function(){return window.phet.chipper.strings.get("JOIST/menuItem.fullscreen");}); define("string!JOIST/menuItem.getUpdate",function(){return window.phet.chipper.strings.get("JOIST/menuItem.getUpdate");}); define("string!JOIST/menuItem.submitInputEventsLog",function(){return window.phet.chipper.strings.get("JOIST/menuItem.submitInputEventsLog");}); // Copyright 2013-2015, University of Colorado Boulder /** * The 'PhET' menu, which appears in the bottom-right of the home screen and the navigation bar, with options like * "PhET Website", "Settings", etc. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/PhetMenu',['require','PHET_CORE/platform','SCENERY/nodes/Node','KITE/Shape','SCENERY/nodes/Path','SCENERY/nodes/Text','PHET_CORE/inherit','JOIST/AboutDialog','JOIST/OptionsDialog','JOIST/UpdateDialog','SUN/MenuItem','SCENERY/nodes/Rectangle','SCENERY_PHET/PhetFont','JOIST/FullScreen','BRAND/Brand','JOIST/ScreenshotGenerator','JOIST/UpdateCheck','JOIST/joist','PHETCOMMON/util/StringUtils','AXON/DerivedProperty','SCENERY/input/Input','SCENERY/display/Display','SCENERY/accessibility/AccessibilityUtil','JOIST/TPhetMenu','string!JOIST/menuItem.options','string!JOIST/menuItem.about','string!JOIST/menuItem.mailInputEventsLog','string!JOIST/menuItem.outputInputEventsLog','string!JOIST/menuItem.phetWebsite','string!JOIST/menuItem.reportAProblem','string!JOIST/menuItem.screenshot','string!JOIST/menuItem.fullscreen','string!JOIST/menuItem.getUpdate','string!JOIST/menuItem.submitInputEventsLog'],function( require ) { 'use strict'; // modules var platform = require( 'PHET_CORE/platform' ); var Node = require( 'SCENERY/nodes/Node' ); var Shape = require( 'KITE/Shape' ); var Path = require( 'SCENERY/nodes/Path' ); var Text = require( 'SCENERY/nodes/Text' ); var inherit = require( 'PHET_CORE/inherit' ); var AboutDialog = require( 'JOIST/AboutDialog' ); var OptionsDialog = require( 'JOIST/OptionsDialog' ); var UpdateDialog = require( 'JOIST/UpdateDialog' ); var MenuItem = require( 'SUN/MenuItem' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var FullScreen = require( 'JOIST/FullScreen' ); var Brand = require( 'BRAND/Brand' ); var ScreenshotGenerator = require( 'JOIST/ScreenshotGenerator' ); var UpdateCheck = require( 'JOIST/UpdateCheck' ); var joist = require( 'JOIST/joist' ); var StringUtils = require( 'PHETCOMMON/util/StringUtils' ); var DerivedProperty = require( 'AXON/DerivedProperty' ); var Input = require( 'SCENERY/input/Input' ); var Display = require( 'SCENERY/display/Display' ); var AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' ); var TPhetMenu = require( 'JOIST/TPhetMenu' ); // strings var menuItemOptionsString = require( 'string!JOIST/menuItem.options' ); var menuItemAboutString = require( 'string!JOIST/menuItem.about' ); var menuItemMailInputEventsLogString = require( 'string!JOIST/menuItem.mailInputEventsLog' ); var menuItemOutputInputEventsLogString = require( 'string!JOIST/menuItem.outputInputEventsLog' ); var menuItemPhetWebsiteString = require( 'string!JOIST/menuItem.phetWebsite' ); var menuItemReportAProblemString = require( 'string!JOIST/menuItem.reportAProblem' ); var menuItemScreenshotString = require( 'string!JOIST/menuItem.screenshot' ); var menuItemFullscreenString = require( 'string!JOIST/menuItem.fullscreen' ); var menuItemGetUpdateString = require( 'string!JOIST/menuItem.getUpdate' ); var menuItemSubmitInputEventsLogString = require( 'string!JOIST/menuItem.submitInputEventsLog' ); // constants var FONT_SIZE = 18; var MAX_ITEM_WIDTH = 400; // For disabling features that are incompatible with fuzzMouse var fuzzMouse = phet.chipper.queryParameters.fuzzMouse; // Creates a comic-book style bubble. var createBubble = function( width, height ) { var rectangle = new Rectangle( 0, 0, width, height, 8, 8, { fill: 'white', lineWidth: 1, stroke: 'black' } ); var tail = new Shape(); tail.moveTo( width - 20, height - 2 ); tail.lineToRelative( 0, 20 ); tail.lineToRelative( -20, -20 ); tail.close(); var tailOutline = new Shape(); tailOutline.moveTo( width - 20, height ); tailOutline.lineToRelative( 0, 20 - 2 ); tailOutline.lineToRelative( -18, -18 ); var bubble = new Node(); bubble.addChild( rectangle ); bubble.addChild( new Path( tail, { fill: 'white' } ) ); bubble.addChild( new Path( tailOutline, { stroke: 'black', lineWidth: 1 } ) ); return bubble; }; /** * @param {Sim} sim * @param {Tandem} tandem * @param {Object} [options] * @constructor */ function PhetMenu( sim, tandem, options ) { //Only show certain features for PhET Sims, such as links to our website var isPhETBrand = Brand.id === 'phet'; var isPhetApp = Brand.isPhetApp; options = _.extend( { //For sims that have save/load enabled, show menu items for those. showSaveAndLoad: false }, options ); var self = this; Node.call( self ); /* * Description of the items in the menu. See Menu Item for a list of properties for each itemDescriptor */ var itemDescriptors = [ { text: menuItemOptionsString, present: !!sim.options.optionsNode, callback: function() { new OptionsDialog( sim.options.optionsNode, { tandem: tandem.createTandem( 'optionsDialog' ) } ).show(); }, tandem: tandem.createTandem( 'optionsMenuItem' ), // a11y tagName: 'button', focusAfterCallback: true }, { text: menuItemPhetWebsiteString, tandem: tandem.createTandem( 'phetWebsiteMenuItem' ), present: isPhETBrand, callback: function() { if ( !fuzzMouse ) { // Open locale-specific PhET home page. If there is no website translation for locale, fallback will be handled by server. See joist#97. if ( !window.phet || !phet.chipper || !phet.chipper.queryParameters || phet.chipper.queryParameters.allowLinks ) { var phetWindow = window.open( 'https://phet.colorado.edu/' + sim.locale, '_blank' ); phetWindow && phetWindow.focus(); } } }, // a11y tagName: 'button' }, { text: menuItemOutputInputEventsLogString, present: !!sim.options.recordInputEventLog, callback: function() { // prints the recorded input event log to the console console.log( sim.getRecordedInputEventLogString() ); }, tagName: 'button' }, { text: menuItemSubmitInputEventsLogString, present: !!sim.options.recordInputEventLog, callback: function() { // submits a recorded event log to the same-origin server (run scenery/tests/event-logs/server/server.js with Node, from the same directory) sim.submitEventLog(); }, tagName: 'button' }, { text: menuItemMailInputEventsLogString, present: !!sim.options.recordInputEventLog, callback: function() { // mailto: link including the body to email sim.mailEventLog(); }, tagName: 'button' }, { text: menuItemReportAProblemString, present: isPhETBrand && !isPhetApp, callback: function() { // Create a smaller version of our dependencies to send, due to the URL length issues. // See https://github.com/phetsims/joist/issues/249. var dependenciesCopy = phet.chipper.dependencies ? JSON.parse( JSON.stringify( phet.chipper.dependencies ) ) : {}; delete dependenciesCopy.comment; for ( var key in dependenciesCopy ) { if ( dependenciesCopy[ key ].sha ) { dependenciesCopy[ key ].sha = dependenciesCopy[ key ].sha.substring( 0, 8 ); } } var url = 'https://phet.colorado.edu/files/troubleshooting/' + '?sim=' + encodeURIComponent( sim.name ) + '&version=' + encodeURIComponent( sim.version + ' ' + ( phet.chipper.buildTimestamp ? phet.chipper.buildTimestamp : '(require.js)' ) ) + '&url=' + encodeURIComponent( window.location.href ) + '&dependencies=' + encodeURIComponent( JSON.stringify( dependenciesCopy ) ); if ( !fuzzMouse ) { if ( !window.phet || !phet.chipper || !phet.chipper.queryParameters || phet.chipper.queryParameters.allowLinks ) { var reportWindow = window.open( url, '_blank' ); reportWindow && reportWindow.focus(); } } }, tandem: tandem.createTandem( 'reportAProblemMenuItem' ), tagName: 'button' }, { text: 'QR code', present: phet.chipper.queryParameters.qrCode, callback: function() { if ( !fuzzMouse ) { var win = window.open( 'http://api.qrserver.com/v1/create-qr-code/?data=' + encodeURIComponent( window.location.href ) + '&size=220x220&margin=0', '_blank' ); win && win.focus(); } }, tandem: tandem.createTandem( 'qrCodeMenuItem' ), // a11y tagName: 'button' }, { text: menuItemGetUpdateString, present: UpdateCheck.areUpdatesChecked, textFill: new DerivedProperty( [ UpdateCheck.stateProperty ], function( state ) { return state === 'out-of-date' ? '#0a0' : '#000'; } ), callback: function() { var phetButton = sim.navigationBar.phetButton; new UpdateDialog( phetButton ).show(); }, tandem: tandem.createTandem( 'getUpdateMenuItem' ), // a11y tagName: 'button', focusAfterCallback: true }, // "Screenshot" Menu item { text: menuItemScreenshotString, present: !platform.ie9 && !isPhetApp, // Not supported by IE9, see https://github.com/phetsims/joist/issues/212 callback: function() { var dataURL = ScreenshotGenerator.generateScreenshot( sim ); // if we have FileSaver support if ( window.Blob && !!new window.Blob() ) { // construct a blob out of it var requiredPrefix = 'data:image/png;base64,'; assert && assert( dataURL.slice( 0, requiredPrefix.length ) === requiredPrefix ); var dataBase64 = dataURL.slice( requiredPrefix.length ); var byteChars = window.atob( dataBase64 ); var byteArray = new window.Uint8Array( byteChars.length ); for ( var i = 0; i < byteArray.length; i++ ) { byteArray[ i ] = byteChars.charCodeAt( i ); // need check to make sure this cast doesn't give problems? } var blob = new window.Blob( [ byteArray ], { type: 'image/png' } ); // our preferred filename var filename = StringUtils.stripEmbeddingMarks( sim.name ) + ' screenshot.png'; if ( !fuzzMouse ) { window.saveAs( blob, filename ); } } else if ( !fuzzMouse ) { window.open( dataURL, '_blank', '' ); } }, tandem: tandem.createTandem( 'screenshotMenuItem' ), tagName: 'button' }, { text: menuItemFullscreenString, present: FullScreen.isFullScreenEnabled() && !isPhetApp && !fuzzMouse && !platform.mobileSafari, checkedProperty: FullScreen.isFullScreenProperty, callback: function() { FullScreen.toggleFullScreen( sim ); }, tandem: tandem.createTandem( 'fullScreenMenuItem' ), tagName: 'button' }, //About dialog button { text: menuItemAboutString, present: true, separatorBefore: isPhETBrand, callback: function() { var phetButton = sim.navigationBar.phetButton; new AboutDialog( sim.name, sim.version, sim.credits, Brand, sim.locale, phetButton, tandem.createTandem( 'aboutDialog' ) ).show(); }, tandem: tandem.createTandem( 'aboutMenuItem' ), tagName: 'button', focusAfterCallback: true } ]; // Menu items have uniform size, so compute the max text dimensions. These are only used for sizing and thus don't // need to be tandemized. var keepItemDescriptors = _.filter( itemDescriptors, function( itemDescriptor ) {return itemDescriptor.present;} ); var textNodes = _.map( keepItemDescriptors, function( item ) { return new Text( item.text, { font: new PhetFont( FONT_SIZE ), maxWidth: MAX_ITEM_WIDTH } ); } ); var maxTextWidth = _.maxBy( textNodes, function( node ) {return node.width;} ).width; var maxTextHeight = _.maxBy( textNodes, function( node ) {return node.height;} ).height; // Create the menu items. var items = this.items = _.map( keepItemDescriptors, function( itemDescriptor ) { return new MenuItem( maxTextWidth, maxTextHeight, options.closeCallback, itemDescriptor.text, itemDescriptor.callback, { textFill: itemDescriptor.textFill, checkedProperty: itemDescriptor.checkedProperty, separatorBefore: itemDescriptor.separatorBefore, tandem: itemDescriptor.tandem, tagName: itemDescriptor.tagName, focusAfterCallback: itemDescriptor.focusAfterCallback } ); } ); var separatorWidth = _.maxBy( items, function( item ) {return item.width;} ).width; var itemHeight = _.maxBy( items, function( item ) {return item.height;} ).height; var content = new Node(); var y = 0; var ySpacing = 2; var separator; _.each( items, function( item ) { // Don't add a separator for the first item if ( item.separatorBefore && items[ 0 ] !== item ) { y += ySpacing; separator = new Path( Shape.lineSegment( 0, y, separatorWidth, y ), { stroke: 'gray', lineWidth: 1 } ); content.addChild( separator ); y = y + separator.height + ySpacing; } item.top = y; content.addChild( item ); y += itemHeight; } ); // Create a comic-book-style bubble. var X_MARGIN = 5; var Y_MARGIN = 5; var bubble = createBubble( content.width + X_MARGIN + X_MARGIN, content.height + Y_MARGIN + Y_MARGIN ); self.addChild( bubble ); self.addChild( content ); content.left = X_MARGIN; content.top = Y_MARGIN; // @private (PhetButton.js) - whether the PhetMenu is showing this.isShowing = false; // a11y, tagname and role for content in the menu this.tagName = 'ul'; this.ariaRole = 'menu'; // a11y - add the keydown listener, handling arrow, escape, and tab keys // When using the arrow keys, we prevent the virtual cursor from moving in VoiceOver var keydownListener = this.addAccessibleInputListener( { keydown: function( event ) { var firstItem = self.items[ 0 ]; var lastItem = self.items[ self.items.length - 1 ]; // this attempts to prevents the scren reader's virtual cursor from also moving with the arrow keys if ( Input.isArrowKey( event.keyCode ) ) { event.preventDefault(); } if ( event.keyCode === Input.KEY_DOWN_ARROW ) { // On down arrow, focus next item in the list, or wrap up to the first item if focus is at the end var nextFocusable = lastItem.focussed ? firstItem : AccessibilityUtil.getNextFocusable(); nextFocusable.focus(); } else if ( event.keyCode === Input.KEY_UP_ARROW ) { // On up arow, focus previous item in the list, or wrap back to the last item if focus is on first item var previousFocusable = firstItem.focussed ? lastItem : AccessibilityUtil.getPreviousFocusable(); previousFocusable.focus(); } else if ( event.keyCode === Input.KEY_ESCAPE ) { // On escape, close the menu and focus the PhET button options.closeCallback(); sim.navigationBar.phetButton.focus(); } else if ( event.keyCode === Input.KEY_TAB ) { // close the menu whenever the user tabs out of it options.closeCallback(); // send focus back to the phet button - the browser should then focus the next/previous focusable // element with default 'tab' behavior sim.navigationBar.phetButton.focus(); } } } ); // a11y - if the focus goes to something outside of the PhET menu, close it var focusListener = function( focus ) { if ( focus && !_.includes( focus.trail.nodes, self ) ) { self.hide(); } }; Display.focusProperty.lazyLink( focusListener ); tandem.addInstance( this, TPhetMenu ); this.disposePhetMenu = function() { tandem.removeInstance( self ); self.removeAccessibleInputListener( keydownListener ); Display.focusProperty.unlink( focusListener ); }; } joist.register( 'PhetMenu', PhetMenu ); inherit( Node, PhetMenu, { // @public show: function() { if ( !this.isShowing ) { // make sure that any previously focused elements no longer have focus Display.focusProperty.set( null ); window.phet.joist.sim.showPopup( this, true ); this.isShowing = true; } }, // @public hide: function() { if ( this.isShowing ) { this.isShowing = false; window.phet.joist.sim.hidePopup( this, true ); } }, // @public (joist-internal) dispose: function() { this.disposePhetMenu(); _.each( this.items, function( item ) { item.dispose(); } ); Node.prototype.dispose.call( this ); } } ); return PhetMenu; } ); define("mipmap!BRAND/logo.png", function(){ var mipmaps = window.phet.chipper.mipmaps["BRAND/logo.png"]; window.phetImages = window.phetImages || [] mipmaps.forEach( function( mipmap ) { mipmap.img = new Image(); window.phetImages.push( mipmap.img ); mipmap.img.src = mipmap.url; } ); return mipmaps; } ); define("mipmap!BRAND/logo-on-white.png", function(){ var mipmaps = window.phet.chipper.mipmaps["BRAND/logo-on-white.png"]; window.phetImages = window.phetImages || [] mipmaps.forEach( function( mipmap ) { mipmap.img = new Image(); window.phetImages.push( mipmap.img ); mipmap.img.src = mipmap.url; } ); return mipmaps; } ); // Copyright 2013-2015, University of Colorado Boulder /** * The button that pops up the PhET menu, which appears in the bottom right of the home screen and on the right side * of the navbar. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/PhetButton',['require','SCENERY/nodes/Node','SCENERY/nodes/Image','KITE/Shape','SCENERY/nodes/Path','PHET_CORE/inherit','JOIST/PhetMenu','AXON/Property','JOIST/JoistButton','JOIST/UpdateCheck','SCENERY/util/TransformTracker','JOIST/JoistA11yStrings','JOIST/joist','mipmap!BRAND/logo.png','mipmap!BRAND/logo-on-white.png'],function( require ) { 'use strict'; // modules var Node = require( 'SCENERY/nodes/Node' ); var Image = require( 'SCENERY/nodes/Image' ); var Shape = require( 'KITE/Shape' ); var Path = require( 'SCENERY/nodes/Path' ); var inherit = require( 'PHET_CORE/inherit' ); var PhetMenu = require( 'JOIST/PhetMenu' ); var Property = require( 'AXON/Property' ); var JoistButton = require( 'JOIST/JoistButton' ); var UpdateCheck = require( 'JOIST/UpdateCheck' ); var TransformTracker = require( 'SCENERY/util/TransformTracker' ); var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' ); var joist = require( 'JOIST/joist' ); // strings var phetString = JoistA11yStrings.phetString; // images // The logo images are loaded from the brand which is selected via query parameter (during requirejs mode) // or a grunt option (during the build), please see initialize-globals.js window.phet.chipper.brand for more // details var brightLogoMipmap = require( 'mipmap!BRAND/logo.png' ); // on a black navbar var darkLogoMipmap = require( 'mipmap!BRAND/logo-on-white.png' ); // on a white navbar // Accommodate logos of any height by scaling them down proportionately. // The primary logo is 108px high and we have been scaling it at 0.28 to make it look good even on higher resolution // displays. The following math scales up the logo to 108px high so the rest of the layout code will work smoothly // Scale to the same height as the PhET logo, so that layout code works correctly. // height of the PhET logo, brand/phet/images/logo.png or brand/adapted-from-phet/images/logo.png var PHET_LOGO_HEIGHT = 108; var PHET_LOGO_SCALE = 0.28; // scale applied to the PhET logo assert && assert( brightLogoMipmap instanceof Array, 'logo must be a mipmap' ); var LOGO_SCALE = PHET_LOGO_SCALE / brightLogoMipmap[ 0 ].height * PHET_LOGO_HEIGHT; /** * @param {Sim} sim * @param {Property.} backgroundFillProperty * @param {Property.} textFillProperty * @param {Tandem} tandem * @constructor */ function PhetButton( sim, backgroundFillProperty, textFillProperty, tandem ) { var phetMenu = new PhetMenu( sim, tandem.createTandem( 'phetMenu' ), { showSaveAndLoad: sim.options.showSaveAndLoad, closeCallback: function() { phetMenu.hide(); } } ); /** * Sim.js handles scaling the popup menu. This code sets the position of the popup menu. * @param {Bounds2} bounds - the size of the window.innerWidth and window.innerHeight, which depends on the scale * @param {Bounds2} screenBounds - subtracts off the size of the navbar from the height * @param {number} scale - the overall scaling factor for elements in the view */ function onResize( bounds, screenBounds, scale ) { phetMenu.right = bounds.right / scale - 2 / scale; var navBarHeight = bounds.height - screenBounds.height; phetMenu.bottom = screenBounds.bottom / scale + navBarHeight / 2 / scale; } // sim.bounds are null on init, but we will get the callback when it is sized for the first time sim.resizedEmitter.addListener( onResize ); var options = { textDescription: 'PhET Menu Button', highlightExtensionWidth: 6, highlightExtensionHeight: 5, highlightCenterOffsetY: 4, listener: function() { phetMenu.show(); }, // a11y tagName: 'button', accessibleLabel: phetString }; // The PhET Label, which is the PhET logo var logoImage = new Image( brightLogoMipmap, { scale: LOGO_SCALE, pickable: false } ); var optionsShape = new Shape(); var optionsCircleRadius = 2.5; for ( var i = 0; i < 3; i++ ) { var circleOffset = i * 3.543 * optionsCircleRadius; optionsShape.arc( 0, circleOffset, optionsCircleRadius, 0, 2 * Math.PI, false ); } var optionsButton = new Path( optionsShape, { left: logoImage.width + 8, bottom: logoImage.bottom - 0.5, pickable: false } ); // The icon combines the PhET label and the thre horizontal bars in the right relative positions var icon = new Node( { children: [ logoImage, optionsButton ] } ); JoistButton.call( this, icon, backgroundFillProperty, tandem, options ); // a11y - the bounds of the button push the default highlight out of dev bounds this.focusHighlight = Shape.bounds( icon.bounds.dilated( 4 ) ); Property.multilink( [ backgroundFillProperty, sim.showHomeScreenProperty, UpdateCheck.stateProperty ], function( backgroundFill, showHomeScreen, updateState ) { var backgroundIsWhite = backgroundFill !== 'black' && !showHomeScreen; var outOfDate = updateState === 'out-of-date'; optionsButton.fill = backgroundIsWhite ? ( outOfDate ? '#0a0' : '#222' ) : ( outOfDate ? '#3F3' : 'white' ); logoImage.image = backgroundIsWhite ? darkLogoMipmap : brightLogoMipmap; } ); // a11y - add a listener that opens the menu on 'click' and 'reset', and closes it on escape and if the // button receives focus again this.addAccessibleInputListener( { click: function() { // open and set focus on the first item phetMenu.show(); phetMenu.items[ 0 ].focus(); } } ); // a11y - add an attribute that lets the user know the button opens a menu this.setAccessibleAttribute( 'aria-haspopup', true ); } joist.register( 'PhetButton', PhetButton ); return inherit( JoistButton, PhetButton, {}, { // @public - How much space between the PhetButton and the right side of the screen. HORIZONTAL_INSET: 10, // @ public - How much space between the PhetButton and the bottom of the screen VERTICAL_INSET: 0, /** * Ensures that the home-screen's phet button will have the same global transform as the navbar's phet button. * Listens to both sides (the navbar button, and the home-screen's button's parent) so that when either changes, * the transforms are synchronized by changing the home-screen's button position. * See https://github.com/phetsims/joist/issues/304. * @public (joist-internal) * * @param {HomeScreenView} homeScreen - The home screen view, where we will position the phet button. * @param {NavigationBar} navigationBar - The main navigation bar * @param {Node} rootNode - The root of the Display's node tree */ linkPhetButtonTransform: function( homeScreen, navigationBar, rootNode ) { var homeScreenButton = homeScreen.view.phetButton; var navBarButtonTracker = new TransformTracker( navigationBar.phetButton.getUniqueTrailTo( rootNode ), { isStatic: true // our listener won't change any listeners - TODO: replace with emitter? see https://github.com/phetsims/scenery/issues/594 } ); var homeScreenTracker = new TransformTracker( homeScreenButton.getParent().getUniqueTrailTo( rootNode ), { isStatic: true // our listener won't change any listeners - TODO: replace with emitter? see https://github.com/phetsims/scenery/issues/594 } ); function transformPhetButton() { // Ensure transform equality: navBarButton(global) = homeScreen(global) * homeScreenButton(self) homeScreenButton.matrix = homeScreenTracker.matrix.inverted().timesMatrix( navBarButtonTracker.matrix ); } // hook up listeners navBarButtonTracker.addListener( transformPhetButton ); homeScreenTracker.addListener( transformPhetButton ); // synchronize immediately, in case there are no more transform changes before display transformPhetButton(); } } ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * Decorative frame around the selected node * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/Frame',['require','PHET_CORE/inherit','SCENERY/nodes/Node','SCENERY/nodes/Rectangle','SCENERY/util/LinearGradient','JOIST/joist'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Node = require( 'SCENERY/nodes/Node' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var LinearGradient = require( 'SCENERY/util/LinearGradient' ); var joist = require( 'JOIST/joist' ); /** * @param {Node} content * @param {Object} [options] * @constructor */ function Frame( content, options ) { // default options options = _.extend( { xMargin1: 6, yMargin1: 6, cornerRadius: 0 // radius of the rounded corners on the background }, options ); Node.call( this ); var frameWidth = content.width + 2 * options.xMargin1; var frameHeight = content.height + 2 * options.yMargin1; // @private this.gradient = new LinearGradient( 0, 0, frameWidth, 0 ).addColorStop( 0, '#fbff41' ).addColorStop( 118 / 800.0, '#fef98b' ).addColorStop( 372 / 800.0, '#feff40' ).addColorStop( 616 / 800, '#fffccd' ).addColorStop( 1, '#fbff41' ); // @private this.rectangle = new Rectangle( 0, 0, frameWidth, frameHeight, options.cornerRadius, options.cornerRadius, { stroke: this.gradient, lineWidth: 3, x: content.x - options.xMargin1, y: content.y - options.yMargin1 } ); this.addChild( this.rectangle ); // Apply options after the layout is done, so that options that use the bounds will work properly. this.mutate( options ); // @private this.frameWidth = frameWidth; // @private this.frameHeight = frameHeight; } joist.register( 'Frame', Frame ); inherit( Node, Frame, { // @public setHighlighted: function( highlighted ) { this.rectangle.lineWidth = highlighted ? 4.5 : 3; //Make the frame larger when highlighted, but only so that it expands out if ( highlighted ) { this.rectangle.setRect( -1.5 / 2, -1.5 / 2, this.frameWidth + 1.5, this.frameHeight + 1.5 ); } else { this.rectangle.setRect( 0, 0, this.frameWidth, this.frameHeight ); } } } ); return Frame; } ); // Copyright 2016, University of Colorado Boulder /** * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) */ define( 'JOIST/TScreenButton',['require','JOIST/joist','SCENERY/nodes/TNode','ifphetio!PHET_IO/assertions/assertInstanceOf','ifphetio!PHET_IO/phetioInherit','ifphetio!PHET_IO/events/toEventOnEmit'],function( require ) { 'use strict'; // modules var joist = require( 'JOIST/joist' ); var TNode = require( 'SCENERY/nodes/TNode' ); // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var toEventOnEmit = require( 'ifphetio!PHET_IO/events/toEventOnEmit' ); /** * Wrapper type for phet/joist's ScreenButton * @param button * @param phetioID * @constructor */ function TScreenButton( button, phetioID ) { assertInstanceOf( button, phet.scenery.VBox ); TNode.call( this, button, phetioID ); toEventOnEmit( button.startedCallbacksForFiredEmitter, button.endedCallbacksForFiredEmitter, 'user', phetioID, this.constructor, 'fired' ); } phetioInherit( TNode, 'TScreenButton', TScreenButton, {}, { documentation: 'A pressable button in the simulation, in the home screen', events: [ 'fired' ] } ); joist.register( 'TScreenButton', TScreenButton ); return TScreenButton; } ); // Copyright 2017, University of Colorado Boulder /** * A ScreenButton is displayed on the HomeScreen. There are small and large ScreenButtons, that can be toggled through * to select the desired sim screen to go to. See HomeScreenView.js for more information. * * @author - Michael Kauzmann (PhET Interactive Simulations) */ define( 'JOIST/ScreenButton',['require','PHET_CORE/inherit','JOIST/joist','AXON/Emitter','SCENERY/nodes/Rectangle','SCENERY/nodes/VBox','SCENERY/nodes/Text','SCENERY_PHET/PhetFont','SCENERY/nodes/Node','JOIST/Frame','DOT/Util','SCENERY_PHET/PhetColorScheme','KITE/Shape','JOIST/TScreenButton'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var joist = require( 'JOIST/joist' ); var Emitter = require( 'AXON/Emitter' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var VBox = require( 'SCENERY/nodes/VBox' ); var Text = require( 'SCENERY/nodes/Text' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var Node = require( 'SCENERY/nodes/Node' ); var Frame = require( 'JOIST/Frame' ); var Util = require( 'DOT/Util' ); var PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' ); var Shape = require( 'KITE/Shape' ); var TScreenButton = require( 'JOIST/TScreenButton' ); // constants var LARGE_ICON_HEIGHT = 140; /** * @param {boolean} large - whether or not this is a large or small screenButton * @param {Sim} sim * @param {number} index - index of this screen so we can get the screen, sorta backwards * @param {Property} highlightedScreenIndexProperty * @param {Tandem} tandem * @param {Object} [options] * @constructor */ function ScreenButton( large, sim, index, highlightedScreenIndexProperty, tandem, options ) { var self = this; options = _.extend( { opacity: 1, // The small screen's nodes have an opacity of .5 tandem: tandem, // To be passed into mutate, but tandem should be a required param in joist phetioType: TScreenButton }, options ); var screen = sim.screens[ index ]; // @public this.startedCallbacksForFiredEmitter = new Emitter(); // @public this.endedCallbacksForFiredEmitter = new Emitter(); // Maps the number of screens to a scale for the small icons. The scale is percentage of LARGE_ICON_HEIGHT. var smallIconScale = Util.linear( 2, 4, 0.875, 0.50, sim.screens.length ); // Use the small icon scale if this is a small screen button var height = large ? LARGE_ICON_HEIGHT : smallIconScale * LARGE_ICON_HEIGHT; // Wrap in a Node because we're scaling, and the same icon will be used for small and large icon, and may be used by // the navigation bar. var icon = new Node( { opacity: options.opacity, children: [ screen.homeScreenIcon ], scale: height / screen.homeScreenIcon.height } ); // Frame for large var frame = large ? new Frame( icon ) : new Rectangle( 0, 0, icon.width, icon.height, { stroke: options.showSmallHomeScreenIconFrame ? '#dddddd' : null, lineWidth: 0.7 } ); // Create the icon with the frame inside var iconWithFrame = new Node( { opacity: options.opacity, children: [ frame, icon ] } ); // Text for the screen button var text = new Text( screen.name, { font: new PhetFont( large ? 42 : 18 ), fill: large ? PhetColorScheme.PHET_LOGO_YELLOW : 'gray', // Color match with the PhET Logo yellow tandem: tandem.createTandem( 'text' ) } ); // Shrink the text if it goes beyond the edge of the image if ( text.width > iconWithFrame.width ) { text.scale( iconWithFrame.width / text.width ); } // Only link if a large button highlightedScreenIndexProperty.link( function( highlightedIndex ) { var highlighted = highlightedIndex === index; frame.setHighlighted && frame.setHighlighted( highlighted ); icon.opacity = (large || highlighted) ? 1 : 0.5; text.fill = (large || highlighted) ? 'white' : 'gray'; } ); // The children are needed in the VBox constructor, but the rest of the options should be mutated later. VBox.call( this, { children: [ iconWithFrame, text ] } ); // Input listeners after the parent call depending on if the ScreenButton is large or small var buttonDown = large ? function() { sim.showHomeScreenProperty.value = false; highlightedScreenIndexProperty.value = -1; } : function() { sim.screenIndexProperty.value = index; }; this.addInputListener( { down: function( event ) { self.startedCallbacksForFiredEmitter.emit(); buttonDown(); self.endedCallbacksForFiredEmitter.emit(); } } ); // Set highlight listeners to the small screen button if ( !large ) { // @public (joist-internal) this.highlightListener = { over: function( event ) { highlightedScreenIndexProperty.value = index; }, out: function( event ) { highlightedScreenIndexProperty.value = -1; } }; // On the home screen if you touch an inactive screen thumbnail, it grows. If then without lifting your finger // you swipe over to the next thumbnail, that one would grow. this.addInputListener( { over: function( event ) { if ( event.pointer.isTouch ) { sim.screenIndexProperty.value = index; } } } ); } this.mouseArea = this.touchArea = Shape.bounds( this.bounds ); // cover the gap in the vbox this.disposeScreenButton = function() { highlightedScreenIndexProperty.unlink(); }; this.mutate( options ); } joist.register( 'ScreenButton', ScreenButton ); return inherit( VBox, ScreenButton, { // @public dispose: function() { this.disposeScreenButton(); VBox.prototype.dispose.call( this ); } } ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * Shows the home screen for a multi-screen simulation, which lets the user see all of the screens and select one. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/HomeScreenView',['require','JOIST/PhetButton','SCENERY/nodes/Node','SCENERY/nodes/HBox','SCENERY/nodes/Text','PHET_CORE/inherit','JOIST/ScreenView','JOIST/ScreenButton','AXON/Property','SCENERY_PHET/PhetFont','DOT/Bounds2','JOIST/joist'],function( require ) { 'use strict'; // modules var PhetButton = require( 'JOIST/PhetButton' ); var Node = require( 'SCENERY/nodes/Node' ); var HBox = require( 'SCENERY/nodes/HBox' ); var Text = require( 'SCENERY/nodes/Text' ); var inherit = require( 'PHET_CORE/inherit' ); var ScreenView = require( 'JOIST/ScreenView' ); var ScreenButton = require( 'JOIST/ScreenButton' ); var Property = require( 'AXON/Property' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var Bounds2 = require( 'DOT/Bounds2' ); var joist = require( 'JOIST/joist' ); // constants var LAYOUT_BOUNDS = new Bounds2( 0, 0, 768, 504 ); // iPad doesn't support Century Gothic, so fall back to Futura, see http://wordpress.org/support/topic/font-not-working-on-ipad-browser var TITLE_FONT_FAMILY = 'Century Gothic, Futura'; /** * @param {Sim} sim * @param {Tandem} tandem * @param {Object} [options] * @constructor */ function HomeScreenView( sim, tandem, options ) { var self = this; options = _.extend( { showSmallHomeScreenIconFrame: false, warningNode: null // {Node | null}, to display below the icons as a warning if available }, options ); // Rendering in SVG seems to solve the problem that the home screen consumes 100% disk and crashes, see // https://github.com/phetsims/joist/issues/17. This also makes it more responsive (and crisper on retina // displays). The renderer must be specified here because the node is added directly to the scene (instead of to // some other node that already has svg renderer). ScreenView.call( this, { layoutBounds: LAYOUT_BOUNDS } ); var title = new Text( sim.name, { font: new PhetFont( { size: 52, family: TITLE_FONT_FAMILY } ), fill: 'white', y: 110, tandem: tandem.createTandem( 'title' ) } ); this.addChild( title ); title.scale( Math.min( 1, 0.9 * this.layoutBounds.width / title.width ) ); title.centerX = this.layoutBounds.centerX; // Keep track of which screen is highlighted so the same screen can remain highlighted even if nodes are replaced // (say when one grows larger or smaller). var highlightedScreenIndexProperty = new Property( -1 ); var screenChildren = _.map( sim.screens, function( screen ) { assert && assert( screen.name, 'name is required for screen ' + sim.screens.indexOf( screen ) ); assert && assert( screen.homeScreenIcon, 'homeScreenIcon is required for screen ' + screen.name ); var index = sim.screens.indexOf( screen ); // Even though in the user interface the small and large buttons seem like a single UI component that has grown // larger, it would be quite a headache to create a composite button for the purposes of tandem, so instead the // large and small buttons are registered as separate instances. See https://github.com/phetsims/phet-io/issues/99 var largeTandem = tandem.createTandem( screen.tandem.tail + 'LargeButton' ); var isLarge = true; var largeScreenButton = new ScreenButton( isLarge, sim, index, highlightedScreenIndexProperty, largeTandem, { // Don't 40 the VBox or it will shift down when the border becomes thicker resize: false, cursor: 'pointer' } ); // Even though in the user interface the small and large buttons seem like a single UI component that has grown // larger, it would be quite a headache to create a composite button for the purposes of tandem, so instead the // large and small buttons are registered as separate instances. See https://github.com/phetsims/phet-io/issues/99 var smallTandem = tandem.createTandem( screen.tandem.tail + 'SmallButton' ); isLarge = false; var smallScreenButton = new ScreenButton( isLarge, sim, index, highlightedScreenIndexProperty, smallTandem, { spacing: 3, cursor: 'pointer', showSmallHomeScreenIconFrame: options.showSmallHomeScreenIconFrame, } ); smallScreenButton.addInputListener( smallScreenButton.highlightListener ); largeScreenButton.addInputListener( smallScreenButton.highlightListener ); // largeScreenButton.mouseArea = largeScreenButton.touchArea = Shape.bounds( largeScreenButton.bounds ); // cover the gap in the vbox return { screen: screen, small: smallScreenButton, large: largeScreenButton, index: index }; } ); // Intermediate node, so that icons are always in the same rendering layer var iconsParentNode = new Node(); self.addChild( iconsParentNode ); // Space the icons out more if there are fewer, so they will be spaced nicely. // Cannot have only 1 screen because for 1-screen sims there is no home screen. var spacing = ( sim.screens.length <= 3 ) ? 60 : 33; var hBox = null; sim.screenIndexProperty.link( function( screenIndex ) { // remove previous layout of icons if ( hBox ) { hBox.removeAllChildren(); // because icons have reference to hBox (their parent) iconsParentNode.removeChild( hBox ); } // add new layout of icons var icons = _.map( screenChildren, function( screenChild ) {return screenChild.index === screenIndex ? screenChild.large : screenChild.small;} ); hBox = new HBox( { spacing: spacing, children: icons, align: 'top', resize: false } ); iconsParentNode.addChild( hBox ); // position the icons iconsParentNode.centerX = self.layoutBounds.width / 2; iconsParentNode.top = 170; } ); //TODO move these Properties to LookAndFeel, see https://github.com/phetsims/joist/issues/255 var homeScreenFillProperty = new Property( 'black' ); var homeScreenTextFillProperty = new Property( 'white' ); // @public (joist-internal) - This PhET button is public since our creator (Sim.js) is responsible for positioning // this button. See https://github.com/phetsims/joist/issues/304. this.phetButton = new PhetButton( sim, homeScreenFillProperty, homeScreenTextFillProperty, tandem.createTandem( 'phetButton' ) ); this.addChild( this.phetButton ); if ( options.warningNode ) { var warningNode = options.warningNode; this.addChild( warningNode ); warningNode.centerX = this.layoutBounds.centerX; warningNode.bottom = this.layoutBounds.maxY - 20; } } joist.register( 'HomeScreenView', HomeScreenView ); return inherit( ScreenView, HomeScreenView, {}, // @public - statics { TITLE_FONT_FAMILY: TITLE_FONT_FAMILY, LAYOUT_BOUNDS: LAYOUT_BOUNDS } ); } ); define("string!JOIST/keyboardShortcuts.title",function(){return window.phet.chipper.strings.get("JOIST/keyboardShortcuts.title");}); // Copyright 2014-2015, University of Colorado Boulder /** * Shows a Dialog with content describing keyboard interactions. Brought up by a button * in the navigation bar. * * @author Jesse Greenberg */ define( 'JOIST/KeyboardHelpDialog',['require','PHET_CORE/inherit','JOIST/Dialog','JOIST/joist','SCENERY/input/ButtonListener','SCENERY/nodes/Path','SCENERY/nodes/Text','KITE/Shape','JOIST/JoistA11yStrings','SCENERY_PHET/PhetFont','string!JOIST/keyboardShortcuts.title'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Dialog = require( 'JOIST/Dialog' ); var joist = require( 'JOIST/joist' ); var ButtonListener = require( 'SCENERY/input/ButtonListener' ); var Path = require( 'SCENERY/nodes/Path' ); var Text = require( 'SCENERY/nodes/Text' ); var Shape = require( 'KITE/Shape' ); var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); // constants var CLOSE_BUTTON_WIDTH = 7; var CLOSE_BUTTON_MARGIN = 10; var HELP_CONTENT_MARGIN = 20; var TITLE_MAX_WIDTH = 500; // string var keyboardShortcutsTitleString = require( 'string!JOIST/keyboardShortcuts.title' ); /** * Constructor. * @param {KeyboardHelpButton} keyboardHelpButton * @param {Node} helpContent - a node containing the sim specific keyboard help content * @param {object} options * @constructor */ function KeyboardHelpDialog( keyboardHelpButton, helpContent, options ) { // title var titleText = new Text( keyboardShortcutsTitleString, { font: new PhetFont( { weight: 'bold', size: 20 } ), maxWidth: TITLE_MAX_WIDTH, // a11y options tagName: 'h1', accessibleLabel: JoistA11yStrings.hotKeysAndHelpString } ); options = _.extend( { titleAlign: 'center', title: titleText, modal: true, hasCloseButton: false, fill: 'rgb( 214, 237, 249 )', xMargin: HELP_CONTENT_MARGIN, yMargin: HELP_CONTENT_MARGIN, titleSpacing: HELP_CONTENT_MARGIN, focusOnCloseNode: keyboardHelpButton }, options ); // shape and path for a custom close button var closeButtonShape = new Shape(); closeButtonShape.moveTo( -CLOSE_BUTTON_WIDTH, - CLOSE_BUTTON_WIDTH ).lineTo( CLOSE_BUTTON_WIDTH, CLOSE_BUTTON_WIDTH ); closeButtonShape.moveTo( CLOSE_BUTTON_WIDTH, -CLOSE_BUTTON_WIDTH ).lineTo( -CLOSE_BUTTON_WIDTH, CLOSE_BUTTON_WIDTH ); // @public (joist-internal) this.closeButtonPath = new Path( closeButtonShape, { stroke: 'black', lineCap: 'round', lineWidth: 2, cursor: 'pointer', // a11y tagName: 'button', accessibleLabel: JoistA11yStrings.closeString, focusHighlight: Shape.bounds( closeButtonShape.getBounds().dilated( 10 ) ) } ); // add a listener to hide the dialog var self = this; this.closeButtonPath.addInputListener( new ButtonListener( { down: function() { self.hide(); } } ) ); // mouse/touch areas for the close button var areaX = this.closeButtonPath.left - this.closeButtonPath.width * 2; var areaY = this.closeButtonPath.top - CLOSE_BUTTON_MARGIN / 2; var width = this.closeButtonPath.width * 4; var height = this.closeButtonPath.height + CLOSE_BUTTON_MARGIN; this.closeButtonPath.mouseArea = Shape.rect( areaX, areaY, width, height ); this.closeButtonPath.touchArea = this.closeButtonPath.mouseArea; Dialog.call( this, helpContent, options ); // position and add the close button this.closeButtonPath.right = helpContent.right + 2 * HELP_CONTENT_MARGIN - CLOSE_BUTTON_MARGIN; this.closeButtonPath.top = helpContent.top + CLOSE_BUTTON_MARGIN; this.addChild( this.closeButtonPath ); // @private (a11y) - input listener for the close button, must be disposed this.clickListener = this.closeButtonPath.addAccessibleInputListener( { click: function() { self.hide(); self.focusActiveElement(); } } ); } joist.register( 'KeyboardHelpDialog', KeyboardHelpDialog ); return inherit( Dialog, KeyboardHelpDialog, { /** * So the Dialog is eligible for garbage collection. */ dispose: function() { this.closeButtonPath.removeAccessibleInputListener( this.clickListener ); Dialog.prototype.dispose && Dialog.prototype.dispose.call( this ); } } ); } ); define("mipmap!JOIST/keyboard-icon.png", function(){ var mipmaps = window.phet.chipper.mipmaps["JOIST/keyboard-icon.png"]; window.phetImages = window.phetImages || [] mipmaps.forEach( function( mipmap ) { mipmap.img = new Image(); window.phetImages.push( mipmap.img ); mipmap.img.src = mipmap.url; } ); return mipmaps; } ); define("mipmap!JOIST/keyboard-icon-on-white.png", function(){ var mipmaps = window.phet.chipper.mipmaps["JOIST/keyboard-icon-on-white.png"]; window.phetImages = window.phetImages || [] mipmaps.forEach( function( mipmap ) { mipmap.img = new Image(); window.phetImages.push( mipmap.img ); mipmap.img.src = mipmap.url; } ); return mipmaps; } ); // Copyright 2016, University of Colorado Boulder /** * The button that pops up the Keyboard Help Dialog, which appears in the right side of the navbar and * to the left of the PhetButton. * * @author Jesse Greenberg */ define( 'JOIST/KeyboardHelpButton',['require','PHET_CORE/inherit','AXON/Property','KITE/Shape','SCENERY/nodes/Image','JOIST/JoistButton','JOIST/KeyboardHelpDialog','JOIST/JoistA11yStrings','JOIST/joist','mipmap!JOIST/keyboard-icon.png','mipmap!JOIST/keyboard-icon-on-white.png'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Property = require( 'AXON/Property' ); var Shape = require( 'KITE/Shape' ); var Image = require( 'SCENERY/nodes/Image' ); var JoistButton = require( 'JOIST/JoistButton' ); var KeyboardHelpDialog = require( 'JOIST/KeyboardHelpDialog' ); var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' ); var joist = require( 'JOIST/joist' ); // images var brightIconMipmap = require( 'mipmap!JOIST/keyboard-icon.png' ); // on a black navbar var darkIconMipmap = require( 'mipmap!JOIST/keyboard-icon-on-white.png' ); // on a white navbar assert && assert( brightIconMipmap instanceof Array, 'icon must be a mipmap' ); // constants var HELP_BUTTON_HEIGHT = 67; var HELP_BUTTON_SCALE = 0.32; // scale applied to the icon var BUTTON_SCALE = HELP_BUTTON_SCALE / brightIconMipmap[ 0 ].height * HELP_BUTTON_HEIGHT; function KeyboardHelpButton( sim, backgroundFillProperty, tandem ) { var self = this; var keyboardHelpDialog = null; var openDialog = function() { if ( !keyboardHelpDialog ) { keyboardHelpDialog = new KeyboardHelpDialog( self, sim.keyboardHelpNode, { tandem: tandem.createTandem( 'keyboardHelpDialog' ) } ); } keyboardHelpDialog.show(); }; var options = { highlightExtensionWidth: 5, highlightExtensionHeight: 10, highlightCenterOffsetY: 3, listener: openDialog, // a11y options tagName: 'button', accessibleLabel: JoistA11yStrings.hotKeysAndHelpString }; var icon = new Image( brightIconMipmap, { scale: BUTTON_SCALE, pickable: false } ); JoistButton.call( this, icon, backgroundFillProperty, tandem, options ); // a11y - focus highlight since the bounds of the button push the default highlight out of bounds this.focusHighlight = Shape.bounds( icon.bounds.dilated( 5 ) ); Property.multilink( [ backgroundFillProperty, sim.showHomeScreenProperty ], function( backgroundFill, showHomeScreen ) { var backgroundIsWhite = backgroundFill !== 'black' && !showHomeScreen; icon.image = backgroundIsWhite ? darkIconMipmap : brightIconMipmap; } ); // a11y - open the dialog on 'spacebar' or 'enter' and focus the 'Close' button immediately this.clickListener = this.addAccessibleInputListener( { click: function() { openDialog(); keyboardHelpDialog.closeButtonPath.focus(); } } ); } joist.register( 'KeyboardHelpButton', KeyboardHelpButton ); return inherit( JoistButton, KeyboardHelpButton, { /** * To make eligible for garbage collection. * @public */ dispose: function() { this.removeAccessibleInputListener( this.clickListener ); JoistButton.prototype.dispose && JoistButton.prototype.dispose.call( this ); } } ); } ); // Copyright 2013-2015, University of Colorado Boulder /** * The navigation bar at the bottom of the screen. * For a single-screen sim, it shows the name of the sim at the far left and the PhET button at the far right. * For a multi-screen sim, it additionally shows buttons for each screen, and a home button. * * Layout of NavigationBar adapts to different text widths, icon widths, and numbers of screens, and attempts to * perform an "optimal" layout. The sim title is initially constrained to a max percentage of the bar width, * and that's used to compute how much space is available for screen buttons. After creation and layout of the * screen buttons, we then compute how much space is actually available for the sim title, and use that to * constrain the title's width. * * The bar is composed of a background (always pixel-perfect), and expandable content (that gets scaled as one part). * If we are width-constrained, the navigation bar is in a 'compact' state where the children of the content (e.g. * home button, screen buttons, phet menu, title) do not change positions. If we are height-constrained, the amount * available to the bar expands, so we lay out the children to fit. See https://github.com/phetsims/joist/issues/283 * for more details on how this is done. * * @author Sam Reid (PhET Interactive Simulations) * @author Chris Malley (PixelZoom, Inc.) * @author Jonathan Olson */ define( 'JOIST/NavigationBar',['require','DOT/Dimension2','JOIST/HomeButton','PHET_CORE/inherit','PHET_CORE/platform','JOIST/NavigationBarScreenButton','JOIST/HomeScreenView','SCENERY/nodes/Node','JOIST/PhetButton','JOIST/KeyboardHelpButton','SCENERY_PHET/PhetFont','SCENERY/nodes/Rectangle','SCENERY/nodes/Text','JOIST/JoistA11yStrings','JOIST/joist'],function( require ) { 'use strict'; // modules var Dimension2 = require( 'DOT/Dimension2' ); var HomeButton = require( 'JOIST/HomeButton' ); var inherit = require( 'PHET_CORE/inherit' ); var platform = require( 'PHET_CORE/platform' ); var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' ); var HomeScreenView = require( 'JOIST/HomeScreenView' ); var Node = require( 'SCENERY/nodes/Node' ); var PhetButton = require( 'JOIST/PhetButton' ); var KeyboardHelpButton = require( 'JOIST/KeyboardHelpButton' ); var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); var Text = require( 'SCENERY/nodes/Text' ); var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' ); var joist = require( 'JOIST/joist' ); // constants var NAVIGATION_BAR_SIZE = new Dimension2( HomeScreenView.LAYOUT_BOUNDS.width, 40 ); var TITLE_LEFT_MARGIN = 10; var TITLE_RIGHT_MARGIN = 25; var PHET_BUTTON_LEFT_MARGIN = 13; var PHET_BUTTON_RIGHT_MARGIN = PhetButton.HORIZONTAL_INSET; // same position as PhetButton on home screen var PHET_BUTTON_BOTTOM_MARGIN = PhetButton.VERTICAL_INSET; // same position as PhetButton on home screen var KEYBOARD_HELP_BUTTON_LEFT_MARGIN = 50; var HOME_BUTTON_LEFT_MARGIN = 5; var HOME_BUTTON_RIGHT_MARGIN = HOME_BUTTON_LEFT_MARGIN; var SCREEN_BUTTON_SPACING = 0; var MINIMUM_SCREEN_BUTTON_WIDTH = 60; // Make sure each button is at least a minimum width so they don't get too close together, see #279 /** * Creates a nav bar. * @param {Sim} sim * @param {Screen[]} screens * @param {Tandem} tandem * @constructor */ function NavigationBar( sim, screens, tandem ) { // @private this.screens = screens; Node.call( this ); // @private - The bar's background (resized in layout) this.background = new Rectangle( 0, 0, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height, { pickable: true } ); sim.lookAndFeel.navigationBarFillProperty.linkAttribute( this.background, 'fill' ); this.addChild( this.background ); // @private - Everything else besides the background in the navigation bar (used for scaling) this.barContents = new Node(); this.addChild( this.barContents ); // Sim title this.titleTextNode = new Text( sim.name, { font: new PhetFont( 16 ), tandem: tandem.createTandem( 'titleTextNode' ) } ); sim.lookAndFeel.navigationBarTextFillProperty.linkAttribute( this.titleTextNode, 'fill' ); this.barContents.addChild( this.titleTextNode ); // @public (joist-internal) - PhET button. The transform of this is tracked, so we can mirror it over to the // homescreen's button. See https://github.com/phetsims/joist/issues/304. this.phetButton = new PhetButton( sim, sim.lookAndFeel.navigationBarFillProperty, sim.lookAndFeel.navigationBarTextFillProperty, tandem.createTandem( 'phetButton' ) ); this.barContents.addChild( this.phetButton ); // @private - Pops open a dialog with information about keyboard navigation this.keyboardHelpButton = new KeyboardHelpButton( sim, sim.lookAndFeel.navigationBarFillProperty, tandem.createTandem( 'keyboardHelpButton' ) ); // only show the keyboard help button if the sim is accessible, there is keyboard help content, and we are // not in mobile safari if ( sim.accessible && sim.keyboardHelpNode && !platform.mobileSafari ) { this.barContents.addChild( this.keyboardHelpButton ); } if ( screens.length === 1 ) { /* single-screen sim */ // title can occupy all space to the left of the PhET button this.titleTextNode.maxWidth = HomeScreenView.LAYOUT_BOUNDS.width - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN - this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN - this.keyboardHelpButton.width - KEYBOARD_HELP_BUTTON_LEFT_MARGIN; } else { /* multi-screen sim */ // Start with the assumption that the title can occupy (at most) this percentage of the bar. var maxTitleWidth = Math.min( this.titleTextNode.width, 0.20 * HomeScreenView.LAYOUT_BOUNDS.width ); // @private - Create the home button this.homeButton = new HomeButton( NAVIGATION_BAR_SIZE.height, sim.lookAndFeel.navigationBarFillProperty, tandem.createTandem( 'homeButton' ), { listener: function() { sim.showHomeScreenProperty.value = true; } } ); // Add the home button, but only if it isn't turned off with ?homeScreen=false phet.chipper.queryParameters.homeScreen && this.barContents.addChild( this.homeButton ); /* * Allocate remaining horizontal space equally for screen buttons, assuming they will be centered in the navbar. * Computations here reflect the left-to-right layout of the navbar. */ // available width left of center var availableLeft = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - TITLE_LEFT_MARGIN - maxTitleWidth - TITLE_RIGHT_MARGIN - this.homeButton.width - HOME_BUTTON_RIGHT_MARGIN; // available width right of center var availableRight = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN - this.keyboardHelpButton.width - KEYBOARD_HELP_BUTTON_LEFT_MARGIN; // total available width for the screen buttons when they are centered var availableTotal = 2 * Math.min( availableLeft, availableRight ); // width per screen button var screenButtonWidth = ( availableTotal - ( screens.length - 1 ) * SCREEN_BUTTON_SPACING ) / screens.length; // Create the screen buttons var screenButtons = _.map( screens, function( screen ) { return new NavigationBarScreenButton( sim.lookAndFeel.navigationBarFillProperty, sim.screenIndexProperty, sim.screens, screen, NAVIGATION_BAR_SIZE.height, { maxButtonWidth: screenButtonWidth, tandem: tandem.createTandem( screen.tandem.tail + 'Button' ) } ); } ); // Layout out screen buttons horizontally, with equal distance between their centers // Make sure each button is at least a minimum size, so they don't get too close together, see #279 var maxScreenButtonWidth = Math.max( MINIMUM_SCREEN_BUTTON_WIDTH, _.maxBy( screenButtons, function( button ) { return button.width; } ).width ); // Compute the distance between *centers* of each button var spaceBetweenButtons = maxScreenButtonWidth + SCREEN_BUTTON_SPACING; for ( var i = 0; i < screenButtons.length; i++ ) { // Equally space the centers of the buttons around the origin of their parent (screenButtonsContainer) screenButtons[ i ].centerX = spaceBetweenButtons * ( i - ( screenButtons.length - 1 ) / 2 ); } // @private - Put all screen buttons under a parent, to simplify layout this.screenButtonsContainer = new Node( { children: screenButtons, // NOTE: these layout settings are duplicated in layout(), but are necessary due to title's maxWidth requiring layout x: this.background.centerX, // since we have buttons centered around our origin, this centers the buttons centerY: this.background.centerY, maxWidth: availableTotal // in case we have so many screens that the screen buttons need to be scaled down } ); this.barContents.addChild( this.screenButtonsContainer ); this.accessibleOrder = [ this.screenButtonsContainer, this.homeButton ]; // Now determine the actual width constraint for the sim title. this.titleTextNode.maxWidth = this.screenButtonsContainer.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN - HOME_BUTTON_RIGHT_MARGIN - this.homeButton.width - HOME_BUTTON_LEFT_MARGIN; } // initial layout (that doesn't need to change when we are re-layed out) this.titleTextNode.left = TITLE_LEFT_MARGIN; this.titleTextNode.centerY = NAVIGATION_BAR_SIZE.height / 2; this.phetButton.bottom = NAVIGATION_BAR_SIZE.height - PHET_BUTTON_BOTTOM_MARGIN; this.keyboardHelpButton.centerY = this.phetButton.centerY; if ( this.screens.length !== 1 ) { this.screenButtonsContainer.centerY = NAVIGATION_BAR_SIZE.height / 2; this.homeButton.centerY = NAVIGATION_BAR_SIZE.height / 2; } this.layout( 1, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height ); // a11y - container tag name and accessible label for all content in the nav bar this.tagName = 'footer'; this.ariaLabel = JoistA11yStrings.simResourcesAndToolsString; // a11y - keyboard help button before phet menu button this.accessibleOrder = [ this.keyboardHelpButton, this.phetButton ]; } joist.register( 'NavigationBar', NavigationBar ); return inherit( Node, NavigationBar, { /** * Called when the navigation bar layout needs to be updated, typically when the browser window is resized. * @param {number} scale * @param {number} width * @param {number} height * @public */ layout: function( scale, width, height ) { // resize the background this.background.rectWidth = width; this.background.rectHeight = height; // scale the entire bar contents this.barContents.setScaleMagnitude( scale ); // determine our local-coordinate 'right' side of the screen, so we can expand if necessary var right; if ( NAVIGATION_BAR_SIZE.width * scale < width ) { // expanded right = width / scale; } else { // compact right = NAVIGATION_BAR_SIZE.width; } // horizontal positioning this.phetButton.right = right - PHET_BUTTON_RIGHT_MARGIN; this.keyboardHelpButton.right = this.phetButton.left - PHET_BUTTON_LEFT_MARGIN; // For multi-screen sims ... if ( this.screens.length !== 1 ) { // screen buttons and home screen button are centered. These buttons are centered // around the origin in the screenButtonsContainer, so the screenButtonsContainer can // be put at the center of the navbar, shifted by the width of the home button. this.screenButtonsContainer.x = right / 2 + this.homeButton.width / 2; // home button to the left of screen buttons this.homeButton.right = this.screenButtonsContainer.left - HOME_BUTTON_RIGHT_MARGIN; // max width relative to position of home button this.titleTextNode.maxWidth = this.homeButton.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN; } } }, { // @public NAVIGATION_BAR_SIZE: NAVIGATION_BAR_SIZE } ); } ); // Copyright 2015, University of Colorado Boulder /** * Screen for the home screen, which shows icons for selecting the sim content screens. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/HomeScreen',['require','PHET_CORE/inherit','JOIST/Screen','JOIST/HomeScreenView','JOIST/joist','AXON/Property'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Screen = require( 'JOIST/Screen' ); var HomeScreenView = require( 'JOIST/HomeScreenView' ); var joist = require( 'JOIST/joist' ); var Property = require( 'AXON/Property' ); /** * * @constructor */ function HomeScreen( sim, tandem, options ) { options = _.extend( { //TODO get this color from LookAndFeel, see https://github.com/phetsims/joist/issues/255 backgroundColorProperty: new Property( 'black' ) }, options ); assert && assert( !options.tandem, 'tandem is a required constructor parameter, not an option' ); options.tandem = tandem; Screen.call( this, // createModel function() { return {}; }, // createView function() { return new HomeScreenView( sim, tandem.createTandem( 'view' ), _.pick( options, [ 'showSmallHomeScreenIconFrame', 'warningNode' ] ) ); }, options ); } joist.register( 'HomeScreen', HomeScreen ); return inherit( Screen, HomeScreen ); } ); // Copyright 2013-2016, University of Colorado Boulder /** * A node which always fills the entire screen, no matter what the transform is. * Used for showing an overlay on the screen e.g., when a popup dialog is shown. * This can fade the background to focus on the dialog/popup as well as intercept mouse events for dismissing the dialog/popup. * Note: This is currently implemented using large numbers, it should be rewritten to work in any coordinate frame, possibly using kite.Shape.plane() * TODO: Implement using infinite geometry * * @author Sam Reid */ define( 'SCENERY/nodes/Plane',['require','PHET_CORE/inherit','SCENERY/scenery','SCENERY/nodes/Rectangle'],function( require ) { 'use strict'; var inherit = require( 'PHET_CORE/inherit' ); var scenery = require( 'SCENERY/scenery' ); var Rectangle = require( 'SCENERY/nodes/Rectangle' ); /** * @public * @constructor * @extends Rectangle * * @param {Object} [options] Passed to Rectangle. See Rectangle for more documentation */ function Plane( options ) { Rectangle.call( this, -2000, -2000, 6000, 6000, options ); } scenery.register( 'Plane', Plane ); return inherit( Rectangle, Plane ); } ); // Copyright 2016, University of Colorado Boulder /** * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) */ define( 'SCENERY_PHET/TBarrierRectangle',['require','SCENERY/nodes/TNode','ifphetio!PHET_IO/assertions/assertInstanceOf','ifphetio!PHET_IO/phetioInherit','SCENERY_PHET/sceneryPhet','ifphetio!PHET_IO/events/toEventOnEmit'],function( require ) { 'use strict'; // modules var TNode = require( 'SCENERY/nodes/TNode' ); // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' ); var toEventOnEmit = require( 'ifphetio!PHET_IO/events/toEventOnEmit' ); /** * Wrapper type for phet/scenery's BarrierRectangle * @param barrierRectangle * @param phetioID * @constructor */ function TBarrierRectangle( barrierRectangle, phetioID ) { assertInstanceOf( barrierRectangle, phet.scenery.Rectangle ); TNode.call( this, barrierRectangle, phetioID ); toEventOnEmit( barrierRectangle.startedCallbacksForFiredEmitter, barrierRectangle.endedCallbacksForFiredEmitter, 'user', phetioID, this.constructor, 'fired' ); } phetioInherit( TNode, 'TBarrierRectangle', TBarrierRectangle, {}, { documentation: 'Shown when a dialog is present, so that clicking on the invisible barrier rectangle will dismiss the dialog', events: [ 'fired' ], dataStreamOnlyType: true } ); sceneryPhet.register( 'TBarrierRectangle', TBarrierRectangle ); return TBarrierRectangle; } ); // Copyright 2017, University of Colorado Boulder /** * Semi-transparent black barrier used to block input events when a dialog (or other popup) is present, and fade out * the background. * * @author - Michael Kauzmann (PhET Interactive Simulations) */ define( 'SCENERY_PHET/BarrierRectangle',['require','PHET_CORE/inherit','SCENERY_PHET/sceneryPhet','AXON/Emitter','SCENERY/nodes/Plane','SCENERY/input/ButtonListener','TANDEM/Tandem','SCENERY_PHET/TBarrierRectangle'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' ); var Emitter = require( 'AXON/Emitter' ); var Plane = require( 'SCENERY/nodes/Plane' ); var ButtonListener = require( 'SCENERY/input/ButtonListener' ); var Tandem = require( 'TANDEM/Tandem' ); var TBarrierRectangle = require( 'SCENERY_PHET/TBarrierRectangle' ); /** * @param {ObservableArray} modalNodeStack - see usage in Sim.js * @param {Object} [options] * @constructor */ function BarrierRectangle( modalNodeStack, options ) { var self = this; options = _.extend( { tandem: Tandem.tandemRequired(), phetioType: TBarrierRectangle }, options ); Plane.call( this ); // @private this.startedCallbacksForFiredEmitter = new Emitter(); this.endedCallbacksForFiredEmitter = new Emitter(); modalNodeStack.lengthProperty.link( function( numBarriers ) { self.visible = numBarriers > 0; } ); this.addInputListener( new ButtonListener( { fire: function( event ) { self.startedCallbacksForFiredEmitter.emit(); assert && assert( modalNodeStack.length > 0, 'There must be a Node in the stack to hide.' ); modalNodeStack.get( modalNodeStack.length - 1 ).hide(); self.endedCallbacksForFiredEmitter.emit(); } } ) ); // @private this.disposeBarrierRectangle = function() { modalNodeStack.lengthProperty.unlink(); }; this.mutate( options ); } sceneryPhet.register( 'BarrierRectangle', BarrierRectangle ); return inherit( Plane, BarrierRectangle, { // @public dispose: function() { this.disposeBarrierRectangle(); Plane.prototype.dispose.call( this ); } } ); } ); // Copyright 2014-2016, University of Colorado Boulder /** * This minimalistic profiler is meant to help understand the time spent in running a PhET simulation. * It was designed to be minimally invasive, so it won't alter the simulation's performance significantly. * Note: just showing the average FPS or ms/frame is not sufficient, since we need to see when garbage collections * happen, which are typically a spike in a single frame. Hence, the data is shown as a histogram. Data that * doesn't fit in the histogram appears in an optional 'longTimes' field. * * Output is displayed in the upper-left corner of the browser window, and updates every 60 frames. * * The general format is: * * FPS - ms/frame - histogram [- longTimes] * * Here's an example: * * 48 FPS - 21ms/frame - 0,0,5,0,0,0,0,0,1,0,0,0,0,3,1,3,18,19,5,3,1,0,1,0,0,0,0,1,0,0 - 50,37,217 * * The histogram field is a sequence of 30 numbers, for 0-29ms. Each number indicates the number of frames that took * that amount of time. In the above example, histogram[2] is 5; there were 5 frames that took 2ms. * * The longTimes field is the set of frame times that exceeded 29ms, and thus don't fit in the histogram. * If 2 frames took 37ms, then 37ms will appear twice. If no frames exceeded 29ms, then this field will be absent. * These values are sorted in descending order, so you can easily identify the largest frame time. * * @author Sam Reid (PhET Interactive Simulations) * @author Chris Malley (PixelZoom, Inc.) */ define( 'JOIST/Profiler',['require','PHET_CORE/inherit','JOIST/joist'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var joist = require( 'JOIST/joist' ); // constants var FIELD_SEPARATOR = ' \u2014 '; // em dash, a long horizontal dash var HISTOGRAM_LENGTH = 30; /** * Construct a Profiler * @constructor */ function Profiler() { // @private These data structured were chosen to minimize CPU time. this.allTimes = []; // {number[]} times for all frames, in ms this.histogram = []; // {number[]} array index corresponds to number of ms, value is number of frames at that time this.longTimes = []; // {number[]} any frame times that didn't fit in histogram this.frameStartTime = 0; // {number} start time of the current frame this.previousFrameStartTime = 0; // {number} start time of the previous frame // initialize histogram for ( var i = 0; i < HISTOGRAM_LENGTH; i++ ) { this.histogram.push( 0 ); } // this is where the profiler displays its output $( 'body' ).append( '
' ); } joist.register( 'Profiler', Profiler ); return inherit( Object, Profiler, { // @private frameStarted: function() { this.frameStartTime = Date.now(); }, // @private frameEnded: function() { // update the display every 60 frames if ( this.allTimes.length > 0 && this.allTimes.length % 60 === 0 ) { var totalTime = 0; for ( var i = 0; i < this.allTimes.length; i++ ) { totalTime += this.allTimes[ i ]; } // FPS var averageFPS = Math.round( 1000 / (totalTime / this.allTimes.length) ); var text = '' + averageFPS + ' FPS'; // ms/frame var averageFrameTime = Math.round( totalTime / this.allTimes.length ); text = text + FIELD_SEPARATOR + averageFrameTime + 'ms/frame'; // histogram text = text + FIELD_SEPARATOR + this.histogram; // longTimes if ( this.longTimes.length > 0 ) { this.longTimes.sort( function( a, b ) { return b - a; } ); // sort longTimes in descending order text = text + FIELD_SEPARATOR + this.longTimes; } // update the display $( '#phetProfiler' ).html( text ); // clear data structures for ( i = 0; i < HISTOGRAM_LENGTH; i++ ) { this.histogram[ i ] = 0; } this.longTimes.length = 0; this.allTimes.length = 0; } // record data for the current frame, skip first frame because we can't compute its dt if ( this.previousFrameStartTime ) { var dt = this.frameStartTime - this.previousFrameStartTime; this.allTimes.push( dt ); if ( dt < HISTOGRAM_LENGTH ) { this.histogram[ dt ]++; // increment the histogram cell for the corresponding time } else { this.longTimes.push( dt ); // time doesn't fit in histogram, record in longTimes } } this.previousFrameStartTime = this.frameStartTime; } }, { // @public start: function( sim ) { var profiler = new Profiler(); sim.frameStartedEmitter.addListener( function() { profiler.frameStarted(); } ); sim.frameEndedEmitter.addListener( function() { profiler.frameEnded(); } ); } } ); } ); // Copyright 2015-2016, University of Colorado Boulder /** * Provides colors for Joist elements. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/LookAndFeel',['require','SCENERY/util/Color','AXON/DerivedProperty','PHET_CORE/inherit','JOIST/joist','AXON/Property'],function( require ) { 'use strict'; // modules var Color = require( 'SCENERY/util/Color' ); var DerivedProperty = require( 'AXON/DerivedProperty' ); var inherit = require( 'PHET_CORE/inherit' ); var joist = require( 'JOIST/joist' ); var Property = require( 'AXON/Property' ); /** * * @constructor */ function LookAndFeel() { // @public background color for the currently selected screen, which will be set on the Display as its backgroundColor this.backgroundColorProperty = new Property( 'black' ); // @public (joist-internal) - Navigation bar background fill this.navigationBarFillProperty = new DerivedProperty( [ this.backgroundColorProperty ], function( backgroundColor ) { var screenIsBlack = !!new Color( backgroundColor ).equals( Color.BLACK ); return screenIsBlack ? 'white' : 'black'; } ); // @public (joist-internal) - Navigation bar text fill this.navigationBarTextFillProperty = new DerivedProperty( [ this.navigationBarFillProperty ], function( navigationBarFill ) { return navigationBarFill === 'black' ? 'white' : 'black'; } ); } joist.register( 'LookAndFeel', LookAndFeel ); return inherit( Object, LookAndFeel, { // @public reset: function() { this.backgroundColorProperty.reset(); } } ); } ); define("PHET_IO/SimIFrameAPI", function(){return function(){ return function(){}; };}); // Copyright 2016, University of Colorado Boulder /** * * @author Sam Reid (PhET Interactive Simulations) * @author Andrew Adare (PhET Interactive Simulations) */ define( 'JOIST/TSim',['require','ifphetio!PHET_IO/assertions/assertInstanceOf','ifphetio!PHET_IO/phetio','ifphetio!PHET_IO/phetioInherit','JOIST/joist','ifphetio!PHET_IO/SimIFrameAPI','ifphetio!PHET_IO/types/TFunctionWrapper','ifphetio!PHET_IO/types/TObject','ifphetio!PHET_IO/events/toEventOnEmit','ifphetio!PHET_IO/types/TString','ifphetio!PHET_IO/types/TVoid'],function( require ) { 'use strict'; // phet-io modules var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' ); var phetio = require( 'ifphetio!PHET_IO/phetio' ); var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' ); var joist = require( 'JOIST/joist' ); var SimIFrameAPI = require( 'ifphetio!PHET_IO/SimIFrameAPI' ); var TFunctionWrapper = require( 'ifphetio!PHET_IO/types/TFunctionWrapper' ); var TObject = require( 'ifphetio!PHET_IO/types/TObject' ); var toEventOnEmit = require( 'ifphetio!PHET_IO/events/toEventOnEmit' ); var TString = require( 'ifphetio!PHET_IO/types/TString' ); var TVoid = require( 'ifphetio!PHET_IO/types/TVoid' ); // constants // The token for the event that occurs when the simulation constructor completes. This is hard-coded in many places // such as th playback wrapper, so should not be changed lightly! var SIM_STARTED = 'simStarted'; /** * Wrapper type for phet/joist's Sim class. * @param sim * @param phetioID * @constructor */ function TSim( sim, phetioID ) { assertInstanceOf( sim, phet.joist.Sim ); TObject.call( this, sim, phetioID ); // startedSimConstructorEmitter is called in the constructor of the sim, and endedSimConstructionEmitter is called // once all of the screens have been fully initialized, hence construction not constructor. // The simStarted event is guaranteed to be a top-level event, not nested under other events. toEventOnEmit( sim.startedSimConstructorEmitter, sim.endedSimConstructionEmitter, 'model', phetioID, this.constructor, SIM_STARTED, function( value ) { var simData = { repoName: value.repoName, simName: value.simName, simVersion: value.simVersion, simURL: value.url, userAgent: window.navigator.userAgent, randomSeed: value.randomSeed, wrapperMetadata: window.simStartedMetadata, provider: 'PhET Interactive Simulations, University of Colorado Boulder' // See #137 }; // Delete this global object once it has been used with this emitted event. delete window.simStartedMetadata; return simData; } ); // Store a reference to the sim so that subsequent calls will be simpler. PhET-iO only works with a single sim. phetio.sim = sim; sim.endedSimConstructionEmitter.addListener( function() { // TODO: Can these be coalesced? See https://github.com/phetsims/joist/issues/412 SimIFrameAPI.triggerSimInitialized(); phetio.simulationStarted(); } ); } phetioInherit( TObject, 'TSim', TSim, { disableRequestAnimationFrame: { returnType: TVoid, parameterTypes: [], implementation: function() { this.instance.disableRequestAnimationFrame(); }, documentation: 'Prevents the simulation from animating/updating' }, addEventListener: { returnType: TVoid, parameterTypes: [ TString, TFunctionWrapper( TVoid, [ TString, TFunctionWrapper( TVoid, [] ) ] ) ], implementation: function( eventName, listener ) { this.instance.onStatic( eventName, listener ); }, documentation: 'Add an event listener to the sim instance' }, getScreenshotDataURL: { returnType: TString, parameterTypes: [], implementation: function() { return window.phet.joist.ScreenshotGenerator.generateScreenshot( this.instance ); }, documentation: 'Gets a base64 representation of a screenshot of the simulation as a data url' } }, { documentation: 'The type for the simulation instance', events: [ SIM_STARTED ] } ); joist.register( 'TSim', TSim ); return TSim; } ); // Copyright 2017, University of Colorado Boulder /** * Support for Legends of Learning platform. Sends init message after sim is constructed and support pause/resume. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/thirdPartySupport/LegendsOfLearningSupport',['require','JOIST/joist','PHET_CORE/inherit'],function( require ) { 'use strict'; // modules var joist = require( 'JOIST/joist' ); var inherit = require( 'PHET_CORE/inherit' ); /** * @param {Sim} sim * @constructor */ function LegendsOfLearningSupport( sim ) { // @private this.sim = sim; // Respond to pause/resume commands from the Legends of Learning platform window.addEventListener( 'message', function( message ) { if ( message.data.messageName === 'pause' ) { sim.activeProperty.value = false; } else if ( message.data.messageName === 'resume' ) { sim.activeProperty.value = true; } } ); } joist.register( 'LegendsOfLearningSupport', LegendsOfLearningSupport ); return inherit( Object, LegendsOfLearningSupport, { start: function() { // Send init message when sim has started up so that Legends of Learning can remove their splash screen this.sim.endedSimConstructionEmitter.addListener( function() { window.parent && window.parent.postMessage( { message: 'init' }, '*' ); } ); } } ); } ); // Copyright 2013-2017, University of Colorado Boulder /** * Main class that represents one simulation. * Provides default initialization, such as polyfills as well. * If the simulation has only one screen, then there is no homescreen, home icon or screen icon in the navigation bar. * * @author Sam Reid (PhET Interactive Simulations) * @author Chris Malley (PixelZoom, Inc.) * @author Jonathan Olson */ define( 'JOIST/Sim',['require','PHET_CORE/inherit','DOT/Bounds2','DOT/Dimension2','SCENERY/util/Features','JOIST/NavigationBar','JOIST/HomeScreen','JOIST/HomeScreenView','JOIST/UpdateCheck','SCENERY/util/Util','SCENERY/display/Display','SCENERY/nodes/Node','AXON/Property','AXON/BooleanProperty','AXON/ObservableArray','PHET_CORE/platform','PHET_CORE/Timer','SCENERY_PHET/BarrierRectangle','JOIST/Profiler','JOIST/LookAndFeel','JOIST/ScreenshotGenerator','JOIST/packageJSON','JOIST/PhetButton','JOIST/joist','TANDEM/Tandem','DOT/Util','AXON/Emitter','TANDEM/axon/TandemEmitter','JOIST/TSim','JOIST/thirdPartySupport/LegendsOfLearningSupport','ifphetio!PHET_IO/types/TBoolean','ifphetio!PHET_IO/types/TNumber'],function( require ) { 'use strict'; // modules var inherit = require( 'PHET_CORE/inherit' ); var Bounds2 = require( 'DOT/Bounds2' ); var Dimension2 = require( 'DOT/Dimension2' ); var Features = require( 'SCENERY/util/Features' ); var NavigationBar = require( 'JOIST/NavigationBar' ); var HomeScreen = require( 'JOIST/HomeScreen' ); var HomeScreenView = require( 'JOIST/HomeScreenView' ); var UpdateCheck = require( 'JOIST/UpdateCheck' ); var Util = require( 'SCENERY/util/Util' ); var Display = require( 'SCENERY/display/Display' ); var Node = require( 'SCENERY/nodes/Node' ); var Property = require( 'AXON/Property' ); var BooleanProperty = require( 'AXON/BooleanProperty' ); var ObservableArray = require( 'AXON/ObservableArray' ); var platform = require( 'PHET_CORE/platform' ); var Timer = require( 'PHET_CORE/Timer' ); var BarrierRectangle = require( 'SCENERY_PHET/BarrierRectangle' ); var Profiler = require( 'JOIST/Profiler' ); var LookAndFeel = require( 'JOIST/LookAndFeel' ); var ScreenshotGenerator = require( 'JOIST/ScreenshotGenerator' ); var packageJSON = require( 'JOIST/packageJSON' ); var PhetButton = require( 'JOIST/PhetButton' ); var joist = require( 'JOIST/joist' ); var Tandem = require( 'TANDEM/Tandem' ); var DotUtil = require( 'DOT/Util' );// eslint-disable-line var Emitter = require( 'AXON/Emitter' ); var TandemEmitter = require( 'TANDEM/axon/TandemEmitter' ); var TSim = require( 'JOIST/TSim' ); var LegendsOfLearningSupport = require( 'JOIST/thirdPartySupport/LegendsOfLearningSupport' ); // phet-io modules var TBoolean = require( 'ifphetio!PHET_IO/types/TBoolean' ); var TNumber = require( 'ifphetio!PHET_IO/types/TNumber' ); // constants var PROGRESS_BAR_WIDTH = 273; // globals phet.joist.elapsedTime = 0; // in milliseconds, use this in Tween.start for replicable playbacks // When the simulation is going to be used to play back a recorded session, the simulation must be put into a special // mode in which it will only update the model + view based on the playback clock events rather than the system clock. // This must be set before the simulation is launched in order to ensure that no errant stepSimulation steps are called // before the playback events begin. This value is overridden for playback by TPhETIO. // @public (phet-io) phet.joist.playbackModeEnabledProperty = new BooleanProperty( false ); /** * Main Sim constructor * @param {string} name - the name of the simulation, to be displayed in the navbar and homescreen * @param {Screen[]} screens - the screens for the sim * @param {Object} [options] - see below for options * @constructor */ function Sim( name, screens, options ) { var self = this; // playbackModeEnabledProperty cannot be changed after Sim construction has begun, hence this listener is added before // anything else is done, see https://github.com/phetsims/phet-io/issues/1146 phet.joist.playbackModeEnabledProperty.lazyLink( function( playbackModeEnabled ) { throw new Error( 'playbackModeEnabledProperty cannot be changed after Sim construction has begun' ); } ); var tandem = Tandem.createRootTandem(); var simTandem = tandem.createTandem( 'sim' ); // @public (phet-io) this.tandem = tandem; // @public (phet-io) Emitter for PhET-iO data stream to describe the startup sequence this.startedSimConstructorEmitter = new Emitter(); // @public (phet-io) Emitter for PhET-iO data stream to describe the startup sequence this.endedSimConstructionEmitter = new Emitter(); // @public Emitter that indicates when the sim resized this.resizedEmitter = new Emitter(); // @public Emitter that indicates when a frame starts this.frameStartedEmitter = new Emitter(); // @public Emitter that indicates when a frame ends // phetioEmitData is false because we only want this manually wired for phetio event recording. this.frameEndedEmitter = new TandemEmitter( { tandem: simTandem.createTandem( 'frameEndedEmitter' ), phetioArgumentTypes: [ TNumber( { units: 'seconds' } ) ], phetioEmitData: false // An adapter in phetio will create input events when recording for playback. // If we are not recording for visual playback, then we omit these from the data stream so that we don't get spammed with dt's. } ); var initialScreen = phet.chipper.queryParameters.initialScreen; // The screens to be included, and their order, may be specified via a query parameter. // For documentation, see the schema for phet.chipper.queryParameters.screens in initialize-globals.js. // Do this before setting options.showHomeScreen, since no home screen should be shown if we have 1 screen. if ( QueryStringMachine.containsKey( 'screens' ) ) { var newScreens = []; phet.chipper.queryParameters.screens.forEach( function( userIndex ) { var screenIndex = userIndex - 1; // screens query parameter is 1-based if ( screenIndex < 0 || screenIndex > screens.length - 1 ) { throw new Error( 'invalid screen index: ' + userIndex ); } newScreens.push( screens[ screenIndex ] ); } ); // If the user specified an initial screen other than the homescreen and specified a subset of screens // remap the selected 1-based index from the original screens list to the filtered screens list. if ( initialScreen !== 0 ) { var index = _.indexOf( newScreens, screens[ initialScreen - 1 ] ); assert && assert( index !== -1, 'screen not found' ); initialScreen = index + 1; } screens = newScreens; } options = _.extend( { // whether to show the home screen, or go immediately to the screen indicated by screenIndex showHomeScreen: ( screens.length > 1 ) && phet.chipper.queryParameters.homeScreen, // index of the screen that will be selected at startup (the query parameter is 1-based) screenIndex: initialScreen === 0 ? 0 : initialScreen - 1, // credits, see AboutDialog for format credits: {}, // a {Node} placed into the Options dialog (if available) optionsNode: null, // a {Node} placed onto the home screen (if available) homeScreenWarningNode: null, // if true, records the scenery input events and sends them to a server that can store them recordInputEventLog: false, // when playing back a recorded scenery input event log, use the specified filename. Please see getEventLogName for more inputEventLogName: undefined, // TODO https://github.com/phetsims/energy-skate-park-basics/issues/370 // this function is currently (9-5-2014) specific to Energy Skate Park: Basics, which shows Save/Load buttons in // the PhET menu. This interface is not very finalized and will probably be changed for future versions, // so don't rely on it. showSaveAndLoad: false, // If true, there will be a border shown around the home screen icons. Use this option if the home screen icons // have the same color as the background, as in Color Vision. showSmallHomeScreenIconFrame: false, // Whether accessibility features are enabled or not. Use this option to render the Parallel DOM for // keyboard navigation and screen reader based auditory descriptions. accessibility: phet.chipper.queryParameters.accessibility, // a {Node} placed into the keyboard help dialog that can be opened from the navigation bar keyboardHelpNode: null, // the default renderer for the rootNode, see #221, #184 and https://github.com/phetsims/molarity/issues/24 rootRenderer: platform.edge ? 'canvas' : 'svg' }, options ); // @private - store this for access from prototype functions, assumes that it won't be changed later this.options = options; // override rootRenderer using query parameter, see #221 and #184 options.rootRenderer = phet.chipper.queryParameters.rootRenderer || options.rootRenderer; // @public (joist-internal) - True if the home screen is showing this.showHomeScreenProperty = new Property( initialScreen === 0 ? options.showHomeScreen : false, { tandem: tandem.createTandem( 'sim.showHomeScreenProperty' ), phetioValueType: TBoolean } ); // @public (joist-internal) - The selected screen's index this.screenIndexProperty = new Property( options.screenIndex, { tandem: tandem.createTandem( 'sim.screenIndexProperty' ), phetioValueType: TNumber( { values: _.range( 0, screens.length ) } ) } ); // @public // When the sim is active, scenery processes inputs and stepSimulation(dt) runs from the system clock. // // Set to false for when the sim will be paused. If the sim has playbackModeEnabledProperty set to true, the activeProperty will // automatically be set to false so the timing and inputs can be controlled by the playback engine this.activeProperty = new Property( !phet.joist.playbackModeEnabledProperty.value, { tandem: tandem.createTandem( 'sim.activeProperty' ), phetioValueType: TBoolean } ); // @public (read-only) - property that indicates whether the browser tab containing the simulation is currently visible this.browserTabVisibleProperty = new Property( true, { tandem: tandem.createTandem( 'browserTabVisibleProperty' ), phetioValueType: TBoolean, phetioInstanceDocumentation: 'this Property is read-only, do not attempt to set its value' } ); // set the state of the property that indicates if the browser tab is visible document.addEventListener( 'visibilitychange', function() { self.browserTabVisibleProperty.set( document.visibilityState === 'visible' ); }, false ); // @public (joist-internal, read-only) - how the home screen and navbar are scaled this.scaleProperty = new Property( 1 ); // @public (joist-internal, read-only) - global bounds for the entire simulation this.boundsProperty = new Property( null ); // @public (joist-internal, read-only) - global bounds for the screen-specific part (excludes the navigation bar) this.screenBoundsProperty = new Property( null ); // @public (joist-internal, read-only) - {Screen|null} - The current screen, or null if showing the home screen this.currentScreenProperty = new Property( null ); // Many other components use addInstance at the end of their constructor but in this case we must register early // to (a) enable the SimIFrameAPI as soon as possible and (b) to enable subsequent component registrations, // which require the sim to be registered simTandem.addInstance( this, TSim ); // @public this.lookAndFeel = new LookAndFeel(); assert && assert( window.phet.joist.launchCalled, 'Sim must be launched using SimLauncher, ' + 'see https://github.com/phetsims/joist/issues/142' ); // @private this.destroyed = false; // @public ( joist-internal, read-only ) this.accessible = options.accessibility; // @public ( joist-internal, read-only ) this.keyboardHelpNode = options.keyboardHelpNode; assert && assert( !window.phet.joist.sim, 'Only supports one sim at a time' ); window.phet.joist.sim = self; // Make ScreenshotGenerator available globally so it can be used in preload files such as PhET-iO. window.phet.joist.ScreenshotGenerator = ScreenshotGenerator; this.name = name; // @public (joist-internal) this.version = packageJSON.version; // @public (joist-internal) this.credits = options.credits; // @public (joist-internal) // @private - number of animation frames that have occurred this.frameCounter = 0; // @private {boolean} - Whether the window has resized since our last updateDisplay() this.resizePending = true; // used to store input events and requestAnimationFrame cycles this.inputEventLog = []; // @public (joist-internal) this.inputEventBounds = Bounds2.NOTHING; // @public (joist-internal) // @public - Make our locale available this.locale = phet.chipper.locale || 'en'; // If the locale query parameter was specified, then we may be running the all.html file, so adjust the title. // See https://github.com/phetsims/chipper/issues/510 if ( QueryStringMachine.containsKey( 'locale' ) ) { $( 'title' ).html( name ); } if ( phet.chipper.queryParameters.recordInputEventLog ) { // enables recording of Scenery's input events, request animation frames, and dt's so the sim can be played back options.recordInputEventLog = true; options.inputEventLogName = phet.chipper.queryParameters.recordInputEventLog; } if ( phet.chipper.queryParameters.playbackInputEventLog ) { // instead of loading like normal, download a previously-recorded event sequence and play it back (unique to the browser and window size) options.playbackInputEventLog = true; options.inputEventLogName = phet.chipper.queryParameters.playbackInputEventLog; } // override window.open with a semi-API-compatible function, so fuzzing doesn't open new windows. if ( phet.chipper.queryParameters.fuzzMouse ) { window.open = function() { return { focus: function() {}, blur: function() {} }; }; } this.startedSimConstructorEmitter.emit1( { repoName: packageJSON.name, simName: this.name, simVersion: this.version, url: window.location.href, randomSeed: window.phet.chipper.randomSeed } ); var $body = $( 'body' ); // prevent scrollbars $body.css( 'padding', '0' ).css( 'margin', '0' ).css( 'overflow', 'hidden' ); // set `user-select: none` on the aria-live container to prevent iOS text selection issue, see // https://github.com/phetsims/scenery/issues/1006 var ariaLiveContainer = document.getElementById( 'aria-live-elements' ); if ( ariaLiveContainer ) { ariaLiveContainer.style[ Features.userSelect ] = 'none'; } // check to see if the sim div already exists in the DOM under the body. This is the case for https://github.com/phetsims/scenery/issues/174 (iOS offline reading list) if ( document.getElementById( 'sim' ) && document.getElementById( 'sim' ).parentNode === document.body ) { document.body.removeChild( document.getElementById( 'sim' ) ); } // Prevents selection cursor issues in Safari, see https://github.com/phetsims/scenery/issues/476 document.onselectstart = function() { return false; }; // @public this.rootNode = new Node( { renderer: options.rootRenderer } ); // When the sim becomes inactive, interrupt any currently active input listeners, see https://github.com/phetsims/scenery/issues/619. this.activeProperty.lazyLink( function( active ) { if ( !active ) { self.rootNode.interruptSubtreeInput(); } } ); // @private this.display = new Display( self.rootNode, { // prevent overflow that can cause iOS bugginess, see https://github.com/phetsims/phet-io/issues/341 allowSceneOverflow: false, // Indicate whether webgl is allowed to facilitate testing on non-webgl platforms, see https://github.com/phetsims/scenery/issues/289 allowWebGL: phet.chipper.queryParameters.webgl, accessibility: options.accessibility, isApplication: false, assumeFullWindow: true // a bit faster if we can assume no coordinate translations are needed for the display. } ); // When the sim is inactive, make it non-interactive, see https://github.com/phetsims/scenery/issues/414 this.activeProperty.link( function( active ) { self.display.interactive = active; // The sim must remain inactive while playbackModeEnabledProperty is true if ( active ) { assert && assert( !phet.joist.playbackModeEnabledProperty.value, 'The sim must remain inactive while playbackModeEnabledProperty is true' ); } } ); var simDiv = self.display.domElement; simDiv.id = 'sim'; document.body.appendChild( simDiv ); // for preventing Safari from going to sleep - added to the simDiv instead of the body to prevent a VoiceOver bug // where the virtual cursor would spontaneously move when the div content changed, see // https://github.com/phetsims/joist/issues/140 var heartbeatDiv = this.heartbeatDiv = document.createElement( 'div' ); heartbeatDiv.style.opacity = 0; // Extra style (also used for accessibility) that makes it take up no visual layout space. // Without this, it could cause some layout issues. See https://github.com/phetsims/gravity-force-lab/issues/39 heartbeatDiv.style.position = 'absolute'; heartbeatDiv.style.left = '0'; heartbeatDiv.style.top = '0'; heartbeatDiv.style.width = '0'; heartbeatDiv.style.height = '0'; heartbeatDiv.style.clip = 'rect(0,0,0,0)'; heartbeatDiv.setAttribute( 'aria-hidden', true ); // hide div from screen readers (a11y) simDiv.appendChild( heartbeatDiv ); if ( phet.chipper.queryParameters.sceneryLog ) { this.display.scenery.enableLogging( phet.chipper.queryParameters.sceneryLog ); } if ( phet.chipper.queryParameters.sceneryStringLog ) { this.display.scenery.switchLogToString(); } this.display.initializeEvents(); // sets up listeners on the document with preventDefault(), and forwards those events to our scene window.phet.joist.rootNode = this.rootNode; // make the scene available for debugging window.phet.joist.display = this.display; // make the display available for debugging // Pass through query parameters to scenery for showing supplemental information self.display.setPointerDisplayVisible( phet.chipper.queryParameters.showPointers ); self.display.setPointerAreaDisplayVisible( phet.chipper.queryParameters.showPointerAreas ); self.display.setCanvasNodeBoundsVisible( phet.chipper.queryParameters.showCanvasNodeBounds ); self.display.setFittedBlockBoundsVisible( phet.chipper.queryParameters.showFittedBlockBounds ); function sleep( millis ) { var date = new Date(); var curDate; do { curDate = new Date(); } while ( curDate - date < millis ); } /* * These are used to make sure our sims still behave properly with an artificially higher load (so we can test what happens * at 30fps, 5fps, etc). There tend to be bugs that only happen on less-powerful devices, and these functions facilitate * testing a sim for robustness, and allowing others to reproduce slow-behavior bugs. */ window.phet.joist.makeEverythingSlow = function() { window.setInterval( function() { sleep( 64 ); }, 16 ); }; window.phet.joist.makeRandomSlowness = function() { window.setInterval( function() { sleep( Math.ceil( 100 + Math.random() * 200 ) ); }, Math.ceil( 100 + Math.random() * 200 ) ); }; // @public this.screens = screens; // Multi-screen sims get a home screen. if ( screens.length > 1 ) { this.homeScreen = new HomeScreen( this, tandem.createTandem( 'homeScreen' ), { warningNode: options.homeScreenWarningNode, showSmallHomeScreenIconFrame: options.showSmallHomeScreenIconFrame } ); this.homeScreen.initializeModelAndView(); } else { this.homeScreen = null; } // @public (joist-internal) this.navigationBar = new NavigationBar( this, screens, tandem.createTandem( 'navigationBar' ) ); // @public (joist-internal) this.updateBackground = function() { self.lookAndFeel.backgroundColorProperty.value = self.currentScreenProperty.value ? self.currentScreenProperty.value.backgroundColorProperty.value : self.homeScreen.backgroundColorProperty.value; }; this.lookAndFeel.backgroundColorProperty.link( function( backgroundColor ) { self.display.backgroundColor = backgroundColor; } ); Property.multilink( [ this.showHomeScreenProperty, this.screenIndexProperty ], function( showHomeScreen, screenIndex ) { self.currentScreenProperty.value = ( showHomeScreen && self.homeScreen ) ? null : screens[ screenIndex ]; self.updateBackground(); } ); // When the user switches screens, interrupt the input on the previous screen. // See https://github.com/phetsims/scenery/issues/218 this.currentScreenProperty.lazyLink( function( newScreen, oldScreen ) { if ( oldScreen === null ) { self.homeScreen.view.interruptSubtreeInput(); } else { oldScreen.view.interruptSubtreeInput(); } } ); // Third party support phet.chipper.queryParameters.legendsOfLearning && new LegendsOfLearningSupport( this ).start(); } joist.register( 'Sim', Sim ); return inherit( Object, Sim, { finishInit: function( screens, tandem ) { var self = this; // ModuleIndex should always be defined. On startup screenIndex=1 to highlight the 1st screen. // When moving from a screen to the homescreen, the previous screen should be highlighted if ( this.homeScreen ) { this.rootNode.addChild( this.homeScreen.view ); } _.each( screens, function( screen ) { screen.view.layerSplit = true; self.rootNode.addChild( screen.view ); } ); this.rootNode.addChild( this.navigationBar ); if ( this.homeScreen ) { // Once both the navbar and homescreen have been added, link the PhET button positions together. // See https://github.com/phetsims/joist/issues/304. PhetButton.linkPhetButtonTransform( this.homeScreen, this.navigationBar, this.rootNode ); } Property.multilink( [ this.showHomeScreenProperty, this.screenIndexProperty ], function( showHomeScreen, screenIndex ) { if ( self.homeScreen ) { // You can't set the active property if the screen is visible, so order matters here if ( showHomeScreen ) { self.homeScreen.activeProperty.set( true ); self.homeScreen.view.setVisible( true ); } else { self.homeScreen.view.setVisible( false ); self.homeScreen.activeProperty.set( false ); } } // Make the selected screen visible and active, other screens invisible and inactive. // screen.isActiveProperty should change only while the screen is invisible. // See https://github.com/phetsims/joist/issues/418. for ( var i = 0; i < screens.length; i++ ) { var screen = screens[ i ]; var visible = ( !showHomeScreen && screenIndex === i ); if ( visible ) { screen.activeProperty.set( visible ); } screen.view.setVisible( visible ); if ( !visible ) { screen.activeProperty.set( visible ); } } self.navigationBar.setVisible( !showHomeScreen ); self.updateBackground(); } ); // layer for popups, dialogs, and their backgrounds and barriers this.topLayer = new Node(); this.rootNode.addChild( this.topLayer ); // @private list of nodes that are "modal" and hence block input with the barrierRectangle. Used by modal dialogs // and the PhetMenu this.modalNodeStack = new ObservableArray( { // tandem: tandem.createTandem( 'modalNodeStack' ), // phetioValueType: TNode } ); // {Node} with node.hide() // @public (joist-internal) Semi-transparent black barrier used to block input events when a dialog (or other popup) // is present, and fade out the background. this.barrierRectangle = new BarrierRectangle( this.modalNodeStack, { fill: 'rgba(0,0,0,0.3)', pickable: true, tandem: tandem.createTandem( 'sim.barrierRectangle' ) } ); this.topLayer.addChild( this.barrierRectangle ); // Fit to the window and render the initial scene // Can't synchronously do this in Firefox, see https://github.com/phetsims/vegas/issues/55 and // https://bugzilla.mozilla.org/show_bug.cgi?id=840412. var resizeListener = function() { // Don't resize on window size changes if we are playing back input events. // See https://github.com/phetsims/joist/issues/37 if ( !phet.joist.playbackModeEnabledProperty.value ) { self.resizePending = true; } }; $( window ).resize( resizeListener ); window.addEventListener( 'resize', resizeListener ); window.addEventListener( 'orientationchange', resizeListener ); window.visualViewport && window.visualViewport.addEventListener( 'resize', resizeListener ); this.resizeToWindow(); // Kick off checking for updates, if that is enabled UpdateCheck.check(); // @public (joist-internal) - Keep track of the previous time for computing dt, and initially signify that time // hasn't been recorded yet. this.lastTime = -1; // @public (joist-internal) // Bind the animation loop so it can be called from requestAnimationFrame with the right this. this.boundRunAnimationLoop = this.runAnimationLoop.bind( this ); }, /* * Adds a popup in the global coordinate frame, and optionally displays a semi-transparent black input barrier behind it. * Use hidePopup() to remove it. * @param {Node} node - Should have node.hide() implemented to hide the popup (should subsequently call * sim.hidePopup()). * @param {boolean} isModal - Whether to display the semi-transparent black input barrier behind it. * @public */ showPopup: function( node, isModal ) { assert && assert( node ); assert && assert( !!node.hide, 'Missing node.hide() for showPopup' ); assert && assert( !this.topLayer.hasChild( node ), 'Popup already shown' ); if ( isModal ) { this.modalNodeStack.push( node ); } this.topLayer.addChild( node ); }, /* * Hides a popup that was previously displayed with showPopup() * @param {Node} node * @param {boolean} isModal - Whether the previous popup was modal (or not) * @public */ hidePopup: function( node, isModal ) { assert && assert( node && this.modalNodeStack.contains( node ) ); assert && assert( this.topLayer.hasChild( node ), 'Popup was not shown' ); if ( isModal ) { this.modalNodeStack.remove( node ); } this.topLayer.removeChild( node ); }, /** * @public (joist-internal) */ resizeToWindow: function() { this.resizePending = false; this.resize( window.innerWidth, window.innerHeight ); }, // @public (joist-internal, phet-io) resize: function( width, height ) { var self = this; var scale = Math.min( width / HomeScreenView.LAYOUT_BOUNDS.width, height / HomeScreenView.LAYOUT_BOUNDS.height ); // 40 px high on iPad Mobile Safari var navBarHeight = scale * NavigationBar.NAVIGATION_BAR_SIZE.height; self.navigationBar.layout( scale, width, navBarHeight ); self.navigationBar.y = height - navBarHeight; self.display.setSize( new Dimension2( width, height ) ); var screenHeight = height - self.navigationBar.height; // Layout each of the screens _.each( self.screens, function( m ) { m.view.layout( width, screenHeight ); } ); // Resize the layer with all of the dialogs, etc. self.topLayer.setScaleMagnitude( scale ); self.homeScreen && self.homeScreen.view.layout( width, height ); // Startup can give spurious resizes (seen on ipad), so defer to the animation loop for painting // Fixes problems where the div would be way off center on iOS7 if ( platform.mobileSafari ) { window.scrollTo( 0, 0 ); } // update our scale and bounds properties after other changes (so listeners can be fired after screens are resized) this.scaleProperty.value = scale; this.boundsProperty.value = new Bounds2( 0, 0, width, height ); this.screenBoundsProperty.value = new Bounds2( 0, 0, width, screenHeight ); // Signify that the sim has been resized. // {Bounds2} bounds - the size of the window.innerWidth and window.innerHeight, which depends on the scale // {Bounds2} screenBounds - subtracts off the size of the navbar from the height // {number} scale - the overall scaling factor for elements in the view this.resizedEmitter.emit3( this.boundsProperty.value, this.screenBoundsProperty.value, this.scaleProperty.value ); }, // @public (joist-internal) start: function() { var self = this; // In order to animate the loading progress bar, we must schedule work with setTimeout // This array of {function} is the work that must be completed to launch the sim. var workItems = []; var screens = this.screens; // Schedule instantiation of the screens screens.forEach( function initializeScreen( screen ) { workItems.push( function() { screen.backgroundColorProperty.link( self.updateBackground ); screen.initializeModel(); } ); workItems.push( function() { screen.initializeView(); } ); } ); // loop to run startup items asynchronously so the DOM can be updated to show animation on the progress bar var runItem = function( i ) { setTimeout( function() { workItems[ i ](); // Move the progress ahead by one so we show the full progress bar for a moment before the sim starts up var progress = DotUtil.linear( 0, workItems.length - 1, 0.25, 1.0, i ); // Support iOS Reading Mode, which saves a DOM snapshot after the progressBarForeground has already // been removed from the document, see https://github.com/phetsims/joist/issues/389 if ( document.getElementById( 'progressBarForeground' ) ) { document.getElementById( 'progressBarForeground' ).setAttribute( 'width', (progress * PROGRESS_BAR_WIDTH) + '' ); } if ( i + 1 < workItems.length ) { runItem( i + 1 ); } else { setTimeout( function() { self.finishInit( screens, self.tandem ); // Make sure requestAnimationFrame is defined Util.polyfillRequestAnimationFrame(); // Option for profiling // if true, prints screen initialization time (total, model, view) to the console and displays // profiling information on the screen if ( phet.chipper.queryParameters.profiler ) { Profiler.start( self ); } // place the rAF *before* the render() to assure as close to 60fps with the setTimeout fallback. // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // Launch the bound version so it can easily be swapped out for debugging. self.boundRunAnimationLoop(); // Communicate sim load (successfully) to joist/tests/test-sims.html if ( phet.chipper.queryParameters.postMessageOnLoad ) { window.parent && window.parent.postMessage( JSON.stringify( { type: 'load', url: window.location.href } ), '*' ); } // After the application is ready to go, remove the splash screen and progress bar $( '#splash' ).remove(); $( '#progressBar' ).remove(); // Signify the end of simulation startup. Used by PhET-iO. self.endedSimConstructionEmitter.emit(); }, 25 ); // pause for a few milliseconds with the progress bar filled in before going to the home screen } }, // The following sets the amount of delay between each work item to make it easier to see the changes to the // progress bar. A total value is divided by the number of work items. This makes it possible to see the // progress bar when few work items exist, such as for a single screen sim, but allows things to move // reasonably quickly when more work items exist, such as for a four-screen sim. 30 / workItems.length ); }; runItem( 0 ); }, // Destroy a sim so that it will no longer consume any resources. Formerly used in Smorgasbord. May not be used by // anything else at the moment. // @public (joist-internal) destroy: function() { this.destroyed = true; var simDiv = this.display.domElement; simDiv.parentNode && simDiv.parentNode.removeChild( simDiv ); }, // @private - Bound to this.boundRunAnimationLoop so it can be run in window.requestAnimationFrame runAnimationLoop: function() { if ( !this.destroyed ) { window.requestAnimationFrame( this.boundRunAnimationLoop ); } // Setting the activeProperty to false pauses the sim and also enables optional support for playback back recorded // events (if playbackModeEnabledProperty) is true if ( this.activeProperty.value ) { // Compute the elapsed time since the last frame, or guess 1/60th of a second if it is the first frame var time = Date.now(); var elapsedTimeMilliseconds = (this.lastTime === -1) ? (1000.0 / 60.0) : (time - this.lastTime); this.lastTime = time; // Convert to seconds var dt = elapsedTimeMilliseconds / 1000.0; // Don't run the simulation on steps back in time (see https://github.com/phetsims/joist/issues/409) if ( dt >= 0 ) { this.stepSimulation( dt ); } } }, /** * Returns the selected screen, or null if the home screen is showing. * @returns {Screen|null} * @private */ getSelectedScreen: function() { return this.showHomeScreenProperty.value ? null : this.screens[ this.screenIndexProperty.value ]; }, /** * Update the simulation model, view, scenery display with an elapsed time of dt. * @param {number} dt in seconds * @public (phet-io) */ stepSimulation: function( dt ) { this.frameStartedEmitter.emit(); // increment this before we can have an exception thrown, to see if we are missing frames this.frameCounter++; phetAllocation && phetAllocation( 'loop' ); // prevent Safari from going to sleep, see https://github.com/phetsims/joist/issues/140 if ( this.frameCounter % 1000 === 0 ) { this.heartbeatDiv.innerHTML = Math.random(); } if ( this.resizePending ) { this.resizeToWindow(); } // fire or synthesize input events if ( phet.chipper.queryParameters.fuzzMouse ) { this.display.fuzzMouseEvents( phet.chipper.queryParameters.fuzzRate ); } // If the user is on the home screen, we won't have a Screen that we'll want to step. This must be done after // fuzz mouse, because fuzzing could change the selected screen, see #130 var screen = this.getSelectedScreen(); // cap dt based on the current screen, see https://github.com/phetsims/joist/issues/130 if ( screen && screen.maxDT ) { dt = Math.min( dt, screen.maxDT ); } // TODO: we are /1000 just to *1000? Seems wasteful and like opportunity for error. See https://github.com/phetsims/joist/issues/387 // Store the elapsed time in milliseconds for usage by Tween clients phet.joist.elapsedTime = phet.joist.elapsedTime + dt * 1000; // Timer step before model/view steps, see https://github.com/phetsims/joist/issues/401 Timer.step( dt ); // If the DT is 0, we will skip the model step (see https://github.com/phetsims/joist/issues/171) if ( screen && screen.model.step && dt ) { screen.model.step( dt ); } // If using the TWEEN animation library, then update all of the tweens (if any) before rendering the scene. // Update the tweens after the model is updated but before the view step. // See https://github.com/phetsims/joist/issues/401. //TODO https://github.com/phetsims/joist/issues/404 run TWEENs for the selected screen only if ( window.TWEEN ) { window.TWEEN.update( phet.joist.elapsedTime ); } // View step is the last thing before updateDisplay(), so we can do paint updates there. // See https://github.com/phetsims/joist/issues/401. if ( screen && screen.view.step ) { screen.view.step( dt ); } this.display.updateDisplay(); this.frameEndedEmitter.emit1( dt ); } } ); } ); // Copyright 2015, University of Colorado Boulder /** * Checks global references (on the phet object) to verify all modules loaded through require.js that match the usual * pattern that would be namespaced. For example, if the sim uses SCENERY_PHET/buttons/ArrowButton, calling this * will check for the presence of phet.sceneryPhet.ArrowButton. * * See https://github.com/phetsims/tasks/issues/378 * * @author Jonathan Olson */ define( 'JOIST/checkNamespaces',['require','JOIST/joist'],function( require ) { 'use strict'; // modules var joist = require( 'JOIST/joist' ); var checkNamespaces = function() { // Get a reference to the defined modules. There doesn't seem to be a common way to access this internal // information yet in the optimizer (with almond) and with require.js, so we have a fall-back set up. var defined = window.requirejs._defined || window.require.s.contexts._.defined; /** * This function iterates over all defined AMD modules and reports to a problemHandler any problems. * @param {function} problemHandler, a function that is called when an error occurs, with an {string} argument that * describes the problem */ var visit = function( problemHandler ) { for ( var moduleName in defined ) { // Skip strings, images, or anything imported with a plugin. Could be added later if needed if ( moduleName.indexOf( '!' ) >= 0 ) { continue; } // Skip anything without a slash (the plugins themselves, and the main/config files) if ( moduleName.indexOf( '/' ) < 0 ) { continue; } // Skip anything with '..' in the path (chipper imports some things where we can't get the repository prefix) if ( moduleName.indexOf( '..' ) >= 0 ) { continue; } var prefix = moduleName.slice( 0, moduleName.indexOf( '/' ) ); // e.g. 'SCENERY_PHET' var name = moduleName.slice( moduleName.lastIndexOf( '/' ) + 1 ); // e.g. 'ArrowButton' // Convert to camel-case, e.g. 'SCENERY_PHET' to 'sceneryPhet' var prefixTokens = prefix.toLowerCase().split( '_' ); var namespace = [ prefixTokens[ 0 ] ].concat( prefixTokens.slice( 1 ).map( function( token ) { return token.charAt( 0 ).toUpperCase() + token.slice( 1 ); } ) ).join( '' ); // Skip the module that contains the namespace object (e.g. scenery.js, tandemNamespace.js) if ( name === namespace || name === ( namespace + 'Namespace' ) ) { continue; } var namespacedObject = phet[ namespace ] && phet[ namespace ][ name ]; if ( !namespacedObject ) { problemHandler( 'not namespaced: ' + namespace + '.' + name ); } if ( namespacedObject && namespacedObject !== defined[ moduleName ] ) { problemHandler( namespace + '.' + name + ' is different than the expected namespaced object' ); } } }; // First pass prints out all issues, to assist developers as we are adding the namespace register calls visit( function( errorMessage ) { console.log( errorMessage ); } ); // Second pass fails with an assertion error. visit( function( errorMessage ) { assert && assert( false, errorMessage ); } ); }; joist.register( 'checkNamespaces', checkNamespaces ); return checkNamespaces; } ); // Copyright 2015-2016, University of Colorado Boulder /** * Random number generator with an optional seed. * * @author John Blanco (PhET Interactive Simulations) * @author Aaron Davis (PhET Interactive Simulations) * @author Sam Reid (PhET Interactive Simulations) * @author Mohamed Safi */ define( 'DOT/Random',['require','DOT/Util','DOT/dot','PHET_CORE/inherit'],function( require ) { 'use strict'; // modules var Util = require( 'DOT/Util' ); var dot = require( 'DOT/dot' ); var inherit = require( 'PHET_CORE/inherit' ); /** * Construct a Random instance. * * If you are developing a PhET Simulation, you should probably use the global `phet.joist.random` because it * provides built-in support for phet-io seeding and a check that it isn't used before the seed has been set. * * @param {Object} [options] * @constructor */ function Random( options ) { options = _.extend( { // {number|null} seed for the random number generator. When seed is null, Math.random() is used. seed: null, // {boolean} if true, use the seed specified statically in `phet.chipper.randomSeed`. This value is declared // in initialize-globals.js and can be overriden by PhET-iO for reproducible playback (see TPhETIO.setRandomSeed). staticSeed: false }, options ); // If staticSeed and seed are both specified, there will be an assertion error. if ( options.seed !== null && options.staticSeed ) { assert && assert( false, 'cannot specify seed and staticSeed, use one or the other' ); } var seed = options.staticSeed ? window.phet.chipper.randomSeed : options.seed; this.setSeed( seed ); } inherit( Object, Random, { /** * Sets the seed of the random number generator. Setting it to null reverts the random generator to Math.random() * @param {number|null} seed */ setSeed: function( seed ) { this.seed = seed; // If seed is provided, create a local random number generator without altering Math.random. this.seedrandom = this.seed !== null ? new Math.seedrandom( this.seed + '' ) : null; }, /** * Returns the next pseudo-random boolean * @public * @returns {boolean} */ nextBoolean: function() { return this.nextDouble() >= 0.5; }, /** * Returns the next pseudo random number from this random number generator sequence. * The random number is an integer ranging from 0 to n-1. * @public * @param {number} n * @returns {number} - an integer */ nextInt: function( n ) { var value = this.nextDouble() * n; return value | 0; // convert to int by removing the decimal places }, /** * Randomly select a random integer between min and max (inclusive). * * @param {number} min - must be an integer * @param {number} max - must be an integer * @returns {number} an integer between min and max, inclusive */ nextIntBetween: function( min, max ) { assert && assert( arguments.length === 2, 'nextIntBetween must have exactly 2 arguments' ); assert && assert( Util.isInteger( min ), 'min must be an integer: ' + min ); assert && assert( Util.isInteger( max ), 'max must be an integer: ' + max ); var range = max - min; return this.nextInt( range + 1 ) + min; }, /** * Randomly select one element from the given array. * @param {Object[]} array - the array from which one element will be selected, must have at least one element * @returns {Object} - the selected element from the array */ sample: function( array ) { assert && assert( array.length > 0, 'Array should have at least 1 item.' ); var index = this.nextIntBetween( 0, array.length - 1 ); return array[ index ]; }, /** * Creates an array of shuffled values, using a version of the Fisher-Yates shuffle. Adapted from lodash-2.4.1 by * Sam Reid on Aug 16, 2016, See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. * * @param {Array} array - the array which will be shuffled * @returns {Array} a new array with all the same elements in the passed-in array, in randomized order. */ shuffle: function( array ) { assert && assert( array, 'Array should exist' ); var self = this; var index = -1; var result = new Array( array.length ); _.forEach( array, function( value ) { var rand = self.nextIntBetween( 0, ++index ); result[ index ] = result[ rand ]; result[ rand ] = value; } ); return result; }, /** * Returns the next pseudo random number from this random number generator sequence in the range [0, 1) * The distribution of the random numbers is uniformly distributed across the interval * @public * @returns {number} - the random number */ nextDouble: function() { return this.seed === null ? Math.random() : this.seedrandom(); }, /** * Returns the next gaussian-distributed random number from this random number generator sequence. * The distribution of the random numbers is gaussian, with a mean = 0 and standard deviation = 1 * @public * @returns {number} */ nextGaussian: function() { return Util.boxMullerTransform( 0, 1, this ); } } ); dot.register( 'Random', Random ); return Random; } ); define("PHET_IO/types/TPhETIO", function(){return function(){ return function(){}; };}); // Copyright 2013-2015, University of Colorado Boulder /** * Launches a PhET Simulation, after preloading the specified images. * * @author Sam Reid (PhET Interactive Simulations) */ define( 'JOIST/SimLauncher',['require','JOIST/checkNamespaces','JOIST/joist','TANDEM/Tandem','DOT/Random','ifphetio!PHET_IO/SimIFrameAPI','ifphetio!PHET_IO/types/TPhETIO','ifphetio!PHET_IO/phetio'],function( require ) { 'use strict'; // modules var checkNamespaces = require( 'JOIST/checkNamespaces' ); var joist = require( 'JOIST/joist' ); var Tandem = require( 'TANDEM/Tandem' ); var Random = require( 'DOT/Random' ); // phet-io modules var SimIFrameAPI = require( 'ifphetio!PHET_IO/SimIFrameAPI' ); var TPhETIO = require( 'ifphetio!PHET_IO/types/TPhETIO' ); var phetio = require( 'ifphetio!PHET_IO/phetio' ); var SimLauncher = { /** * Launch the Sim by preloading the images and calling the callback. * * @param callback the callback function which should create and start the sim, given that the images are loaded * @public - to be called by main()s everywhere */ launch: function( callback ) { assert && assert( !window.phet.launchCalled, 'Tried to launch twice' ); //Signify that the SimLauncher was called, see https://github.com/phetsims/joist/issues/142 window.phet.joist.launchCalled = true; // image elements to remove once we are fully loaded var elementsToRemove = []; function doneLoadingImages() { window.phetLaunchSimulation = function() { // Register all of the static tandems. Tandem.launch(); // Provide a global Random that is easy to use and seedable from phet-io for playback // phet-io configuration happens after SimLauncher.launch is called and before phetLaunchSimulation is called phet.joist.random = new Random( { staticSeed: true } ); // Instantiate the sim and show it. callback(); }; // PhET-iO simulations support an initialization phase (before the sim launches) if ( phet.phetio ) { new Tandem( 'phetio' ).addInstance( phetio, TPhETIO ); SimIFrameAPI.initialize(); // calls back to window.phetLaunchSimulation } if ( phet.phetio && !phet.phetio.queryParameters.phetioStandalone ) { // Wait for phet-io to finish adding listeners. It will direct the launch from there. } else { window.phetLaunchSimulation(); } } // if image dimensions exist, immediately fire the "all images loaded" event var loaded = 0; // Taken from http://stackoverflow.com/questions/1977871/check-if-an-image-is-loaded-no-errors-in-javascript function isImageOK( img ) { // During the onload event, IE correctly identifies any images that // weren’t downloaded as not complete. Others should too. Gecko-based // browsers act like NS4 in that they report this incorrectly. if ( !img.complete ) { return false; } // However, they do have two very useful properties: naturalWidth and // naturalHeight. These give the true size of the image. If it failed // to load, either of these should be zero. if ( typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0 ) { return false; } // No other way of checking: assume it’s ok. return true; } //For the images that were written to base64 format using requirejs, make sure they are loaded. //img.src = base64 is asynchronous on IE10 and OSX/Safari, so we have to make sure they loaded before returning. if ( window.phetImages ) { for ( var i = 0; i < window.phetImages.length; i++ ) { var phetImage = window.phetImages[ i ]; // For built versions that use phet-io, the simulation may have already loaded all of the images, so // check them here before scheduling them for load. if ( isImageOK( phetImage ) ) { loaded++; if ( loaded === window.phetImages.length ) { doneLoadingImages(); } } else { phetImage.onload = function() { loaded++; if ( loaded === window.phetImages.length ) { doneLoadingImages(); } }; } } } else { doneLoadingImages(); } $( window ).load( function() { // if images were not loaded immediately, signal the "all images loaded" event // we wait for here to remove the images from the DOM, otherwise IE9/10 treat the images as completely blank! _.each( elementsToRemove, function( element ) { //TODO: Why is this null sometimes? see https://github.com/phetsims/joist/issues/388 if ( element.parentNode ) { element.parentNode.removeChild( element ); } } ); } ); // Check namespaces if assertions are enabled, see https://github.com/phetsims/joist/issues/307. assert && checkNamespaces(); } }; joist.register( 'SimLauncher', SimLauncher ); return SimLauncher; } ); define("string!EXPRESSION_EXCHANGE/expression-exchange.title",function(){return window.phet.chipper.strings.get("EXPRESSION_EXCHANGE/expression-exchange.title");}); // Copyright 2015, University of Colorado Boulder /** * Main entry point for the sim. * * @author John Blanco */ define( 'expression-exchange-main',['require','EXPRESSION_EXCHANGE/basics/EEBasicsScreen','EXPRESSION_EXCHANGE/explore/EEExploreScreen','EXPRESSION_EXCHANGE/game/EEGameScreen','EXPRESSION_EXCHANGE/negatives/EENegativesScreen','EXPRESSION_EXCHANGE/common/EEQueryParameters','EXPRESSION_EXCHANGE/expressionExchange','JOIST/Sim','JOIST/SimLauncher','string!EXPRESSION_EXCHANGE/expression-exchange.title'],function( require ) { 'use strict'; // modules var EEBasicsScreen = require( 'EXPRESSION_EXCHANGE/basics/EEBasicsScreen' ); var EEExploreScreen = require( 'EXPRESSION_EXCHANGE/explore/EEExploreScreen' ); var EEGameScreen = require( 'EXPRESSION_EXCHANGE/game/EEGameScreen' ); var EENegativesScreen = require( 'EXPRESSION_EXCHANGE/negatives/EENegativesScreen' ); var EEQueryParameters = require( 'EXPRESSION_EXCHANGE/common/EEQueryParameters' ); var expressionExchange = require( 'EXPRESSION_EXCHANGE/expressionExchange' ); var Sim = require( 'JOIST/Sim' ); var SimLauncher = require( 'JOIST/SimLauncher' ); // strings var expressionExchangeTitleString = require( 'string!EXPRESSION_EXCHANGE/expression-exchange.title' ); // credits var simOptions = { credits: { leadDesign: 'Amanda McGarry', softwareDevelopment: 'John Blanco', graphicArts: 'Mariah Hermsmeyer', team: 'Ariel Paul, Kathy Perkins, David Webb', qualityAssurance: 'Steele Dalton, Alex Dornan, Ethan Johnson' } }; // Add support for logging if specified. This is intended for debug of things like coin term and expression creation // and removal, so it doesn't need to be enabled before the module loading (RequireJS) phase. if ( EEQueryParameters.enableLogging ) { console.log( 'enabling log' ); expressionExchange.log = function( message ) { console.log( '%clog: ' + message, 'color: #009900' ); // green }; } // launch the sim SimLauncher.launch( function() { var sim = new Sim( expressionExchangeTitleString, [ new EEBasicsScreen(), new EEExploreScreen(), new EENegativesScreen(), new EEGameScreen() ], simOptions ); sim.start(); } ); } ); require(["expression-exchange-main"]); }());
OSZAR »