OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. | |
3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> | |
4 * Copyright (C) 2009 Google Inc. All rights reserved. | |
5 * | |
6 * Redistribution and use in source and binary forms, with or without | |
7 * modification, are permitted provided that the following conditions | |
8 * are met: | |
9 * | |
10 * 1. Redistributions of source code must retain the above copyright | |
11 * notice, this list of conditions and the following disclaimer. | |
12 * 2. Redistributions in binary form must reproduce the above copyright | |
13 * notice, this list of conditions and the following disclaimer in the | |
14 * documentation and/or other materials provided with the distribution. | |
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | |
16 * its contributors may be used to endorse or promote products derived | |
17 * from this software without specific prior written permission. | |
18 * | |
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | |
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 */ | |
30 | |
31 /** | |
32 * @constructor | |
33 */ | |
34 WebInspector.TimelineGrid = function() | |
35 { | |
36 this.element = createElement("div"); | |
37 | |
38 this._dividersElement = this.element.createChild("div", "resources-dividers"
); | |
39 | |
40 this._gridHeaderElement = createElement("div"); | |
41 this._gridHeaderElement.id = "timeline-grid-header"; | |
42 this._eventDividersElement = this._gridHeaderElement.createChild("div", "res
ources-event-dividers"); | |
43 this._dividersLabelBarElement = this._gridHeaderElement.createChild("div", "
resources-dividers-label-bar"); | |
44 this.element.appendChild(this._gridHeaderElement); | |
45 | |
46 this._leftCurtainElement = this.element.createChild("div", "timeline-cpu-cur
tain-left"); | |
47 this._rightCurtainElement = this.element.createChild("div", "timeline-cpu-cu
rtain-right"); | |
48 } | |
49 | |
50 /** | |
51 * @param {!WebInspector.TimelineGrid.Calculator} calculator | |
52 * @return {!{offsets: !Array.<number>, precision: number}} | |
53 */ | |
54 WebInspector.TimelineGrid.calculateDividerOffsets = function(calculator) | |
55 { | |
56 const minGridSlicePx = 64; // minimal distance between grid lines. | |
57 const gridFreeZoneAtLeftPx = 50; | |
58 | |
59 var clientWidth = calculator.computePosition(calculator.maximumBoundary()); | |
60 var dividersCount = clientWidth / minGridSlicePx; | |
61 var gridSliceTime = calculator.boundarySpan() / dividersCount; | |
62 var pixelsPerTime = clientWidth / calculator.boundarySpan(); | |
63 | |
64 // Align gridSliceTime to a nearest round value. | |
65 // We allow spans that fit into the formula: span = (1|2|5)x10^n, | |
66 // e.g.: ... .1 .2 .5 1 2 5 10 20 50 ... | |
67 // After a span has been chosen make grid lines at multiples of the span. | |
68 | |
69 var logGridSliceTime = Math.ceil(Math.log(gridSliceTime) / Math.LN10); | |
70 gridSliceTime = Math.pow(10, logGridSliceTime); | |
71 if (gridSliceTime * pixelsPerTime >= 5 * minGridSlicePx) | |
72 gridSliceTime = gridSliceTime / 5; | |
73 if (gridSliceTime * pixelsPerTime >= 2 * minGridSlicePx) | |
74 gridSliceTime = gridSliceTime / 2; | |
75 | |
76 var firstDividerTime = Math.ceil((calculator.minimumBoundary() - calculator.
zeroTime()) / gridSliceTime) * gridSliceTime + calculator.zeroTime(); | |
77 var lastDividerTime = calculator.maximumBoundary(); | |
78 // Add some extra space past the right boundary as the rightmost divider lab
el text | |
79 // may be partially shown rather than just pop up when a new rightmost divid
er gets into the view. | |
80 if (calculator.paddingLeft() > 0) | |
81 lastDividerTime = lastDividerTime + minGridSlicePx / pixelsPerTime; | |
82 dividersCount = Math.ceil((lastDividerTime - firstDividerTime) / gridSliceTi
me); | |
83 | |
84 var skipLeftmostDividers = calculator.paddingLeft() === 0; | |
85 | |
86 if (!gridSliceTime) | |
87 dividersCount = 0; | |
88 | |
89 var offsets = []; | |
90 for (var i = 0; i < dividersCount; ++i) { | |
91 var left = calculator.computePosition(firstDividerTime + gridSliceTime *
i); | |
92 if (skipLeftmostDividers && left < gridFreeZoneAtLeftPx) | |
93 continue; | |
94 offsets.push(firstDividerTime + gridSliceTime * i); | |
95 } | |
96 | |
97 return {offsets: offsets, precision: Math.max(0, -Math.floor(Math.log(gridSl
iceTime * 1.01) / Math.LN10))}; | |
98 } | |
99 | |
100 /** | |
101 * @param {!Object} canvas | |
102 * @param {!WebInspector.TimelineGrid.Calculator} calculator | |
103 * @param {?Array.<number>=} dividerOffsets | |
104 */ | |
105 WebInspector.TimelineGrid.drawCanvasGrid = function(canvas, calculator, dividerO
ffsets) | |
106 { | |
107 var context = canvas.getContext("2d"); | |
108 context.save(); | |
109 var ratio = window.devicePixelRatio; | |
110 context.scale(ratio, ratio); | |
111 var printDeltas = !!dividerOffsets; | |
112 var width = canvas.width / window.devicePixelRatio; | |
113 var height = canvas.height / window.devicePixelRatio; | |
114 var precision = 0; | |
115 if (!dividerOffsets) { | |
116 var dividersData = WebInspector.TimelineGrid.calculateDividerOffsets(cal
culator); | |
117 dividerOffsets = dividersData.offsets; | |
118 precision = dividersData.precision; | |
119 } | |
120 | |
121 context.fillStyle = "rgba(255, 255, 255, 0.5)"; | |
122 context.fillRect(0, 0, width, 15); | |
123 | |
124 context.fillStyle = "#333"; | |
125 context.strokeStyle = "rgba(0, 0, 0, 0.1)"; | |
126 context.textBaseline = "hanging"; | |
127 context.font = (printDeltas ? "italic bold 11px " : " 11px ") + WebInspector
.fontFamily(); | |
128 context.lineWidth = 1; | |
129 | |
130 context.translate(0.5, 0.5); | |
131 const minWidthForTitle = 60; | |
132 var lastPosition = 0; | |
133 var time = 0; | |
134 var lastTime = 0; | |
135 var paddingRight = 4; | |
136 var paddingTop = 3; | |
137 for (var i = 0; i < dividerOffsets.length; ++i) { | |
138 time = dividerOffsets[i]; | |
139 var position = calculator.computePosition(time); | |
140 context.beginPath(); | |
141 if (position - lastPosition > minWidthForTitle) { | |
142 if (!printDeltas || i !== 0) { | |
143 var text = printDeltas ? calculator.formatTime(calculator.zeroTi
me() + time - lastTime) : calculator.formatTime(time, precision); | |
144 var textWidth = context.measureText(text).width; | |
145 var textPosition = printDeltas ? (position + lastPosition - text
Width) / 2 : position - textWidth - paddingRight; | |
146 context.fillText(text, textPosition, paddingTop); | |
147 } | |
148 } | |
149 context.moveTo(position, 0); | |
150 context.lineTo(position, height); | |
151 context.stroke(); | |
152 lastTime = time; | |
153 lastPosition = position; | |
154 } | |
155 context.restore(); | |
156 }, | |
157 | |
158 WebInspector.TimelineGrid.prototype = { | |
159 get dividersElement() | |
160 { | |
161 return this._dividersElement; | |
162 }, | |
163 | |
164 get dividersLabelBarElement() | |
165 { | |
166 return this._dividersLabelBarElement; | |
167 }, | |
168 | |
169 removeDividers: function() | |
170 { | |
171 this._dividersElement.removeChildren(); | |
172 this._dividersLabelBarElement.removeChildren(); | |
173 }, | |
174 | |
175 /** | |
176 * @param {!WebInspector.TimelineGrid.Calculator} calculator | |
177 * @param {?Array.<number>=} dividerOffsets | |
178 * @param {boolean=} printDeltas | |
179 * @return {boolean} | |
180 */ | |
181 updateDividers: function(calculator, dividerOffsets, printDeltas) | |
182 { | |
183 var precision = 0; | |
184 if (!dividerOffsets) { | |
185 var dividersData = WebInspector.TimelineGrid.calculateDividerOffsets
(calculator); | |
186 dividerOffsets = dividersData.offsets; | |
187 precision = dividersData.precision; | |
188 printDeltas = false; | |
189 } | |
190 | |
191 var dividersElementClientWidth = this._dividersElement.clientWidth; | |
192 | |
193 // Reuse divider elements and labels. | |
194 var divider = /** @type {?Element} */ (this._dividersElement.firstChild)
; | |
195 var dividerLabelBar = /** @type {?Element} */ (this._dividersLabelBarEle
ment.firstChild); | |
196 | |
197 const minWidthForTitle = 60; | |
198 var lastPosition = 0; | |
199 var lastTime = 0; | |
200 for (var i = 0; i < dividerOffsets.length; ++i) { | |
201 if (!divider) { | |
202 divider = createElement("div"); | |
203 divider.className = "resources-divider"; | |
204 this._dividersElement.appendChild(divider); | |
205 | |
206 dividerLabelBar = createElement("div"); | |
207 dividerLabelBar.className = "resources-divider"; | |
208 var label = createElement("div"); | |
209 label.className = "resources-divider-label"; | |
210 dividerLabelBar._labelElement = label; | |
211 dividerLabelBar.appendChild(label); | |
212 this._dividersLabelBarElement.appendChild(dividerLabelBar); | |
213 } | |
214 | |
215 var time = dividerOffsets[i]; | |
216 var position = calculator.computePosition(time); | |
217 if (position - lastPosition > minWidthForTitle) | |
218 dividerLabelBar._labelElement.textContent = printDeltas ? calcul
ator.formatTime(time - lastTime) : calculator.formatTime(time, precision); | |
219 else | |
220 dividerLabelBar._labelElement.textContent = ""; | |
221 | |
222 if (printDeltas) | |
223 dividerLabelBar._labelElement.style.width = Math.ceil(position -
lastPosition) + "px"; | |
224 else | |
225 dividerLabelBar._labelElement.style.removeProperty("width"); | |
226 | |
227 lastPosition = position; | |
228 lastTime = time; | |
229 var percentLeft = 100 * position / dividersElementClientWidth; | |
230 divider.style.left = percentLeft + "%"; | |
231 dividerLabelBar.style.left = percentLeft + "%"; | |
232 | |
233 divider = /** @type {?Element} */ (divider.nextSibling); | |
234 dividerLabelBar = /** @type {?Element} */ (dividerLabelBar.nextSibli
ng); | |
235 } | |
236 | |
237 // Remove extras. | |
238 while (divider) { | |
239 var nextDivider = divider.nextSibling; | |
240 this._dividersElement.removeChild(divider); | |
241 divider = nextDivider; | |
242 } | |
243 while (dividerLabelBar) { | |
244 var nextDivider = dividerLabelBar.nextSibling; | |
245 this._dividersLabelBarElement.removeChild(dividerLabelBar); | |
246 dividerLabelBar = nextDivider; | |
247 } | |
248 return true; | |
249 }, | |
250 | |
251 addEventDivider: function(divider) | |
252 { | |
253 this._eventDividersElement.appendChild(divider); | |
254 }, | |
255 | |
256 addEventDividers: function(dividers) | |
257 { | |
258 this._gridHeaderElement.removeChild(this._eventDividersElement); | |
259 for (var i = 0; i < dividers.length; ++i) { | |
260 if (dividers[i]) | |
261 this._eventDividersElement.appendChild(dividers[i]); | |
262 } | |
263 this._gridHeaderElement.appendChild(this._eventDividersElement); | |
264 }, | |
265 | |
266 removeEventDividers: function() | |
267 { | |
268 this._eventDividersElement.removeChildren(); | |
269 }, | |
270 | |
271 hideEventDividers: function() | |
272 { | |
273 this._eventDividersElement.classList.add("hidden"); | |
274 }, | |
275 | |
276 showEventDividers: function() | |
277 { | |
278 this._eventDividersElement.classList.remove("hidden"); | |
279 }, | |
280 | |
281 hideDividers: function() | |
282 { | |
283 this._dividersElement.classList.add("hidden"); | |
284 }, | |
285 | |
286 showDividers: function() | |
287 { | |
288 this._dividersElement.classList.remove("hidden"); | |
289 }, | |
290 | |
291 hideCurtains: function() | |
292 { | |
293 this._leftCurtainElement.classList.add("hidden"); | |
294 this._rightCurtainElement.classList.add("hidden"); | |
295 }, | |
296 | |
297 /** | |
298 * @param {number} gapOffset | |
299 * @param {number} gapWidth | |
300 */ | |
301 showCurtains: function(gapOffset, gapWidth) | |
302 { | |
303 this._leftCurtainElement.style.width = gapOffset + "px"; | |
304 this._leftCurtainElement.classList.remove("hidden"); | |
305 this._rightCurtainElement.style.left = (gapOffset + gapWidth) + "px"; | |
306 this._rightCurtainElement.classList.remove("hidden"); | |
307 }, | |
308 | |
309 setScrollAndDividerTop: function(scrollTop, dividersTop) | |
310 { | |
311 this._dividersLabelBarElement.style.top = scrollTop + "px"; | |
312 this._eventDividersElement.style.top = scrollTop + "px"; | |
313 this._leftCurtainElement.style.top = scrollTop + "px"; | |
314 this._rightCurtainElement.style.top = scrollTop + "px"; | |
315 } | |
316 } | |
317 | |
318 /** | |
319 * @interface | |
320 */ | |
321 WebInspector.TimelineGrid.Calculator = function() { } | |
322 | |
323 WebInspector.TimelineGrid.Calculator.prototype = { | |
324 /** | |
325 * @return {number} | |
326 */ | |
327 paddingLeft: function() { }, | |
328 | |
329 /** | |
330 * @param {number} time | |
331 * @return {number} | |
332 */ | |
333 computePosition: function(time) { }, | |
334 | |
335 /** | |
336 * @param {number} time | |
337 * @param {number=} precision | |
338 * @return {string} | |
339 */ | |
340 formatTime: function(time, precision) { }, | |
341 | |
342 /** @return {number} */ | |
343 minimumBoundary: function() { }, | |
344 | |
345 /** @return {number} */ | |
346 zeroTime: function() { }, | |
347 | |
348 /** @return {number} */ | |
349 maximumBoundary: function() { }, | |
350 | |
351 /** @return {number} */ | |
352 boundarySpan: function() { } | |
353 } | |
OLD | NEW |