1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this 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'; 16import {TrackState} from '../common/state'; 17import {TrackData} from '../common/track_data'; 18import {checkerboard} from './checkerboard'; 19 20import {globals} from './globals'; 21import {TrackButtonAttrs} from './track_panel'; 22 23/** 24 * This interface forces track implementations to have some static properties. 25 * Typescript does not have abstract static members, which is why this needs to 26 * be in a separate interface. 27 */ 28export interface TrackCreator { 29 // Store the kind explicitly as a string as opposed to using class.kind in 30 // case we ever minify our code. 31 readonly kind: string; 32 33 // We need the |create| method because the stored value in the registry is an 34 // abstract class, and we cannot call 'new' on an abstract class. 35 create(TrackState: TrackState): Track; 36} 37 38export interface SliceRect { 39 left: number; 40 width: number; 41 top: number; 42 height: number; 43 visible: boolean; 44} 45 46/** 47 * The abstract class that needs to be implemented by all tracks. 48 */ 49export abstract class Track<Config = {}, Data extends TrackData = TrackData> { 50 private trackId: string; 51 constructor(trackState: TrackState) { 52 this.trackId = trackState.id; 53 } 54 protected abstract renderCanvas(ctx: CanvasRenderingContext2D): void; 55 56 protected get trackState(): TrackState { 57 return globals.state.tracks[this.trackId]; 58 } 59 60 get config(): Config { 61 return globals.state.tracks[this.trackId].config as Config; 62 } 63 64 data(): Data|undefined { 65 return globals.trackDataStore.get(this.trackId) as Data; 66 } 67 68 getHeight(): number { 69 return 40; 70 } 71 72 getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> { 73 return []; 74 } 75 76 onMouseMove(_position: {x: number, y: number}) {} 77 78 /** 79 * Returns whether the mouse click has selected something. 80 * Used to prevent further propagation if necessary. 81 */ 82 onMouseClick(_position: {x: number, y: number}): boolean { 83 return false; 84 } 85 86 onMouseOut() {} 87 88 render(ctx: CanvasRenderingContext2D) { 89 globals.frontendLocalState.addVisibleTrack(this.trackState.id); 90 if (this.data() === undefined) { 91 const {visibleWindowTime, timeScale} = globals.frontendLocalState; 92 const startPx = Math.floor(timeScale.timeToPx(visibleWindowTime.start)); 93 const endPx = Math.ceil(timeScale.timeToPx(visibleWindowTime.end)); 94 checkerboard(ctx, this.getHeight(), startPx, endPx); 95 } else { 96 this.renderCanvas(ctx); 97 } 98 } 99 100 drawTrackHoverTooltip( 101 ctx: CanvasRenderingContext2D, xPos: number, text: string, 102 text2?: string) { 103 ctx.font = '10px Roboto Condensed'; 104 const textWidth = ctx.measureText(text).width; 105 let width = textWidth; 106 let textYPos = this.getHeight() / 2; 107 108 if (text2 !== undefined) { 109 const text2Width = ctx.measureText(text2).width; 110 width = Math.max(textWidth, text2Width); 111 textYPos = this.getHeight() / 2 - 6; 112 } 113 114 // Move tooltip over if it would go off the right edge of the viewport. 115 const rectWidth = width + 16; 116 const endPx = globals.frontendLocalState.timeScale.endPx; 117 if (xPos + rectWidth > endPx) { 118 xPos -= (xPos + rectWidth - endPx); 119 } 120 121 ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; 122 const rectMargin = this.getHeight() / 12; 123 ctx.fillRect( 124 xPos, rectMargin, rectWidth, this.getHeight() - rectMargin * 2); 125 ctx.fillStyle = 'hsl(200, 50%, 40%)'; 126 ctx.textAlign = 'left'; 127 ctx.textBaseline = 'middle'; 128 ctx.fillText(text, xPos + 8, textYPos); 129 130 if (text2 !== undefined) { 131 ctx.fillText(text2, xPos + 8, this.getHeight() / 2 + 6); 132 } 133 } 134 135 /** 136 * Returns a place where a given slice should be drawn. Should be implemented 137 * only for track types that support slices e.g. chrome_slice, async_slices 138 * tStart - slice start time in seconds, tEnd - slice end time in seconds, 139 * depth - slice depth 140 */ 141 getSliceRect(_tStart: number, _tEnd: number, _depth: number): SliceRect 142 |undefined { 143 return undefined; 144 } 145} 146