• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 size 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 m from 'mithril';
16import {BigintMath} from '../base/bigint_math';
17
18import {Span, tpTimeToString} from '../common/time';
19import {
20  TPTime,
21  TPTimeSpan,
22} from '../common/time';
23
24import {
25  BACKGROUND_COLOR,
26  FOREGROUND_COLOR,
27  TRACK_SHELL_WIDTH,
28} from './css_constants';
29import {globals} from './globals';
30import {
31  getMaxMajorTicks,
32  TickGenerator,
33  TickType,
34  timeScaleForVisibleWindow,
35} from './gridline_helper';
36import {Panel, PanelSize} from './panel';
37
38export interface BBox {
39  x: number;
40  y: number;
41  width: number;
42  height: number;
43}
44
45// Draws a vertical line with two horizontal tails at the left and right and
46// a label in the middle. It looks a bit like a stretched H:
47// |--- Label ---|
48// The |target| bounding box determines where to draw the H.
49// The |bounds| bounding box gives the visible region, this is used to adjust
50// the positioning of the label to ensure it is on screen.
51function drawHBar(
52    ctx: CanvasRenderingContext2D, target: BBox, bounds: BBox, label: string) {
53  ctx.fillStyle = FOREGROUND_COLOR;
54
55  const xLeft = Math.floor(target.x);
56  const xRight = Math.floor(target.x + target.width);
57  const yMid = Math.floor(target.height / 2 + target.y);
58  const xWidth = xRight - xLeft;
59
60  // Don't draw in the track shell.
61  ctx.beginPath();
62  ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height);
63  ctx.clip();
64
65  // Draw horizontal bar of the H.
66  ctx.fillRect(xLeft, yMid, xWidth, 1);
67  // Draw left vertical bar of the H.
68  ctx.fillRect(xLeft, target.y, 1, target.height);
69  // Draw right vertical bar of the H.
70  ctx.fillRect(xRight, target.y, 1, target.height);
71
72  const labelWidth = ctx.measureText(label).width;
73
74  // Find a good position for the label:
75  // By default put the label in the middle of the H:
76  let labelXLeft = Math.floor(xWidth / 2 - labelWidth / 2 + xLeft);
77
78  if (labelWidth > target.width || labelXLeft < bounds.x ||
79      (labelXLeft + labelWidth) > (bounds.x + bounds.width)) {
80    // It won't fit in the middle or would be at least partly out of bounds
81    // so put it either to the left or right:
82    if (xRight > bounds.x + bounds.width) {
83      // If the H extends off the right side of the screen the label
84      // goes on the left of the H.
85      labelXLeft = xLeft - labelWidth - 3;
86    } else {
87      // Otherwise the label goes on the right of the H.
88      labelXLeft = xRight + 3;
89    }
90  }
91
92  ctx.fillStyle = BACKGROUND_COLOR;
93  ctx.fillRect(labelXLeft - 1, 0, labelWidth + 1, target.height);
94
95  ctx.textBaseline = 'middle';
96  ctx.fillStyle = FOREGROUND_COLOR;
97  ctx.font = '10px Roboto Condensed';
98  ctx.fillText(label, labelXLeft, yMid);
99}
100
101function drawIBar(
102    ctx: CanvasRenderingContext2D, xPos: number, bounds: BBox, label: string) {
103  if (xPos < bounds.x) return;
104
105  ctx.fillStyle = FOREGROUND_COLOR;
106  ctx.fillRect(xPos, 0, 1, bounds.width);
107
108  const yMid = Math.floor(bounds.height / 2 + bounds.y);
109  const labelWidth = ctx.measureText(label).width;
110  const padding = 3;
111
112  let xPosLabel;
113  if (xPos + padding + labelWidth > bounds.width) {
114    xPosLabel = xPos - padding;
115    ctx.textAlign = 'right';
116  } else {
117    xPosLabel = xPos + padding;
118    ctx.textAlign = 'left';
119  }
120
121  ctx.fillStyle = BACKGROUND_COLOR;
122  ctx.fillRect(xPosLabel - 1, 0, labelWidth + 2, bounds.height);
123
124  ctx.textBaseline = 'middle';
125  ctx.fillStyle = FOREGROUND_COLOR;
126  ctx.font = '10px Roboto Condensed';
127  ctx.fillText(label, xPosLabel, yMid);
128}
129
130export class TimeSelectionPanel extends Panel {
131  view() {
132    return m('.time-selection-panel');
133  }
134
135  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
136    ctx.fillStyle = '#999';
137    ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height);
138
139    ctx.save();
140    ctx.beginPath();
141    ctx.rect(TRACK_SHELL_WIDTH, 0, size.width - TRACK_SHELL_WIDTH, size.height);
142    ctx.clip();
143
144    const span = globals.frontendLocalState.visibleWindow.timestampSpan;
145    if (size.width > TRACK_SHELL_WIDTH && span.duration > 0n) {
146      const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
147      const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
148      for (const {type, time} of new TickGenerator(
149               span, maxMajorTicks, globals.state.traceTime.start)) {
150        const px = Math.floor(map.tpTimeToPx(time));
151        if (type === TickType.MAJOR) {
152          ctx.fillRect(px, 0, 1, size.height);
153        }
154      }
155    }
156
157    const localArea = globals.frontendLocalState.selectedArea;
158    const selection = globals.state.currentSelection;
159    if (localArea !== undefined) {
160      const start = BigintMath.min(localArea.start, localArea.end);
161      const end = BigintMath.max(localArea.start, localArea.end);
162      this.renderSpan(ctx, size, new TPTimeSpan(start, end));
163    } else if (selection !== null && selection.kind === 'AREA') {
164      const selectedArea = globals.state.areas[selection.areaId];
165      const start = BigintMath.min(selectedArea.start, selectedArea.end);
166      const end = BigintMath.max(selectedArea.start, selectedArea.end);
167      this.renderSpan(ctx, size, new TPTimeSpan(start, end));
168    }
169
170    if (globals.state.hoverCursorTimestamp !== -1n) {
171      this.renderHover(ctx, size, globals.state.hoverCursorTimestamp);
172    }
173
174    for (const note of Object.values(globals.state.notes)) {
175      const noteIsSelected = selection !== null && selection.kind === 'AREA' &&
176          selection.noteId === note.id;
177      if (note.noteType === 'AREA' && !noteIsSelected) {
178        const selectedArea = globals.state.areas[note.areaId];
179        this.renderSpan(
180            ctx, size, new TPTimeSpan(selectedArea.start, selectedArea.end));
181      }
182    }
183
184    ctx.restore();
185  }
186
187  renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: TPTime) {
188    const {visibleTimeScale} = globals.frontendLocalState;
189    const xPos =
190        TRACK_SHELL_WIDTH + Math.floor(visibleTimeScale.tpTimeToPx(ts));
191    const offsetTime = tpTimeToString(ts - globals.state.traceTime.start);
192    const timeFromStart = tpTimeToString(ts);
193    const label = `${offsetTime} (${timeFromStart})`;
194    drawIBar(ctx, xPos, this.bounds(size), label);
195  }
196
197  renderSpan(
198      ctx: CanvasRenderingContext2D, size: PanelSize, span: Span<TPTime>) {
199    const {visibleTimeScale} = globals.frontendLocalState;
200    const xLeft = visibleTimeScale.tpTimeToPx(span.start);
201    const xRight = visibleTimeScale.tpTimeToPx(span.end);
202    const label = tpTimeToString(span.duration);
203    drawHBar(
204        ctx,
205        {
206          x: TRACK_SHELL_WIDTH + xLeft,
207          y: 0,
208          width: xRight - xLeft,
209          height: size.height,
210        },
211        this.bounds(size),
212        label);
213  }
214
215  private bounds(size: PanelSize): BBox {
216    return {
217      x: TRACK_SHELL_WIDTH,
218      y: 0,
219      width: size.width - TRACK_SHELL_WIDTH,
220      height: size.height,
221    };
222  }
223}
224