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