// Copyright (C) 2018 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 {assertFalse, assertTrue} from '../base/logging'; import {TimeSpan} from '../common/time'; const MAX_ZOOM_SPAN_SEC = 1e-6; // 1us. /** * Defines a mapping between number and seconds for the entire application. * Linearly scales time values from boundsMs to pixel values in boundsPx and * back. */ export class TimeScale { private timeBounds: TimeSpan; private _startPx: number; private _endPx: number; private secPerPx = 0; constructor(timeBounds: TimeSpan, boundsPx: [number, number]) { this.timeBounds = timeBounds; this._startPx = boundsPx[0]; this._endPx = boundsPx[1]; this.updateSlope(); } private updateSlope() { this.secPerPx = this.timeBounds.duration / (this._endPx - this._startPx); } deltaTimeToPx(time: number): number { return Math.round(time / this.secPerPx); } timeToPx(time: number): number { return this._startPx + (time - this.timeBounds.start) / this.secPerPx; } pxToTime(px: number): number { return this.timeBounds.start + (px - this._startPx) * this.secPerPx; } deltaPxToDuration(px: number): number { return px * this.secPerPx; } setTimeBounds(timeBounds: TimeSpan) { this.timeBounds = timeBounds; this.updateSlope(); } setLimitsPx(pxStart: number, pxEnd: number) { assertFalse(pxStart === pxEnd); assertTrue(pxStart >= 0 && pxEnd >= 0); this._startPx = pxStart; this._endPx = pxEnd; this.updateSlope(); } timeInBounds(time: number): boolean { return this.timeBounds.isInBounds(time); } get startPx(): number { return this._startPx; } get endPx(): number { return this._endPx; } } export function computeZoom( scale: TimeScale, span: TimeSpan, zoomFactor: number, zoomPx: number): TimeSpan { const startPx = scale.startPx; const endPx = scale.endPx; const deltaPx = endPx - startPx; const deltaTime = span.end - span.start; const newDeltaTime = Math.max(deltaTime * zoomFactor, MAX_ZOOM_SPAN_SEC); const clampedZoomPx = Math.max(startPx, Math.min(endPx, zoomPx)); const zoomTime = scale.pxToTime(clampedZoomPx); const r = (clampedZoomPx - startPx) / deltaPx; const newStartTime = zoomTime - newDeltaTime * r; const newEndTime = newStartTime + newDeltaTime; return new TimeSpan(newStartTime, newEndTime); }