• 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 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 {isString} from '../base/object_utils';
16import {globals} from '../frontend/globals';
17
18export function cropText(str: string, charWidth: number, rectWidth: number) {
19  let displayText = '';
20  const maxLength = Math.floor(rectWidth / charWidth) - 1;
21  if (str.length <= maxLength) {
22    displayText = str;
23  } else {
24    let limit = maxLength;
25    let maybeTripleDot = '';
26    if (maxLength > 1) {
27      limit = maxLength - 1;
28      maybeTripleDot = '\u2026';
29    }
30    // Javascript strings are UTF-16. |limit| could point in the middle of a
31    // 32-bit double-wchar codepoint (e.g., an emoji). Here we detect if the
32    // |limit|-th wchar is a leading surrogate and attach the trailing one.
33    const lastCharCode = str.charCodeAt(limit - 1);
34    limit += lastCharCode >= 0xd800 && lastCharCode < 0xdc00 ? 1 : 0;
35    displayText = str.substring(0, limit) + maybeTripleDot;
36  }
37  return displayText;
38}
39
40export function drawDoubleHeadedArrow(
41  ctx: CanvasRenderingContext2D,
42  x: number,
43  y: number,
44  length: number,
45  showArrowHeads: boolean,
46  width = 2,
47  color = 'black',
48) {
49  ctx.beginPath();
50  ctx.lineWidth = width;
51  ctx.lineCap = 'round';
52  ctx.strokeStyle = color;
53  ctx.moveTo(x, y);
54  ctx.lineTo(x + length, y);
55  ctx.stroke();
56  ctx.closePath();
57  // Arrowheads on the each end of the line.
58  if (showArrowHeads) {
59    ctx.beginPath();
60    ctx.moveTo(x + length - 8, y - 4);
61    ctx.lineTo(x + length, y);
62    ctx.lineTo(x + length - 8, y + 4);
63    ctx.stroke();
64    ctx.closePath();
65    ctx.beginPath();
66    ctx.moveTo(x + 8, y - 4);
67    ctx.lineTo(x, y);
68    ctx.lineTo(x + 8, y + 4);
69    ctx.stroke();
70    ctx.closePath();
71  }
72}
73
74export function drawIncompleteSlice(
75  ctx: CanvasRenderingContext2D,
76  x: number,
77  y: number,
78  width: number,
79  height: number,
80  showGradient: boolean = true,
81) {
82  if (width <= 0 || height <= 0) {
83    return;
84  }
85  ctx.beginPath();
86  const triangleSize = height / 4;
87  ctx.moveTo(x, y);
88  ctx.lineTo(x + width, y);
89  ctx.lineTo(x + width - 3, y + triangleSize * 0.5);
90  ctx.lineTo(x + width, y + triangleSize);
91  ctx.lineTo(x + width - 3, y + triangleSize * 1.5);
92  ctx.lineTo(x + width, y + 2 * triangleSize);
93  ctx.lineTo(x + width - 3, y + triangleSize * 2.5);
94  ctx.lineTo(x + width, y + 3 * triangleSize);
95  ctx.lineTo(x + width - 3, y + triangleSize * 3.5);
96  ctx.lineTo(x + width, y + 4 * triangleSize);
97  ctx.lineTo(x, y + height);
98
99  const fillStyle = ctx.fillStyle;
100  if (isString(fillStyle)) {
101    if (showGradient) {
102      const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
103      gradient.addColorStop(0.66, fillStyle);
104      gradient.addColorStop(1, '#FFFFFF');
105      ctx.fillStyle = gradient;
106    }
107  } else {
108    throw new Error(
109      `drawIncompleteSlice() expects fillStyle to be a simple color not ${fillStyle}`,
110    );
111  }
112
113  ctx.fill();
114  ctx.fillStyle = fillStyle;
115}
116
117export function drawTrackHoverTooltip(
118  ctx: CanvasRenderingContext2D,
119  pos: {x: number; y: number},
120  maxHeight: number,
121  text: string,
122  text2?: string,
123) {
124  ctx.font = '10px Roboto Condensed';
125  ctx.textBaseline = 'middle';
126  ctx.textAlign = 'left';
127
128  // TODO(hjd): Avoid measuring text all the time (just use monospace?)
129  const textMetrics = ctx.measureText(text);
130  const text2Metrics = ctx.measureText(text2 || '');
131
132  // Padding on each side of the box containing the tooltip:
133  const paddingPx = 4;
134
135  // Figure out the width of the tool tip box:
136  let width = Math.max(textMetrics.width, text2Metrics.width);
137  width += paddingPx * 2;
138
139  // and the height:
140  let height = 0;
141  height += textMetrics.fontBoundingBoxAscent;
142  height += textMetrics.fontBoundingBoxDescent;
143  if (text2 !== undefined) {
144    height += text2Metrics.fontBoundingBoxAscent;
145    height += text2Metrics.fontBoundingBoxDescent;
146  }
147  height += paddingPx * 2;
148
149  let x = pos.x;
150  let y = pos.y;
151
152  // Move box to the top right of the mouse:
153  x += 10;
154  y -= 10;
155
156  // Ensure the box is on screen:
157  const endPx = globals.timeline.visibleTimeScale.pxSpan.end;
158  if (x + width > endPx) {
159    x -= x + width - endPx;
160  }
161  if (y < 0) {
162    y = 0;
163  }
164  if (y + height > maxHeight) {
165    y -= y + height - maxHeight;
166  }
167
168  // Draw everything:
169  ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
170  ctx.fillRect(x, y, width, height);
171
172  ctx.fillStyle = 'hsl(200, 50%, 40%)';
173  ctx.fillText(
174    text,
175    x + paddingPx,
176    y + paddingPx + textMetrics.fontBoundingBoxAscent,
177  );
178  if (text2 !== undefined) {
179    const yOffsetPx =
180      textMetrics.fontBoundingBoxAscent +
181      textMetrics.fontBoundingBoxDescent +
182      text2Metrics.fontBoundingBoxAscent;
183    ctx.fillText(text2, x + paddingPx, y + paddingPx + yOffsetPx);
184  }
185}
186
187export function canvasClip(
188  ctx: CanvasRenderingContext2D,
189  x: number,
190  y: number,
191  w: number,
192  h: number,
193): void {
194  ctx.beginPath();
195  ctx.rect(x, y, w, h);
196  ctx.clip();
197}
198