| OLD | NEW |
| (Empty) |
| 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 * @unrestricted | |
| 33 */ | |
| 34 UI.OverviewGrid = class { | |
| 35 /** | |
| 36 * @param {string} prefix | |
| 37 */ | |
| 38 constructor(prefix) { | |
| 39 this.element = createElement('div'); | |
| 40 this.element.id = prefix + '-overview-container'; | |
| 41 | |
| 42 this._grid = new UI.TimelineGrid(); | |
| 43 this._grid.element.id = prefix + '-overview-grid'; | |
| 44 this._grid.setScrollTop(0); | |
| 45 | |
| 46 this.element.appendChild(this._grid.element); | |
| 47 | |
| 48 this._window = new UI.OverviewGrid.Window(this.element, this._grid.dividersL
abelBarElement); | |
| 49 } | |
| 50 | |
| 51 /** | |
| 52 * @return {number} | |
| 53 */ | |
| 54 clientWidth() { | |
| 55 return this.element.clientWidth; | |
| 56 } | |
| 57 | |
| 58 /** | |
| 59 * @param {!UI.TimelineGrid.Calculator} calculator | |
| 60 */ | |
| 61 updateDividers(calculator) { | |
| 62 this._grid.updateDividers(calculator); | |
| 63 } | |
| 64 | |
| 65 /** | |
| 66 * @param {!Array.<!Element>} dividers | |
| 67 */ | |
| 68 addEventDividers(dividers) { | |
| 69 this._grid.addEventDividers(dividers); | |
| 70 } | |
| 71 | |
| 72 removeEventDividers() { | |
| 73 this._grid.removeEventDividers(); | |
| 74 } | |
| 75 | |
| 76 reset() { | |
| 77 this._window.reset(); | |
| 78 } | |
| 79 | |
| 80 /** | |
| 81 * @return {number} | |
| 82 */ | |
| 83 windowLeft() { | |
| 84 return this._window.windowLeft; | |
| 85 } | |
| 86 | |
| 87 /** | |
| 88 * @return {number} | |
| 89 */ | |
| 90 windowRight() { | |
| 91 return this._window.windowRight; | |
| 92 } | |
| 93 | |
| 94 /** | |
| 95 * @param {number} left | |
| 96 * @param {number} right | |
| 97 */ | |
| 98 setWindow(left, right) { | |
| 99 this._window._setWindow(left, right); | |
| 100 } | |
| 101 | |
| 102 /** | |
| 103 * @param {symbol} eventType | |
| 104 * @param {function(!Common.Event)} listener | |
| 105 * @param {!Object=} thisObject | |
| 106 * @return {!Common.EventTarget.EventDescriptor} | |
| 107 */ | |
| 108 addEventListener(eventType, listener, thisObject) { | |
| 109 return this._window.addEventListener(eventType, listener, thisObject); | |
| 110 } | |
| 111 | |
| 112 /** | |
| 113 * @param {?function(!Event):boolean} clickHandler | |
| 114 */ | |
| 115 setClickHandler(clickHandler) { | |
| 116 this._window.setClickHandler(clickHandler); | |
| 117 } | |
| 118 | |
| 119 /** | |
| 120 * @param {number} zoomFactor | |
| 121 * @param {number} referencePoint | |
| 122 */ | |
| 123 zoom(zoomFactor, referencePoint) { | |
| 124 this._window._zoom(zoomFactor, referencePoint); | |
| 125 } | |
| 126 | |
| 127 /** | |
| 128 * @param {boolean} enabled | |
| 129 */ | |
| 130 setResizeEnabled(enabled) { | |
| 131 this._window.setEnabled(enabled); | |
| 132 } | |
| 133 }; | |
| 134 | |
| 135 UI.OverviewGrid.MinSelectableSize = 14; | |
| 136 | |
| 137 UI.OverviewGrid.WindowScrollSpeedFactor = .3; | |
| 138 | |
| 139 UI.OverviewGrid.ResizerOffset = 3.5; // half pixel because offset values are no
t rounded but ceiled | |
| 140 | |
| 141 /** | |
| 142 * @unrestricted | |
| 143 */ | |
| 144 UI.OverviewGrid.Window = class extends Common.Object { | |
| 145 /** | |
| 146 * @param {!Element} parentElement | |
| 147 * @param {!Element=} dividersLabelBarElement | |
| 148 */ | |
| 149 constructor(parentElement, dividersLabelBarElement) { | |
| 150 super(); | |
| 151 this._parentElement = parentElement; | |
| 152 | |
| 153 UI.installDragHandle( | |
| 154 this._parentElement, this._startWindowSelectorDragging.bind(this), this.
_windowSelectorDragging.bind(this), | |
| 155 this._endWindowSelectorDragging.bind(this), 'text', null); | |
| 156 if (dividersLabelBarElement) { | |
| 157 UI.installDragHandle( | |
| 158 dividersLabelBarElement, this._startWindowDragging.bind(this), this._w
indowDragging.bind(this), null, | |
| 159 '-webkit-grabbing', '-webkit-grab'); | |
| 160 } | |
| 161 | |
| 162 this._parentElement.addEventListener('mousewheel', this._onMouseWheel.bind(t
his), true); | |
| 163 this._parentElement.addEventListener('dblclick', this._resizeWindowMaximum.b
ind(this), true); | |
| 164 UI.appendStyle(this._parentElement, 'ui_lazy/overviewGrid.css'); | |
| 165 | |
| 166 this._leftResizeElement = parentElement.createChild('div', 'overview-grid-wi
ndow-resizer'); | |
| 167 UI.installDragHandle( | |
| 168 this._leftResizeElement, this._resizerElementStartDragging.bind(this), | |
| 169 this._leftResizeElementDragging.bind(this), null, 'ew-resize'); | |
| 170 this._rightResizeElement = parentElement.createChild('div', 'overview-grid-w
indow-resizer'); | |
| 171 UI.installDragHandle( | |
| 172 this._rightResizeElement, this._resizerElementStartDragging.bind(this), | |
| 173 this._rightResizeElementDragging.bind(this), null, 'ew-resize'); | |
| 174 | |
| 175 this._leftCurtainElement = parentElement.createChild('div', 'window-curtain-
left'); | |
| 176 this._rightCurtainElement = parentElement.createChild('div', 'window-curtain
-right'); | |
| 177 this.reset(); | |
| 178 } | |
| 179 | |
| 180 reset() { | |
| 181 this.windowLeft = 0.0; | |
| 182 this.windowRight = 1.0; | |
| 183 this.setEnabled(true); | |
| 184 this._updateCurtains(); | |
| 185 } | |
| 186 | |
| 187 /** | |
| 188 * @param {boolean} enabled | |
| 189 */ | |
| 190 setEnabled(enabled) { | |
| 191 this._enabled = enabled; | |
| 192 } | |
| 193 | |
| 194 /** | |
| 195 * @param {?function(!Event):boolean} clickHandler | |
| 196 */ | |
| 197 setClickHandler(clickHandler) { | |
| 198 this._clickHandler = clickHandler; | |
| 199 } | |
| 200 | |
| 201 /** | |
| 202 * @param {!Event} event | |
| 203 */ | |
| 204 _resizerElementStartDragging(event) { | |
| 205 if (!this._enabled) | |
| 206 return false; | |
| 207 this._resizerParentOffsetLeft = event.pageX - event.offsetX - event.target.o
ffsetLeft; | |
| 208 event.stopPropagation(); | |
| 209 return true; | |
| 210 } | |
| 211 | |
| 212 /** | |
| 213 * @param {!Event} event | |
| 214 */ | |
| 215 _leftResizeElementDragging(event) { | |
| 216 this._resizeWindowLeft(event.pageX - this._resizerParentOffsetLeft); | |
| 217 event.preventDefault(); | |
| 218 } | |
| 219 | |
| 220 /** | |
| 221 * @param {!Event} event | |
| 222 */ | |
| 223 _rightResizeElementDragging(event) { | |
| 224 this._resizeWindowRight(event.pageX - this._resizerParentOffsetLeft); | |
| 225 event.preventDefault(); | |
| 226 } | |
| 227 | |
| 228 /** | |
| 229 * @param {!Event} event | |
| 230 * @return {boolean} | |
| 231 */ | |
| 232 _startWindowSelectorDragging(event) { | |
| 233 if (!this._enabled) | |
| 234 return false; | |
| 235 this._offsetLeft = this._parentElement.totalOffsetLeft(); | |
| 236 var position = event.x - this._offsetLeft; | |
| 237 this._overviewWindowSelector = new UI.OverviewGrid.WindowSelector(this._pare
ntElement, position); | |
| 238 return true; | |
| 239 } | |
| 240 | |
| 241 /** | |
| 242 * @param {!Event} event | |
| 243 */ | |
| 244 _windowSelectorDragging(event) { | |
| 245 this._overviewWindowSelector._updatePosition(event.x - this._offsetLeft); | |
| 246 event.preventDefault(); | |
| 247 } | |
| 248 | |
| 249 /** | |
| 250 * @param {!Event} event | |
| 251 */ | |
| 252 _endWindowSelectorDragging(event) { | |
| 253 var window = this._overviewWindowSelector._close(event.x - this._offsetLeft)
; | |
| 254 delete this._overviewWindowSelector; | |
| 255 var clickThreshold = 3; | |
| 256 if (window.end - window.start < clickThreshold) { | |
| 257 if (this._clickHandler && this._clickHandler.call(null, event)) | |
| 258 return; | |
| 259 var middle = window.end; | |
| 260 window.start = Math.max(0, middle - UI.OverviewGrid.MinSelectableSize / 2)
; | |
| 261 window.end = Math.min(this._parentElement.clientWidth, middle + UI.Overvie
wGrid.MinSelectableSize / 2); | |
| 262 } else if (window.end - window.start < UI.OverviewGrid.MinSelectableSize) { | |
| 263 if (this._parentElement.clientWidth - window.end > UI.OverviewGrid.MinSele
ctableSize) | |
| 264 window.end = window.start + UI.OverviewGrid.MinSelectableSize; | |
| 265 else | |
| 266 window.start = window.end - UI.OverviewGrid.MinSelectableSize; | |
| 267 } | |
| 268 this._setWindowPosition(window.start, window.end); | |
| 269 } | |
| 270 | |
| 271 /** | |
| 272 * @param {!Event} event | |
| 273 * @return {boolean} | |
| 274 */ | |
| 275 _startWindowDragging(event) { | |
| 276 this._dragStartPoint = event.pageX; | |
| 277 this._dragStartLeft = this.windowLeft; | |
| 278 this._dragStartRight = this.windowRight; | |
| 279 event.stopPropagation(); | |
| 280 return true; | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * @param {!Event} event | |
| 285 */ | |
| 286 _windowDragging(event) { | |
| 287 event.preventDefault(); | |
| 288 var delta = (event.pageX - this._dragStartPoint) / this._parentElement.clien
tWidth; | |
| 289 if (this._dragStartLeft + delta < 0) | |
| 290 delta = -this._dragStartLeft; | |
| 291 | |
| 292 if (this._dragStartRight + delta > 1) | |
| 293 delta = 1 - this._dragStartRight; | |
| 294 | |
| 295 this._setWindow(this._dragStartLeft + delta, this._dragStartRight + delta); | |
| 296 } | |
| 297 | |
| 298 /** | |
| 299 * @param {number} start | |
| 300 */ | |
| 301 _resizeWindowLeft(start) { | |
| 302 // Glue to edge. | |
| 303 if (start < 10) | |
| 304 start = 0; | |
| 305 else if (start > this._rightResizeElement.offsetLeft - 4) | |
| 306 start = this._rightResizeElement.offsetLeft - 4; | |
| 307 this._setWindowPosition(start, null); | |
| 308 } | |
| 309 | |
| 310 /** | |
| 311 * @param {number} end | |
| 312 */ | |
| 313 _resizeWindowRight(end) { | |
| 314 // Glue to edge. | |
| 315 if (end > this._parentElement.clientWidth - 10) | |
| 316 end = this._parentElement.clientWidth; | |
| 317 else if (end < this._leftResizeElement.offsetLeft + UI.OverviewGrid.MinSelec
tableSize) | |
| 318 end = this._leftResizeElement.offsetLeft + UI.OverviewGrid.MinSelectableSi
ze; | |
| 319 this._setWindowPosition(null, end); | |
| 320 } | |
| 321 | |
| 322 _resizeWindowMaximum() { | |
| 323 this._setWindowPosition(0, this._parentElement.clientWidth); | |
| 324 } | |
| 325 | |
| 326 /** | |
| 327 * @param {number} windowLeft | |
| 328 * @param {number} windowRight | |
| 329 */ | |
| 330 _setWindow(windowLeft, windowRight) { | |
| 331 this.windowLeft = windowLeft; | |
| 332 this.windowRight = windowRight; | |
| 333 this._updateCurtains(); | |
| 334 this.dispatchEventToListeners(UI.OverviewGrid.Events.WindowChanged); | |
| 335 } | |
| 336 | |
| 337 _updateCurtains() { | |
| 338 var left = this.windowLeft; | |
| 339 var right = this.windowRight; | |
| 340 var width = right - left; | |
| 341 | |
| 342 // We allow actual time window to be arbitrarily small but don't want the UI
window to be too small. | |
| 343 var widthInPixels = width * this._parentElement.clientWidth; | |
| 344 var minWidthInPixels = UI.OverviewGrid.MinSelectableSize / 2; | |
| 345 if (widthInPixels < minWidthInPixels) { | |
| 346 var factor = minWidthInPixels / widthInPixels; | |
| 347 left = ((this.windowRight + this.windowLeft) - width * factor) / 2; | |
| 348 right = ((this.windowRight + this.windowLeft) + width * factor) / 2; | |
| 349 } | |
| 350 this._leftResizeElement.style.left = (100 * left).toFixed(2) + '%'; | |
| 351 this._rightResizeElement.style.left = (100 * right).toFixed(2) + '%'; | |
| 352 | |
| 353 this._leftCurtainElement.style.width = (100 * left).toFixed(2) + '%'; | |
| 354 this._rightCurtainElement.style.width = (100 * (1 - right)).toFixed(2) + '%'
; | |
| 355 } | |
| 356 | |
| 357 /** | |
| 358 * @param {?number} start | |
| 359 * @param {?number} end | |
| 360 */ | |
| 361 _setWindowPosition(start, end) { | |
| 362 var clientWidth = this._parentElement.clientWidth; | |
| 363 var windowLeft = typeof start === 'number' ? start / clientWidth : this.wind
owLeft; | |
| 364 var windowRight = typeof end === 'number' ? end / clientWidth : this.windowR
ight; | |
| 365 this._setWindow(windowLeft, windowRight); | |
| 366 } | |
| 367 | |
| 368 /** | |
| 369 * @param {!Event} event | |
| 370 */ | |
| 371 _onMouseWheel(event) { | |
| 372 if (!this._enabled) | |
| 373 return; | |
| 374 if (typeof event.wheelDeltaY === 'number' && event.wheelDeltaY) { | |
| 375 const zoomFactor = 1.1; | |
| 376 const mouseWheelZoomSpeed = 1 / 120; | |
| 377 | |
| 378 var reference = event.offsetX / event.target.clientWidth; | |
| 379 this._zoom(Math.pow(zoomFactor, -event.wheelDeltaY * mouseWheelZoomSpeed),
reference); | |
| 380 } | |
| 381 if (typeof event.wheelDeltaX === 'number' && event.wheelDeltaX) { | |
| 382 var offset = Math.round(event.wheelDeltaX * UI.OverviewGrid.WindowScrollSp
eedFactor); | |
| 383 var windowLeft = this._leftResizeElement.offsetLeft + UI.OverviewGrid.Resi
zerOffset; | |
| 384 var windowRight = this._rightResizeElement.offsetLeft + UI.OverviewGrid.Re
sizerOffset; | |
| 385 | |
| 386 if (windowLeft - offset < 0) | |
| 387 offset = windowLeft; | |
| 388 | |
| 389 if (windowRight - offset > this._parentElement.clientWidth) | |
| 390 offset = windowRight - this._parentElement.clientWidth; | |
| 391 | |
| 392 this._setWindowPosition(windowLeft - offset, windowRight - offset); | |
| 393 | |
| 394 event.preventDefault(); | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 /** | |
| 399 * @param {number} factor | |
| 400 * @param {number} reference | |
| 401 */ | |
| 402 _zoom(factor, reference) { | |
| 403 var left = this.windowLeft; | |
| 404 var right = this.windowRight; | |
| 405 var windowSize = right - left; | |
| 406 var newWindowSize = factor * windowSize; | |
| 407 if (newWindowSize > 1) { | |
| 408 newWindowSize = 1; | |
| 409 factor = newWindowSize / windowSize; | |
| 410 } | |
| 411 left = reference + (left - reference) * factor; | |
| 412 left = Number.constrain(left, 0, 1 - newWindowSize); | |
| 413 | |
| 414 right = reference + (right - reference) * factor; | |
| 415 right = Number.constrain(right, newWindowSize, 1); | |
| 416 this._setWindow(left, right); | |
| 417 } | |
| 418 }; | |
| 419 | |
| 420 /** @enum {symbol} */ | |
| 421 UI.OverviewGrid.Events = { | |
| 422 WindowChanged: Symbol('WindowChanged') | |
| 423 }; | |
| 424 | |
| 425 /** | |
| 426 * @unrestricted | |
| 427 */ | |
| 428 UI.OverviewGrid.WindowSelector = class { | |
| 429 constructor(parent, position) { | |
| 430 this._startPosition = position; | |
| 431 this._width = parent.offsetWidth; | |
| 432 this._windowSelector = createElement('div'); | |
| 433 this._windowSelector.className = 'overview-grid-window-selector'; | |
| 434 this._windowSelector.style.left = this._startPosition + 'px'; | |
| 435 this._windowSelector.style.right = this._width - this._startPosition + 'px'; | |
| 436 parent.appendChild(this._windowSelector); | |
| 437 } | |
| 438 | |
| 439 _close(position) { | |
| 440 position = Math.max(0, Math.min(position, this._width)); | |
| 441 this._windowSelector.remove(); | |
| 442 return this._startPosition < position ? {start: this._startPosition, end: po
sition} : | |
| 443 {start: position, end: this._startPo
sition}; | |
| 444 } | |
| 445 | |
| 446 _updatePosition(position) { | |
| 447 position = Math.max(0, Math.min(position, this._width)); | |
| 448 if (position < this._startPosition) { | |
| 449 this._windowSelector.style.left = position + 'px'; | |
| 450 this._windowSelector.style.right = this._width - this._startPosition + 'px
'; | |
| 451 } else { | |
| 452 this._windowSelector.style.left = this._startPosition + 'px'; | |
| 453 this._windowSelector.style.right = this._width - position + 'px'; | |
| 454 } | |
| 455 } | |
| 456 }; | |
| OLD | NEW |