1// Copyright (C) 2019 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 {Actions} from '../common/actions'; 16import {getContainingTrackId} from '../common/state'; 17import {fromNs, TimeSpan, toNs} from '../common/time'; 18 19import {globals} from './globals'; 20 21const INCOMPLETE_SLICE_TIME_S = 0.00003; 22 23/** 24 * Given a timestamp, if |ts| is not currently in view move the view to 25 * center |ts|, keeping the same zoom level. 26 */ 27export function horizontalScrollToTs(ts: number) { 28 const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start); 29 const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end); 30 const currentViewNs = endNs - startNs; 31 if (ts < startNs || ts > endNs) { 32 // TODO(hjd): This is an ugly jump, we should do a smooth pan instead. 33 globals.frontendLocalState.updateVisibleTime(new TimeSpan( 34 fromNs(ts - currentViewNs / 2), fromNs(ts + currentViewNs / 2))); 35 } 36} 37 38/** 39 * Given a start and end timestamp (in ns), move the view to center this range 40 * and zoom to a level where the range is 1/5 of the viewport. 41 */ 42export function horizontalScrollAndZoomToRange(startTs: number, endTs: number) { 43 const visibleDur = globals.frontendLocalState.visibleWindowTime.end - 44 globals.frontendLocalState.visibleWindowTime.start; 45 let selectDur = endTs - startTs; 46 if (toNs(selectDur) === -1) { // Unfinished slice 47 selectDur = INCOMPLETE_SLICE_TIME_S; 48 endTs = startTs; 49 } 50 const viewStartNs = toNs(globals.frontendLocalState.visibleWindowTime.start); 51 const viewEndNs = toNs(globals.frontendLocalState.visibleWindowTime.end); 52 if (selectDur / visibleDur < 0.05 || startTs < viewStartNs || 53 endTs > viewEndNs) { 54 globals.frontendLocalState.updateVisibleTime( 55 new TimeSpan(startTs - (selectDur * 2), endTs + (selectDur * 2))); 56 } 57} 58 59/** 60 * Given a track id, find a track with that id and scroll it into view. If the 61 * track is nested inside a track group, scroll to that track group instead. 62 * If |openGroup| then open the track group and scroll to the track. 63 */ 64export function verticalScrollToTrack( 65 trackId: string|number, openGroup = false) { 66 const trackIdString = `${trackId}`; 67 const track = document.querySelector('#track_' + trackIdString); 68 69 if (track) { 70 // block: 'nearest' means that it will only scroll if the track is not 71 // currently in view. 72 track.scrollIntoView({behavior: 'smooth', block: 'nearest'}); 73 return; 74 } 75 76 let trackGroup = null; 77 const trackGroupId = getContainingTrackId(globals.state, trackIdString); 78 if (trackGroupId) { 79 trackGroup = document.querySelector('#track_' + trackGroupId); 80 } 81 82 if (!trackGroupId || !trackGroup) { 83 console.error(`Can't scroll, track (${trackIdString}) not found.`); 84 return; 85 } 86 87 // The requested track is inside a closed track group, either open the track 88 // group and scroll to the track or just scroll to the track group. 89 if (openGroup) { 90 // After the track exists in the dom, it will be scrolled to. 91 globals.frontendLocalState.scrollToTrackId = trackId; 92 globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId})); 93 return; 94 } else { 95 trackGroup.scrollIntoView({behavior: 'smooth', block: 'nearest'}); 96 } 97} 98 99 100/** 101 * Scroll vertically and horizontally to reach track (|trackId|) at |ts|. 102 */ 103export function scrollToTrackAndTs( 104 trackId: string|number|undefined, ts: number, openGroup = false) { 105 if (trackId !== undefined) { 106 verticalScrollToTrack(trackId, openGroup); 107 } 108 horizontalScrollToTs(ts); 109} 110 111/** 112 * Returns the UI track Id that is associated with the given |traceTrackId| in 113 * the trace_processor. Due to concepts like Async tracks and TrackGroups this 114 * is not always a one to one mapping. 115 */ 116export function findUiTrackId(traceTrackId: number) { 117 for (const [uiTrackId, trackState] of Object.entries(globals.state.tracks)) { 118 const config = trackState.config as {trackId: number}; 119 if (config.trackId === traceTrackId) return uiTrackId; 120 const multiple = trackState.config as {trackIds: number[]}; 121 if (multiple.trackIds !== undefined && 122 multiple.trackIds.includes(traceTrackId)) { 123 return uiTrackId; 124 } 125 } 126 return null; 127} 128