1// Copyright (C) 2018 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 {assertFalse, assertTrue} from '../base/logging'; 16import {TimeSpan} from '../common/time'; 17 18const MAX_ZOOM_SPAN_SEC = 1e-6; // 1us. 19 20/** 21 * Defines a mapping between number and seconds for the entire application. 22 * Linearly scales time values from boundsMs to pixel values in boundsPx and 23 * back. 24 */ 25export class TimeScale { 26 private timeBounds: TimeSpan; 27 private _startPx: number; 28 private _endPx: number; 29 private secPerPx = 0; 30 31 constructor(timeBounds: TimeSpan, boundsPx: [number, number]) { 32 this.timeBounds = timeBounds; 33 this._startPx = boundsPx[0]; 34 this._endPx = boundsPx[1]; 35 this.updateSlope(); 36 } 37 38 private updateSlope() { 39 this.secPerPx = this.timeBounds.duration / (this._endPx - this._startPx); 40 } 41 42 deltaTimeToPx(time: number): number { 43 return Math.round(time / this.secPerPx); 44 } 45 46 timeToPx(time: number): number { 47 return this._startPx + (time - this.timeBounds.start) / this.secPerPx; 48 } 49 50 pxToTime(px: number): number { 51 return this.timeBounds.start + (px - this._startPx) * this.secPerPx; 52 } 53 54 deltaPxToDuration(px: number): number { 55 return px * this.secPerPx; 56 } 57 58 setTimeBounds(timeBounds: TimeSpan) { 59 this.timeBounds = timeBounds; 60 this.updateSlope(); 61 } 62 63 setLimitsPx(pxStart: number, pxEnd: number) { 64 assertFalse(pxStart === pxEnd); 65 assertTrue(pxStart >= 0 && pxEnd >= 0); 66 this._startPx = pxStart; 67 this._endPx = pxEnd; 68 this.updateSlope(); 69 } 70 71 timeInBounds(time: number): boolean { 72 return this.timeBounds.isInBounds(time); 73 } 74 75 get startPx(): number { 76 return this._startPx; 77 } 78 79 get endPx(): number { 80 return this._endPx; 81 } 82} 83 84export function computeZoom( 85 scale: TimeScale, span: TimeSpan, zoomFactor: number, zoomPx: number): 86 TimeSpan { 87 const startPx = scale.startPx; 88 const endPx = scale.endPx; 89 const deltaPx = endPx - startPx; 90 const deltaTime = span.end - span.start; 91 const newDeltaTime = Math.max(deltaTime * zoomFactor, MAX_ZOOM_SPAN_SEC); 92 const clampedZoomPx = Math.max(startPx, Math.min(endPx, zoomPx)); 93 const zoomTime = scale.pxToTime(clampedZoomPx); 94 const r = (clampedZoomPx - startPx) / deltaPx; 95 const newStartTime = zoomTime - newDeltaTime * r; 96 const newEndTime = newStartTime + newDeltaTime; 97 return new TimeSpan(newStartTime, newEndTime); 98} 99