Home | History | Annotate | Download | only in ui
      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