Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(241)

Side by Side Diff: experimental/conways_life/life.js

Issue 10928195: First round of dead file removal (Closed) Base URL: https://github.com/samclegg/nativeclient-sdk.git@master
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « experimental/conways_life/life.cc ('k') | experimental/conways_life/life_application.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2011 (c) The Native Client Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @file
7 * The life Application object. This object instantiates a Dragger object and
8 * connects it to the element named @a life_module.
9 */
10
11 goog.provide('life.Application');
12
13 goog.require('goog.Disposable');
14 goog.require('goog.array');
15 goog.require('goog.events.EventType');
16 goog.require('goog.style');
17
18 goog.require('life.controllers.ViewController');
19 goog.require('stamp.StampPanel');
20
21 /**
22 * Constructor for the Application class. Use the run() method to populate
23 * the object with controllers and wire up the events.
24 * @constructor
25 * @extends {goog.Disposable}
26 */
27 life.Application = function() {
28 goog.Disposable.call(this);
29 }
30 goog.inherits(life.Application, goog.Disposable);
31
32 /**
33 * The view controller for the application. A DOM element that encapsulates
34 * the grayskull plugin; this is allocated at run time. Connects to the
35 * element with id life.Application.DomIds_.VIEW.
36 * @type {life.ViewController}
37 * @private
38 */
39 life.Application.prototype.viewController_ = null;
40
41 /**
42 * The automaton rule string. It is expressed as SSS/BB, where S is the
43 * neighbour count that keeps a cell alive, and B is the count that causes a
44 * cell to become alive. See the .LIF 1.05 section in
45 * http://psoup.math.wisc.edu/mcell/ca_files_formats.html for more info.
46 * @default 23/3 the "Normal" Conway rules.
47 * @type {Object.<Array>}
48 * @private
49 */
50 life.Application.prototype.automatonRules_ = {
51 birthRule: [3],
52 keepAliveRule: [2, 3]
53 };
54
55 /**
56 * The id of the current stamp. Defaults to the DEFAULT_STAMP_ID.
57 * @type {string}
58 * @private
59 */
60 life.Application.prototype.currentStampId_ =
61 life.controllers.ViewController.DEFAULT_STAMP_ID;
62
63 /**
64 * The ids used for elements in the DOM. The Life Application expects these
65 * elements to exist.
66 * @enum {string}
67 * @private
68 */
69 life.Application.DomIds_ = {
70 BIRTH_FIELD: 'birth_field', // Text field with the birth rule string.
71 CLEAR_BUTTON: 'clear_button', // The clear button element.
72 KEEP_ALIVE_FIELD: 'keep_alive_field', // Keep alive rule string.
73 MODULE: 'life_module', // The <embed> element representing the NaCl module.
74 PLAY_MODE_SELECT: 'play_mode_select', // The <select> element for play mode.
75 PLAY_BUTTON: 'play_button', // The play button element.
76 SOUND_SELECT: 'sound_select', // The <select> element for the stamp sound.
77 VIEW: 'life_view' // The <div> containing the NaCl element.
78 };
79
80 /**
81 * The Run/Stop button attribute labels. These are used to determine the state
82 * and label of the button.
83 * @enum {string}
84 * @private
85 */
86 life.Application.PlayButtonAttributes_ = {
87 ALT_IMAGE: 'altimage', // Image to display in the "on" state.
88 STATE: 'state' // The button's state.
89 };
90
91 /**
92 * Override of disposeInternal() to dispose of retained objects.
93 * @override
94 */
95 life.Application.prototype.disposeInternal = function() {
96 this.terminate();
97 life.Application.superClass_.disposeInternal.call(this);
98 }
99
100 /**
101 * Called by the module loading function once the module has been loaded. Wire
102 * up a Dragger object to @a this.
103 * @param {?String} opt_naclModuleId The id of an <EMBED> element which
104 * contains the NaCl module. If unspecified, defaults to DomIds_.MODULE.
105 * If the DOM element doesn't exist, the program asserts and exits.
106 */
107 life.Application.prototype.moduleDidLoad = function(opt_naclModuleId) {
108 // Listen for 'unload' in order to terminate cleanly.
109 goog.events.listen(window, goog.events.EventType.UNLOAD, this.terminate);
110
111 // Set up the stamp editor.
112 var stampEditorButton =
113 document.getElementById(stamp.StampPanel.DomIds.STAMP_EDITOR_BUTTON);
114 this.stampPanel_ = new stamp.StampPanel(stampEditorButton);
115 var stampEditorButtons = {
116 mainPanel:
117 document.getElementById(stamp.StampPanel.DomIds.STAMP_EDITOR_PANEL),
118 editorContainer:
119 document.getElementById(stamp.StampPanel.DomIds.STAMP_EDITOR_CONTAINER),
120 addColumnButton:
121 document.getElementById(stamp.StampPanel.DomIds.ADD_COLUMN_BUTTON),
122 removeColumnButton:
123 document.getElementById(stamp.StampPanel.DomIds.REMOVE_COLUMN_BUTTON),
124 addRowButton:
125 document.getElementById(stamp.StampPanel.DomIds.ADD_ROW_BUTTON),
126 removeRowButton:
127 document.getElementById(stamp.StampPanel.DomIds.REMOVE_ROW_BUTTON),
128 cancelButton:
129 document.getElementById(stamp.StampPanel.DomIds.CANCEL_BUTTON),
130 okButton: document.getElementById(stamp.StampPanel.DomIds.OK_BUTTON)
131 };
132 this.stampPanel_.makeStampEditorPanel(stampEditorButtons);
133
134 // When the stamp editor panel is about to open, set its stamp to the
135 // current stamp.
136 goog.events.listen(this.stampPanel_, stamp.StampPanel.Events.PANEL_WILL_OPEN,
137 this.handlePanelWillOpen_, false, this);
138
139 // Listen for the "PANEL_DID_SAVE" event posted by the stamp editor.
140 goog.events.listen(this.stampPanel_, stamp.StampPanel.Events.PANEL_DID_SAVE,
141 this.handlePanelDidSave_, false, this);
142
143 // Set up the view controller, it contains the NaCl module.
144 var naclModuleId = opt_naclModuleId || life.Application.DomIds_.MODULE;
145 this.viewController_ = new life.controllers.ViewController(
146 document.getElementById(naclModuleId));
147 this.viewController_.setAutomatonRules(this.automatonRules_);
148 // Initialize the module with the default stamp.
149 this.currentStampId_ = this.viewController_.DEFAULT_STAMP_ID;
150 this.viewController_.selectStamp(this.currentStampId_);
151
152 this.viewController_.setStampSoundUrl('sounds/boing_x.wav');
153
154 // Wire up the various controls.
155 var playModeSelect =
156 document.getElementById(life.Application.DomIds_.PLAY_MODE_SELECT);
157 if (playModeSelect) {
158 goog.events.listen(playModeSelect, goog.events.EventType.CHANGE,
159 this.selectPlayMode, false, this);
160 }
161
162 var soundSelect =
163 document.getElementById(life.Application.DomIds_.SOUND_SELECT);
164 if (playModeSelect) {
165 goog.events.listen(soundSelect, goog.events.EventType.CHANGE,
166 this.selectSound, false, this);
167 }
168
169 var clearButton =
170 document.getElementById(life.Application.DomIds_.CLEAR_BUTTON);
171 if (clearButton) {
172 goog.events.listen(clearButton, goog.events.EventType.CLICK,
173 this.clear, false, this);
174 }
175
176 var playButton =
177 document.getElementById(life.Application.DomIds_.PLAY_BUTTON);
178 if (playButton) {
179 goog.events.listen(playButton, goog.events.EventType.CLICK,
180 this.togglePlayButton, false, this);
181 }
182
183 var birthField =
184 document.getElementById(life.Application.DomIds_.BIRTH_FIELD);
185 if (birthField) {
186 goog.events.listen(birthField, goog.events.EventType.CHANGE,
187 this.updateBirthRule, false, this);
188 }
189
190 var keepAliveField =
191 document.getElementById(life.Application.DomIds_.KEEP_ALIVE_FIELD);
192 if (keepAliveField) {
193 goog.events.listen(keepAliveField, goog.events.EventType.CHANGE,
194 this.updateKeepAliveRule, false, this);
195 }
196 }
197
198 /**
199 * Change the play mode.
200 * @param {!goog.events.Event} changeEvent The CHANGE event that triggered this
201 * handler.
202 */
203 life.Application.prototype.selectPlayMode = function(changeEvent) {
204 changeEvent.stopPropagation();
205 this.viewController_.setPlayMode(changeEvent.target.value);
206 }
207
208 /**
209 * Change the stamp sound.
210 * @param {!goog.events.Event} changeEvent The CHANGE event that triggered this
211 * handler.
212 */
213 life.Application.prototype.selectSound = function(changeEvent) {
214 changeEvent.stopPropagation();
215 this.viewController_.setStampSoundUrl(changeEvent.target.value);
216 }
217
218 /**
219 * Toggle the simulation.
220 * @param {!goog.events.Event} clickEvent The CLICK event that triggered this
221 * handler.
222 */
223 life.Application.prototype.togglePlayButton = function(clickEvent) {
224 clickEvent.stopPropagation();
225 var button = clickEvent.target;
226 var buttonImage = button.style.backgroundImage;
227 var altImage = button.getAttribute(
228 life.Application.PlayButtonAttributes_.ALT_IMAGE);
229 var state = button.getAttribute(
230 life.Application.PlayButtonAttributes_.STATE);
231 // Switch the inner and alternate labels.
232 goog.style.setStyle(button, { 'backgroundImage': altImage });
233 button.setAttribute(life.Application.PlayButtonAttributes_.ALT_IMAGE,
234 buttonImage);
235 if (state == 'off') {
236 button.setAttribute(
237 life.Application.PlayButtonAttributes_.STATE, 'on');
238 this.viewController_.run();
239 } else {
240 button.setAttribute(
241 life.Application.PlayButtonAttributes_.STATE, 'off');
242 this.viewController_.stop();
243 }
244 }
245
246 /**
247 * Handle the "panel will open" event: set the stamp in the stamp editor to
248 * the current stamp.
249 * @param {!goog.events.Event} event The event that triggered this handler.
250 * @private
251 */
252 life.Application.prototype.handlePanelWillOpen_ = function(event) {
253 event.stopPropagation();
254 var currentStamp =
255 this.viewController_.stampWithId(this.currentStampId_);
256 if (currentStamp)
257 this.stampPanel_.setStampFromString(currentStamp);
258 return true;
259 }
260
261 /**
262 * Handle the "panel did save" event: grab the stamp from the stamp editor,
263 * add it to the dictionary of stamps and set it as the current stamp.
264 * @param {!goog.events.Event} event The event that triggered this handler.
265 * @private
266 */
267 life.Application.prototype.handlePanelDidSave_ = function(event) {
268 event.stopPropagation();
269 var stampString = this.stampPanel_.getStampAsString();
270 this.viewController_.addStampWithId(stampString, this.currentStampId_);
271 this.viewController_.selectStamp(this.currentStampId_);
272 }
273
274 /**
275 * Clear the current simulation.
276 * @param {!goog.events.Event} clickEvent The CLICK event that triggered this
277 */
278 life.Application.prototype.clear = function(clickEvent) {
279 clickEvent.stopPropagation();
280 this.viewController_.clear();
281 }
282
283 /**
284 * Read the text input and change it from a comma-separated list into a string
285 * of the form BB, where B is a digit in [0..9] that represents the neighbour
286 * count that causes a cell to come to life.
287 * @param {!goog.events.Event} changeEvent The CHANGE event that triggered this
288 * handler.
289 */
290 life.Application.prototype.updateBirthRule = function(changeEvent) {
291 changeEvent.stopPropagation();
292 var birthRule = this.parseAutomatonRule_(changeEvent.target.value);
293 // Put the sanitized version of the rule string back into the text field.
294 changeEvent.target.value = birthRule.join(',');
295 // Make the new rule string and tell the NaCl module.
296 this.automatonRules_.birthRule = birthRule;
297 this.viewController_.setAutomatonRules(this.automatonRules_);
298 }
299
300 /**
301 * Read the text input and change it from a comma-separated list into a string
302 * of the form SSS, where S is a digit in [0..9] that represents the neighbour
303 * count that allows a cell to stay alive.
304 * @param {!goog.events.Event} changeEvent The CHANGE event that triggered this
305 * handler.
306 */
307 life.Application.prototype.updateKeepAliveRule = function(changeEvent) {
308 changeEvent.stopPropagation();
309 var keepAliveRule = this.parseAutomatonRule_(changeEvent.target.value);
310 // Put the sanitized version of the rule string back into the text field.
311 changeEvent.target.value = keepAliveRule.join(',');
312 // Make the new rule string and tell the NaCl module.
313 this.automatonRules_.keepAliveRule = keepAliveRule;
314 this.viewController_.setAutomatonRules(this.automatonRules_);
315 }
316
317 /**
318 * Parse a user-input string representing an automaton rule into an array of
319 * neighbour counts. |ruleString| is expected to be a comma-separated string
320 * of integers in range [0..9]. This routine attempts to sanitize non-
321 * conforming values by clipping (numbers outside [0..9] are clipped), and
322 * replaces non-numberic input with 0. The resulting array is sorted, and each
323 * value is unique. For example: '1,3,2,2' will produce [1, 2, 3].
324 * @param {!string} ruleString The user-input string.
325 * @return {Array.<number>} An array of neighbour counts that can be used to
326 * create an automaton rule.
327 * @private
328 */
329 life.Application.prototype.parseAutomatonRule_ = function(ruleString) {
330 var rule = ruleString.split(',');
331
332 /**
333 * Helper function to parse a single rule element: trim off any leading or
334 * trailing white-space, then attempt to convert the resulting string into
335 * an integer. Clip the integer to range [0..8]. Replace the element in
336 * |array| with the resulting number. Note: non-numeric values are replaced
337 * with 0.
338 * @param {string} ruleString The string to parse.
339 * @param {number} index The index of the element in the original array.
340 * @param {Array} ruleArray The array of rules.
341 */
342 function parseOneRule(ruleString, index, ruleArray) {
343 var neighbourCount = parseInt(ruleString.trim());
344 if (isNaN(neighbourCount) || neighbourCount < 0)
345 neighbourCount = 0;
346 if (neighbourCount > 8)
347 neighbourCount = 8;
348 ruleArray[index] = neighbourCount;
349 }
350
351 // Each rule has to be an integer in [0..8]
352 goog.array.forEach(rule, parseOneRule, this);
353 // Sort the rules numerically.
354 rule.sort(function(a, b) { return a - b; });
355 goog.array.removeDuplicates(rule);
356 return rule;
357 }
358
359 /**
360 * Asserts that cond is true; issues an alert and throws an Error otherwise.
361 * @param {bool} cond The condition.
362 * @param {String} message The error message issued if cond is false.
363 */
364 life.Application.prototype.assert = function(cond, message) {
365 if (!cond) {
366 message = "Assertion failed: " + message;
367 alert(message);
368 throw new Error(message);
369 }
370 }
371
372 /**
373 * The run() method starts and 'runs' the application. An <EMBED> tag is
374 * injected into the <DIV> element |opt_viewDivName| which causes the NaCl
375 * module to be loaded. Once loaded, the moduleDidLoad() method is called via
376 * a 'load' event handler that is attached to the <DIV> element.
377 * @param {?String} opt_viewDivName The id of a DOM element in which to
378 * embed the NaCl module. If unspecified, defaults to DomIds_.VIEW. The
379 * DOM element must exist.
380 */
381 life.Application.prototype.run = function(opt_viewDivName) {
382 var viewDivName = opt_viewDivName || life.Application.DomIds_.VIEW;
383 var viewDiv = document.getElementById(viewDivName);
384 this.assert(viewDiv, "Missing DOM element '" + viewDivName + "'");
385
386 // A small handler for the 'load' event. It stops propagation of the 'load'
387 // event and then calls moduleDidLoad(). The handler is bound to |this| so
388 // that the calling context of the closure makes |this| point to this
389 // instance of the life.Applicaiton object.
390 var loadEventHandler = function(loadEvent) {
391 this.moduleDidLoad(life.Application.DomIds_.MODULE);
392 }
393
394 // Note that the <EMBED> element is wrapped inside a <DIV>, which has a 'load'
395 // event listener attached. This method is used instead of attaching the
396 // 'load' event listener directly to the <EMBED> element to ensure that the
397 // listener is active before the NaCl module 'load' event fires.
398 viewDiv.addEventListener('load', goog.bind(loadEventHandler, this), true);
399
400 var viewSize = goog.style.getSize(viewDiv);
401 viewDiv.innerHTML = '<embed id="' + life.Application.DomIds_.MODULE + '" ' +
402 ' class="autosize"' +
403 ' width=' + viewSize.width +
404 ' height=' + viewSize.height +
405 ' src="life.nmf"' +
406 ' type="application/x-nacl" />'
407 }
408
409 /**
410 * Shut down the application instance. This unhooks all the event listeners
411 * and deletes the objects created in moduleDidLoad().
412 */
413 life.Application.prototype.terminate = function() {
414 goog.events.removeAll();
415 this.viewController_ = null;
416 }
417
418 /**
419 * Extend the String class to trim whitespace.
420 * @return {string} the original string with leading and trailing whitespace
421 * removed.
422 */
423 String.prototype.trim = function () {
424 return this.replace(/^\s*/, '').replace(/\s*$/, '');
425 }
OLDNEW
« no previous file with comments | « experimental/conways_life/life.cc ('k') | experimental/conways_life/life_application.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698