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