1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import { 18 Component, 19 ElementRef, 20 EventEmitter, 21 HostListener, 22 Input, 23 Output, 24 SimpleChanges, 25 ViewChild, 26} from '@angular/core'; 27import {TimelineData} from 'app/timeline_data'; 28import {assertDefined} from 'common/assert_utils'; 29import {Timestamp} from 'trace/timestamp'; 30import {Traces} from 'trace/traces'; 31import {TracePosition} from 'trace/trace_position'; 32import {TraceType} from 'trace/trace_type'; 33import {MiniCanvasDrawer, MiniCanvasDrawerInput} from './mini_canvas_drawer'; 34 35@Component({ 36 selector: 'mini-timeline', 37 template: ` 38 <div id="mini-timeline-wrapper" #miniTimelineWrapper> 39 <canvas #canvas></canvas> 40 </div> 41 `, 42 styles: [ 43 ` 44 #mini-timeline-wrapper { 45 width: 100%; 46 min-height: 5em; 47 height: 100%; 48 } 49 `, 50 ], 51}) 52export class MiniTimelineComponent { 53 @Input() timelineData!: TimelineData; 54 @Input() currentTracePosition!: TracePosition; 55 @Input() selectedTraces!: TraceType[]; 56 57 @Output() onTracePositionUpdate = new EventEmitter<TracePosition>(); 58 @Output() onSeekTimestampUpdate = new EventEmitter<Timestamp | undefined>(); 59 60 @ViewChild('miniTimelineWrapper', {static: false}) miniTimelineWrapper!: ElementRef; 61 @ViewChild('canvas', {static: false}) canvasRef!: ElementRef; 62 get canvas(): HTMLCanvasElement { 63 return this.canvasRef.nativeElement; 64 } 65 66 private drawer: MiniCanvasDrawer | undefined = undefined; 67 68 ngAfterViewInit(): void { 69 this.makeHiPPICanvas(); 70 71 const updateTimestampCallback = (timestamp: Timestamp) => { 72 this.onSeekTimestampUpdate.emit(undefined); 73 this.onTracePositionUpdate.emit(TracePosition.fromTimestamp(timestamp)); 74 }; 75 76 this.drawer = new MiniCanvasDrawer( 77 this.canvas, 78 () => this.getMiniCanvasDrawerInput(), 79 (position) => { 80 const timestampType = this.timelineData.getTimestampType()!; 81 this.onSeekTimestampUpdate.emit(position); 82 }, 83 updateTimestampCallback, 84 (selection) => { 85 const timestampType = this.timelineData.getTimestampType()!; 86 this.timelineData.setSelectionTimeRange(selection); 87 }, 88 updateTimestampCallback 89 ); 90 this.drawer.draw(); 91 } 92 93 ngOnChanges(changes: SimpleChanges) { 94 if (this.drawer !== undefined) { 95 this.drawer.draw(); 96 } 97 } 98 99 private getMiniCanvasDrawerInput() { 100 return new MiniCanvasDrawerInput( 101 this.timelineData.getFullTimeRange(), 102 this.currentTracePosition.timestamp, 103 this.timelineData.getSelectionTimeRange(), 104 this.getTracesToShow() 105 ); 106 } 107 108 private getTracesToShow(): Traces { 109 const traces = new Traces(); 110 this.selectedTraces 111 .filter((type) => this.timelineData.getTraces().getTrace(type) !== undefined) 112 .forEach((type) => { 113 traces.setTrace(type, assertDefined(this.timelineData.getTraces().getTrace(type))); 114 }); 115 return traces; 116 } 117 118 private makeHiPPICanvas() { 119 // Reset any size before computing new size to avoid it interfering with size computations 120 this.canvas.width = 0; 121 this.canvas.height = 0; 122 this.canvas.style.width = 'auto'; 123 this.canvas.style.height = 'auto'; 124 125 const width = this.miniTimelineWrapper.nativeElement.offsetWidth; 126 const height = this.miniTimelineWrapper.nativeElement.offsetHeight; 127 128 const HiPPIwidth = window.devicePixelRatio * width; 129 const HiPPIheight = window.devicePixelRatio * height; 130 131 this.canvas.width = HiPPIwidth; 132 this.canvas.height = HiPPIheight; 133 this.canvas.style.width = width + 'px'; 134 this.canvas.style.height = height + 'px'; 135 136 // ensure all drawing operations are scaled 137 if (window.devicePixelRatio !== 1) { 138 const context = this.canvas.getContext('2d')!; 139 context.scale(window.devicePixelRatio, window.devicePixelRatio); 140 } 141 } 142 143 @HostListener('window:resize', ['$event']) 144 onResize(event: Event) { 145 this.makeHiPPICanvas(); 146 this.drawer?.draw(); 147 } 148} 149