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