1<!DOCTYPE html>
2<!--
3Copyright (c) 2013 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="stylesheet" href="/tracing/ui/tracks/object_instance_track.css">
9
10<link rel="import" href="/tracing/base/extension_registry.html">
11<link rel="import" href="/tracing/base/sorted_array_utils.html">
12<link rel="import" href="/tracing/model/event.html">
13<link rel="import" href="/tracing/ui/base/event_presenter.html">
14<link rel="import" href="/tracing/ui/base/heading.html">
15<link rel="import" href="/tracing/ui/base/ui.html">
16<link rel="import" href="/tracing/ui/tracks/track.html">
17
18<script>
19'use strict';
20
21tr.exportTo('tr.ui.tracks', function() {
22
23  var SelectionState = tr.model.SelectionState;
24  var EventPresenter = tr.ui.b.EventPresenter;
25
26  /**
27   * A track that displays an array of Slice objects.
28   * @constructor
29   * @extends {Track}
30   */
31  var ObjectInstanceTrack = tr.ui.b.define(
32      'object-instance-track', tr.ui.tracks.Track);
33
34  ObjectInstanceTrack.prototype = {
35    __proto__: tr.ui.tracks.Track.prototype,
36
37    decorate: function(viewport) {
38      tr.ui.tracks.Track.prototype.decorate.call(this, viewport);
39      this.classList.add('object-instance-track');
40      this.objectInstances_ = [];
41      this.objectSnapshots_ = [];
42
43      this.heading_ = document.createElement('tr-ui-heading');
44      this.appendChild(this.heading_);
45    },
46
47    set heading(heading) {
48      this.heading_.heading = heading;
49    },
50
51    get heading() {
52      return this.heading_.heading;
53    },
54
55    set tooltip(tooltip) {
56      this.heading_.tooltip = tooltip;
57    },
58
59    get objectInstances() {
60      return this.objectInstances_;
61    },
62
63    set objectInstances(objectInstances) {
64      if (!objectInstances || objectInstances.length == 0) {
65        this.heading = '';
66        this.objectInstances_ = [];
67        this.objectSnapshots_ = [];
68        return;
69      }
70      this.heading = objectInstances[0].typeName;
71      this.objectInstances_ = objectInstances;
72      this.objectSnapshots_ = [];
73      this.objectInstances_.forEach(function(instance) {
74        this.objectSnapshots_.push.apply(
75            this.objectSnapshots_, instance.snapshots);
76      }, this);
77      this.objectSnapshots_.sort(function(a, b) {
78        return a.ts - b.ts;
79      });
80    },
81
82    get height() {
83      return window.getComputedStyle(this).height;
84    },
85
86    set height(height) {
87      this.style.height = height;
88    },
89
90    get snapshotRadiusView() {
91      return 7 * (window.devicePixelRatio || 1);
92    },
93
94    draw: function(type, viewLWorld, viewRWorld) {
95      switch (type) {
96        case tr.ui.tracks.DrawType.GENERAL_EVENT:
97          this.drawLetterDots_(viewLWorld, viewRWorld);
98          break;
99      }
100    },
101
102    drawLetterDots_: function(viewLWorld, viewRWorld) {
103      var ctx = this.context();
104      var pixelRatio = window.devicePixelRatio || 1;
105
106      var bounds = this.getBoundingClientRect();
107      var height = bounds.height * pixelRatio;
108      var halfHeight = height * 0.5;
109      var twoPi = Math.PI * 2;
110
111      // Culling parameters.
112      var dt = this.viewport.currentDisplayTransform;
113      var snapshotRadiusView = this.snapshotRadiusView;
114      var snapshotRadiusWorld = dt.xViewVectorToWorld(height);
115      var loI;
116
117      // Begin rendering in world space.
118      ctx.save();
119      dt.applyTransformToCanvas(ctx);
120
121      // Instances
122      var objectInstances = this.objectInstances_;
123      var loI = tr.b.findLowIndexInSortedArray(
124          objectInstances,
125          function(instance) {
126            return instance.deletionTs;
127          },
128          viewLWorld);
129      ctx.strokeStyle = 'rgb(0,0,0)';
130      for (var i = loI; i < objectInstances.length; ++i) {
131        var instance = objectInstances[i];
132        var x = instance.creationTs;
133        if (x > viewRWorld)
134          break;
135
136        var right = instance.deletionTs == Number.MAX_VALUE ?
137            viewRWorld : instance.deletionTs;
138        ctx.fillStyle = EventPresenter.getObjectInstanceColor(instance);
139        ctx.fillRect(x, pixelRatio, right - x, height - 2 * pixelRatio);
140      }
141      ctx.restore();
142
143      // Snapshots. Has to run in worldspace because ctx.arc gets transformed.
144      var objectSnapshots = this.objectSnapshots_;
145      loI = tr.b.findLowIndexInSortedArray(
146          objectSnapshots,
147          function(snapshot) {
148            return snapshot.ts + snapshotRadiusWorld;
149          },
150          viewLWorld);
151      for (var i = loI; i < objectSnapshots.length; ++i) {
152        var snapshot = objectSnapshots[i];
153        var x = snapshot.ts;
154        if (x - snapshotRadiusWorld > viewRWorld)
155          break;
156        var xView = dt.xWorldToView(x);
157
158        ctx.fillStyle = EventPresenter.getObjectSnapshotColor(snapshot);
159        ctx.beginPath();
160        ctx.arc(xView, halfHeight, snapshotRadiusView, 0, twoPi);
161        ctx.fill();
162        if (snapshot.selected) {
163          ctx.lineWidth = 5;
164          ctx.strokeStyle = 'rgb(100,100,0)';
165          ctx.stroke();
166
167          ctx.beginPath();
168          ctx.arc(xView, halfHeight, snapshotRadiusView - 1, 0, twoPi);
169          ctx.lineWidth = 2;
170          ctx.strokeStyle = 'rgb(255,255,0)';
171          ctx.stroke();
172        } else {
173          ctx.lineWidth = 1;
174          ctx.strokeStyle = 'rgb(0,0,0)';
175          ctx.stroke();
176        }
177      }
178      ctx.lineWidth = 1;
179
180      // For performance reasons we only check the SelectionState of the first
181      // instance. If it's DIMMED we assume that all are DIMMED.
182      // TODO(egraether): Allow partial highlight.
183      var selectionState = SelectionState.NONE;
184      if (objectInstances.length &&
185          objectInstances[0].selectionState === SelectionState.DIMMED) {
186        selectionState = SelectionState.DIMMED;
187      }
188
189      // Dim the track when there is an active highlight.
190      if (selectionState === SelectionState.DIMMED) {
191        var width = bounds.width * pixelRatio;
192        ctx.fillStyle = 'rgba(255,255,255,0.5)';
193        ctx.fillRect(0, 0, width, height);
194        ctx.restore();
195      }
196    },
197
198    addEventsToTrackMap: function(eventToTrackMap) {
199      if (this.objectInstance_ !== undefined) {
200        this.objectInstance_.forEach(function(obj) {
201          eventToTrackMap.addEvent(obj, this);
202        }, this);
203      }
204
205      if (this.objectSnapshots_ !== undefined) {
206        this.objectSnapshots_.forEach(function(obj) {
207          eventToTrackMap.addEvent(obj, this);
208        }, this);
209      }
210    },
211
212    addIntersectingEventsInRangeToSelectionInWorldSpace: function(
213        loWX, hiWX, viewPixWidthWorld, selection) {
214      // Pick snapshots first.
215      var foundSnapshot = false;
216      function onSnapshot(snapshot) {
217        selection.push(snapshot);
218        foundSnapshot = true;
219      }
220      var snapshotRadiusView = this.snapshotRadiusView;
221      var snapshotRadiusWorld = viewPixWidthWorld * snapshotRadiusView;
222      tr.b.iterateOverIntersectingIntervals(
223          this.objectSnapshots_,
224          function(x) { return x.ts - snapshotRadiusWorld; },
225          function(x) { return 2 * snapshotRadiusWorld; },
226          loWX, hiWX,
227          onSnapshot);
228      if (foundSnapshot)
229        return;
230
231      // Try picking instances.
232      tr.b.iterateOverIntersectingIntervals(
233          this.objectInstances_,
234          function(x) { return x.creationTs; },
235          function(x) { return x.deletionTs - x.creationTs; },
236          loWX, hiWX,
237          selection.push.bind(selection));
238    },
239
240    /**
241     * Add the item to the left or right of the provided event, if any, to the
242     * selection.
243     * @param {event} The current event item.
244     * @param {Number} offset Number of slices away from the event to look.
245     * @param {Selection} selection The selection to add an event to,
246     * if found.
247     * @return {boolean} Whether an event was found.
248     * @private
249     */
250    addEventNearToProvidedEventToSelection: function(event, offset, selection) {
251      var events;
252      if (event instanceof tr.model.ObjectSnapshot)
253        events = this.objectSnapshots_;
254      else if (event instanceof tr.model.ObjectInstance)
255        events = this.objectInstances_;
256      else
257        throw new Error('Unrecognized event');
258
259      var index = events.indexOf(event);
260      var newIndex = index + offset;
261      if (newIndex >= 0 && newIndex < events.length) {
262        selection.push(events[newIndex]);
263        return true;
264      }
265      return false;
266    },
267
268    addAllEventsMatchingFilterToSelection: function(filter, selection) {
269    },
270
271    addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
272                                         selection) {
273      var snapshot = tr.b.findClosestElementInSortedArray(
274          this.objectSnapshots_,
275          function(x) { return x.ts; },
276          worldX,
277          worldMaxDist);
278
279      if (!snapshot)
280        return;
281
282      selection.push(snapshot);
283
284      // TODO(egraether): Search for object instances as well, which was not
285      // implemented because it makes little sense with the current visual and
286      // needs to take care of overlapping intervals.
287    }
288  };
289
290
291  var options = new tr.b.ExtensionRegistryOptions(
292      tr.b.TYPE_BASED_REGISTRY_MODE);
293  tr.b.decorateExtensionRegistry(ObjectInstanceTrack, options);
294
295  return {
296    ObjectInstanceTrack: ObjectInstanceTrack
297  };
298});
299</script>
300