| // Copyright (c) 2012 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. |
| |
| 'use strict'; |
| |
| base.requireStylesheet('ui.quad_view'); |
| |
| base.require('base.color'); |
| base.require('base.events'); |
| base.require('base.raf'); |
| base.require('ui'); |
| base.require('ui.quad_view_viewport'); |
| |
| base.exportTo('ui', function() { |
| // FIXME(pdr): Remove this extra scaling so our rasters are pixel-perfect. |
| // https://code.google.com/p/trace-viewer/issues/detail?id=228 |
| // FIXME(jjb): simplify until we have the camera working (or 228 happens ;-) |
| var RASTER_SCALE = 1.0; // Adjust the resolution of our backing canvases. |
| |
| // Care of bckenney@ via |
| // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 |
| function drawTexturedTriangle( |
| ctx, |
| img, x0, y0, x1, y1, x2, y2, |
| u0, v0, u1, v1, u2, v2) { |
| |
| ctx.beginPath(); |
| ctx.moveTo(x0, y0); |
| ctx.lineTo(x1, y1); |
| ctx.lineTo(x2, y2); |
| ctx.closePath(); |
| |
| x1 -= x0; |
| y1 -= y0; |
| x2 -= x0; |
| y2 -= y0; |
| |
| u1 -= u0; |
| v1 -= v0; |
| u2 -= u0; |
| v2 -= v0; |
| |
| var det = 1 / (u1 * v2 - u2 * v1), |
| |
| // linear transformation |
| a = (v2 * x1 - v1 * x2) * det, |
| b = (v2 * y1 - v1 * y2) * det, |
| c = (u1 * x2 - u2 * x1) * det, |
| d = (u1 * y2 - u2 * y1) * det, |
| |
| // translation |
| e = x0 - a * u0 - c * v0, |
| f = y0 - b * u0 - d * v0; |
| |
| ctx.save(); |
| ctx.transform(a, b, c, d, e, f); |
| ctx.clip(); |
| ctx.drawImage(img, 0, 0); |
| ctx.restore(); |
| } |
| |
| var QuadView = ui.define('quad-view'); |
| |
| QuadView.prototype = { |
| __proto__: HTMLUnknownElement.prototype, |
| |
| decorate: function() { |
| base.EventTargetHelper.decorate(this); |
| |
| this.quads_ = undefined; |
| this.viewport_ = undefined; |
| this.canvas_ = document.createElement('canvas'); |
| |
| this.appendChild(this.canvas_); |
| |
| this.onViewportChanged_ = this.onViewportChanged_.bind(this); |
| |
| this.onMouseDown_ = this.onMouseDown_.bind(this); |
| this.onMouseMove_ = this.onMouseMove_.bind(this); |
| this.onMouseUp_ = this.onMouseUp_.bind(this); |
| this.canvas_.addEventListener('mousedown', this.onMouseDown_); |
| |
| this.canvas_.addEventListener('focus', this.redrawCanvas_.bind(this)); |
| this.canvas_.addEventListener('blur', this.redrawCanvas_.bind(this)); |
| this.canvas_.tabIndex = 0; |
| }, |
| |
| get viewport() { |
| return this.viewport_; |
| }, |
| |
| set viewport(viewport) { |
| if (this.viewport_) |
| this.viewport_.removeEventListener('change', this.onViewportChanged_); |
| this.viewport_ = viewport; |
| if (this.viewport_) |
| this.viewport_.addEventListener('change', this.onViewportChanged_); |
| this.updateChildren_(); |
| }, |
| |
| onViewportChanged_: function() { |
| if (!this.hasRequiredProprties_) |
| return; |
| this.redrawCanvas_(); |
| }, |
| |
| get quads() { |
| return this.quads_; |
| }, |
| |
| set quads(quads) { |
| this.quads_ = quads; |
| if (!this.quads_) { |
| this.updateChildren_(); |
| return; |
| } |
| this.viewport_ = this.viewport_ || |
| this.createViewportFromQuads_(this.quads_); |
| this.updateChildren_(); |
| }, |
| |
| get hasRequiredProprties_() { |
| return this.quads_ && |
| this.viewport_; |
| }, |
| |
| updateChildren_: function() { |
| var canvas = this.canvas_; |
| if (!this.hasRequiredProprties_) { |
| canvas.width = 0; |
| canvas.height = 0; |
| return; |
| } |
| |
| this.scheduleRedrawCanvas_(); |
| }, |
| |
| scheduleRedrawCanvas_: function() { |
| if (this.redrawScheduled_) |
| return false; |
| this.redrawScheduled_ = true; |
| base.requestAnimationFrameInThisFrameIfPossible( |
| this.redrawCanvas_, this); |
| }, |
| |
| redrawCanvas_: function() { |
| this.redrawScheduled_ = false; |
| |
| var resizedCanvas = this.viewport_.updateBoxSize(this.canvas_); |
| |
| var ctx = this.canvas_.getContext('2d'); |
| |
| var vp = this.viewport_; |
| |
| if (!resizedCanvas) // Canvas resizing automatically clears the context. |
| ctx.clearRect(0, 0, this.canvas_.width, this.canvas_.height); |
| |
| ctx.save(); |
| ctx.scale(ui.RASTER_SCALE, ui.RASTER_SCALE); |
| |
| // The quads are in the world coordinate system. We are drawing |
| // into a canvas with 0,0 in the top left corner. Tell the canvas to |
| // transform drawing ops from world to canvas coordinates. |
| vp.applyTransformToContext(ctx); |
| ctx.lineWidth = vp.getDeviceLineWidthAssumingTransformIsApplied(1.0); |
| |
| var quads = this.quads_ || []; |
| |
| // Background colors. |
| for (var i = 0; i < quads.length; i++) { |
| var quad = quads[i]; |
| if (quad.canvas) { |
| if (quad.isRectangle()) { |
| var bounds = quad.boundingRect(); |
| ctx.drawImage(quad.canvas, 0, 0, |
| quad.canvas.width, quad.canvas.height, |
| bounds.x, bounds.y, bounds.width, bounds.height); |
| } else { |
| ctx.save(); |
| var quadBBox = new base.BBox2(); |
| quadBBox.addQuad(quad); |
| var iw = quad.canvas.width; |
| var ih = quad.canvas.height; |
| drawTexturedTriangle( |
| ctx, quad.canvas, |
| quad.p1[0], quad.p1[1], |
| quad.p2[0], quad.p2[1], |
| quad.p4[0], quad.p4[1], |
| 0, 0, iw, 0, 0, ih); |
| drawTexturedTriangle( |
| ctx, quad.canvas, |
| quad.p2[0], quad.p2[1], |
| quad.p3[0], quad.p3[1], |
| quad.p4[0], quad.p4[1], |
| iw, 0, iw, ih, 0, ih); |
| ctx.restore(); |
| } |
| } |
| |
| if (quad.backgroundColor) { |
| ctx.fillStyle = quad.backgroundColor; |
| ctx.beginPath(); |
| ctx.moveTo(quad.p1[0], quad.p1[1]); |
| ctx.lineTo(quad.p2[0], quad.p2[1]); |
| ctx.lineTo(quad.p3[0], quad.p3[1]); |
| ctx.lineTo(quad.p4[0], quad.p4[1]); |
| ctx.closePath(); |
| ctx.fill(); |
| } |
| } |
| |
| // Outlines. |
| for (var i = 0; i < quads.length; i++) { |
| var quad = quads[i]; |
| ctx.beginPath(); |
| ctx.moveTo(quad.p1[0], quad.p1[1]); |
| ctx.lineTo(quad.p2[0], quad.p2[1]); |
| ctx.lineTo(quad.p3[0], quad.p3[1]); |
| ctx.lineTo(quad.p4[0], quad.p4[1]); |
| ctx.closePath(); |
| if (quad.borderColor) |
| ctx.strokeStyle = quad.borderColor; |
| else |
| ctx.strokeStyle = 'rgb(128,128,128)'; |
| ctx.stroke(); |
| } |
| |
| // Selection outlines. |
| ctx.lineWidth = vp.getDeviceLineWidthAssumingTransformIsApplied(8.0); |
| var rules = window.getMatchedCSSRules(this.canvas_); |
| |
| // TODO(nduca): Figure out how to get these from css. |
| for (var i = 0; i < quads.length; i++) { |
| var quad = quads[i]; |
| if (!quad.upperBorderColor) |
| continue; |
| if (document.activeElement == this.canvas_) { |
| var tmp = base.Color.fromString(quad.upperBorderColor).brighten(0.25); |
| ctx.strokeStyle = tmp.toString(); |
| } else { |
| ctx.strokeStyle = quad.upperBorderColor; |
| } |
| |
| ctx.beginPath(); |
| ctx.moveTo(quad.p1[0], quad.p1[1]); |
| ctx.lineTo(quad.p2[0], quad.p2[1]); |
| ctx.lineTo(quad.p3[0], quad.p3[1]); |
| ctx.lineTo(quad.p4[0], quad.p4[1]); |
| ctx.closePath(); |
| ctx.stroke(); |
| } |
| |
| ctx.restore(); |
| }, |
| |
| selectQuadsAtCanvasClientPoint: function(clientX, clientY) { |
| clientX *= ui.RASTER_SCALE; |
| clientY *= ui.RASTER_SCALE; |
| var selectedQuadIndices = this.findQuadsAtCanvasClientPoint( |
| clientX, clientY); |
| var e = new base.Event('selectionChanged'); |
| e.selectedQuadIndices = selectedQuadIndices; |
| this.dispatchEvent(e); |
| this.viewport_.forceRedrawAll(); |
| }, |
| |
| findQuadsAtCanvasClientPoint: function(clientX, clientY) { |
| var bounds = this.canvas_.getBoundingClientRect(); |
| var vecInLayout = vec2.createXY(clientX - bounds.left, |
| clientY - bounds.top); |
| var vecInWorldPixels = |
| this.viewport_.layoutPixelsToWorldPixels(vecInLayout); |
| |
| var quads = this.quads_; |
| var hitIndices = []; |
| for (var i = 0; i < quads.length; i++) { |
| var hit = quads[i].vecInside(vecInWorldPixels); |
| if (hit) |
| hitIndices.push(i); |
| } |
| return hitIndices; |
| }, |
| |
| createViewportFromQuads_: function() { |
| var quads = this.quads_ || []; |
| var quadBBox = new base.BBox2(); |
| quads.forEach(function(quad) { |
| quadBBox.addQuad(quad); |
| }); |
| return new ui.QuadViewViewport(quadBBox.asRect()); |
| }, |
| |
| onMouseDown_: function(e) { |
| if (!this.hasEventListener('selectionChanged')) |
| return; |
| this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY); |
| document.addEventListener('mousemove', this.onMouseMove_); |
| document.addEventListener('mouseup', this.onMouseUp_); |
| e.preventDefault(); |
| this.canvas_.focus(); |
| return true; |
| }, |
| |
| onMouseMove_: function(e) { |
| this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY); |
| }, |
| |
| onMouseUp_: function(e) { |
| this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY); |
| document.removeEventListener('mousemove', this.onMouseMove_); |
| document.removeEventListener('mouseup', this.onMouseUp_); |
| } |
| |
| }; |
| |
| return { |
| QuadView: QuadView, |
| RASTER_SCALE: RASTER_SCALE |
| }; |
| }); |