• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {time} from '../base/time';
16import {Actions} from '../common/actions';
17import {
18  HighPrecisionTime,
19  HighPrecisionTimeSpan,
20} from '../common/high_precision_time';
21import {getContainingGroupKey} from '../common/state';
22
23import {globals} from './globals';
24
25// Given a timestamp, if |ts| is not currently in view move the view to
26// center |ts|, keeping the same zoom level.
27export function horizontalScrollToTs(ts: time) {
28  const time = HighPrecisionTime.fromTime(ts);
29  const visibleWindow = globals.timeline.visibleWindowTime;
30  if (!visibleWindow.contains(time)) {
31    // TODO(hjd): This is an ugly jump, we should do a smooth pan instead.
32    const halfDuration = visibleWindow.duration.divide(2);
33    const newStart = time.sub(halfDuration);
34    const newWindow = new HighPrecisionTimeSpan(
35      newStart,
36      newStart.add(visibleWindow.duration),
37    );
38    globals.timeline.updateVisibleTime(newWindow);
39  }
40}
41
42// Given a start and end timestamp (in ns), move the viewport to center this
43// range and zoom if necessary:
44// - If [viewPercentage] is specified, the viewport will be zoomed so that
45//   the given time range takes up this percentage of the viewport.
46// The following scenarios assume [viewPercentage] is undefined.
47// - If the new range is more than 50% of the viewport, zoom out to a level
48// where
49//   the range is 1/5 of the viewport.
50// - If the new range is already centered, update the zoom level for the
51// viewport
52//   to cover 1/5 of the viewport.
53// - Otherwise, preserve the zoom range.
54export function focusHorizontalRange(
55  start: time,
56  end: time,
57  viewPercentage?: number,
58) {
59  const visible = globals.timeline.visibleWindowTime;
60  const trace = globals.stateTraceTime();
61  const select = HighPrecisionTimeSpan.fromTime(start, end);
62
63  if (viewPercentage !== undefined) {
64    if (viewPercentage <= 0.0 || viewPercentage > 1.0) {
65      console.warn(
66        'Invalid value for [viewPercentage]. ' +
67          'Value must be between 0.0 (exclusive) and 1.0 (inclusive).',
68      );
69      // Default to 50%.
70      viewPercentage = 0.5;
71    }
72    const paddingPercentage = 1.0 - viewPercentage;
73    const paddingTime = select.duration.multiply(paddingPercentage);
74    const halfPaddingTime = paddingTime.divide(2);
75    globals.timeline.updateVisibleTime(select.pad(halfPaddingTime));
76    return;
77  }
78  // If the range is too large to fit on the current zoom level, resize.
79  if (select.duration.gt(visible.duration.multiply(0.5))) {
80    const paddedRange = select.pad(select.duration.multiply(2));
81    globals.timeline.updateVisibleTime(paddedRange);
82    return;
83  }
84  // Calculate the new visible window preserving the zoom level.
85  let newStart = select.midpoint.sub(visible.duration.divide(2));
86  let newEnd = select.midpoint.add(visible.duration.divide(2));
87
88  // Adjust the new visible window if it intersects with the trace boundaries.
89  // It's needed to make the "update the zoom level if visible window doesn't
90  // change" logic reliable.
91  if (newEnd.gt(trace.end)) {
92    newStart = trace.end.sub(visible.duration);
93    newEnd = trace.end;
94  }
95  if (newStart.lt(trace.start)) {
96    newStart = trace.start;
97    newEnd = trace.start.add(visible.duration);
98  }
99
100  const view = new HighPrecisionTimeSpan(newStart, newEnd);
101
102  // If preserving the zoom doesn't change the visible window, update the zoom
103  // level.
104  if (view.start.eq(visible.start) && view.end.eq(visible.end)) {
105    const padded = select.pad(select.duration.multiply(2));
106    globals.timeline.updateVisibleTime(padded);
107  } else {
108    globals.timeline.updateVisibleTime(view);
109  }
110}
111
112// Given a track id, find a track with that id and scroll it into view. If the
113// track is nested inside a track group, scroll to that track group instead.
114// If |openGroup| then open the track group and scroll to the track.
115export function verticalScrollToTrack(
116  trackKey: string | number,
117  openGroup = false,
118) {
119  const trackKeyString = `${trackKey}`;
120  const track = document.querySelector('#track_' + trackKeyString);
121
122  if (track) {
123    // block: 'nearest' means that it will only scroll if the track is not
124    // currently in view.
125    track.scrollIntoView({behavior: 'smooth', block: 'nearest'});
126    return;
127  }
128
129  let trackGroup = null;
130  const groupKey = getContainingGroupKey(globals.state, trackKeyString);
131  if (groupKey) {
132    trackGroup = document.querySelector('#track_' + groupKey);
133  }
134
135  if (!groupKey || !trackGroup) {
136    console.error(`Can't scroll, track (${trackKeyString}) not found.`);
137    return;
138  }
139
140  // The requested track is inside a closed track group, either open the track
141  // group and scroll to the track or just scroll to the track group.
142  if (openGroup) {
143    // After the track exists in the dom, it will be scrolled to.
144    globals.scrollToTrackKey = trackKey;
145    globals.dispatch(Actions.toggleTrackGroupCollapsed({groupKey}));
146    return;
147  } else {
148    trackGroup.scrollIntoView({behavior: 'smooth', block: 'nearest'});
149  }
150}
151
152// Scroll vertically and horizontally to reach track (|trackKey|) at |ts|.
153export function scrollToTrackAndTs(
154  trackKey: string | number | undefined,
155  ts: time,
156  openGroup = false,
157) {
158  if (trackKey !== undefined) {
159    verticalScrollToTrack(trackKey, openGroup);
160  }
161  horizontalScrollToTs(ts);
162}
163
164// Scroll vertically and horizontally to a track and time range
165export function reveal(
166  trackKey: string | number,
167  start: time,
168  end: time,
169  openGroup = false,
170) {
171  verticalScrollToTrack(trackKey, openGroup);
172  focusHorizontalRange(start, end);
173}
174