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