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 {Component, Input} from '@angular/core'; 18import {assertDefined} from 'common/assert_utils'; 19import {Point} from 'common/geometry_types'; 20import {Rect} from 'common/rect'; 21import {Timestamp} from 'common/time'; 22import {Trace, TraceEntry} from 'trace/trace'; 23import {AbstractTimelineRowComponent} from './abstract_timeline_row_component'; 24 25@Component({ 26 selector: 'single-timeline', 27 template: ` 28 <div 29 class="single-timeline" 30 (click)="onTimelineClick($event)" 31 [style.background-color]="getBackgroundColor()" #wrapper> 32 <canvas 33 id="canvas" 34 (mousemove)="trackMousePos($event)" 35 (mouseleave)="onMouseLeave($event)" #canvas></canvas> 36 </div> 37 `, 38 styles: [ 39 ` 40 .single-timeline { 41 height: 2rem; 42 padding: 1rem 0; 43 } 44 .single-timeline:hover { 45 background-color: var(--hover-element-color); 46 cursor: pointer; 47 } 48 `, 49 ], 50}) 51export class DefaultTimelineRowComponent extends AbstractTimelineRowComponent<{}> { 52 @Input() selectedEntry: TraceEntry<{}> | undefined; 53 @Input() trace: Trace<{}> | undefined; 54 55 hoveringEntry?: Timestamp; 56 57 ngOnInit() { 58 assertDefined(this.trace); 59 assertDefined(this.selectionRange); 60 } 61 62 getEntryWidth() { 63 return this.canvasDrawer.getScaledCanvasHeight(); 64 } 65 66 getAvailableWidth() { 67 return Math.floor( 68 this.canvasDrawer.getScaledCanvasWidth() - this.getEntryWidth(), 69 ); 70 } 71 72 override onHover(mousePoint: Point) { 73 this.drawEntryHover(mousePoint); 74 } 75 76 override handleMouseOut(e: MouseEvent) { 77 if (this.hoveringEntry) { 78 // If undefined there is no current hover effect so no need to clear 79 this.redraw(); 80 } 81 this.hoveringEntry = undefined; 82 } 83 84 override drawTimeline() { 85 assertDefined(this.trace) 86 .sliceTime( 87 assertDefined(this.selectionRange).from, 88 assertDefined(this.selectionRange).to.add(1n), 89 ) 90 .forEachTimestamp((entry) => { 91 this.drawEntry(entry); 92 }); 93 this.drawSelectedEntry(); 94 } 95 96 protected override getEntryAt(mousePoint: Point): TraceEntry<{}> | undefined { 97 const timestampOfClick = this.getTimestampOf(mousePoint.x); 98 const candidateEntry = assertDefined(this.trace).findLastLowerOrEqualEntry( 99 timestampOfClick, 100 ); 101 102 if (candidateEntry !== undefined) { 103 const timestamp = candidateEntry.getTimestamp(); 104 const rect = this.entryRect(timestamp); 105 if (rect.containsPoint(mousePoint)) { 106 return candidateEntry; 107 } 108 } 109 110 return undefined; 111 } 112 113 private drawEntryHover(mousePoint: Point) { 114 const currentHoverEntry = this.getEntryAt(mousePoint)?.getTimestamp(); 115 116 if (this.hoveringEntry === currentHoverEntry) { 117 return; 118 } 119 120 if (this.hoveringEntry) { 121 // If null there is no current hover effect so no need to clear 122 this.redraw(); 123 } 124 125 this.hoveringEntry = currentHoverEntry; 126 127 if (!this.hoveringEntry) { 128 return; 129 } 130 131 const rect = this.entryRect(this.hoveringEntry); 132 133 this.canvasDrawer.drawRect(rect, this.color, 1.0); 134 this.canvasDrawer.drawRectBorder(rect); 135 } 136 137 private entryRect(entry: Timestamp, padding = 0): Rect { 138 const xPos = this.getXPosOf(entry); 139 140 return new Rect( 141 xPos + padding, 142 padding, 143 this.getEntryWidth() - 2 * padding, 144 this.getEntryWidth() - 2 * padding, 145 ); 146 } 147 148 private getXPosOf(entry: Timestamp): number { 149 const start = assertDefined(this.selectionRange).from.getValueNs(); 150 const end = assertDefined(this.selectionRange).to.getValueNs(); 151 152 return Number( 153 (BigInt(this.getAvailableWidth()) * (entry.getValueNs() - start)) / 154 (end - start), 155 ); 156 } 157 158 private getTimestampOf(x: number): Timestamp { 159 const start = assertDefined(this.selectionRange).from.getValueNs(); 160 const end = assertDefined(this.selectionRange).to.getValueNs(); 161 const ts = 162 (BigInt(Math.floor(x)) * (end - start)) / 163 BigInt(this.getAvailableWidth()) + 164 start; 165 return assertDefined(this.timestampConverter).makeTimestampFromNs(ts); 166 } 167 168 private drawEntry(entry: Timestamp) { 169 const rect = this.entryRect(entry); 170 171 this.canvasDrawer.drawRect(rect, this.color, 0.2); 172 } 173 174 private drawSelectedEntry() { 175 if (this.selectedEntry === undefined) { 176 return; 177 } 178 179 const rect = this.entryRect(this.selectedEntry.getTimestamp(), 1); 180 this.canvasDrawer.drawRect(rect, this.color, 1.0); 181 this.canvasDrawer.drawRectBorder(rect); 182 } 183} 184