1 // Copyright (c) 2012 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 base.requireStylesheet('ui.quad_view'); 8 9 base.require('base.color'); 10 base.require('base.events'); 11 base.require('base.raf'); 12 base.require('ui'); 13 base.require('ui.quad_view_viewport'); 14 15 base.exportTo('ui', function() { 16 // FIXME(pdr): Remove this extra scaling so our rasters are pixel-perfect. 17 // https://code.google.com/p/trace-viewer/issues/detail?id=228 18 // FIXME(jjb): simplify until we have the camera working (or 228 happens ;-) 19 var RASTER_SCALE = 1.0; // Adjust the resolution of our backing canvases. 20 21 // Care of bckenney@ via 22 // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 23 function drawTexturedTriangle( 24 ctx, 25 img, x0, y0, x1, y1, x2, y2, 26 u0, v0, u1, v1, u2, v2) { 27 28 ctx.beginPath(); 29 ctx.moveTo(x0, y0); 30 ctx.lineTo(x1, y1); 31 ctx.lineTo(x2, y2); 32 ctx.closePath(); 33 34 x1 -= x0; 35 y1 -= y0; 36 x2 -= x0; 37 y2 -= y0; 38 39 u1 -= u0; 40 v1 -= v0; 41 u2 -= u0; 42 v2 -= v0; 43 44 var det = 1 / (u1 * v2 - u2 * v1), 45 46 // linear transformation 47 a = (v2 * x1 - v1 * x2) * det, 48 b = (v2 * y1 - v1 * y2) * det, 49 c = (u1 * x2 - u2 * x1) * det, 50 d = (u1 * y2 - u2 * y1) * det, 51 52 // translation 53 e = x0 - a * u0 - c * v0, 54 f = y0 - b * u0 - d * v0; 55 56 ctx.save(); 57 ctx.transform(a, b, c, d, e, f); 58 ctx.clip(); 59 ctx.drawImage(img, 0, 0); 60 ctx.restore(); 61 } 62 63 var QuadView = ui.define('quad-view'); 64 65 QuadView.prototype = { 66 __proto__: HTMLUnknownElement.prototype, 67 68 decorate: function() { 69 base.EventTargetHelper.decorate(this); 70 71 this.quads_ = undefined; 72 this.viewport_ = undefined; 73 this.canvas_ = document.createElement('canvas'); 74 75 this.appendChild(this.canvas_); 76 77 this.onViewportChanged_ = this.onViewportChanged_.bind(this); 78 79 this.onMouseDown_ = this.onMouseDown_.bind(this); 80 this.onMouseMove_ = this.onMouseMove_.bind(this); 81 this.onMouseUp_ = this.onMouseUp_.bind(this); 82 this.canvas_.addEventListener('mousedown', this.onMouseDown_); 83 84 this.canvas_.addEventListener('focus', this.redrawCanvas_.bind(this)); 85 this.canvas_.addEventListener('blur', this.redrawCanvas_.bind(this)); 86 this.canvas_.tabIndex = 0; 87 }, 88 89 get viewport() { 90 return this.viewport_; 91 }, 92 93 set viewport(viewport) { 94 if (this.viewport_) 95 this.viewport_.removeEventListener('change', this.onViewportChanged_); 96 this.viewport_ = viewport; 97 if (this.viewport_) 98 this.viewport_.addEventListener('change', this.onViewportChanged_); 99 this.updateChildren_(); 100 }, 101 102 onViewportChanged_: function() { 103 if (!this.hasRequiredProprties_) 104 return; 105 this.redrawCanvas_(); 106 }, 107 108 get quads() { 109 return this.quads_; 110 }, 111 112 set quads(quads) { 113 this.quads_ = quads; 114 if (!this.quads_) { 115 this.updateChildren_(); 116 return; 117 } 118 this.viewport_ = this.viewport_ || 119 this.createViewportFromQuads_(this.quads_); 120 this.updateChildren_(); 121 }, 122 123 get hasRequiredProprties_() { 124 return this.quads_ && 125 this.viewport_; 126 }, 127 128 updateChildren_: function() { 129 var canvas = this.canvas_; 130 if (!this.hasRequiredProprties_) { 131 canvas.width = 0; 132 canvas.height = 0; 133 return; 134 } 135 136 this.scheduleRedrawCanvas_(); 137 }, 138 139 scheduleRedrawCanvas_: function() { 140 if (this.redrawScheduled_) 141 return false; 142 this.redrawScheduled_ = true; 143 base.requestAnimationFrameInThisFrameIfPossible( 144 this.redrawCanvas_, this); 145 }, 146 147 redrawCanvas_: function() { 148 this.redrawScheduled_ = false; 149 150 var resizedCanvas = this.viewport_.updateBoxSize(this.canvas_); 151 152 var ctx = this.canvas_.getContext('2d'); 153 154 var vp = this.viewport_; 155 156 if (!resizedCanvas) // Canvas resizing automatically clears the context. 157 ctx.clearRect(0, 0, this.canvas_.width, this.canvas_.height); 158 159 ctx.save(); 160 ctx.scale(ui.RASTER_SCALE, ui.RASTER_SCALE); 161 162 // The quads are in the world coordinate system. We are drawing 163 // into a canvas with 0,0 in the top left corner. Tell the canvas to 164 // transform drawing ops from world to canvas coordinates. 165 vp.applyTransformToContext(ctx); 166 ctx.lineWidth = vp.getDeviceLineWidthAssumingTransformIsApplied(1.0); 167 168 var quads = this.quads_ || []; 169 170 // Background colors. 171 for (var i = 0; i < quads.length; i++) { 172 var quad = quads[i]; 173 if (quad.canvas) { 174 if (quad.isRectangle()) { 175 var bounds = quad.boundingRect(); 176 ctx.drawImage(quad.canvas, 0, 0, 177 quad.canvas.width, quad.canvas.height, 178 bounds.x, bounds.y, bounds.width, bounds.height); 179 } else { 180 ctx.save(); 181 var quadBBox = new base.BBox2(); 182 quadBBox.addQuad(quad); 183 var iw = quad.canvas.width; 184 var ih = quad.canvas.height; 185 drawTexturedTriangle( 186 ctx, quad.canvas, 187 quad.p1[0], quad.p1[1], 188 quad.p2[0], quad.p2[1], 189 quad.p4[0], quad.p4[1], 190 0, 0, iw, 0, 0, ih); 191 drawTexturedTriangle( 192 ctx, quad.canvas, 193 quad.p2[0], quad.p2[1], 194 quad.p3[0], quad.p3[1], 195 quad.p4[0], quad.p4[1], 196 iw, 0, iw, ih, 0, ih); 197 ctx.restore(); 198 } 199 } 200 201 if (quad.backgroundColor) { 202 ctx.fillStyle = quad.backgroundColor; 203 ctx.beginPath(); 204 ctx.moveTo(quad.p1[0], quad.p1[1]); 205 ctx.lineTo(quad.p2[0], quad.p2[1]); 206 ctx.lineTo(quad.p3[0], quad.p3[1]); 207 ctx.lineTo(quad.p4[0], quad.p4[1]); 208 ctx.closePath(); 209 ctx.fill(); 210 } 211 } 212 213 // Outlines. 214 for (var i = 0; i < quads.length; i++) { 215 var quad = quads[i]; 216 ctx.beginPath(); 217 ctx.moveTo(quad.p1[0], quad.p1[1]); 218 ctx.lineTo(quad.p2[0], quad.p2[1]); 219 ctx.lineTo(quad.p3[0], quad.p3[1]); 220 ctx.lineTo(quad.p4[0], quad.p4[1]); 221 ctx.closePath(); 222 if (quad.borderColor) 223 ctx.strokeStyle = quad.borderColor; 224 else 225 ctx.strokeStyle = 'rgb(128,128,128)'; 226 ctx.stroke(); 227 } 228 229 // Selection outlines. 230 ctx.lineWidth = vp.getDeviceLineWidthAssumingTransformIsApplied(8.0); 231 var rules = window.getMatchedCSSRules(this.canvas_); 232 233 // TODO(nduca): Figure out how to get these from css. 234 for (var i = 0; i < quads.length; i++) { 235 var quad = quads[i]; 236 if (!quad.upperBorderColor) 237 continue; 238 if (document.activeElement == this.canvas_) { 239 var tmp = base.Color.fromString(quad.upperBorderColor).brighten(0.25); 240 ctx.strokeStyle = tmp.toString(); 241 } else { 242 ctx.strokeStyle = quad.upperBorderColor; 243 } 244 245 ctx.beginPath(); 246 ctx.moveTo(quad.p1[0], quad.p1[1]); 247 ctx.lineTo(quad.p2[0], quad.p2[1]); 248 ctx.lineTo(quad.p3[0], quad.p3[1]); 249 ctx.lineTo(quad.p4[0], quad.p4[1]); 250 ctx.closePath(); 251 ctx.stroke(); 252 } 253 254 ctx.restore(); 255 }, 256 257 selectQuadsAtCanvasClientPoint: function(clientX, clientY) { 258 clientX *= ui.RASTER_SCALE; 259 clientY *= ui.RASTER_SCALE; 260 var selectedQuadIndices = this.findQuadsAtCanvasClientPoint( 261 clientX, clientY); 262 var e = new base.Event('selectionChanged'); 263 e.selectedQuadIndices = selectedQuadIndices; 264 this.dispatchEvent(e); 265 this.viewport_.forceRedrawAll(); 266 }, 267 268 findQuadsAtCanvasClientPoint: function(clientX, clientY) { 269 var bounds = this.canvas_.getBoundingClientRect(); 270 var vecInLayout = vec2.createXY(clientX - bounds.left, 271 clientY - bounds.top); 272 var vecInWorldPixels = 273 this.viewport_.layoutPixelsToWorldPixels(vecInLayout); 274 275 var quads = this.quads_; 276 var hitIndices = []; 277 for (var i = 0; i < quads.length; i++) { 278 var hit = quads[i].vecInside(vecInWorldPixels); 279 if (hit) 280 hitIndices.push(i); 281 } 282 return hitIndices; 283 }, 284 285 createViewportFromQuads_: function() { 286 var quads = this.quads_ || []; 287 var quadBBox = new base.BBox2(); 288 quads.forEach(function(quad) { 289 quadBBox.addQuad(quad); 290 }); 291 return new ui.QuadViewViewport(quadBBox.asRect()); 292 }, 293 294 onMouseDown_: function(e) { 295 if (!this.hasEventListener('selectionChanged')) 296 return; 297 this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY); 298 document.addEventListener('mousemove', this.onMouseMove_); 299 document.addEventListener('mouseup', this.onMouseUp_); 300 e.preventDefault(); 301 this.canvas_.focus(); 302 return true; 303 }, 304 305 onMouseMove_: function(e) { 306 this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY); 307 }, 308 309 onMouseUp_: function(e) { 310 this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY); 311 document.removeEventListener('mousemove', this.onMouseMove_); 312 document.removeEventListener('mouseup', this.onMouseUp_); 313 } 314 315 }; 316 317 return { 318 QuadView: QuadView, 319 RASTER_SCALE: RASTER_SCALE 320 }; 321 }); 322