| // 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(taylori): 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; |
| } |
| return null; |
| } |