• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
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 */
15
16import { ns2s, Rect, Render } from './ProcedureWorkerCommon';
17import { ColorUtils } from '../../component/trace/base/ColorUtils';
18import { TraceRow } from '../../component/trace/base/TraceRow';
19import { CpuStruct } from './cpu/ProcedureWorkerCPU';
20
21//绘制时间轴
22let timeRuler: TimeRuler | undefined;
23let rangeRuler: RangeRuler | undefined;
24let sportRuler: SportRuler | undefined;
25let offsetTop: number = 0;
26let offsetLeft: number = 0;
27
28export class TimelineRender extends Render {
29  renderMainThread(req: any, row: TraceRow<any>) {}
30}
31
32export function timeline(
33  canvas: OffscreenCanvas,
34  ctx: OffscreenCanvasRenderingContext2D,
35  totalNS: number,
36  frame: Rect,
37  keyPressCode: any,
38  keyUpCode: any,
39  mouseDown: any,
40  mouseUp: any,
41  mouseMove: any,
42  mouseOut: any,
43  _offsetLeft: number,
44  _offsetTop: number,
45  changeHandler: Function
46) {
47  offsetLeft = _offsetLeft;
48  offsetTop = _offsetTop;
49  if (timeRuler == undefined) {
50    timeRuler = new TimeRuler(canvas, ctx, new Rect(0, 0, frame.width, 20), totalNS);
51  }
52  if (!sportRuler) {
53    sportRuler = new SportRuler(canvas, ctx, new Rect(0, 100.5, frame.width, frame.height - 100));
54  }
55  if (!rangeRuler) {
56    rangeRuler = new RangeRuler(
57      canvas,
58      ctx!,
59      new Rect(0, 25, frame.width, 75),
60      {
61        startX: 0,
62        endX: frame.width,
63        startNS: 0,
64        endNS: totalNS,
65        totalNS: totalNS,
66        xs: [],
67        xsTxt: [],
68      },
69      (a) => {
70        if (sportRuler) {
71          sportRuler.range = a;
72        }
73        changeHandler(a);
74      }
75    );
76  }
77  rangeRuler.frame.width = frame.width;
78  sportRuler.frame.width = frame.width;
79  timeRuler.frame.width = frame.width;
80  timelineMouseEvent(keyPressCode, keyUpCode, mouseDown, mouseUp, mouseMove, mouseOut);
81}
82function timelineMouseEvent(
83  keyPressCode: any,
84  keyUpCode: any,
85  mouseDown: any,
86  mouseUp: any,
87  mouseMove: any,
88  mouseOut: any
89) {
90  if (keyPressCode) {
91    rangeRuler!.keyPress(keyPressCode);
92  } else if (keyUpCode) {
93    rangeRuler!.keyUp(keyUpCode);
94  } else if (mouseDown) {
95    rangeRuler!.mouseDown(mouseDown);
96  } else if (mouseUp) {
97    rangeRuler!.mouseUp(mouseUp);
98  } else if (mouseMove) {
99    rangeRuler!.mouseMove(mouseMove);
100  } else if (mouseOut) {
101    rangeRuler!.mouseOut(mouseOut);
102  } else {
103    timeRuler!.draw();
104    rangeRuler!.draw();
105  }
106}
107
108export abstract class Graph {
109  // @ts-ignore
110  c: OffscreenCanvasRenderingContext2D;
111  // @ts-ignore
112  canvas: OffscreenCanvas | undefined | null;
113  frame: Rect;
114
115  protected constructor(
116    // @ts-ignore
117    canvas: OffscreenCanvas | undefined | null,
118    // @ts-ignore
119    c: OffscreenCanvasRenderingContext2D,
120    frame: Rect
121  ) {
122    this.canvas = canvas;
123    this.frame = frame;
124    this.c = c;
125  }
126
127  abstract draw(): void;
128}
129
130export class TimeRuler extends Graph {
131  totalNS: number;
132  private stepSmall: number;
133  private step: number;
134  private stepNS: number;
135
136  constructor(
137    // @ts-ignore
138    canvas: OffscreenCanvas | undefined | null,
139    // @ts-ignore
140    c: OffscreenCanvasRenderingContext2D,
141    frame: Rect,
142    totalNS: number = 10_000_000_000
143  ) {
144    super(canvas, c, frame);
145    this.totalNS = totalNS;
146    this.step = this.frame.width / 10;
147    this.stepSmall = this.frame.width / 100;
148    this.stepNS = this.totalNS / 10;
149  }
150
151  draw() {
152    this.step = this.frame.width / 10;
153    this.stepSmall = this.frame.width / 100;
154    this.stepNS = this.totalNS / 10;
155    this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height);
156    this.c.beginPath();
157    this.c.strokeStyle = '#999';
158    this.c.lineWidth = 1;
159    for (let i = 0; i <= 10; i++) {
160      let x = Math.floor(i * this.step) + this.frame.x;
161      this.c.moveTo(x, 0);
162      this.c.lineTo(x, this.frame.height);
163      if (i == 10) break;
164      for (let j = 1; j < 10; j++) {
165        this.c.moveTo(x + Math.floor(j * this.stepSmall), 0);
166        this.c.lineTo(x + Math.floor(j * this.stepSmall), this.frame.height / 4);
167      }
168      this.c.fillStyle = '#999';
169      this.c.fillText(`${ns2s(i * this.stepNS)}`, x + 5, this.frame.height - 1);
170    }
171    this.c.stroke();
172    this.c.closePath();
173  }
174}
175
176/**
177 * SportRuler
178 */
179export class SportRuler extends Graph {
180  public static rulerFlagObj: Flag | null = null;
181  public flagList: Array<Flag> = [];
182  public flagListIdx: number | null = null;
183  public obj = [{ x: 3 }, { x: 2 }];
184  lineColor: string | null = null;
185  private rangeFlag = new Flag(0, 0, 0, 0, 0);
186  private ruler_w = 1022;
187  private _range: TimeRange = {} as TimeRange;
188
189  constructor(
190    // @ts-ignore
191    canvas: OffscreenCanvas | undefined | null,
192    // @ts-ignore
193    c: OffscreenCanvasRenderingContext2D,
194    frame: Rect
195  ) {
196    super(canvas, c, frame);
197  }
198
199  get range(): TimeRange {
200    return this._range;
201  }
202
203  set range(value: TimeRange) {
204    this._range = value;
205    this.draw();
206  }
207
208  modifyFlagList(type: string, flag: any = {}) {
209    if (type == 'amend') {
210      if (flag.text && this.flagListIdx !== null) {
211        this.flagList[this.flagListIdx].text = flag.text;
212      }
213      if (flag.color && this.flagListIdx !== null) {
214        this.flagList[this.flagListIdx].color = flag.color;
215      }
216    } else if (type == 'remove') {
217      if (this.flagListIdx !== null) {
218        this.flagList.splice(this.flagListIdx, 1);
219      }
220    }
221    this.draw();
222  }
223
224  draw(): void {
225    this.ruler_w = this.frame.width;
226    this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height);
227    this.c.beginPath();
228    this.lineColor = '#dadada';
229    this.c.strokeStyle = this.lineColor; //"#dadada"
230    this.c.lineWidth = 1;
231    this.c.moveTo(this.frame.x, this.frame.y);
232    this.c.lineTo(this.frame.x + this.frame.width, this.frame.y);
233    this.c.stroke();
234    this.c.closePath();
235    this.c.beginPath();
236    this.c.lineWidth = 3;
237    this.c.strokeStyle = '#999999';
238    this.c.moveTo(this.frame.x, this.frame.y);
239    this.c.lineTo(this.frame.x, this.frame.y + this.frame.height);
240    this.c.stroke();
241    this.c.closePath();
242    this.c.beginPath();
243    this.c.lineWidth = 1;
244    this.c.strokeStyle = this.lineColor; //"#999999"
245    this.c.fillStyle = '#999999';
246    this.c.font = '8px sans-serif';
247    this.range.xs?.forEach((it, i) => {
248      this.c.moveTo(it, this.frame.y);
249      this.c.lineTo(it, this.frame.y + this.frame.height);
250      this.c.fillText(`+${this.range.xsTxt[i]}`, it + 3, this.frame.y + 12);
251    });
252
253    this.c.stroke();
254    this.c.closePath();
255  }
256
257  // drawTheFlag
258  drawTheFlag(x: number, color: string = '#999999', isFill: boolean = false, text: string = '') {
259    this.c.beginPath();
260    this.c.fillStyle = color;
261    this.c.strokeStyle = color;
262    this.c.moveTo(x, 125);
263    this.c.lineTo(x + 10, 125);
264    this.c.lineTo(x + 10, 127);
265    this.c.lineTo(x + 18, 127);
266    this.c.lineTo(x + 18, 137);
267    this.c.lineTo(x + 10, 137);
268    this.c.lineTo(x + 10, 135);
269    this.c.lineTo(x + 2, 135);
270    this.c.lineTo(x + 2, 143);
271    this.c.lineTo(x, 143);
272    this.c.closePath();
273    if (isFill) {
274      this.c.fill();
275    }
276    this.c.stroke();
277
278    if (text !== '') {
279      this.c.font = '10px Microsoft YaHei';
280      const { width } = this.c.measureText(text);
281      this.c.fillStyle = 'rgba(255, 255, 255, 0.8)'; //
282      this.c.fillRect(x + 21, 132, width + 4, 12);
283      this.c.fillStyle = 'black';
284      this.c.fillText(text, x + 23, 142);
285      this.c.stroke();
286    }
287  }
288
289  //随机生成十六位进制颜色
290  randomRgbColor() {
291    const letters = '0123456789ABCDEF';
292    let color = '#';
293    for (let i = 0; i < 6; i++) {
294      color += letters[Math.floor(Math.random() * 16)];
295    }
296    return color;
297  }
298
299  //鼠标点击绘画旗子、点击旗子
300  mouseUp(ev: MouseEvent) {}
301
302  //选中的旗子
303  onFlagRangeEvent(flagObj: Flag, idx: number) {
304    SportRuler.rulerFlagObj = flagObj;
305    this.flagListIdx = idx;
306  }
307
308  //鼠标移动 绘画旗子
309  mouseMove(ev: MouseEvent) {
310    let x = ev.offsetX - (offsetLeft || 0);
311    let y = ev.offsetY - (offsetTop || 0);
312    if (y >= 50 && y < 200) {
313      this.draw();
314      if (y >= 123 && y < 142 && x > 0) {
315        let onFlagRange = this.flagList.findIndex((flagObj: Flag) => {
316          let flag_x = Math.round(
317            (this.ruler_w * (flagObj.time - this.range.startNS)) / (this.range.endNS - this.range.startNS)
318          );
319          return x >= flag_x && x <= flag_x + 18;
320        });
321      }
322    }
323  }
324}
325
326const markPadding = 5;
327
328export class Mark extends Graph {
329  name: string | undefined;
330  inspectionFrame: Rect;
331  private _isHover: boolean = false;
332
333  constructor(
334    // @ts-ignore
335    canvas: OffscreenCanvas | undefined | null,
336    name: string,
337    // @ts-ignore
338    c: OffscreenCanvasRenderingContext2D,
339    frame: Rect
340  ) {
341    super(canvas, c, frame);
342    this.name = name;
343    this.inspectionFrame = new Rect(frame.x - markPadding, frame.y, frame.width + markPadding * 2, frame.height);
344  }
345
346  get isHover(): boolean {
347    return this._isHover;
348  }
349
350  set isHover(value: boolean) {
351    this._isHover = value;
352  }
353
354  draw(): void {
355    this.c.beginPath();
356    this.c.lineWidth = 7;
357    this.c.strokeStyle = '#999999';
358    this.c.moveTo(this.frame.x, this.frame.y);
359    this.c.lineTo(this.frame.x, this.frame.y + this.frame.height / 3);
360    this.c.stroke();
361    this.c.lineWidth = 1;
362    this.c.strokeStyle = '#999999';
363    this.c.moveTo(this.frame.x, this.frame.y);
364    this.c.lineTo(this.frame.x, this.frame.y + this.frame.height);
365    this.c.stroke();
366    this.c.closePath();
367  }
368}
369
370export interface TimeRange {
371  totalNS: number;
372  startX: number;
373  endX: number;
374  startNS: number;
375  endNS: number;
376  xs: Array<number>;
377  xsTxt: Array<string>;
378}
379
380export class RangeRuler extends Graph {
381  public rangeRect: Rect;
382  public markA: Mark;
383  public markB: Mark;
384  public range: TimeRange;
385  mouseDownOffsetX = 0;
386  mouseDownMovingMarkX = 0;
387  movingMark: Mark | undefined | null;
388  isMouseDown: boolean = false;
389  isMovingRange: boolean = false;
390  isNewRange: boolean = false;
391  markAX: number = 0;
392  markBX: number = 0;
393  isPress: boolean = false;
394  pressFrameId: number = -1;
395  currentDuration: number = 0;
396  centerXPercentage: number = 0;
397  animaStartTime: number | undefined;
398  animTime: number = 100;
399  p: number = 800;
400  private readonly notifyHandler: (r: TimeRange) => void;
401  private scale: number = 0;
402  //缩放级别
403  private scales: Array<number> = [
404    50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000, 50_000, 100_000, 200_000, 500_000, 1_000_000, 2_000_000,
405    5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000, 1_000_000_000, 2_000_000_000,
406    5_000_000_000, 10_000_000_000, 20_000_000_000, 50_000_000_000, 100_000_000_000, 200_000_000_000, 500_000_000_000,
407  ];
408  private _cpuUsage: Array<{ cpu: number; ro: number; rate: number }> = [];
409
410  constructor(
411    // @ts-ignore
412    canvas: OffscreenCanvas | undefined | null,
413    // @ts-ignore
414    c: OffscreenCanvasRenderingContext2D,
415    frame: Rect,
416    range: TimeRange,
417    notifyHandler: (r: TimeRange) => void
418  ) {
419    super(canvas, c, frame);
420    this.range = range;
421    this.notifyHandler = notifyHandler;
422    this.markA = new Mark(canvas, 'A', c, new Rect(range.startX, frame.y, 1, frame.height));
423    this.markB = new Mark(canvas, 'B', c, new Rect(range.endX, frame.y, 1, frame.height));
424    this.rangeRect = new Rect(range.startX, frame.y, range.endX - range.startX, frame.height);
425  }
426
427  set cpuUsage(value: Array<{ cpu: number; ro: number; rate: number }>) {
428    this._cpuUsage = value;
429    this.draw();
430  }
431
432  drawCpuUsage() {
433    let maxNum = Math.round(this._cpuUsage.length / 100);
434    let miniHeight = Math.round(this.frame.height / CpuStruct.cpuCount); //每格高度
435    let miniWidth = Math.ceil(this.frame.width / 100); //每格宽度
436    for (let i = 0; i < this._cpuUsage.length; i++) {
437      //cpu: 0, ro: 0, rate: 0.987620037556431
438      let it = this._cpuUsage[i];
439      this.c.fillStyle = ColorUtils.MD_PALETTE[it.cpu];
440      this.c.globalAlpha = it.rate;
441      this.c.fillRect(this.frame.x + miniWidth * it.ro, this.frame.y + it.cpu * miniHeight, miniWidth, miniHeight);
442    }
443  }
444
445  scaleSetting(): void {
446    let l20 = (this.range.endNS - this.range.startNS) / 20;
447    let min = 0;
448    let max = 0;
449    let weight = 0;
450    for (let index = 0; index < this.scales.length; index++) {
451      if (this.scales[index] > l20) {
452        if (index > 0) {
453          min = this.scales[index - 1];
454        } else {
455          min = 0;
456        }
457        max = this.scales[index];
458        weight = ((l20 - min) * 1.0) / (max - min);
459        if (weight > 0.243) {
460          this.scale = max;
461        } else {
462          this.scale = min;
463        }
464        break;
465      }
466    }
467    if (this.scale == 0) {
468      this.scale = this.scales[0];
469    }
470  }
471
472  draw(discardNotify: boolean = false): void {
473    this.c.clearRect(this.frame.x - markPadding, this.frame.y, this.frame.width + markPadding * 2, this.frame.height);
474    this.c.beginPath();
475    if (this._cpuUsage.length > 0) {
476      this.drawCpuUsage();
477      this.c.globalAlpha = 0;
478    } else {
479      this.c.globalAlpha = 1;
480    }
481    //绘制选中区域
482    this.drawRangeSelection();
483    if (this.notifyHandler) {
484      this.range.startX = this.rangeRect.x;
485      this.range.endX = this.rangeRect.x + this.rangeRect.width;
486      this.range.startNS = (this.range.startX * this.range.totalNS) / (this.frame.width || 0);
487      this.range.endNS = (this.range.endX * this.range.totalNS) / (this.frame.width || 0);
488      this.scaleSetting();
489      let tmpNs = 0;
490      let timeLineYu = this.range.startNS % this.scale;
491      let timeLineRealW = (this.scale * this.frame.width) / (this.range.endNS - this.range.startNS);
492      let timeLineStartX = 0;
493      if (this.range.xs) {
494        this.range.xs.length = 0;
495      } else {
496        this.range.xs = [];
497      }
498      if (this.range.xsTxt) {
499        this.range.xsTxt.length = 0;
500      } else {
501        this.range.xsTxt = [];
502      }
503      if (timeLineYu !== 0) {
504        let firstNodeWidth = ((this.scale - timeLineYu) / this.scale) * timeLineRealW;
505        timeLineStartX += firstNodeWidth;
506        tmpNs += timeLineYu;
507        this.range.xs.push(timeLineStartX);
508        this.range.xsTxt.push(ns2s(tmpNs));
509      }
510      while (tmpNs < this.range.endNS - this.range.startNS) {
511        timeLineStartX += timeLineRealW;
512        tmpNs += this.scale;
513        this.range.xs.push(timeLineStartX);
514        this.range.xsTxt.push(ns2s(tmpNs));
515      }
516      if (!discardNotify) {
517        this.notifyHandler(this.range);
518      }
519    }
520  }
521
522  private drawRangeSelection(): void {
523    this.c.fillStyle = '#ffffff';
524    this.rangeRect.x = this.markA.frame.x < this.markB.frame.x ? this.markA.frame.x : this.markB.frame.x;
525    this.rangeRect.width = Math.abs(this.markB.frame.x - this.markA.frame.x);
526    this.c.fillRect(this.rangeRect.x, this.rangeRect.y, this.rangeRect.width, this.rangeRect.height);
527    this.c.globalAlpha = 1;
528    this.c.globalAlpha = 0.5;
529    this.c.fillStyle = '#999999';
530    this.c.fillRect(this.frame.x, this.frame.y, this.rangeRect.x, this.rangeRect.height);
531    this.c.fillRect(
532      this.rangeRect.x + this.rangeRect.width,
533      this.frame.y,
534      this.frame.width - this.rangeRect.width,
535      this.rangeRect.height
536    );
537    this.c.globalAlpha = 1;
538    this.c.closePath();
539    this.markA.draw();
540    this.markB.draw();
541  }
542
543  mouseDown(ev: MouseEvent) {
544    let timeLineMouseDownX = ev.offsetX - (offsetLeft || 0);
545    let timeLineMouseDownY = ev.offsetY - (offsetTop || 0);
546    this.isMouseDown = true;
547    this.mouseDownOffsetX = timeLineMouseDownX;
548    if (this.markA.isHover) {
549      this.movingMark = this.markA;
550      this.mouseDownMovingMarkX = this.movingMark.frame.x || 0;
551    } else if (this.markB.isHover) {
552      this.movingMark = this.markB;
553      this.mouseDownMovingMarkX = this.movingMark.frame.x || 0;
554    } else {
555      this.movingMark = null;
556    }
557    if (this.rangeRect.containsWithPadding(timeLineMouseDownX, timeLineMouseDownY, 5, 0)) {
558      this.isMovingRange = true;
559      this.markAX = this.markA.frame.x;
560      this.markBX = this.markB.frame.x;
561    } else if (
562      this.frame.containsWithMargin(timeLineMouseDownX, timeLineMouseDownY, 20, 0, 0, 0) &&
563      !this.rangeRect.containsWithMargin(timeLineMouseDownX, timeLineMouseDownY, 0, markPadding, 0, markPadding)
564    ) {
565      this.isNewRange = true;
566    }
567  }
568
569  movingRange(maxX: number, x: number): void {
570    let result = x - this.mouseDownOffsetX;
571    let mA = result + this.markAX;
572    let mB = result + this.markBX;
573    if (mA >= 0 && mA <= maxX) {
574      this.markA.frame.x = mA;
575    } else if (mA < 0) {
576      this.markA.frame.x = 0;
577    } else {
578      this.markA.frame.x = maxX;
579    }
580    this.markA.inspectionFrame.x = this.markA.frame.x - markPadding;
581    if (mB >= 0 && mB <= maxX) {
582      this.markB.frame.x = mB;
583    } else if (mB < 0) {
584      this.markB.frame.x = 0;
585    } else {
586      this.markB.frame.x = maxX;
587    }
588    this.markB.inspectionFrame.x = this.markB.frame.x - markPadding;
589    requestAnimationFrame(() => this.draw());
590  }
591
592  mouseUp(ev: MouseEvent) {
593    this.isMouseDown = false;
594    this.isMovingRange = false;
595    this.isNewRange = false;
596    this.movingMark = null;
597  }
598
599  movingNewRange(maxX: number, x: number): void {
600    this.markA.frame.x = this.mouseDownOffsetX;
601    this.markA.inspectionFrame.x = this.mouseDownOffsetX - markPadding;
602    if (x >= 0 && x <= maxX) {
603      this.markB.frame.x = x;
604    } else if (x < 0) {
605      this.markB.frame.x = 0;
606    } else {
607      this.markB.frame.x = maxX;
608    }
609    this.markB.inspectionFrame.x = this.markB.frame.x - markPadding;
610    requestAnimationFrame(() => this.draw());
611  }
612
613  mouseMove(ev: MouseEvent) {
614    let x = ev.offsetX - (offsetLeft || 0);
615    let y = ev.offsetY - (offsetTop || 0);
616    this.centerXPercentage = x / (this.frame.width || 0);
617    if (this.centerXPercentage <= 0) {
618      this.centerXPercentage = 0;
619    } else if (this.centerXPercentage >= 1) {
620      this.centerXPercentage = 1;
621    }
622    let maxX = this.frame.width || 0;
623    if (this.markA.inspectionFrame.contains(x, y)) {
624      this.markA.isHover = true;
625    } else if (this.markB.inspectionFrame.contains(x, y)) {
626      this.markB.isHover = true;
627    } else {
628      this.markA.isHover = false;
629      this.markB.isHover = false;
630    }
631    if (this.movingMark) {
632      let result = x - this.mouseDownOffsetX + this.mouseDownMovingMarkX;
633      if (result >= 0 && result <= maxX) {
634        this.movingMark.frame.x = result;
635      } else if (result < 0) {
636        this.movingMark.frame.x = 0;
637      } else {
638        this.movingMark.frame.x = maxX;
639      }
640      this.movingMark.inspectionFrame.x = this.movingMark.frame.x - markPadding;
641      requestAnimationFrame(() => this.draw());
642    }
643    if (this.isMovingRange && this.isMouseDown) {
644      this.movingRange(maxX, x);
645    } else if (this.isNewRange) {
646      this.movingNewRange(maxX, x);
647    }
648  }
649
650  mouseOut(ev: MouseEvent) {
651    this.movingMark = null;
652  }
653
654  fillX() {
655    if (this.range.startNS < 0) this.range.startNS = 0;
656    if (this.range.endNS < 0) this.range.endNS = 0;
657    if (this.range.endNS > this.range.totalNS) this.range.endNS = this.range.totalNS;
658    if (this.range.startNS > this.range.totalNS) this.range.startNS = this.range.totalNS;
659    this.range.startX = (this.range.startNS * (this.frame.width || 0)) / this.range.totalNS;
660    this.range.endX = (this.range.endNS * (this.frame.width || 0)) / this.range.totalNS;
661    this.markA.frame.x = this.range.startX;
662    this.markA.inspectionFrame.x = this.markA.frame.x - markPadding;
663    this.markB.frame.x = this.range.endX;
664    this.markB.inspectionFrame.x = this.markB.frame.x - markPadding;
665  }
666
667  keyPressInW(): void {
668    let animW = () => {
669      if (this.scale === 50) return;
670      this.range.startNS += (this.centerXPercentage * this.currentDuration * 2 * this.scale) / this.p;
671      this.range.endNS -= ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale) / this.p;
672      this.fillX();
673      this.draw();
674      this.pressFrameId = requestAnimationFrame(animW);
675    };
676    this.pressFrameId = requestAnimationFrame(animW);
677  }
678  keyPressInS(): void {
679    let animS = () => {
680      if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return;
681      this.range.startNS -= (this.centerXPercentage * this.currentDuration * 2 * this.scale) / this.p;
682      this.range.endNS += ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale) / this.p;
683      this.fillX();
684      this.draw();
685      this.pressFrameId = requestAnimationFrame(animS);
686    };
687    this.pressFrameId = requestAnimationFrame(animS);
688  }
689  keyPressInA(): void {
690    let animA = () => {
691      if (this.range.startNS == 0) return;
692      let s = (this.scale / this.p) * this.currentDuration;
693      this.range.startNS -= s;
694      this.range.endNS -= s;
695      this.fillX();
696      this.draw();
697      this.pressFrameId = requestAnimationFrame(animA);
698    };
699    this.pressFrameId = requestAnimationFrame(animA);
700  }
701  keyPressInD(): void {
702    let animD = () => {
703      if (this.range.endNS >= this.range.totalNS) return;
704      this.range.startNS += (this.scale / this.p) * this.currentDuration;
705      this.range.endNS += (this.scale / this.p) * this.currentDuration;
706      this.fillX();
707      this.draw();
708      this.pressFrameId = requestAnimationFrame(animD);
709    };
710    this.pressFrameId = requestAnimationFrame(animD);
711  }
712  keyPress(ev: KeyboardEvent): void {
713    if (this.animaStartTime === undefined) {
714      this.animaStartTime = new Date().getTime();
715    }
716    let startTime = new Date().getTime();
717    let duration = startTime - this.animaStartTime;
718    if (duration < this.animTime) duration = this.animTime;
719    this.currentDuration = duration;
720    if (this.isPress) return;
721    this.isPress = true;
722    switch (ev.key.toLocaleLowerCase()) {
723      case 'w':
724        this.keyPressInW();
725        break;
726      case 's':
727        this.keyPressInS();
728        break;
729      case 'a':
730        this.keyPressInA();
731        break;
732      case 'd':
733        this.keyPressInD();
734        break;
735    }
736  }
737  keyUpInW(startTime: number): void {
738    let animW = () => {
739      if (this.scale === 50) return;
740      let dur = new Date().getTime() - startTime;
741      this.range.startNS += (this.centerXPercentage * 100 * this.scale) / this.p;
742      this.range.endNS -= ((1 - this.centerXPercentage) * 100 * this.scale) / this.p;
743      this.fillX();
744      this.draw();
745      if (dur < 300) {
746        requestAnimationFrame(animW);
747      }
748    };
749    requestAnimationFrame(animW);
750  }
751  keyUpInS(startTime: number): void {
752    let animS = () => {
753      if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return;
754      let dur = new Date().getTime() - startTime;
755      this.range.startNS -= (this.centerXPercentage * 100 * this.scale) / this.p;
756      this.range.endNS += ((1 - this.centerXPercentage) * 100 * this.scale) / this.p;
757      this.fillX();
758      this.draw();
759      if (dur < 300) {
760        requestAnimationFrame(animS);
761      }
762    };
763    requestAnimationFrame(animS);
764  }
765  keyUpInA(startTime: number): void {
766    let animA = () => {
767      if (this.range.startNS <= 0) return;
768      let dur = new Date().getTime() - startTime;
769      let s = (this.scale * 80) / this.p;
770      this.range.startNS -= s;
771      this.range.endNS -= s;
772      this.fillX();
773      this.draw();
774      if (dur < 300) {
775        requestAnimationFrame(animA);
776      }
777    };
778    animA();
779  }
780  keyUpInD(startTime: number): void {
781    let animD = () => {
782      if (this.range.endNS >= this.range.totalNS) return;
783      let dur = new Date().getTime() - startTime;
784      let s = (this.scale * 80) / this.p;
785      this.range.startNS += s;
786      this.range.endNS += s;
787      this.fillX();
788      this.draw();
789      if (dur < 300) {
790        requestAnimationFrame(animD);
791      }
792    };
793    animD();
794  }
795
796  keyUp(ev: KeyboardEvent): void {
797    this.animaStartTime = undefined;
798    this.isPress = false;
799    if (this.pressFrameId != -1) {
800      cancelAnimationFrame(this.pressFrameId);
801    }
802    let startTime = new Date().getTime();
803    switch (ev.key) {
804      case 'w':
805        this.keyUpInW.call(this, startTime);
806        break;
807      case 's':
808        this.keyUpInS.call(this, startTime);
809        break;
810      case 'a':
811        this.keyUpInA.call(this, startTime);
812        break;
813      case 'd':
814        this.keyUpInD.call(this, startTime);
815        break;
816    }
817  }
818}
819
820export class Flag {
821  x: number = 0;
822  y: number = 0;
823  width: number = 0;
824  height: number = 0;
825  time: number = 0;
826  color: string = '';
827  selected: boolean = false;
828  text: string = '';
829
830  constructor(
831    x: number,
832    y: number,
833    width: number,
834    height: number,
835    time: number,
836    color: string = '#999999',
837    selected = false
838  ) {
839    this.x = x;
840    this.y = y;
841    this.width = width;
842    this.height = height;
843    this.time = time;
844    this.color = color;
845    this.selected = selected;
846  }
847}
848