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