1 // Copyright (c) 2013 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.
7 tvcm.require('tvcm.range');
8 tvcm.require('tracing.constants');
9 tvcm.require('tracing.selection');
10 tvcm.require('tracing.trace_model.slice');
13 * @fileoverview Provides the TimingTool class.
15 tvcm.exportTo('tracing', function() {
17 var constants = tracing.constants;
20 * Tool for taking time measurements in the TimelineTrackView using
24 var TimingTool = function(viewport, targetElement) {
25 this.viewport = viewport;
27 this.rangeStartMarker_ = viewport.createMarker(0);
28 this.rangeEndMarker_ = viewport.createMarker(0);
29 this.cursorMarker_ = viewport.createMarker(0);
30 this.activeMarker_ = this.cursorMarker_;
32 // Prepare the event handlers to be added and removed repeatedly.
33 this.onMouseMove_ = this.onMouseMove_.bind(this);
34 this.onDblClick_ = this.onDblClick_.bind(this);
35 this.targetElement_ = targetElement;
38 TimingTool.prototype = {
40 getWorldXFromEvent_: function(e) {
41 var pixelRatio = window.devicePixelRatio || 1;
42 var modelTrackContainer = this.viewport.modelTrackContainer;
43 var viewX = (e.clientX -
44 modelTrackContainer.offsetLeft -
45 constants.HEADING_WIDTH) * pixelRatio;
46 return this.viewport.currentDisplayTransform.xViewToWorld(viewX);
49 onEnterTiming: function(e) {
50 // Show the cursor marker if it was the active marker, otherwise the two
51 // range markers should be left.
52 if (this.activeMarker_ === this.cursorMarker_)
53 this.viewport.addMarker(this.cursorMarker_);
55 this.targetElement_.addEventListener('mousemove', this.onMouseMove_);
56 this.targetElement_.addEventListener('dblclick', this.onDblClick_);
59 onBeginTiming: function(e) {
60 var worldX = this.getWorldXFromEvent_(e);
62 // Check if click was on a range marker that can be moved.
63 if (!this.activeMarker_) {
64 var marker = this.viewport.findMarkerNear(worldX, 6);
65 if (marker === this.rangeStartMarker_ ||
66 marker === this.rangeEndMarker_) {
67 // Set the clicked marker as active marker so it will be moved.
68 this.activeMarker_ = marker;
69 marker.selected = true;
73 // Otherwise start selecting a new range by hiding the cursor marker and
74 // adding the end marker. This is skipped if there was already a range
76 this.viewport.removeMarker(this.cursorMarker_);
77 this.viewport.addMarker(this.rangeEndMarker_);
80 // Set the range markers to the mouse or snapped position and select them.
81 var snapPos = this.getSnappedToEventPosition_(e);
82 this.updateMarkerToSnapPosition_(this.rangeStartMarker_, snapPos);
83 this.updateMarkerToSnapPosition_(this.rangeEndMarker_, snapPos);
85 this.rangeStartMarker_.selected = true;
86 this.rangeEndMarker_.selected = true;
88 // The end marker is the one that is moved.
89 this.activeMarker_ = this.rangeEndMarker_;
92 onUpdateTiming: function(e) {
93 if (!this.activeMarker_ || this.activeMarker_ === this.cursorMarker_)
96 // Update the position of the active marker to the cursor position.
97 // This is either the end marker when creating a range, or one of the
98 // range markers when they are moved.
99 var snapPos = this.getSnappedToEventPosition_(e);
100 this.updateMarkerToSnapPosition_(this.activeMarker_, snapPos);
102 // When creating a range, only show the start marker after the range
103 // exceeds a certain amount. This prevents a short flicker showing the
104 // dimmed areas left and right of the range when clicking.
105 if (this.rangeStartMarker_.selected && this.rangeEndMarker_.selected) {
106 var rangeX = Math.abs(this.rangeStartMarker_.positionView -
107 this.rangeEndMarker_.positionView);
108 if (rangeX >= constants.MIN_MOUSE_SELECTION_DISTANCE)
109 this.viewport.addMarker(this.rangeStartMarker_);
113 onEndTiming: function(e) {
114 if (!this.activeMarker_ || !this.activeMarker_.selected)
119 // Check if a range selection is finished now.
120 if (this.rangeStartMarker_.selected && this.rangeEndMarker_.selected) {
121 var rangeX = Math.abs(this.rangeStartMarker_.positionView -
122 this.rangeEndMarker_.positionView);
124 // The range is only valid when it exceeds the minimum mouse selection
125 // distance, otherwise it could have been just a click.
126 if (rangeX >= constants.MIN_MOUSE_SELECTION_DISTANCE) {
127 this.rangeStartMarker_.selected = false;
128 this.rangeEndMarker_.selected = false;
129 this.activeMarker_ = null;
131 // If the range is not valid, hide it and activate the cursor marker.
132 this.viewport.removeMarker(this.rangeStartMarker_);
133 this.viewport.removeMarker(this.rangeEndMarker_);
135 this.viewport.addMarker(this.cursorMarker_);
136 this.cursorMarker_.positionWorld =
137 this.getWorldXFromEvent_(e);
138 this.activeMarker_ = this.cursorMarker_;
144 // Deselect and deactivate a range marker that was moved.
145 this.activeMarker_.selected = false;
146 this.activeMarker_ = null;
149 onExitTiming: function(e) {
150 // If there is a selected range the markers are left on screen, but the
151 // cursor marker gets removed.
152 if (this.activeMarker_ === this.cursorMarker_)
153 this.viewport.removeMarker(this.cursorMarker_);
155 this.targetElement_.removeEventListener('mousemove', this.onMouseMove_);
156 this.targetElement_.removeEventListener('dblclick', this.onDblClick_);
159 onMouseMove_: function(e) {
160 var worldX = this.getWorldXFromEvent_(e);
162 if (this.activeMarker_) {
163 // Update the position of the cursor marker.
164 if (this.activeMarker_ === this.cursorMarker_) {
165 var snapPos = this.getSnappedToEventPosition_(e);
166 this.updateMarkerToSnapPosition_(this.cursorMarker_, snapPos);
171 // If there is no active marker then look for a marker close to the cursor
172 // and indicate that it can be moved by displaying it selected.
173 var marker = this.viewport.findMarkerNear(worldX, 6);
174 if (marker === this.rangeStartMarker_ ||
175 marker === this.rangeEndMarker_) {
176 marker.selected = true;
178 // Otherwise deselect markers that may have been selected before.
179 this.rangeEndMarker_.selected = false;
180 this.rangeStartMarker_.selected = false;
184 onDblClick_: function(e) {
185 var modelTrackContainer = this.viewport.modelTrackContainer;
186 var modelTrackContainerRect = modelTrackContainer.getBoundingClientRect();
188 var eventWorldX = this.getWorldXFromEvent_(e);
191 var selection = new tracing.Selection();
192 modelTrackContainer.addClosestEventToSelection(
193 eventWorldX, Infinity, y, y, selection);
195 if (!selection.length)
198 var slice = selection[0];
200 if (!(slice instanceof tracing.trace_model.Slice))
203 if (slice.start > eventWorldX || slice.end < eventWorldX)
206 var track = this.viewport.trackForEvent(slice);
207 var trackRect = track.getBoundingClientRect();
212 modelTrackContainer.scrollTop - modelTrackContainerRect.top,
213 height: trackRect.height,
216 this.updateMarkerToSnapPosition_(this.rangeStartMarker_, snapPos);
217 snapPos.x = slice.end;
218 this.updateMarkerToSnapPosition_(this.rangeEndMarker_, snapPos);
220 this.viewport.addMarker(this.rangeStartMarker_);
221 this.viewport.addMarker(this.rangeEndMarker_);
222 this.viewport.removeMarker(this.cursorMarker_);
223 this.activeMarker_ = null;
227 * Get the closest position of an event within a vertical range of the mouse
228 * position if possible, otherwise use the position of the mouse pointer.
229 * @param {MouseEvent} e Mouse event with the current mouse coordinates.
231 * {Number} x, The x coordinate in world space.
232 * {Number} y, The y coordinate in world space.
233 * {Number} height, The height of the event.
234 * {boolean} snapped Whether the coordinates are from a snapped event or
235 * the mouse position.
238 getSnappedToEventPosition_: function(e) {
239 var pixelRatio = window.devicePixelRatio || 1;
240 var EVENT_SNAP_RANGE = 16 * pixelRatio;
242 var modelTrackContainer = this.viewport.modelTrackContainer;
243 var modelTrackContainerRect = modelTrackContainer.getBoundingClientRect();
245 var viewport = this.viewport;
246 var dt = viewport.currentDisplayTransform;
247 var worldMaxDist = dt.xViewVectorToWorld(EVENT_SNAP_RANGE);
249 var worldX = this.getWorldXFromEvent_(e);
250 var mouseY = e.clientY;
252 var selection = new tracing.Selection();
254 // Look at the track under mouse position first for better performance.
255 modelTrackContainer.addClosestEventToSelection(
256 worldX, worldMaxDist, mouseY, mouseY, selection);
258 // Look at all tracks visible on screen.
259 if (!selection.length) {
260 modelTrackContainer.addClosestEventToSelection(
261 worldX, worldMaxDist,
262 modelTrackContainerRect.top, modelTrackContainerRect.bottom,
266 var minDistX = worldMaxDist;
267 var minDistY = Infinity;
268 var pixWidth = dt.xViewVectorToWorld(1);
270 // Create result object with the mouse coordinates.
273 y: mouseY - modelTrackContainerRect.top,
278 var eventBounds = new tvcm.Range();
279 for (var i = 0; i < selection.length; i++) {
280 var event = selection[i];
281 var track = viewport.trackForEvent(event);
282 var trackRect = track.getBoundingClientRect();
285 event.addBoundsToRange(eventBounds);
287 if (Math.abs(eventBounds.min - worldX) <
288 Math.abs(eventBounds.max - worldX)) {
289 eventX = eventBounds.min;
291 eventX = eventBounds.max;
294 var distX = eventX - worldX;
296 var eventY = trackRect.top;
297 var eventHeight = trackRect.height;
298 var distY = Math.abs(eventY + eventHeight / 2 - mouseY);
300 // Prefer events with a closer y position if their x difference is below
301 // the width of a pixel.
302 if ((distX <= minDistX || Math.abs(distX - minDistX) < pixWidth) &&
307 // Retrieve the event position from the hit.
310 modelTrackContainer.scrollTop - modelTrackContainerRect.top;
311 result.height = eventHeight;
312 result.snapped = true;
320 * Update the marker to the snapped position.
321 * @param {ViewportMarker} marker The marker to be updated.
323 * {Number} x, The new positionWorld of the marker.
324 * {Number} y, The new indicatorY of the marker.
325 * {Number} height, The new indicatorHeight of the marker.
326 * {boolean} snapped Whether the coordinates are from a snapped event or
327 * the mouse position.
330 updateMarkerToSnapPosition_: function(marker, snapPos) {
331 marker.setSnapIndicator(snapPos.snapped, snapPos.y, snapPos.height);
332 marker.positionWorld = snapPos.x;
337 TimingTool: TimingTool