• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {hex} from 'color-convert';
16import * as m from 'mithril';
17
18import {assertExists} from '../base/logging';
19import {Actions} from '../common/actions';
20import {
21  getContainingTrackId,
22  TrackGroupState,
23  TrackState
24} from '../common/state';
25
26import {globals} from './globals';
27import {drawGridLines} from './gridline_helper';
28import {
29  BLANK_CHECKBOX,
30  CHECKBOX,
31  EXPAND_DOWN,
32  EXPAND_UP,
33  INDETERMINATE_CHECKBOX
34} from './icons';
35import {Panel, PanelSize} from './panel';
36import {Track} from './track';
37import {TrackContent} from './track_panel';
38import {trackRegistry} from './track_registry';
39import {
40  drawVerticalLineAtTime,
41  drawVerticalSelection,
42} from './vertical_line_helper';
43
44interface Attrs {
45  trackGroupId: string;
46  selectable: boolean;
47}
48
49export class TrackGroupPanel extends Panel<Attrs> {
50  private readonly trackGroupId: string;
51  private shellWidth = 0;
52  private backgroundColor = '#ffffff';  // Updated from CSS later.
53  private summaryTrack: Track;
54
55  constructor({attrs}: m.CVnode<Attrs>) {
56    super();
57    this.trackGroupId = attrs.trackGroupId;
58    const trackCreator = trackRegistry.get(this.summaryTrackState.kind);
59    this.summaryTrack = trackCreator.create(this.summaryTrackState);
60  }
61
62  get trackGroupState(): TrackGroupState {
63    return assertExists(globals.state.trackGroups[this.trackGroupId]);
64  }
65
66  get summaryTrackState(): TrackState {
67    return assertExists(
68        globals.state.tracks[this.trackGroupState.summaryTrackId]);
69  }
70
71  view({attrs}: m.CVnode<Attrs>) {
72    const collapsed = this.trackGroupState.collapsed;
73    let name = this.trackGroupState.name;
74    if (name[0] === '/') {
75      name = StripPathFromExecutable(name);
76    }
77
78    // The shell should be highlighted if the current search result is inside
79    // this track group.
80    let highlightClass = '';
81    const searchIndex = globals.frontendLocalState.searchIndex;
82    if (searchIndex !== -1) {
83      const trackId = globals.currentSearchResults
84                          .trackIds[globals.frontendLocalState.searchIndex];
85      const parentTrackId = getContainingTrackId(globals.state, trackId);
86      if (parentTrackId === attrs.trackGroupId) {
87        highlightClass = 'flash';
88      }
89    }
90
91    const selectedArea = globals.frontendLocalState.selectedArea.area;
92    const trackGroup = globals.state.trackGroups[attrs.trackGroupId];
93    let checkBox = BLANK_CHECKBOX;
94    if (selectedArea) {
95      if (selectedArea.tracks.includes(attrs.trackGroupId) &&
96          trackGroup.tracks.every(id => selectedArea.tracks.includes(id))) {
97        checkBox = CHECKBOX;
98      } else if (
99          selectedArea.tracks.includes(attrs.trackGroupId) ||
100          trackGroup.tracks.some(id => selectedArea.tracks.includes(id))) {
101        checkBox = INDETERMINATE_CHECKBOX;
102      }
103    }
104
105    return m(
106        `.track-group-panel[collapsed=${collapsed}]`,
107        {id: 'track_' + this.trackGroupId},
108        m(`.shell`,
109          {
110            onclick: (e: MouseEvent) => {
111              globals.dispatch(Actions.toggleTrackGroupCollapsed({
112                trackGroupId: attrs.trackGroupId,
113              })),
114                  e.stopPropagation();
115            },
116            class: `${highlightClass}`,
117          },
118
119          m('.fold-button',
120            m('i.material-icons',
121              this.trackGroupState.collapsed ? EXPAND_DOWN : EXPAND_UP)),
122          m('h1',
123            {
124              title: name,
125            },
126            name),
127          selectedArea ? m('i.material-icons.track-button',
128                           {
129                             onclick: (e: MouseEvent) => {
130                               globals.frontendLocalState.toggleTrackSelection(
131                                   attrs.trackGroupId, true /*trackGroup*/);
132                               e.stopPropagation();
133                             }
134                           },
135                           checkBox) :
136                         ''),
137
138        this.summaryTrack ? m(TrackContent, {track: this.summaryTrack}) : null);
139  }
140
141  oncreate(vnode: m.CVnodeDOM<Attrs>) {
142    this.onupdate(vnode);
143  }
144
145  onupdate({dom}: m.CVnodeDOM<Attrs>) {
146    const shell = assertExists(dom.querySelector('.shell'));
147    this.shellWidth = shell.getBoundingClientRect().width;
148    this.backgroundColor =
149        getComputedStyle(dom).getPropertyValue('--collapsed-background');
150  }
151
152  highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
153    const localState = globals.frontendLocalState;
154    const area = localState.selectedArea.area;
155    if (area && area.tracks.includes(this.trackGroupId)) {
156      ctx.fillStyle = '#ebeef9';
157      ctx.fillRect(
158          localState.timeScale.timeToPx(area.startSec) + this.shellWidth,
159          0,
160          localState.timeScale.deltaTimeToPx(area.endSec - area.startSec),
161          size.height);
162    }
163  }
164
165  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
166    const collapsed = this.trackGroupState.collapsed;
167    if (!collapsed) return;
168
169    ctx.save();
170
171    ctx.fillStyle = this.backgroundColor;
172    ctx.fillRect(0, 0, size.width, size.height);
173
174    this.highlightIfTrackSelected(ctx, size);
175
176    drawGridLines(
177        ctx,
178        globals.frontendLocalState.timeScale,
179        globals.frontendLocalState.visibleWindowTime,
180        size.width,
181        size.height);
182
183    ctx.translate(this.shellWidth, 0);
184
185    if (this.summaryTrack) {
186      this.summaryTrack.render(ctx);
187    }
188    ctx.restore();
189
190    const localState = globals.frontendLocalState;
191    // Draw vertical line when hovering on the notes panel.
192    if (localState.hoveredNoteTimestamp !== -1) {
193      drawVerticalLineAtTime(
194          ctx,
195          localState.timeScale,
196          localState.hoveredNoteTimestamp,
197          size.height,
198          `#aaa`);
199    }
200    if (localState.hoveredLogsTimestamp !== -1) {
201      drawVerticalLineAtTime(
202          ctx,
203          localState.timeScale,
204          localState.hoveredLogsTimestamp,
205          size.height,
206          `rgb(52,69,150)`);
207    }
208    if (localState.selectedArea.area !== undefined &&
209        !globals.frontendLocalState.selectingArea) {
210      drawVerticalSelection(
211          ctx,
212          localState.timeScale,
213          localState.selectedArea.area.startSec,
214          localState.selectedArea.area.endSec,
215          size.height,
216          `rgba(0,0,0,0.5)`);
217    }
218    if (globals.state.currentSelection !== null) {
219      if (globals.state.currentSelection.kind === 'NOTE') {
220        const note = globals.state.notes[globals.state.currentSelection.id];
221        drawVerticalLineAtTime(ctx,
222                               localState.timeScale,
223                               note.timestamp,
224                               size.height,
225                               note.color);
226        if (note.noteType === 'AREA') {
227          drawVerticalLineAtTime(
228              ctx,
229              localState.timeScale,
230              note.area.endSec,
231              size.height,
232              note.color);
233        }
234      }
235      if (globals.state.currentSelection.kind === 'SLICE' &&
236          globals.sliceDetails.wakeupTs !== undefined) {
237        drawVerticalLineAtTime(
238            ctx,
239            localState.timeScale,
240            globals.sliceDetails.wakeupTs,
241            size.height,
242            `black`);
243      }
244    }
245    // All marked areas should have semi-transparent vertical lines
246    // marking the start and end.
247    for (const note of Object.values(globals.state.notes)) {
248      if (note.noteType === 'AREA') {
249        const transparentNoteColor =
250            'rgba(' + hex.rgb(note.color.substr(1)).toString() + ', 0.65)';
251        drawVerticalLineAtTime(
252            ctx,
253            localState.timeScale,
254            note.area.startSec,
255            size.height,
256            transparentNoteColor,
257            1);
258        drawVerticalLineAtTime(
259            ctx,
260            localState.timeScale,
261            note.area.endSec,
262            size.height,
263            transparentNoteColor,
264            1);
265      }
266    }
267  }
268}
269
270function StripPathFromExecutable(path: string) {
271  return path.split('/').slice(-1)[0];
272}
273