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

Side by Side Diff: experimental/flocking_geese/js/speedometer.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
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 speedometer object. This draws an array of speedometers as adjacent
8 * vertical bars. The height of the bars is a percentage of the maximum
9 * possible speed.
10 */
11
12
13 goog.provide('Speedometer');
14 goog.provide('Speedometer.Attributes');
15
16 goog.require('goog.Disposable');
17 goog.require('goog.events');
18 goog.require('goog.events.EventTarget');
19
20 /**
21 * Manages a map of individual speedometers. Use addMeterWithName()
22 * to add speedometer bars.
23 * @param {Canvas} opt_canvas The <CANVAS> element to use for drawing.
24 * @constructor
25 * @extends {goog.events.EventTarget}
26 */
27 Speedometer = function(opt_canvas) {
28 goog.events.EventTarget.call(this);
29
30 /**
31 * Speedometer has two canvases, one is the "offscreen" canvas used to
32 * assemble the final speedometer image and the other is the "onscreen"
33 * canvas in the DOM which renders the final image.
34 * @type {Canvas}
35 * @private
36 */
37 this.domCanvas_ = opt_canvas || null;
38 this.offscreenCanvas_ = null;
39
40 /**
41 * The dictionary of individual meters.
42 * @type {Array.Object}
43 * @private
44 */
45 this.meters_ = [];
46
47 /**
48 * The number of unique meters added to the speedometer.
49 * @type {number}
50 * @private
51 */
52 this.meterCount_ = 0;
53
54 /**
55 * The maximum speed that any meter can reach. Meters that go past this
56 * value are clipped.
57 * @type {real}
58 * @private
59 */
60 this.maxSpeed_ = 1.0;
61
62 /**
63 * Display timers. There is a timer for updating the needles and one for
64 * updating the odometers, these run on different frequencies. These
65 * timers are started with the first call to render().
66 * @type {Timer}
67 * @private
68 */
69 this.needleUpdateTimer_ = null;
70 this.odometerUpdateTimer_ = null;
71
72 /**
73 * The images.
74 * @type {Object.Image}
75 * @private
76 */
77 this.images_ = {}
78 };
79 goog.inherits(Speedometer, goog.events.EventTarget);
80
81 /**
82 * Meter dictionary values.
83 * @enum {string}
84 */
85 Speedometer.Attributes = {
86 DISPLAY_VALUE: 'displayValue',
87 THEME: 'theme', // The needle theme. Has value Speedometer.Themes.
88 NAME: 'name', // The name of the meter. Not optional.
89 ODOMETER_DISPLAY_VALUE: 'odometerDisplayValue',
90 ODOMETER_LEFT: 'odometerLeft', // The left coordinate of the odometer.
91 ODOMETER_TOP: 'odometerTop',
92 VALUE: 'value', // The value of the meter. Not optional.
93 };
94
95 /**
96 * Drawing themes.
97 * @enum {string}
98 */
99 Speedometer.Themes = {
100 DEFAULT: 'greenTheme',
101 GREEN: 'greenTheme',
102 RED: 'redTheme'
103 };
104
105 /**
106 * Odometer dimensions.
107 * @enum {number}
108 * @private
109 */
110 Speedometer.prototype.OdometerDims_ = {
111 COLUMN_HEIGHT: 16,
112 COLUMN_WIDTH: 12,
113 DIGIT_HEIGHT: 14,
114 DIGIT_WIDTH: 8
115 };
116
117 /**
118 * The needle update interval time. Measured in milliseconds.
119 * @type {number}
120 * @private
121 */
122 Speedometer.prototype.NEEDLE_UPDATE_INTERVAL_ = 83.0; // About 12 frames/sec.
123
124 /**
125 * Beginning and ending angles for the meter. |METER_ANGLE_START_| represents
126 * "speed" of 0, |METER_ANGLE_RANGE_| is the angular range of the meter needle.
127 * maximumSpeed() is |METER_ANGLE_START_ + METER_ANGLE_RANGE_|. Measured
128 * cockwise in radians from the line y = 0.
129 * @type {number}
130 * @private
131 */
132 Speedometer.prototype.METER_ANGLE_START_ = 3.0 * Math.PI / 4.0;
133 Speedometer.prototype.METER_ANGLE_RANGE_ = 6.0 * Math.PI / 4.0;
134
135 /**
136 * Value used to provde damping to the meter. The meter reading can change by
137 * at most this amount per frame. Measured in radians.
138 * @type {number}
139 * @private
140 */
141 Speedometer.prototype.DAMPING_FACTOR_ = Math.PI / 36.0;
142
143 /**
144 * Maximum odometer value. Speeds are clapmed to this.
145 * @type {number}
146 * @private
147 */
148 Speedometer.prototype.MAX_ODOMETER_VALUE_ = 999999.99;
149
150 /**
151 * The odometer update interval time. Measured in milliseconds.
152 * @type {number}
153 * @private
154 */
155 Speedometer.prototype.ODOMETER_UPDATE_INTERVAL_ = 2000.0;
156
157 /**
158 * The amount of time to roll a digit. Measured in number of needle update
159 * intervals. So, if NEEDLE_UPDATE_INTERVAL_ is 12 fps (83 msec), then a roll
160 * time of 6 means each digit takes about 1/2 sec to roll.
161 * @type {number}
162 * @private
163 */
164 Speedometer.prototype.ODOMETER_ROLL_TIME_ = 6;
165
166 /**
167 * The number of odometer digits.
168 * @type {number}
169 * @private
170 */
171 Speedometer.prototype.ODOMETER_DIGIT_COUNT_ = 7;
172
173 /**
174 * Override of disposeInternal() to dispose of retained objects and unhook all
175 * events.
176 * @override
177 */
178 Speedometer.prototype.disposeInternal = function() {
179 function stopTimer(timer) {
180 if (timer) {
181 clearTimeout(timer);
182 }
183 }
184
185 stopTimer(this.needleUpdateTimer_);
186 this.needleUpdateTimer_ = null;
187 stopTimer(this.odometerUpdateTimer_);
188 this.odometerUpdateTimer_ = null;
189 for (image in this.images_) {
190 delete this.images_[image];
191 this.images_[image] = null;
192 }
193 this.domCanvas_ = null;
194 delete this.offscreenCanvas_;
195 this.offscreenCanvas_ = null;
196 Speedometer.superClass_.disposeInternal.call(this);
197 }
198
199 /**
200 * Set the canvas for drawing.
201 * @param !{Canvas} canvas The <CANVAS> element for drawing.
202 */
203 Speedometer.prototype.setCanvas = function(canvas) {
204 this.domCanvas_ = canvas;
205 if (this.offscreenCanvas_) {
206 delete this.offscreenCanvas_;
207 this.offscreenCanvas_ = null;
208 }
209 if (!canvas) {
210 return;
211 }
212 this.offscreenCanvas_ = document.createElement('canvas');
213 this.offscreenCanvas_.width = canvas.width;
214 this.offscreenCanvas_.height = canvas.height;
215 }
216
217 /**
218 * Return the current maximum speed.
219 * @return {real} The current maximum speed. Guaranteed to be >= 0.0
220 */
221 Speedometer.prototype.maximumSpeed = function() {
222 return this.maxSpeed_;
223 }
224
225 /**
226 * Set the maximum speed value for all meters.
227 * @param {real} maxSpeed The new maximum speed. Must be > 0.
228 * @return {real} The previous maximum speed.
229 */
230 Speedometer.prototype.setMaximumSpeed = function(maxSpeed) {
231 if (maxSpeed <= 0.0) {
232 throw Error('maxSpeed must be >= 0.0');
233 }
234 var oldMaxSpeed = this.maxSpeed_;
235 this.maxSpeed_ = maxSpeed;
236 return oldMaxSpeed;
237 }
238
239 /**
240 * Add a named meter. If a meter with |meterName| already exists, then do
241 * nothing.
242 * @param {!string} meterName The name for the new meter.
243 * @param {?Object} opt_attributes A dictionary containing optional attributes
244 * for the meter. Some key names are special, for example the 'valueLabel'
245 * key contains the id of a value label DOM element for the meter - this
246 * label will be updated with a text representation of the meter's value.
247 */
248 Speedometer.prototype.addMeterWithName = function(meterName, opt_attributes) {
249 if (!(meterName in this.meters_)) {
250 this.meterCount_++;
251 }
252 var attributes = opt_attributes || {};
253 // Fill in the non-optional attributes.
254 var meter = {
255 name: meterName,
256 value: 0.0,
257 displayValue: 0.0, // Updated every NEEDLE_UPDATE_INTERVAL_.
258 maxValue: 0.0,
259 theme: Speedometer.Themes.DEFAULT,
260 odometerLeft: 0,
261 odometerTop: 0,
262 previousAngle: 0.0,
263 odometerDisplayValue: 0.0 // Updated every ODOMETER_UPDATE_INTERVAL_.
264 };
265 for (attrib in attributes) {
266 // Overwrite meter defaults with passed-in values if present. Otherwise,
267 // add the passed-in element.
268 meter[attrib] = attributes[attrib];
269 }
270 // Each digit has its current display offset and a delta value used to
271 // compute its next display offset when rolling the digit.
272 meter.odometerDigits_ = new Array(this.ODOMETER_DIGIT_COUNT_);
273 for (var i = 0; i < this.ODOMETER_DIGIT_COUNT_; i++) {
274 meter.odometerDigits_[i] = this.createOdometerDigitDict_();
275 }
276
277 this.meters_[meterName] = meter;
278 }
279
280 /**
281 * Update the value of a named meter. If the meter does not exist, then do
282 * nothing. If the value is successfully changed, then post a VALUE_CHANGED
283 * event.
284 * @param {!string} meterName The name of the meter.
285 * @param {!real} value The new value for the meter. Must be >= 0
286 * @return {real} The previous value of the meter.
287 */
288 Speedometer.prototype.updateMeterNamed =
289 function(meterName, value) {
290 var oldValue = 0.0;
291 if (meterName in this.meters_) {
292 var meter = this.meters_[meterName];
293 oldValue = meter.value;
294 // Update the actual values. Display values are updated by the update
295 // interval handlers.
296 meter.value = value;
297 meter.maxValue = Math.max(meter.maxValue, value);
298 }
299 return oldValue;
300 }
301
302 /**
303 * The value for a meter. If the meter doesn't exist, or if the given key is
304 * not valid, returns -1.
305 * @param {!string} meterName The name of the meter.
306 * @param {?string} opt_key The key name of the value. Defaults to 'value'.
307 * @return {number} The current meter's value or -1 if the meter doesn't exist.
308 */
309 Speedometer.prototype.valueForMeterNamed = function(meterName, opt_key) {
310 if (meterName in this.meters_) {
311 var key = opt_key || Speedometer.Attributes.VALUE;
312 var meter = this.meters_[meterName];
313 if (key in meter) {
314 return meter[key];
315 }
316 }
317 return -1;
318 }
319
320 /**
321 * Update the needles to their new values, and restart the needle update timer.
322 */
323 Speedometer.prototype.updateNeedles = function() {
324 for (meterName in this.meters_) {
325 var meter = this.meters_[meterName];
326 meter.displayValue = meter.value;
327 }
328 this.render();
329 this.needleUpdateTimer_ = setTimeout(goog.bind(this.updateNeedles, this),
330 this.NEEDLE_UPDATE_INTERVAL_);
331 }
332
333 /**
334 * Update the odometers to their new values, and restart the odometer update
335 * timer. Recomputes all the animation parameters for rolling the digits to
336 * their new values.
337 */
338 Speedometer.prototype.updateOdometers = function() {
339 for (meterName in this.meters_) {
340 var meter = this.meters_[meterName];
341 meter.odometerDisplayValue = meter.value;
342 var odometerValue = Math.min(meter.odometerDisplayValue.toFixed(2),
343 this.MAX_ODOMETER_VALUE_);
344 for (var digit = this.ODOMETER_DIGIT_COUNT_ - 1; digit >= 0; digit--) {
345 var digitDict = this.createOdometerDigitDict_();
346 var digitValue = (odometerValue - Math.floor(odometerValue)) * 10;
347 var prevOffset = meter.odometerDigits_[digit].previousOffset;
348 digitDict.offset = Math.floor(digitValue) *
349 this.OdometerDims_.DIGIT_HEIGHT;
350 digitDict.rollDelta = (digitDict.offset - prevOffset) /
351 this.ODOMETER_ROLL_TIME_;
352 digitDict.previousOffset = prevOffset;
353 meter.odometerDigits_[digit] = digitDict;
354 odometerValue /= 10;
355 }
356 }
357 this.odometerUpdateTimer_ = setTimeout(goog.bind(this.updateOdometers, this),
358 this.ODOMETER_UPDATE_INTERVAL_);
359 }
360
361 /**
362 * Start the display timers if needed and perform any other display
363 * initialization.
364 */
365 Speedometer.prototype.reset = function() {
366 this.loadImages_();
367 for (meterName in this.meters_) {
368 var meter = this.meters_[meterName];
369 meter.displayValue = meter.value;
370 meter.odometerDisplayValue = meter.maxValue;
371 }
372 if (!this.needleUpdateTimer_) {
373 this.needleUpdateTimer_ = setTimeout(goog.bind(this.updateNeedles, this),
374 this.NEEDLE_UPDATE_INTERVAL_);
375 }
376 if (!this.odometerUpdateTimer_) {
377 this.odometerUpdateTimer_ = setTimeout(
378 goog.bind(this.updateOdometers, this), this.ODOMETER_UPDATE_INTERVAL_);
379 }
380 }
381
382 /**
383 * Render the speedometer.
384 */
385 Speedometer.prototype.render = function() {
386 if (!this.offscreenCanvas_) {
387 this.setCanvas(this.domCanvas_);
388 if (!this.offscreenCanvas_) {
389 return;
390 }
391 }
392 var context2d = this.offscreenCanvas_.getContext('2d');
393 context2d.save();
394 this.drawBackground_(context2d,
395 this.offscreenCanvas_.width,
396 this.offscreenCanvas_.height);
397 // Paint the meters.
398 for (meterName in this.meters_) {
399 var meter = this.meters_[meterName];
400 this.drawOdometer_(context2d, meter);
401 this.drawNeedle_(context2d, meter);
402 }
403 // Draw the hub over the needles.
404 var canvasCenterX = context2d.canvas.width / 2;
405 var canvasCenterY = context2d.canvas.height / 2;
406 var hubRadius = this.images_.hub.width / 2;
407 context2d.translate(canvasCenterX - hubRadius, canvasCenterY - hubRadius);
408 context2d.drawImage(this.images_.hub, 0, 0);
409 context2d.restore();
410 this.flushDrawing_();
411 }
412
413 /**
414 * Flush the contents of the offscreen canvas to the onscreen one.
415 * @private
416 */
417 Speedometer.prototype.flushDrawing_ = function() {
418 if (!this.domCanvas_ || !this.offscreenCanvas_) {
419 return;
420 }
421 var context2d = this.domCanvas_.getContext('2d');
422 context2d.drawImage(this.offscreenCanvas_, 0, 0);
423 }
424
425 /**
426 * Draw the background.
427 * @param {Graphics2d} context2d The 2D canvas context.
428 * @param {number} width The background width, measured in pixels.
429 * @param {number} height The background height, measured in pixels.
430 * @private
431 */
432 Speedometer.prototype.drawBackground_ = function(context2d, width, height) {
433 // Paint the background image.
434 if (this.images_.background.complete) {
435 context2d.drawImage(this.images_.background, 0, 0);
436 } else {
437 context2d.fillStyle = 'black';
438 context2d.fillRect(0, 0, width, height);
439 }
440 }
441
442 /**
443 * Draw a meter's needle.
444 * @param {Graphics2d} context2d The 2D canvas context.
445 * @param {Object} meter The meter.
446 * @private
447 */
448 Speedometer.prototype.drawNeedle_ = function(context2d, meter) {
449 var canvasCenterX = context2d.canvas.width / 2;
450 var canvasCenterY = context2d.canvas.height / 2;
451 var meterAngle = (meter.displayValue / this.maxSpeed_) *
452 this.METER_ANGLE_RANGE_;
453 meterAngle = Math.min(meterAngle, this.METER_ANGLE_RANGE_);
454 meterAngle = Math.max(meterAngle, 0.0);
455 // Dampen the meter's angular change.
456 var delta = meterAngle - meter.previousAngle;
457 if (Math.abs(delta) > this.DAMPING_FACTOR_) {
458 delta = delta < 0 ? -this.DAMPING_FACTOR_ : this.DAMPING_FACTOR_;
459 }
460 var dampedAngle = meter.previousAngle + delta;
461 meter.previousAngle = dampedAngle;
462 dampedAngle += this.METER_ANGLE_START_;
463 context2d.save();
464 context2d.translate(canvasCenterX, canvasCenterY);
465 context2d.rotate(dampedAngle);
466 // Select the needle image based on the meter's theme and value.
467 var needleImage = null;
468 if (meter.displayValue == 0) {
469 needleImage = this.images_.stoppedNeedle;
470 } else if (meter.theme == Speedometer.Themes.GREEN) {
471 needleImage = this.images_.greenNeedle;
472 } else if (meter.theme == Speedometer.Themes.RED) {
473 needleImage = this.images_.redNeedle;
474 }
475 if (needleImage && needleImage.complete) {
476 context2d.drawImage(
477 needleImage,
478 0, 0, needleImage.width, needleImage.height,
479 -5, -5, needleImage.width, needleImage.height);
480 }
481 context2d.restore();
482 }
483
484 /**
485 * Create an odometer digit dictionary.
486 * @return {Object} a newly-created digit dictionary.
487 * @private
488 */
489 Speedometer.prototype.createOdometerDigitDict_ = function() {
490 var digitDict = {
491 rollDelta: 0,
492 previousOffset: 0,
493 offset: 0
494 };
495 return digitDict;
496 }
497
498 /**
499 * Draw the odometer for a given meter.
500 * @param {Graphics2d} context2d The 2D canvas context.
501 * @param {Object} meter The meter.
502 * @private
503 */
504 Speedometer.prototype.drawOdometer_ = function(context2d, meter) {
505 if (!this.images_.odometerTenths.complete ||
506 !this.images_.odometerDigits.complete) {
507 return;
508 }
509 context2d.save();
510 context2d.translate(meter.odometerLeft, meter.odometerTop);
511 context2d.drawImage(this.images_.odometer, 0, 0);
512 // Center the odometer digits in the odometer cells.
513 context2d.translate(
514 (this.OdometerDims_.COLUMN_WIDTH - this.OdometerDims_.DIGIT_WIDTH) / 2,
515 (this.OdometerDims_.COLUMN_HEIGHT - this.OdometerDims_.DIGIT_HEIGHT) / 2);
516 // Draw the tenths digit.
517 this.drawOdometerDigit_(context2d, meter, 6, this.images_.odometerTenths);
518 // Draw the integer part, up to 5 digits (max value is 999,999.9).
519 for (var column = 5; column >= 0; column--) {
520 this.drawOdometerDigit_(context2d, meter, column,
521 this.images_.odometerDigits);
522 }
523 context2d.restore();
524 }
525
526 /**
527 * Draw a single odometer digit. The digit source is a vertical strip of
528 * pre-rendered numbers. When drawing the last digit in the strip, the image
529 * source is wrapped back to the beginning of the strip. In this way, drawing
530 * a partial '9' will also draw part of the '0'.
531 * @param {Graphics2d} context2d The drawing context.
532 * @param {Object} meter The meter related to this odometer.
533 * @param {number} column The odometer column index, 0-based numbered from the
534 * left-most (most significant) digit. For example, column 0 is the
535 * 100,000's place, column 6 is the 1/10's place.
536 * @param {Image} digits The digit source image.
537 * @private
538 */
539 Speedometer.prototype.drawOdometerDigit_ = function(
540 context2d, meter, column, digits) {
541 var digitDict = meter.odometerDigits_[column];
542 var offset = digitDict.previousOffset;
543 var digitHeight = Math.min(digits.height - offset,
544 this.OdometerDims_.DIGIT_HEIGHT);
545 var digitWrapHeight = this.OdometerDims_.DIGIT_HEIGHT - digitHeight;
546 context2d.drawImage(digits,
547 0, offset,
548 this.OdometerDims_.DIGIT_WIDTH, digitHeight,
549 column * this.OdometerDims_.COLUMN_WIDTH, 0,
550 this.OdometerDims_.DIGIT_WIDTH, digitHeight);
551 if (digitWrapHeight > 0) {
552 context2d.drawImage(digits,
553 0, 0,
554 this.OdometerDims_.DIGIT_WIDTH, digitWrapHeight,
555 column * this.OdometerDims_.COLUMN_WIDTH,
556 this.OdometerDims_.DIGIT_HEIGHT - digitWrapHeight,
557 this.OdometerDims_.DIGIT_WIDTH, digitWrapHeight);
558 }
559 if (digitDict.rollDelta == 0) {
560 return;
561 }
562 if ((digitDict.rollDelta > 0 && offset >= digitDict.offset) ||
563 (digitDict.rollDelta < 0 && offset <= digitDict.offset)) {
564 digitDict.previousOffset = digitDict.offset;
565 digitDict.rollDelta = 0;
566 return;
567 }
568 digitDict.previousOffset += digitDict.rollDelta;
569 if (digitDict.previousOffset < 0) {
570 // Wrap the offset around to the end of the digits image.
571 digitDict.previousOffset += digits.height;
572 }
573 if (digitDict.previousOffset > digits.height) {
574 // Wrap the offset around to the top of the digits image.
575 digitDict.previousOffset = digitDict.previousOffset - digits.height;
576 }
577 }
578
579 /**
580 * Load all the images.
581 * @private
582 */
583 Speedometer.prototype.loadImages_ = function() {
584 this.images_.background = new Image();
585 this.images_.background.src = 'images/DialFace.png';
586 this.images_.hub = new Image();
587 this.images_.hub.src = 'images/hub.png';
588 this.images_.odometer = new Image();
589 this.images_.odometer.src = 'images/odometer.png';
590 this.images_.odometerDigits = new Image();
591 this.images_.odometerDigits.src = 'images/numbers_grey.png';
592 this.images_.odometerTenths = new Image();
593 this.images_.odometerTenths.src = 'images/numbers_red.png';
594 this.images_.stoppedNeedle = new Image();
595 this.images_.stoppedNeedle.src = 'images/pointer_grey.png'
596 this.images_.greenNeedle = new Image();
597 this.images_.greenNeedle.src = 'images/pointer_green.png'
598 this.images_.redNeedle = new Image();
599 this.images_.redNeedle.src = 'images/pointer_red.png'
600 }
601
OLDNEW
« no previous file with comments | « experimental/flocking_geese/js/slider_event.js ('k') | experimental/flocking_geese/manifest.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698