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 |