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 { BaseElement, element } from '../../../base-ui/BaseElement'; 17import { TimeRuler } from './timer-shaft/TimeRuler'; 18import { Rect } from './timer-shaft/Rect'; 19import { RangeRuler, TimeRange } from './timer-shaft/RangeRuler'; 20import { SlicesTime, SportRuler } from './timer-shaft/SportRuler'; 21import { procedurePool } from '../../database/Procedure'; 22import { Flag } from './timer-shaft/Flag'; 23import { info } from '../../../log/Log'; 24import { TraceSheet } from './base/TraceSheet'; 25import { SelectionParam } from '../../bean/BoxSelection'; 26import { type SpSystemTrace, CurrentSlicesTime } from '../SpSystemTrace'; 27import './timer-shaft/CollapseButton'; 28import { TimerShaftElementHtml } from './TimerShaftElement.html'; 29//随机生成十六位进制颜色 30export function randomRgbColor() { 31 let r = Math.floor(Math.random() * 255); 32 let g = Math.floor(Math.random() * 255); 33 let b = Math.floor(Math.random() * 255); 34 if (r * 0.299 + g * 0.587 + b * 0.114 < 192) { 35 let r16 = r.toString(16).length === 1 && r.toString(16) <= 'f' ? 0 + r.toString(16) : r.toString(16); 36 let g16 = g.toString(16).length === 1 && g.toString(16) <= 'f' ? 0 + g.toString(16) : g.toString(16); 37 let b16 = b.toString(16).length === 1 && b.toString(16) <= 'f' ? 0 + b.toString(16) : b.toString(16); 38 let color = '#' + r16 + g16 + b16; 39 return color; 40 } else { 41 randomRgbColor(); 42 } 43} 44 45export function ns2s(ns: number): string { 46 let one_second = 1_000_000_000; // 1 second 47 let one_millisecond = 1_000_000; // 1 millisecond 48 let one_microsecond = 1_000; // 1 microsecond 49 let nanosecond1 = 1000.0; 50 let result; 51 if (ns >= one_second) { 52 result = (ns / 1000 / 1000 / 1000).toFixed(1) + ' s'; 53 } else if (ns >= one_millisecond) { 54 result = (ns / 1000 / 1000).toFixed(1) + ' ms'; 55 } else if (ns >= one_microsecond) { 56 result = (ns / 1000).toFixed(1) + ' μs'; 57 } else if (ns > 0) { 58 result = ns.toFixed(1) + ' ns'; 59 } else { 60 result = ns.toFixed(1) + ' s'; 61 } 62 return result; 63} 64 65export function ns2UnitS(ns: number, scale: number): string { 66 let one_second = 1_000_000_000; // 1 second 67 let result; 68 if (scale >= 10_000_000_000) { 69 result = (ns / one_second).toFixed(0) + ' s'; 70 } else if (scale >= 1_000_000_000) { 71 result = (ns / one_second).toFixed(1) + ' s'; 72 } else if (scale >= 100_000_000) { 73 result = (ns / one_second).toFixed(2) + ' s'; 74 } else if (scale >= 10_000_000) { 75 result = (ns / one_second).toFixed(3) + ' s'; 76 } else if (scale >= 1_000_000) { 77 result = (ns / one_second).toFixed(4) + ' s'; 78 } else if (scale >= 100_000) { 79 result = (ns / one_second).toFixed(5) + ' s'; 80 } else { 81 result = (ns / one_second).toFixed(6) + ' s'; 82 } 83 return result; 84} 85 86export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: Rect): number { 87 if (endNS == 0) { 88 endNS = duration; 89 } 90 let xSize: number = ((ns - startNS) * rect.width) / (endNS - startNS); 91 if (xSize < 0) { 92 xSize = 0; 93 } 94 if (xSize > rect.width) { 95 xSize = rect.width; 96 } 97 return xSize; 98} 99 100@element('timer-shaft-element') 101export class TimerShaftElement extends BaseElement { 102 // @ts-ignore 103 offscreen: OffscreenCanvas | undefined; 104 isOffScreen: boolean = false; 105 public ctx: CanvasRenderingContext2D | undefined | null; 106 public canvas: HTMLCanvasElement | null | undefined; 107 public totalEL: HTMLDivElement | null | undefined; 108 public timeTotalEL: HTMLSpanElement | null | undefined; 109 public timeOffsetEL: HTMLSpanElement | null | undefined; 110 public collectGroup: HTMLDivElement | null | undefined; 111 public collect1: HTMLInputElement | null | undefined; 112 public loadComplete: boolean = false; 113 public collecBtn: HTMLElement | null | undefined; 114 rangeChangeHandler: ((timeRange: TimeRange) => void) | undefined = undefined; 115 rangeClickHandler: ((sliceTime: SlicesTime | undefined | null) => void) | undefined = undefined; 116 flagChangeHandler: ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined = 117 undefined; 118 flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined = undefined; 119 /** 120 * 离线渲染需要的变量 121 */ 122 dpr = window.devicePixelRatio || 1; 123 frame: Rect = new Rect(0, 0, 0, 0); 124 must: boolean = true; 125 hoverX: number = 0; 126 hoverY: number = 0; 127 canvasWidth: number = 0; 128 canvasHeight: number = 0; 129 _cpuUsage: Array<{ cpu: number; ro: number; rate: number }> = []; 130 protected timeRuler: TimeRuler | undefined; 131 protected _rangeRuler: RangeRuler | undefined; 132 protected _sportRuler: SportRuler | undefined; 133 private root: HTMLDivElement | undefined | null; 134 private _totalNS: number = 10_000_000_000; 135 private _startNS: number = 0; 136 private _endNS: number = 10_000_000_000; 137 private traceSheetEL: TraceSheet | undefined | null; 138 private sliceTime: SlicesTime | undefined | null; 139 public selectionList: Array<SelectionParam> = []; 140 public selectionMap: Map<string, SelectionParam> = new Map<string, SelectionParam>(); 141 142 get sportRuler(): SportRuler | undefined { 143 return this._sportRuler; 144 } 145 146 get rangeRuler(): RangeRuler | undefined { 147 return this._rangeRuler; 148 } 149 150 set cpuUsage(value: Array<{ cpu: number; ro: number; rate: number }>) { 151 info('set cpuUsage values :', value); 152 this._cpuUsage = value; 153 if (this._rangeRuler) { 154 this._rangeRuler.cpuUsage = this._cpuUsage; 155 } 156 } 157 158 get totalNS(): number { 159 return this._totalNS; 160 } 161 162 set totalNS(value: number) { 163 info('set totalNS values :', value); 164 this._totalNS = value; 165 if (this.timeRuler) this.timeRuler.totalNS = value; 166 if (this._rangeRuler) this._rangeRuler.range.totalNS = value; 167 if (this.timeTotalEL) this.timeTotalEL.textContent = `${ns2s(value)}`; 168 requestAnimationFrame(() => this.render()); 169 } 170 171 get startNS(): number { 172 return this._startNS; 173 } 174 175 set startNS(value: number) { 176 this._startNS = value; 177 } 178 179 get endNS(): number { 180 return this._endNS; 181 } 182 183 set endNS(value: number) { 184 this._endNS = value; 185 } 186 187 isScaling(): boolean { 188 return this._rangeRuler?.isPress || false; 189 } 190 191 reset(): void { 192 this.loadComplete = false; 193 this.totalNS = 10_000_000_000; 194 this.startNS = 0; 195 this.endNS = 10_000_000_000; 196 if (this._rangeRuler) { 197 this._rangeRuler.drawMark = false; 198 this._rangeRuler.range.totalNS = this.totalNS; 199 this._rangeRuler.markAObj.frame.x = 0; 200 this._rangeRuler.markBObj.frame.x = this._rangeRuler.frame.width; 201 this._rangeRuler.cpuUsage = []; 202 this.sportRuler!.flagList.length = 0; 203 this.sportRuler!.slicesTimeList.length = 0; 204 this.selectionList.length = 0; 205 this.selectionMap.clear(); 206 this._rangeRuler.rangeRect = new Rect(0, 25, this.canvas?.clientWidth || 0, 75); 207 this.sportRuler!.isRangeSelect = false; 208 this.setSlicesMark(); 209 } 210 this.removeTriangle('inverted'); 211 this.setRangeNS(0, this.endNS); 212 } 213 214 initElements(): void { 215 this.root = this.shadowRoot?.querySelector('.root'); 216 this.canvas = this.shadowRoot?.querySelector('.panel'); 217 this.totalEL = this.shadowRoot?.querySelector('.total'); 218 this.collect1 = this.shadowRoot?.querySelector('#collect1'); 219 this.timeTotalEL = this.shadowRoot?.querySelector('.time-total'); 220 this.timeOffsetEL = this.shadowRoot?.querySelector('.time-offset'); 221 this.collecBtn = this.shadowRoot?.querySelector('.time-collect'); 222 this.collectGroup = this.shadowRoot?.querySelector('.collect_group'); 223 this.collectGroup?.addEventListener('click', (e) => { 224 // @ts-ignore 225 if (e.target && e.target.tagName === 'INPUT') { 226 // @ts-ignore 227 window.publish(window.SmartEvent.UI.CollectGroupChange, e.target.value); 228 } 229 }); 230 procedurePool.timelineChange = (a: any) => this.rangeChangeHandler?.(a); 231 window.subscribe(window.SmartEvent.UI.TimeRange, (b) => this.setRangeNS(b.startNS, b.endNS)); 232 } 233 234 getRangeRuler() { 235 return this._rangeRuler; 236 } 237 238 connectedCallback(): RangeRuler | undefined { 239 if (this.canvas) { 240 if (this.isOffScreen) { 241 // @ts-ignore 242 this.offscreen = this.canvas.transferControlToOffscreen(); 243 return; 244 } else { 245 this.ctx = this.canvas?.getContext('2d', { alpha: true }); 246 } 247 } 248 if (this.timeTotalEL) this.timeTotalEL.textContent = ns2s(this._totalNS); 249 if (this.timeOffsetEL && this._rangeRuler) 250 this.timeOffsetEL.textContent = ns2UnitS(this._startNS, this._rangeRuler.getScale()); 251 const width = this.canvas?.clientWidth || 0; 252 const height = this.canvas?.clientHeight || 0; 253 this.setTimeRuler(width); 254 this.setSportRuler(width, height); 255 this.setRangeRuler(width); 256 } 257 258 private setTimeRuler(width: number): void { 259 if (!this.timeRuler) { 260 this.timeRuler = new TimeRuler(this, new Rect(0, 0, width, 20), this._totalNS); 261 } 262 this.timeRuler.frame.width = width; 263 } 264 265 private setSportRuler(width: number, height: number): void { 266 if (!this._sportRuler) { 267 this._sportRuler = new SportRuler( 268 this, 269 new Rect(0, 100, width, height - 100), 270 (hoverFlag, selectFlag) => { 271 this.flagChangeHandler?.(hoverFlag, selectFlag); 272 }, 273 (flag) => { 274 this.flagClickHandler?.(flag); 275 }, 276 (slicetime) => { 277 this.rangeClickHandler?.(slicetime); 278 } 279 ); 280 } 281 this._sportRuler.frame.width = width; 282 } 283 284 private setRangeRuler(width: number): void { 285 if (!this._rangeRuler) { 286 this._rangeRuler = new RangeRuler( 287 this, 288 new Rect(0, 25, width, 75), 289 { 290 slicesTime: { 291 startTime: null, 292 endTime: null, 293 color: null, 294 }, 295 scale: 0, 296 startX: 0, 297 endX: this.canvas?.clientWidth || 0, 298 startNS: 0, 299 endNS: this.totalNS, 300 totalNS: this.totalNS, 301 refresh: true, 302 xs: [], 303 xsTxt: [], 304 }, 305 (a) => { 306 if (this._sportRuler) { 307 this._sportRuler.range = a; 308 } 309 if (this.timeOffsetEL && this._rangeRuler) { 310 this.timeOffsetEL.textContent = ns2UnitS(a.startNS, this._rangeRuler.getScale()); 311 } 312 if (this.loadComplete) { 313 this.rangeChangeHandler?.(a); 314 } 315 } 316 ); 317 } 318 this._rangeRuler.frame.width = width; 319 } 320 321 setRangeNS(startNS: number, endNS: number): void { 322 info('set startNS values :' + startNS + 'endNS values : ' + endNS); 323 this._rangeRuler?.setRangeNS(startNS, endNS); 324 } 325 326 getRange(): TimeRange | undefined { 327 return this._rangeRuler?.getRange(); 328 } 329 330 updateWidth(width: number): void { 331 this.dpr = window.devicePixelRatio || 1; 332 this.canvas!.width = width - (this.totalEL?.clientWidth || 0); 333 this.canvas!.height = this.shadowRoot!.host.clientHeight || 0; 334 let oldWidth = this.canvas!.width; 335 let oldHeight = this.canvas!.height; 336 this.canvas!.width = Math.ceil(oldWidth * this.dpr); 337 this.canvas!.height = Math.ceil(oldHeight * this.dpr); 338 this.canvas!.style.width = oldWidth + 'px'; 339 this.canvas!.style.height = oldHeight + 'px'; 340 this.ctx?.scale(this.dpr, this.dpr); 341 this.ctx?.translate(0, 0); 342 this._rangeRuler!.frame.width = oldWidth; 343 this._sportRuler!.frame.width = oldWidth; 344 this.timeRuler!.frame.width = oldWidth; 345 this._rangeRuler?.fillX(); 346 this.render(); 347 } 348 349 documentOnMouseDown = (ev: MouseEvent): void => { 350 if ((window as any).isSheetMove) return; 351 this._rangeRuler?.mouseDown(ev); 352 }; 353 354 documentOnMouseUp = (ev: MouseEvent): void => { 355 if ((window as any).isSheetMove) return; 356 this._rangeRuler?.mouseUp(ev); 357 this.sportRuler?.mouseUp(ev); 358 }; 359 360 documentOnMouseMove = (ev: MouseEvent, trace: SpSystemTrace): void => { 361 trace.style.cursor = 'default'; 362 let x = ev.offsetX - (this.canvas?.offsetLeft || 0); // 鼠标的x轴坐标 363 let y = ev.offsetY; // 鼠标的y轴坐标 364 let findSlicestime = this.sportRuler?.findSlicesTime(x, y); // 查找帽子 365 if (!findSlicestime) { 366 // 如果在该位置没有找到一个“帽子”,则可以显示一个旗子。 367 this.sportRuler?.showHoverFlag(); 368 this._rangeRuler?.mouseMove(ev, trace); 369 if (this.sportRuler?.edgeDetection(ev)) { 370 this.sportRuler?.mouseMove(ev); 371 } else { 372 this.sportRuler?.mouseOut(ev); 373 } 374 } else { 375 this.sportRuler?.clearHoverFlag(); 376 this.sportRuler?.modifyFlagList(null); //重新绘制旗子,清除hover flag 377 } 378 }; 379 380 documentOnMouseOut = (ev: MouseEvent): void => { 381 this._rangeRuler?.mouseOut(ev); 382 this.sportRuler?.mouseOut(ev); 383 }; 384 385 documentOnKeyPress = (ev: KeyboardEvent, currentSlicesTime?: CurrentSlicesTime): void => { 386 if ((window as any).isSheetMove) return; 387 if ((window as any).flagInputFocus) return; 388 this._rangeRuler?.keyPress(ev, currentSlicesTime); 389 this.sportRuler?.clearHoverFlag(); 390 }; 391 392 documentOnKeyUp = (ev: KeyboardEvent): void => { 393 if ((window as any).isSheetMove) return; 394 if ((window as any).flagInputFocus) return; 395 this._rangeRuler?.keyUp(ev); 396 }; 397 398 disconnectedCallback(): void {} 399 400 firstRender = true; 401 402 lineColor(): string { 403 return window.getComputedStyle(this.canvas!, null).getPropertyValue('color'); 404 } 405 406 render(): void { 407 this.dpr = window.devicePixelRatio || 1; 408 if (this.ctx) { 409 this.ctx.fillStyle = 'transparent'; 410 this.ctx?.fillRect(0, 0, this.canvas?.width || 0, this.canvas?.height || 0); 411 this.timeRuler?.draw(); 412 this._rangeRuler?.draw(); 413 this._sportRuler?.draw(); 414 } else { 415 procedurePool.submitWithName( 416 `timeline`, 417 `timeline`, 418 { 419 offscreen: this.must ? this.offscreen : undefined, //是否离屏 420 dpr: this.dpr, //屏幕dpr值 421 hoverX: this.hoverX, 422 hoverY: this.hoverY, 423 canvasWidth: this.canvasWidth, 424 canvasHeight: this.canvasHeight, 425 keyPressCode: null, 426 keyUpCode: null, 427 lineColor: '#dadada', 428 startNS: this.startNS, 429 endNS: this.endNS, 430 totalNS: this.totalNS, 431 frame: this.frame, 432 }, 433 this.must ? this.offscreen : undefined, 434 (res: any) => { 435 this.must = false; 436 } 437 ); 438 } 439 } 440 441 modifyFlagList(flag: Flag | null | undefined): void { 442 this._sportRuler?.modifyFlagList(flag); 443 } 444 445 modifySlicesList(slicestime: SlicesTime | null | undefined): void { 446 this._sportRuler?.modifySicesTimeList(slicestime); 447 } 448 cancelPressFrame(): void { 449 this._rangeRuler?.cancelPressFrame(); 450 } 451 452 cancelUpFrame(): void { 453 this._rangeRuler?.cancelUpFrame(); 454 } 455 456 stopWASD(ev: any): void { 457 this._rangeRuler?.keyUp(ev); 458 } 459 460 drawTriangle(time: number, type: string): number | undefined { 461 return this._sportRuler?.drawTriangle(time, type); 462 } 463 464 removeTriangle(type: string): void { 465 this._sportRuler?.removeTriangle(type); 466 } 467 468 setSlicesMark( 469 startTime: null | number = null, 470 endTime: null | number = null, 471 shiftKey: null | boolean = false 472 ): SlicesTime | null | undefined { 473 let sliceTime = this._sportRuler?.setSlicesMark(startTime, endTime, shiftKey); 474 if (sliceTime && sliceTime != undefined) { 475 this.traceSheetEL?.displayCurrent(sliceTime); // 给当前pane准备数据 476 477 // 取最新创建的那个selection对象 478 let selection = this.selectionList[this.selectionList.length - 1]; 479 if (selection) { 480 selection.isCurrentPane = true; // 设置当前面板为可以显示的状态 481 //把刚刚创建的slicetime和selection对象关联起来,以便后面再次选中“跑道”的时候显示对应的面板。 482 this.selectionMap.set(sliceTime.id, selection); 483 this.traceSheetEL?.rangeSelect(selection); // 显示选中区域对应的面板 484 } 485 } 486 return sliceTime; 487 } 488 489 displayCollect(showCollect: boolean): void { 490 if (showCollect) { 491 this.collecBtn!.style.display = 'flex'; 492 } else { 493 this.collecBtn!.style.display = 'none'; 494 } 495 } 496 497 initHtml(): string { 498 return TimerShaftElementHtml; 499 } 500} 501