blob: 3e0e6d14b88bb959fcd65be1686c878ae0dc1791 [file] [log] [blame]
// 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;
}