1 /*
  2  *Copyright (c) 2009, TellurianRing.com
  3  *All rights reserved.
  4  *
  5  *Redistribution and use in source and binary forms, with or without modification,
  6  *are permitted provided that the following conditions are met:
  7  *
  8  *   Redistributions of source code must retain the above copyright notice, this
  9  *   list of conditions and the following disclaimer.
 10  *   Redistributions in binary form must reproduce the above copyright notice,
 11  *   this list of conditions and the following disclaimer in the documentation
 12  *   and/or other materials provided with the distribution.
 13  *   Neither the name of the Organization (TellurianRing.com) nor the names of
 14  *   its contributors may be used to endorse or promote products derived from
 15  *   this software without specific prior written permission.
 16  *
 17  *THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 18  *ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 19  *WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 20  *DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 21  *ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 22  *(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 23  *LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 24  *ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 25  *(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 26  *SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 27  */
 28 /**
 29  * @class LoadingScene Loads a given set of assets. Assets include Graphics and Sounds.
 30  * @example // Displays a single message as assets are loading.
 31  * LoadingScene({
 32  *    steps: {
 33  *       "Assets": [
 34  *          Graphic({ url: "./images/ball.png" }),
 35  *          Sound({ url: "./sounds/bang.ogg" }),
 36  *          ...
 37  *       ]
 38  *    }
 39  * })
 40  * // This example will show the following message:
 41  * // "Loading - Assets [percent]% complete, overall [percent]% complete"
 42  * @example // Having two steps for images and sounds.
 43  * LoadingScene({
 44  *    steps: {
 45  *       "Graphics": [
 46  *          Graphic({ url: "./images/ball.png" }),
 47  *          ...
 48  *       ],
 49  *       "Sounds": [
 50  *          Sound({ url: "./sounds/bang.ogg" }),
 51  *          ...
 52  *       ]
 53  *    }
 54  * })
 55  * @example // You can also supply your own content.
 56  * // Say you want to display your own background image as the assets are loading
 57   * LoadingScene({
 58  *    steps: {
 59  *       "Assets": [
 60  *          Graphic({ url: "./images/ball.png" }),
 61  *          Sound({ url: "./sounds/bang.ogg" }),
 62  *          ...
 63  *       ]
 64  *    },
 65  *    content: [
 66  *       Graphic({ url: "./images/loading_bg.png" })
 67  *    ]
 68  * })
 69  * @param {Object} _details A JSON object with the following properties.
 70  * <table cellpadding="0" cellspacing="1" border="0" class="constructor_details">
 71  *    <tr><th>Property</th> <th>Required</th> <th>Default</th></tr>
 72  *    <tr><td>content</td>  <td>no</td>       <td></td></tr>
 73  *    <tr><td>steps</td>    <td>no</td>       <td></td></tr>
 74  * </table>
 75  */
 76 function LoadingScene(_details) { // {{{
 77    function _LoadingScene(_details) { // {{{
 78       
 79       // Private Members {{{
 80       var content = _details.content;
 81       var current_step = 0;
 82       var finished = false;
 83       var step_loaded = 0;
 84       var step_loading = 0;
 85       var step_names = new Array();
 86       var step_totals = {};
 87       var steps = check(_details.steps, {});
 88       var that = this;
 89       var total = 0;
 90       var total_loaded = 0;
 91       var total_loading = 0;
 92       // }}} Private Members
 93       
 94       // Public Members {{{
 95       /**
 96        * Creates the default content for this LoadingScene. The default content
 97        * is a Text object which displays the following message in the middle of
 98        * the scene:
 99        * "Loading - [step] [percent]% complete, overall [percent]% complete"
100        * @function
101        */
102       this.createDefaultContent = function() { // {{{
103          var scene_bounds = this.getScene().getBounds();
104          var defaultContent = [
105             Text({
106                   x: scene_bounds.w / 2, y: scene_bounds.h / 2,
107                   align: Text.Align.center, font: Font({ size: "16px", weight: Font.Weights.bold }),
108                   update: function(_run_time, _step, _step_percent, _total_percent, _finished) {
109                      var text = "Loading - " + _step + " " + Math.round(_step_percent * 100) + "% complete, overall " + Math.round(_total_percent * 100) + "% complete";
110                      if(_finished) {
111                         text = "Loaded!";
112                      }
113                      this.setText(text);
114                   }
115             })
116          ];
117          return defaultContent;
118       } // }}} createDefaultContent
119       
120       this.draw = function(_context) { // {{{
121          for(var ci in content) {
122             var component = content[ci];
123             component.draw(_context);
124          }
125       } // }}} draw
126       
127       /**
128        * @function called when all the assets have been loaded. Calls finish 
129        * function of details if one is provided. This function can be used to
130        * set the scene on the stage after the assets have been loaded.
131        * @example
132        * var stage = Stage({
133        *    scene: Scene({
134        *       content: [
135        *          LoadingScene({
136        *             steps: { ... },
137        *             finished: function() {
138        *                stage.unload(); stage.setScene(nextScene); stage.load();
139        *             }
140        *          })
141        *       ]
142        *    })
143        * });
144        */
145       this.finish = function() { // {{{
146          finished = true;
147          if(exists(this.details.finish)) {
148             details.finish();
149          }
150       } // }}} finish
151       
152       /**
153        * Loads any content provided then starts the loader thread to preload
154        * any assets provided in the steps.
155        * @function
156        */
157       this.load = function(_scene, _parent) { // {{{
158          this.loadBase(_scene, _parent);
159          if(!exists(content)) {
160             content = this.createDefaultContent();
161          }
162          
163          // Load content.
164          for(var ci in content) {
165             var component = content[ci];
166             component.load(_scene, that);
167          }
168          
169          // set up the info.
170          var stepCount = 0;
171          for(var si in steps) {
172             var step = steps[si];
173             step_names[stepCount] = si;
174             var assetCount = 0;
175             for(var ai in step) {
176                var asset = step[ai];
177                assetCount++;
178                total++;
179             }
180             step_totals[si] = assetCount;
181             stepCount++;
182          }
183          // start the loading thread
184          setTimeout(this.loadNextAsset, 30);
185       } // }}} load
186       
187       
188       /**
189        * Loads the next asset in the current step.
190        * @function
191        */
192       this.loadNextAsset = function() { // {{{
193          var step_name = step_names[current_step];
194          var step = steps[step_name];
195          // get next current asset and increment loading variables.
196          var asset = step[step_loading++]; total_loading++;
197          asset.details.onload = function() {
198             // increment loaded variables.
199             total_loaded++; step_loaded++;
200             if(step_loading < step_totals[step_name]) {
201                // if loading is less than total number of assets for current step
202                // we load the next one.
203                setTimeout(that.loadNextAsset, 30);
204             } else if (++current_step < step_names.length) {
205                // else if the next current_step is less than total number of steps
206                // start on the next step.
207                step_loaded = 0;
208                step_loading = 0;
209                setTimeout(that.loadNextAsset, 30);
210             } else {
211                // we're done.
212                that.finish();
213             }
214          };
215          asset.load(that.getScene(), that);
216       } // }}} loadNextAsset
217       
218       /**
219        * Updates all content within this LoadingScene. This update function
220        * differs from the Scene object because it passes extra information to 
221        * each content. It calls each component's update function with the
222        * following:
223        * <table cellpadding="0" cellspacing="1" border="0" class="constructor_details">
224        *    <tr><th>Property</th>      <th>Type</th>    <th>Description</th></tr>
225        *    <tr><td>runtime</td>       <td>Integer</td> <td>Current time in milliseconds since the scene was loaded.</td></tr>
226        *    <tr><td>step</td>          <td>String</td>  <td>Current step name as defined in the _details object.</td></tr>
227        *    <tr><td>step_percent</td>  <td>Decimal</td> <td>Percent complete within the current step.</td></tr>
228        *    <tr><td>total_percent</td> <td>Decimal</td> <td>Percent complete overall.</td></tr>
229        *    <tr><td>finished</td>      <td>Boolean</td> <td>Have all the assets been loaded.</td></tr>
230        * </table>
231        * @function
232        */
233       this.update = function(_run_time) { // {{{
234          var current_step_name = step_names[current_step];
235          var step_total = step_totals[current_step_name];
236          for(var ci in content) {
237             var component = content[ci];
238             component.update(_run_time, current_step_name, step_loaded / step_total, total_loaded / total, finished);
239          }
240       } // }}} update
241       
242       // }}} Public Members
243    } // }}} _LoadingScene
244    SceneContent(_details).extend(_LoadingScene);
245    var theLoadingScene = new _LoadingScene(_details);
246    return theLoadingScene;
247 } // }}} LoadingScene
248 // These properties are for jEdit - Programmer's Text Editor.
249 // Load this file in jEdit to see what they do.
250 // ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
251