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('tracing.tracks.object_instance_track');
8
9base.require('base.sorted_array_utils');
10base.require('tracing.tracks.heading_track');
11base.require('tracing.color_scheme');
12base.require('ui');
13
14base.exportTo('tracing.tracks', function() {
15
16  var palette = tracing.getColorPalette();
17  var highlightIdBoost = tracing.getColorPaletteHighlightIdBoost();
18
19  /**
20   * A track that displays an array of Slice objects.
21   * @constructor
22   * @extends {HeadingTrack}
23   */
24
25  var ObjectInstanceTrack = ui.define(
26      'object-instance-track', tracing.tracks.HeadingTrack);
27
28  ObjectInstanceTrack.prototype = {
29    __proto__: tracing.tracks.HeadingTrack.prototype,
30
31    decorate: function(viewport) {
32      tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
33      this.classList.add('object-instance-track');
34      this.objectInstances_ = [];
35      this.objectSnapshots_ = [];
36    },
37
38    get objectInstances() {
39      return this.objectInstances_;
40    },
41
42    set objectInstances(objectInstances) {
43      if (!objectInstances || objectInstances.length == 0) {
44        this.heading = '';
45        this.objectInstances_ = [];
46        this.objectSnapshots_ = [];
47        return;
48      }
49      this.heading = objectInstances[0].typeName;
50      this.objectInstances_ = objectInstances;
51      this.objectSnapshots_ = [];
52      this.objectInstances_.forEach(function(instance) {
53        this.objectSnapshots_.push.apply(
54            this.objectSnapshots_, instance.snapshots);
55      }, this);
56    },
57
58    get height() {
59      return window.getComputedStyle(this).height;
60    },
61
62    set height(height) {
63      this.style.height = height;
64    },
65
66    get snapshotRadiusView() {
67      return 7 * (window.devicePixelRatio || 1);
68    },
69
70    draw: function(type, viewLWorld, viewRWorld) {
71      switch (type) {
72        case tracing.tracks.DrawType.SLICE:
73          this.drawSlices_(viewLWorld, viewRWorld);
74          break;
75      }
76    },
77
78    drawSlices_: function(viewLWorld, viewRWorld) {
79      var ctx = this.context();
80      var pixelRatio = window.devicePixelRatio || 1;
81
82      var bounds = this.getBoundingClientRect();
83      var height = bounds.height * pixelRatio;
84      var halfHeight = height * 0.5;
85      var twoPi = Math.PI * 2;
86
87      // Culling parameters.
88      var vp = this.viewport;
89      var snapshotRadiusView = this.snapshotRadiusView;
90      var snapshotRadiusWorld = vp.xViewVectorToWorld(height);
91      var loI;
92
93      // Begin rendering in world space.
94      ctx.save();
95      vp.applyTransformToCanvas(ctx);
96
97      // Instances
98      var objectInstances = this.objectInstances_;
99      var loI = base.findLowIndexInSortedArray(
100          objectInstances,
101          function(instance) {
102            return instance.deletionTs;
103          },
104          viewLWorld);
105      ctx.globalAlpha = 0.25;
106      ctx.strokeStyle = 'rgb(0,0,0)';
107      for (var i = loI; i < objectInstances.length; ++i) {
108        var instance = objectInstances[i];
109        var x = instance.creationTs;
110        if (x > viewRWorld)
111          break;
112
113        var colorId = instance.selected ?
114            instance.colorId + highlightIdBoost :
115            instance.colorId;
116
117        var right = instance.deletionTs == Number.MAX_VALUE ?
118            viewRWorld : instance.deletionTs;
119        ctx.fillStyle = palette[colorId];
120        ctx.fillRect(x, pixelRatio, right - x, height - 2 * pixelRatio);
121      }
122      ctx.globalAlpha = 1;
123      ctx.restore();
124
125      // Snapshots. Has to run in worldspace because ctx.arc gets transformed.
126      var objectSnapshots = this.objectSnapshots_;
127      loI = base.findLowIndexInSortedArray(
128          objectSnapshots,
129          function(snapshot) {
130            return snapshot.ts +
131                snapshotRadiusWorld;
132          },
133          viewLWorld);
134      for (var i = loI; i < objectSnapshots.length; ++i) {
135        var snapshot = objectSnapshots[i];
136        var x = snapshot.ts;
137        if (x - snapshotRadiusWorld > viewRWorld)
138          break;
139        var xView = vp.xWorldToView(x);
140
141        var colorId = snapshot.selected ?
142            snapshot.objectInstance.colorId + highlightIdBoost :
143            snapshot.objectInstance.colorId;
144
145        ctx.fillStyle = palette[colorId];
146        ctx.beginPath();
147        ctx.arc(xView, halfHeight, snapshotRadiusView, 0, twoPi);
148        ctx.fill();
149        if (snapshot.selected) {
150          ctx.lineWidth = 5;
151          ctx.strokeStyle = 'rgb(100,100,0)';
152          ctx.stroke();
153
154          ctx.beginPath();
155          ctx.arc(xView, halfHeight, snapshotRadiusView - 1, 0, twoPi);
156          ctx.lineWidth = 2;
157          ctx.strokeStyle = 'rgb(255,255,0)';
158          ctx.stroke();
159        } else {
160          ctx.lineWidth = 1;
161          ctx.strokeStyle = 'rgb(0,0,0)';
162          ctx.stroke();
163        }
164      }
165      ctx.lineWidth = 1;
166    },
167
168    addIntersectingItemsInRangeToSelectionInWorldSpace: function(
169        loWX, hiWX, viewPixWidthWorld, selection) {
170      var that = this;
171
172      // Pick snapshots first.
173      var foundSnapshot = false;
174      function onSnapshotHit(snapshot) {
175        selection.addObjectSnapshot(that, snapshot);
176        foundSnapshot = true;
177      }
178      var snapshotRadiusView = this.snapshotRadiusView;
179      var snapshotRadiusWorld = viewPixWidthWorld * snapshotRadiusView;
180      base.iterateOverIntersectingIntervals(
181          this.objectSnapshots_,
182          function(x) { return x.ts - snapshotRadiusWorld; },
183          function(x) { return 2 * snapshotRadiusWorld; },
184          loWX, hiWX,
185          onSnapshotHit);
186      if (foundSnapshot)
187        return;
188
189      // Try picking instances.
190      function onInstanceHit(instance) {
191        selection.addObjectInstance(that, instance);
192      }
193      base.iterateOverIntersectingIntervals(
194          this.objectInstances_,
195          function(x) { return x.creationTs; },
196          function(x) { return x.deletionTs - x.creationTs; },
197          loWX, hiWX,
198          onInstanceHit);
199    },
200
201    /**
202     * Add the item to the left or right of the provided hit, if any, to the
203     * selection.
204     * @param {slice} The current slice.
205     * @param {Number} offset Number of slices away from the hit to look.
206     * @param {Selection} selection The selection to add a hit to,
207     * if found.
208     * @return {boolean} Whether a hit was found.
209     * @private
210     */
211    addItemNearToProvidedHitToSelection: function(hit, offset, selection) {
212      if (hit instanceof tracing.SelectionObjectSnapshotHit) {
213        var index = this.objectSnapshots_.indexOf(hit.objectSnapshot);
214        var newIndex = index + offset;
215        if (newIndex >= 0 && newIndex < this.objectSnapshots_.length) {
216          selection.addObjectSnapshot(this, this.objectSnapshots_[newIndex]);
217          return true;
218        }
219      } else if (hit instanceof tracing.SelectionObjectInstanceHit) {
220        var index = this.objectInstances_.indexOf(hit.objectInstance);
221        var newIndex = index + offset;
222        if (newIndex >= 0 && newIndex < this.objectInstances_.length) {
223          selection.addObjectInstance(this, this.objectInstances_[newIndex]);
224          return true;
225        }
226      } else {
227        throw new Error('Unrecognized hit');
228      }
229      return false;
230    },
231
232    addAllObjectsMatchingFilterToSelection: function(filter, selection) {
233    }
234  };
235
236  ObjectInstanceTrack.typeNameToTrackConstructorMap = {};
237  ObjectInstanceTrack.register = function(typeName, constructor) {
238    if (ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName])
239      throw new Error('Handler already registered for ' + typeName);
240    ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName] =
241        constructor;
242  };
243
244  ObjectInstanceTrack.getTrackConstructor = function(typeName) {
245    return ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName];
246  };
247
248  return {
249    ObjectInstanceTrack: ObjectInstanceTrack
250  };
251});
252