| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 | 5 |
| 6 /** | 6 /** |
| 7 * @fileoverview Renders an array of slices into the provided div, | 7 * @fileoverview Renders an array of slices into the provided div, |
| 8 * using a child canvas element. Uses a FastRectRenderer to draw only | 8 * using a child canvas element. Uses a FastRectRenderer to draw only |
| 9 * the visible slices. | 9 * the visible slices. |
| 10 */ | 10 */ |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 } | 31 } |
| 32 function colorToString(c) { | 32 function colorToString(c) { |
| 33 return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')'; | 33 return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')'; |
| 34 } | 34 } |
| 35 | 35 |
| 36 const selectedIdBoost = palletteBase.length; | 36 const selectedIdBoost = palletteBase.length; |
| 37 | 37 |
| 38 const pallette = palletteBase.concat(palletteBase.map(brighten)). | 38 const pallette = palletteBase.concat(palletteBase.map(brighten)). |
| 39 map(colorToString); | 39 map(colorToString); |
| 40 | 40 |
| 41 var textWidthMap = { }; |
| 42 function quickMeasureText(ctx, text) { |
| 43 var w = textWidthMap[text]; |
| 44 if (!w) { |
| 45 w = ctx.measureText(text).width; |
| 46 textWidthMap[text] = w; |
| 47 } |
| 48 return w; |
| 49 } |
| 50 |
| 51 /** |
| 52 * Generic base class for timeline tracks |
| 53 */ |
| 54 TimelineThreadTrack = cr.ui.define('div'); |
| 55 TimelineThreadTrack.prototype = { |
| 56 __proto__: HTMLDivElement.prototype, |
| 57 |
| 58 decorate: function() { |
| 59 this.className = 'timeline-thread-track'; |
| 60 }, |
| 61 |
| 62 set thread(thread) { |
| 63 this.thread_ = thread; |
| 64 this.updateChildTracks_(); |
| 65 }, |
| 66 |
| 67 set viewport(v) { |
| 68 this.viewport_ = v; |
| 69 for (var i = 0; i < this.tracks_.length; i++) |
| 70 this.tracks_[i].viewport = v; |
| 71 this.invalidate(); |
| 72 }, |
| 73 |
| 74 invalidate: function() { |
| 75 if (this.parentNode) |
| 76 this.parentNode.invalidate(); |
| 77 }, |
| 78 |
| 79 onResize: function() { |
| 80 for (var i = 0; i < this.tracks_.length; i++) |
| 81 this.tracks_[i].onResize(); |
| 82 }, |
| 83 |
| 84 get firstCanvas() { |
| 85 if (this.tracks_.length) |
| 86 return this.tracks_[0].firstCanvas; |
| 87 return undefined; |
| 88 }, |
| 89 |
| 90 redraw: function() { |
| 91 for (var i = 0; i < this.tracks_.length; i++) |
| 92 this.tracks_[i].redraw(); |
| 93 }, |
| 94 |
| 95 updateChildTracks_: function() { |
| 96 this.textContent = ''; |
| 97 this.tracks_ = []; |
| 98 if (this.thread_) { |
| 99 for (var srI = 0; srI < this.thread_.subRows.length; ++srI) { |
| 100 var track = new TimelineSliceTrack(); |
| 101 |
| 102 if (srI == 0) |
| 103 track.heading = this.thread_.parent.pid + ': ' + |
| 104 this.thread_.tid + ': '; |
| 105 else |
| 106 track.heading = ''; |
| 107 track.slices = this.thread_.subRows[srI]; |
| 108 track.viewport = this.viewport_; |
| 109 |
| 110 this.tracks_.push(track); |
| 111 this.appendChild(track); |
| 112 } |
| 113 } |
| 114 }, |
| 115 |
| 116 /** |
| 117 * Picks a slice, if any, at a given location. |
| 118 * @param {number} wX X location to search at, in worldspace. |
| 119 * @param {number} wY Y location to search at, in offset space. |
| 120 * offset space. |
| 121 * @param {function():*} onHitCallback Callback to call with the slice, |
| 122 * if one is found. |
| 123 * @return {boolean} true if a slice was found, otherwise false. |
| 124 */ |
| 125 pick: function(wX, wY, onHitCallback) { |
| 126 for (var i = 0; i < this.tracks_.length; i++) { |
| 127 var track = this.tracks_[i]; |
| 128 if (wY >= track.offsetTop && wY < track.offsetTop + track.offsetHeight) |
| 129 return track.pick(wX, onHitCallback); |
| 130 } |
| 131 return false; |
| 132 }, |
| 133 |
| 134 /** |
| 135 * Finds slices intersecting the given interval. |
| 136 * @param {number} loWX Lower X bound of the interval to search, in |
| 137 * worldspace. |
| 138 * @param {number} hiWX Upper X bound of the interval to search, in |
| 139 * worldspace. |
| 140 * @param {number} loY Lower Y bound of the interval to search, in |
| 141 * offset space. |
| 142 * @param {number} hiY Upper Y bound of the interval to search, in |
| 143 * offset space. |
| 144 * @param {function():*} onHitCallback Function to call for each slice |
| 145 * intersecting the interval. |
| 146 */ |
| 147 pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) { |
| 148 for (var i = 0; i < this.tracks_.length; i++) { |
| 149 var a = Math.max(loY, this.tracks_[i].offsetTop); |
| 150 var b = Math.min(hiY, this.tracks_[i].offsetTop + |
| 151 this.tracks_[i].offsetHeight); |
| 152 if (a <= b) |
| 153 this.tracks_[i].pickRange(loWX, hiWX, loY, hiY, onHitCallback); |
| 154 } |
| 155 } |
| 156 }; |
| 157 |
| 41 /** | 158 /** |
| 42 * Creates a new timeline track div element | 159 * Creates a new timeline track div element |
| 43 * @constructor | 160 * @constructor |
| 44 * @extends {HTMLDivElement} | 161 * @extends {HTMLDivElement} |
| 45 */ | 162 */ |
| 46 TimelineTrack = cr.ui.define('div'); | 163 TimelineSliceTrack = cr.ui.define('div'); |
| 47 | 164 |
| 48 TimelineTrack.prototype = { | 165 TimelineSliceTrack.prototype = { |
| 49 __proto__: HTMLDivElement.prototype, | 166 __proto__: HTMLDivElement.prototype, |
| 50 | 167 |
| 51 decorate: function() { | 168 decorate: function() { |
| 52 this.classList.add('timeline-track'); | 169 this.className = 'timeline-slice-track'; |
| 53 this.slices_ = null; | 170 this.slices_ = null; |
| 54 | 171 |
| 55 this.titleDiv_ = document.createElement('div'); | 172 this.titleDiv_ = document.createElement('div'); |
| 56 this.titleDiv_.className = 'timeline-track-title'; | 173 this.titleDiv_.className = 'timeline-slice-track-title'; |
| 57 this.appendChild(this.titleDiv_); | 174 this.appendChild(this.titleDiv_); |
| 58 | 175 |
| 59 this.canvasContainer_ = document.createElement('div'); | 176 this.canvasContainer_ = document.createElement('div'); |
| 60 this.canvasContainer_.className = 'timeline-track-canvas-container'; | 177 this.canvasContainer_.className = 'timeline-slice-track-canvas-container'; |
| 61 this.appendChild(this.canvasContainer_); | 178 this.appendChild(this.canvasContainer_); |
| 62 this.canvas_ = document.createElement('canvas'); | 179 this.canvas_ = document.createElement('canvas'); |
| 63 this.canvas_.className = 'timeline-track-canvas'; | 180 this.canvas_.className = 'timeline-slice-track-canvas'; |
| 64 this.canvasContainer_.appendChild(this.canvas_); | 181 this.canvasContainer_.appendChild(this.canvas_); |
| 65 | 182 |
| 66 this.ctx_ = this.canvas_.getContext('2d'); | 183 this.ctx_ = this.canvas_.getContext('2d'); |
| 67 }, | 184 }, |
| 68 | 185 |
| 69 set heading(text) { | 186 set heading(text) { |
| 70 this.titleDiv_.textContent = text; | 187 this.titleDiv_.textContent = text; |
| 71 }, | 188 }, |
| 72 | 189 |
| 73 set slices(slices) { | 190 set slices(slices) { |
| 74 this.slices_ = slices; | 191 this.slices_ = slices; |
| 75 this.invalidate(); | 192 this.invalidate(); |
| 76 }, | 193 }, |
| 77 get canvas() { | |
| 78 return this.canvas_; | |
| 79 }, | |
| 80 | 194 |
| 81 onResize: function() { | 195 set viewport(v) { |
| 82 this.canvas_.width = this.canvasContainer_.clientWidth; | 196 this.viewport_ = v; |
| 83 this.canvas_.height = this.canvasContainer_.clientHeight; | |
| 84 this.invalidate(); | 197 this.invalidate(); |
| 85 }, | 198 }, |
| 86 | 199 |
| 87 invalidate: function() { | 200 invalidate: function() { |
| 88 if (this.parentNode) | 201 if (this.parentNode) |
| 89 this.parentNode.invalidate(); | 202 this.parentNode.invalidate(); |
| 90 }, | 203 }, |
| 91 | 204 |
| 92 set viewport(v) { | 205 get firstCanvas() { |
| 93 this.viewport_ = v; | 206 return this.canvas_; |
| 207 }, |
| 208 |
| 209 onResize: function() { |
| 210 this.canvas_.width = this.canvasContainer_.clientWidth; |
| 211 this.canvas_.height = this.canvasContainer_.clientHeight; |
| 94 this.invalidate(); | 212 this.invalidate(); |
| 95 }, | 213 }, |
| 96 | 214 |
| 97 getCanvasWidth: function() { | |
| 98 return this.canvas_.width; | |
| 99 }, | |
| 100 | |
| 101 redraw: function() { | 215 redraw: function() { |
| 102 if (!this.viewport_) | 216 if (!this.viewport_) |
| 103 return; | 217 return; |
| 104 var ctx = this.ctx_; | 218 var ctx = this.ctx_; |
| 105 var canvasW = this.canvas_.width; | 219 var canvasW = this.canvas_.width; |
| 106 var canvasH = this.canvas_.height; | 220 var canvasH = this.canvas_.height; |
| 107 | 221 |
| 108 ctx.clearRect(0, 0, canvasW, canvasH); | 222 ctx.clearRect(0, 0, canvasW, canvasH); |
| 109 | 223 |
| 110 // culling... | 224 // culling... |
| (...skipping 19 matching lines...) Expand all Loading... |
| 130 colorId = slice.selected ? | 244 colorId = slice.selected ? |
| 131 slice.colorId + selectedIdBoost : | 245 slice.colorId + selectedIdBoost : |
| 132 slice.colorId; | 246 slice.colorId; |
| 133 | 247 |
| 134 if (w < pixWidth) | 248 if (w < pixWidth) |
| 135 w = pixWidth; | 249 w = pixWidth; |
| 136 tr.fillRect(x, w, colorId); | 250 tr.fillRect(x, w, colorId); |
| 137 } | 251 } |
| 138 tr.flush(); | 252 tr.flush(); |
| 139 ctx.restore(); | 253 ctx.restore(); |
| 254 |
| 255 // labels |
| 256 ctx.textAlign = 'center'; |
| 257 ctx.textBaseline = 'top'; |
| 258 ctx.font = '10px sans-serif'; |
| 259 ctx.strokeStyle = 'rgb(0,0,0)'; |
| 260 ctx.fillStyle = 'rgb(0,0,0)'; |
| 261 var quickDiscardThresshold = pixWidth * 20; // dont render until 20px wide |
| 262 for (var i = 0; i < slices.length; ++i) { |
| 263 var slice = slices[i]; |
| 264 if (slice.duration > quickDiscardThresshold) { |
| 265 var labelWidth = quickMeasureText(ctx, slice.title) + 2; |
| 266 var labelWidthWorld = pixWidth * labelWidth; |
| 267 if (labelWidthWorld < slice.duration) { |
| 268 var cX = vp.xWorldToView(slice.start + 0.5 * slice.duration); |
| 269 ctx.fillText(slice.title, cX, 2.5); |
| 270 } |
| 271 } |
| 272 } |
| 140 }, | 273 }, |
| 141 | 274 |
| 142 /** | 275 /** |
| 143 * Picks a slice, if any, at a given location. | 276 * Picks a slice, if any, at a given location. |
| 144 * @param {number} wX Location to search at, in worldspace. | 277 * @param {number} wX X location to search at, in worldspace. |
| 278 * @param {number} wY Y location to search at, in offset space. |
| 279 * offset space. |
| 145 * @param {function():*} onHitCallback Callback to call with the slice, | 280 * @param {function():*} onHitCallback Callback to call with the slice, |
| 146 * if one is found. | 281 * if one is found. |
| 147 * @return {boolean} true if a slice was found, otherwise false. | 282 * @return {boolean} true if a slice was found, otherwise false. |
| 148 */ | 283 */ |
| 149 pick: function(wX, onHitCallback) { | 284 pick: function(wX, wY, onHitCallback) { |
| 285 if (wY < this.offsetTop || wY >= this.offsetTop + this.offsetHeight) |
| 286 return false; |
| 150 var x = gpu.findLowIndexInSortedIntervals(this.slices_, | 287 var x = gpu.findLowIndexInSortedIntervals(this.slices_, |
| 151 function(x) { return x.start; }, | 288 function(x) { return x.start; }, |
| 152 function(x) { return x.duration; }, | 289 function(x) { return x.duration; }, |
| 153 wX); | 290 wX); |
| 154 if (x >= 0 && x < this.slices_.length) { | 291 if (x >= 0 && x < this.slices_.length) { |
| 155 onHitCallback('slice', this, this.slices_[x]); | 292 onHitCallback('slice', this, this.slices_[x]); |
| 156 return true; | 293 return true; |
| 157 } | 294 } |
| 158 return false; | 295 return false; |
| 159 }, | 296 }, |
| 160 | 297 |
| 161 /** | 298 /** |
| 162 * Finds slices intersecting the given interval. | 299 * Finds slices intersecting the given interval. |
| 163 * @param {number} loWX Lower bound of the interval to search, in | 300 * @param {number} loWX Lower X bound of the interval to search, in |
| 164 * worldspace. | 301 * worldspace. |
| 165 * @param {number} hiWX Upper bound of the interval to search, in | 302 * @param {number} hiWX Upper X bound of the interval to search, in |
| 166 * worldspace. | 303 * worldspace. |
| 304 * @param {number} loY Lower Y bound of the interval to search, in |
| 305 * offset space. |
| 306 * @param {number} hiY Upper Y bound of the interval to search, in |
| 307 * offset space. |
| 167 * @param {function():*} onHitCallback Function to call for each slice | 308 * @param {function():*} onHitCallback Function to call for each slice |
| 168 * intersecting the interval. | 309 * intersecting the interval. |
| 169 */ | 310 */ |
| 170 pickRange: function(loWX, hiWX, onHitCallback) { | 311 pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) { |
| 312 var a = Math.max(loY, this.offsetTop); |
| 313 var b = Math.min(hiY, this.offsetTop + this.offsetHeight); |
| 314 if (a > b) |
| 315 return; |
| 316 |
| 171 function onPickHit(slice) { | 317 function onPickHit(slice) { |
| 172 onHitCallback('slice', this, slice); | 318 onHitCallback('slice', this, slice); |
| 173 } | 319 } |
| 174 gpu.iterateOverIntersectingIntervals(this.slices_, | 320 gpu.iterateOverIntersectingIntervals(this.slices_, |
| 175 function(x) { return x.start; }, | 321 function(x) { return x.start; }, |
| 176 function(x) { return x.duration; }, | 322 function(x) { return x.duration; }, |
| 177 loWX, hiWX, | 323 loWX, hiWX, |
| 178 onPickHit); | 324 onPickHit); |
| 179 } | 325 } |
| 180 | 326 |
| 181 }; | 327 }; |
| 182 | 328 |
| 183 return { | 329 return { |
| 184 TimelineTrack: TimelineTrack | 330 TimelineSliceTrack: TimelineSliceTrack, |
| 331 TimelineThreadTrack: TimelineThreadTrack |
| 185 }; | 332 }; |
| 186 }); | 333 }); |
| OLD | NEW |