Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @param {string} prefix
     34  */
     35 WebInspector.OverviewGrid = function(prefix)
     36 {
     37     this.element = document.createElement("div");
     38     this.element.className = "fill";
     39     this.element.id = prefix + "-overview-container";
     40 
     41     this._grid = new WebInspector.TimelineGrid();
     42     this._grid.element.id = prefix + "-overview-grid";
     43     this._grid.setScrollAndDividerTop(0, 0);
     44 
     45     this.element.appendChild(this._grid.element);
     46 
     47     this._window = new WebInspector.OverviewGrid.Window(this.element, this._grid.dividersLabelBarElement);
     48 }
     49 
     50 WebInspector.OverviewGrid.prototype = {
     51     /**
     52      * @return {number}
     53      */
     54     clientWidth: function()
     55     {
     56         return this.element.clientWidth;
     57     },
     58 
     59     /**
     60      * @param {!WebInspector.TimelineGrid.Calculator} calculator
     61      */
     62     updateDividers: function(calculator)
     63     {
     64         this._grid.updateDividers(calculator);
     65     },
     66 
     67     /**
     68      * @param {!Array.<Element>} dividers
     69      */
     70     addEventDividers: function(dividers)
     71     {
     72         this._grid.addEventDividers(dividers);
     73     },
     74 
     75     removeEventDividers: function()
     76     {
     77         this._grid.removeEventDividers();
     78     },
     79 
     80     /**
     81      * @param {?number} start
     82      * @param {?number} end
     83      */
     84     setWindowPosition: function(start, end)
     85     {
     86         this._window._setWindowPosition(start, end);
     87     },
     88 
     89     reset: function()
     90     {
     91         this._window.reset();
     92     },
     93 
     94     /**
     95      * @return {number}
     96      */
     97     windowLeft: function()
     98     {
     99         return this._window.windowLeft;
    100     },
    101 
    102     /**
    103      * @return {number}
    104      */
    105     windowRight: function()
    106     {
    107         return this._window.windowRight;
    108     },
    109 
    110     /**
    111      * @param {number} left
    112      * @param {number} right
    113      */
    114     setWindow: function(left, right)
    115     {
    116         this._window._setWindow(left, right);
    117     },
    118 
    119     /**
    120      * @param {string} eventType
    121      * @param {function(WebInspector.Event)} listener
    122      * @param {Object=} thisObject
    123      */
    124     addEventListener: function(eventType, listener, thisObject)
    125     {
    126         this._window.addEventListener(eventType, listener, thisObject);
    127     },
    128 
    129     /**
    130      * @param {!number} zoomFactor
    131      * @param {!number} referencePoint
    132      */
    133     zoom: function(zoomFactor, referencePoint)
    134     {
    135         this._window._zoom(zoomFactor, referencePoint);
    136     },
    137 
    138     /**
    139      * @param {boolean} enabled
    140      */
    141     setResizeEnabled: function(enabled)
    142     {
    143         this._window._setEnabled(!!enabled);
    144     }
    145 }
    146 
    147 
    148 WebInspector.OverviewGrid.MinSelectableSize = 14;
    149 
    150 WebInspector.OverviewGrid.WindowScrollSpeedFactor = .3;
    151 
    152 WebInspector.OverviewGrid.ResizerOffset = 3.5; // half pixel because offset values are not rounded but ceiled
    153 
    154 /**
    155  * @constructor
    156  * @extends {WebInspector.Object}
    157  * @param {Element} parentElement
    158  * @param {Element} dividersLabelBarElement
    159  */
    160 WebInspector.OverviewGrid.Window = function(parentElement, dividersLabelBarElement)
    161 {
    162     this._parentElement = parentElement;
    163     this._dividersLabelBarElement = dividersLabelBarElement;
    164 
    165     WebInspector.installDragHandle(this._parentElement, this._startWindowSelectorDragging.bind(this), this._windowSelectorDragging.bind(this), this._endWindowSelectorDragging.bind(this), "ew-resize");
    166     WebInspector.installDragHandle(this._dividersLabelBarElement, this._startWindowDragging.bind(this), this._windowDragging.bind(this), null, "move");
    167 
    168     this.windowLeft = 0.0;
    169     this.windowRight = 1.0;
    170 
    171     this._parentElement.addEventListener("mousewheel", this._onMouseWheel.bind(this), true);
    172     this._parentElement.addEventListener("dblclick", this._resizeWindowMaximum.bind(this), true);
    173 
    174     this._overviewWindowElement = parentElement.createChild("div", "overview-grid-window");
    175     this._overviewWindowBordersElement = parentElement.createChild("div", "overview-grid-window-rulers");
    176     parentElement.createChild("div", "overview-grid-dividers-background");
    177 
    178     this._leftResizeElement = parentElement.createChild("div", "overview-grid-window-resizer");
    179     this._leftResizeElement.style.left = 0;
    180     WebInspector.installDragHandle(this._leftResizeElement, this._resizerElementStartDragging.bind(this), this._leftResizeElementDragging.bind(this), null, "ew-resize");
    181 
    182     this._rightResizeElement = parentElement.createChild("div", "overview-grid-window-resizer overview-grid-window-resizer-right");
    183     this._rightResizeElement.style.right = 0;
    184     WebInspector.installDragHandle(this._rightResizeElement, this._resizerElementStartDragging.bind(this), this._rightResizeElementDragging.bind(this), null, "ew-resize");
    185     this._setEnabled(true);
    186 }
    187 
    188 WebInspector.OverviewGrid.Events = {
    189     WindowChanged: "WindowChanged"
    190 }
    191 
    192 WebInspector.OverviewGrid.Window.prototype = {
    193     reset: function()
    194     {
    195         this.windowLeft = 0.0;
    196         this.windowRight = 1.0;
    197 
    198         this._overviewWindowElement.style.left = "0%";
    199         this._overviewWindowElement.style.width = "100%";
    200         this._overviewWindowBordersElement.style.left = "0%";
    201         this._overviewWindowBordersElement.style.right = "0%";
    202         this._leftResizeElement.style.left = "0%";
    203         this._rightResizeElement.style.left = "100%";
    204         this._setEnabled(true);
    205     },
    206 
    207     /**
    208      * @param {boolean} enabled
    209      */
    210     _setEnabled: function(enabled)
    211     {
    212         enabled = !!enabled;
    213         if (this._enabled === enabled)
    214             return;
    215         this._enabled = enabled;
    216         this._parentElement.enableStyleClass("resize-enabled", enabled);
    217     },
    218 
    219     /**
    220      * @param {Event} event
    221      */
    222     _resizerElementStartDragging: function(event)
    223     {
    224         if (!this._enabled)
    225             return false;
    226         this._resizerParentOffsetLeft = event.pageX - event.offsetX - event.target.offsetLeft;
    227         event.preventDefault();
    228         return true;
    229     },
    230 
    231     /**
    232      * @param {Event} event
    233      */
    234     _leftResizeElementDragging: function(event)
    235     {
    236         this._resizeWindowLeft(event.pageX - this._resizerParentOffsetLeft);
    237         event.preventDefault();
    238     },
    239 
    240     /**
    241      * @param {Event} event
    242      */
    243     _rightResizeElementDragging: function(event)
    244     {
    245         this._resizeWindowRight(event.pageX - this._resizerParentOffsetLeft);
    246         event.preventDefault();
    247     },
    248 
    249     /**
    250      * @param {Event} event
    251      * @return {boolean}
    252      */
    253     _startWindowSelectorDragging: function(event)
    254     {
    255         if (!this._enabled)
    256             return false;
    257         this._offsetLeft = event.pageX - event.offsetX;
    258         var position = event.pageX - this._offsetLeft;
    259         this._overviewWindowSelector = new WebInspector.OverviewGrid.WindowSelector(this._parentElement, position);
    260         return true;
    261     },
    262 
    263     /**
    264      * @param {Event} event
    265      */
    266     _windowSelectorDragging: function(event)
    267     {
    268         this._overviewWindowSelector._updatePosition(event.pageX - this._offsetLeft);
    269         event.preventDefault();
    270     },
    271 
    272     /**
    273      * @param {Event} event
    274      */
    275     _endWindowSelectorDragging: function(event)
    276     {
    277         var window = this._overviewWindowSelector._close(event.pageX - this._offsetLeft);
    278         delete this._overviewWindowSelector;
    279         if (window.end === window.start) { // Click, not drag.
    280             var middle = window.end;
    281             window.start = Math.max(0, middle - WebInspector.OverviewGrid.MinSelectableSize / 2);
    282             window.end = Math.min(this._parentElement.clientWidth, middle + WebInspector.OverviewGrid.MinSelectableSize / 2);
    283         } else if (window.end - window.start < WebInspector.OverviewGrid.MinSelectableSize) {
    284             if (this._parentElement.clientWidth - window.end > WebInspector.OverviewGrid.MinSelectableSize)
    285                 window.end = window.start + WebInspector.OverviewGrid.MinSelectableSize;
    286             else
    287                 window.start = window.end - WebInspector.OverviewGrid.MinSelectableSize;
    288         }
    289         this._setWindowPosition(window.start, window.end);
    290     },
    291 
    292     /**
    293      * @param {Event} event
    294      * @return {boolean}
    295      */
    296     _startWindowDragging: function(event)
    297     {
    298         this._dragStartPoint = event.pageX;
    299         this._dragStartLeft = this.windowLeft;
    300         this._dragStartRight = this.windowRight;
    301         return true;
    302     },
    303 
    304     /**
    305      * @param {Event} event
    306      */
    307     _windowDragging: function(event)
    308     {
    309         event.preventDefault();
    310         var delta = (event.pageX - this._dragStartPoint) / this._parentElement.clientWidth;
    311         if (this._dragStartLeft + delta < 0)
    312             delta = -this._dragStartLeft;
    313 
    314         if (this._dragStartRight + delta > 1)
    315             delta = 1 - this._dragStartRight;
    316 
    317         this._setWindow(this._dragStartLeft + delta, this._dragStartRight + delta);
    318     },
    319 
    320     /**
    321      * @param {number} start
    322      */
    323     _resizeWindowLeft: function(start)
    324     {
    325         // Glue to edge.
    326         if (start < 10)
    327             start = 0;
    328         else if (start > this._rightResizeElement.offsetLeft -  4)
    329             start = this._rightResizeElement.offsetLeft - 4;
    330         this._setWindowPosition(start, null);
    331     },
    332 
    333     /**
    334      * @param {number} end
    335      */
    336     _resizeWindowRight: function(end)
    337     {
    338         // Glue to edge.
    339         if (end > this._parentElement.clientWidth - 10)
    340             end = this._parentElement.clientWidth;
    341         else if (end < this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.MinSelectableSize)
    342             end = this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.MinSelectableSize;
    343         this._setWindowPosition(null, end);
    344     },
    345 
    346     _resizeWindowMaximum: function()
    347     {
    348         this._setWindowPosition(0, this._parentElement.clientWidth);
    349     },
    350 
    351     /**
    352      * @param {number} windowLeft
    353      * @param {number} windowRight
    354      */
    355     _setWindow: function(windowLeft, windowRight)
    356     {
    357         var left = windowLeft;
    358         var right = windowRight;
    359         var width = windowRight - windowLeft;
    360 
    361         // We allow actual time window to be arbitrarily small but don't want the UI window to be too small.
    362         var widthInPixels = width * this._parentElement.clientWidth;
    363         var minWidthInPixels = WebInspector.OverviewGrid.MinSelectableSize / 2;
    364         if (widthInPixels < minWidthInPixels) {
    365             var factor = minWidthInPixels / widthInPixels;
    366             left = ((windowRight + windowLeft) - width * factor) / 2;
    367             right = ((windowRight + windowLeft) + width * factor) / 2;
    368         }
    369 
    370         this.windowLeft = windowLeft;
    371         this._leftResizeElement.style.left = left * 100 + "%";
    372         this.windowRight = windowRight;
    373         this._rightResizeElement.style.left = right * 100 + "%";
    374 
    375         this._overviewWindowElement.style.left = left * 100 + "%";
    376         this._overviewWindowBordersElement.style.left = left * 100 + "%";
    377         this._overviewWindowElement.style.width = (right - left) * 100 + "%";
    378         this._overviewWindowBordersElement.style.right = (1 - right) * 100 + "%";
    379 
    380         this.dispatchEventToListeners(WebInspector.OverviewGrid.Events.WindowChanged);
    381     },
    382 
    383     /**
    384      * @param {?number} start
    385      * @param {?number} end
    386      */
    387     _setWindowPosition: function(start, end)
    388     {
    389         var clientWidth = this._parentElement.clientWidth;
    390         var windowLeft = typeof start === "number" ? start / clientWidth : this.windowLeft;
    391         var windowRight = typeof end === "number" ? end / clientWidth : this.windowRight;
    392         this._setWindow(windowLeft, windowRight);
    393     },
    394 
    395     /**
    396      * @param {Event} event
    397      */
    398     _onMouseWheel: function(event)
    399     {
    400         if (typeof event.wheelDeltaY === "number" && event.wheelDeltaY) {
    401             const zoomFactor = 1.1;
    402             const mouseWheelZoomSpeed = 1 / 120;
    403 
    404             var reference = event.offsetX / event.target.clientWidth;
    405             this._zoom(Math.pow(zoomFactor, -event.wheelDeltaY * mouseWheelZoomSpeed), reference);
    406         }
    407         if (typeof event.wheelDeltaX === "number" && event.wheelDeltaX) {
    408             var offset = Math.round(event.wheelDeltaX * WebInspector.OverviewGrid.WindowScrollSpeedFactor);
    409             var windowLeft = this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
    410             var windowRight = this._rightResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
    411 
    412             if (windowLeft - offset < 0)
    413                 offset = windowLeft;
    414 
    415             if (windowRight - offset > this._parentElement.clientWidth)
    416                 offset = windowRight - this._parentElement.clientWidth;
    417 
    418             this._setWindowPosition(windowLeft - offset, windowRight - offset);
    419 
    420             event.preventDefault();
    421         }
    422     },
    423 
    424     /**
    425      * @param {number} factor
    426      * @param {number} reference
    427      */
    428     _zoom: function(factor, reference)
    429     {
    430         var left = this.windowLeft;
    431         var right = this.windowRight;
    432         var windowSize = right - left;
    433         var newWindowSize = factor * windowSize;
    434         if (newWindowSize > 1) {
    435             newWindowSize = 1;
    436             factor = newWindowSize / windowSize;
    437         }
    438         left = reference + (left - reference) * factor;
    439         left = Number.constrain(left, 0, 1 - newWindowSize);
    440 
    441         right = reference + (right - reference) * factor;
    442         right = Number.constrain(right, newWindowSize, 1);
    443         this._setWindow(left, right);
    444     },
    445 
    446     __proto__: WebInspector.Object.prototype
    447 }
    448 
    449 /**
    450  * @constructor
    451  */
    452 WebInspector.OverviewGrid.WindowSelector = function(parent, position)
    453 {
    454     this._startPosition = position;
    455     this._width = parent.offsetWidth;
    456     this._windowSelector = document.createElement("div");
    457     this._windowSelector.className = "overview-grid-window-selector";
    458     this._windowSelector.style.left = this._startPosition + "px";
    459     this._windowSelector.style.right = this._width - this._startPosition + "px";
    460     parent.appendChild(this._windowSelector);
    461 }
    462 
    463 WebInspector.OverviewGrid.WindowSelector.prototype = {
    464     _createSelectorElement: function(parent, left, width, height)
    465     {
    466         var selectorElement = document.createElement("div");
    467         selectorElement.className = "overview-grid-window-selector";
    468         selectorElement.style.left = left + "px";
    469         selectorElement.style.width = width + "px";
    470         selectorElement.style.top = "0px";
    471         selectorElement.style.height = height + "px";
    472         parent.appendChild(selectorElement);
    473         return selectorElement;
    474     },
    475 
    476     _close: function(position)
    477     {
    478         position = Math.max(0, Math.min(position, this._width));
    479         this._windowSelector.remove();
    480         return this._startPosition < position ? {start: this._startPosition, end: position} : {start: position, end: this._startPosition};
    481     },
    482 
    483     _updatePosition: function(position)
    484     {
    485         position = Math.max(0, Math.min(position, this._width));
    486         if (position < this._startPosition) {
    487             this._windowSelector.style.left = position + "px";
    488             this._windowSelector.style.right = this._width - this._startPosition + "px";
    489         } else {
    490             this._windowSelector.style.left = this._startPosition + "px";
    491             this._windowSelector.style.right = this._width - position + "px";
    492         }
    493     }
    494 }
    495