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 EventEmitter, 20 HostListener, 21 Input, 22 Output, 23 QueryList, 24 ViewChildren, 25} from '@angular/core'; 26import {TimelineData} from 'app/timeline_data'; 27import {assertDefined} from 'common/assert_utils'; 28import {Trace} from 'trace/trace'; 29import {TRACE_INFO} from 'trace/trace_info'; 30import {TracePosition} from 'trace/trace_position'; 31import {TraceType, TraceTypeUtils} from 'trace/trace_type'; 32import {AbstractTimelineRowComponent} from './abstract_timeline_row_component'; 33import {DefaultTimelineRowComponent} from './default_timeline_row_component'; 34import {TransitionTimelineComponent} from './transition_timeline_component'; 35 36@Component({ 37 selector: 'expanded-timeline', 38 template: ` 39 <div id="expanded-timeline-wrapper" #expandedTimelineWrapper> 40 <div 41 *ngFor="let trace of getTracesSortedByDisplayOrder(); trackBy: trackTraceByType" 42 class="timeline row"> 43 <div class="icon-wrapper"> 44 <mat-icon 45 class="icon" 46 [matTooltip]="TRACE_INFO[trace.type].name" 47 [style]="{color: TRACE_INFO[trace.type].color}"> 48 {{ TRACE_INFO[trace.type].icon }} 49 </mat-icon> 50 </div> 51 <transition-timeline 52 *ngIf="trace.type === TraceType.TRANSITION" 53 [color]="TRACE_INFO[trace.type].color" 54 [trace]="trace" 55 [traceEntries]="timelineData.getTransitions()" 56 [selectedEntry]="timelineData.findCurrentEntryFor(trace)" 57 [selectionRange]="timelineData.getSelectionTimeRange()" 58 [timestampConverter]="timelineData.getTimestampConverter()" 59 [isActive]="isActiveTrace(trace)" 60 (onTracePositionUpdate)="onTracePositionUpdate.emit($event)" 61 (onScrollEvent)="updateScroll($event)" 62 (onTraceClicked)="onTraceClicked.emit($event)" 63 (onMouseXRatioUpdate)="onMouseXRatioUpdate.emit($event)" 64 class="single-timeline"> 65 </transition-timeline> 66 <single-timeline 67 *ngIf="trace.type !== TraceType.TRANSITION" 68 [color]="TRACE_INFO[trace.type].color" 69 [trace]="trace" 70 [selectedEntry]="timelineData.findCurrentEntryFor(trace)" 71 [selectionRange]="timelineData.getSelectionTimeRange()" 72 [timestampConverter]="timelineData.getTimestampConverter()" 73 [isActive]="isActiveTrace(trace)" 74 (onTracePositionUpdate)="onTracePositionUpdate.emit($event)" 75 (onScrollEvent)="updateScroll($event)" 76 (onTraceClicked)="onTraceClicked.emit($event)" 77 (onMouseXRatioUpdate)="onMouseXRatioUpdate.emit($event)" 78 class="single-timeline"> 79 </single-timeline> 80 81 <div class="icon-wrapper"> 82 <mat-icon class="icon placeholder-icon"></mat-icon> 83 </div> 84 </div> 85 </div> 86 `, 87 styles: [ 88 ` 89 #expanded-timeline-wrapper { 90 display: flex; 91 flex-direction: column; 92 height: 100%; 93 position: relative; 94 } 95 #pointer-overlay { 96 pointer-events: none; 97 position: absolute; 98 top: 0; 99 bottom: 0; 100 left: 0; 101 right: 0; 102 display: flex; 103 align-items: stretch; 104 } 105 .timeline { 106 display: flex; 107 flex-direction: row; 108 align-items: center; 109 justify-content: center; 110 width: 100%; 111 } 112 .timeline.row { 113 border-bottom: 1px solid var(--drawer-block-primary); 114 } 115 .timeline .single-timeline { 116 flex-grow: 1; 117 } 118 .selection-cursor { 119 flex-grow: 1; 120 } 121 .icon-wrapper { 122 align-self: stretch; 123 display: flex; 124 justify-content: center; 125 background-color: var(--drawer-block-primary); 126 } 127 .icon { 128 margin: 1rem; 129 align-self: center; 130 } 131 .units-row { 132 flex-grow: 1; 133 align-self: baseline; 134 } 135 .units-row .placeholder-icon { 136 visibility: hidden; 137 } 138 `, 139 ], 140}) 141export class ExpandedTimelineComponent { 142 @Input() timelineData: TimelineData | undefined; 143 @Output() readonly onTracePositionUpdate = new EventEmitter<TracePosition>(); 144 @Output() readonly onScrollEvent = new EventEmitter<WheelEvent>(); 145 @Output() readonly onTraceClicked = new EventEmitter<Trace<object>>(); 146 @Output() readonly onMouseXRatioUpdate = new EventEmitter< 147 number | undefined 148 >(); 149 150 @ViewChildren(DefaultTimelineRowComponent) 151 singleTimelines: QueryList<DefaultTimelineRowComponent> | undefined; 152 153 @ViewChildren(TransitionTimelineComponent) 154 transitionTimelines: QueryList<TransitionTimelineComponent> | undefined; 155 156 TRACE_INFO = TRACE_INFO; 157 TraceType = TraceType; 158 159 @HostListener('window:resize', ['$event']) 160 onResize(event: Event) { 161 this.resizeCanvases(); 162 } 163 164 trackTraceByType = (index: number, trace: Trace<{}>): TraceType => { 165 return trace.type; 166 }; 167 168 getTracesSortedByDisplayOrder(): Array<Trace<{}>> { 169 const traces = assertDefined(this.timelineData) 170 .getTraces() 171 .mapTrace((trace) => trace); 172 return traces.sort((a, b) => 173 TraceTypeUtils.compareByDisplayOrder(a.type, b.type), 174 ); 175 } 176 177 updateScroll(event: WheelEvent) { 178 this.onScrollEvent.emit(event); 179 } 180 181 isActiveTrace(trace: Trace<object>) { 182 return trace === this.timelineData?.getActiveTrace(); 183 } 184 185 private resizeCanvases() { 186 // Reset any size before computing new size to avoid it interfering with size computations. 187 // Needs to be done together because otherwise the sizes of each timeline will interfere with 188 // each other, since if one timeline is still too big the container will stretch to that size. 189 const timelines = [ 190 ...(this.transitionTimelines as QueryList< 191 AbstractTimelineRowComponent<{}> 192 >), 193 ...(this.singleTimelines as QueryList<AbstractTimelineRowComponent<{}>>), 194 ]; 195 for (const timeline of timelines) { 196 timeline.getCanvas().width = 0; 197 timeline.getCanvas().height = 0; 198 timeline.getCanvas().style.width = 'auto'; 199 timeline.getCanvas().style.height = 'auto'; 200 } 201 202 for (const timeline of timelines) { 203 timeline.initializeCanvas(); 204 timeline.getCanvas().height = 0; 205 timeline.getCanvas().style.width = 'auto'; 206 timeline.getCanvas().style.height = 'auto'; 207 } 208 } 209} 210