• 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 {Size2D, Point2D} from './geom';
16import {isString} from './object_utils';
17
18export function drawDoubleHeadedArrow(
19  ctx: CanvasRenderingContext2D,
20  x: number,
21  y: number,
22  length: number,
23  showArrowHeads: boolean,
24  width = 2,
25  color = 'black',
26) {
27  ctx.beginPath();
28  ctx.lineWidth = width;
29  ctx.lineCap = 'round';
30  ctx.strokeStyle = color;
31  ctx.moveTo(x, y);
32  ctx.lineTo(x + length, y);
33  ctx.stroke();
34  ctx.closePath();
35  // Arrowheads on the each end of the line.
36  if (showArrowHeads) {
37    ctx.beginPath();
38    ctx.moveTo(x + length - 8, y - 4);
39    ctx.lineTo(x + length, y);
40    ctx.lineTo(x + length - 8, y + 4);
41    ctx.stroke();
42    ctx.closePath();
43    ctx.beginPath();
44    ctx.moveTo(x + 8, y - 4);
45    ctx.lineTo(x, y);
46    ctx.lineTo(x + 8, y + 4);
47    ctx.stroke();
48    ctx.closePath();
49  }
50}
51
52export function drawIncompleteSlice(
53  ctx: CanvasRenderingContext2D,
54  x: number,
55  y: number,
56  width: number,
57  height: number,
58  showGradient: boolean = true,
59) {
60  if (width <= 0 || height <= 0) {
61    return;
62  }
63  ctx.beginPath();
64  const triangleSize = height / 4;
65  ctx.moveTo(x, y);
66  ctx.lineTo(x + width, y);
67  ctx.lineTo(x + width - 3, y + triangleSize * 0.5);
68  ctx.lineTo(x + width, y + triangleSize);
69  ctx.lineTo(x + width - 3, y + triangleSize * 1.5);
70  ctx.lineTo(x + width, y + 2 * triangleSize);
71  ctx.lineTo(x + width - 3, y + triangleSize * 2.5);
72  ctx.lineTo(x + width, y + 3 * triangleSize);
73  ctx.lineTo(x + width - 3, y + triangleSize * 3.5);
74  ctx.lineTo(x + width, y + 4 * triangleSize);
75  ctx.lineTo(x, y + height);
76
77  const fillStyle = ctx.fillStyle;
78  if (isString(fillStyle)) {
79    if (showGradient) {
80      const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
81      gradient.addColorStop(0.66, fillStyle);
82      gradient.addColorStop(1, '#FFFFFF');
83      ctx.fillStyle = gradient;
84    }
85  } else {
86    throw new Error(
87      `drawIncompleteSlice() expects fillStyle to be a simple color not ${fillStyle}`,
88    );
89  }
90
91  ctx.fill();
92  ctx.fillStyle = fillStyle;
93}
94
95export function drawTrackHoverTooltip(
96  ctx: CanvasRenderingContext2D,
97  pos: Point2D,
98  trackSize: Size2D,
99  text: string,
100  text2?: string,
101) {
102  ctx.font = '10px Roboto Condensed';
103  ctx.textBaseline = 'middle';
104  ctx.textAlign = 'left';
105
106  // TODO(hjd): Avoid measuring text all the time (just use monospace?)
107  const textMetrics = ctx.measureText(text);
108  const text2Metrics = ctx.measureText(text2 ?? '');
109
110  // Padding on each side of the box containing the tooltip:
111  const paddingPx = 4;
112
113  // Figure out the width of the tool tip box:
114  let width = Math.max(textMetrics.width, text2Metrics.width);
115  width += paddingPx * 2;
116
117  // and the height:
118  let height = 0;
119  height += textMetrics.fontBoundingBoxAscent;
120  height += textMetrics.fontBoundingBoxDescent;
121  if (text2 !== undefined) {
122    height += text2Metrics.fontBoundingBoxAscent;
123    height += text2Metrics.fontBoundingBoxDescent;
124  }
125  height += paddingPx * 2;
126
127  let x = pos.x;
128  let y = pos.y;
129
130  // Move box to the top right of the mouse:
131  x += 10;
132  y -= 10;
133
134  // Ensure the box is on screen:
135  const endPx = trackSize.width;
136  if (x + width > endPx) {
137    x -= x + width - endPx;
138  }
139  if (y < 0) {
140    y = 0;
141  }
142  if (y + height > trackSize.height) {
143    y -= y + height - trackSize.height;
144  }
145
146  // Draw everything:
147  ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
148  ctx.fillRect(x, y, width, height);
149
150  ctx.fillStyle = 'hsl(200, 50%, 40%)';
151  ctx.fillText(
152    text,
153    x + paddingPx,
154    y + paddingPx + textMetrics.fontBoundingBoxAscent,
155  );
156  if (text2 !== undefined) {
157    const yOffsetPx =
158      textMetrics.fontBoundingBoxAscent +
159      textMetrics.fontBoundingBoxDescent +
160      text2Metrics.fontBoundingBoxAscent;
161    ctx.fillText(text2, x + paddingPx, y + paddingPx + yOffsetPx);
162  }
163}
164
165/**
166 * Clip a canvas using a rect-like object.
167 *
168 * @param ctx - The canvas context to clip.
169 * @param rect - The position and dimensions of the rect to clip.
170 */
171export function canvasClip(
172  ctx: CanvasRenderingContext2D,
173  rect: Point2D & Size2D,
174): void;
175
176/**
177 * Clip a canvas using a separate x, y, width, height values.
178 *
179 * @param ctx - The canvas context to clip.
180 */
181export function canvasClip(
182  ctx: CanvasRenderingContext2D,
183  x: number,
184  y: number,
185  w: number,
186  h: number,
187): void;
188
189// This function can either take individual x, y, w, h parameters to define the
190// rect, or x can be a rect-like object.
191export function canvasClip(
192  ctx: CanvasRenderingContext2D,
193  x: number | (Point2D & Size2D),
194  y?: number,
195  w?: number,
196  h?: number,
197): void {
198  ctx.beginPath();
199  if (typeof x === 'number') {
200    // TypeScript ensures y, w, and h are defined here
201    ctx.rect(x, y!, w!, h!);
202  } else {
203    ctx.rect(x.x, x.y, x.width, x.height);
204  }
205  ctx.clip();
206}
207
208/**
209 * Save the state of the canvas, returning a disposable which restores the state
210 * when disposed.
211 *
212 * Allows using the |using| keyword to automatically restore the canvas state.
213 * @param ctx - The canvas context to save the state of.
214 * @returns A disposable.
215 *
216 * @example
217 * {
218 *   using const _ = canvasSave(ctx);
219 *   ctx.translate(123, 456); // Manipulate the canvas state
220 * } // ctx.restore() is automatically called when the _ falls out of scope
221 */
222export function canvasSave(ctx: CanvasRenderingContext2D): Disposable {
223  ctx.save();
224  return {
225    [Symbol.dispose](): void {
226      ctx.restore();
227    },
228  };
229}
230