Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / third_party / tvcm / src / tvcm / ui / quad_stack_view.js
1 // Copyright (c) 2013 The Chromium 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 'use strict';
6
7 /**
8  * @fileoverview QuadStackView controls the content and viewing angle a
9  * QuadStack.
10  */
11 tvcm.requireStylesheet('tvcm.ui.quad_stack_view');
12
13 tvcm.requireTemplate('tvcm.ui.quad_stack_view');
14
15 tvcm.require('tvcm.bbox2');
16 tvcm.require('tvcm.gl_matrix');
17 tvcm.require('tvcm.quad');
18 tvcm.require('tvcm.raf');
19 tvcm.require('tvcm.rect');
20 tvcm.require('tvcm.settings');
21 tvcm.require('tvcm.ui.camera');
22 tvcm.require('tvcm.ui.mouse_mode_selector');
23 tvcm.require('tvcm.ui.mouse_tracker');
24
25 tvcm.exportTo('tvcm.ui', function() {
26   var constants = {};
27   constants.IMAGE_LOAD_RETRY_TIME_MS = 500;
28   constants.SUBDIVISION_MINIMUM = 1;
29   constants.SUBDIVISION_RECURSION_DEPTH = 3;
30   constants.SUBDIVISION_DEPTH_THRESHOLD = 100;
31   constants.FAR_PLANE_DISTANCE = 10000;
32
33   // Care of bckenney@ via
34   // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
35   function drawTexturedTriangle(ctx, img, p0, p1, p2, t0, t1, t2) {
36     var tmp_p0 = [p0[0], p0[1]];
37     var tmp_p1 = [p1[0], p1[1]];
38     var tmp_p2 = [p2[0], p2[1]];
39     var tmp_t0 = [t0[0], t0[1]];
40     var tmp_t1 = [t1[0], t1[1]];
41     var tmp_t2 = [t2[0], t2[1]];
42
43     ctx.beginPath();
44     ctx.moveTo(tmp_p0[0], tmp_p0[1]);
45     ctx.lineTo(tmp_p1[0], tmp_p1[1]);
46     ctx.lineTo(tmp_p2[0], tmp_p2[1]);
47     ctx.closePath();
48
49     tmp_p1[0] -= tmp_p0[0];
50     tmp_p1[1] -= tmp_p0[1];
51     tmp_p2[0] -= tmp_p0[0];
52     tmp_p2[1] -= tmp_p0[1];
53
54     tmp_t1[0] -= tmp_t0[0];
55     tmp_t1[1] -= tmp_t0[1];
56     tmp_t2[0] -= tmp_t0[0];
57     tmp_t2[1] -= tmp_t0[1];
58
59     var det = 1 / (tmp_t1[0] * tmp_t2[1] - tmp_t2[0] * tmp_t1[1]),
60
61         // linear transformation
62         a = (tmp_t2[1] * tmp_p1[0] - tmp_t1[1] * tmp_p2[0]) * det,
63         b = (tmp_t2[1] * tmp_p1[1] - tmp_t1[1] * tmp_p2[1]) * det,
64         c = (tmp_t1[0] * tmp_p2[0] - tmp_t2[0] * tmp_p1[0]) * det,
65         d = (tmp_t1[0] * tmp_p2[1] - tmp_t2[0] * tmp_p1[1]) * det,
66
67         // translation
68         e = tmp_p0[0] - a * tmp_t0[0] - c * tmp_t0[1],
69         f = tmp_p0[1] - b * tmp_t0[0] - d * tmp_t0[1];
70
71     ctx.save();
72     ctx.transform(a, b, c, d, e, f);
73     ctx.clip();
74     ctx.drawImage(img, 0, 0);
75     ctx.restore();
76   }
77
78   function drawTriangleSub(
79       ctx, img, p0, p1, p2, t0, t1, t2, opt_recursion_depth) {
80     var depth = opt_recursion_depth || 0;
81
82     // We may subdivide if we are not at the limit of recursion.
83     var subdivisionIndex = 0;
84     if (depth < constants.SUBDIVISION_MINIMUM) {
85       subdivisionIndex = 7;
86     } else if (depth < constants.SUBDIVISION_RECURSION_DEPTH) {
87       if (Math.abs(p0[2] - p1[2]) > constants.SUBDIVISION_DEPTH_THRESHOLD)
88         subdivisionIndex += 1;
89       if (Math.abs(p0[2] - p2[2]) > constants.SUBDIVISION_DEPTH_THRESHOLD)
90         subdivisionIndex += 2;
91       if (Math.abs(p1[2] - p2[2]) > constants.SUBDIVISION_DEPTH_THRESHOLD)
92         subdivisionIndex += 4;
93     }
94
95     // These need to be created every time, since temporaries
96     // outside of the scope will be rewritten in recursion.
97     var p01 = vec4.create();
98     var p02 = vec4.create();
99     var p12 = vec4.create();
100     var t01 = vec2.create();
101     var t02 = vec2.create();
102     var t12 = vec2.create();
103
104     // Calculate the position before w-divide.
105     for (var i = 0; i < 2; ++i) {
106       p0[i] *= p0[2];
107       p1[i] *= p1[2];
108       p2[i] *= p2[2];
109     }
110
111     // Interpolate the 3d position.
112     for (var i = 0; i < 4; ++i) {
113       p01[i] = (p0[i] + p1[i]) / 2;
114       p02[i] = (p0[i] + p2[i]) / 2;
115       p12[i] = (p1[i] + p2[i]) / 2;
116     }
117
118     // Re-apply w-divide to the original points and the interpolated ones.
119     for (var i = 0; i < 2; ++i) {
120       p0[i] /= p0[2];
121       p1[i] /= p1[2];
122       p2[i] /= p2[2];
123
124       p01[i] /= p01[2];
125       p02[i] /= p02[2];
126       p12[i] /= p12[2];
127     }
128
129     // Interpolate the texture coordinates.
130     for (var i = 0; i < 2; ++i) {
131       t01[i] = (t0[i] + t1[i]) / 2;
132       t02[i] = (t0[i] + t2[i]) / 2;
133       t12[i] = (t1[i] + t2[i]) / 2;
134     }
135
136     // Based on the index, we subdivide the triangle differently.
137     // Assuming the triangle is p0, p1, p2 and points between i j
138     // are represented as pij (that is, a point between p2 and p0
139     // is p02, etc), then the new triangles are defined by
140     // the 3rd 4th and 5th arguments into the function.
141     switch (subdivisionIndex) {
142       case 1:
143         drawTriangleSub(ctx, img, p0, p01, p2, t0, t01, t2, depth + 1);
144         drawTriangleSub(ctx, img, p01, p1, p2, t01, t1, t2, depth + 1);
145         break;
146       case 2:
147         drawTriangleSub(ctx, img, p0, p1, p02, t0, t1, t02, depth + 1);
148         drawTriangleSub(ctx, img, p1, p02, p2, t1, t02, t2, depth + 1);
149         break;
150       case 3:
151         drawTriangleSub(ctx, img, p0, p01, p02, t0, t01, t02, depth + 1);
152         drawTriangleSub(ctx, img, p02, p01, p2, t02, t01, t2, depth + 1);
153         drawTriangleSub(ctx, img, p01, p1, p2, t01, t1, t2, depth + 1);
154         break;
155       case 4:
156         drawTriangleSub(ctx, img, p0, p12, p2, t0, t12, t2, depth + 1);
157         drawTriangleSub(ctx, img, p0, p1, p12, t0, t1, t12, depth + 1);
158         break;
159       case 5:
160         drawTriangleSub(ctx, img, p0, p01, p2, t0, t01, t2, depth + 1);
161         drawTriangleSub(ctx, img, p2, p01, p12, t2, t01, t12, depth + 1);
162         drawTriangleSub(ctx, img, p01, p1, p12, t01, t1, t12, depth + 1);
163         break;
164       case 6:
165         drawTriangleSub(ctx, img, p0, p12, p02, t0, t12, t02, depth + 1);
166         drawTriangleSub(ctx, img, p0, p1, p12, t0, t1, t12, depth + 1);
167         drawTriangleSub(ctx, img, p02, p12, p2, t02, t12, t2, depth + 1);
168         break;
169       case 7:
170         drawTriangleSub(ctx, img, p0, p01, p02, t0, t01, t02, depth + 1);
171         drawTriangleSub(ctx, img, p01, p12, p02, t01, t12, t02, depth + 1);
172         drawTriangleSub(ctx, img, p01, p1, p12, t01, t1, t12, depth + 1);
173         drawTriangleSub(ctx, img, p02, p12, p2, t02, t12, t2, depth + 1);
174         break;
175       default:
176         // In the 0 case and all other cases, we simply draw the triangle.
177         drawTexturedTriangle(ctx, img, p0, p1, p2, t0, t1, t2);
178         break;
179     }
180   }
181
182   // Created to avoid creating garbage when doing bulk transforms.
183   var tmp_vec4 = vec4.create();
184   function transform(transformed, point, matrix, viewport) {
185     vec4.set(tmp_vec4, point[0], point[1], 0, 1);
186     vec4.transformMat4(tmp_vec4, tmp_vec4, matrix);
187
188     var w = tmp_vec4[3];
189     if (w < 1e-6) w = 1e-6;
190
191     transformed[0] = ((tmp_vec4[0] / w) + 1) * viewport.width / 2;
192     transformed[1] = ((tmp_vec4[1] / w) + 1) * viewport.height / 2;
193     transformed[2] = w;
194   }
195
196   function drawProjectedQuadBackgroundToContext(
197       quad, p1, p2, p3, p4, ctx, quadCanvas) {
198     if (quad.imageData) {
199       quadCanvas.width = quad.imageData.width;
200       quadCanvas.height = quad.imageData.height;
201       quadCanvas.getContext('2d').putImageData(quad.imageData, 0, 0);
202       var quadBBox = new tvcm.BBox2();
203       quadBBox.addQuad(quad);
204       var iw = quadCanvas.width;
205       var ih = quadCanvas.height;
206       drawTriangleSub(
207           ctx, quadCanvas,
208           p1, p2, p4,
209           [0, 0], [iw, 0], [0, ih]);
210       drawTriangleSub(
211           ctx, quadCanvas,
212           p2, p3, p4,
213           [iw, 0], [iw, ih], [0, ih]);
214     }
215
216     if (quad.backgroundColor) {
217       ctx.fillStyle = quad.backgroundColor;
218       ctx.beginPath();
219       ctx.moveTo(p1[0], p1[1]);
220       ctx.lineTo(p2[0], p2[1]);
221       ctx.lineTo(p3[0], p3[1]);
222       ctx.lineTo(p4[0], p4[1]);
223       ctx.closePath();
224       ctx.fill();
225     }
226   }
227
228   function drawProjectedQuadOutlineToContext(
229       quad, p1, p2, p3, p4, ctx, quadCanvas) {
230     ctx.beginPath();
231     ctx.moveTo(p1[0], p1[1]);
232     ctx.lineTo(p2[0], p2[1]);
233     ctx.lineTo(p3[0], p3[1]);
234     ctx.lineTo(p4[0], p4[1]);
235     ctx.closePath();
236     ctx.save();
237     if (quad.borderColor)
238       ctx.strokeStyle = quad.borderColor;
239     else
240       ctx.strokeStyle = 'rgb(128,128,128)';
241
242     if (quad.shadowOffset) {
243       ctx.shadowColor = 'rgb(0, 0, 0)';
244       ctx.shadowOffsetX = quad.shadowOffset[0];
245       ctx.shadowOffsetY = quad.shadowOffset[1];
246       if (quad.shadowBlur)
247         ctx.shadowBlur = quad.shadowBlur;
248     }
249
250     if (quad.borderWidth)
251       ctx.lineWidth = quad.borderWidth;
252     else
253       ctx.lineWidth = 1;
254
255     ctx.stroke();
256     ctx.restore();
257   }
258
259   function drawProjectedQuadSelectionOutlineToContext(
260       quad, p1, p2, p3, p4, ctx, quadCanvas) {
261     if (!quad.upperBorderColor)
262       return;
263
264     ctx.lineWidth = 8;
265     ctx.strokeStyle = quad.upperBorderColor;
266
267     ctx.beginPath();
268     ctx.moveTo(p1[0], p1[1]);
269     ctx.lineTo(p2[0], p2[1]);
270     ctx.lineTo(p3[0], p3[1]);
271     ctx.lineTo(p4[0], p4[1]);
272     ctx.closePath();
273     ctx.stroke();
274   }
275
276   function drawProjectedQuadToContext(
277       passNumber, quad, p1, p2, p3, p4, ctx, quadCanvas) {
278     if (passNumber === 0) {
279       drawProjectedQuadBackgroundToContext(
280           quad, p1, p2, p3, p4, ctx, quadCanvas);
281     } else if (passNumber === 1) {
282       drawProjectedQuadOutlineToContext(
283           quad, p1, p2, p3, p4, ctx, quadCanvas);
284     } else if (passNumber === 2) {
285       drawProjectedQuadSelectionOutlineToContext(
286           quad, p1, p2, p3, p4, ctx, quadCanvas);
287     } else {
288       throw new Error('Invalid pass number');
289     }
290   }
291
292   var tmp_p1 = vec3.create();
293   var tmp_p2 = vec3.create();
294   var tmp_p3 = vec3.create();
295   var tmp_p4 = vec3.create();
296   function transformAndProcessQuads(
297       matrix, viewport, quads, numPasses, handleQuadFunc, opt_arg1, opt_arg2) {
298
299     for (var passNumber = 0; passNumber < numPasses; passNumber++) {
300       for (var i = 0; i < quads.length; i++) {
301         var quad = quads[i];
302         transform(tmp_p1, quad.p1, matrix, viewport);
303         transform(tmp_p2, quad.p2, matrix, viewport);
304         transform(tmp_p3, quad.p3, matrix, viewport);
305         transform(tmp_p4, quad.p4, matrix, viewport);
306         handleQuadFunc(passNumber, quad,
307                        tmp_p1, tmp_p2, tmp_p3, tmp_p4,
308                        opt_arg1, opt_arg2);
309       }
310     }
311   }
312
313   /**
314    * @constructor
315    */
316   var QuadStackView = tvcm.ui.define('quad-stack-view');
317
318   QuadStackView.prototype = {
319     __proto__: HTMLUnknownElement.prototype,
320
321     decorate: function() {
322       this.className = 'quad-stack-view';
323
324       var node = tvcm.instantiateTemplate('#quad-stack-view-template');
325       this.appendChild(node);
326
327       this.canvas_ = this.querySelector('#canvas');
328       this.chromeImages_ = {
329         left: this.querySelector('#chrome-left'),
330         mid: this.querySelector('#chrome-mid'),
331         right: this.querySelector('#chrome-right')
332       };
333
334       this.trackMouse_();
335
336       this.camera_ = new tvcm.ui.Camera(this.mouseModeSelector_);
337       this.camera_.addEventListener('renderrequired',
338           this.onRenderRequired_.bind(this));
339       this.cameraWasReset_ = false;
340       this.camera_.canvas = this.canvas_;
341
342       this.viewportRect_ = tvcm.Rect.fromXYWH(0, 0, 0, 0);
343
344       this.stackingDistance_ = 45;
345       this.pixelRatio_ = window.devicePixelRatio || 1;
346     },
347
348     onStackingDistanceChange: function(e) {
349       this.stackingDistance_ = parseInt(e.target.value);
350       this.scheduleRender();
351     },
352
353     get mouseModeSelector() {
354       return this.mouseModeSelector_;
355     },
356
357     get camera() {
358       return this.camera_;
359     },
360
361     set quads(q) {
362       this.quads_ = q;
363       this.scheduleRender();
364     },
365
366     set deviceRect(rect) {
367       if (!rect || rect.equalTo(this.deviceRect_))
368         return;
369
370       this.deviceRect_ = rect;
371       this.camera_.deviceRect = rect;
372       this.chromeQuad_ = undefined;
373     },
374
375     resize: function() {
376       if (!this.offsetParent)
377         return true;
378
379       var width = parseInt(window.getComputedStyle(this.offsetParent).width);
380       var height = parseInt(window.getComputedStyle(this.offsetParent).height);
381       var rect = tvcm.Rect.fromXYWH(0, 0, width, height);
382
383       if (rect.equalTo(this.viewportRect_))
384         return false;
385
386       this.viewportRect_ = rect;
387       this.style.width = width + 'px';
388       this.style.height = height + 'px';
389       this.canvas_.style.width = width + 'px';
390       this.canvas_.style.height = height + 'px';
391       this.canvas_.width = this.pixelRatio_ * width;
392       this.canvas_.height = this.pixelRatio_ * height;
393       if (!this.cameraWasReset_) {
394         this.camera_.resetCamera();
395         this.cameraWasReset_ = true;
396       }
397       return true;
398     },
399
400     readyToDraw: function() {
401       // If src isn't set yet, set it to ensure we can use
402       // the image to draw onto a canvas.
403       if (!this.chromeImages_.left.src) {
404         var leftContent =
405             window.getComputedStyle(this.chromeImages_.left).content;
406         leftContent = leftContent.replace(/url\((.*)\)/, '$1');
407
408         var midContent =
409             window.getComputedStyle(this.chromeImages_.mid).content;
410         midContent = midContent.replace(/url\((.*)\)/, '$1');
411
412         var rightContent =
413             window.getComputedStyle(this.chromeImages_.right).content;
414         rightContent = rightContent.replace(/url\((.*)\)/, '$1');
415
416         this.chromeImages_.left.src = leftContent;
417         this.chromeImages_.mid.src = midContent;
418         this.chromeImages_.right.src = rightContent;
419       }
420
421       // If all of the images are loaded (height > 0), then
422       // we are ready to draw.
423       return (this.chromeImages_.left.height > 0) &&
424              (this.chromeImages_.mid.height > 0) &&
425              (this.chromeImages_.right.height > 0);
426     },
427
428     get chromeQuad() {
429       if (this.chromeQuad_)
430         return this.chromeQuad_;
431
432       // Draw the chrome border into a separate canvas.
433       var chromeCanvas = document.createElement('canvas');
434       var offsetY = this.chromeImages_.left.height;
435
436       chromeCanvas.width = this.deviceRect_.width;
437       chromeCanvas.height = this.deviceRect_.height + offsetY;
438
439       var leftWidth = this.chromeImages_.left.width;
440       var midWidth = this.chromeImages_.mid.width;
441       var rightWidth = this.chromeImages_.right.width;
442
443       var chromeCtx = chromeCanvas.getContext('2d');
444       chromeCtx.drawImage(this.chromeImages_.left, 0, 0);
445
446       chromeCtx.save();
447       chromeCtx.translate(leftWidth, 0);
448
449       // Calculate the scale of the mid image.
450       var s = (this.deviceRect_.width - leftWidth - rightWidth) / midWidth;
451       chromeCtx.scale(s, 1);
452
453       chromeCtx.drawImage(this.chromeImages_.mid, 0, 0);
454       chromeCtx.restore();
455
456       chromeCtx.drawImage(
457           this.chromeImages_.right, leftWidth + s * midWidth, 0);
458
459       // Construct the quad.
460       var chromeRect = tvcm.Rect.fromXYWH(
461           this.deviceRect_.x,
462           this.deviceRect_.y - offsetY,
463           this.deviceRect_.width,
464           this.deviceRect_.height + offsetY);
465       var chromeQuad = tvcm.Quad.fromRect(chromeRect);
466       chromeQuad.stackingGroupId = this.maxStackingGroupId_ + 1;
467       chromeQuad.imageData = chromeCtx.getImageData(
468           0, 0, chromeCanvas.width, chromeCanvas.height);
469       chromeQuad.shadowOffset = [0, 0];
470       chromeQuad.shadowBlur = 5;
471       chromeQuad.borderWidth = 3;
472       this.chromeQuad_ = chromeQuad;
473       return this.chromeQuad_;
474     },
475
476     scheduleRender: function() {
477       if (this.redrawScheduled_)
478         return false;
479       this.redrawScheduled_ = true;
480       tvcm.requestAnimationFrame(this.render, this);
481     },
482
483     onRenderRequired_: function(e) {
484       this.scheduleRender();
485     },
486
487     stackTransformAndProcessQuads_: function(
488         numPasses, handleQuadFunc, includeChromeQuad, opt_arg1, opt_arg2) {
489       var mv = this.camera_.modelViewMatrix;
490       var p = this.camera_.projectionMatrix;
491
492       var viewport = tvcm.Rect.fromXYWH(
493           0, 0, this.canvas_.width, this.canvas_.height);
494
495       // Calculate the quad stacks.
496       var quadStacks = [];
497       for (var i = 0; i < this.quads_.length; ++i) {
498         var quad = this.quads_[i];
499         var stackingId = quad.stackingGroupId || 0;
500         while (stackingId >= quadStacks.length)
501           quadStacks.push([]);
502
503         quadStacks[stackingId].push(quad);
504       }
505
506       var mvp = mat4.create();
507       this.maxStackingGroupId_ = quadStacks.length;
508       var stackingDistance =
509           this.stackingDistance_ * this.camera_.stackingDistanceDampening;
510
511       // Draw the quad stacks, raising each subsequent level.
512       mat4.multiply(mvp, p, mv);
513       for (var i = 0; i < quadStacks.length; ++i) {
514         transformAndProcessQuads(mvp, viewport, quadStacks[i],
515                                  numPasses, handleQuadFunc,
516                                  opt_arg1, opt_arg2);
517
518         mat4.translate(mv, mv, [0, 0, stackingDistance]);
519         mat4.multiply(mvp, p, mv);
520       }
521
522       if (includeChromeQuad && this.deviceRect_) {
523         transformAndProcessQuads(mvp, viewport, [this.chromeQuad],
524                                  numPasses, drawProjectedQuadToContext,
525                                  opt_arg1, opt_arg2);
526       }
527     },
528
529     render: function() {
530       this.redrawScheduled_ = false;
531
532       if (!this.readyToDraw()) {
533         setTimeout(this.scheduleRender.bind(this),
534                    constants.IMAGE_LOAD_RETRY_TIME_MS);
535         return;
536       }
537
538       if (!this.quads_)
539         return;
540
541       var canvasCtx = this.canvas_.getContext('2d');
542       if (!this.resize())
543         canvasCtx.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
544
545       var quadCanvas = document.createElement('canvas');
546       this.stackTransformAndProcessQuads_(
547           3, drawProjectedQuadToContext, true,
548           canvasCtx, quadCanvas);
549       quadCanvas.width = 0; // Hack: Frees the quadCanvas' resources.
550     },
551
552     trackMouse_: function() {
553       this.mouseModeSelector_ = new tvcm.ui.MouseModeSelector(this);
554       this.mouseModeSelector_.supportedModeMask =
555           tvcm.ui.MOUSE_SELECTOR_MODE.SELECTION |
556           tvcm.ui.MOUSE_SELECTOR_MODE.PANSCAN |
557           tvcm.ui.MOUSE_SELECTOR_MODE.ZOOM |
558           tvcm.ui.MOUSE_SELECTOR_MODE.ROTATE;
559       this.mouseModeSelector_.mode = tvcm.ui.MOUSE_SELECTOR_MODE.PANSCAN;
560       this.mouseModeSelector_.pos = {x: 0, y: 100};
561       this.appendChild(this.mouseModeSelector_);
562       this.mouseModeSelector_.settingsKey =
563           'quadStackView.mouseModeSelector';
564
565       this.mouseModeSelector_.setModifierForAlternateMode(
566           tvcm.ui.MOUSE_SELECTOR_MODE.ROTATE, tvcm.ui.MODIFIER.SHIFT);
567       this.mouseModeSelector_.setModifierForAlternateMode(
568           tvcm.ui.MOUSE_SELECTOR_MODE.PANSCAN, tvcm.ui.MODIFIER.SPACE);
569       this.mouseModeSelector_.setModifierForAlternateMode(
570           tvcm.ui.MOUSE_SELECTOR_MODE.ZOOM, tvcm.ui.MODIFIER.CMD_OR_CTRL);
571
572       this.mouseModeSelector_.addEventListener('updateselection',
573           this.onSelectionUpdate_.bind(this));
574       this.mouseModeSelector_.addEventListener('endselection',
575           this.onSelectionUpdate_.bind(this));
576     },
577
578     extractRelativeMousePosition_: function(e) {
579       var br = this.canvas_.getBoundingClientRect();
580       return [
581         this.pixelRatio_ * (e.clientX - this.canvas_.offsetLeft - br.left),
582         this.pixelRatio_ * (e.clientY - this.canvas_.offsetTop - br.top)
583       ];
584     },
585
586     onSelectionUpdate_: function(e) {
587       var mousePos = this.extractRelativeMousePosition_(e);
588       var res = [];
589       function handleQuad(passNumber, quad, p1, p2, p3, p4) {
590         if (tvcm.pointInImplicitQuad(mousePos, p1, p2, p3, p4))
591           res.push(quad);
592       }
593       this.stackTransformAndProcessQuads_(1, handleQuad, false);
594       var e = new Event('selectionchange', false, false);
595       e.quads = res;
596       this.dispatchEvent(e);
597     }
598   };
599
600   return {
601     QuadStackView: QuadStackView
602   };
603 });