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
7base.requireStylesheet('ui.quad_view');
8
9base.require('base.color');
10base.require('base.events');
11base.require('base.raf');
12base.require('ui');
13base.require('ui.quad_view_viewport');
14
15base.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