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