| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| <link rel="import" href="/base/bbox2.html"> |
| <link rel="import" href="/base/gl_matrix.html"> |
| <link rel="import" href="/base/quad.html"> |
| <link rel="import" href="/base/raf.html"> |
| <link rel="import" href="/base/rect.html"> |
| <link rel="import" href="/base/settings.html"> |
| <link rel="import" href="/base/ui/camera.html"> |
| <link rel="import" href="/base/ui/mouse_mode_selector.html"> |
| <link rel="import" href="/base/ui/mouse_tracker.html"> |
| |
| <style> |
| * /deep/ quad-stack-view { |
| display: block; |
| float: left; |
| height: 100%; |
| overflow: hidden; |
| position: relative; /* For the absolute positioned mouse-mode-selector */ |
| width: 100%; |
| } |
| |
| * /deep/ quad-stack-view > #header { |
| position: absolute; |
| font-size: 70%; |
| top: 10px; |
| left: 10px; |
| width: 800px; |
| } |
| * /deep/ quad-stack-view > #stacking-distance-slider { |
| position: absolute; |
| font-size: 70%; |
| top: 10px; |
| right: 10px; |
| } |
| |
| * /deep/ quad-stack-view > #chrome-left { |
| content: url('../images/chrome-left.png'); |
| display: none; |
| } |
| |
| * /deep/ quad-stack-view > #chrome-mid { |
| content: url('../images/chrome-mid.png'); |
| display: none; |
| } |
| |
| * /deep/ quad-stack-view > #chrome-right { |
| content: url('../images/chrome-right.png'); |
| display: none; |
| } |
| </style> |
| |
| <template id='quad-stack-view-template'> |
| <div id="header"></div> |
| <input id="stacking-distance-slider" type="range" min=1 max=400 step=1> |
| </input> |
| <canvas id='canvas'></canvas> |
| <img id='chrome-left'/> |
| <img id='chrome-mid'/> |
| <img id='chrome-right'/> |
| </template> |
| |
| <script> |
| 'use strict'; |
| |
| /** |
| * @fileoverview QuadStackView controls the content and viewing angle a |
| * QuadStack. |
| */ |
| tv.exportTo('tv.b.ui', function() { |
| var THIS_DOC = document.currentScript.ownerDocument; |
| |
| var constants = {}; |
| constants.IMAGE_LOAD_RETRY_TIME_MS = 500; |
| constants.SUBDIVISION_MINIMUM = 1; |
| constants.SUBDIVISION_RECURSION_DEPTH = 3; |
| constants.SUBDIVISION_DEPTH_THRESHOLD = 100; |
| constants.FAR_PLANE_DISTANCE = 10000; |
| |
| // Care of bckenney@ via |
| // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 |
| function drawTexturedTriangle(ctx, img, p0, p1, p2, t0, t1, t2) { |
| var tmp_p0 = [p0[0], p0[1]]; |
| var tmp_p1 = [p1[0], p1[1]]; |
| var tmp_p2 = [p2[0], p2[1]]; |
| var tmp_t0 = [t0[0], t0[1]]; |
| var tmp_t1 = [t1[0], t1[1]]; |
| var tmp_t2 = [t2[0], t2[1]]; |
| |
| ctx.beginPath(); |
| ctx.moveTo(tmp_p0[0], tmp_p0[1]); |
| ctx.lineTo(tmp_p1[0], tmp_p1[1]); |
| ctx.lineTo(tmp_p2[0], tmp_p2[1]); |
| ctx.closePath(); |
| |
| tmp_p1[0] -= tmp_p0[0]; |
| tmp_p1[1] -= tmp_p0[1]; |
| tmp_p2[0] -= tmp_p0[0]; |
| tmp_p2[1] -= tmp_p0[1]; |
| |
| tmp_t1[0] -= tmp_t0[0]; |
| tmp_t1[1] -= tmp_t0[1]; |
| tmp_t2[0] -= tmp_t0[0]; |
| tmp_t2[1] -= tmp_t0[1]; |
| |
| var det = 1 / (tmp_t1[0] * tmp_t2[1] - tmp_t2[0] * tmp_t1[1]), |
| |
| // linear transformation |
| a = (tmp_t2[1] * tmp_p1[0] - tmp_t1[1] * tmp_p2[0]) * det, |
| b = (tmp_t2[1] * tmp_p1[1] - tmp_t1[1] * tmp_p2[1]) * det, |
| c = (tmp_t1[0] * tmp_p2[0] - tmp_t2[0] * tmp_p1[0]) * det, |
| d = (tmp_t1[0] * tmp_p2[1] - tmp_t2[0] * tmp_p1[1]) * det, |
| |
| // translation |
| e = tmp_p0[0] - a * tmp_t0[0] - c * tmp_t0[1], |
| f = tmp_p0[1] - b * tmp_t0[0] - d * tmp_t0[1]; |
| |
| ctx.save(); |
| ctx.transform(a, b, c, d, e, f); |
| ctx.clip(); |
| ctx.drawImage(img, 0, 0); |
| ctx.restore(); |
| } |
| |
| function drawTriangleSub( |
| ctx, img, p0, p1, p2, t0, t1, t2, opt_recursion_depth) { |
| var depth = opt_recursion_depth || 0; |
| |
| // We may subdivide if we are not at the limit of recursion. |
| var subdivisionIndex = 0; |
| if (depth < constants.SUBDIVISION_MINIMUM) { |
| subdivisionIndex = 7; |
| } else if (depth < constants.SUBDIVISION_RECURSION_DEPTH) { |
| if (Math.abs(p0[2] - p1[2]) > constants.SUBDIVISION_DEPTH_THRESHOLD) |
| subdivisionIndex += 1; |
| if (Math.abs(p0[2] - p2[2]) > constants.SUBDIVISION_DEPTH_THRESHOLD) |
| subdivisionIndex += 2; |
| if (Math.abs(p1[2] - p2[2]) > constants.SUBDIVISION_DEPTH_THRESHOLD) |
| subdivisionIndex += 4; |
| } |
| |
| // These need to be created every time, since temporaries |
| // outside of the scope will be rewritten in recursion. |
| var p01 = vec4.create(); |
| var p02 = vec4.create(); |
| var p12 = vec4.create(); |
| var t01 = vec2.create(); |
| var t02 = vec2.create(); |
| var t12 = vec2.create(); |
| |
| // Calculate the position before w-divide. |
| for (var i = 0; i < 2; ++i) { |
| p0[i] *= p0[2]; |
| p1[i] *= p1[2]; |
| p2[i] *= p2[2]; |
| } |
| |
| // Interpolate the 3d position. |
| for (var i = 0; i < 4; ++i) { |
| p01[i] = (p0[i] + p1[i]) / 2; |
| p02[i] = (p0[i] + p2[i]) / 2; |
| p12[i] = (p1[i] + p2[i]) / 2; |
| } |
| |
| // Re-apply w-divide to the original points and the interpolated ones. |
| for (var i = 0; i < 2; ++i) { |
| p0[i] /= p0[2]; |
| p1[i] /= p1[2]; |
| p2[i] /= p2[2]; |
| |
| p01[i] /= p01[2]; |
| p02[i] /= p02[2]; |
| p12[i] /= p12[2]; |
| } |
| |
| // Interpolate the texture coordinates. |
| for (var i = 0; i < 2; ++i) { |
| t01[i] = (t0[i] + t1[i]) / 2; |
| t02[i] = (t0[i] + t2[i]) / 2; |
| t12[i] = (t1[i] + t2[i]) / 2; |
| } |
| |
| // Based on the index, we subdivide the triangle differently. |
| // Assuming the triangle is p0, p1, p2 and points between i j |
| // are represented as pij (that is, a point between p2 and p0 |
| // is p02, etc), then the new triangles are defined by |
| // the 3rd 4th and 5th arguments into the function. |
| switch (subdivisionIndex) { |
| case 1: |
| drawTriangleSub(ctx, img, p0, p01, p2, t0, t01, t2, depth + 1); |
| drawTriangleSub(ctx, img, p01, p1, p2, t01, t1, t2, depth + 1); |
| break; |
| case 2: |
| drawTriangleSub(ctx, img, p0, p1, p02, t0, t1, t02, depth + 1); |
| drawTriangleSub(ctx, img, p1, p02, p2, t1, t02, t2, depth + 1); |
| break; |
| case 3: |
| drawTriangleSub(ctx, img, p0, p01, p02, t0, t01, t02, depth + 1); |
| drawTriangleSub(ctx, img, p02, p01, p2, t02, t01, t2, depth + 1); |
| drawTriangleSub(ctx, img, p01, p1, p2, t01, t1, t2, depth + 1); |
| break; |
| case 4: |
| drawTriangleSub(ctx, img, p0, p12, p2, t0, t12, t2, depth + 1); |
| drawTriangleSub(ctx, img, p0, p1, p12, t0, t1, t12, depth + 1); |
| break; |
| case 5: |
| drawTriangleSub(ctx, img, p0, p01, p2, t0, t01, t2, depth + 1); |
| drawTriangleSub(ctx, img, p2, p01, p12, t2, t01, t12, depth + 1); |
| drawTriangleSub(ctx, img, p01, p1, p12, t01, t1, t12, depth + 1); |
| break; |
| case 6: |
| drawTriangleSub(ctx, img, p0, p12, p02, t0, t12, t02, depth + 1); |
| drawTriangleSub(ctx, img, p0, p1, p12, t0, t1, t12, depth + 1); |
| drawTriangleSub(ctx, img, p02, p12, p2, t02, t12, t2, depth + 1); |
| break; |
| case 7: |
| drawTriangleSub(ctx, img, p0, p01, p02, t0, t01, t02, depth + 1); |
| drawTriangleSub(ctx, img, p01, p12, p02, t01, t12, t02, depth + 1); |
| drawTriangleSub(ctx, img, p01, p1, p12, t01, t1, t12, depth + 1); |
| drawTriangleSub(ctx, img, p02, p12, p2, t02, t12, t2, depth + 1); |
| break; |
| default: |
| // In the 0 case and all other cases, we simply draw the triangle. |
| drawTexturedTriangle(ctx, img, p0, p1, p2, t0, t1, t2); |
| break; |
| } |
| } |
| |
| // Created to avoid creating garbage when doing bulk transforms. |
| var tmp_vec4 = vec4.create(); |
| function transform(transformed, point, matrix, viewport) { |
| vec4.set(tmp_vec4, point[0], point[1], 0, 1); |
| vec4.transformMat4(tmp_vec4, tmp_vec4, matrix); |
| |
| var w = tmp_vec4[3]; |
| if (w < 1e-6) w = 1e-6; |
| |
| transformed[0] = ((tmp_vec4[0] / w) + 1) * viewport.width / 2; |
| transformed[1] = ((tmp_vec4[1] / w) + 1) * viewport.height / 2; |
| transformed[2] = w; |
| } |
| |
| function drawProjectedQuadBackgroundToContext( |
| quad, p1, p2, p3, p4, ctx, quadCanvas) { |
| if (quad.imageData) { |
| quadCanvas.width = quad.imageData.width; |
| quadCanvas.height = quad.imageData.height; |
| quadCanvas.getContext('2d').putImageData(quad.imageData, 0, 0); |
| var quadBBox = new tv.b.BBox2(); |
| quadBBox.addQuad(quad); |
| var iw = quadCanvas.width; |
| var ih = quadCanvas.height; |
| drawTriangleSub( |
| ctx, quadCanvas, |
| p1, p2, p4, |
| [0, 0], [iw, 0], [0, ih]); |
| drawTriangleSub( |
| ctx, quadCanvas, |
| p2, p3, p4, |
| [iw, 0], [iw, ih], [0, ih]); |
| } |
| |
| if (quad.backgroundColor) { |
| ctx.fillStyle = quad.backgroundColor; |
| ctx.beginPath(); |
| ctx.moveTo(p1[0], p1[1]); |
| ctx.lineTo(p2[0], p2[1]); |
| ctx.lineTo(p3[0], p3[1]); |
| ctx.lineTo(p4[0], p4[1]); |
| ctx.closePath(); |
| ctx.fill(); |
| } |
| } |
| |
| function drawProjectedQuadOutlineToContext( |
| quad, p1, p2, p3, p4, ctx, quadCanvas) { |
| ctx.beginPath(); |
| ctx.moveTo(p1[0], p1[1]); |
| ctx.lineTo(p2[0], p2[1]); |
| ctx.lineTo(p3[0], p3[1]); |
| ctx.lineTo(p4[0], p4[1]); |
| ctx.closePath(); |
| ctx.save(); |
| if (quad.borderColor) |
| ctx.strokeStyle = quad.borderColor; |
| else |
| ctx.strokeStyle = 'rgb(128,128,128)'; |
| |
| if (quad.shadowOffset) { |
| ctx.shadowColor = 'rgb(0, 0, 0)'; |
| ctx.shadowOffsetX = quad.shadowOffset[0]; |
| ctx.shadowOffsetY = quad.shadowOffset[1]; |
| if (quad.shadowBlur) |
| ctx.shadowBlur = quad.shadowBlur; |
| } |
| |
| if (quad.borderWidth) |
| ctx.lineWidth = quad.borderWidth; |
| else |
| ctx.lineWidth = 1; |
| |
| ctx.stroke(); |
| ctx.restore(); |
| } |
| |
| function drawProjectedQuadSelectionOutlineToContext( |
| quad, p1, p2, p3, p4, ctx, quadCanvas) { |
| if (!quad.upperBorderColor) |
| return; |
| |
| ctx.lineWidth = 8; |
| ctx.strokeStyle = quad.upperBorderColor; |
| |
| ctx.beginPath(); |
| ctx.moveTo(p1[0], p1[1]); |
| ctx.lineTo(p2[0], p2[1]); |
| ctx.lineTo(p3[0], p3[1]); |
| ctx.lineTo(p4[0], p4[1]); |
| ctx.closePath(); |
| ctx.stroke(); |
| } |
| |
| function drawProjectedQuadToContext( |
| passNumber, quad, p1, p2, p3, p4, ctx, quadCanvas) { |
| if (passNumber === 0) { |
| drawProjectedQuadBackgroundToContext( |
| quad, p1, p2, p3, p4, ctx, quadCanvas); |
| } else if (passNumber === 1) { |
| drawProjectedQuadOutlineToContext( |
| quad, p1, p2, p3, p4, ctx, quadCanvas); |
| } else if (passNumber === 2) { |
| drawProjectedQuadSelectionOutlineToContext( |
| quad, p1, p2, p3, p4, ctx, quadCanvas); |
| } else { |
| throw new Error('Invalid pass number'); |
| } |
| } |
| |
| var tmp_p1 = vec3.create(); |
| var tmp_p2 = vec3.create(); |
| var tmp_p3 = vec3.create(); |
| var tmp_p4 = vec3.create(); |
| function transformAndProcessQuads( |
| matrix, viewport, quads, numPasses, handleQuadFunc, opt_arg1, opt_arg2) { |
| |
| for (var passNumber = 0; passNumber < numPasses; passNumber++) { |
| for (var i = 0; i < quads.length; i++) { |
| var quad = quads[i]; |
| transform(tmp_p1, quad.p1, matrix, viewport); |
| transform(tmp_p2, quad.p2, matrix, viewport); |
| transform(tmp_p3, quad.p3, matrix, viewport); |
| transform(tmp_p4, quad.p4, matrix, viewport); |
| handleQuadFunc(passNumber, quad, |
| tmp_p1, tmp_p2, tmp_p3, tmp_p4, |
| opt_arg1, opt_arg2); |
| } |
| } |
| } |
| |
| /** |
| * @constructor |
| */ |
| var QuadStackView = tv.b.ui.define('quad-stack-view'); |
| |
| QuadStackView.prototype = { |
| __proto__: HTMLUnknownElement.prototype, |
| |
| decorate: function() { |
| this.className = 'quad-stack-view'; |
| |
| var node = tv.b.instantiateTemplate('#quad-stack-view-template', |
| THIS_DOC); |
| this.appendChild(node); |
| this.updateHeaderVisibility_(); |
| this.canvas_ = this.querySelector('#canvas'); |
| this.chromeImages_ = { |
| left: this.querySelector('#chrome-left'), |
| mid: this.querySelector('#chrome-mid'), |
| right: this.querySelector('#chrome-right') |
| }; |
| |
| var stackingDistanceSlider = this.querySelector( |
| '#stacking-distance-slider'); |
| stackingDistanceSlider.value = tv.b.Settings.get( |
| 'quadStackView.stackingDistance', 45); |
| stackingDistanceSlider.addEventListener( |
| 'change', this.onStackingDistanceChange_.bind(this)); |
| stackingDistanceSlider.addEventListener( |
| 'input', this.onStackingDistanceChange_.bind(this)); |
| |
| this.trackMouse_(); |
| |
| this.camera_ = new tv.b.ui.Camera(this.mouseModeSelector_); |
| this.camera_.addEventListener('renderrequired', |
| this.onRenderRequired_.bind(this)); |
| this.cameraWasReset_ = false; |
| this.camera_.canvas = this.canvas_; |
| |
| this.viewportRect_ = tv.b.Rect.fromXYWH(0, 0, 0, 0); |
| |
| this.pixelRatio_ = window.devicePixelRatio || 1; |
| }, |
| |
| updateHeaderVisibility_: function() { |
| if (this.headerText) |
| this.querySelector('#header').style.display = ''; |
| else |
| this.querySelector('#header').style.display = 'none'; |
| }, |
| |
| get headerText() { |
| return this.querySelector('#header').textContent; |
| }, |
| |
| set headerText(headerText) { |
| this.querySelector('#header').textContent = headerText; |
| this.updateHeaderVisibility_(); |
| }, |
| |
| onStackingDistanceChange_: function(e) { |
| tv.b.Settings.set('quadStackView.stackingDistance', |
| this.stackingDistance); |
| this.scheduleRender(); |
| e.stopPropagation(); |
| }, |
| |
| get stackingDistance() { |
| return this.querySelector('#stacking-distance-slider').value; |
| }, |
| |
| get mouseModeSelector() { |
| return this.mouseModeSelector_; |
| }, |
| |
| get camera() { |
| return this.camera_; |
| }, |
| |
| set quads(q) { |
| this.quads_ = q; |
| this.scheduleRender(); |
| }, |
| |
| set deviceRect(rect) { |
| if (!rect || rect.equalTo(this.deviceRect_)) |
| return; |
| |
| this.deviceRect_ = rect; |
| this.camera_.deviceRect = rect; |
| this.chromeQuad_ = undefined; |
| }, |
| |
| resize: function() { |
| if (!this.offsetParent) |
| return true; |
| |
| var width = parseInt(window.getComputedStyle(this.offsetParent).width); |
| var height = parseInt(window.getComputedStyle(this.offsetParent).height); |
| var rect = tv.b.Rect.fromXYWH(0, 0, width, height); |
| |
| if (rect.equalTo(this.viewportRect_)) |
| return false; |
| |
| this.viewportRect_ = rect; |
| this.style.width = width + 'px'; |
| this.style.height = height + 'px'; |
| this.canvas_.style.width = width + 'px'; |
| this.canvas_.style.height = height + 'px'; |
| this.canvas_.width = this.pixelRatio_ * width; |
| this.canvas_.height = this.pixelRatio_ * height; |
| if (!this.cameraWasReset_) { |
| this.camera_.resetCamera(); |
| this.cameraWasReset_ = true; |
| } |
| return true; |
| }, |
| |
| readyToDraw: function() { |
| // If src isn't set yet, set it to ensure we can use |
| // the image to draw onto a canvas. |
| if (!this.chromeImages_.left.src) { |
| var leftContent = |
| window.getComputedStyle(this.chromeImages_.left).content; |
| leftContent = leftContent.replace(/url\((.*)\)/, '$1'); |
| |
| var midContent = |
| window.getComputedStyle(this.chromeImages_.mid).content; |
| midContent = midContent.replace(/url\((.*)\)/, '$1'); |
| |
| var rightContent = |
| window.getComputedStyle(this.chromeImages_.right).content; |
| rightContent = rightContent.replace(/url\((.*)\)/, '$1'); |
| |
| this.chromeImages_.left.src = leftContent; |
| this.chromeImages_.mid.src = midContent; |
| this.chromeImages_.right.src = rightContent; |
| } |
| |
| // If all of the images are loaded (height > 0), then |
| // we are ready to draw. |
| return (this.chromeImages_.left.height > 0) && |
| (this.chromeImages_.mid.height > 0) && |
| (this.chromeImages_.right.height > 0); |
| }, |
| |
| get chromeQuad() { |
| if (this.chromeQuad_) |
| return this.chromeQuad_; |
| |
| // Draw the chrome border into a separate canvas. |
| var chromeCanvas = document.createElement('canvas'); |
| var offsetY = this.chromeImages_.left.height; |
| |
| chromeCanvas.width = this.deviceRect_.width; |
| chromeCanvas.height = this.deviceRect_.height + offsetY; |
| |
| var leftWidth = this.chromeImages_.left.width; |
| var midWidth = this.chromeImages_.mid.width; |
| var rightWidth = this.chromeImages_.right.width; |
| |
| var chromeCtx = chromeCanvas.getContext('2d'); |
| chromeCtx.drawImage(this.chromeImages_.left, 0, 0); |
| |
| chromeCtx.save(); |
| chromeCtx.translate(leftWidth, 0); |
| |
| // Calculate the scale of the mid image. |
| var s = (this.deviceRect_.width - leftWidth - rightWidth) / midWidth; |
| chromeCtx.scale(s, 1); |
| |
| chromeCtx.drawImage(this.chromeImages_.mid, 0, 0); |
| chromeCtx.restore(); |
| |
| chromeCtx.drawImage( |
| this.chromeImages_.right, leftWidth + s * midWidth, 0); |
| |
| // Construct the quad. |
| var chromeRect = tv.b.Rect.fromXYWH( |
| this.deviceRect_.x, |
| this.deviceRect_.y - offsetY, |
| this.deviceRect_.width, |
| this.deviceRect_.height + offsetY); |
| var chromeQuad = tv.b.Quad.fromRect(chromeRect); |
| chromeQuad.stackingGroupId = this.maxStackingGroupId_ + 1; |
| chromeQuad.imageData = chromeCtx.getImageData( |
| 0, 0, chromeCanvas.width, chromeCanvas.height); |
| chromeQuad.shadowOffset = [0, 0]; |
| chromeQuad.shadowBlur = 5; |
| chromeQuad.borderWidth = 3; |
| this.chromeQuad_ = chromeQuad; |
| return this.chromeQuad_; |
| }, |
| |
| scheduleRender: function() { |
| if (this.redrawScheduled_) |
| return false; |
| this.redrawScheduled_ = true; |
| tv.b.requestAnimationFrame(this.render, this); |
| }, |
| |
| onRenderRequired_: function(e) { |
| this.scheduleRender(); |
| }, |
| |
| stackTransformAndProcessQuads_: function( |
| numPasses, handleQuadFunc, includeChromeQuad, opt_arg1, opt_arg2) { |
| var mv = this.camera_.modelViewMatrix; |
| var p = this.camera_.projectionMatrix; |
| |
| var viewport = tv.b.Rect.fromXYWH( |
| 0, 0, this.canvas_.width, this.canvas_.height); |
| |
| // Calculate the quad stacks. |
| var quadStacks = []; |
| for (var i = 0; i < this.quads_.length; ++i) { |
| var quad = this.quads_[i]; |
| var stackingId = quad.stackingGroupId || 0; |
| while (stackingId >= quadStacks.length) |
| quadStacks.push([]); |
| |
| quadStacks[stackingId].push(quad); |
| } |
| |
| var mvp = mat4.create(); |
| this.maxStackingGroupId_ = quadStacks.length; |
| var effectiveStackingDistance = |
| this.stackingDistance * this.camera_.stackingDistanceDampening; |
| |
| // Draw the quad stacks, raising each subsequent level. |
| mat4.multiply(mvp, p, mv); |
| for (var i = 0; i < quadStacks.length; ++i) { |
| transformAndProcessQuads(mvp, viewport, quadStacks[i], |
| numPasses, handleQuadFunc, |
| opt_arg1, opt_arg2); |
| |
| mat4.translate(mv, mv, [0, 0, effectiveStackingDistance]); |
| mat4.multiply(mvp, p, mv); |
| } |
| |
| if (includeChromeQuad && this.deviceRect_) { |
| transformAndProcessQuads(mvp, viewport, [this.chromeQuad], |
| numPasses, drawProjectedQuadToContext, |
| opt_arg1, opt_arg2); |
| } |
| }, |
| |
| render: function() { |
| this.redrawScheduled_ = false; |
| |
| if (!this.readyToDraw()) { |
| setTimeout(this.scheduleRender.bind(this), |
| constants.IMAGE_LOAD_RETRY_TIME_MS); |
| return; |
| } |
| |
| if (!this.quads_) |
| return; |
| |
| var canvasCtx = this.canvas_.getContext('2d'); |
| if (!this.resize()) |
| canvasCtx.clearRect(0, 0, this.canvas_.width, this.canvas_.height); |
| |
| var quadCanvas = document.createElement('canvas'); |
| this.stackTransformAndProcessQuads_( |
| 3, drawProjectedQuadToContext, true, |
| canvasCtx, quadCanvas); |
| quadCanvas.width = 0; // Hack: Frees the quadCanvas' resources. |
| }, |
| |
| trackMouse_: function() { |
| this.mouseModeSelector_ = new tv.b.ui.MouseModeSelector(this.canvas_); |
| this.mouseModeSelector_.supportedModeMask = |
| tv.b.ui.MOUSE_SELECTOR_MODE.SELECTION | |
| tv.b.ui.MOUSE_SELECTOR_MODE.PANSCAN | |
| tv.b.ui.MOUSE_SELECTOR_MODE.ZOOM | |
| tv.b.ui.MOUSE_SELECTOR_MODE.ROTATE; |
| this.mouseModeSelector_.mode = tv.b.ui.MOUSE_SELECTOR_MODE.PANSCAN; |
| this.mouseModeSelector_.pos = {x: 0, y: 100}; |
| this.appendChild(this.mouseModeSelector_); |
| this.mouseModeSelector_.settingsKey = |
| 'quadStackView.mouseModeSelector'; |
| |
| this.mouseModeSelector_.setModifierForAlternateMode( |
| tv.b.ui.MOUSE_SELECTOR_MODE.ROTATE, tv.b.ui.MODIFIER.SHIFT); |
| this.mouseModeSelector_.setModifierForAlternateMode( |
| tv.b.ui.MOUSE_SELECTOR_MODE.PANSCAN, tv.b.ui.MODIFIER.SPACE); |
| this.mouseModeSelector_.setModifierForAlternateMode( |
| tv.b.ui.MOUSE_SELECTOR_MODE.ZOOM, tv.b.ui.MODIFIER.CMD_OR_CTRL); |
| |
| this.mouseModeSelector_.addEventListener('updateselection', |
| this.onSelectionUpdate_.bind(this)); |
| this.mouseModeSelector_.addEventListener('endselection', |
| this.onSelectionUpdate_.bind(this)); |
| }, |
| |
| extractRelativeMousePosition_: function(e) { |
| var br = this.canvas_.getBoundingClientRect(); |
| return [ |
| this.pixelRatio_ * (e.clientX - this.canvas_.offsetLeft - br.left), |
| this.pixelRatio_ * (e.clientY - this.canvas_.offsetTop - br.top) |
| ]; |
| }, |
| |
| onSelectionUpdate_: function(e) { |
| var mousePos = this.extractRelativeMousePosition_(e); |
| var res = []; |
| function handleQuad(passNumber, quad, p1, p2, p3, p4) { |
| if (tv.b.pointInImplicitQuad(mousePos, p1, p2, p3, p4)) |
| res.push(quad); |
| } |
| this.stackTransformAndProcessQuads_(1, handleQuad, false); |
| var e = new Event('selectionchange', false, false); |
| e.quads = res; |
| this.dispatchEvent(e); |
| } |
| }; |
| |
| return { |
| QuadStackView: QuadStackView |
| }; |
| }); |
| </script> |