// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {Actions} from '../common/actions'; import {getContainingTrackId} from '../common/state'; import {fromNs, TimeSpan, toNs} from '../common/time'; import {globals} from './globals'; const INCOMPLETE_SLICE_TIME_S = 0.00003; /** * Given a timestamp, if |ts| is not currently in view move the view to * center |ts|, keeping the same zoom level. */ export function horizontalScrollToTs(ts: number) { const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start); const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end); const currentViewNs = endNs - startNs; if (ts < startNs || ts > endNs) { // TODO(hjd): This is an ugly jump, we should do a smooth pan instead. globals.frontendLocalState.updateVisibleTime(new TimeSpan( fromNs(ts - currentViewNs / 2), fromNs(ts + currentViewNs / 2))); } } /** * Given a start and end timestamp (in ns), move the view to center this range * and zoom to a level where the range is 1/5 of the viewport. */ export function horizontalScrollAndZoomToRange(startTs: number, endTs: number) { const visibleDur = globals.frontendLocalState.visibleWindowTime.end - globals.frontendLocalState.visibleWindowTime.start; let selectDur = endTs - startTs; if (toNs(selectDur) === -1) { // Unfinished slice selectDur = INCOMPLETE_SLICE_TIME_S; endTs = startTs; } const viewStartNs = toNs(globals.frontendLocalState.visibleWindowTime.start); const viewEndNs = toNs(globals.frontendLocalState.visibleWindowTime.end); if (selectDur / visibleDur < 0.05 || startTs < viewStartNs || endTs > viewEndNs) { globals.frontendLocalState.updateVisibleTime( new TimeSpan(startTs - (selectDur * 2), endTs + (selectDur * 2))); } } /** * Given a track id, find a track with that id and scroll it into view. If the * track is nested inside a track group, scroll to that track group instead. * If |openGroup| then open the track group and scroll to the track. */ export function verticalScrollToTrack( trackId: string|number, openGroup = false) { const trackIdString = `${trackId}`; const track = document.querySelector('#track_' + trackIdString); if (track) { // block: 'nearest' means that it will only scroll if the track is not // currently in view. track.scrollIntoView({behavior: 'smooth', block: 'nearest'}); return; } let trackGroup = null; const trackGroupId = getContainingTrackId(globals.state, trackIdString); if (trackGroupId) { trackGroup = document.querySelector('#track_' + trackGroupId); } if (!trackGroupId || !trackGroup) { console.error(`Can't scroll, track (${trackIdString}) not found.`); return; } // The requested track is inside a closed track group, either open the track // group and scroll to the track or just scroll to the track group. if (openGroup) { // After the track exists in the dom, it will be scrolled to. globals.frontendLocalState.scrollToTrackId = trackId; globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId})); return; } else { trackGroup.scrollIntoView({behavior: 'smooth', block: 'nearest'}); } } /** * Scroll vertically and horizontally to reach track (|trackId|) at |ts|. */ export function scrollToTrackAndTs( trackId: string|number|undefined, ts: number, openGroup = false) { if (trackId !== undefined) { verticalScrollToTrack(trackId, openGroup); } horizontalScrollToTs(ts); } /** * Returns the UI track Id that is associated with the given |traceTrackId| in * the trace_processor. Due to concepts like Async tracks and TrackGroups this * is not always a one to one mapping. */ export function findUiTrackId(traceTrackId: number) { for (const [uiTrackId, trackState] of Object.entries(globals.state.tracks)) { const config = trackState.config as {trackId: number}; if (config.trackId === traceTrackId) return uiTrackId; const multiple = trackState.config as {trackIds: number[]}; if (multiple.trackIds !== undefined && multiple.trackIds.includes(traceTrackId)) { return uiTrackId; } } return null; }