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