// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import {Timeline} from '../../timeline.mjs'; import {SelectTimeEvent} from '../events.mjs'; import {CSSColor, delay, SVG} from '../helper.mjs'; import {TimelineTrackBase} from './timeline-track-base.mjs' const kItemHeight = 8; export class TimelineTrackStackedBase extends TimelineTrackBase { _originalContentWidth = 0; _drawableItems = new Timeline(); _updateChunks() { // We don't need to update the chunks here. this._updateDimensions(); this.requestUpdate(); } set data(timeline) { super.data = timeline; this._contentWidth = 0; if (timeline.values.length > 0) this._prepareDrawableItems(); } _handleDoubleClick(event) { if (event.button !== 0) return; this._selectionHandler.clearSelection(); const item = this._getDrawableItemForEvent(event); if (item === undefined) return; event.stopImmediatePropagation(); this.dispatchEvent(new SelectTimeEvent(item.startTime, item.endTime)); return false; } _getStackDepthForEvent(event) { return Math.floor(event.layerY / kItemHeight) - 1; } _getDrawableItemForEvent(event) { const depth = this._getStackDepthForEvent(event); const time = this.positionToTime(event.pageX); const index = this._drawableItems.find(time); for (let i = index - 1; i > 0; i--) { const item = this._drawableItems.at(i); if (item.depth != depth) continue; if (item.endTime < time) continue; return item; } return undefined; } _drawableItemToLogEntry(item) { return item; } _getEntryForEvent(event) { const item = this._getDrawableItemForEvent(event); const logEntry = this._drawableItemToLogEntry(item); if (item === undefined) return undefined; const node = this.getToolTipTargetNode(logEntry); if (!node) return logEntry; const style = node.style; style.left = `${event.layerX}px`; style.top = `${(item.depth + 1) * kItemHeight}px`; style.height = `${kItemHeight}px` return logEntry; } _prepareDrawableItems() { // Subclass responsibility. } _adjustStackDepth(maxDepth) { // Account for empty top line maxDepth++; this._adjustHeight(maxDepth * kItemHeight); } _scaleContent(currentWidth) { if (this._originalContentWidth == 0) return; // Instead of repainting just scale the content. const ratio = currentWidth / this._originalContentWidth; this._scalableContentNode.style.transform = `scale(${ratio}, 1)`; this.style.setProperty('--txt-scale', `scale(${1 / ratio}, 1)`); return ratio; } async _drawContent() { if (this._originalContentWidth > 0) return; this._originalContentWidth = parseInt(this.timelineMarkersNode.style.width); this._scalableContentNode.innerHTML = ''; let buffer = ''; const add = async () => { const svg = SVG.svg(); svg.innerHTML = buffer; this._scalableContentNode.appendChild(svg); buffer = ''; await delay(50); }; const items = this._drawableItems.values; for (let i = 0; i < items.length; i++) { if ((i % 3000) == 0) await add(); buffer += this._drawItem(items[i], i); } add(); } _drawItem(item, i, outline = false) { const x = roundTo3Digits(this.timeToPosition(item.time)); const y = (item.depth + 1) * kItemHeight; let width = roundTo3Digits(item.duration * this._timeToPixel); if (outline) { return ``; } let color = this._legend.colorForType(item.type); if (i % 2 == 1) { color = CSSColor.darken(color, 20); } return ``; } _drawItemText(item) { const type = item.type; const kHeight = 9; const x = this.timeToPosition(item.time); const y = item.depth * (kHeight + 1); let width = item.duration * this._timeToPixel; width -= width * 0.1; let buffer = ''; if (width < 15 || type == 'Other') return buffer; const rawName = item.entry.getName(); if (rawName.length == 0) return buffer; const kChartWidth = 5; const maxChars = Math.floor(width / kChartWidth) const text = rawName.substr(0, maxChars); buffer += `${text}` return buffer; } } function roundTo3Digits(value) { return ((value * 1000) | 0) / 1000; }