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