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