• 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 m from 'mithril';
16import {DisposableStack} from '../../base/disposable_stack';
17import {toHTMLElement} from '../../base/dom_utils';
18import {Rect2D} from '../../base/geom';
19import {TimeScale} from '../../base/time_scale';
20import {AppImpl} from '../../core/app_impl';
21import {featureFlags} from '../../core/feature_flags';
22import {PageWithTraceImplAttrs} from '../../core/page_manager';
23import {raf} from '../../core/raf_scheduler';
24import {OverviewTimeline} from './overview_timeline_panel';
25import {TabPanel} from './tab_panel';
26import {TimelineHeader} from './timeline_header';
27import {TrackTreeView} from './track_tree_view';
28import {KeyboardNavigationHandler} from './wasd_navigation_handler';
29import {trackMatchesFilter} from '../../core/track_manager';
30
31const OVERVIEW_PANEL_FLAG = featureFlags.register({
32  id: 'overviewVisible',
33  name: 'Overview Panel',
34  description: 'Show the panel providing an overview of the trace',
35  defaultValue: true,
36});
37
38export class ViewerPage implements m.ClassComponent<PageWithTraceImplAttrs> {
39  private readonly trash = new DisposableStack();
40  private timelineBounds?: Rect2D;
41
42  view({attrs}: m.CVnode<PageWithTraceImplAttrs>) {
43    const {trace} = attrs;
44
45    return m(
46      '.pf-viewer-page.page',
47      m(
48        TabPanel,
49        {trace},
50        OVERVIEW_PANEL_FLAG.get() &&
51          m(OverviewTimeline, {
52            trace,
53            className: 'pf-viewer-page__overview',
54          }),
55        m(TimelineHeader, {
56          trace,
57          className: 'pf-viewer-page__header',
58          // There are three independent canvases on this page which we could
59          // use keep track of the timeline width, but we use the header one
60          // because it's always rendered.
61          onTimelineBoundsChange: (rect) => (this.timelineBounds = rect),
62        }),
63        // Hide tracks while the trace is loading to prevent thrashing.
64        !AppImpl.instance.isLoadingTrace && [
65          // Don't render pinned tracks if we have none.
66          trace.workspace.pinnedTracks.length > 0 &&
67            m(TrackTreeView, {
68              trace,
69              className: 'pf-viewer-page__pinned-track-tree',
70              rootNode: trace.workspace.pinnedTracksNode,
71              canReorderNodes: true,
72              scrollToNewTracks: true,
73            }),
74          m(TrackTreeView, {
75            trace,
76            className: 'pf-viewer-page__scrolling-track-tree',
77            rootNode: trace.workspace.tracks,
78            canReorderNodes: trace.workspace.userEditable,
79            canRemoveNodes: trace.workspace.userEditable,
80            trackFilter: (track) => trackMatchesFilter(trace, track),
81          }),
82        ],
83      ),
84    );
85  }
86
87  oncreate(vnode: m.VnodeDOM<PageWithTraceImplAttrs>) {
88    const {attrs, dom} = vnode;
89
90    // Handles WASD keybindings to pan & zoom
91    const panZoomHandler = new KeyboardNavigationHandler({
92      element: toHTMLElement(dom),
93      onPanned: (pannedPx: number) => {
94        if (!this.timelineBounds) return;
95        const timeline = attrs.trace.timeline;
96        const timescale = new TimeScale(
97          timeline.visibleWindow,
98          this.timelineBounds,
99        );
100        const tDelta = timescale.pxToDuration(pannedPx);
101        timeline.panVisibleWindow(tDelta);
102        raf.scheduleCanvasRedraw();
103      },
104      onZoomed: (zoomedPositionPx: number, zoomRatio: number) => {
105        if (!this.timelineBounds) return;
106        const timeline = attrs.trace.timeline;
107        const zoomPx = zoomedPositionPx - this.timelineBounds.left;
108        const centerPoint = zoomPx / this.timelineBounds.width;
109        timeline.zoomVisibleWindow(1 - zoomRatio, centerPoint);
110        raf.scheduleCanvasRedraw();
111      },
112    });
113    this.trash.use(panZoomHandler);
114    this.onupdate(vnode);
115  }
116
117  onupdate({attrs}: m.VnodeDOM<PageWithTraceImplAttrs>) {
118    // TODO(stevegolton): It's assumed that the TrackStacks will call into
119    // trace.tracks.getTrackRenderer() in their view() functions which will mark
120    // track renderers as used. We call flushOldTracks() here as it's guaranteed
121    // to be called after view() on all child elements, and is only called once
122    // per render cycle. However, this approach involves a bit too much magic.
123    // The TODO is to sort this out and make it so the track flushing is
124    // consolidated into one place.
125    attrs.trace.tracks.flushOldTracks();
126  }
127
128  onremove() {
129    this.trash.dispose();
130  }
131}
132