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