1 /*
  2 SpiderGL Computer Graphics Library
  3 Copyright (c) 2010, Marco Di Benedetto - Visual Computing Lab, ISTI - CNR
  4 All rights reserved.
  5 
  6 Redistribution and use in source and binary forms, with or without
  7 modification, are permitted provided that the following conditions are met:
  8     * Redistributions of source code must retain the above copyright
  9       notice, this list of conditions and the following disclaimer.
 10     * Redistributions in binary form must reproduce the above copyright
 11       notice, this list of conditions and the following disclaimer in the
 12       documentation and/or other materials provided with the distribution.
 13     * Neither the name of SpiderGL nor the
 14       names of its contributors may be used to endorse or promote products
 15       derived from 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 PAUL BRUNT BE LIABLE FOR ANY
 21 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
 24 ON 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 /**
 30  * @fileOverview UI
 31  */
 32 
 33 /**
 34  * The SpiderGL.UserInterface namespace.
 35  *
 36  * @namespace The SpiderGL.UserInterface namespace.
 37  */
 38 SpiderGL.UserInterface = { };
 39 
 40 /**
 41  * Creates a SpiderGL.UserInterface.CanvasHandler.
 42  *
 43  * SpiderGL.UserInterface.CanvasHandler is an event handler used to implement an easy controller for WebGL canvas.
 44  * It should not be directly used. Use {@link SpiderGL.UserInterface.handleCanvas} or {@link SpiderGL.UserInterface.handleCanvasOnLoad} to install it to a listener object.
 45  *
 46  * @class The SpiderGL.UserInterface.CanvasHandler is an event handler used to implement an easy controller for WebGL canvas. It should not be directly used.
 47  *
 48  * @augments SpiderGL.Core.ObjectBase
 49  *
 50  * @param {WebGLRenderingcontext} gl The WebGLRenderingcontext to pass to the handler.
 51  * @param {object} handler The event handler to which events will be dispatched.
 52  * @param {object} [options] Optional parameters
 53  * @param {bool} [options.standardGLUnpack=SpiderGL.UserInterface.CanvasHandler.DEFAULT_STANDARD_GL_UNPACK] If true, sets the default OpenGL unpack behaviour.
 54  * @param {number} [options.animateRate=SpiderGL.UserInterface.CanvasHandler.DEFAULT_ANIMATE_RATE] Additional options.
 55  *
 56  * @see SpiderGL.UserInterface.handleCanvas
 57  * @see SpiderGL.UserInterface.handleCanvasOnLoad
 58  */
 59 SpiderGL.UserInterface.CanvasHandler = function (gl, handler, options) {
 60 	SpiderGL.Core.ObjectBase.call(this);
 61 
 62 	options = options || { };
 63 
 64 	var that = this;
 65 	var canvas = gl.canvas;
 66 
 67 	this._gl        = gl;
 68 	this._canvas    = canvas;
 69 	this._handler   = handler;
 70 
 71 	this._ignoreKeyRepeat = SpiderGL.Utility.getDefaultValue(options.ignoreKeyRepeat, SpiderGL.UserInterface.CanvasHandler.DEFAULT_IGNORE_KEY_REPEAT);
 72 	this._keysDown = { };
 73 
 74 	this._mouseButtonsDown = [false, false, false];
 75 
 76 	this._dragging     = [false, false, false];
 77 	this._dragStartPos = [[0, 0], [0, 0], [0, 0]];
 78 	this._dragEndPos   = [[0, 0], [0, 0], [0, 0]];
 79 	this._dragDeltaPos = [[0, 0], [0, 0], [0, 0]];
 80 
 81 	this._cursorPos      = [0, 0];
 82 	this._cursorPrevPos  = [0, 0];
 83 	this._cursorDeltaPos = [0, 0];
 84 
 85 	this._drawEventPending      = false;
 86 	this._drawEventHandler      = function () { that._onDraw(); };
 87 	this._postDrawEventFunction = function () { that._postDrawEvent(); };
 88 
 89 	this._animateTime         = Date.now();
 90 	this._animatePrevTime     = this._animateTime;
 91 	this._animateDeltaTime    = 0;
 92 	this._animateRate         = 0;
 93 	this._animateID           = null;
 94 	this._animateEventHandler = function () { that._onAnimate(); };
 95 	this._animateMS           = -1;
 96 	this._animateWithTimeout  = false;
 97 	this._fastAnimate         = false;
 98 
 99 	this._fpsUpdateMS = 1000;
100 	this._fpsTime     = Date.now();
101 	this._fpsCount    = 0;
102 	this._fps         = 0;
103 
104 	/** @private */
105 	var handleMessage = function (evt) {
106 		if (evt.source != window) return;
107 		if (evt.data == SpiderGL.UserInterface.CanvasHandler._FAST_ANIMATE_MESSAGE_NAME) {
108 			evt.stopPropagation();
109 			that._onAnimate();
110 		}
111 		else if (evt.data == SpiderGL.UserInterface.CanvasHandler._FAST_DRAW_MESSAGE_NAME) {
112 			evt.stopPropagation();
113 			that._onDraw();
114 		}
115 	};
116 	window.addEventListener("message", handleMessage, true);
117  
118 	canvas.tabIndex = 0;
119 
120 	canvas.addEventListener("unload",          function (e) { that._onTerminate       (e); }, false);
121 	canvas.addEventListener("keydown",         function (e) { that._onKeyDown         (e); }, false);
122 	canvas.addEventListener("keyup",           function (e) { that._onKeyUp           (e); }, false);
123 	canvas.addEventListener("keypress",        function (e) { that._onKeyPress        (e); }, false);
124 	canvas.addEventListener("mousedown",       function (e) { that._onMouseButtonDown (e); }, false);
125 	canvas.addEventListener("mouseup",         function (e) { that._onMouseButtonUp   (e); }, false);
126 	canvas.addEventListener("mousemove",       function (e) { that._onMouseMove       (e); }, false);
127 	canvas.addEventListener("mouseout",        function (e) { that._onMouseOut        (e); }, false);
128 	canvas.addEventListener("click",           function (e) { that._onClick           (e); }, false);
129 	canvas.addEventListener("dblclick",        function (e) { that._onDoubleClick     (e); }, false);
130 	canvas.addEventListener("resize",          function (e) { that._onResize          (e); }, false);
131 	canvas.addEventListener("DOMMouseScroll",  function (e) { that._onMouseWheel      (e); }, false);
132 	canvas.addEventListener("mousewheel",      function (e) { that._onMouseWheel      (e); }, false);
133 	canvas.addEventListener("blur",            function (e) { that._onBlur            (e); }, false);
134 
135 	window.addEventListener("mouseup",         function (e) { that._onWindowMouseButtonUp (e); }, false);
136 	window.addEventListener("mousemove",       function (e) { that._onWindowMouseMove     (e); }, false);
137 
138 	canvas.addEventListener("touchstart",      SpiderGL.UserInterface.CanvasHandler._touchHandler, true);
139 	canvas.addEventListener("touchend",        SpiderGL.UserInterface.CanvasHandler._touchHandler, true);
140 	canvas.addEventListener("touchmove",       SpiderGL.UserInterface.CanvasHandler._touchHandler, true);
141 	canvas.addEventListener("touchcancel",     SpiderGL.UserInterface.CanvasHandler._touchHandler, true);
142 
143 	var standardGLUnpack = SpiderGL.Utility.getDefaultValue(options.standardGLUnpack, SpiderGL.UserInterface.CanvasHandler.DEFAULT_STANDARD_GL_UNPACK);
144 	if (standardGLUnpack) {
145 		SpiderGL.WebGL.Context.setStandardGLUnpack(gl);
146 	}
147 
148 	this.animateRate = SpiderGL.Utility.getDefaultValue(options.animateRate, SpiderGL.UserInterface.CanvasHandler.DEFAULT_ANIMATE_RATE);
149 }
150 
151 SpiderGL.UserInterface.CanvasHandler._FAST_DRAW_MESSAGE_NAME    = "spidergl-fast-draw-message";
152 SpiderGL.UserInterface.CanvasHandler._FAST_ANIMATE_MESSAGE_NAME = "spidergl-fast-animate-message";
153 
154 /**
155  * Default value for animate rate.
156  *
157  * @type number
158  *
159  * @default 0
160  */
161 SpiderGL.UserInterface.CanvasHandler.DEFAULT_ANIMATE_RATE = 0;
162 
163 /**
164  * Default value for ignoring key repeats.
165  *
166  * @type bool
167  *
168  * @default true
169  */
170 SpiderGL.UserInterface.CanvasHandler.DEFAULT_IGNORE_KEY_REPEAT = true;
171 
172 /**
173  * Default value for applying standard OpenGL pixel unpack parameters.
174  *
175  * @type bool
176  *
177  * @default true
178  */
179 SpiderGL.UserInterface.CanvasHandler.DEFAULT_STANDARD_GL_UNPACK = true;
180 
181 /**
182  * Default name of the property to install in the handler object for accessing the canvas handler.
183  *
184  * @type string
185  *
186  * @default "ui"
187  */
188 SpiderGL.UserInterface.CanvasHandler.DEFAULT_PROPERTY_NAME = "ui";
189 
190 SpiderGL.UserInterface.CanvasHandler._touchHandler = function (event) {
191 	var touches = event.changedTouches,
192 
193 	first = touches[0],
194 	type = "";
195 
196 	switch(event.type)
197 	{
198 		case "touchstart": type = "mousedown"; break;
199 		case "touchmove":  type = "mousemove"; break;        
200 		case "touchend":   type = "mouseup";   break;
201 		default: return;
202 	}
203 
204 	//initMouseEvent(type, canBubble, cancelable, view, clickCount, 
205 	//           screenX, screenY, clientX, clientY, ctrlKey, 
206 	//           altKey, shiftKey, metaKey, button, relatedTarget);
207 
208 	var simulatedEvent = document.createEvent("MouseEvent");
209 	simulatedEvent.initMouseEvent(
210 		type, true, true, window, 1, 
211 		first.screenX, first.screenY, 
212 		first.clientX, first.clientY, false, 
213 		false, false, false, 0/*left*/, null
214 	);
215 
216 	first.target.dispatchEvent(simulatedEvent);
217 	event.preventDefault();
218 };
219 
220 SpiderGL.UserInterface.CanvasHandler.prototype = {
221 	_firstNotify : function () {
222 		this._onInitialize();
223 		if (this._animateRate > 0) {
224 			this._onAnimate();
225 		}
226 		this.postDrawEvent();
227 	},
228 
229 	_dispatch : function () {
230 		var evt     = arguments[0];
231 		var handler = this._handler;
232 		var method  = handler[evt];
233 		if (!method) return;
234 		var args = Array.prototype.slice.call(arguments, 1);
235 		var r = method.apply(handler, args);
236 		//if (r === false) return;
237 		//this._postDrawEvent();
238 	},
239 
240 	_postDrawEvent : function () {
241 		if (this._drawEventPending) return;
242 		this._drawEventPending = true;
243 		//setTimeout(this._drawEventHandler, 0);
244 		window.postMessage(SpiderGL.UserInterface.CanvasHandler._FAST_DRAW_MESSAGE_NAME, "*");
245 	},
246 
247 	_getMouseClientPos : function(e) {
248 		var r = this._canvas.getBoundingClientRect();
249 		var w = this._canvas.width;
250 		var h = this._canvas.height;
251 		var x = e.clientX - r.left;
252 		var y = h - 1 - (e.clientY - r.top);
253 		var outside = ((x < 0) || (x >= w) || (y < 0) || (y >= h));
254 		return [x, y, outside];
255 	},
256 
257 	/*
258 	_getTouchClientPos : function(e) {
259 		return this._getMouseClientPos(e);
260 	},
261 	*/
262 
263 	_onInitialize : function () {
264 		this._dispatch("onInitialize");
265 	},
266 
267 	_onTerminate : function () {
268 		this._dispatch("onTerminate");
269 	},
270 
271 	_onBlur : function (e) {
272 		var gl = this._gl;
273 		var ks = this._keysDown;
274 		for (var c in ks) {
275 			if (ks[c]) {
276 				ks[c] = false;
277 				this._dispatch("onKeyUp", c, null);
278 			}
279 		}
280 	},
281 
282 	_onKeyDown : function (e) {
283 		var c = e.keyCode || e.charCode;
284 		var s = String.fromCharCode(c);
285 		if (s.length > 0) {
286 			c = s.toUpperCase();
287 		}
288 		var wasDown = this._keysDown[c];
289 		this._keysDown[c] = true;
290 		if (!wasDown || !this._ignoreKeyRepeat) {
291 			this._dispatch("onKeyDown", c, e);
292 		}
293 	},
294 
295 	_onKeyUp : function (e) {
296 		var c = e.keyCode || e.charCode;
297 		var s = String.fromCharCode(c);
298 		if (s.length > 0) {
299 			c = s.toUpperCase();
300 		}
301 		this._keysDown[c] = false;
302 		this._dispatch("onKeyUp", c, e);
303 	},
304 
305 	_onKeyPress : function (e) {
306 		var c = e.keyCode || e.charCode;
307 		var s = String.fromCharCode(c);
308 		if (s.length > 0) {
309 			c = s;
310 		}
311 		this._dispatch("onKeyPress", c, e);
312 	},
313 
314 	/*
315 	_onTouchStart : function (e) {
316 		e = e.changedTouches[0];
317 		this._canvas.focus();
318 		var xy  = this._getTouchClientPos(e);
319 		this._dispatch("onTouchStart", xy[0], xy[1], e);
320 		e.stopPropagation();
321 	},
322 
323 	_onTouchEnd : function (e) {
324 		e = e.changedTouches[0];
325 		this._canvas.focus();
326 		var xy  = this._getTouchClientPos(e);
327 		this._dispatch("onTouchEnd", xy[0], xy[1], e);
328 		e.stopPropagation();
329 	},
330 
331 	_onTouchMove : function (e) {
332 		e = e.changedTouches[0];
333 		this._canvas.focus();
334 		var xy  = this._getTouchClientPos(e);
335 		this._dispatch("onTouchMove", xy[0], xy[1], e);
336 		e.stopPropagation();
337 	},
338 	*/
339 
340 	_onMouseButtonDown : function (e) {
341 		this._canvas.focus();
342 
343 		var xy = this._getMouseClientPos(e);
344 		this._cursorPos = xy;
345 
346 		var btn = e.button;
347 		this._mouseButtonsDown[btn] = true;
348 		this._dragStartPos[btn] = [xy[0], xy[1]];
349 		this._dispatch("onMouseButtonDown", btn, xy[0], xy[1], e);
350 
351 		e.preventDefault();
352 		e.stopPropagation();
353 	},
354 
355 	_onMouseButtonUp : function (e) {
356 		var xy = this._getMouseClientPos(e);
357 		this._cursorPos = xy;
358 
359 		var btn = e.button;
360 		this._mouseButtonsDown[btn] = false;
361 		this._dispatch("onMouseButtonUp", btn, xy[0], xy[1], e);
362 
363 		if (this._dragging[btn]) {
364 			this._dragging[btn] = false;
365 			var sPos = this._dragStartPos[btn];
366 			var ePos = [xy[0], xy[1]];
367 			this._dragEndPos[btn] = ePos;
368 			this._dragDeltaPos[btn] = [ePos[0] - sPos[0], ePos[1] - sPos[1]];
369 			this._dispatch("onDragEnd", btn, ePos[0], ePos[1]);
370 		}
371 
372 		e.stopPropagation();
373 	},
374 
375 	_onWindowMouseButtonUp : function (e) {
376 		var xy = this._getMouseClientPos(e);
377 		this._cursorPos = xy;
378 
379 		var btn = e.button;
380 		if (!xy[2] || !this._mouseButtonsDown[btn]) return;
381 
382 		this._mouseButtonsDown[btn] = false;
383 		this._dispatch("onMouseButtonUp", btn, xy[0], xy[1], e);
384 
385 		if (this._dragging[btn]) {
386 			this._dragging[btn] = false;
387 			var sPos = this._dragStartPos[btn];
388 			var ePos = [xy[0], xy[1]];
389 			this._dragEndPos[btn] = ePos;
390 			this._dragDeltaPos[btn] = [ePos[0] - sPos[0], ePos[1] - sPos[1]];
391 			this._dispatch("onDragStart", btn, ePos[0], ePos[1]);
392 		}
393 
394 		e.stopPropagation();
395 	},
396 
397 	_onMouseMove : function (e) {
398 		this._cursorPrevPos = this._cursorPos;
399 
400 		var xy = this._getMouseClientPos(e);
401 		this._cursorPos      = xy;
402 
403 		this._cursorDeltaPos = [this._cursorPos[0] - this._cursorPrevPos[0], this._cursorPos[1] - this._cursorPrevPos[1]];
404 		this._dispatch("onMouseMove", xy[0], xy[1], e);
405 
406 		for (var i=0; i<3; ++i) {
407 			if (!this._mouseButtonsDown[i]) continue;
408 			var sPos = this._dragStartPos[i];
409 			var ePos = [xy[0], xy[1]];
410 			this._dragEndPos[i] = ePos;
411 			this._dragDeltaPos[i] = [ePos[0] - sPos[0], ePos[1] - sPos[1]];
412 			if (!this._dragging[i]) {
413 				this._dragging[i] = true;
414 				this._dispatch("onDragStart", i, sPos[0], sPos[1]);
415 			}
416 			this._dispatch("onDrag", i, ePos[0], ePos[1]);
417 		}
418 
419 		e.stopPropagation();
420 	},
421 
422 	_onWindowMouseMove : function (e) {
423 		var gl = this._gl;
424 		var xy = this._getMouseClientPos(e);
425 
426 		if (!xy[2]) return;
427 
428 		for (var i=0; i<3; ++i) {
429 			if (!this._dragging[i]) continue;
430 			var sPos = this._dragStartPos[i];
431 			var ePos = [xy[0], xy[1]];
432 			this._dragEndPos[i] = ePos;
433 			this._dragDeltaPos[i] = [ePos[0] - sPos[0], ePos[1] - sPos[1]];
434 			this._dispatch("onDrag", i, ePos[0], ePos[1]);
435 		}
436 
437 		e.stopPropagation();
438 	},
439 
440 	_onMouseWheel : function (e) {
441 		var xy = this._getMouseClientPos(e);
442 		var delta = 0;
443 		if (!e) {
444 			e = window.event;
445 		}
446 		if (e.wheelDelta) {
447 			delta = e.wheelDelta / 120;
448 			if (window.opera) {
449 				delta = -delta;
450 			}
451 		}
452 		else if (e.detail) {
453 			delta = -e.detail / 3;
454 		}
455 		if (delta) {
456 			this._dispatch("onMouseWheel", delta, xy[0], xy[1], e);
457 		}
458 
459 		if (e.preventDefault) {
460 			e.preventDefault();
461 		}
462 
463 		e.stopPropagation();
464 	},
465 
466 	_onMouseOut: function(e) {
467 	},
468 
469 	_onClick : function (e) {
470 		var xy = this._getMouseClientPos(e);
471 		this._dispatch("onClick", e.button, xy[0], xy[1], e);
472 	},
473 
474 	_onDoubleClick : function (e) {
475 		var xy = this._getMouseClientPos(e);
476 		this._dispatch("onDoubleClick", e.button, xy[0], xy[1], e);
477 	},
478 
479 	_onResize : function (e) {
480 		this._dispatch("onResize", this._canvas.width, this._canvas.height, e);
481 	},
482 
483 	_onAnimate : function () {
484 		this._animatePrevTime  = this._animateTime;
485 		this._animateTime      = Date.now();
486 		this._animateDeltaTime = this._animateTime - this._animatePrevTime;
487 		this._dispatch("onAnimate", this._animateDeltaTime / 1000);
488 		if (this._animateMS >= 0) {
489 			if (this._animateWithTimeout) {
490 				setTimeout(this._animateEventHandler, this._animateMS);
491 			}
492 		}
493 		else if (this._fastAnimate) {
494 			window.postMessage(SpiderGL.UserInterface.CanvasHandler._FAST_ANIMATE_MESSAGE_NAME, "*");
495 		}
496 	},
497 
498 	_onDraw : function () {
499 		this._drawEventPending = false;
500 
501 		this._fpsCount++;
502 
503 		var now   = Date.now();
504 		var fpsDT = now - this._fpsTime;
505 		if (fpsDT >= this._fpsUpdateMS) {
506 			this._fps      = SpiderGL.Math.floor(this._fpsCount * 1000 / fpsDT);
507 			this._fpsTime  = now;
508 			this._fpsCount = 0;
509 		}
510 
511 		this._dispatch("onDraw");
512 	},
513 
514 	/**
515 	 * Gets the canvas hijacked WebGLRenderingContext.
516 	 *
517 	 * @type WebGLRenderingContext
518 	 *
519 	 * @readonly
520 	 */
521 	get gl() {
522 		return this._gl;
523 	},
524 
525 	/**
526 	 * Gets the associated canvas.
527 	 *
528 	 * @type HTMLCanvasElement
529 	 *
530 	 * @readonly
531 	 */
532 	get canvas() {
533 		return this._canvas;
534 	},
535 
536 	/**
537 	 * Gets the width of the associated canvas.
538 	 *
539 	 * @type number
540 	 *
541 	 * @readonly
542 	 */
543 	get width() {
544 		return this._canvas.width;
545 	},
546 
547 	/**
548 	 * Gets the height of the associated canvas.
549 	 *
550 	 * @type number
551 	 *
552 	 * @readonly
553 	 */
554 	get height() {
555 		return this._canvas.height;
556 	},
557 
558 	/**
559 	 * Gets a function that, once called, sends a draw event, which once handled will call the onDraw method of the registered handler.
560 	 *
561 	 * @type function
562 	 *
563 	 * @readonly
564 	 */
565 	get postDrawEvent() {
566 		return this._postDrawEventFunction;
567 	},
568 
569 	/**
570 	 * Gets the time, in seconds, of the current onAnimate event.
571 	 *
572 	 * @type number
573 	 *
574 	 * @readonly
575 	 */
576 	get animateTime() {
577 		return this._animateTime;
578 	},
579 
580 	/**
581 	 * Gets the time, in seconds, of the last onAnimate event.
582 	 *
583 	 * @type number
584 	 *
585 	 * @readonly
586 	 */
587 	get animatePrevTime() {
588 		return this._animatePrevTime;
589 	},
590 
591 	/**
592 	 * Gets the elapsed time, in milliseconds, between the last and the current onAnimate event.
593 	 *
594 	 * @type number
595 	 *
596 	 * @readonly
597 	 */
598 	get animateDeltaTime() {
599 		return this._animateDeltaTime;
600 	},
601 
602 	/**
603 	 * Gets/Sets the frequency (calls per second) used to emit the onAnimate event.
604 	 * If zero, the onAnimate event will be disabled. If greater than zero, specifies how many times emit the event per second.
605 	 * If less than zero, the event will be emitted as fast as possible.
606 	 *
607 	 * @type number
608 	 *
609 	 * @default SpiderGL.UserInterface.CanvasHandler.DEFAULT_ANIMATE_RATE
610 	 */
611 	get animateRate() {
612 		return this._animateRate;
613 	},
614 
615 	set animateRate(r) {
616 		r = SpiderGL.Utility.getDefaultValue(r, SpiderGL.UserInterface.CanvasHandler.DEFAULT_ANIMATE_RATE);
617 		if (this._animateRate === r) return;
618 
619 		this._fastAnimate = false;
620 		this._animateMS   = -1;
621 
622 		this._animateTime      = Date.now();
623 		this._animatePrevTime  = this._animateTime;
624 		this._animateDeltaTime = 0;
625 
626 		if (this._animateID) {
627 			clearInterval(this._animateID);
628 			this._animateID = null;
629 		}
630 
631 		this._animateRate = r;
632 
633 		if (r > 0) {
634 			this._animateMS = SpiderGL.Math.floor(1000 / r);
635 			if (this._animateWithTimeout) {
636 				setTimeout(this._animateEventHandler, this._animateMS);
637 			}
638 			else {
639 				this._animateID = setInterval(this._animateEventHandler, this._animateMS);
640 			}
641 		}
642 		else if (r < 0) {
643 			this._fastAnimate = true;
644 			window.postMessage(SpiderGL.UserInterface.CanvasHandler._FAST_ANIMATE_MESSAGE_NAME, "*");
645 		}
646 	},
647 
648 	/**
649 	 * Gets the number of draw events occurred in the last second.
650 	 *
651 	 * @type number
652 	 *
653 	 * @readonly
654 	 */
655 	get framesPerSecond() {
656 		return this._fps;
657 	},
658 
659 	/**
660 	 * Gets/Sets if the key repeat must be ignored.
661 	 *
662 	 * @type bool
663 	 * 
664 	 * @default SpiderGL.UserInterface.CanvasHandler.DEFAULT_IGNORE_KEY_REPEAT
665 	 */
666 	get ignoreKeyRepeat() {
667 		return this._ignoreKeyRepeat;
668 	},
669 
670 	set ignoreKeyRepeat(on) {
671 		this._ignoreKeyRepeat = SpiderGL.Utility.getDefaultValue(on, SpiderGL.UserInterface.CanvasHandler.DEFAULT_IGNORE_KEY_REPEAT);
672 	},
673 
674 	/**
675 	 * Tests if a key is pressed.
676 	 *
677 	 * @param {string|number} key The key to test.
678 	 *
679 	 * @returns {bool} True if the key is pressed, false otherwise.
680 	 */
681 	isKeyDown : function (key) {
682 		if (key.toUpperCase) {
683 			key = key.toUpperCase();
684 		}
685 		return this._keysDown[key];
686 	},
687 
688 	/**
689 	 * Tests if a mouse button is pressed.
690 	 *
691 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
692 	 *
693 	 * @returns {bool} True if the mouse button is pressed, false otherwise.
694 	 */
695 	isMouseButtonDown : function (btn) {
696 		return this._mouseButtonsDown[btn];
697 	},
698 
699 	/**
700 	 * Tests if a dragging (mouse move + mouse button down) operation is active.
701 	 *
702 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
703 	 *
704 	 * @returns {bool} True if the dragging operation is active with the specified mouse button, false otherwise.
705 	 */
706 	isDragging : function (btn) {
707 		return this._dragging[btn];
708 	},
709 
710 	/**
711 	 * Gets the cursor x position when dragging has started.
712 	 *
713 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
714 	 *
715 	 * @returns {number} The cursor x position, relative to the canvas lower left corner, when the dragging has started.
716 	 */
717 	dragStartX : function (btn) {
718 		return this._dragStartPos[btn][0];
719 	},
720 
721 	/**
722 	 * Gets the cursor y position when dragging has started.
723 	 *
724 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
725 	 *
726 	 * @returns {number} The cursor y position, relative to the canvas lower left corner, when the dragging has started.
727 	 */
728 	dragStartY : function (btn) {
729 		return this._dragStartPos[btn][1];
730 	},
731 
732 	/**
733 	 * Gets the cursor x position when dragging has finished.
734 	 *
735 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
736 	 *
737 	 * @returns {number} The cursor x position, relative to the canvas lower left corner, when the dragging has finished.
738 	 */
739 	dragEndX : function (btn) {
740 		return this._dragEndPos[btn][0];
741 	},
742 
743 	/**
744 	 * Gets the cursor y position when dragging has finished.
745 	 *
746 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
747 	 *
748 	 * @returns {number} The cursor y position, relative to the canvas lower left corner, when the dragging has finished.
749 	 */
750 	dragEndY : function (btn) {
751 		return this._dragEndPos[btn][1];
752 	},
753 
754 	/**
755 	 * If dragging is ongoing, gets the difference between the current cursor x position and the cursor x position at dragging start.
756 	 *
757 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
758 	 *
759 	 * @returns {number} The difference between the current cursor x position and the cursor x position at dragging start.
760 	 */
761 	dragDeltaX : function (btn) {
762 		return this._dragDeltaPos[btn][0];
763 	},
764 
765 	/**
766 	 * If dragging is ongoing, gets the difference between the current cursor y position and the cursor x position at dragging start.
767 	 *
768 	 * @param {number} btn The button to test (0 = left, 1 = right, 2 = middle).
769 	 *
770 	 * @returns {number} The difference between the current cursor y position and the cursor y position at dragging start.
771 	 */
772 	dragDeltaY : function (btn) {
773 		return this._dragDeltaPos[btn][1];
774 	},
775 
776 	/**
777 	 * Gets the cursor x position, relative to the canvas lower left corner.
778 	 *
779 	 * @type number
780 	 */
781 	get cursorX() {
782 		return this._cursorPos[0];
783 	},
784 
785 	/**
786 	 * Gets the cursor y position, relative to the canvas lower left corner.
787 	 *
788 	 * @type number
789 	 */
790 	get cursorY() {
791 		return this._cursorPos[1];
792 	},
793 
794 	/**
795 	 * Gets the previous cursor x position, relative to the canvas lower left corner.
796 	 *
797 	 * @type number
798 	 */
799 	get cursorPrevX() {
800 		return this._cursorPrevPos[0];
801 	},
802 
803 	/**
804 	 * Gets the previous cursor y position, relative to the canvas lower left corner.
805 	 *
806 	 * @type number
807 	 */
808 	get cursorPrevY() {
809 		return this._cursorPrevPos[1];
810 	},
811 
812 	/**
813 	 * Gets the difference between the current and the previous cursor x position, relative to the canvas lower left corner.
814 	 *
815 	 * @type number
816 	 */
817 	get cursorDeltaX() {
818 		return this._cursorDeltaPos[0];
819 	},
820 
821 	/**
822 	 * Gets the difference between the current and the previous cursor y position, relative to the canvas lower left corner.
823 	 *
824 	 * @type number
825 	 */
826 	get cursorDeltaY() {
827 		return this._cursorDeltaPos[1];
828 	},
829 
830 	/**
831 	 * Calls immediately the onDraw event handler.
832 	 */
833 	draw : function () {
834 		this._onDraw();
835 	}
836 };
837 
838 SpiderGL.Type.extend(SpiderGL.UserInterface.CanvasHandler, SpiderGL.Core.ObjectBase);
839 
840 /**
841  * Sets up a WebGL context and canvas event handling.
842  * The WebGLRenderingContext is created and hijacked using {@link SpiderGL.WebGL.Context.getHijacked}.
843  * A {@link SpiderGL.UserInterface.CanvasHandler} is created to handle the canvas events and dispatch them to the provided handler.
844  * The canvas handler will be installed in the handler object as a named property.
845  *
846  * @param {HTMLCanvasElement|string} canvas The canvas used for rendering and from which event will be received.
847  * @param {object} handler The event handler.
848  * @param {function()} [handler.onInitialize] onInitialize event handler.
849  * @param {function()} [handler.onTerminate] onTerminate event handler.
850  * @param {function(keyCode, event)} [handler.onKeyUp] onKeyUp event handler.
851  * @param {function(keyCode, event)} [handler.onKeyDown] onKeyDown event handler.
852  * @param {function(keyCode, event)} [handler.onKeyPress] onKeyPress event handler.
853  * @param {function(button, cursorX, cursorY, event)} [handler.onMouseButtonDown] onMouseButtonDown event handler.
854  * @param {function(button, cursorX, cursorY, event)} [handler.onMouseButtonUp] onMouseButtonUp event handler.
855  * @param {function(cursorX, cursorY, event)} [handler.onMouseMove] onMouseMove event handler.
856  * @param {function(wheelDelta, cursorX, cursorY, event)} [handler.onMouseWheel] onMouseWheel event handler.
857  * @param {function(button, cursorX, cursorY, event)} [handler.onClick] onClick event handler.
858  * @param {function(button, cursorX, cursorY, event)} [handler.onDoubleClick] onDoubleClick event handler.
859  * @param {function(button, cursorX, cursorY)} [handler.onDragStart] onDragStart event handler.
860  * @param {function(button, cursorX, cursorY)} [handler.onDragEnd] onDragEnd event handler.
861  * @param {function(button, cursorX, cursorY)} [handler.onDrag] onDrag event handler.
862  * @param {function(canvasWidth, canvasHeight)} [handler.onResize] onResize event handler.
863  * @param {function(deltaTimeSecs)} [handler.onAnimate] onAnimate event handler.
864  * @param {function()} [handler.onDraw] onDraw event handler.
865  * @param {object} options Optional parameters (see {@link SpiderGL.UserInterface.CanvasHandler}).
866  * @param {string} [options.uiName=SpiderGL.UserInterface.CanvasHandler.DEFAULT_PROPERTY_NAME] The name of the property to install in the handler object for accessing the canvas handler.
867  *
868  * @see SpiderGL.UserInterface.handleCanvasOnLoad
869  * @see SpiderGL.UserInterface.CanvasHandler
870  */
871 SpiderGL.UserInterface.handleCanvas = function (canvas, handler, options) {
872 	if (!canvas || !handler) return false;
873 
874 	options = options || { };
875 
876 	var gl = SpiderGL.WebGL.Context.getHijacked(canvas, options);
877 	if (!gl) return false;
878 
879 	var ui = new SpiderGL.UserInterface.CanvasHandler(gl, handler, options);
880 	if (!ui) return false;
881 
882 	var uiName = SpiderGL.Utility.getDefaultValue(options.uiName, SpiderGL.UserInterface.CanvasHandler.DEFAULT_PROPERTY_NAME);
883 	handler[uiName] = ui;
884 	ui._firstNotify();
885 
886 	return true;
887 }
888 
889 /**
890  * Sets up a WebGL context and canvas event handling after window loading.
891  * When the window fires the onload event, {@link SpiderGL.UserInterface.handleCanvas} is called.
892  *
893  * @param {HTMLCanvasElement|string} canvas The canvas used for rendering and from which event will be received.
894  * @param {object} handler The event handler (see {@link SpiderGL.UserInterface.handleCanvas}).
895  * @param {object} options Optional parameters (see {@link SpiderGL.UserInterface.handleCanvas}).
896  *
897  * @see SpiderGL.UserInterface.handleCanvas
898  */
899 SpiderGL.UserInterface.handleCanvasOnLoad = function (canvas, handler, options) {
900 	if (!canvas || !handler) return false;
901 
902 	options = options || { };
903 	var onLoad = SpiderGL.Utility.getDefaultValue(options.onLoad, null);
904 
905 	function doHandle() {
906 		SpiderGL.UserInterface.handleCanvas(canvas, handler, options);
907 		if (onLoad) { onLoad(); }
908 	}
909 
910 	window.addEventListener("load", doHandle, false);
911 	return true;
912 }
913 
914