1// Copyright (C) 2019 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use size file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import * as m from 'mithril'; 16 17import {timeToString} from '../common/time'; 18import {TimeSpan} from '../common/time'; 19 20import {TRACK_SHELL_WIDTH} from './css_constants'; 21import {globals} from './globals'; 22import {gridlines} from './gridline_helper'; 23import {Panel, PanelSize} from './panel'; 24 25export interface BBox { 26 x: number; 27 y: number; 28 width: number; 29 height: number; 30} 31 32// Draws a vertical line with two horizontal tails at the left and right and 33// a label in the middle. It looks a bit like a stretched H: 34// |--- Label ---| 35// The |target| bounding box determines where to draw the H. 36// The |bounds| bounding box gives the visible region, this is used to adjust 37// the positioning of the label to ensure it is on screen. 38function drawHBar( 39 ctx: CanvasRenderingContext2D, target: BBox, bounds: BBox, label: string) { 40 ctx.fillStyle = '#222'; 41 42 const xLeft = Math.floor(target.x); 43 const xRight = Math.ceil(target.x + target.width); 44 const yMid = Math.floor(target.height / 2 + target.y); 45 const xWidth = xRight - xLeft; 46 47 // Don't draw in the track shell. 48 ctx.beginPath(); 49 ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height); 50 ctx.clip(); 51 52 // Draw horizontal bar of the H. 53 ctx.fillRect(xLeft, yMid, xWidth, 1); 54 // Draw left vertical bar of the H. 55 ctx.fillRect(xLeft, target.y, 1, target.height); 56 // Draw right vertical bar of the H. 57 ctx.fillRect(xRight, target.y, 1, target.height); 58 59 const labelWidth = ctx.measureText(label).width; 60 61 // Find a good position for the label: 62 // By default put the label in the middle of the H: 63 let labelXLeft = Math.floor(xWidth / 2 - labelWidth / 2 + xLeft); 64 65 if (labelWidth > target.width || labelXLeft < bounds.x || 66 (labelXLeft + labelWidth) > (bounds.x + bounds.width)) { 67 // It won't fit in the middle or would be at least partly out of bounds 68 // so put it either to the left or right: 69 if (xRight > bounds.x + bounds.width) { 70 // If the H extends off the right side of the screen the label 71 // goes on the left of the H. 72 labelXLeft = xLeft - labelWidth - 3; 73 } else { 74 // Otherwise the label goes on the right of the H. 75 labelXLeft = xRight + 3; 76 } 77 } 78 79 ctx.fillStyle = '#ffffff'; 80 ctx.fillRect(labelXLeft - 1, 0, labelWidth + 1, target.height); 81 82 ctx.textBaseline = 'middle'; 83 ctx.fillStyle = '#222'; 84 ctx.font = '10px Roboto Condensed'; 85 ctx.fillText(label, labelXLeft, yMid); 86} 87 88function drawIBar( 89 ctx: CanvasRenderingContext2D, xPos: number, bounds: BBox, label: string) { 90 if (xPos < bounds.x) return; 91 92 ctx.fillStyle = '#222'; 93 ctx.fillRect(xPos, 0, 1, bounds.width); 94 95 const yMid = Math.floor(bounds.height / 2 + bounds.y); 96 const labelWidth = ctx.measureText(label).width; 97 const padding = 3; 98 99 let xPosLabel; 100 if (xPos + padding + labelWidth > bounds.width) { 101 xPosLabel = xPos - padding; 102 ctx.textAlign = 'right'; 103 } else { 104 xPosLabel = xPos + padding; 105 ctx.textAlign = 'left'; 106 } 107 108 ctx.fillStyle = '#ffffff'; 109 ctx.fillRect(xPosLabel - 1, 0, labelWidth + 2, bounds.height); 110 111 ctx.textBaseline = 'middle'; 112 ctx.fillStyle = '#222'; 113 ctx.font = '10px Roboto Condensed'; 114 ctx.fillText(label, xPosLabel, yMid); 115} 116 117export class TimeSelectionPanel extends Panel { 118 view() { 119 return m('.time-selection-panel'); 120 } 121 122 renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { 123 const range = globals.frontendLocalState.visibleWindowTime; 124 const timeScale = globals.frontendLocalState.timeScale; 125 126 ctx.fillStyle = '#999'; 127 ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height); 128 for (const xAndTime of gridlines(size.width, range, timeScale)) { 129 ctx.fillRect(xAndTime[0], 0, 1, size.height); 130 } 131 132 const localArea = globals.frontendLocalState.selectedArea; 133 const selection = globals.state.currentSelection; 134 if (localArea !== undefined) { 135 const start = Math.min(localArea.startSec, localArea.endSec); 136 const end = Math.max(localArea.startSec, localArea.endSec); 137 this.renderSpan(ctx, size, new TimeSpan(start, end)); 138 } else if (selection !== null && selection.kind === 'AREA') { 139 const selectedArea = globals.state.areas[selection.areaId]; 140 const start = Math.min(selectedArea.startSec, selectedArea.endSec); 141 const end = Math.max(selectedArea.startSec, selectedArea.endSec); 142 this.renderSpan(ctx, size, new TimeSpan(start, end)); 143 } 144 145 if (globals.frontendLocalState.hoveredLogsTimestamp !== -1) { 146 this.renderHover( 147 ctx, size, globals.frontendLocalState.hoveredLogsTimestamp); 148 } 149 150 for (const note of Object.values(globals.state.notes)) { 151 const noteIsSelected = selection !== null && selection.kind === 'AREA' && 152 selection.noteId === note.id; 153 if (note.noteType === 'AREA' && !noteIsSelected) { 154 const selectedArea = globals.state.areas[note.areaId]; 155 this.renderSpan( 156 ctx, 157 size, 158 new TimeSpan(selectedArea.startSec, selectedArea.endSec)); 159 } 160 } 161 } 162 163 renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: number) { 164 const timeScale = globals.frontendLocalState.timeScale; 165 const xPos = TRACK_SHELL_WIDTH + Math.floor(timeScale.timeToPx(ts)); 166 const offsetTime = timeToString(ts - globals.state.traceTime.startSec); 167 const timeFromStart = timeToString(ts); 168 const label = `${offsetTime} (${timeFromStart})`; 169 drawIBar(ctx, xPos, this.bounds(size), label); 170 } 171 172 renderSpan(ctx: CanvasRenderingContext2D, size: PanelSize, span: TimeSpan) { 173 const timeScale = globals.frontendLocalState.timeScale; 174 const xLeft = timeScale.timeToPx(span.start); 175 const xRight = timeScale.timeToPx(span.end); 176 const label = timeToString(span.duration); 177 drawHBar( 178 ctx, 179 { 180 x: TRACK_SHELL_WIDTH + xLeft, 181 y: 0, 182 width: xRight - xLeft, 183 height: size.height 184 }, 185 this.bounds(size), 186 label); 187 } 188 189 private bounds(size: PanelSize): BBox { 190 return { 191 x: TRACK_SHELL_WIDTH, 192 y: 0, 193 width: size.width - TRACK_SHELL_WIDTH, 194 height: size.height 195 }; 196 } 197} 198