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.
8 * @fileoverview Interactive visualizaiton of TraceModel objects
9 * based loosely on gantt charts. Each thread in the TraceModel is given a
10 * set of Tracks, one per subrow in the thread. The TimelineTrackView class
11 * acts as a controller, creating the individual tracks, while Tracks
14 * Visually, the TimelineTrackView produces (prettier) visualizations like the
16 * Thread1: AAAAAAAAAA AAAAA
18 * Thread2: CCCCCC CCCCC
21 tvcm.requireStylesheet('tvcm.ui.common');
22 tvcm.requireStylesheet('tracing.timeline_track_view');
23 tvcm.require('tvcm.events');
24 tvcm.require('tvcm.properties');
25 tvcm.require('tvcm.settings');
26 tvcm.require('tracing.filter');
27 tvcm.require('tracing.selection');
28 tvcm.require('tracing.timeline_viewport');
29 tvcm.require('tracing.timeline_display_transform_animations');
30 tvcm.require('tracing.timing_tool');
31 tvcm.require('tracing.trace_model.event');
32 tvcm.require('tracing.tracks.drawing_container');
33 tvcm.require('tracing.tracks.trace_model_track');
34 tvcm.require('tracing.tracks.ruler_track');
35 tvcm.require('tvcm.ui');
36 tvcm.require('tvcm.ui.mouse_mode_selector');
38 tvcm.exportTo('tracing', function() {
40 var Selection = tracing.Selection;
41 var SelectionState = tracing.trace_model.SelectionState;
42 var Viewport = tracing.TimelineViewport;
44 var tempDisplayTransform = new tracing.TimelineDisplayTransform();
46 function intersectRect_(r1, r2) {
47 var results = new Object;
48 if (r2.left > r1.right || r2.right < r1.left ||
49 r2.top > r1.bottom || r2.bottom < r1.top) {
52 results.left = Math.max(r1.left, r2.left);
53 results.top = Math.max(r1.top, r2.top);
54 results.right = Math.min(r1.right, r2.right);
55 results.bottom = Math.min(r1.bottom, r2.bottom);
56 results.width = (results.right - results.left);
57 results.height = (results.bottom - results.top);
62 * Renders a TraceModel into a div element, making one
63 * Track for each subrow in each thread of the model, managing
64 * overall track layout, and handling user interaction with the
68 * @extends {HTMLDivElement}
70 var TimelineTrackView = tvcm.ui.define('div');
72 TimelineTrackView.prototype = {
73 __proto__: HTMLDivElement.prototype,
77 decorate: function() {
79 this.classList.add('timeline-track-view');
81 this.viewport_ = new Viewport(this);
82 this.viewportDisplayTransformAtMouseDown_ = null;
84 this.rulerTrackContainer_ =
85 new tracing.tracks.DrawingContainer(this.viewport_);
86 this.appendChild(this.rulerTrackContainer_);
87 this.rulerTrackContainer_.invalidate();
89 this.rulerTrack_ = new tracing.tracks.RulerTrack(this.viewport_);
90 this.rulerTrackContainer_.appendChild(this.rulerTrack_);
92 this.modelTrackContainer_ =
93 new tracing.tracks.DrawingContainer(this.viewport_);
94 this.appendChild(this.modelTrackContainer_);
95 this.modelTrackContainer_.style.display = 'block';
96 this.modelTrackContainer_.invalidate();
98 this.viewport_.modelTrackContainer = this.modelTrackContainer_;
100 this.modelTrack_ = new tracing.tracks.TraceModelTrack(this.viewport_);
101 this.modelTrackContainer_.appendChild(this.modelTrack_);
103 this.timingTool_ = new tracing.TimingTool(this.viewport_,
106 this.initMouseModeSelector();
108 this.dragBox_ = this.ownerDocument.createElement('div');
109 this.dragBox_.className = 'drag-box';
110 this.appendChild(this.dragBox_);
113 this.bindEventListener_(document, 'keypress', this.onKeypress_, this);
114 this.bindEventListener_(document, 'keydown', this.onKeydown_, this);
115 this.bindEventListener_(document, 'keyup', this.onKeyup_, this);
117 this.bindEventListener_(this, 'dblclick', this.onDblClick_, this);
118 this.bindEventListener_(this, 'mousewheel', this.onMouseWheel_, this);
120 this.addEventListener('mousemove', this.onMouseMove_);
122 this.mouseViewPosAtMouseDown_ = {x: 0, y: 0};
123 this.lastMouseViewPos_ = {x: 0, y: 0};
125 this.selection_ = new Selection();
126 this.highlight_ = new Selection();
128 this.isPanningAndScanning_ = false;
129 this.isZooming_ = false;
133 * Wraps the standard addEventListener but automatically binds the provided
134 * func to the provided target, tracking the resulting closure. When detach
135 * is called, these listeners will be automatically removed.
137 bindEventListener_: function(object, event, func, target) {
138 if (!this.boundListeners_)
139 this.boundListeners_ = [];
140 var boundFunc = func.bind(target);
141 this.boundListeners_.push({object: object,
143 boundFunc: boundFunc});
144 object.addEventListener(event, boundFunc);
147 initMouseModeSelector: function() {
148 this.mouseModeSelector_ = new tvcm.ui.MouseModeSelector(this);
149 this.appendChild(this.mouseModeSelector_);
151 this.mouseModeSelector_.addEventListener('beginpan',
152 this.onBeginPanScan_.bind(this));
153 this.mouseModeSelector_.addEventListener('updatepan',
154 this.onUpdatePanScan_.bind(this));
155 this.mouseModeSelector_.addEventListener('endpan',
156 this.onEndPanScan_.bind(this));
158 this.mouseModeSelector_.addEventListener('beginselection',
159 this.onBeginSelection_.bind(this));
160 this.mouseModeSelector_.addEventListener('updateselection',
161 this.onUpdateSelection_.bind(this));
162 this.mouseModeSelector_.addEventListener('endselection',
163 this.onEndSelection_.bind(this));
165 this.mouseModeSelector_.addEventListener('beginzoom',
166 this.onBeginZoom_.bind(this));
167 this.mouseModeSelector_.addEventListener('updatezoom',
168 this.onUpdateZoom_.bind(this));
169 this.mouseModeSelector_.addEventListener('endzoom',
170 this.onEndZoom_.bind(this));
172 this.mouseModeSelector_.addEventListener('entertiming',
173 this.timingTool_.onEnterTiming.bind(this.timingTool_));
174 this.mouseModeSelector_.addEventListener('begintiming',
175 this.timingTool_.onBeginTiming.bind(this.timingTool_));
176 this.mouseModeSelector_.addEventListener('updatetiming',
177 this.timingTool_.onUpdateTiming.bind(this.timingTool_));
178 this.mouseModeSelector_.addEventListener('endtiming',
179 this.timingTool_.onEndTiming.bind(this.timingTool_));
180 this.mouseModeSelector_.addEventListener('exittiming',
181 this.timingTool_.onExitTiming.bind(this.timingTool_));
183 var m = tvcm.ui.MOUSE_SELECTOR_MODE;
184 this.mouseModeSelector_.supportedModeMask =
185 m.SELECTION | m.PANSCAN | m.ZOOM | m.TIMING;
186 this.mouseModeSelector_.settingsKey =
187 'timelineTrackView.mouseModeSelector';
188 this.mouseModeSelector_.setKeyCodeForMode(m.PANSCAN, '2'.charCodeAt(0));
189 this.mouseModeSelector_.setKeyCodeForMode(m.SELECTION, '1'.charCodeAt(0));
190 this.mouseModeSelector_.setKeyCodeForMode(m.ZOOM, '3'.charCodeAt(0));
191 this.mouseModeSelector_.setKeyCodeForMode(m.TIMING, '4'.charCodeAt(0));
193 this.mouseModeSelector_.setModifierForAlternateMode(
194 m.SELECTION, tvcm.ui.MODIFIER.SHIFT);
195 this.mouseModeSelector_.setModifierForAlternateMode(
196 m.PANSCAN, tvcm.ui.MODIFIER.SPACE);
197 this.mouseModeSelector_.setModifierForAlternateMode(
198 m.ZOOM, tvcm.ui.MODIFIER.CMD_OR_CTRL);
202 this.modelTrack_.detach();
204 for (var i = 0; i < this.boundListeners_.length; i++) {
205 var binding = this.boundListeners_[i];
206 binding.object.removeEventListener(binding.event, binding.boundFunc);
208 this.boundListeners_ = undefined;
209 this.viewport_.detach();
213 return this.viewport_;
222 throw new Error('Model cannot be null');
224 var modelInstanceChanged = this.model_ != model;
226 this.modelTrack_.model = model;
228 // Set up a reasonable viewport.
229 if (modelInstanceChanged)
230 this.viewport_.setWhenPossible(this.setInitialViewport_.bind(this));
232 tvcm.setPropertyAndDispatchChange(this, 'model', model);
235 get hasVisibleContent() {
236 return this.modelTrack_.hasVisibleContent;
239 setInitialViewport_: function() {
240 var w = this.modelTrackContainer_.canvas.width;
245 if (this.model_.bounds.isEmpty) {
248 } else if (this.model_.bounds.range == 0) {
249 min = this.model_.bounds.min;
252 min = this.model_.bounds.min;
253 range = this.model_.bounds.range;
255 var boost = range * 0.15;
256 tempDisplayTransform.set(this.viewport.currentDisplayTransform);
257 tempDisplayTransform.xSetWorldBounds(min - boost,
260 this.viewport.setDisplayTransformImmediately(tempDisplayTransform);
264 * @param {Filter} filter The filter to use for finding matches.
265 * @param {Selection} selection The selection to add matches to.
266 * @return {Array} An array of objects that match the provided
269 addAllObjectsMatchingFilterToSelection: function(filter, selection) {
270 this.modelTrack_.addAllObjectsMatchingFilterToSelection(
275 * @return {Element} The element whose focused state determines
276 * whether to respond to keyboard inputs.
277 * Defaults to the parent element.
280 if (this.focusElement_)
281 return this.focusElement_;
282 return this.parentElement;
286 * Sets the element whose focus state will determine whether
287 * to respond to keybaord input.
289 set focusElement(value) {
290 this.focusElement_ = value;
293 get listenToKeys_() {
294 if (!this.viewport_.isAttachedToDocument_)
296 if (this.activeElement instanceof tracing.FindControl)
298 if (!this.focusElement_)
300 if (this.focusElement.tabIndex >= 0) {
301 if (document.activeElement == this.focusElement)
303 return tvcm.ui.elementIsChildOf(document.activeElement,
309 onMouseMove_: function(e) {
311 // Zooming requires the delta since the last mousemove so we need to avoid
312 // tracking it when the zoom interaction is active.
316 this.storeLastMousePos_(e);
319 onKeypress_: function(e) {
320 var vp = this.viewport_;
321 if (!this.listenToKeys_)
323 if (document.activeElement.nodeName == 'INPUT')
325 var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
326 var curMouseV, curCenterW;
331 this.zoomBy_(1.5, true);
335 this.zoomBy_(1 / 1.5, true);
338 this.onGridToggle_(true);
341 this.onGridToggle_(false);
345 this.zoomBy_(10, true);
349 this.zoomBy_(1 / 10, true);
352 this.queueSmoothPan_(viewWidth * 0.3, 0);
356 this.queueSmoothPan_(viewWidth * -0.3, 0);
359 this.queueSmoothPan_(viewWidth * 0.5, 0);
362 this.queueSmoothPan_(viewWidth * -0.5, 0);
365 this.setInitialViewport_();
368 this.zoomToSelection();
370 case 'm'.charCodeAt(0):
371 this.putMarkAroundCurrentSelection_();
376 // Not all keys send a keypress.
377 onKeydown_: function(e) {
378 if (!this.listenToKeys_)
381 var vp = this.viewport;
382 var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
385 case 37: // left arrow
386 sel = this.selection.getShiftedSelection(
389 this.setSelectionAndClearHighlight(sel);
390 this.panToSelection();
393 this.queueSmoothPan_(viewWidth * 0.3, 0);
396 case 39: // right arrow
397 sel = this.selection.getShiftedSelection(
400 this.setSelectionAndClearHighlight(sel);
401 this.panToSelection();
404 this.queueSmoothPan_(-viewWidth * 0.3, 0);
408 if (this.focusElement.tabIndex == -1) {
410 this.selectPrevious_(e);
419 onKeyup_: function(e) {
420 if (!this.listenToKeys_)
423 if (this.dragBeginEvent_) {
424 this.setDragBoxPosition_(this.dragBoxXStart_, this.dragBoxYStart_,
425 this.dragBoxXEnd_, this.dragBoxYEnd_);
431 onDblClick_: function(e) {
432 if (this.mouseModeSelector_.mode !==
433 tvcm.ui.MOUSE_SELECTOR_MODE.SELECTION)
436 if (!this.selection.length || !this.selection[0].title)
439 var selection = new Selection();
440 var filter = new tracing.ExactTitleFilter(this.selection[0].title);
441 this.addAllObjectsMatchingFilterToSelection(filter, selection);
443 this.setSelectionAndClearHighlight(selection);
446 onMouseWheel_: function(e) {
450 var delta = e.wheelDelta / 120;
451 var zoomScale = Math.pow(1.5, delta);
452 this.zoomBy_(zoomScale);
456 queueSmoothPan_: function(viewDeltaX, deltaY) {
457 var deltaX = this.viewport_.currentDisplayTransform.xViewVectorToWorld(
459 var animation = new tracing.TimelineDisplayTransformPanAnimation(
461 this.viewport_.queueDisplayTransformAnimation(animation);
465 * Zoom in or out on the timeline by the given scale factor.
466 * @param {Number} scale The scale factor to apply. If <1, zooms out.
467 * @param {boolean} Whether to change the zoom level smoothly.
469 zoomBy_: function(scale, smooth) {
471 var vp = this.viewport;
472 var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
473 var pixelRatio = window.devicePixelRatio || 1;
475 var goalFocalPointXView = this.lastMouseViewPos_.x * pixelRatio;
476 var goalFocalPointXWorld = vp.currentDisplayTransform.xViewToWorld(
477 goalFocalPointXView);
479 var animation = new tracing.TimelineDisplayTransformZoomToAnimation(
480 goalFocalPointXWorld, goalFocalPointXView,
481 vp.currentDisplayTransform.panY,
483 vp.queueDisplayTransformAnimation(animation);
485 tempDisplayTransform.set(vp.currentDisplayTransform);
486 tempDisplayTransform.scaleX = tempDisplayTransform.scaleX * scale;
487 tempDisplayTransform.xPanWorldPosToViewPos(
488 goalFocalPointXWorld, goalFocalPointXView, viewWidth);
489 vp.setDisplayTransformImmediately(tempDisplayTransform);
494 * Zoom into the current selection.
496 zoomToSelection: function() {
497 if (!this.selectionOfInterest.length)
500 var bounds = this.selectionOfInterest.bounds;
504 var worldCenter = bounds.center;
505 var adjustedWorldRange = bounds.range * 1.25;
506 var newScale = this.modelTrackContainer_.canvas.width /
508 var zoomInRatio = newScale / this.viewport.currentDisplayTransform.scaleX;
509 var animation = new tracing.TimelineDisplayTransformZoomToAnimation(
510 worldCenter, 'center',
511 this.viewport.currentDisplayTransform.panY,
513 this.viewport.queueDisplayTransformAnimation(animation);
517 * Pan the view so the current selection becomes visible.
519 panToSelection: function() {
520 if (!this.selectionOfInterest.length)
523 var bounds = this.selectionOfInterest.bounds;
524 var worldCenter = bounds.center;
525 var viewWidth = this.modelTrackContainer_.canvas.width;
527 var dt = this.viewport.currentDisplayTransform;
528 if (false && !bounds.range) {
529 if (dt.xWorldToView(bounds.center) < 0 ||
530 dt.xWorldToView(bounds.center) > viewWidth) {
531 tempDisplayTransform.set(dt);
532 tempDisplayTransform.xPanWorldPosToViewPos(
533 worldCenter, 'center', viewWidth);
534 var deltaX = tempDisplayTransform.panX - dt.panX;
535 var animation = new tracing.TimelineDisplayTransformPanAnimation(
537 this.viewport.queueDisplayTransformAnimation(animation);
542 tempDisplayTransform.set(dt);
543 tempDisplayTransform.xPanWorldBoundsIntoView(
547 var deltaX = tempDisplayTransform.panX - dt.panX;
548 var animation = new tracing.TimelineDisplayTransformPanAnimation(
550 this.viewport.queueDisplayTransformAnimation(animation);
553 putMarkAroundCurrentSelection_: function() {
554 var selectionBounds = this.selection.bounds;
555 if (selectionBounds.empty)
556 return this.viewport.removeAllMarkers();
557 var markerBounds = this.viewport.getMarkerBounds();
560 if (selectionBounds.equals(markerBounds)) {
561 return this.viewport.removeAllMarkers();
563 this.viewport.removeAllMarkers();
564 this.viewport.addMarker(this.viewport.createMarker(selectionBounds.min));
565 this.viewport.addMarker(this.viewport.createMarker(selectionBounds.max));
569 * Sets the selected events and changes the SelectionState of the events to
571 * @param {Selection} selection A Selection of the new selected events.
573 set selection(selection) {
574 this.setSelectionAndHighlight(selection, this.highlight_);
578 return this.selection_;
582 * Sets the highlighted events and changes the SelectionState of the events
583 * to HIGHLIGHTED. All other events are set to DIMMED, except SELECTED
585 * @param {Selection} selection A Selection of the new selected events.
587 set highlight(highlight) {
588 this.setSelectionAndHighlight(this.selection_, highlight);
592 return this.highlight_;
596 * Getter for events of interest, primarily SELECTED and secondarily
597 * HIGHLIGHTED events.
599 get selectionOfInterest() {
600 if (!this.selection_.length && this.highlight_.length)
601 return this.highlight_;
602 return this.selection_;
606 * Sets the selected events, changes the SelectionState of the events to
607 * SELECTED and clears the highlighted events.
608 * @param {Selection} selection A Selection of the new selected events.
610 setSelectionAndClearHighlight: function(selection) {
611 this.setSelectionAndHighlight(selection, null);
615 * Sets the highlighted events, changes the SelectionState of the events to
616 * HIGHLIGHTED and clears the selected events. All other events are set to
618 * @param {Selection} highlight A Selection of the new highlighted events.
620 setHighlightAndClearSelection: function(highlight) {
621 this.setSelectionAndHighlight(null, highlight);
625 * Sets both selected and highlighted events. If an event is both it will be
626 * set to SELECTED. All other events are set to DIMMED.
627 * @param {Selection} selection A Selection of the new selected events.
628 * @param {Selection} highlight A Selection of the new highlighted events.
630 setSelectionAndHighlight: function(selection, highlight) {
631 if (selection === this.selection_ && highlight === this.highlight_)
634 if ((selection !== null && !(selection instanceof Selection)) ||
635 (highlight !== null && !(highlight instanceof Selection))) {
636 throw new Error('Expected Selection');
639 if (highlight && highlight.length) {
640 // Set all events to DIMMED. This needs to be done before clearing the
641 // old highlight, so that the old events are still available. This is
642 // also necessary when the highlight doesn't change, because it might
643 // have overlapping events with selection.
644 this.resetEventsTo_(SelectionState.DIMMED);
646 // Switch the highlight.
647 if (highlight !== this.highlight_)
648 this.highlight_ = highlight;
650 // Set HIGHLIGHTED on the events of the new highlight.
651 this.setSelectionState_(highlight, SelectionState.HIGHLIGHTED);
653 // If no highlight is active the SelectionState needs to be cleared.
654 // Note that this also clears old SELECTED events, so it doesn't need
655 // to be called again when setting the selection.
656 this.resetEventsTo_(SelectionState.NONE);
657 this.highlight_ = new Selection();
660 if (selection && selection.length) {
661 // Switch the selection
662 if (selection !== this.selection_)
663 this.selection_ = selection;
665 // Set SELECTED on the events of the new highlight.
666 this.setSelectionState_(selection, SelectionState.SELECTED);
668 this.selection_ = new Selection();
670 tvcm.dispatchSimpleEvent(this, 'selectionChange');
672 if (this.selectionOfInterest.length) {
673 var track = this.viewport.trackForEvent(this.selectionOfInterest[0]);
675 track.scrollIntoViewIfNeeded();
678 this.viewport.dispatchChangeEvent(); // Triggers a redraw.
682 * Sets a new SelectionState on all events in the selection.
683 * @param {Selection} selection The affected selection.
684 * @param {SelectionState} selectionState The new selection state.
686 setSelectionState_: function(selection, selectionState) {
687 for (var i = 0; i < selection.length; i++)
688 selection[i].selectionState = selectionState;
692 * Resets all events to the provided SelectionState. When the SelectionState
693 * changes from or to DIMMED all events in the model need to get updated.
694 * @param {SelectionState} selectionState The SelectionState to reset to.
696 resetEventsTo_: function(selectionState) {
697 var dimmed = this.highlight_.length;
698 var resetAll = (dimmed && selectionState !== SelectionState.DIMMED) ||
699 (!dimmed && selectionState === SelectionState.DIMMED);
701 this.model.iterateAllEvents(
702 function(event) { event.selectionState = selectionState; });
704 this.setSelectionState_(this.selection_, selectionState);
705 this.setSelectionState_(this.highlight_, selectionState);
709 hideDragBox_: function() {
710 this.dragBox_.style.left = '-1000px';
711 this.dragBox_.style.top = '-1000px';
712 this.dragBox_.style.width = 0;
713 this.dragBox_.style.height = 0;
716 setDragBoxPosition_: function(xStart, yStart, xEnd, yEnd) {
717 var loY = Math.min(yStart, yEnd);
718 var hiY = Math.max(yStart, yEnd);
719 var loX = Math.min(xStart, xEnd);
720 var hiX = Math.max(xStart, xEnd);
721 var modelTrackRect = this.modelTrack_.getBoundingClientRect();
722 var dragRect = {left: loX, top: loY, width: hiX - loX, height: hiY - loY};
724 dragRect.right = dragRect.left + dragRect.width;
725 dragRect.bottom = dragRect.top + dragRect.height;
727 var modelTrackContainerRect =
728 this.modelTrackContainer_.getBoundingClientRect();
730 left: modelTrackContainerRect.left,
731 top: modelTrackContainerRect.top,
732 right: modelTrackContainerRect.right,
733 bottom: modelTrackContainerRect.bottom
736 var headingWidth = window.getComputedStyle(
737 this.querySelector('heading')).width;
738 var trackTitleWidth = parseInt(headingWidth);
739 clipRect.left = clipRect.left + trackTitleWidth;
741 var finalDragBox = intersectRect_(clipRect, dragRect);
743 this.dragBox_.style.left = finalDragBox.left + 'px';
744 this.dragBox_.style.width = finalDragBox.width + 'px';
745 this.dragBox_.style.top = finalDragBox.top + 'px';
746 this.dragBox_.style.height = finalDragBox.height + 'px';
748 var pixelRatio = window.devicePixelRatio || 1;
749 var canv = this.modelTrackContainer_.canvas;
750 var dt = this.viewport.currentDisplayTransform;
751 var loWX = dt.xViewToWorld(
752 (loX - canv.offsetLeft) * pixelRatio);
753 var hiWX = dt.xViewToWorld(
754 (hiX - canv.offsetLeft) * pixelRatio);
756 var roundedDuration = Math.round((hiWX - loWX) * 100) / 100;
757 this.dragBox_.textContent = roundedDuration + 'ms';
759 var e = new tvcm.Event('selectionChanging');
762 this.dispatchEvent(e);
765 onGridToggle_: function(left) {
766 var tb = left ? this.selection.bounds.min : this.selection.bounds.max;
768 // Toggle the grid off if the grid is on, the marker position is the same
769 // and the same element is selected (same timebase).
770 if (this.viewport.gridEnabled &&
771 this.viewport.gridSide === left &&
772 this.viewport.gridInitialTimebase === tb) {
773 this.viewport.gridside = undefined;
774 this.viewport.gridEnabled = false;
775 this.viewport.gridInitialTimebase = undefined;
779 // Shift the timebase left until its just left of model_.bounds.min.
780 var numIntervalsSinceStart = Math.ceil((tb - this.model_.bounds.min) /
781 this.viewport.gridStep_);
783 this.viewport.gridEnabled = true;
784 this.viewport.gridSide = left;
785 this.viewport.gridInitialTimebase = tb;
786 this.viewport.gridTimebase = tb -
787 (numIntervalsSinceStart + 1) * this.viewport.gridStep_;
790 storeLastMousePos_: function(e) {
791 this.lastMouseViewPos_ = this.extractRelativeMousePosition_(e);
794 extractRelativeMousePosition_: function(e) {
795 var canv = this.modelTrackContainer_.canvas;
797 x: e.clientX - canv.offsetLeft,
798 y: e.clientY - canv.offsetTop
802 storeInitialMouseDownPos_: function(e) {
804 var position = this.extractRelativeMousePosition_(e);
806 this.mouseViewPosAtMouseDown_.x = position.x;
807 this.mouseViewPosAtMouseDown_.y = position.y;
810 focusElements_: function() {
811 if (document.activeElement)
812 document.activeElement.blur();
813 if (this.focusElement.tabIndex >= 0)
814 this.focusElement.focus();
817 storeInitialInteractionPositionsAndFocus_: function(e) {
819 this.storeInitialMouseDownPos_(e);
820 this.storeLastMousePos_(e);
822 this.focusElements_();
825 onBeginPanScan_: function(e) {
826 var vp = this.viewport;
827 this.viewportDisplayTransformAtMouseDown_ =
828 vp.currentDisplayTransform.clone();
829 this.isPanningAndScanning_ = true;
831 this.storeInitialInteractionPositionsAndFocus_(e);
835 onUpdatePanScan_: function(e) {
836 if (!this.isPanningAndScanning_)
839 var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
841 var pixelRatio = window.devicePixelRatio || 1;
842 var xDeltaView = pixelRatio * (this.lastMouseViewPos_.x -
843 this.mouseViewPosAtMouseDown_.x);
845 var yDelta = this.lastMouseViewPos_.y -
846 this.mouseViewPosAtMouseDown_.y;
848 tempDisplayTransform.set(this.viewportDisplayTransformAtMouseDown_);
849 tempDisplayTransform.incrementPanXInViewUnits(xDeltaView);
850 tempDisplayTransform.panY -= yDelta;
851 this.viewport.setDisplayTransformImmediately(tempDisplayTransform);
856 this.storeLastMousePos_(e);
859 onEndPanScan_: function(e) {
860 this.isPanningAndScanning_ = false;
862 this.storeLastMousePos_(e);
868 onBeginSelection_: function(e) {
869 var canv = this.modelTrackContainer_.canvas;
870 var rect = this.modelTrack_.getBoundingClientRect();
871 var canvRect = canv.getBoundingClientRect();
874 e.clientX >= rect.left &&
875 e.clientX < rect.right &&
876 e.clientY >= rect.top &&
877 e.clientY < rect.bottom &&
878 e.clientX >= canvRect.left &&
879 e.clientX < canvRect.right;
884 this.dragBeginEvent_ = e;
886 this.storeInitialInteractionPositionsAndFocus_(e);
890 onUpdateSelection_: function(e) {
891 if (!this.dragBeginEvent_)
894 // Update the drag box
895 this.dragBoxXStart_ = this.dragBeginEvent_.clientX;
896 this.dragBoxXEnd_ = e.clientX;
897 this.dragBoxYStart_ = this.dragBeginEvent_.clientY;
898 this.dragBoxYEnd_ = e.clientY;
899 this.setDragBoxPosition_(this.dragBoxXStart_, this.dragBoxYStart_,
900 this.dragBoxXEnd_, this.dragBoxYEnd_);
904 onEndSelection_: function(e) {
907 if (!this.dragBeginEvent_)
910 // Stop the dragging.
912 var eDown = this.dragBeginEvent_;
913 this.dragBeginEvent_ = null;
915 // Figure out extents of the drag.
916 var loY = Math.min(eDown.clientY, e.clientY);
917 var hiY = Math.max(eDown.clientY, e.clientY);
918 var loX = Math.min(eDown.clientX, e.clientX);
919 var hiX = Math.max(eDown.clientX, e.clientX);
920 var tracksContainerBoundingRect =
921 this.modelTrackContainer_.getBoundingClientRect();
922 var topBoundary = tracksContainerBoundingRect.height;
924 // Convert to worldspace.
925 var canv = this.modelTrackContainer_.canvas;
926 var loVX = loX - canv.offsetLeft;
927 var hiVX = hiX - canv.offsetLeft;
929 // Figure out what has been selected.
930 var selection = new Selection();
931 this.modelTrack_.addIntersectingItemsInRangeToSelection(
932 loVX, hiVX, loY, hiY, selection);
934 // Activate the new selection.
935 this.setSelectionAndClearHighlight(selection);
938 onBeginZoom_: function(e) {
939 this.isZooming_ = true;
941 this.storeInitialInteractionPositionsAndFocus_(e);
945 onUpdateZoom_: function(e) {
946 if (!this.isZooming_)
948 var newPosition = this.extractRelativeMousePosition_(e);
950 var zoomScaleValue = 1 + (this.lastMouseViewPos_.y -
951 newPosition.y) * 0.01;
953 this.zoomBy_(zoomScaleValue, false);
954 this.storeLastMousePos_(e);
957 onEndZoom_: function(e) {
958 this.isZooming_ = false;
966 TimelineTrackView: TimelineTrackView