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