|  | // Copyright (C) 2019 The Android Open Source Project | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use size file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | import m from 'mithril'; | 
|  | import {BigintMath} from '../base/bigint_math'; | 
|  |  | 
|  | import {Span, tpTimeToString} from '../common/time'; | 
|  | import { | 
|  | TPTime, | 
|  | TPTimeSpan, | 
|  | } from '../common/time'; | 
|  |  | 
|  | import { | 
|  | BACKGROUND_COLOR, | 
|  | FOREGROUND_COLOR, | 
|  | TRACK_SHELL_WIDTH, | 
|  | } from './css_constants'; | 
|  | import {globals} from './globals'; | 
|  | import { | 
|  | getMaxMajorTicks, | 
|  | TickGenerator, | 
|  | TickType, | 
|  | timeScaleForVisibleWindow, | 
|  | } from './gridline_helper'; | 
|  | import {Panel, PanelSize} from './panel'; | 
|  |  | 
|  | export interface BBox { | 
|  | x: number; | 
|  | y: number; | 
|  | width: number; | 
|  | height: number; | 
|  | } | 
|  |  | 
|  | // Draws a vertical line with two horizontal tails at the left and right and | 
|  | // a label in the middle. It looks a bit like a stretched H: | 
|  | // |--- Label ---| | 
|  | // The |target| bounding box determines where to draw the H. | 
|  | // The |bounds| bounding box gives the visible region, this is used to adjust | 
|  | // the positioning of the label to ensure it is on screen. | 
|  | function drawHBar( | 
|  | ctx: CanvasRenderingContext2D, target: BBox, bounds: BBox, label: string) { | 
|  | ctx.fillStyle = FOREGROUND_COLOR; | 
|  |  | 
|  | const xLeft = Math.floor(target.x); | 
|  | const xRight = Math.floor(target.x + target.width); | 
|  | const yMid = Math.floor(target.height / 2 + target.y); | 
|  | const xWidth = xRight - xLeft; | 
|  |  | 
|  | // Don't draw in the track shell. | 
|  | ctx.beginPath(); | 
|  | ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height); | 
|  | ctx.clip(); | 
|  |  | 
|  | // Draw horizontal bar of the H. | 
|  | ctx.fillRect(xLeft, yMid, xWidth, 1); | 
|  | // Draw left vertical bar of the H. | 
|  | ctx.fillRect(xLeft, target.y, 1, target.height); | 
|  | // Draw right vertical bar of the H. | 
|  | ctx.fillRect(xRight, target.y, 1, target.height); | 
|  |  | 
|  | const labelWidth = ctx.measureText(label).width; | 
|  |  | 
|  | // Find a good position for the label: | 
|  | // By default put the label in the middle of the H: | 
|  | let labelXLeft = Math.floor(xWidth / 2 - labelWidth / 2 + xLeft); | 
|  |  | 
|  | if (labelWidth > target.width || labelXLeft < bounds.x || | 
|  | (labelXLeft + labelWidth) > (bounds.x + bounds.width)) { | 
|  | // It won't fit in the middle or would be at least partly out of bounds | 
|  | // so put it either to the left or right: | 
|  | if (xRight > bounds.x + bounds.width) { | 
|  | // If the H extends off the right side of the screen the label | 
|  | // goes on the left of the H. | 
|  | labelXLeft = xLeft - labelWidth - 3; | 
|  | } else { | 
|  | // Otherwise the label goes on the right of the H. | 
|  | labelXLeft = xRight + 3; | 
|  | } | 
|  | } | 
|  |  | 
|  | ctx.fillStyle = BACKGROUND_COLOR; | 
|  | ctx.fillRect(labelXLeft - 1, 0, labelWidth + 1, target.height); | 
|  |  | 
|  | ctx.textBaseline = 'middle'; | 
|  | ctx.fillStyle = FOREGROUND_COLOR; | 
|  | ctx.font = '10px Roboto Condensed'; | 
|  | ctx.fillText(label, labelXLeft, yMid); | 
|  | } | 
|  |  | 
|  | function drawIBar( | 
|  | ctx: CanvasRenderingContext2D, xPos: number, bounds: BBox, label: string) { | 
|  | if (xPos < bounds.x) return; | 
|  |  | 
|  | ctx.fillStyle = FOREGROUND_COLOR; | 
|  | ctx.fillRect(xPos, 0, 1, bounds.width); | 
|  |  | 
|  | const yMid = Math.floor(bounds.height / 2 + bounds.y); | 
|  | const labelWidth = ctx.measureText(label).width; | 
|  | const padding = 3; | 
|  |  | 
|  | let xPosLabel; | 
|  | if (xPos + padding + labelWidth > bounds.width) { | 
|  | xPosLabel = xPos - padding; | 
|  | ctx.textAlign = 'right'; | 
|  | } else { | 
|  | xPosLabel = xPos + padding; | 
|  | ctx.textAlign = 'left'; | 
|  | } | 
|  |  | 
|  | ctx.fillStyle = BACKGROUND_COLOR; | 
|  | ctx.fillRect(xPosLabel - 1, 0, labelWidth + 2, bounds.height); | 
|  |  | 
|  | ctx.textBaseline = 'middle'; | 
|  | ctx.fillStyle = FOREGROUND_COLOR; | 
|  | ctx.font = '10px Roboto Condensed'; | 
|  | ctx.fillText(label, xPosLabel, yMid); | 
|  | } | 
|  |  | 
|  | export class TimeSelectionPanel extends Panel { | 
|  | view() { | 
|  | return m('.time-selection-panel'); | 
|  | } | 
|  |  | 
|  | renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { | 
|  | ctx.fillStyle = '#999'; | 
|  | ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height); | 
|  |  | 
|  | ctx.save(); | 
|  | ctx.beginPath(); | 
|  | ctx.rect(TRACK_SHELL_WIDTH, 0, size.width - TRACK_SHELL_WIDTH, size.height); | 
|  | ctx.clip(); | 
|  |  | 
|  | const span = globals.frontendLocalState.visibleWindow.timestampSpan; | 
|  | if (size.width > TRACK_SHELL_WIDTH && span.duration > 0n) { | 
|  | const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH); | 
|  | const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width); | 
|  | for (const {type, time} of new TickGenerator( | 
|  | span, maxMajorTicks, globals.state.traceTime.start)) { | 
|  | const px = Math.floor(map.tpTimeToPx(time)); | 
|  | if (type === TickType.MAJOR) { | 
|  | ctx.fillRect(px, 0, 1, size.height); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | const localArea = globals.frontendLocalState.selectedArea; | 
|  | const selection = globals.state.currentSelection; | 
|  | if (localArea !== undefined) { | 
|  | const start = BigintMath.min(localArea.start, localArea.end); | 
|  | const end = BigintMath.max(localArea.start, localArea.end); | 
|  | this.renderSpan(ctx, size, new TPTimeSpan(start, end)); | 
|  | } else if (selection !== null && selection.kind === 'AREA') { | 
|  | const selectedArea = globals.state.areas[selection.areaId]; | 
|  | const start = BigintMath.min(selectedArea.start, selectedArea.end); | 
|  | const end = BigintMath.max(selectedArea.start, selectedArea.end); | 
|  | this.renderSpan(ctx, size, new TPTimeSpan(start, end)); | 
|  | } | 
|  |  | 
|  | if (globals.state.hoverCursorTimestamp !== -1n) { | 
|  | this.renderHover(ctx, size, globals.state.hoverCursorTimestamp); | 
|  | } | 
|  |  | 
|  | for (const note of Object.values(globals.state.notes)) { | 
|  | const noteIsSelected = selection !== null && selection.kind === 'AREA' && | 
|  | selection.noteId === note.id; | 
|  | if (note.noteType === 'AREA' && !noteIsSelected) { | 
|  | const selectedArea = globals.state.areas[note.areaId]; | 
|  | this.renderSpan( | 
|  | ctx, size, new TPTimeSpan(selectedArea.start, selectedArea.end)); | 
|  | } | 
|  | } | 
|  |  | 
|  | ctx.restore(); | 
|  | } | 
|  |  | 
|  | renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: TPTime) { | 
|  | const {visibleTimeScale} = globals.frontendLocalState; | 
|  | const xPos = | 
|  | TRACK_SHELL_WIDTH + Math.floor(visibleTimeScale.tpTimeToPx(ts)); | 
|  | const offsetTime = tpTimeToString(ts - globals.state.traceTime.start); | 
|  | const timeFromStart = tpTimeToString(ts); | 
|  | const label = `${offsetTime} (${timeFromStart})`; | 
|  | drawIBar(ctx, xPos, this.bounds(size), label); | 
|  | } | 
|  |  | 
|  | renderSpan( | 
|  | ctx: CanvasRenderingContext2D, size: PanelSize, span: Span<TPTime>) { | 
|  | const {visibleTimeScale} = globals.frontendLocalState; | 
|  | const xLeft = visibleTimeScale.tpTimeToPx(span.start); | 
|  | const xRight = visibleTimeScale.tpTimeToPx(span.end); | 
|  | const label = tpTimeToString(span.duration); | 
|  | drawHBar( | 
|  | ctx, | 
|  | { | 
|  | x: TRACK_SHELL_WIDTH + xLeft, | 
|  | y: 0, | 
|  | width: xRight - xLeft, | 
|  | height: size.height, | 
|  | }, | 
|  | this.bounds(size), | 
|  | label); | 
|  | } | 
|  |  | 
|  | private bounds(size: PanelSize): BBox { | 
|  | return { | 
|  | x: TRACK_SHELL_WIDTH, | 
|  | y: 0, | 
|  | width: size.width - TRACK_SHELL_WIDTH, | 
|  | height: size.height, | 
|  | }; | 
|  | } | 
|  | } |