1 /**
  2  * The Render Engine
  3  * AbstractRenderContext
  4  *
  5  * @fileoverview The base class for all render contexts.
  6  *
  7  * @author: Brett Fattori (brettf@renderengine.com)
  8  * @author: $Author: bfattori@gmail.com $
  9  * @version: $Revision: 1570 $
 10  *
 11  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 12  *
 13  * Permission is hereby granted, free of charge, to any person obtaining a copy
 14  * of this software and associated documentation files (the "Software"), to deal
 15  * in the Software without restriction, including without limitation the rights
 16  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 17  * copies of the Software, and to permit persons to whom the Software is
 18  * furnished to do so, subject to the following conditions:
 19  *
 20  * The above copyright notice and this permission notice shall be included in
 21  * all copies or substantial portions of the Software.
 22  *
 23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 29  * THE SOFTWARE.
 30  *
 31  */
 32 
 33 // The class this file defines and its required classes
 34 R.Engine.define({
 35     "class":"R.rendercontexts.AbstractRenderContext",
 36     "requires":[
 37         "R.struct.Container",
 38         "R.math.Math2D",
 39         "R.engine.GameObject",
 40         "R.struct.MouseInfo"
 41     ]
 42 });
 43 
 44 /**
 45  * @class A base rendering context.  Game objects are rendered to a context
 46  * during engine runtime.  A render context is a container of all of the objects
 47  * added to it so that each object is given the chance to render.  A render context
 48  * is logically a scene graph.  While each context can have multiple contexts associated
 49  * with it, the root of the scene graph is always located at {@link R.Engine#getDefaultContext}.
 50  *
 51  * @param contextName {String} The name of this context.  Default: RenderContext
 52  * @param [surface] {HTMLElement} The surface node that all objects will be rendered to.
 53  * @extends R.struct.Container
 54  * @constructor
 55  * @description Creates a render context
 56  */
 57 R.rendercontexts.AbstractRenderContext = function () {
 58     return R.struct.Container.extend(/** @scope R.rendercontexts.AbstractRenderContext.prototype */{
 59 
 60         surface:null,
 61         transformStackDepth:0,
 62         worldBoundary:null,
 63         viewport:null,
 64         expViewport:null,
 65         worldPosition:null,
 66         worldRotation:null,
 67         worldScale:null,
 68         staticCtx:null,
 69         safeRemoveList:null,
 70         _handlers:null,
 71 
 72         /** @private */
 73         constructor:function (contextName, surface) {
 74             this.worldBoundary = null;
 75             this.worldScale = 1;
 76             this.worldPosition = R.math.Point2D.create(0, 0);
 77             this.worldRotation = 0;
 78             this.viewport = R.math.Rectangle2D.create(0, 0, 100, 100);
 79             this.expViewport = R.math.Rectangle2D.create(-25, -25, 125, 125);
 80             this.staticCtx = false;
 81             this.safeRemoveList = [];
 82             this._handlers = [];
 83 
 84             this.base(contextName || "RenderContext");
 85             this.surface = surface;
 86             this.setElement(surface);
 87 
 88             var pType = this.jQ().css("position");
 89             if (pType === "" || pType === "auto" || pType === "static") {
 90                 // Make it relative so we can get it's offset in the document
 91                 this.jQ().css("position", "relative");
 92             }
 93         },
 94 
 95         /**
 96          * Releases this context back into the pool for reuse
 97          */
 98         release:function () {
 99             this.base();
100             this.surface = null;
101             this.transformStackDepth = 0;
102             this.worldScale = null;
103             this.worldPosition = null;
104             this.worldRotation = null;
105             this.staticCtx = null;
106             this.safeRemoveList = null;
107             this.viewport = null;
108             this.expViewport = null;
109             this.worldBoundary = null;
110             this._handlers = null;
111         },
112 
113         /**
114          * Destroy the rendering context, any objects within the context, and detach
115          * the surface from its parent container.
116          */
117         destroy:function () {
118             // Destroy all of the objects
119             this.cleanUp();
120             this.surface = null;
121             this.viewport.destroy();
122             this.expViewport.destroy();
123             this.worldPosition.destroy();
124             if (this.worldBoundary != null) {
125                 this.worldBoundary.destroy();
126             }
127             if (this._handlers[0]) {
128                 this.uncaptureMouse();
129             }
130             if (this._handlers[1]) {
131                 this.uncaptureTouch();
132             }
133             this.base();
134         },
135 
136         /**
137          * Set the surface element that objects will be rendered to.
138          *
139          * @param element {HTMLElement} The document node that all objects will be rendered to.
140          */
141         setSurface:function (element) {
142             this.surface = element;
143             this.setElement(element);
144         },
145 
146         /**
147          * Get the surface node that all objects will be rendered to.
148          * @return {HTMLElement} The document node that represents the rendering surface
149          */
150         getSurface:function () {
151             return this.surface;
152         },
153 
154         /**
155          * Set the context to be static.  Setting a context to be static effectively removes
156          * it from the automatic update when the world is updated.  The user will need to call
157          * {@link #render}, passing the world time (gotten with {@link R.Engine#worldTime})
158          * to manually render the context.  Any objects within the context will then render
159          * to the context.
160          *
161          * @param state {Boolean} <tt>true</tt> to set the context to static
162          */
163         setStatic:function (state) {
164             this.staticCtx = state;
165         },
166 
167         /**
168          * Determine if the context is static.
169          * @return {Boolean}
170          */
171         isStatic:function () {
172             return this.staticCtx;
173         },
174 
175         /**
176          * Enable mouse event capturing within the render context.  The mouse
177          * event data will be accessible by calling {@link #getMouseInfo}.
178          */
179         captureMouse:function () {
180             if (!this._handlers[0]) {
181                 R.rendercontexts.AbstractRenderContext.assignMouseHandler(this);
182                 var self = this;
183                 R.Engine.onShutdown(function () {
184                     self.uncaptureMouse();
185                 });
186                 this._handlers[0] = true;
187             }
188         },
189 
190         /**
191          * Disable mouse event capturing within the render context.
192          */
193         uncaptureMouse:function () {
194             R.rendercontexts.AbstractRenderContext.removeMouseHandler(this);
195             this._handlers[0] = false;
196         },
197 
198         /**
199          * Get the state of the mouse in the rendering context.  You must first enable mouse
200          * event capturing with {@link #captureMouse}.  See {@link R.struct.MouseInfo} for more
201          * information about what is in the object.
202          *
203          * @return {R.struct.MouseInfo} The current state of the mouse
204          */
205         getMouseInfo:function () {
206             return this.getObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL);
207         },
208 
209         /**
210          * Enable touch event capturing within the render context.  The touch
211          * event data will be accessible by calling {@link #getTouchInfo}.
212          */
213         captureTouch:function () {
214             if (!this._handlers[1]) {
215                 R.rendercontexts.AbstractRenderContext.assignTouchHandler(this);
216                 var self = this;
217                 R.Engine.onShutdown(function () {
218                     self.uncaptureTouch();
219                 });
220                 this._handlers[1] = true;
221             }
222         },
223 
224         /**
225          * Disable touch event capturing within the render context.
226          */
227         uncaptureTouch:function () {
228             R.rendercontexts.AbstractRenderContext.removeTouchHandler(this);
229             this._handlers[1] = false;
230         },
231 
232         /**
233          * Get the state of touches in the rendering context.  You must first enable touch
234          * event capturing with {@link #captureTouch}.  See {@link R.struct.TouchInfo} for more
235          * information about what is in the object.
236          *
237          * @return {R.struct.TouchInfo} The current state of touches
238          */
239         getTouchInfo:function () {
240             return this.getObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL);
241         },
242 
243         /**
244          * [ABSTRACT] Set the scale of the rendering context.
245          *
246          * @param scaleX {Number} The scale along the X dimension
247          * @param scaleY {Number} The scale along the Y dimension
248          */
249         setScale:function (scaleX, scaleY) {
250         },
251 
252         /**
253          * Set the world scale of the rendering context.  All objects should
254          * be adjusted by this scale when the context renders.
255          *
256          * @param scale {Number} The uniform scale of the world
257          */
258         setWorldScale:function (scale) {
259             this.worldScale = scale;
260         },
261 
262         /**
263          * Set the world rotation of the rendering context.  All objects
264          * should be adjusted by this rotation when the context renders.
265          * @param rotation {Number} The rotation angle
266          */
267         setWorldRotation:function (rotation) {
268             this.worldRotation = rotation;
269         },
270 
271         /**
272          * Set the world position of the rendering context.  All objects
273          * should be adjuest by this position when the context renders.
274          * @param point {R.math.Point2D|Number} The world position or X coordinate
275          * @param [y] {Number} If <tt>point</tt> is a number, this is the Y coordinate
276          */
277         setWorldPosition:function (point, y) {
278             this.worldPosition.set(point, y);
279         },
280 
281         /**
282          * Gets an array representing the rendering scale of the world.
283          * @return {Array} The first element is the X axis, the second is the Y axis
284          */
285         getWorldScale:function () {
286             return this.worldScale;
287         },
288 
289         /**
290          * Gets the world rotation angle.
291          * @return {Number}
292          */
293         getWorldRotation:function () {
294             return this.worldRotation;
295         },
296 
297         /**
298          * Get the world render position.
299          * @return {R.math.Point2D}
300          */
301         getWorldPosition:function () {
302             return this.worldPosition;
303         },
304 
305         /**
306          * Set the world boundaries.  Set the world boundaries bigger than the viewport
307          * to create a virtual world.  By default, the world boundary matches the viewport.
308          * @param rect {R.math.Rectangle2D}
309          */
310         setWorldBoundary:function (rect) {
311             this.worldBoundary = rect;
312         },
313 
314         /**
315          * get the world boundaries.
316          * @return {R.math.Rectangle2D}
317          */
318         getWorldBoundary:function (rect) {
319             return this.worldBoundary;
320         },
321 
322         /**
323          * Set the viewport of the render context.  The viewport is a window
324          * upon the world so that not all of the world is rendered at one time.
325          * @param rect {R.math.Rectangle2D} A rectangle defining the viewport
326          */
327         setViewport:function (rect) {
328             this.viewport.set(rect);
329 
330             // Calculate the expanded viewport
331             var w = rect.getDims().x * 0.25, h = rect.getDims().y * 0.25,
332                 tl = rect.getTopLeft(), d = rect.getDims();
333             this.expViewport.set(tl.x - w, tl.y - h, tl.x + d.x + (w * 2), tl.y + d.y + (h * 2));
334         },
335 
336         /**
337          * Get the viewport of the render context.
338          * @return {R.math.Rectangle2D}
339          */
340         getViewport:function () {
341             return this.viewport;
342         },
343 
344         /**
345          * A viewport that is 25% larger than {@link #getViewport} to account for
346          * an area slightly outside the viewing area.  Typically used to determin
347          * what objects are to be processed in the scenegraph.
348          * @return {R.math.Rectangle2D}
349          */
350         getExpandedViewport:function () {
351             return this.expViewport;
352         },
353 
354         /**
355          * Add an object to the context.  Only objects
356          * within the context will be rendered.  If an object declared
357          * an <tt>afterAdd()</tt> method, it will be called after the object
358          * has been added to the context.
359          *
360          * @param obj {R.engine.BaseObject} The object to add to the render list
361          */
362         add:function (obj) {
363             this.base(obj);
364 
365             // Create a structure to hold information that is related to
366             // the render context that keeps it separate from the rest of the object.
367             obj.setObjectDataModel(R.rendercontexts.AbstractRenderContext.DATA_MODEL, {});
368 
369             if (obj instanceof R.engine.GameObject) {
370                 obj.setRenderContext(this);
371                 this.sort();
372             }
373         },
374 
375         /**
376          * Remove an object from the render context.  The object is
377          * not destroyed when it is removed from the container.  The removal
378          * occurs after each update to avoid disrupting the flow of object
379          * traversal.
380          *
381          * @param obj {Object} The object to remove from the container.
382          * @return {Object} The object that was removed
383          */
384         remove:function (obj) {
385             this.safeRemoveList.push(obj);
386         },
387 
388         /**
389          * Remove an object from the render context at the specified index.
390          * The object is not destroyed when it is removed.  The removal
391          * occurs after each update to avoid disrupting the flow of object
392          * traversal.
393          *
394          * @param idx {Number} An index between zero and the size of the container minus 1.
395          * @return {Object} The object removed from the container.
396          */
397         removeAtIndex:function (idx) {
398             this.safeRemoveList.push(this.get(idx));
399         },
400 
401         /**
402          * This method is called after the update to remove items from the
403          * context.
404          * @private
405          */
406         _safeRemove:function () {
407             var obj;
408             while ((obj = this.safeRemoveList.shift()) != null) {
409                 R.struct.Container.prototype.remove.call(this, obj);
410             }
411             this.safeRemoveList.length = 0;
412         },
413 
414         /**
415          * Returns the structure that contains information held about
416          * the rendering context.  This object allows a context to store
417          * extra information on an object that an object wouldn't know about.
418          * @return {Object} An object with data used by the context
419          */
420         getContextData:function (obj) {
421             return obj.getObjectDataModel(R.rendercontexts.AbstractRenderContext.DATA_MODEL);
422         },
423 
424         /**
425          * [ABSTRACT] Sort the render context's objects.
426          * @param sortFn {Function} A function to sort with, or <tt>null</tt> to use the default
427          */
428         sort:function (sortFn) {
429             this.base(sortFn);
430         },
431 
432         /**
433          * [ABSTRACT] Clear the context and prepare it for rendering.  If you pass a
434          * rectangle, only that portion of the world will be cleared.  If
435          * you don't pass a rectangle, the entire context is cleared.
436          *
437          * @param rect {R.math.Rectangle2D} The area to clear in the context, or
438          *             <tt>null</tt> to clear the entire context.
439          */
440         reset:function (rect) {
441         },
442 
443         /**
444          * Update the render context before rendering the objects to the surface.
445          * If the context isn't static, this will reset and then render the context.
446          * If the context is static, you'll need to perform the reset and render yourself.
447          * This allows you to update objects in the world, skip the reset, and then
448          * render yourself.  This can be an effective way to handle redrawing the world
449          * only as needed.
450          *
451          * @param parentContext {R.rendercontexts.AbstractRenderContext} A parent context, or <tt>null</tt>
452          * @param time {Number} The current render time in milliseconds from the engine.
453          * @param dt {Number} The delta between the world time and the last time the world was updated
454          *          in milliseconds.
455          */
456         update:function (parentContext, time, dt) {
457             if (!this.staticCtx) {
458                 // Clear and render world
459                 this.reset();
460                 this.render(time, dt);
461             }
462         },
463 
464         /**
465          * Called to render all of the objects to the context.
466          *
467          * @param time {Number} The current world time in milliseconds from the engine.
468          * @param dt {Number} The delta between the world time and the last time the world was updated
469          *          in milliseconds.
470          */
471         render:function (time, dt) {
472             // Push the world transform
473             this.pushTransform();
474 
475             this.setupWorld(time, dt);
476 
477             // Run the objects through their components
478             var objs = this.iterator();
479             while (objs.hasNext()) {
480                 this.renderObject(objs.next(), time, dt);
481             }
482 
483             objs.destroy();
484 
485             // Restore the world transform
486             this.popTransform();
487 
488             // Safely remove any objects that were removed from
489             // the context while it was rendering
490             if (this.safeRemoveList.length > 0) {
491                 this._safeRemove();
492             }
493         },
494 
495         /**
496          * Render a single object into the world for the given time.
497          * @param obj {R.engine.BaseObject} An object to render
498          * @param time {Number} The world time, in milliseconds
499          * @param dt {Number} The delta between the world time and the last time the world was updated
500          *          in milliseconds.
501          */
502         renderObject:function (obj, time, dt) {
503             obj.update(this, time, dt);
504         },
505 
506         /**
507          * [ABSTRACT] Gives the render context a chance to initialize the world.
508          * Use this method to change the world position, rotation, scale, etc.
509          *
510          * @param time {Number} The current world time
511          * @param dt {Number} The delta between the world time and the last time the world was updated
512          *          in milliseconds.
513          */
514         setupWorld:function (time, dt) {
515         },
516 
517         /**
518          * Increment the transform stack counter.
519          */
520         pushTransform:function () {
521             this.transformStackDepth++;
522         },
523 
524         /**
525          * Decrement the transform stack counter and ensure that the stack
526          * is not unbalanced.  An unbalanced stack can be indicative of
527          * objects that do not reset the state after rendering themselves.
528          */
529         popTransform:function () {
530             this.transformStackDepth--;
531             Assert((this.transformStackDepth >= 0), "Unbalanced transform stack!");
532         },
533 
534         /**
535          * This is a potentially expensive call, and can lead to rendering
536          * errors.  It is recommended against calling this method!
537          */
538         resetTransformStack:function () {
539             while (this.transformStackDepth > 0) {
540                 this.popTransform();
541             }
542         }
543     }, /** @scope R.rendercontexts.AbstractRenderContext.prototype */{
544 
545         /**
546          * Get the class name of this object
547          * @return {String} "R.rendercontexts.AbstractRenderContext"
548          */
549         getClassName:function () {
550             return "R.rendercontexts.AbstractRenderContext";
551         },
552 
553         /**
554          * @private
555          */
556         DATA_MODEL:"RenderContext",
557 
558         /**
559          * @private
560          */
561         MOUSE_DATA_MODEL:"mouseInfo",
562 
563         /**
564          * @private
565          */
566         TOUCH_DATA_MODEL:"touchInfo",
567 
568         /**
569          * Assigns the mouse handlers.
570          * @private
571          * @param ctx {R.rendercontexts.AbstractRenderContext} The context to assign the handlers to
572          */
573         assignMouseHandler:function (ctx) {
574             // Assign handlers to the context, making sure to only add
575             // the handler once.  This way we don't have hundreds of mouse move
576             // handlers taking up precious milliseconds.
577             var mouseInfo = ctx.getObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL);
578 
579             if (!mouseInfo) {
580                 mouseInfo = ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL,
581                     R.struct.MouseInfo.create());
582 
583                 ctx.addEvent(ctx, "mousemove", function (evt) {
584                     if (mouseInfo.moveTimer != null) {
585                         mouseInfo.moveTimer.destroy();
586                         mouseInfo.moveTimer = null;
587                     }
588                     mouseInfo.lastPosition.set(mouseInfo.position);
589 
590                     // BAF: 05/31/2011 - https://github.com/bfattori/TheRenderEngine/issues/7
591                     // BAF: 06/17/2011 - https://github.com/bfattori/TheRenderEngine/issues/9
592                     // Adjust for position of context
593                     var x = evt.pageX, y = evt.pageY;
594                     mouseInfo.position.set(x, y).sub(ctx.getObjectDataModel("DOMPosition"));
595 
596                     // Move vector is calculated relative to the last position
597                     mouseInfo.moveVec.set(mouseInfo.lastPosition);
598                     mouseInfo.moveVec.sub(mouseInfo.position);
599 
600                     if (mouseInfo.button != R.engine.Events.MOUSE_NO_BUTTON) {
601                         // Drag vector originates from the "down" position and is normalized
602                         mouseInfo.dragVec.set(mouseInfo.downPosition);
603                         mouseInfo.dragVec.sub(mouseInfo.position).normalize();
604                     }
605 
606                     mouseInfo.moveTimer = R.lang.Timeout.create("mouseMove", 33, function () {
607                         mouseInfo.moveVec.set(0, 0);
608                     });
609                 });
610 
611                 ctx.addEvent(ctx, "mousedown", function (evt) {
612                     mouseInfo.button = evt.which;
613                     var x = evt.pageX, y = evt.pageY;
614 
615                     mouseInfo.downPosition.set(x, y).sub(ctx.getObjectDataModel("DOMPosition"));
616                     evt.preventDefault();
617                 });
618 
619                 ctx.addEvent(ctx, "mouseup", function (evt) {
620                     mouseInfo.button = R.engine.Events.MOUSE_NO_BUTTON;
621                     mouseInfo.dragVec.set(0, 0);
622                 });
623 
624             }
625         },
626 
627         /**
628          * Remove the mouse handlers
629          * @param ctx
630          * @private
631          */
632         removeMouseHandler:function (ctx) {
633             ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL, undefined);
634             ctx.removeEvent(ctx, "mousemove");
635             ctx.removeEvent(ctx, "mousedown");
636             ctx.removeEvent(ctx, "mouseup");
637         },
638 
639         /**
640          * Assigns the touch handlers.
641          * @private
642          * @param ctx {R.rendercontexts.AbstractRenderContext} The context to assign the handlers to
643          */
644         assignTouchHandler:function (ctx) {
645             // Assign handlers to the context, making sure to only add
646             // the handler once.  This way we don't have hundreds of mouse move
647             // handlers taking up precious milliseconds.
648             var touchInfo = ctx.getObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL);
649 
650             if (!touchInfo) {
651                 touchInfo = ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL,
652                     R.struct.TouchInfo.create());
653 
654                 ctx.addEvent(ctx, "touchmove", function (evt) {
655                     touchInfo.touches = R.struct.TouchInfo.processTouches(evt.originalEvent);
656 
657                     if (touchInfo.moveTimer != null) {
658                         touchInfo.moveTimer.destroy();
659                         touchInfo.moveTimer = null;
660                     }
661                     touchInfo.lastPosition.set(touchInfo.position);
662 
663                     // Use the first touch as the position
664                     var x = touchInfo.touches[0].getX(), y = touchInfo.touches[0].getY();
665 
666                     touchInfo.position.set(x, y).sub(ctx.getObjectDataModel("DOMPosition"));
667 
668                     // Move vector is calculated relative to the last position
669                     touchInfo.moveVec.set(touchInfo.lastPosition);
670                     touchInfo.moveVec.sub(touchInfo.position);
671 
672                     // Drag vector originates from the "down" touch and is normalized
673                     touchInfo.dragVec.set(touchInfo.downPosition);
674                     touchInfo.dragVec.sub(touchInfo.position).normalize();
675 
676                     touchInfo.moveTimer = R.lang.Timeout.create("touchMove", 33, function () {
677                         touchInfo.moveVec.set(0, 0);
678                     });
679                 });
680 
681                 ctx.addEvent(ctx, "touchstart", function (evt) {
682                     touchInfo.touches = R.struct.TouchInfo.processTouches(evt.originalEvent);
683                     touchInfo.button = R.engine.Events.MOUSE_LEFT_BUTTON;
684 
685                     // Use the first touch as the down position
686                     var x = touchInfo.touches[0].getX(), y = touchInfo.touches[0].getY();
687 
688                     touchInfo.downPosition.set(x, y).sub(this._offset);
689                     evt.preventDefault();
690                 });
691 
692                 ctx.addEvent(ctx, "touchend", function (evt) {
693                     touchInfo.touches = R.struct.TouchInfo.processTouches(evt.originalEvent);
694                     touchInfo.button = R.engine.Events.MOUSE_NO_BUTTON;
695                     touchInfo.dragVec.set(0, 0);
696                 });
697 
698             }
699         },
700 
701         /**
702          * Remove the touch handlers
703          * @param ctx
704          * @private
705          */
706         removeTouchHandler:function (ctx) {
707             ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL, undefined);
708             ctx.removeEvent(ctx, "touchmove");
709             ctx.removeEvent(ctx, "touchstart");
710             ctx.removeEvent(ctx, "touchend");
711         }
712 
713     });
714 
715 };
716