blob: c636a0076cfcc29bef8cf5bb5a7cdbfe5bd69edf [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="stylesheet" href="/core/tracks/object_instance_track.css">
<link rel="import" href="/base/extension_registry.html">
<link rel="import" href="/model/event.html">
<link rel="import" href="/core/tracks/heading_track.html">
<link rel="import" href="/core/event_presenter.html">
<link rel="import" href="/base/sorted_array_utils.html">
<link rel="import" href="/base/ui.html">
<script>
'use strict';
tr.exportTo('tr.c.tracks', function() {
var SelectionState = tr.model.SelectionState;
var EventPresenter = tr.c.EventPresenter;
/**
* A track that displays an array of Slice objects.
* @constructor
* @extends {HeadingTrack}
*/
var ObjectInstanceTrack = tr.b.ui.define(
'object-instance-track', tr.c.tracks.HeadingTrack);
ObjectInstanceTrack.prototype = {
__proto__: tr.c.tracks.HeadingTrack.prototype,
decorate: function(viewport) {
tr.c.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
this.classList.add('object-instance-track');
this.objectInstances_ = [];
this.objectSnapshots_ = [];
},
get objectInstances() {
return this.objectInstances_;
},
set objectInstances(objectInstances) {
if (!objectInstances || objectInstances.length == 0) {
this.heading = '';
this.objectInstances_ = [];
this.objectSnapshots_ = [];
return;
}
this.heading = objectInstances[0].typeName;
this.objectInstances_ = objectInstances;
this.objectSnapshots_ = [];
this.objectInstances_.forEach(function(instance) {
this.objectSnapshots_.push.apply(
this.objectSnapshots_, instance.snapshots);
}, this);
this.objectSnapshots_.sort(function(a, b) {
return a.ts - b.ts;
});
},
get height() {
return window.getComputedStyle(this).height;
},
set height(height) {
this.style.height = height;
},
get snapshotRadiusView() {
return 7 * (window.devicePixelRatio || 1);
},
draw: function(type, viewLWorld, viewRWorld) {
switch (type) {
case tr.c.tracks.DrawType.GENERAL_EVENT:
this.drawLetterDots_(viewLWorld, viewRWorld);
break;
}
},
drawLetterDots_: function(viewLWorld, viewRWorld) {
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.getBoundingClientRect();
var height = bounds.height * pixelRatio;
var halfHeight = height * 0.5;
var twoPi = Math.PI * 2;
// Culling parameters.
var dt = this.viewport.currentDisplayTransform;
var snapshotRadiusView = this.snapshotRadiusView;
var snapshotRadiusWorld = dt.xViewVectorToWorld(height);
var loI;
// Begin rendering in world space.
ctx.save();
dt.applyTransformToCanvas(ctx);
// Instances
var objectInstances = this.objectInstances_;
var loI = tr.b.findLowIndexInSortedArray(
objectInstances,
function(instance) {
return instance.deletionTs;
},
viewLWorld);
ctx.strokeStyle = 'rgb(0,0,0)';
for (var i = loI; i < objectInstances.length; ++i) {
var instance = objectInstances[i];
var x = instance.creationTs;
if (x > viewRWorld)
break;
var right = instance.deletionTs == Number.MAX_VALUE ?
viewRWorld : instance.deletionTs;
ctx.fillStyle = EventPresenter.getObjectInstanceColor(instance);
ctx.fillRect(x, pixelRatio, right - x, height - 2 * pixelRatio);
}
ctx.restore();
// Snapshots. Has to run in worldspace because ctx.arc gets transformed.
var objectSnapshots = this.objectSnapshots_;
loI = tr.b.findLowIndexInSortedArray(
objectSnapshots,
function(snapshot) {
return snapshot.ts + snapshotRadiusWorld;
},
viewLWorld);
for (var i = loI; i < objectSnapshots.length; ++i) {
var snapshot = objectSnapshots[i];
var x = snapshot.ts;
if (x - snapshotRadiusWorld > viewRWorld)
break;
var xView = dt.xWorldToView(x);
ctx.fillStyle = EventPresenter.getObjectSnapshotColor(snapshot);
ctx.beginPath();
ctx.arc(xView, halfHeight, snapshotRadiusView, 0, twoPi);
ctx.fill();
if (snapshot.selected) {
ctx.lineWidth = 5;
ctx.strokeStyle = 'rgb(100,100,0)';
ctx.stroke();
ctx.beginPath();
ctx.arc(xView, halfHeight, snapshotRadiusView - 1, 0, twoPi);
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(255,255,0)';
ctx.stroke();
} else {
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgb(0,0,0)';
ctx.stroke();
}
}
ctx.lineWidth = 1;
// For performance reasons we only check the SelectionState of the first
// instance. If it's DIMMED we assume that all are DIMMED.
// TODO(egraether): Allow partial highlight.
var selectionState = SelectionState.NONE;
if (objectInstances.length &&
objectInstances[0].selectionState === SelectionState.DIMMED) {
selectionState = SelectionState.DIMMED;
}
// Dim the track when there is an active highlight.
if (selectionState === SelectionState.DIMMED) {
var width = bounds.width * pixelRatio;
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.fillRect(0, 0, width, height);
ctx.restore();
}
},
addEventsToTrackMap: function(eventToTrackMap) {
if (this.objectInstance_ !== undefined) {
this.objectInstance_.forEach(function(obj) {
eventToTrackMap.addEvent(obj, this);
}, this);
}
if (this.objectSnapshots_ !== undefined) {
this.objectSnapshots_.forEach(function(obj) {
eventToTrackMap.addEvent(obj, this);
}, this);
}
},
addIntersectingEventsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
// Pick snapshots first.
var foundSnapshot = false;
function onSnapshot(snapshot) {
selection.push(snapshot);
foundSnapshot = true;
}
var snapshotRadiusView = this.snapshotRadiusView;
var snapshotRadiusWorld = viewPixWidthWorld * snapshotRadiusView;
tr.b.iterateOverIntersectingIntervals(
this.objectSnapshots_,
function(x) { return x.ts - snapshotRadiusWorld; },
function(x) { return 2 * snapshotRadiusWorld; },
loWX, hiWX,
onSnapshot);
if (foundSnapshot)
return;
// Try picking instances.
tr.b.iterateOverIntersectingIntervals(
this.objectInstances_,
function(x) { return x.creationTs; },
function(x) { return x.deletionTs - x.creationTs; },
loWX, hiWX,
selection.push.bind(selection));
},
/**
* Add the item to the left or right of the provided event, if any, to the
* selection.
* @param {event} The current event item.
* @param {Number} offset Number of slices away from the event to look.
* @param {Selection} selection The selection to add an event to,
* if found.
* @return {boolean} Whether an event was found.
* @private
*/
addEventNearToProvidedEventToSelection: function(event, offset, selection) {
var events;
if (event instanceof tr.model.ObjectSnapshot)
events = this.objectSnapshots_;
else if (event instanceof tr.model.ObjectInstance)
events = this.objectInstances_;
else
throw new Error('Unrecognized event');
var index = events.indexOf(event);
var newIndex = index + offset;
if (newIndex >= 0 && newIndex < events.length) {
selection.push(events[newIndex]);
return true;
}
return false;
},
addAllEventsMatchingFilterToSelection: function(filter, selection) {
},
addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
selection) {
var snapshot = tr.b.findClosestElementInSortedArray(
this.objectSnapshots_,
function(x) { return x.ts; },
worldX,
worldMaxDist);
if (!snapshot)
return;
selection.push(snapshot);
// TODO(egraether): Search for object instances as well, which was not
// implemented because it makes little sense with the current visual and
// needs to take care of overlapping intervals.
}
};
var options = new tr.b.ExtensionRegistryOptions(
tr.b.TYPE_BASED_REGISTRY_MODE);
tr.b.decorateExtensionRegistry(ObjectInstanceTrack, options);
return {
ObjectInstanceTrack: ObjectInstanceTrack
};
});
</script>